Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,11 @@ def _path_score(target_path: str, qnorm: str) -> float:

async def search_paths_single(ctx: Any, query: str, limit: int) -> list[dict]:
"""Path search against one repo context. Returns ranked file result dicts."""
qnorm = query.strip().lower().replace("\\", "/")
# Path mode is substring matching, so boundary ``*?`` markers carry no
# information and are stripped. Mid-string globs (``src/*/main.py``) are
# left alone: a substring match cannot honour them, so stripping there
# would change the query.
qnorm = query.strip().lower().replace("\\", "/").strip("*?")
if not qnorm:
return []

Expand Down
50 changes: 50 additions & 0 deletions tests/unit/server/mcp/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,56 @@ async def _seed_page(page_id, target_path, page_type="file_page"):
await session.commit()


@pytest.mark.asyncio
@pytest.mark.parametrize("glob", ["*", "?"])
async def test_path_search_accepts_trailing_glob(setup_mcp, glob):
"""A trailing glob narrows a path search without becoming a literal."""
import types

import repowise.server.mcp_server as mcp_mod
from repowise.server.mcp_server.tool_search_symbols import search_paths_single

await _seed_page("file_page:tool_overview.py", "packages/server/tool_overview.py")
await _seed_page("file_page:tool_answer.py", "packages/server/tool_answer.py")

ctx = types.SimpleNamespace(
session_factory=mcp_mod._session_factory,
path="/tmp/test-repo",
)
result = await search_paths_single(ctx, f"tool_overview{glob}", limit=10)

assert [item["file"] for item in result] == ["packages/server/tool_overview.py"]


@pytest.mark.asyncio
@pytest.mark.parametrize(
("query", "expected"),
[
("tool_overview.py", ["packages/server/tool_overview.py"]),
(" *tool_overview? ", ["packages/server/tool_overview.py"]),
("*", []),
("?", []),
("tool_*_overview", []),
],
)
async def test_path_search_preserves_non_trailing_glob_behavior(setup_mcp, query, expected):
"""Only boundary glob markers are supported by path-mode substring search."""
import types

import repowise.server.mcp_server as mcp_mod
from repowise.server.mcp_server.tool_search_symbols import search_paths_single

await _seed_page("file_page:tool_overview.py", "packages/server/tool_overview.py")

ctx = types.SimpleNamespace(
session_factory=mcp_mod._session_factory,
path="/tmp/test-repo",
)
result = await search_paths_single(ctx, query, limit=10)

assert [item["file"] for item in result] == expected


class TestDecisionDownweight:
"""Decision records must not crowd file pages out of the top ranks."""

Expand Down