Repository navigation
Conversation
|
Thanks for the pull request, @krishhgg. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
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. PR-changed functions: 0/7 verified (0 proven, 0 may-equivalent, 0 distinguished) · 7 not verified (3 vacuous, 4 unsupported).
Not verified on this run: build\_merge (vacuous: never exercised), merge\_raw\_extraction (unsupported), \_prune\_graph\_json\_sources (unsupported), dispatch\_command (vacuous: never exercised), extract (vacuous: never exercised), \_extract\_generic (unsupported), \_rebuild\_code (unsupported).
Graphify review — findings
Adds Python constructor linking: when a Foo(...) call provably resolves to a project class, link_python_constructors walks the class's verified single-base chain and emits an INFERRED calls edge (context "constructor") to the nearest __init__, so whatever __init__ calls becomes reachable from the call site. If the chain breaks, a class defines __new__, or no __init__ exists, it adds nothing. build_merge and merge_raw_extraction strip existing constructor edges and relink them over the whole merged graph, _prune_graph_json_sources does the same when given a root, and the bumped _AST_CACHE_SCHEMA forces re-extraction so cached files pick up the new constructor facts.
Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4549 functions depend on the 1088 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 801 callers, 53 callees - new:
_rebuild_code()— 164 callers, 57 callees - new:
build_from_json()— 234 callers, 21 callees - new:
detect()— 121 callers, 16 callees - new:
build_merge()— 87 callers, 16 callees - new:
_extract_generic()— 18 callers, 37 callees - new:
save_semantic_cache()— 65 callers, 9 callees - new:
to_obsidian()— 41 callers, 14 callees - …and 116 more — each is listed as a finding
Verification — 4549 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: 4325 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
262 of 345 test file(s) selected (76%) via static blast radius.
tests/test_affected_cli.py— impacttests/test_agents_platform.py— impacttests/test_analyze.py— impacttests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_atomic_canvas_export.py— impacttests/test_atomic_writes.py— impacttests/test_benchmark.py— impacttests/test_benchmark_raw_graph.py— impacttests/test_blade_extractor.py— impacttests/test_build.py— impacttests/test_build_located_semantic_identity.py— impacttests/test_build_merge_dedup_scope.py— impacttests/test_build_merge_hyperedges_and_prune.py— impacttests/test_build_merge_shrink_guard.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_cache.py— impacttests/test_cache_stale_import_target.py— impacttests/test_carried_hyperedge_remap.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_charmap_encoding.py— impacttests/test_chunking.py— impacttests/test_cjs_module_extension.py— impacttests/test_claude_cli_backend.py— impacttests/test_cli_export.py— impacttests/test_cluster.py— impacttests/test_cobol_extractor.py— impacttests/test_codebuddy.py— impacttests/test_community_labels_skill.py— impacttests/test_confidence.py— impacttests/test_corrupt_graph_json.py— impacttests/test_cpp_method_declarations.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_tuple_type_refs.py— impacttests/test_csharp_type_resolution.py— impacttests/test_dedup.py— impact- … and 212 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Docs that may be stale (advisory)
ARCHITECTURE.md§ Calling extract() from your own code (lines 36-49): references changed symbolsresolveCHANGELOG.md§ 0.9.80 (2026-10-07) (lines 5-24): references changed symbolsresolveCHANGELOG.md§ 0.9.77 (2026-10-05) (lines 45-64): references changed symbols__init__CHANGELOG.md§ 0.9.75 (2026-10-04) (lines 84-104): references changed symbolsmergedCHANGELOG.md§ 0.9.73 (2026-09-30) (lines 121-136): references changed symbolslinkCHANGELOG.md§ 0.9.71 (2026-09-28) (lines 153-163): references changed symbolslinkCHANGELOG.md§ 0.9.69 (2026-09-26) (lines 174-184): references changed symbolslinkCHANGELOG.md§ 0.9.65 (2026-09-20) (lines 222-234): references changed symbolsresolveCHANGELOG.md§ 0.9.61 (2026-09-12) (lines 274-278): references changed symbolsresolveCHANGELOG.md§ 0.9.58 (2026-09-10) (lines 297-314): references changed symbols__init__,resolve
…and 10 more.
Formal verification
Could not verify: Could not verify build\_merge.
The verifier did not have enough to check build\_merge, 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: no capturable inputs from the test suite; property tier: not verifiable: all 224 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify merge\_raw\_extraction.
The verifier did not have enough to check merge\_raw\_extraction, 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: no capturable inputs from the test suite; property tier: parameter `prune_sources` is annotated `'list[str] | None'` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_prune\_graph\_json\_sources.
The verifier did not have enough to check \_prune\_graph\_json\_sources, 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: no capturable inputs from the test suite; property tier: not verifiable: the input domain has 83 values but only 11 distinct were tested — a small finite domain must be EXHAUSTED, not sampled (an untested input could invert the result)
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: no capturable inputs from the test suite; property tier: not verifiable: all 40 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 extract.
The verifier did not have enough to check extract, 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: no capturable inputs from the test suite; property tier: not verifiable: all 90 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, 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: no capturable inputs from the test suite; property tier: parameter `config` is annotated `LanguageConfig` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_rebuild\_code.
The verifier did not have enough to check \_rebuild\_code, 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: no capturable inputs from the test suite; property tier: nondeterministic output (both versions disagreed with themselves on the witness) — not a behaviour change; a function whose output is not a function of its inputs (random / time / uuid / os.urandom, or state carried across calls) cannot be soundly distinguished by execution, so this tier abstains for the whole function
· 4 grounded finding(s) anchored inline below; 120 more finding(s) on lines outside this diff (see the check run).
| ) | ||
|
|
||
|
|
||
| def link_python_constructors( |
There was a problem hiding this comment.
link_python_constructors()
fans out to 7 callees (efferent coupling); 6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return False | ||
|
|
||
|
|
||
| def _python_poisons_setattr(root_node, source: bytes) -> bool: |
There was a problem hiding this comment.
_python_poisons_setattr()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return ("other", node.start_point[0] + 1, name, "", _python_scope_of(node)) | ||
|
|
||
|
|
||
| def _python_name_uses(root_node, source: bytes) -> _PythonNameUses: |
There was a problem hiding this comment.
_python_name_uses()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return None | ||
| return kind, line, origin, module | ||
|
|
||
| def walk_calls( |
There was a problem hiding this comment.
walk_calls()
fans out to 29 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
eb52397 to
48c7275
Compare
`Foo(...)` was linked only to the class node, so nothing `__init__` calls was reachable from the call site in affected, query or path. Add an INFERRED `calls` edge (context "constructor") from the caller to the nearest `__init__` up the class chain, and keep the edge to the class. The edge is added only when static rules prove that `__init__` runs: the called name and every base are bound once in their files, by a class statement or an import that resolves to the defining file (never a stdlib name); each class in the chain is plain; no module involved patches classes or uses dynamic namespaces; no other definition shares the `__init__` node id. graph.json stores each file's facts, and every incremental merge links the constructor edges again over the merged graph, leaving all other nodes and edges as v8 builds them. AST cache schema 17 -> 18. The constructor-only stdlib guard preserves the existing sibling-import resolver namespace and rules. Keep v0.9.92's cache facts and salted class/member identities. Update the collision tests to check the separated initializer exactly, retain runtime checks, and keep unrelated helper bodies unreachable through cold, cached and incremental builds. Constructor proof rules are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Codex <noreply@openai.com>
48c7275 to
b1b9804
Compare
What does this PR do?
Related issue: #4209.
Foo(...)reaches the class node but stops before its initializer.affected,queryandpathtraverse calls rather than class-to-method ownership, so a change to somethingFoo.__init__calls can miss the constructor's callers and their tests.This adds an
INFERREDcalls edge from the caller to the nearest initializer in a supported class chain, with score 0.85 andcontext: "constructor". The existing edge to the class stays. For example,build() -> CachedStore()now also reaches the inheritedStore.__init__, soaffected "open_db()"findsbuild()andtest_build().The pass requires an extracted class call, an unambiguous source binding, and a plain module-level class chain with at most one base per class. It refuses decorated classes or initializers, metaclasses, custom allocation, ambiguous or conditional bindings, stdlib name collisions, initializer ID collisions, and source patterns that can replace construction behavior. Unsupported cases add nothing. Analysis exceptions also add nothing.
Three private node attributes store class-chain and call-binding facts. Incremental merges remove the derived constructor edges and relink them over the merged graph. AST cache schema advances from 17 to 18. Existing nodes and edges keep all other attributes.
Cache coordination: v8 schema 17 stores pytest-fixture facts, salted Python/Rust/Go identities, TypeScript field metadata and Solidity callable facts. This branch uses schema 18 for constructor-entry facts. It preserves grammar-version namespacing, language-override salts and v8's separate sibling-import rules.
Type of change
Verification & Invariants
Invariant: only source-backed, unambiguous constructor calls in the supported static subset gain an initializer edge. Existing graph output stays unchanged apart from the derived edges and stored facts.
Rebased onto v0.9.92,
86c57be8204673c2a1b9849a3292a7dd3ab373d8. The branch retains upstream's JavaScript member-ID helper at the insertion conflict and advances the cache schema. Constructor proof rules are unchanged. Collision tests now assert the separated initializer's exact node and keep unrelated helper bodies unreachable through cold, cached and incremental builds. The subclass/base and aliased-binding safeguards remain.Fresh full builds of seven repositories preserve every baseline node and edge attribute except the three private facts. They add 703 constructor edges. An independent Python
astderivation confirms all 703. SCIP can judge 586, and all 586 agree on the class, inheritance chain and initializer. The existing SCIP indexes refer to unchanged source snapshots; mappings use unique file, label and location anchors in the fresh graphs, with no missing or ambiguous anchors.Limitations:
.pyextension, even when.graphifyrcdeclares another extension as Python. Remapped files can still supply classes and initializers for.pycallers.build_mergedrops the inferred constructor edge because its class-call evidence no longer exists. Directed graphs retain both directions.How was this tested?
The full suites use an isolated SQL grammar through
PYTHONPATH. GPT-6-Astra at high reasoning approved candidateb1b98048e76de433f209c80183cbf02e546e5347and reviewed this description. Both final full suites completed without failures. The strict comparison passes 444 paired stages: 316 retained stages, 16 new upstream-interaction stages, and 112 collision stages. It compares every baseline edge attribute and all node attributes except the three private constructor facts. Native language overrides allow no exclusions. Added edges must be.pyconstructor calls with INFERRED confidence and score 0.85. Hyperedges, labels, pending labels, membership signatures and orphan-sweep accounting also compare exactly.The new checks retain TS instance/static field maps and private-member identities, Go case-distinct owners, Rust imported names and external file provenance, pytest-fixture receiver typing, and salted Python class/function/method IDs. Direct and inherited collision cases check exact initializer ownership and helper unreachability through extraction, caches and incremental rebuilds. True duplicate or replaced initializers still refuse inference.
Schema 17-to-18 retirement, grammar namespaces, parser salts, semantic-loss guards, opaque identities, provenance checks and standard-library namespace separation pass. The reviewer ran 115 focused upstream tests. Eight Solidity tests skipped because that optional grammar was unavailable; an isolated offline install found no cached package. The report does not claim those cases were exercised. No virtual environment or shared dependency target changed.
The retained label matrix covers watch and cluster-only with and without signatures, strict-majority retention and exact-half rejection. It fixes partitions and fallback names to test saved labels; new edges can still change natural communities and hub names. Windows was not tested.
Graphify-specific checklist