fix: promote cross-file call edges to EXTRACTED when import evidence exists

The cross-file call resolver in `extract()` unconditionally marked every
resolved call edge as INFERRED with confidence_score 0.8 — even when the
caller's file had an explicit `imports` (symbol) or `imports_from`
(module) edge to the callee. The new CJS require handler made this gap
visible: imports were correctly EXTRACTED but the call edges that those
imports backed remained INFERRED, so downstream consumers couldn't tell
high-evidence calls apart from name-match guesses.

This pass runs after the file-id remap (line 4736), so we relativize
node `source_file` paths before computing file_nids — otherwise the
caller's computed file_nid (absolute-path-derived) wouldn't match the
imports_from edge source (already remapped to relative form).

Promotion rule:
  - Symbol-level `imports` edge from caller's file -> callee node id
    => EXTRACTED, confidence_score 1.0
  - Module-level `imports_from` edge from caller's file -> callee's file
    => EXTRACTED, confidence_score 1.0
  - Otherwise => INFERRED, confidence_score 0.8 (existing behavior)

Validated on a 92-file CJS orchestrator: 5 previously-INFERRED edges
from runExecute() now resolve to EXTRACTED, and 88% of cross-file calls
in the corpus (104 of 118) promote, leaving INFERRED only for genuine
heuristic guesses with no import backing.

Adds two tests:
  - test_cross_file_call_promoted_to_extracted_with_import_evidence
  - test_cross_file_call_remains_inferred_without_import_evidence
This commit is contained in:
Mani Saint-Victor, MD
2026-05-06 13:13:30 -04:00
parent c902ae952b
commit 2dd6ee6a9c
3 changed files with 98 additions and 2 deletions
+49
View File
@@ -240,3 +240,52 @@ def test_extract_js_arrow_function_still_extracted():
assert "greet()" in labels
finally:
arrow_fixture.unlink()
def test_cross_file_call_promoted_to_extracted_with_import_evidence(tmp_path):
"""A cross-file `calls` edge must be EXTRACTED when the caller's file has
an `imports` or `imports_from` edge linking it to the callee."""
caller = tmp_path / "caller.js"
callee = tmp_path / "lib.js"
caller.write_text(
"const { doWork } = require('./lib');\n"
"function run() { doWork(); }\n"
)
callee.write_text(
"function doWork() { return 1; }\n"
"module.exports = { doWork };\n"
)
result = extract([caller, callee], cache_root=tmp_path)
nodes = {n["id"]: n for n in result["nodes"]}
call_edges = [
e for e in result["edges"]
if e["relation"] == "calls"
and nodes[e["source"]]["label"] == "run()"
and nodes[e["target"]]["label"] == "doWork()"
]
assert len(call_edges) == 1
assert call_edges[0]["confidence"] == "EXTRACTED"
assert call_edges[0]["confidence_score"] == 1.0
def test_cross_file_call_remains_inferred_without_import_evidence(tmp_path):
"""A cross-file `calls` edge must stay INFERRED when there is no import
edge — name collision alone is insufficient evidence."""
caller = tmp_path / "caller.js"
callee = tmp_path / "lib.js"
# Caller does NOT require lib — same-name function happens to exist elsewhere
caller.write_text("function run() { doUnique(); }\n")
callee.write_text(
"function doUnique() { return 1; }\n"
"module.exports = { doUnique };\n"
)
result = extract([caller, callee], cache_root=tmp_path)
nodes = {n["id"]: n for n in result["nodes"]}
call_edges = [
e for e in result["edges"]
if e["relation"] == "calls"
and nodes[e["source"]]["label"] == "run()"
and nodes[e["target"]]["label"] == "doUnique()"
]
assert len(call_edges) == 1
assert call_edges[0]["confidence"] == "INFERRED"