Skip to content

fix(search): score symbols on the query's identifier, not the whole question#853

Merged
swati510 merged 1 commit into
mainfrom
fix/hybrid-symbol-query-identifier
Jul 15, 2026
Merged

fix(search): score symbols on the query's identifier, not the whole question#853
swati510 merged 1 commit into
mainfrom
fix/hybrid-symbol-query-identifier

Conversation

@swati510

Copy link
Copy Markdown
Collaborator

The bug

A hybrid query is prose wrapped around an identifier ("where is X defined").
Both symbol and hybrid mode handed the raw query to search_symbols_single,
but that scorer ranks on token overlap, so the question's prose tokens compete
as if they were symbol names.

Live, on this repo:

search_codebase("filter_dicts_by_key")
  -> mode=symbol, exact_match=true
     _helpers.py::filter_dicts_by_key at rank 1, score 160

search_codebase("where is filter_dicts_by_key defined")
  -> mode=hybrid, exact_match=false, plus the note:
     "No indexed symbol exactly matches 'filter_dicts_by_key'"

The claim is false. The symbol is indexed at _helpers.py:532. What ranked
instead was is_ci (matching the token "is") and FilterRegistry.get
(matching "filter"). The real symbol never entered the result window, so
_has_exact_symbol saw no match among the returned symbols and the response
asserted the symbol was absent from the index.

That is the worst shape of wrong: the tool is most confident exactly where it
is wrong, and an agent that believes "this symbol is not indexed" stops looking
for something that is right there.

The fix

_identifier_candidates already extracted the identifier at the call site
below, but only to compute the exact flag; it never reached the search. This
scores symbols on the extracted identifiers when the query is hybrid, and
leaves symbol mode (already a bare identifier) and identifier-free queries on
the raw text.

Testing

tests/unit/server/mcp/test_search.py: 42 passed.

The new test asserts the query the scorer receives rather than the resulting
ranking. A fixture small enough to unit-test carries no decoys for the
identifier to lose to, so an end-to-end assertion on rank passes against the
bug. Verified to fail without the change (symbol scorer got prose, not the identifier: ['where is AuthService defined']) and pass with it.

…uestion

A hybrid query is prose wrapped around an identifier ("where is X defined").
Both symbol and hybrid mode handed the raw query to `search_symbols_single`,
but that scorer ranks on token overlap, so the question's prose tokens
compete as if they were symbol names.

Live, on this repo:

    search_codebase("filter_dicts_by_key")
      -> mode=symbol, exact_match=true, `_helpers.py::filter_dicts_by_key` at rank 1

    search_codebase("where is filter_dicts_by_key defined")
      -> mode=hybrid, exact_match=false, and the note
         "No indexed symbol exactly matches 'filter_dicts_by_key'"

The claim is false: the symbol is indexed at `_helpers.py:532`. What ranked
instead was `is_ci` (matching the token "is") and `FilterRegistry.get`
(matching "filter"). The real symbol never entered the result window, so
`_has_exact_symbol` saw no match and the response asserted the symbol was
absent from the index.

That is the worst shape of wrong: the tool is most confident exactly where it
is wrong, and an agent that believes it stops looking for a symbol that is
right there.

`_identifier_candidates` already extracted the identifier at the call site
below, but only to compute the `exact` flag; it never reached the search.
This scores symbols on the extracted identifiers when the query is hybrid,
and leaves symbol mode (already a bare identifier) and identifier-free
queries on the raw text.

The test asserts the query the scorer receives rather than the resulting
ranking: a fixture small enough to unit-test carries no decoys for the
identifier to lose to, so an end-to-end assertion passes against the bug.
Verified to fail without the change and pass with it.
@repowise-bot

repowise-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

✅ Health: 7.6 (unchanged)

📋 At a glance
2 files changed health · 2 hotspots touched · 3 new findings introduced · 5 co-change pairs left out · 2 files with recent fix history.

Files & modules (2)
  • packages (1 file)
    • .../mcp_server/tool_search.py
  • tests (1 file)
    • .../mcp/test_search.py

🩹 Review priority (files here with the most recent bug-fix history — defects cluster, so review these first)

File Score Δ Why
.../mcp_server/tool_search.py 2.5 → 2.2 ▼ -0.2 🔻 introduced brain method, bumpy road, large method · ✅ resolved function hotspot, io in loop
.../mcp/test_search.py 5.7 → 5.8 ▲ +0.2 score drifted on re-parse — no PR-attributable biomarkers

💡 .../mcp_server/tool_search.py: Split this function. It carries high cyclomatic complexity and many dependents — extract cohesive responsibilities into helpers so each call site sees a smaller surface area.

🔎 More signals (2)

🔥 Hotspots touched (2)

  • .../mcp_server/tool_search.py — 14 commits/90d, 12 dependents · primary owner: Raghav Chamadiya (84%)
  • .../mcp/test_search.py — 7 commits/90d, 1 dependents · primary owner: Raghav Chamadiya (80%)

🔗 Hidden coupling (1 file)

  • .../mcp_server/tool_search.py co-changes with these files (not in this PR):
    • .../mcp_server/tool_overview.py (8× — 🟢 routine)
    • .../mcp_server/tool_symbol.py (7× — 🟢 routine)
    • .../mcp_server/tool_why.py (6× — 🟢 routine)
    • .../mcp_server/tool_dead_code.py (5× — 🟢 routine)
    • .../templates/claude_md.j2 (5× — 🟢 routine)

📊 Full report · ⭐ Star Repowise · 📥 Install bot · Last updated 2026-07-15 15:52 UTC
Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot

@swati510 swati510 merged commit b2cc874 into main Jul 15, 2026
7 checks passed
@swati510 swati510 deleted the fix/hybrid-symbol-query-identifier branch July 15, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants