Repository navigation
Call-graph comparison tooling: reference contract + cache, triage, one-command check, explain, session hook (#3773–#3776, #3778) - #3793
Conversation
…e-command check, explain, session hook (#3773-#3776, #3778) Epic #3772's infrastructure, so each new language costs an adapter, not a week. - callgraph_refs.py (#3773): one JSON contract for every reference (defs, edges with their first call site, optional external), a registry (python/ pyan3 2.8.1 on language-crucible, typescript/tsc 6.0.2 on the pinned import-graph repos), and a cache keyed on the corpus repo's git tree, the tool version and the adapter's own code. call_graph_resolution.py scores any registered language through it; python and typescript numbers are unchanged (99.8/51.6/97.5 and 99.9/57.1/73.7). zod's reference loads in ~1 s cached vs ~20 s built. - callgraph_triage.py (#3774): every missed reference edge in exactly one bucket (not_extracted/in_string, after_nested_def, same_name, caller_unmapped, ambiguous/<step>, resolved_outside, ...) and every confident link's verdict, ranked, with source lines; the buckets add up to the recall gap exactly. Its first run on zod found #3787 (spread calls), #3788 (namespace-alias imports) and #3789 (package self-imports). - callgraph_check.py (#3775): gates, focused tests, crucible (both modes when tiktoken's encoding is reachable, zero otherwise, and it says why), tree-sitter audit and lint audits, one line per step, logs on disk. A missing tool or pin is MISSING and fails; --regenerate only after a pass. - explain_call.py (#3776): re-runs the real resolver on a master DB's own rehydrated inputs, checks it against the recorded rows, and lists every candidate with def_shape and reach. It found #3786 (delta scans lose class inheritance), which it works around locally. - .claude/hooks/session-start.sh (#3778): web sessions get CI's pins (pytest, mypy+stubs, ruff 0.16.0, pyan3, typescript 6.0.2, ML deps), both corpora, the ast-accuracy tag, and a PATH that prefers this interpreter's tools over uv shims while keeping the right node. tiktoken is installed only when its encoding host is reachable (#3791). 29 s first start, 6 s after. - ts_callgraph.js now reports each edge's first call site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbvXUJpva5XxxbssoKG9Jt
node_env moved to callgraph_refs; the alias only served the gate test, which now calls callgraph_refs.node_env directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbvXUJpva5XxxbssoKG9Jt
|
Failing: Why it isn't this PR's:
Root cause: on Windows the master DB stores
Proposed patch (test-only): look the file up separator-agnostically in def _cached(cache, rel):
"""ram_cache is keyed by the DB's file_path, which is OS-native (`\\` on Windows)."""
(node,) = [v for k, v in cache.items() if k.replace("\\", "/") == rel]
return node
# test_open_sites_survive_a_delta_scan
rows = {d["dd_name"]: d["open_sites"] for d in _cached(cache, "src/LEDGER.cbl")["dataset_bindings"]}
# test_a_baseline_without_open_sites_still_rehydrates
assert all("open_sites" not in d for d in _cached(cache, "src/LEDGER.cbl")["dataset_bindings"])The alternative is normalizing the keys in the rehydrator itself. That's an engine change, and galaxyscope's delta-scan comparison, which uses native paths on Windows, would need to agree with it. So it's better as its own PR if you want it. A re-run wouldn't change the result: this is deterministic on Windows. I haven't pushed the patch here, to keep this PR to tooling. I can add it to this PR or open it separately, whichever you prefer. Generated by Claude Code |
Summary
This is the infrastructure half of epic #3772. Closes #3773, closes #3774, closes #3775, closes #3776, closes #3778.
The TypeScript round (#3756–#3767) spent its time in five places:
This PR addresses each of those except the serial fixes. It's tooling and docs only; no engine code changes.
tests/tools/callgraph_refs.pydefs,edgeswith the first call site, optionalexternal). A registry holds python (pyan3 2.8.1, language-crucible) and typescript (tsc 6.0.2, pinned import-graph repos). A cache is keyed on the corpus repo's git tree, the tool version and the adapter's own code.call_graph_resolution.pynow scores any registered language through it.tests/tools/callgraph_triage.pycaller_unmapped,not_extracted/{in_string,after_nested_def,same_name,other},ambiguous/<step>,resolved_outside, …) and gives every confident link a precision verdict. Buckets are ranked, with source lines, and must add up to the recall gap (checked at runtime).tests/tools/callgraph_check.pyMISSINGand fails. It chooses the crucible mode by whether tiktoken's encoding is reachable, and says why.--regeneraterewrites baselines only after a full pass.tests/tools/explain_call.pyfcall_datarows. For each callee it shows the step, the qualifiers tried, and every candidate with owner,def_shapeand whether a bare call or a receiver can reach it..claude/hooks/session-start.sh+settings.jsonv2.4.7tag the AST audit needs. It also writes a session PATH that prefers this interpreter's tools over uv shims but keeps the currentnode.ts_callgraph.jsnow reports each edge's first call site, and the workflow'spaths:includescallgraph_refs.py.Measured
callgraph_check.pyon this branch, under the hook's environment, printed 11 lines:What the tools found while being built (filed, not fixed here)
...f()records qualifier<expr>, so a bare call resolves as an untyped receiver and finds nothing #3787: a spread call...f()records qualifier<expr>and resolves to nothing. 51 edges on zod.ns.f()misses when the namespace import's alias differs from the file name (import * as processors from "./json-schema-processors.js") #3788:ns.f()misses when the namespace alias differs from the file stem (import * as processors from "./json-schema-processors.js"). 53 edges by triage's count, not yet checked against source.from "zod/v4") resolve nowhere, costing ~130 call links on zod #3789: a package importing itself by name (from "zod/v4") resolves nowhere. 133 edges, the largest single cause.inheritance_parentsbut the resolver readsinheritance#3786: delta scans lose class inheritance. The rehydrator restoresinheritance_parentsand the resolver readsinheritance.explain_call.pyworks around it locally and cites the issue.import gitgalaxy.core.detectorfail instead of falling back to Zero-Dependency Mode. The session hook avoids it by installing tiktoken only when the host is reachable.Type of change
gitgalaxy/core/detector.py,language_standards.py,prism.py, or a per-language rule)CI checklist
ruff_audit.py,dead_key_audit.pyandmypy_audit.py(--ci) pass, viacallgraph_check.py.ruff format --checkis clean on the 7 touched Python files. Under the hook's PATH, mypy is the interpreter's own with stubs, so the four yaml-stub findings reported in earlier PRs no longer appear.tests/tools/test_callgraph_tools.py(16 new tests, including an end-to-end scan of a tiny git repo against a registered fake reference, with no node or pyan3 needed) andtest_call_graph_resolution_gate.py: 21 passed, run as plainpytestfrom/tmp(CI's invocation). The focused set is 290 passed.crucible_check.py: zero-dependency PASS. Full precision can't run here, because this environment blocks tiktoken's encoding host, so CI'scrucible-audit (full-precision)is the check for that half. No engine code changed, so no drift is expected.tree_sitter_accuracy_audit.py --ci --all: 30 languages OK.Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01AbvXUJpva5XxxbssoKG9Jt
Generated by Claude Code