Summary
The cross-file calls-edge resolver misattributes calls to the wrong function when two files define a same-named helper and only one of them is actually imported by the caller. Found while auditing a /graphify . run over this repo's own graphify/ source tree — one of the reported "god nodes" (_make_id(), 54 edges) turned out to include 27 misattributed edges.
Root cause
There are two independent definitions of _make_id:
graphify/extractors/base.py:54 — def _make_id(*parts): return make_id(*parts)
graphify/mcp_ingest.py:379 — def _make_id(*parts): return _shared_make_id(*parts) (comment: "kept local; mirror extract.py shape")
graphify/extract.py imports _make_id only from graphify.extractors.base (line 31: from graphify.extractors.base import (..., _make_id, ...)) and defines no local override. All 84 _make_id(...) call sites inside extract.py should therefore resolve to extractors/base.py's _make_id.
Instead, the built graph attributes 27 of those calls — from functions including extract(), extract_astro(), extract_csproj(), extract_lazarus_package(), extract_python_rationale(), extract_slnx(), extract_spock_fallback(), extract_svelte() — to mcp_ingest.py's _make_id instead, and marks these edges EXTRACTED (the highest confidence tier), i.e. confidently wrong rather than a low-confidence guess.
Meanwhile, the 52 correctly-resolved cross-file callers of extractors/base.py's _make_id (from the various language extractors, e.g. extract_apex(), extract_bash(), extract_dm(), extract_elixir()) are all marked INFERRED — so the resolver is inconsistent even on the correctly-resolved side (see the separate, likely-related observation below).
Repro
graphify . (or a scoped subfolder) on a checkout of Graphify-Labs/graphify itself, targeting graphify/ as the corpus.
- Inspect
graphify-out/graph.json for edges where target == "mcp_ingest_make_id".
- Cross-reference each edge's
source node's source_file against that file's actual import statements.
- 27 of them (all functions defined in
extract.py) point at mcp_ingest_make_id despite extract.py never importing anything from mcp_ingest named _make_id.
import json
g = json.load(open("graphify-out/graph.json", encoding="utf-8"))
edges = g.get("links", g.get("edges", []))
callers = [e for e in edges if e.get("target") == "mcp_ingest_make_id" and e.get("relation") == "calls"]
print(len(callers)) # 27, all sourced from extract.py functions
Impact
- Silently wrong provenance for
calls edges whenever two files define a same-named helper and that name is called cross-file from a third location — the resolver appears to pick a candidate without checking the caller's actual import statement.
- Made worse by confidence labeling: the wrong edges are
EXTRACTED (implying certainty), while the correct edges to the same target function (from files that genuinely do import it) are INFERRED — so confidence tier doesn't correlate with correctness here.
- Scope: only affects codebases with duplicate function names across files where at least one of the duplicates is called from a third file (not just within the two defining files). In this repo, checked all 11 duplicate function names (
load_graph, _node_community_map, main, _strip_diacritics, _yaml_str, _git_head, install, _safe_filename, _make_id, _nfc, __getattr__) — _make_id was the only one with cross-file (third-party) callers, so it's the only one exhibiting the bug in this corpus. Likely to recur in any codebase with a similarly widely-used, ambiguously-named helper.
Suggested fix direction
When resolving a bare-name call to a symbol that has multiple same-named definitions across files, check the caller's actual import statements (from module import name) before falling back to a heuristic/positional match. If the caller's file has an unambiguous from X import name for that exact symbol, resolve to X's definition with EXTRACTED confidence rather than guessing.
Environment
- graphify package version: 0.9.48 (repo
pyproject.toml at time of testing)
- Corpus:
graphify/ subfolder of this repo (82 code files)
- Extraction mode: default (AST-only, code-only corpus, no LLM semantic pass involved — this is a pure structural/AST resolution bug, not an LLM extraction issue)
Summary
The cross-file
calls-edge resolver misattributes calls to the wrong function when two files define a same-named helper and only one of them is actually imported by the caller. Found while auditing a/graphify .run over this repo's owngraphify/source tree — one of the reported "god nodes" (_make_id(), 54 edges) turned out to include 27 misattributed edges.Root cause
There are two independent definitions of
_make_id:graphify/extractors/base.py:54—def _make_id(*parts): return make_id(*parts)graphify/mcp_ingest.py:379—def _make_id(*parts): return _shared_make_id(*parts)(comment: "kept local; mirror extract.py shape")graphify/extract.pyimports_make_idonly fromgraphify.extractors.base(line 31:from graphify.extractors.base import (..., _make_id, ...)) and defines no local override. All 84_make_id(...)call sites insideextract.pyshould therefore resolve toextractors/base.py's_make_id.Instead, the built graph attributes 27 of those calls — from functions including
extract(),extract_astro(),extract_csproj(),extract_lazarus_package(),extract_python_rationale(),extract_slnx(),extract_spock_fallback(),extract_svelte()— tomcp_ingest.py's_make_idinstead, and marks these edgesEXTRACTED(the highest confidence tier), i.e. confidently wrong rather than a low-confidence guess.Meanwhile, the 52 correctly-resolved cross-file callers of
extractors/base.py's_make_id(from the various language extractors, e.g.extract_apex(),extract_bash(),extract_dm(),extract_elixir()) are all markedINFERRED— so the resolver is inconsistent even on the correctly-resolved side (see the separate, likely-related observation below).Repro
graphify .(or a scoped subfolder) on a checkout ofGraphify-Labs/graphifyitself, targetinggraphify/as the corpus.graphify-out/graph.jsonfor edges wheretarget == "mcp_ingest_make_id".sourcenode'ssource_fileagainst that file's actualimportstatements.extract.py) point atmcp_ingest_make_iddespiteextract.pynever importing anything frommcp_ingestnamed_make_id.Impact
callsedges whenever two files define a same-named helper and that name is called cross-file from a third location — the resolver appears to pick a candidate without checking the caller's actual import statement.EXTRACTED(implying certainty), while the correct edges to the same target function (from files that genuinely do import it) areINFERRED— so confidence tier doesn't correlate with correctness here.load_graph,_node_community_map,main,_strip_diacritics,_yaml_str,_git_head,install,_safe_filename,_make_id,_nfc,__getattr__) —_make_idwas the only one with cross-file (third-party) callers, so it's the only one exhibiting the bug in this corpus. Likely to recur in any codebase with a similarly widely-used, ambiguously-named helper.Suggested fix direction
When resolving a bare-name call to a symbol that has multiple same-named definitions across files, check the caller's actual import statements (
from module import name) before falling back to a heuristic/positional match. If the caller's file has an unambiguousfrom X import namefor that exact symbol, resolve toX's definition withEXTRACTEDconfidence rather than guessing.Environment
pyproject.tomlat time of testing)graphify/subfolder of this repo (82 code files)