Fix JS/TS graph resolution and preserve parallel edges - #2883
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) alter behavior, breaking input(s) attached.
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
Graphify review — findings
Adds a --multigraph build flag that stores the authoritative graph as a directed MultiDiGraph (persisted per graph) so parallel edges between the same node pair aren't collapsed, routing degree/betweenness analysis through a new analysis_projection helper in build.py. Adds an --out/--output selection that is remembered per scanned source root under ~/.graphify/output-roots/, so later extract/update/watch/query/export commands reuse it; updates all CLI help defaults to "configured source output". Updates README with the new flags and output-root behavior.
Worth a look
- MultiDiGraph merge attrs still contains 'key' filtered but occurrence_count included in hash —
graphify/build.py:1360· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- edge_betweenness returns keys not present in original G for edge_data lookup —
graphify/analyze.py:359· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- Non-multigraph edge attribute named key is dropped —
graphify/build.py:1263· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- build_from_json drops existing edge key attribute for non-multigraph graphs —
graphify/build.py:1264· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- update trusts persisted scan root without containment validation —
graphify/cli.py:2155· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3730 functions depend on the 1580 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 475 callers, 43 callees - new:
_rebuild_code()— 99 callers, 55 callees - new:
build_from_json()— 185 callers, 20 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 48 callers, 14 callees - new:
_extract_generic()— 18 callers, 24 callees - new:
save_manifest()— 39 callers, 11 callees - new:
to_obsidian()— 32 callers, 12 callees - …and 105 more — each is listed as a finding
Verification — 3730 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: 3596 function(s) in the blast radius were not formally verified this run
Formal verification
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
No difference found (not proven): No behavior difference found in \_cross\_community\_surprises (not a proof).
The verifier ran both versions of \_cross\_community\_surprises 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.
No difference found (not proven): No behavior difference found in \_cross\_file\_surprises (not a proof).
The verifier ran both versions of \_cross\_file\_surprises 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.
No difference found (not proven): No behavior difference found in god\_nodes (not a proof).
The verifier ran both versions of god\_nodes 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.
Could not verify: Could not verify build.
The verifier did not have enough to check build, 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 9 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 build\_from\_json.
The verifier did not have enough to check build\_from\_json, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
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: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_load\_existing\_graph.
The verifier did not have enough to check \_load\_existing\_graph, 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: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set
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: parameter `graph_path` is annotated `str | Path` — outside the synthesizable primitive/collection set
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)
No difference found (not proven): No behavior difference found in cluster (not a proof).
The verifier ran both versions of cluster 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.
No difference found (not proven): No behavior difference found in cohesion\_score (not a proof).
The verifier ran both versions of cohesion\_score 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.
No difference found (not proven): No behavior difference found in label\_communities\_by\_hub (not a proof).
The verifier ran both versions of label\_communities\_by\_hub 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.
No difference found (not proven): No behavior difference found in score\_all (not a proof).
The verifier ran both versions of score\_all 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.
Could not verify: Could not verify detect\_incremental.
The verifier did not have enough to check detect\_incremental, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify diagnose\_extraction.
The verifier did not have enough to check diagnose\_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: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify diagnose\_file.
The verifier did not have enough to check diagnose\_file, 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: parameter `path` is annotated `str | Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify format\_diagnostic\_report.
The verifier did not have enough to check format\_diagnostic\_report, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly KeyError — names the real obstacle, not a sampling gap)
No difference found (not proven): No behavior difference found in to\_graphml (not a proof).
The verifier ran both versions of to\_graphml 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.
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: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_import\_js.
The verifier did not have enough to check \_import\_js, 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 200 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 \_apply\_symbol\_resolution\_facts.
The verifier did not have enough to check \_apply\_symbol\_resolution\_facts, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_js\_exported\_declaration\_names.
The verifier did not have enough to check \_js\_exported\_declaration\_names, 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 200 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 \_package\_entry\_candidates.
The verifier did not have enough to check \_package\_entry\_candidates, 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: parameter `package_dir` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_resolve\_export\_target (not a proof).
The verifier ran both versions of \_resolve\_export\_target 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.
· 4 grounded finding(s) anchored inline below; 109 more finding(s) on lines outside this diff (see the check run).
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) alter behavior, breaking input(s) attached.
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
Graphify review — findings
Adds a --multigraph flag to graphify extract/update that persists parallel directed edges as a MultiDiGraph, routing analytics (god_nodes, cross-file/cross-community surprise detection) through a new analysis_projection in build.py so degree and betweenness stay correct on the multigraph. Introduces per-source-root output persistence under ~/.graphify/output-roots/ so --out selections are remembered and reused across extract/update/watch/query/export, and updates the CLI help text to reflect "configured source output" defaults. Extends watch/extract/export plumbing (relativize, AST manifest, resolution, obsidian stems) to carry the multigraph and remembered-output settings.
Worth a look
- god_nodes degree field no longer reports input graph degree —
graphify/analyze.py:115· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- god_nodes degree uses projection topology but iterates over original G.nodes —
graphify/analyze.py:115· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- Projected degree lookup can crash on filtered community members —
graphify/cluster.py:104· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- Inserted cache_root breaks positional gitignore callers —
graphify/detect.py:2221· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
- New optional parameter breaks positional gitignore callers —
graphify/detect.py:2221· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3769 functions depend on the 1665 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 475 callers, 43 callees - new:
_rebuild_code()— 99 callers, 55 callees - new:
build_from_json()— 186 callers, 20 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 48 callers, 14 callees - new:
_extract_generic()— 18 callers, 24 callees - new:
save_manifest()— 39 callers, 11 callees - new:
to_obsidian()— 32 callers, 12 callees - …and 105 more — each is listed as a finding
Verification — 3769 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: 3635 function(s) in the blast radius were not formally verified this run
Formal verification
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
No difference found (not proven): No behavior difference found in \_cross\_community\_surprises (not a proof).
The verifier ran both versions of \_cross\_community\_surprises 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.
No difference found (not proven): No behavior difference found in \_cross\_file\_surprises (not a proof).
The verifier ran both versions of \_cross\_file\_surprises 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.
No difference found (not proven): No behavior difference found in god\_nodes (not a proof).
The verifier ran both versions of god\_nodes 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.
Could not verify: Could not verify build.
The verifier did not have enough to check build, 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 9 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 build\_from\_json.
The verifier did not have enough to check build\_from\_json, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
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: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_load\_existing\_graph.
The verifier did not have enough to check \_load\_existing\_graph, 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: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set
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: parameter `graph_path` is annotated `str | Path` — outside the synthesizable primitive/collection set
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)
No difference found (not proven): No behavior difference found in cluster (not a proof).
The verifier ran both versions of cluster 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.
No difference found (not proven): No behavior difference found in cohesion\_score (not a proof).
The verifier ran both versions of cohesion\_score 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.
No difference found (not proven): No behavior difference found in label\_communities\_by\_hub (not a proof).
The verifier ran both versions of label\_communities\_by\_hub 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.
No difference found (not proven): No behavior difference found in score\_all (not a proof).
The verifier ran both versions of score\_all 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.
Could not verify: Could not verify detect\_incremental.
The verifier did not have enough to check detect\_incremental, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify diagnose\_extraction.
The verifier did not have enough to check diagnose\_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: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify diagnose\_file.
The verifier did not have enough to check diagnose\_file, 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: parameter `path` is annotated `str | Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify format\_diagnostic\_report.
The verifier did not have enough to check format\_diagnostic\_report, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly KeyError — names the real obstacle, not a sampling gap)
No difference found (not proven): No behavior difference found in to\_graphml (not a proof).
The verifier ran both versions of to\_graphml 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.
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: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_import\_js.
The verifier did not have enough to check \_import\_js, 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 200 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 \_apply\_symbol\_resolution\_facts.
The verifier did not have enough to check \_apply\_symbol\_resolution\_facts, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_js\_exported\_declaration\_names.
The verifier did not have enough to check \_js\_exported\_declaration\_names, 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 200 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 \_package\_entry\_candidates.
The verifier did not have enough to check \_package\_entry\_candidates, 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: parameter `package_dir` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_resolve\_export\_target (not a proof).
The verifier ran both versions of \_resolve\_export\_target 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.
· 4 grounded finding(s) anchored inline below; 109 more finding(s) on lines outside this diff (see the check run).
|
Resolving with a rebase |
5516564 to
0808ec3
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) alter behavior, breaking input(s) attached.
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
Graphify review — findings
Adds a --multigraph extract flag that stores the authoritative graph as a MultiDiGraph to preserve parallel directed edges, persisting the setting with the graph. Introduces per-source-root output-root remembering under ~/.graphify/output-roots/ so --out can point anywhere and is reused across extract/update/watch/query/export; adds an analysis_projection helper so degree-based analytics collapse parallel edges correctly. Updates CLI help text and README to describe the new flag and configured-output behavior.
Worth a look
- analysis_projection may drop nodes, causing KeyError when indexing degree by G.nodes —
graphify/analyze.py:116· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- god_nodes can raise for nodes omitted from analysis_projection —
graphify/analyze.py:116· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- graphify update without a path now rejects a saved scan root when it differs from the configured binding —
graphify/cli.py:99· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- --no-dedup is ignored for no-cluster multigraph extraction —
graphify/cli.py:3925· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- cluster() no longer clusters over all caller-supplied edges —
graphify/cluster.py:159· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3845 functions depend on the 1684 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 477 callers, 43 callees - new:
_rebuild_code()— 99 callers, 54 callees - new:
build_from_json()— 189 callers, 20 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 48 callers, 14 callees - new:
_extract_generic()— 18 callers, 24 callees - new:
save_manifest()— 39 callers, 11 callees - new:
to_obsidian()— 34 callers, 12 callees - …and 107 more — each is listed as a finding
Verification — 3845 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: 3694 function(s) in the blast radius were not formally verified this run
Formal verification
Behavior changes: \_default\_graph\_path changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_default\_graph\_path behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This is a sound refutation from differential testing (both versions run on many generated inputs): a concrete input on which the two versions provably differ.
No difference found (not proven): No behavior difference found in \_cross\_community\_surprises (not a proof).
The verifier ran both versions of \_cross\_community\_surprises 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.
No difference found (not proven): No behavior difference found in \_cross\_file\_surprises (not a proof).
The verifier ran both versions of \_cross\_file\_surprises 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.
No difference found (not proven): No behavior difference found in god\_nodes (not a proof).
The verifier ran both versions of god\_nodes 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.
Could not verify: Could not verify build.
The verifier did not have enough to check build, 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 9 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 build\_from\_json.
The verifier did not have enough to check build\_from\_json, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
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: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_load\_existing\_graph.
The verifier did not have enough to check \_load\_existing\_graph, 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: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set
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: parameter `graph_path` is annotated `str | Path` — outside the synthesizable primitive/collection set
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)
No difference found (not proven): No behavior difference found in cluster (not a proof).
The verifier ran both versions of cluster 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.
No difference found (not proven): No behavior difference found in cohesion\_score (not a proof).
The verifier ran both versions of cohesion\_score 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.
No difference found (not proven): No behavior difference found in label\_communities\_by\_hub (not a proof).
The verifier ran both versions of label\_communities\_by\_hub 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.
No difference found (not proven): No behavior difference found in score\_all (not a proof).
The verifier ran both versions of score\_all 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.
Could not verify: Could not verify detect\_incremental.
The verifier did not have enough to check detect\_incremental, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify diagnose\_extraction.
The verifier did not have enough to check diagnose\_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: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify diagnose\_file.
The verifier did not have enough to check diagnose\_file, 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: parameter `path` is annotated `str | Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify format\_diagnostic\_report.
The verifier did not have enough to check format\_diagnostic\_report, 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 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly KeyError — names the real obstacle, not a sampling gap)
No difference found (not proven): No behavior difference found in to\_graphml (not a proof).
The verifier ran both versions of to\_graphml 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.
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: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_import\_js.
The verifier did not have enough to check \_import\_js, 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 200 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 \_apply\_symbol\_resolution\_facts.
The verifier did not have enough to check \_apply\_symbol\_resolution\_facts, 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: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_js\_exported\_declaration\_names.
The verifier did not have enough to check \_js\_exported\_declaration\_names, 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 200 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 \_package\_entry\_candidates.
The verifier did not have enough to check \_package\_entry\_candidates, 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: parameter `package_dir` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_resolve\_export\_target (not a proof).
The verifier ran both versions of \_resolve\_export\_target 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.
· 4 grounded finding(s) anchored inline below; 111 more finding(s) on lines outside this diff (see the check run).
|
@safishamsi When you have time, could you review this PR? It addresses #2849 and includes regression coverage for the reported JS/TS resolution and parallel-edge failures. The branch is rebased onto v8 and the visible checks pass. |
Summary
This fixes missing and overwritten relationships in JavaScript and TypeScript graphs.
Graphify now resolves common source layouts that previously produced edges to nodes that did not exist.
With
--multigraph, it also preserves distinct relationships between the same two nodes instead of overwriting all but one.An explicit
--outselection now persists for the scanned folder.Later commands reuse the external output without adding
graphify-outto the source repository.Addresses #2849
What changes for users
distoutput..jsand.jsxspecifiers can resolve TypeScript source and declaration files..d.tsfiles provide one resolvable export set.styles.css?urlresolve without losing the query context.--multigraphpreserves parallel directed relationships and their source evidence.--outapplies to later extract, update, watch, query, path, explain, diagnose, tree, benchmark, memory, and export commands.Why relationships went missing
A dangling endpoint is an edge whose source or target has no matching node.
Several resolver paths could produce these endpoints.
Workspace imports could select ignored build output,
.jsspecifiers did not try every TypeScript counterpart, and imported members could stop at a barrel instead of reaching their declaration.A separate problem occurred after extraction.
A NetworkX
DiGraphstores one edge for each ordered node pair, so adding another relationship between the same nodes replaced the previous relationship.These failures affected graph queries, path searches, exports, and reports because the stored graph no longer matched the extracted relationships.
How the fix works
JavaScript and TypeScript resolution
The resolver now considers all eligible workspace export conditions and prefers candidates included in the scanned source set.
Explicit JavaScript extensions receive replacement candidates instead of appended extensions.
For example,
button.jscan resolve tobutton.ts,button.tsx, orbutton.d.ts.Runtime JavaScript modules can use symbols from companion declaration files.
This allows imports such as
type MutationCtxfromserver.jsto resolve toserver.d.tswhile runtime values still resolve toserver.js.Re-export resolution follows imported bindings, aliases, local barrels, and workspace exports.
Cycle detection uses the file and exported name together.
Edge storage
When
--multigraphis enabled, directed builds useMultiDiGraphas the stored graph.Each distinct edge receives a deterministic key based on its relationship and evidence.
Exact duplicate records can share an
occurrence_count, while records with different relationships, locations, or contexts remain separate.Algorithms that require one edge per node pair receive a separate simple projection.
The stored graph remains the source of truth.
Endpoint classification
The build materializes endpoint nodes before graph construction and classifies non-source references as:
externalexcluded_localunresolved_internalDiagnostics report these categories separately.
External dependencies no longer appear as graph corruption merely because they are outside the scanned repository.
Persistent external output
A successful explicit
--outwrites a small per-source binding under:Each binding contains the canonical source root and output root.
The filename is a hash of the source root, so separate processes can update different projects without replacing one shared configuration file.
Commands started in a project subdirectory inherit the nearest source binding.
A later explicit
--outreplaces the binding.A failed extraction leaves the previous binding unchanged.
The binding write is atomic.
A non-default
GRAPHIFY_OUTenvironment value keeps its existing precedence.Incremental detection and AST caches use the selected output root too, so update and watch operations do not recreate cache files inside the source repository.
NeonFlux verification
A fresh code-only build used the NeonFlux snapshot from the issue at
341d66b.MultiDiGraphThe build also verified the cases named in the issue:
distnodes or endpoints.dashboard-ui.jsresolves todashboard-ui.tsx.QueryCtx,MutationCtx,api, andDocimports resolve to generated declaration nodes.__root.tsxresolves tostyles.css.routeTree.gen.tscontributes 213 edges with no dangling endpoints.The audited NeonFlux checkout does not contain the
.sourcefiles referenced bycollections/browserandcollections/server.Those two references are therefore
unresolved_internal.A separate regression test verifies that an alias pointing to an existing but unscanned file receives
excluded_local.Output persistence verification
The process-level regression test performs this sequence:
--outand--multigraph.--out.updatewithout--out.graphify-outdirectory.Checks
graphify update . --no-clustercompletes successfully.The full Windows test run reports 4,694 passed, 67 skipped, 18 failed, and 2 deselected.
The same 18 failures reproduce on the base branch.
They cover unavailable Unix APIs and tools, optional dependencies, installer home assumptions, and existing Windows path or encoding behavior.