Add --force flag and GRAPHIFY_FORCE env var to graphify update

Bypasses the node-count safety check in to_json for refactors that
legitimately shrink the graph (renames, package deletions). Also
honored by post-commit and post-checkout hooks via GRAPHIFY_FORCE=1.

Implements the approach from #639 (targeted at v5) adapted for v6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-05-02 13:52:09 +01:00
co-authored by Claude Sonnet 4.6
parent 8e01d686f7
commit ca48092413
3 changed files with 22 additions and 8 deletions
+10 -3
View File
@@ -1009,6 +1009,8 @@ def main() -> None:
print(" --dir <path> target directory (default: ./raw)")
print(" watch <path> watch a folder and rebuild the graph on code changes")
print(" update <path> re-extract code files and update the graph (no LLM needed)")
print(" --force overwrite graph.json even if the rebuild has fewer nodes")
print(" (also: GRAPHIFY_FORCE=1 env var; use after refactors that delete code)")
print(" cluster-only <path> rerun clustering on an existing graph.json and regenerate report")
print(" query \"<question>\" BFS traversal of graph.json for a question")
print(" --dfs use depth-first instead of breadth-first")
@@ -1423,8 +1425,13 @@ def main() -> None:
print(f"Done — {len(communities)} communities. GRAPH_REPORT.md, graph.json and graph.html updated.")
elif cmd == "update":
if len(sys.argv) > 2:
watch_path = Path(sys.argv[2])
force = os.environ.get("GRAPHIFY_FORCE", "").lower() in ("1", "true", "yes")
argv = list(sys.argv)
if "--force" in argv[2:]:
force = True
argv = [a for a in argv if a != "--force"]
if len(argv) > 2:
watch_path = Path(argv[2])
else:
# Try to recover the scan root saved by the last full build
saved = Path("graphify-out/.graphify_root")
@@ -1437,7 +1444,7 @@ def main() -> None:
sys.exit(1)
from graphify.watch import _rebuild_code
print(f"Re-extracting code files in {watch_path} (no LLM needed)...")
ok = _rebuild_code(watch_path)
ok = _rebuild_code(watch_path, force=force)
if ok:
print("Code graph updated. For doc/paper/image changes run /graphify --update in your AI assistant.")
if not os.environ.get("MOONSHOT_API_KEY") and not os.environ.get("GRAPHIFY_NO_TIPS"):
+6 -3
View File
@@ -80,8 +80,10 @@ if not changed:
print(f'[graphify hook] {len(changed)} file(s) changed - rebuilding graph...')
try:
import os as _os
from graphify.watch import _rebuild_code
_rebuild_code(Path('.'))
_force = _os.environ.get('GRAPHIFY_FORCE', '').lower() in ('1', 'true', 'yes')
_rebuild_code(Path('.'), force=_force)
except Exception as exc:
print(f'[graphify hook] Rebuild failed: {exc}')
sys.exit(1)
@@ -124,9 +126,10 @@ echo "[graphify] Branch switched - launching background rebuild (log: $_GRAPHIFY
nohup $GRAPHIFY_PYTHON -c "
from graphify.watch import _rebuild_code
from pathlib import Path
import sys
import os, sys
try:
_rebuild_code(Path('.'))
_force = os.environ.get('GRAPHIFY_FORCE', '').lower() in ('1', 'true', 'yes')
_rebuild_code(Path('.'), force=_force)
except Exception as exc:
print(f'[graphify] Rebuild failed: {exc}')
sys.exit(1)
+6 -2
View File
@@ -33,9 +33,13 @@ def _relativize_source_files(payload: dict, root: Path) -> None:
continue
def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool:
def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False, force: bool = False) -> bool:
"""Re-run AST extraction + build + cluster + report for code files. No LLM needed.
When ``force`` is True the node-count safety check in ``to_json`` is bypassed
so the rebuilt graph overwrites graph.json even if it has fewer nodes.
Use this after refactors that legitimately delete code.
Returns True on success, False on error.
"""
watch_root = watch_path.resolve()
@@ -105,7 +109,7 @@ def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool:
out.mkdir(exist_ok=True)
(out / ".graphify_root").write_text(str(watch_root), encoding="utf-8")
json_written = to_json(G, communities, str(out / "graph.json"))
json_written = to_json(G, communities, str(out / "graph.json"), force=force)
if not json_written:
return False