fix(extract): preserve existing semantic layer on --code-only --force - #2925
fix(extract): preserve existing semantic layer on --code-only --force#2925santhiprakash wants to merge 1 commit into
Conversation
…Graphify-Labs#2923) graphify extract --code-only --force previously rewrote graph.json with only the AST tier, silently dropping every doc/paper/image node plus its connected hyperedges. --force disables incremental mode so the merge path that would otherwise carry the surviving semantic tier forward never ran. A code-only run cannot touch the semantic tier at all (no LLM dispatch), so discarding the existing semantic layer is a destructive side effect with no correctness justification. Re-enable the incremental merge when --force and --code-only are combined and an existing graph.json is present; the AST tier is still fully replaced (full re-scan, semantic cache reads skipped) while doc/paper/image nodes are carried forward via build_merge / merge_raw_extraction. graph_stale_sources still prunes semantic nodes for files deleted from disk between the prior extract and this one, so the merge cannot resurrect nodes for sources that no longer exist. Adds two regression tests in test_extract_code_only_cli.py: - test_code_only_force_preserves_existing_semantic_layer: seeded graph with AST + SEMANTIC nodes; verifies the SEMANTIC tier survives --code-only --force. - test_code_only_force_prunes_removed_semantic_files: deletes NOTES.txt between seed and re-run; verifies its semantic nodes are pruned, not resurrected, by the merge. Ref: Graphify-Labs#2923
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 2 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Fixes --force --code-only in dispatch_command silently erasing the existing semantic layer: when a graph.json exists, it now forces incremental merge so doc/paper/image nodes and their hyperedges carry forward while the AST tier is fully re-scanned, and prints a line explaining the branch. Adds two regression tests in test_extract_code_only_cli.py covering surviving semantic nodes and pruning of semantic nodes for files deleted from disk.
Worth a look
- --force --code-only no longer produces a code-only fresh graph —
graphify/cli.py:3139· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- --force --code-only re-enables incremental_mode, silently reactivating manifest-gated AST cache reads that --force was meant to bypass —
graphify/cli.py:3142· 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 — 262 functions depend on the 76 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 119 callees - new:
_stale_graph_sources()— 7 callers, 6 callees - new:
_run_hook_guard()— 4 callers, 7 callees - new:
test_poisoned_manifest_is_healed()— 0 callers, 6 callees
Verification — 262 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: 215 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)
· 4 more finding(s) on lines outside this diff (see the check run).
What
graphify extract --code-only --forcepreviously rewrotegraph.jsonwith only the AST tier, silently dropping every doc/paper/image node plus its connected hyperedges.--forcedisables incremental mode so the merge path that would otherwise carry the surviving semantic tier forward never ran.A code-only run cannot touch the semantic tier at all (no LLM dispatch), so discarding the existing semantic layer is a destructive side effect with no correctness justification.
Why
Reproduces per issue #2923:
The second command's user-facing output (
skipping 1 non-code file(s) — no LLM extraction) reads as a cost-saving notice, not a data-loss warning. The semantic layer is only the only record of those nodes for any subsequent query, and the only recovery is the timestamped backup folder the tool itself dropped to disk — exactly the pathgraphify updateand the git-hook_rebuild_codecorrectly avoid.Fix
Re-enable the incremental merge when
--forceand--code-onlyare combined and an existinggraph.jsonis present. The AST tier is still fully replaced (full re-scan, semantic cache reads skipped); doc/paper/image nodes are carried forward viabuild_merge/merge_raw_extraction.graph_stale_sourcesstill prunes semantic nodes for files deleted from disk between the prior extract and this one, so the merge cannot resurrect nodes for sources that no longer exist.A new
[graphify extract] --force --code-only: full AST re-scan, existing semantic layer preserved (no semantic pass this run)console line announces the merge path so the user can see the combination did the right thing.Verification
test_code_only_force_preserves_existing_semantic_layer— seeded graph with AST + SEMANTIC nodes;--code-only --forcepreserves the SEMANTIC tier. Without the fix the assertion fires (semantic nodes remaining: set()).test_code_only_force_prunes_removed_semantic_files—NOTES.txtdeleted between seed and re-run; its semantic nodes are pruned (graph_stale_sources catches the dead file).tests/test_extract_code_only_cli.py(12 tests) all green.uv run pytest tests/test_extract.py tests/test_build.py tests/test_build_merge_hyperedges_and_prune.py tests/test_build_merge_shrink_guard.py tests/test_extract_cli.py tests/test_extraction_spec_ids.py→ 393 passed, 4 skipped.uv run ruff check graphify/cli.py tests/test_extract_code_only_cli.py→ clean.uv run ruff format --check→ only pre-existing reformat churn (none of the lines this PR touches).Closes
#2923