fix(watch): write manifest.json to the target's graphify-out (#2316)

_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 <target>` 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).
This commit is contained in:
Rishet Mehra authored and safishamsi committed 2026-08-01 11:36:30 +01:00
1 parent 875c7df8f6
commit 144a3ff8fb
1 file changed
+24 -7
+24 -7
View File
@@ -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 <target>`
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: