Files
graphify/tests/test_explain_cli.py
T
safishamsi 1fbc623271 fix(query): report real call-site lines + stop silent query truncation (benchmark)
Two correctness defects found in a head-to-head benchmark of 0.9.22.

Caller line numbers: explain/affected/get_neighbors/query printed the caller
node's def line for an incoming call, presented as a precise citation, so
click-through landed in the wrong place. The `calls` edge already carries the
true call-site line (engine.py sets it); every caller/relation listing now reads
the traversed edge's source_file:source_location, falling back to the node's own
line only when the edge lacks one.

Silent query truncation: rendered nodes were degree-ordered (a low-degree
definition node ranked last, cut first), the queried symbol wasn't guaranteed to
appear, and the truncation marker sat only at the end so silence read as absence.
Nodes are now ranked by hop distance from the seeds (deterministic), the seed the
question named is rendered first and never truncated, and a prominent TRUNCATED
notice at the top states shown/total counts and how to widen the budget. Also
rewires the seed-first ordering the renderer already supported — a branch merge
had silently dropped the `seeds=` argument, leaving it dead code.
2026-07-21 18:39:05 +01:00

153 lines
6.4 KiB
Python

"""Regression tests for `graphify explain` arrow direction (#853)."""
from __future__ import annotations
import json
import graphify.__main__ as mainmod
def _write_graph(tmp_path):
graph_data = {
"directed": False, "multigraph": False, "graph": {},
"nodes": [
{"id": "validate", "label": "validateSanitySession()",
"source_file": "server/sanity-validate-session.ts", "community": 0},
{"id": "create_patch", "label": "createPatchHandler()",
"source_file": "server/create-patch-handler.ts", "community": 0},
{"id": "create_edit", "label": "createEditHandler()",
"source_file": "server/create-edit-handler.ts", "community": 0},
{"id": "stable_stringify", "label": "stableStringify()",
"source_file": "shared/stringify.ts", "community": 0},
],
"links": [
{"source": "create_patch", "target": "validate",
"relation": "calls", "confidence": "EXTRACTED"},
{"source": "create_edit", "target": "validate",
"relation": "calls", "confidence": "EXTRACTED"},
{"source": "validate", "target": "stable_stringify",
"relation": "calls", "confidence": "EXTRACTED"},
],
}
p = tmp_path / "graph.json"
p.write_text(json.dumps(graph_data))
return p
def _run(monkeypatch, graph_path, label, capsys):
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(mainmod.sys, "argv",
["graphify", "explain", label, "--graph", str(graph_path)])
mainmod.main()
return capsys.readouterr().out
def test_callee_shows_callers_as_inbound(monkeypatch, tmp_path, capsys):
p = _write_graph(tmp_path)
out = _run(monkeypatch, p, "validateSanitySession", capsys)
assert "<-- createPatchHandler() [calls]" in out
assert "<-- createEditHandler() [calls]" in out
assert "--> stableStringify() [calls]" in out
assert "--> createPatchHandler() [calls]" not in out
assert "--> createEditHandler() [calls]" not in out
def test_caller_shows_callee_as_outbound(monkeypatch, tmp_path, capsys):
p = _write_graph(tmp_path)
out = _run(monkeypatch, p, "createPatchHandler", capsys)
assert "--> validateSanitySession() [calls]" in out
assert "<-- " not in out
def test_explain_source_file_path_prefers_file_level_node(monkeypatch, tmp_path, capsys):
source_file = "app/api/example/route.ts"
graph_data = {
"directed": False, "multigraph": False, "graph": {},
"nodes": [
{"id": "example_route_get", "label": "GET()",
"source_file": source_file, "source_location": "L42", "community": 0},
{"id": "example_route", "label": "route.ts",
"source_file": source_file, "source_location": "L1", "community": 0},
],
"links": [
{"source": "example_route", "target": "example_route_get",
"relation": "contains", "confidence": "EXTRACTED"},
],
}
p = tmp_path / "graph.json"
p.write_text(json.dumps(graph_data))
out = _run(monkeypatch, p, source_file, capsys)
assert "Node: route.ts" in out
assert "ID: example_route" in out
assert f"Source: {source_file} L1" in out
assert "Node: GET()" not in out
# --- work-memory overlay Lesson line ------------------------------------------
def _write_sidecar(tmp_path, nodes):
(tmp_path / ".graphify_learning.json").write_text(
json.dumps({"version": 1, "generated_at": "2026-06-01T00:00:00+00:00",
"nodes": nodes}),
encoding="utf-8",
)
def test_explain_shows_preferred_lesson_line(monkeypatch, tmp_path, capsys):
p = _write_graph(tmp_path)
_write_sidecar(tmp_path, {
"validate": {"status": "preferred", "score": 2.4, "uses": 3,
"label": "validateSanitySession()", "source_file": "",
"code_fingerprint": "", "provenance": []},
})
out = _run(monkeypatch, p, "validateSanitySession", capsys)
assert "Lesson: preferred source (start here) — 3 useful, score=2.4" in out
assert "code changed" not in out
def test_explain_shows_contested_and_stale_lesson(monkeypatch, tmp_path, capsys):
p = _write_graph(tmp_path)
# source_file points at a path that does not exist -> loader marks it stale.
_write_sidecar(tmp_path, {
"validate": {"status": "contested", "score": -0.1, "uses": 2, "neg": 1,
"verdict": "dead end", "label": "validateSanitySession()",
"source_file": "server/sanity-validate-session.ts",
"code_fingerprint": "deadbeef", "provenance": []},
})
out = _run(monkeypatch, p, "validateSanitySession", capsys)
assert "Lesson: contested (useful 2 / dead-end 1)" in out
assert "[code changed since — re-verify]" in out
def test_explain_no_lesson_line_for_unannotated_node(monkeypatch, tmp_path, capsys):
"""No sidecar => no Lesson line; output identical to pre-feature."""
p = _write_graph(tmp_path)
out = _run(monkeypatch, p, "validateSanitySession", capsys)
assert "Lesson:" not in out
def test_explain_connection_shows_call_site_line(monkeypatch, tmp_path, capsys):
"""BUG1: an explain connection shows the edge's call-SITE line (in the
caller's file), not the caller's def line."""
graph_data = {
"directed": False, "multigraph": False, "graph": {},
"nodes": [
{"id": "loader", "label": "load_state()",
"source_file": "apollo.py", "source_location": "L90", "community": 0},
{"id": "trans", "label": "transition_state()",
"source_file": "state.py", "source_location": "L56", "community": 0},
],
"links": [
{"source": "loader", "target": "trans", "relation": "calls",
"confidence": "EXTRACTED", "source_file": "apollo.py", "source_location": "L158"},
],
}
p = tmp_path / "graph.json"
p.write_text(json.dumps(graph_data))
out = _run(monkeypatch, p, "transition_state", capsys)
# The inbound caller line must cite the call site apollo.py:L158.
caller_line = next(l for l in out.splitlines() if "<-- load_state()" in l)
assert "apollo.py:L158" in caller_line, f"call site missing from: {caller_line!r}"
assert "apollo.py:L90" not in caller_line # never the caller's def line
# The queried node's own header still shows its def line (correct).
assert "state.py" in out and "L56" in out