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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions