What was observed
On a TypeScript corpus extracted with graphifyy 0.9.45 — cold full extract, no cache, graphify extract . --code-only, pure AST path — the graph contained 6 edges tagged INFERRED. Five of the six shared one shape:
- an unresolved short identifier bound cross-file to a same-named symbol,
- with no import statement between the two files,
- and in 3 of those 5 the target was a non-callable string constant.
I am not asserting a defect. I could not reproduce this on 0.9.46, and both halves of the shape appear to be already fixed. This is a mapped negative result and one question.
Could not reproduce on 0.9.46
repro/fileA.ts:
export const B = "some-string";
export function makeLabel(suffix: string): string {
return B + "-" + suffix;
}
repro/fileB.ts:
export function run(value: string): string {
return B(value);
}
B is called in fileB.ts, defined nowhere in it, imported nowhere in it.
graphify extract ./repro --code-only
Result: 5 nodes, 3 edges — all EXTRACTED, all contains. No calls or indirect_call edge from fileb_run to filea_b. The target node exists and carries no _callable marker, while filea_makelabel and fileb_run both carry "_callable": true.
Variants tried, all negative:
- Non-exported
const B — produces no node at all, so there is no candidate to bind to; exporting it is what puts a target in the graph.
- Target is a real exported function called cross-file with no import — still no edge, so the absence is not specific to non-callable targets.
- Call outside return position (
const out = B(value);).
- Lowercase and multi-letter identifiers, ruling out a single-uppercase-letter heuristic.
- Identifier referenced by name rather than called (
sink.add(B)), exercising the indirect_call path.
- Multi-file layout: three single-letter string constants exported from one file, called without import from a second file that carries a real decoy import from a third. 11 nodes, 10 edges, zero
INFERRED; the only cross-file edge is the genuinely imported call, promoted to EXTRACTED/1.0.
- C, Ruby, Rust, Python — in each, scalar constants do not become nodes, so no candidate exists and the case does not arise.
Reproduction environment: graphifyy 0.9.46 via uv tool install, macOS, Python 3.13, source read at branch v8 @ 558df6d.
Both halves appear already fixed
- #1659 — JS/TS cross-file calls gated on import evidence. Landed in 0.9.7.
- #2241 — false
indirect_call at INFERRED/0.8 binding to an unrelated single-letter helper. Fixed in 0.9.29.
Both predate 0.9.45, which is what makes the observation puzzling rather than actionable.
Resolver map
Live cross-file raw-call pass, inside extract():
| What |
Location |
| Raw-call loop |
extract.py:6346 |
| Candidate lookup by bare callee label |
extract.py:6380 onward |
| Ambiguous-candidate tie-breakers |
extract.py:6440–6476 |
callable_nids / class_nids construction |
extract.py:6265 / 6269 |
Import-evidence index (imports / imports_from) |
extract.py:6288–6296 |
Per-call import sets, _has_import_evidence() |
extract.py:6427–6428, 6430 |
indirect_call emission (callable-gated) |
extract.py:6488–6500 |
| JS/TS no-import gate |
extract.py:6514 |
Direct calls emission |
extract.py:6516–6537 |
The gate:
if not has_import_evidence and str(rc.get("source_file", "")).endswith(_JS_TS_CALL_SUFFIXES):
continue
The caller's import list is available at bind time via imported_symbols / imported_modules. Both raw-call emitters in extractors/engine.py (lines 4662 and 5223) always populate source_file, so the endswith check does not appear to fail open. Existing no-import coverage: tests/test_phantom_cross_package_call.py:36/46/59/80 and tests/test_extract.py:1124.
For reference when reading edge scores: 0.8 is a hard-coded literal at both no-import emitters (extract.py:6497 and 6526), while 0.5 is the INFERRED default backfilled by _CONFIDENCE_SCORE_DEFAULTS in export.py:160 for edges carrying no explicit score — so those two values come from different producers rather than from one scale.
The question
Is there a path on which the gate at extract.py:6514 does not apply, or on which a JS/TS cross-file call is emitted without passing through it?
One candidate I could not test: the incremental path noted at extract.py:6265 (#2438), where callable_nids is built by reading the _callable marker from persisted nodes rather than from a fresh parse. The observed run was a cold full extract with no cache, so that path was not exercised there and I have not exercised it here either.
symbol_resolution.py:307 (resolve_cross_file_raw_calls) emits INFERRED/0.8, takes no import information, and gates its label index on file_type == "code" rather than _callable — but git log -S finds no commit wiring it into extract.py, and tests/test_symbol_resolution.py is its only caller, so it does not appear to be reachable in a real run.
Happy to run further fixtures against any shape that would be useful.
What was observed
On a TypeScript corpus extracted with graphifyy 0.9.45 — cold full extract, no cache,
graphify extract . --code-only, pure AST path — the graph contained 6 edges taggedINFERRED. Five of the six shared one shape:I am not asserting a defect. I could not reproduce this on 0.9.46, and both halves of the shape appear to be already fixed. This is a mapped negative result and one question.
Could not reproduce on 0.9.46
repro/fileA.ts:repro/fileB.ts:Bis called infileB.ts, defined nowhere in it, imported nowhere in it.Result: 5 nodes, 3 edges — all
EXTRACTED, allcontains. Nocallsorindirect_calledge fromfileb_runtofilea_b. The target node exists and carries no_callablemarker, whilefilea_makelabelandfileb_runboth carry"_callable": true.Variants tried, all negative:
const B— produces no node at all, so there is no candidate to bind to; exporting it is what puts a target in the graph.const out = B(value);).sink.add(B)), exercising theindirect_callpath.INFERRED; the only cross-file edge is the genuinely imported call, promoted toEXTRACTED/1.0.Reproduction environment: graphifyy 0.9.46 via
uv tool install, macOS, Python 3.13, source read at branchv8@558df6d.Both halves appear already fixed
indirect_callat INFERRED/0.8 binding to an unrelated single-letter helper. Fixed in 0.9.29.Both predate 0.9.45, which is what makes the observation puzzling rather than actionable.
Resolver map
Live cross-file raw-call pass, inside
extract():extract.py:6346extract.py:6380onwardextract.py:6440–6476callable_nids/class_nidsconstructionextract.py:6265/6269imports/imports_from)extract.py:6288–6296_has_import_evidence()extract.py:6427–6428,6430indirect_callemission (callable-gated)extract.py:6488–6500extract.py:6514callsemissionextract.py:6516–6537The gate:
The caller's import list is available at bind time via
imported_symbols/imported_modules. Both raw-call emitters inextractors/engine.py(lines 4662 and 5223) always populatesource_file, so theendswithcheck does not appear to fail open. Existing no-import coverage:tests/test_phantom_cross_package_call.py:36/46/59/80andtests/test_extract.py:1124.For reference when reading edge scores:
0.8is a hard-coded literal at both no-import emitters (extract.py:6497and6526), while0.5is theINFERREDdefault backfilled by_CONFIDENCE_SCORE_DEFAULTSinexport.py:160for edges carrying no explicit score — so those two values come from different producers rather than from one scale.The question
Is there a path on which the gate at
extract.py:6514does not apply, or on which a JS/TS cross-file call is emitted without passing through it?One candidate I could not test: the incremental path noted at
extract.py:6265(#2438), wherecallable_nidsis built by reading the_callablemarker from persisted nodes rather than from a fresh parse. The observed run was a cold full extract with no cache, so that path was not exercised there and I have not exercised it here either.symbol_resolution.py:307(resolve_cross_file_raw_calls) emits INFERRED/0.8, takes no import information, and gates its label index onfile_type == "code"rather than_callable— butgit log -Sfinds no commit wiring it intoextract.py, andtests/test_symbol_resolution.pyis its only caller, so it does not appear to be reachable in a real run.Happy to run further fixtures against any shape that would be useful.