Motivation
The three-way benchmark (grep / code-index-mcp 0.2.2 / a structural LSP-backed search server) on a ~940-file C# repo showed our one documented blind spot costs real accuracy: code that participates in a flow by calling a symbol, without containing the query's terms, is invisible to both the vector and symbol branches. The structural competitor won on completeness largely because of find_references-style tools, not better ranking. README already documents this under Known limitations; this issue proposes the cheapest fix that stays inside our architecture.
Proposal
A new MCP tool code_references that answers: "where is this exact identifier mentioned?" — declarations excluded, call/usage sites included.
Index side
At chunking time we already tokenize every declaration's source text. Add an inverted identifier map built from identifier tokens only:
identifier (ordinal, case-sensitive) -> postings: [(chunk_id, line_numbers[])]
- Only C# identifier tokens (Roslyn syntax tokenizer, no semantic model, no compilation).
- Stored in the existing per-project cache dir, refreshed by the same file-hash incremental mechanism
code_search already uses (index refreshes before every call).
- Rough size: identifiers repeat heavily; postings are ints. Expect low tens of MB for a 7.4k-chunk project — acceptable next to the embedding matrix.
Query side
code_references(symbol, project?, limit = 20, path_filter?, include_declarations = false)
- Exact ordinal lookup, no fuzziness (that's
code_search's job).
- Each hit: relative path, line number, the single trimmed source line as context, owning chunk
id (for code_get_chunk follow-up), and a is_declaration flag; declaration sites of the same identifier are excluded by default but reported via the flag when included.
- Group hits by file, order files by hit count desc, then path; reuse
ResultDiversifier semantics only if one file floods the output (maxPerFile for references probably higher, e.g. 5).
Honest limitations (documented up front, same policy as the README rewrite)
- Name-based, not semantic: two unrelated symbols named
Send are indistinguishable. We report all matches and say so in the tool description — this is grep-accuracy at symbol granularity, positioned as a navigation aid, not resolution.
- No cross-project resolution, no inheritance/interface dispatch awareness.
- Razor generated code: references living in
.razor markup are found only where the raw file text contains the identifier (it usually does).
Why not full LSP/Roslyn semantics
Semantic FindReferences needs a compilation (MSBuildWorkspace, package restore, target frameworks) — heavy, slow to warm, fragile on partial checkouts, and duplicates what the competitor already does well. The identifier map is O(index) to build, O(1) to query, zero new dependencies, and closes most of the measured completeness gap: in the benchmark tasks, the misses were almost always "didn't find the call site", not "couldn't resolve overloads".
Acceptance sketch
- Unit: postings built/refreshed incrementally; declaration exclusion; ordinal case sensitivity; path_filter; limit semantics consistent with code_search (0 valid, negative rejected).
- Integration: on the benchmark corpus,
code_references(\"BalanceInUsd\")-style queries must return the known call sites that the benchmark's golden key lists and 0.2.2 missed.
- Payload discipline: default output must stay within the same size envelope as
code_search (one line of context per hit, no bodies).
Open questions
- Tool count is a cost for MCP clients (schema tokens per session). Alternative: a
mode: \"references\" parameter on code_search instead of a new tool. Leaning towards a separate tool — different contract, different output shape — but worth weighing.
- Should
limit default higher than search (references are cheap per-hit)?
- Store line text in postings vs re-read from disk at query time (staleness vs size) — leaning re-read + the existing
excerpt_may_be_stale mechanism.
Motivation
The three-way benchmark (grep / code-index-mcp 0.2.2 / a structural LSP-backed search server) on a ~940-file C# repo showed our one documented blind spot costs real accuracy: code that participates in a flow by calling a symbol, without containing the query's terms, is invisible to both the vector and symbol branches. The structural competitor won on completeness largely because of
find_references-style tools, not better ranking. README already documents this under Known limitations; this issue proposes the cheapest fix that stays inside our architecture.Proposal
A new MCP tool
code_referencesthat answers: "where is this exact identifier mentioned?" — declarations excluded, call/usage sites included.Index side
At chunking time we already tokenize every declaration's source text. Add an inverted identifier map built from identifier tokens only:
code_searchalready uses (index refreshes before every call).Query side
code_search's job).id(forcode_get_chunkfollow-up), and ais_declarationflag; declaration sites of the same identifier are excluded by default but reported via the flag when included.ResultDiversifiersemantics only if one file floods the output (maxPerFile for references probably higher, e.g. 5).Honest limitations (documented up front, same policy as the README rewrite)
Sendare indistinguishable. We report all matches and say so in the tool description — this is grep-accuracy at symbol granularity, positioned as a navigation aid, not resolution..razormarkup are found only where the raw file text contains the identifier (it usually does).Why not full LSP/Roslyn semantics
Semantic
FindReferencesneeds a compilation (MSBuildWorkspace, package restore, target frameworks) — heavy, slow to warm, fragile on partial checkouts, and duplicates what the competitor already does well. The identifier map is O(index) to build, O(1) to query, zero new dependencies, and closes most of the measured completeness gap: in the benchmark tasks, the misses were almost always "didn't find the call site", not "couldn't resolve overloads".Acceptance sketch
code_references(\"BalanceInUsd\")-style queries must return the known call sites that the benchmark's golden key lists and 0.2.2 missed.code_search(one line of context per hit, no bodies).Open questions
mode: \"references\"parameter oncode_searchinstead of a new tool. Leaning towards a separate tool — different contract, different output shape — but worth weighing.limitdefault higher than search (references are cheap per-hit)?excerpt_may_be_stalemechanism.