mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-27 07:55:43 +00:00
fix(cli): keep outputs/cache with --out / --graph, not the corpus or CWD (#1747)
Case 1 — `extract <corpus> --out <dir>`: the graph went to <dir> (cache_root is already passed to the AST extractor), but detect()'s word-count/stat-index cache uses the scan root, so a stray graphify-out/cache/ was created inside the corpus (and left behind even when the run aborted at the no-LLM-key gate). Thread an optional cache_root through detect() -> cached_word_count() -> _ensure_stat_index() and pass out_root from the extract CLI, so the stat index lives under --out. Entry keys are absolute paths, so relocating the index file is safe. Case 2 — `cluster-only --graph <elsewhere>/graphify-out/graph.json`: outputs (GRAPH_REPORT.md, re-clustered graph.json, labels, analysis, html) were written to the CWD's graphify-out/, ignoring where --graph lives. They now write beside the input graph when it sits in a graphify-out/ dir (another project/tenant's output), while still falling back to the CWD for an arbitrary archived backup/graph.json — the restore-into-place workflow #934 pins. Regression tests for both cases; #934 still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
bdc6e531a6
commit
c5db9ffb06
+8
-4
@@ -100,11 +100,15 @@ def _stat_index_file(root: Path) -> Path:
|
||||
return base / "cache" / "stat-index.json"
|
||||
|
||||
|
||||
def _ensure_stat_index(root: Path) -> None:
|
||||
def _ensure_stat_index(root: Path, cache_root: "Path | None" = None) -> None:
|
||||
global _stat_index, _stat_index_root, _stat_index_dirty
|
||||
if _stat_index_root is not None:
|
||||
return
|
||||
_stat_index_root = Path(root).resolve()
|
||||
# The stat index only determines the cache FILE location (entry keys are
|
||||
# absolute paths), so honoring an explicit cache_root keeps detect()'s
|
||||
# word-count cache under the requested --out dir instead of polluting the
|
||||
# scanned corpus with a stray graphify-out/ (#1747).
|
||||
_stat_index_root = Path(cache_root if cache_root is not None else root).resolve()
|
||||
p = _stat_index_file(_stat_index_root)
|
||||
if p.exists():
|
||||
try:
|
||||
@@ -212,7 +216,7 @@ def file_hash(path: Path, root: Path = Path(".")) -> str:
|
||||
return digest
|
||||
|
||||
|
||||
def cached_word_count(path: Path, root: Path, compute) -> int:
|
||||
def cached_word_count(path: Path, root: Path, compute, cache_root: "Path | None" = None) -> int:
|
||||
"""Word count with the same (size, mtime_ns) stat-fastpath cache as
|
||||
:func:`file_hash`, persisted in the shared stat index.
|
||||
|
||||
@@ -228,7 +232,7 @@ def cached_word_count(path: Path, root: Path, compute) -> int:
|
||||
global _stat_index_dirty
|
||||
p = _normalize_path(Path(path))
|
||||
root = _normalize_path(Path(root))
|
||||
_ensure_stat_index(root)
|
||||
_ensure_stat_index(root, cache_root=cache_root)
|
||||
abs_key = str(p.resolve())
|
||||
st: "os.stat_result | None" = None
|
||||
try:
|
||||
|
||||
+14
-2
@@ -1046,7 +1046,19 @@ def dispatch_command(cmd: str) -> None:
|
||||
gods = god_nodes(G)
|
||||
surprises = surprising_connections(G, communities)
|
||||
stages.mark("analyze")
|
||||
out = watch_path / _GRAPHIFY_OUT
|
||||
# Where outputs (GRAPH_REPORT.md, re-clustered graph.json, labels,
|
||||
# analysis, html) land. When `--graph` points at a graph INSIDE a
|
||||
# graphify-out/ dir (another project/tenant's output), write beside it,
|
||||
# not into a stray graphify-out/ in the CWD (#1747). But when `--graph`
|
||||
# points at an arbitrary path — e.g. a `backup/graph.json` archived
|
||||
# before re-clustering (#934) — fall back to the CWD's graphify-out/,
|
||||
# which is the restore-into-place workflow that test pins. The default
|
||||
# (no --graph) case already has graph_json under watch_path/graphify-out.
|
||||
_out_name = Path(_GRAPHIFY_OUT).name
|
||||
if graph_override is not None and graph_json.parent.name == _out_name:
|
||||
out = graph_json.parent
|
||||
else:
|
||||
out = watch_path / _GRAPHIFY_OUT
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
labels_path = out / ".graphify_labels.json"
|
||||
existing_labels: dict[int, str] = {}
|
||||
@@ -2090,7 +2102,7 @@ def dispatch_command(cmd: str) -> None:
|
||||
unchanged_total = sum(len(v) for v in detection.get("unchanged_files", {}).values())
|
||||
else:
|
||||
print(f"[graphify extract] scanning {target}")
|
||||
detection = _detect(target, google_workspace=google_workspace or None, extra_excludes=cli_excludes or None)
|
||||
detection = _detect(target, google_workspace=google_workspace or None, extra_excludes=cli_excludes or None, cache_root=out_root)
|
||||
files_by_type = detection.get("files", {})
|
||||
code_files = [Path(p) for p in files_by_type.get("code", [])]
|
||||
doc_files = [Path(p) for p in files_by_type.get("document", [])]
|
||||
|
||||
+4
-2
@@ -1065,7 +1065,7 @@ def _resolves_under_root(path: Path, root: Path) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None) -> dict:
|
||||
def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None, cache_root: Path | None = None) -> dict:
|
||||
root = root.resolve()
|
||||
if follow_symlinks is None:
|
||||
follow_symlinks = False
|
||||
@@ -1082,8 +1082,10 @@ def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace:
|
||||
def _wc(path: Path) -> int:
|
||||
# Cache word counts against each file's stat signature so unchanged
|
||||
# PDFs/docx aren't re-parsed on every run just to size the corpus (#1656).
|
||||
# cache_root (when given, e.g. from `extract --out`) keeps this cache out
|
||||
# of the scanned corpus (#1747).
|
||||
from graphify import cache as _cache
|
||||
return _cache.cached_word_count(path, root, count_words)
|
||||
return _cache.cached_word_count(path, root, count_words, cache_root=cache_root)
|
||||
|
||||
skipped_sensitive: list[str] = []
|
||||
unclassified: list[str] = []
|
||||
|
||||
Reference in New Issue
Block a user