From 144a3ff8fb9fdebf2d3abb380a45fc17845aeb6b Mon Sep 17 00:00:00 2001 From: Rishet Mehra Date: Fri, 31 Jul 2026 14:42:13 +0530 Subject: [PATCH] fix(watch): write manifest.json to the target's graphify-out (#2316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _rebuild_code computed the correct output dir as `out = watch_path / _GRAPHIFY_OUT` and routed every artifact through it except the three save_manifest calls, which omitted manifest_path= and fell back to detect._MANIFEST_PATH — a module-import-time constant built from the relative out_path("graphify-out/manifest.json"), so it resolved against the process CWD. `graphify update ` therefore wrote the target's manifest into the invoking project and left the target with none. save_manifest also read-modify-writes that path and full-scan callers pass scan_corpus (#1908 pruning), so the invoking project's own rows were pruned as out-of-corpus and overwritten. Both projects lost their incremental baseline, not just the target. Anchor the manifest keys to watch_root at the same three calls. root= previously followed project_root, which is CWD-derived for a relative invocation, so `graphify update ../other` stored out-of-root files under absolute keys and broke #777/#1964 portability. project_root itself is left alone: the persisted graph deliberately rehomes source_file against it across invocation styles. Also pass cwd= to _git_head. Without it `git rev-parse HEAD` inherited the caller's directory, stamping the invoking repo's commit into the target's graph.json built_at_commit (cf. #2081). --- graphify/watch.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/graphify/watch.py b/graphify/watch.py index c87ec8e6f..001c6aa59 100644 --- a/graphify/watch.py +++ b/graphify/watch.py @@ -242,11 +242,21 @@ def _apply_resource_limits() -> None: pass -def _git_head() -> str | None: - """Return current git HEAD commit hash, or None outside a repo.""" +def _git_head(cwd: Path | str | None = None) -> str | None: + """Return current git HEAD commit hash, or None outside a repo. + + ``cwd`` selects the repository to ask (#2316). Without it the command + inherits the caller's working directory, so `graphify update ` + stamped the *invoking* repo's commit into the target's graph.json — the + same CWD-anchoring mistake as the manifest path, but writing wrong + provenance rather than a misplaced file. + """ import subprocess as _sp try: - r = _sp.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=3) + r = _sp.run( + ["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=3, + cwd=str(cwd) if cwd is not None else None, + ) return r.stdout.strip() if r.returncode == 0 else None except Exception: return None @@ -947,6 +957,10 @@ def _rebuild_code( return ok watch_root = watch_path.resolve() + # project_root stays CWD-anchored for a relative invocation on purpose: the + # persisted graph rehomes source_file across invocation styles against it + # (tests/test_watch.py:1389, :1428). The manifest is a different artifact + # with a different anchor — see the save_manifest calls below. project_root = Path.cwd().resolve() if not watch_path.is_absolute() else watch_root report_root = _report_root_label(watch_path) try: @@ -1114,7 +1128,7 @@ def _rebuild_code( # below still counts the doc as a rebuilt source. extract_targets = [p for p in code_files if p not in semantic_doc_files] - commit = _git_head() + commit = _git_head(cwd=watch_root) result = extract(extract_targets, cache_root=watch_root) if extract_targets else { "nodes": [], "edges": [], "hyperedges": [], "input_tokens": 0, "output_tokens": 0, @@ -1230,7 +1244,8 @@ def _rebuild_code( # scan but still exist on disk (newly excluded) are pruned # instead of surviving as phantom "deleted" entries (#1908). save_manifest( - detected["files"], kind="ast", root=project_root, + detected["files"], manifest_path=str(out / "manifest.json"), + kind="ast", root=watch_root, scan_corpus={f for _fl in detected["files"].values() for f in _fl}, ) except Exception: @@ -1273,7 +1288,8 @@ def _rebuild_code( from graphify.detect import save_manifest # Full-scan save: prune excluded-but-alive rows (#1908). save_manifest( - detected["files"], kind="ast", root=project_root, + detected["files"], manifest_path=str(out / "manifest.json"), + kind="ast", root=watch_root, scan_corpus={f for _fl in detected["files"].values() for f in _fl}, ) except Exception: @@ -1419,7 +1435,8 @@ def _rebuild_code( from graphify.detect import save_manifest # Full-scan save: prune excluded-but-alive rows (#1908). save_manifest( - detected["files"], kind="ast", root=project_root, + detected["files"], manifest_path=str(out / "manifest.json"), + kind="ast", root=watch_root, scan_corpus={f for _fl in detected["files"].values() for f in _fl}, ) except Exception: