Skip to content

trace_path / reverse-dependency returns an INCOMPLETE caller set when a symbol is duplicated by an ambient .d.ts declaration (callers silently split by import style) #546

Closed

Description

@mingxingmeng-ctrl

Version

codebase-memory-mcp 0.8.1 (CLI)

Summary

When an exported symbol is defined in real source and also re-declared (signature only, no body) in an ambient .d.ts file, the indexer creates two distinct Function nodes for the same logical symbol. CALLS edges from consumers are then partitioned across the two nodes depending on how each consumer imports the symbol:

  • consumers importing via the path alias that the .d.ts types → edge points to the .d.ts stub node
  • consumers importing via the relative path to the real file → edge points to the implementation node

As a result, trace_path(name, mode=calls) resolves the name to one of the two nodes and returns only that node's callers, silently omitting the rest. There is no warning that the symbol is fragmented, so reverse-dependency / impact analysis under-reports consumers.

Environment (where this naturally occurs)

A TypeScript monorepo where an app consumes a workspace package through a tsconfig path alias, and the app ships a hand-written ambient declaration file to type that alias. This is a common pattern, so the duplicate-node split is easy to hit in real repos.

Minimal reproduction (synthetic)

repo/
  packages/widget/src/scroll.ts
      export function alignToEdge(el: HTMLElement): () => void { /* real impl */ }
  packages/widget/src/internalConsumer.ts
      import { alignToEdge } from './scroll'        // relative import
      alignToEdge(node)
  app/types/widget-shim.d.ts
      export function alignToEdge(el: HTMLElement): () => void   // signature only, no body
  app/src/externalConsumer.ts
      import { alignToEdge } from '@widget'         // path-alias import, typed by widget-shim.d.ts
      alignToEdge(node)
  # tsconfig paths: "@widget": ["packages/widget/src"]   (alias typed by the .d.ts shim)

Index the repo, then:

query_graph: MATCH (n {name:'alignToEdge'}) RETURN n.qualified_name
  -> 2 nodes: packages/widget/src/scroll.ts  AND  app/types/widget-shim.d.ts

trace_path(function_name='alignToEdge', mode='calls')
  -> returns callers of ONE node only (e.g. externalConsumer), missing internalConsumer

query_graph: MATCH (n)-[:CALLS]->(t {qualified_name:'<impl node>'}) RETURN n   -> internalConsumer
query_graph: MATCH (n)-[:CALLS]->(t {qualified_name:'<.d.ts node>'}) RETURN n  -> externalConsumer

Neither query alone yields the full {internalConsumer, externalConsumer} set.

Expected

Reverse-dependency / trace_path(mode=calls) for a logical symbol should return all of its callers, regardless of whether each caller imported via the real path or via an alias typed by an ambient .d.ts. Either:

  • a body-less ambient .d.ts declaration should not create a CALLS-target node separate from the implementation (merge / alias the two), or
  • trace_path and reverse queries should union nodes that share name + signature, or
  • at minimum, the result should flag that the symbol resolves to multiple nodes so callers know the set is partial.

Actual

Two separate Function nodes; callers partitioned by import style; trace_path returns a partial set with no indication it is incomplete.

Impact

This directly undercuts the impact-analysis / blast-radius use case the tool is built for: "who depends on this symbol if I change it?" returns a confidently partial answer, which is worse than no answer for change-safety decisions.

Question

Is the duplicate node from a body-less ambient .d.ts declaration intended? If so, is there a recommended query pattern to retrieve the unified caller set across alias-typed and real-path consumers? Happy to provide more detail.

Activity

  1. DeusData commented on Jun 22, 2026

    @DeusData
    Owner

    Hey, thx for noticing, will check ASAP

  2. added
    bugSomething isn't working
    parsing/qualityGraph extraction bugs, false positives, missing edges
    on Jun 23, 2026
  3. added a commit that references this issue on Jun 25, 2026
  4. added a commit that references this issue on Jun 27, 2026
    d03f564
  5. mvanhorn commented on Jun 27, 2026

    @mvanhorn
    Contributor

    Implemented in #650.

  6. DeusData commented on Jun 28, 2026

    @DeusData
    Owner

    This issue now has a first-class, durable reproduction in the cumulative bug-reproduction suite landing in #667 — a test that pins the exact failing behaviour, so it's tracked and guarded against silent regression. Where #667 also lands the root-cause fix, that's called out in the PR; otherwise the reproduction makes the eventual fix straightforward and verifiable.

    The reproduction goes live once #667 merges. (Posted from the release branch's QA pass.)

  7. added
    priority/normalStandard review queue; useful PR with ordinary maintainer urgency.
    on Jun 30, 2026
  8. DeusData commented on Jul 1, 2026

    @DeusData
    Owner

    Fixed on main in 3f754cf via bfs_union_same_name (src/mcp/mcp.c): trace_path now BFSs from every graph node sharing the resolved name and merges the results deduplicated by node id, so when a symbol is split across a real implementation and a body-less ambient .d.ts stub — with CALLS edges partitioned by import style — the full caller/callee set is returned instead of only one node's.

    A follow-up (#748) additionally hardens the surrounding resolution so that genuinely-different same-named functions are disambiguated rather than conflated, while the impl-plus-.d.ts-stub case here keeps unioning. Both are guarded by reproduce-first tests.

    Closing as completed. Thanks @mvanhorn for the precise root-cause report (and the independent fix in #650).

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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/normalStandard review queue; useful PR with ordinary maintainer urgency.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions