Skip to content

fix(extract): preserve existing semantic layer on --code-only --force - #2925

Open
santhiprakash wants to merge 1 commit into
Graphify-Labs:v8from
santhiprakash:fix/code-only-force-merge
Open

fix(extract): preserve existing semantic layer on --code-only --force#2925
santhiprakash wants to merge 1 commit into
Graphify-Labs:v8from
santhiprakash:fix/code-only-force-merge

Conversation

@santhiprakash

Copy link
Copy Markdown

What

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.

Why

Reproduces per issue #2923:

graphify extract . --backend claude-cli     # 6 nodes (2 code + 4 doc/concept)
graphify extract . --code-only --force     # 2 nodes — 4 doc/concept lost

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 path graphify update and the git-hook _rebuild_code correctly avoid.

Fix

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); 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.

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

  • New: test_code_only_force_preserves_existing_semantic_layer — seeded graph with AST + SEMANTIC nodes; --code-only --force preserves the SEMANTIC tier. Without the fix the assertion fires (semantic nodes remaining: set()).
  • New: test_code_only_force_prunes_removed_semantic_filesNOTES.txt deleted between seed and re-run; its semantic nodes are pruned (graph_stale_sources catches the dead file).
  • Existing 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

…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

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 graphgraphify/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 bypassgraphify/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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant