Skip to content

fix: trace_path returns full caller set across .d.ts-duplicated symbol nodes - #650

Closed
mvanhorn wants to merge 1 commit into
DeusData:mainfrom
mvanhorn:fix/546-trace-path-dts-duplicate-callers
Closed

mvanhorn wants to merge 1 commit into
DeusData:mainfrom
mvanhorn:fix/546-trace-path-dts-duplicate-callers

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

What does this PR do?

Root cause is in the trace_path handler in src/mcp/mcp.c: cbm_store_find_nodes_by_name returns all same-named nodes, then pick_resolved_node selects exactly one (sel) via node_resolution_score = label_rank*weight + (end_line - start_line). A body-less .d.ts declaration has span 0, so it always scores below the real implementation, loses the pick, is NOT flagged as a tie, and its callers are dropped — BFS runs only from nodes[sel].id. Fix per the issue's option 2 ("union nodes that share name + signature"): after pick_resolved_node chooses the canonical node, gather the sibling node ids that represent the same logical symbol — same name, callable label (Function/Method), and body-less span (start_line == end_line, the ambient-declaration signal) — and run the inbound/outbound BFS from each, merging cbm_traverse_result_t sets deduplicated by node id before serialization.

When an exported symbol is defined in real source AND re-declared (signature only, no body) in an ambient .d.ts file, the indexer creates two distinct callable nodes for the same logical symbol. CALLS edges from consumers get partitioned across the two nodes by import style: consumers importing via the tsconfig path alias point at the .d.ts stub node, consumers importing via the relative path point at the implementation node. trace_path(function_name, mode=calls) then resolves the name to a single node and returns only that node's callers, silently omitting the rest. Reverse-dependency / blast-radius analysis (the tool's core use case) thus returns a confidently partial answer with no warning.

Fixes #546

Checklist

  • Every commit is signed off (git commit -s) — required, CI rejects
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by a test (reproduce-first for bug fixes)

…l nodes

Fixes DeusData#546

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
@DeusData

Copy link
Copy Markdown
Owner

Huge thanks for opening this PR and for the work you put into it.

The maintainer shop is currently full, so this may sit for a bit before it gets a proper review. We will come back to this as soon as possible with real feedback; I wanted to make sure it did not sit unacknowledged in the meantime.

@DeusData DeusData added bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker. labels Jun 29, 2026
@DeusData

DeusData commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Thanks, this is nicely focused. Since it changes trace_path resolution and response behavior, please add a short compatibility note describing the new ambiguous/fragmented-symbol behavior and confirm existing clients still receive the current callers/callees fields unchanged.

@DeusData

DeusData commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Thank you @mvanhorn — this is a genuinely sharp piece of work, and your diagnosis of #546 is exactly right (a .ts impl + a body-less ambient .d.ts stub become two callable nodes, and trace_path traces only one → a partial caller set).

Two things happened in parallel. First, #546's under-report was independently fixed on main in 3f754cf (bfs_union_same_name) a few hours before this PR, which is why it now shows CONFLICTING — your trace_-prefixed helpers collide 1:1 with the landed bfs_union_same_name / pick_resolved_node / node_resolution_score, so it can't merge as-is.

Second — and this is the valuable part — reviewing your PR surfaced that the shipped fix has a real flaw your approach avoids: bfs_union_same_name unions every same-named node unconditionally, and pick_resolved_node only flagged ambiguity on an exact score tie. So two genuinely-different same-named functions with different body lengths were being over-unioned into one confidently-conflated caller set (the mirror of #546). Your key insight — a body-less .d.ts stub is a fragment to union, but 2+ real definitions are genuine ambiguity — is precisely the fix.

I've distilled exactly that into #748 (7d6d52b), co-authored with you, as a minimal gate on the existing code (2+ real callable defs → the existing disambiguation path; body-less stubs still union with their impl), guarded by two reproduce-first tests derived from your scenarios — one proving distinct symbols no longer conflate (RED→GREEN), one guarding that the .d.ts stub+impl union still works. Full suite green.

Closing this as superseded by the distilled follow-up, with full credit to you. The separate #546 issue is being closed as already-fixed by 3f754cf. Really appreciate the careful work. 🙏

@DeusData DeusData closed this Jul 1, 2026
timothybrush pushed a commit to timothybrush/codebase-memory-mcp that referenced this pull request Jul 1, 2026
The DeusData#546 fix (bfs_union_same_name) BFSs from every graph node sharing the
resolved name and merges the results, so a symbol split across a real
implementation and a body-less ambient .d.ts stub reports a complete caller
set. But pick_resolved_node flagged ambiguity only on an exact score tie, and
node_resolution_score adds the body span, so two genuinely-DIFFERENT functions
that share a name but differ in body length score differently, dodge the tie,
and get their caller sets unioned into one confidently-conflated answer -- the
mirror of the DeusData#546 under-report.

Complete the ambiguity rule: two or more *real* callable definitions sharing a
name (callable label + non-empty body span) are ambiguous and routed to the
existing disambiguation (snippet_suggestions) path. A body-less span==start node
is an ambient declaration / signature stub -- a fragment of one logical symbol
-- so a real implementation plus its .d.ts stub stays non-ambiguous and still
unions, preserving the DeusData#546 behavior.

Reproduce-first: tool_trace_call_path_distinct_defs_not_over_unioned (two
unrelated same-named functions with different spans -> merged caller set on the
old code = RED; ambiguous on the fix = GREEN) plus
tool_trace_call_path_dts_stub_unions_with_impl guarding that the stub+impl union
still works. Full suite green.

Distilled from DeusData#650 (thanks @mvanhorn); its trace_-prefixed reimplementation
collided with main's already-landed bfs_union_same_name and could not merge.

Refs DeusData#546, DeusData#650

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/high Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

Projects

None yet

2 participants