fix(query): name the graph the answer came from (#2789) - #2808
fix(query): name the graph the answer came from (#2789)#2808abhay-codes07 wants to merge 1 commit into
Conversation
graphify-out/ resolves against the CWD. Running `graphify query` from a parent
project while thinking about a vendored subproject answers from the parent's
graph, and the answer is well-formed and confidently wrong: nothing in the output
named the graph file, the scan root, or the node count. The reporter only caught
it because the community labels were ones they had not assigned in that project.
The header now leads with the graph and its size:
Graph: graphify-out/graph.json (355 nodes) | Traversal: BFS depth=2 | ...
Shown relative to the CWD when the graph sits underneath it -- the ordinary case,
and short -- and absolute when it does not, which is exactly the case worth
noticing:
Graph: C:/other/proj/graphify-out/graph.json (3178 nodes) | Traversal: ...
The node count travels with the path because "355 nodes" against "3178 nodes" is
often the first thing that looks wrong, and it is deliberately the corpus size
rather than the traversal's `N nodes found`; conflating the two would hide the
mismatch this exists to surface.
`graph_path` is optional and the header is byte-identical when it is not passed,
so nothing that calls _query_graph_text without it changes. Both call sites that
do know their path -- the CLI `query` command and the MCP query tool -- now pass
it, and both already had the resolved path on hand for querylog.
_display_graph_path is a display helper and swallows resolution failures: it must
never be the reason a query fails.
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds a Graph: line to the query header naming the source graph file and its node count, via a new _display_graph_path helper in serve.py (relative under CWD, absolute otherwise, always POSIX). Threads graph_path through _query_graph_text and passes it from both the CLI dispatch_command and the MCP _build_server call sites. Covers the helper and header behavior in a new tests/test_query_names_its_graph.py.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 527 functions depend on the 179 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 117 callees - new:
_query_graph_text()— 20 callers, 9 callees - new:
_score_query()— 15 callers, 5 callees - new:
_query_terms()— 17 callers, 3 callees - new:
run_benchmark()— 16 callers, 3 callees - new:
_stale_graph_sources()— 7 callers, 6 callees - new:
_build_server()— 2 callers, 16 callees - new:
_load_graph()— 9 callers, 3 callees - …and 11 more — each is listed as a finding
Verification — 527 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 484 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly ImportError — names the real obstacle, not a sampling gap)
No difference found (not proven): No behavior difference found in \_query\_graph\_text (not a proof).
The verifier ran both versions of \_query\_graph\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 grounded finding(s) anchored inline below; 18 more finding(s) on lines outside this diff (see the check run).
| return str(graph_path) | ||
|
|
||
|
|
||
| def _query_graph_text( |
There was a problem hiding this comment.
_query_graph_text()
fans out to 9 callees (efferent coupling); 20 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Fixes #2789.
Thanks @cdtatarevich — "confident, well-formed answer from the wrong corpus" is the right framing, and it is what made this worth fixing rather than documenting.
The change
The query header now leads with the graph it opened, and its size:
Relative to the CWD when the graph sits underneath it — the ordinary case, and short enough not to be noise. Absolute when it does not, which is exactly the case worth noticing:
Two details that are deliberate rather than incidental:
The count is the corpus, not the traversal. The line already ends with
N nodes found, which is the traversal result. The number next to the path is the whole graph, because your report's tell was a 355-node subproject answering as a 3178-node parent. Reusing the traversal count would have hidden precisely that.Absolute is the loud form. Elsewhere in the codebase absolute host paths are something to avoid (#2598, #2628) because they get written into artifacts and travel between machines. This is terminal output for the operator standing in a directory, so the reasoning inverts: a path outside the CWD should look different from one inside it.
Scope
graph_pathis optional and the header is byte-identical when it is not passed, so no existing caller changes behaviour. The two call sites that do know their path — the CLIquerycommand and the MCPquerytool — now pass it, and both already had the resolved path in hand forquerylog.log_query(corpus=...), so nothing new is computed.I did not add a
--graph-mismatch warning or makequerysearch upward for a nearergraphify-out/. Both were tempting, but changing which graph is opened is a behavioural decision with its own failure modes, and the issue's actual complaint is that the choice was invisible. Making it visible is the smaller, complete fix; happy to follow up on resolution if you want it._display_graph_pathswallows resolution failures and falls back to the string it was handed — a display helper must never be the reason a query fails, and there is a test feeding it null bytes and 300-character garbage.Tests
tests/test_query_names_its_graph.py(9 tests): the relative and absolute forms, POSIX separators on both platforms, the unchanged header when no path is supplied, the corpus-vs-traversal count distinction, hostile input, and an end-to-end check that a parent project and a vendored subproject produce headers you can tell apart — the exact scenario from the report.Validation
Windows 11, Python 3.12, branched off
4fca621(0.9.44).20 failed, 4469 passed->20 failed, 4478 passed. Identical failure set — no regressions; the +9 are the new tests.