Repository navigation
Question: is there a path on which the JS/TS import gate (extract.py:6514) does not apply? #2837
Description
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-onlyResult: 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 theindirect_callpath. - 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 toEXTRACTED/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_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():
| 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):
continueThe 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.
Activity