Skip to content

fix(mcp): resolve module-qualified symbol lookups (#173)#179

Merged
colbymchenry merged 1 commit into
mainfrom
fix/module-qualified-lookup-173
May 19, 2026
Merged

fix(mcp): resolve module-qualified symbol lookups (#173)#179
colbymchenry merged 1 commit into
mainfrom
fix/module-qualified-lookup-173

Conversation

@colbymchenry
Copy link
Copy Markdown
Owner

Summary

  • Treat :: as a token separator in the FTS5 query builder so stage_apply::run no longer collapses to the unsearchable stage_applyrun
  • Extend matchesSymbol to accept ::, ., and / qualifier syntaxes, fall back to file-path containment when qualifiedName doesn't carry the module hierarchy (Rust / Python), and strip Rust path prefixes (crate, super, self)
  • Stop falling through to fuzzy matches when a qualified lookup has no exact match — stage_apply::nonexistent_fn returns null instead of resolving to an unrelated rollback
  • 9 new tests in __tests__/symbol-lookup.test.ts covering ::, multi-level, crate::-prefixed, /-style, bare-name, and the unknown-qualifier case

Why

#173codegraph_callees stage_apply::run returned "not found" against a repo with 7-9 sibling Rust modules each exporting pub async fn run. The bare-name run lookup was reported to silently resolve to an unrelated run_due_tasks (already addressed by findAllSymbols aggregation in 0.7.x — kept and pinned by test).

Root causes:

Layer Problem Fix
FTS5 query builder (src/db/queries.ts) Strips : as a special char without splitting on ::, collapsing stage_apply::run to stage_applyrun Replace :: with whitespace before the strip pass
matchesSymbol (src/mcp/tools.ts) Only understood Parent.child (dot) qualifiers and required the qualifier to appear in qualifiedName Accept ::, ., /. Fall back to file-path containment so Rust file-level functions (stage_apply.rsstage_apply module) match. Strip Rust path prefixes (crate, super, self)
findSymbol (src/mcp/tools.ts) Fell through to fuzzy text match on a qualified miss Return null for qualified lookups with no qualifier-respecting match

End-to-end verification

Indexed https://github.com/kernex-dev/kernex-agent at the exact commit 7eb5d13 from the issue (59 files, 1,340 nodes):

findSymbol "stage_apply::run"        -> src/configurator/stage_apply.rs:43  (correct)
findSymbol "stage_detect::run"       -> src/configurator/stage_detect.rs:14 (correct)
findSymbol "stage_verify::run"       -> src/configurator/stage_verify.rs:36 (correct)
findSymbol "stage_apply::nonexistent_fn"  -> NOT FOUND (no fuzzy fallback)
findAllSymbols "run"                 -> 9 matches, with disambiguation note
findSymbol "render_and_write"        -> src/configurator/stage_apply.rs:177 (correct)

Test plan

  • npx vitest run __tests__/symbol-lookup.test.ts — 9 new tests
  • npm test — all 599 tests pass (zero regressions in pr19-improvements disambiguation tests, search-query-parser, mcp-initialize)
  • npm run build — clean tsc
  • End-to-end on the kernex-agent repo at the issue's reference commit (above)

Closes #173

🤖 Generated with Claude Code

`codegraph_callees stage_apply::run` (and `_node`, `_impact`, ...)
returned "not found" against a repo with 7-9 sibling Rust modules,
each exporting `pub async fn run`. Two underlying issues:

1. The FTS5 query builder stripped `:` as a special char without
   splitting on `::`, so `stage_apply::run` collapsed to the literal
   `stage_applyrun` which matches nothing. Treat `::` as whitespace
   before the strip step so both halves become FTS tokens.

2. `matchesSymbol` only understood `Parent.child` qualifiers and
   relied on `qualifiedName` carrying the module path. Rust file-
   level functions don't have their module name in `qualifiedName`
   (it's encoded in the file path instead), so even dot-style
   lookups failed. Accept `::`, `.`, `/` as separators; multi-level
   forms compose; Rust `crate::`/`super::`/`self::` prefixes get
   stripped before path matching. Fall back to file-path containment
   when the qualified-name suffix doesn't match — `stage_apply::run`
   matches a `run` in any file whose path has a `stage_apply` segment.

Also tightens the no-match branch: qualified lookups no longer fall
through to a fuzzy text match. `stage_apply::nonexistent_fn` returns
`null` instead of silently resolving to an unrelated `rollback` in
the same file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant