Fix #686 #652: GRAPHIFY_OUT env var for worktrees, Antigravity install auto-updates rules/workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-05-03 17:36:17 +01:00
co-authored by Claude Sonnet 4.6
parent 6df69dce6c
commit 8e81720bc8
3 changed files with 37 additions and 13 deletions
+19 -5
View File
@@ -14,6 +14,10 @@ try:
except Exception:
__version__ = "unknown"
# Output directory — override with GRAPHIFY_OUT env var for worktrees or shared-output setups.
# Accepts a relative name ("graphify-out-feature") or an absolute path ("/shared/graphify-out").
_GRAPHIFY_OUT = os.environ.get("GRAPHIFY_OUT", "graphify-out")
def _check_skill_version(skill_dst: Path) -> None:
"""Warn if the installed skill is from an older graphify version."""
@@ -553,7 +557,12 @@ def _antigravity_install(project_dir: Path) -> None:
rules_path = project_dir / _ANTIGRAVITY_RULES_PATH
rules_path.parent.mkdir(parents=True, exist_ok=True)
if rules_path.exists():
print(f"graphify rule already exists at {rules_path} (no change)")
existing = rules_path.read_text(encoding="utf-8")
if _ANTIGRAVITY_RULES.strip() != existing.strip():
rules_path.write_text(_ANTIGRAVITY_RULES, encoding="utf-8")
print(f"graphify rule updated at {rules_path.resolve()}")
else:
print(f"graphify rule already up to date at {rules_path.resolve()}")
else:
rules_path.write_text(_ANTIGRAVITY_RULES, encoding="utf-8")
print(f"graphify rule written to {rules_path.resolve()}")
@@ -562,7 +571,12 @@ def _antigravity_install(project_dir: Path) -> None:
wf_path = project_dir / _ANTIGRAVITY_WORKFLOW_PATH
wf_path.parent.mkdir(parents=True, exist_ok=True)
if wf_path.exists():
print(f"graphify workflow already exists at {wf_path} (no change)")
existing = wf_path.read_text(encoding="utf-8")
if _ANTIGRAVITY_WORKFLOW.strip() != existing.strip():
wf_path.write_text(_ANTIGRAVITY_WORKFLOW, encoding="utf-8")
print(f"graphify workflow updated at {wf_path.resolve()}")
else:
print(f"graphify workflow already up to date at {wf_path.resolve()}")
else:
wf_path.write_text(_ANTIGRAVITY_WORKFLOW, encoding="utf-8")
print(f"graphify workflow written to {wf_path.resolve()}")
@@ -1539,7 +1553,7 @@ def main() -> None:
watch_path = Path(argv[2])
else:
# Try to recover the scan root saved by the last full build
saved = Path("graphify-out/.graphify_root")
saved = Path(_GRAPHIFY_OUT) / ".graphify_root"
if saved.exists():
watch_path = Path(saved.read_text(encoding="utf-8").strip())
else:
@@ -1578,7 +1592,7 @@ def main() -> None:
# showing top-K outbound edges per symbol.
from typing import Optional as _Opt
from graphify.tree_html import write_tree_html, DEFAULT_MAX_CHILDREN
graph_path = Path("graphify-out/graph.json")
graph_path = Path(_GRAPHIFY_OUT) / "graph.json"
output_path: "_Opt[Path]" = None
root: "_Opt[str]" = None
max_children = DEFAULT_MAX_CHILDREN
@@ -1630,7 +1644,7 @@ def main() -> None:
# graphify merge-graphs graph1.json graph2.json ... --out merged.json
args = sys.argv[2:]
graph_paths: list[Path] = []
out_path = Path("graphify-out/merged-graph.json")
out_path = Path(_GRAPHIFY_OUT) / "merged-graph.json"
i = 0
while i < len(args):
if args[i] == "--out" and i + 1 < len(args):
+11 -4
View File
@@ -7,6 +7,11 @@ import os
import tempfile
from pathlib import Path
# Output directory name — override with GRAPHIFY_OUT env var for worktrees or
# shared-output setups. Accepts a relative name ("graphify-out-feature") or an
# absolute path ("/shared/graphify-out").
_GRAPHIFY_OUT = os.environ.get("GRAPHIFY_OUT", "graphify-out")
def _body_content(content: bytes) -> bytes:
"""Strip YAML frontmatter from Markdown content, returning only the body."""
@@ -62,7 +67,9 @@ def cache_dir(root: Path = Path("."), kind: str = "ast") -> Path:
kind is "ast" or "semantic". Separate subdirectories prevent semantic cache
entries from overwriting AST cache entries for the same source_file (#582).
"""
d = Path(root).resolve() / "graphify-out" / "cache" / kind
_out = Path(_GRAPHIFY_OUT)
base = _out if _out.is_absolute() else Path(root).resolve() / _out
d = base / "cache" / kind
d.mkdir(parents=True, exist_ok=True)
return d
@@ -89,7 +96,7 @@ def load_cached(path: Path, root: Path = Path("."), kind: str = "ast") -> dict |
return None
# Migration fallback: check legacy flat cache/ dir for AST entries
if kind == "ast":
legacy = Path(root).resolve() / "graphify-out" / "cache" / f"{h}.json"
legacy = Path(root).resolve() / _GRAPHIFY_OUT / "cache" / f"{h}.json"
if legacy.exists():
try:
return json.loads(legacy.read_text(encoding="utf-8"))
@@ -140,7 +147,7 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a
def cached_files(root: Path = Path(".")) -> set[str]:
"""Return set of file hashes that have a valid cache entry (any kind)."""
base = Path(root).resolve() / "graphify-out" / "cache"
base = Path(root).resolve() / _GRAPHIFY_OUT / "cache"
hashes: set[str] = set()
# Legacy flat entries
if base.is_dir():
@@ -155,7 +162,7 @@ def cached_files(root: Path = Path(".")) -> set[str]:
def clear_cache(root: Path = Path(".")) -> None:
"""Delete all cache entries (ast/, semantic/, and legacy flat entries)."""
base = Path(root).resolve() / "graphify-out" / "cache"
base = Path(root).resolve() / _GRAPHIFY_OUT / "cache"
# Legacy flat entries
if base.is_dir():
for f in base.glob("*.json"):
+7 -4
View File
@@ -1,10 +1,13 @@
# monitor a folder and auto-trigger --update when files change
from __future__ import annotations
import json
import os
import sys
import time
from pathlib import Path
_GRAPHIFY_OUT = os.environ.get("GRAPHIFY_OUT", "graphify-out")
from graphify.detect import CODE_EXTENSIONS, DOC_EXTENSIONS, PAPER_EXTENSIONS, IMAGE_EXTENSIONS
@@ -68,7 +71,7 @@ def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False, force: boo
# Filter by node ID membership in the new AST output, not by file_type —
# INFERRED/AMBIGUOUS nodes extracted from code files also carry file_type="code"
# and would be wrongly dropped by a file_type-based filter.
out = watch_path / "graphify-out"
out = watch_path / _GRAPHIFY_OUT
existing_graph = out / "graph.json"
if existing_graph.exists():
try:
@@ -159,7 +162,7 @@ def check_update(watch_path: Path) -> bool:
re-extraction via `/graphify --update` — this function only signals
that the update is needed.
"""
flag = Path(watch_path) / "graphify-out" / "needs_update"
flag = Path(watch_path) / _GRAPHIFY_OUT / "needs_update"
if flag.exists():
print(f"[graphify check-update] Pending non-code changes in {watch_path}.")
print("[graphify check-update] Run `/graphify --update` to apply semantic re-extraction.")
@@ -168,7 +171,7 @@ def check_update(watch_path: Path) -> bool:
def _notify_only(watch_path: Path) -> None:
"""Write a flag file and print a notification (fallback for non-code-only corpora)."""
flag = watch_path / "graphify-out" / "needs_update"
flag = watch_path / _GRAPHIFY_OUT / "needs_update"
flag.parent.mkdir(parents=True, exist_ok=True)
flag.write_text("1", encoding="utf-8")
print(f"\n[graphify watch] New or changed files detected in {watch_path}")
@@ -213,7 +216,7 @@ def watch(watch_path: Path, debounce: float = 3.0) -> None:
return
if any(part.startswith(".") for part in path.parts):
return
if "graphify-out" in path.parts:
if _GRAPHIFY_OUT in path.parts:
return
last_trigger = time.monotonic()
pending = True