Skip to content

Proposal: code_references tool — identifier-level reference lookup over the existing index #44

Description

@Platonenkov

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

  1. 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.
  2. Should limit default higher than search (references are cheap per-hit)?
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions