Repository navigation
fix(rebuild): keep a --code-only graph code-only on rebuilds (#4923) - #4939
Deepjyoti-Sarmah wants to merge 5 commits into
Conversation
…builds (Graphify-Labs#4923) A graph built with `extract --code-only` grew .md/.mdx/.qmd nodes on the next hook, `graphify update` or full rebuild, because the rebuild has no CLI flag and nothing recorded the scope. These tests drive the real CLI and a real post-commit hook and assert on the resulting graph only, with controls proving a rebuild still tracks new and deleted code and that a default graph still gets doc nodes. They fail on this commit by design; the following commits make them pass.
…raphify-Labs#4923) Persist `code_only` beside `excludes` and `gitignore` (the Graphify-Labs#1886/Graphify-Labs#1971 pattern) so a later rebuild can tell a code-only graph from a full one. Nothing reads it yet; this commit changes no rebuild behaviour. test_explicit_exclude_replaces_persisted_setting_with_custom_out asserted the whole file equals {"excludes": [...]} after a --code-only run; the file now also carries code_only, so the expectation includes it.
… rebuilds (Graphify-Labs#4923) _rebuild_code now reads the recorded scope and skips the .md/.mdx/.qmd AST quick-scan for a code-only graph. Doc nodes already in the graph are left as they are, matching how --code-only already treats the semantic layer (Graphify-Labs#2923). No new parameter: the hooks, `graphify update` and `graphify watch` all go through _rebuild_code, so every entry point is covered.
…raphify-Labs#4923) The Graphify-Labs#4740 gate skips a rebuild when inputs and build state match. The scope changes what a rebuild writes, so it has to be part of that state; otherwise flipping the scope with unchanged files would be skipped as "No relevant changes".
…nly scope (Graphify-Labs#4923) Running `extract` without --code-only on a corpus with docs, past the API-key check, is an explicit move back to a full graph, so the recorded scope is cleared. An aborted run (no key) and a corpus with no docs leave it alone, so a plain `extract` cannot silently drop the scope.
|
Thanks for the pull request, @Deepjyoti-Sarmah. 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.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; formal verification proved 0 of 4 changed function(s) (4 not verified).
Formal verification. PR-changed functions: 0/4 verified (0 proven, 0 may-equivalent, 0 distinguished) · 4 not verified (3 vacuous, 1 unsupported).
Not verified on this run: dispatch\_command (vacuous: never exercised), \_current\_build\_state (vacuous: never exercised), \_rebuild\_code (unsupported), \_write\_build\_config (vacuous: never exercised).
Graphify review — findings
Fixes #4923 by persisting extract --code-only as a code_only flag in the build config, so hook, update and watch rebuilds keep the graph code-only instead of growing doc nodes. _rebuild_code reads the flag with _read_build_code_only (defaulting to False if the config is missing or unreadable), skips the markdown quick-scan, and leaves existing doc nodes untouched. The flag also feeds _current_build_state, but dispatch_command records the scope switch before extraction finishes, and the config write is not atomic, so an aborted or concurrent run can leave the recorded scope wrong. A flagless extract that actually ingests docs resets the scope to a full graph. A run with no docs, or one that aborts at the key check, leaves the recorded scope alone. Graphs with no recorded scope behave as before.
No blocking issues surfaced. 5 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 837 functions depend on the 258 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
_rebuild_code()— 221 callers, 68 callees - new:
test_issue_repro_via_real_post_commit_hook()— 0 callers, 8 callees
Verification — 837 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: 783 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
73 of 490 test file(s) selected (15%) via static blast radius.
tests/test_affected_cli.py— impacttests/test_affected_seed_reference_forms.py— impacttests/test_agents_platform.py— impacttests/test_cache_stale_import_target.py— impacttests/test_cli_export.py— impacttests/test_cloud_cta.py— impacttests/test_code_only_scope_4923.py— impact, changed-testtests/test_codebuddy.py— impacttests/test_compact_graph_json.py— impacttests/test_confidence_score_output.py— impacttests/test_csharp_method_owner_labels.py— impacttests/test_dedup_shrink_refuses_force_write.py— impacttests/test_devin.py— impacttests/test_edge_also_seen.py— impacttests/test_elixir_import_resolution.py— impacttests/test_elixir_qualified_calls.py— impacttests/test_elixir_unqualified_call_scope.py— impacttests/test_explain_cli.py— impacttests/test_explain_same_file_tie.py— impacttests/test_export_direction.py— impacttests/test_external_stub_endpoints.py— impacttests/test_extract_cli.py— impacttests/test_extract_code_only_cli.py— impact, changed-testtests/test_extract_cost_ledger.py— impacttests/test_global_add_tag_inference.py— impacttests/test_go_receiver_member_calls.py— impacttests/test_god_nodes_cli.py— impacttests/test_hollow_chunks_arm_shrink_guard.py— impacttests/test_hook_guard_token_match.py— impacttests/test_hook_nudge_wording.py— impacttests/test_hook_out_of_project_paths.py— impacttests/test_hook_strict.py— impacttests/test_incomplete_build_guard.py— impacttests/test_incremental_unscanned_placeholders.py— impacttests/test_install.py— impacttests/test_install_references.py— impacttests/test_java_member_calls.py— impacttests/test_kotlin_grammar.py— impacttests/test_labeling.py— impacttests/test_language_override_lifecycle.py— impacttests/test_lookup_exit_codes.py— impacttests/test_lost_saved_semantic_nodes.py— impacttests/test_markdown_code_spans.py— impacttests/test_merge_chunks_validation.py— impacttests/test_multigraph_diagnostics.py— impacttests/test_no_cluster_undeclared_endpoints.py— impacttests/test_no_dedup_flag.py— impacttests/test_opencode_plugin_refresh.py— impacttests/test_partial_cache.py— impacttests/test_path_cli.py— impact- … and 23 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)
graphify/skill-agents.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 630-739): references changed symbols_corpusgraphify/skill-aider.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 710-792): references changed symbols_corpusgraphify/skill-amp.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 630-739): references changed symbols_corpusgraphify/skill-claw.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 633-742): references changed symbols_corpusgraphify/skill-codex.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 634-743): references changed symbols_corpusgraphify/skill-copilot.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 633-742): references changed symbols_corpusgraphify/skill-devin.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 828-911): references changed symbols_corpusgraphify/skill-droid.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 630-739): references changed symbols_corpusgraphify/skill-kilo.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 633-742): references changed symbols_corpusgraphify/skill-kiro.md§ Step 9 - Save manifest, update cost tracker, clean up, and report (lines 633-742): references changed symbols_corpus
…and 10 more.
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: 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 \_current\_build\_state.
The verifier did not have enough to check \_current\_build\_state, 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 1 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 \_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
Could not verify: Could not verify \_write\_build\_config.
The verifier did not have enough to check \_write\_build\_config, 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 9 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)
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def test_issue_repro_via_real_post_commit_hook(tmp_path): |
There was a problem hiding this comment.
test_issue_repro_via_real_post_commit_hook()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Landed in 0.9.101 (published to PyPI), cherry-picked with your authorship preserved. Fixes #4923. Thanks @Deepjyoti-Sarmah. |
What does this PR do?
Fixes #4923.
A graph built with
graphify extract . --code-onlygrows.md,.mdxand.qmdnodes on the next git hook,graphify updateorgraphify watchrebuild._rebuild_codehas no--code-onlyflag and nothing recorded the scope, so it re-detects the repo and adds every markdown-family file that has an AST extractor (the #4750 doc quick-scan).This PR records the scope when
extract --code-onlyruns, and_rebuild_codereads it back.code_onlysits in.graphify_build.jsonnext toexcludesandgitignore(--excludepatterns only apply to the initial scan — update/watch/hook rebuilds silently re-include excluded paths #1886, Allow extracting git-ignored code: add--no-gitignoreopt-out #1971)._rebuild_codegets no new parameter, so every rebuild entry point is covered without touching the hooks.code_onlyis part of the build state thegraphify updateon a cloned checkout re-extracts every file and rebuilds the whole graph (3 to 7 min, ~6 GB) even with zero changes #4740 no-change gate compares, so flipping the scope on unchanged files is not skipped as "No relevant changes".extractclears the scope only when it goes on to ingest docs, meaning it got past the API key check. An aborted run, or a repo with no docs, leaves it alone.--code-onlyalready keeps the semantic layer (extract --code-only --force silently discards ALL doc/paper/image nodes and hyperedges instead of preserving them (0.9.47) #2923).Type of change
Evidence
Before (
upstream/v8at32e5efe9): scratch repo with 2.pyand 3.mdfiles.After:
The issue's own steps are covered by a test that installs the real post-commit hook, commits a changed
.pyand.md, waits for the new code symbol to land, then checks for doc nodes.Tests:
The one failure is a file-watcher timing test that does not touch this change. It failed 1 run in 5 on the unmodified base (
32e5efe9), and it passes 5 of 5 on this branch when run alone.tests/test_extract_code_only_cli.py::test_explicit_exclude_replaces_persisted_setting_with_custom_outasserted the whole.graphify_build.jsonequals{"excludes": [...]}after a--code-onlyrun. The file now also carries"code_only": true, so the expectation includes it. The test still checks what it was written for: an explicit--excludereplaces the persisted list.Verification & Invariants
The invariant: a rebuild keeps the scope the graph was built with. A code-only graph stays code-only until the user runs a flagless
extractthat ingests docs.The tests drive the real CLI and assert on the resulting graph. They pin:
graphify update, and a new doc added later.extractand a flaglessextracton a repo with no docs keep the scope.extractthat ingests docs clears it.Known limitations:
extract --code-onlyonce to record it.manifest.jsonchanging on every rebuild) are not part of this PR.How was this tested?
Branch
test/4923-code-only-scopeis based onupstream/v8(32e5efe9, v0.9.100). It is five commits, and each one passes its own tests: the tests (red by design), the recorded scope, the rebuild fix, the no-change gate, and the reset.Merge Danger
Door: Two-way.
A graph with no
code_onlykey rebuilds exactly as before. No cache schema bump and no graph format change. Reverting leaves a straycode_onlykey in.graphify_build.json, which older code ignores.Blast Radius: Rebuild scope.
The change only affects graphs built with
--code-only. Someone who built one and still wanted doc headings from hook rebuilds will stop getting them until they run a flaglessextract.Graphify-specific checklist
uv run python -m tools.skillgen --bless) when changing their source fragments. No source fragments changed.