@@ -67,6 +67,8 @@ def _invoke(
6767 label : str = "" ,
6868 status = None ,
6969 stream_json : bool = False ,
70+ env : dict | None = None ,
71+ extra_argv : list [str ] | None = None ,
7072) -> None :
7173 """Run the leaf's configured command in ``workdir``, feeding it ``prompt``.
7274
@@ -83,9 +85,10 @@ def _invoke(
8385 leaf is using right now. Ignored for non-claude families (e.g. a codex reviewer),
8486 which don't speak that format.
8587 """
86- argv = list (leaf .argv )
88+ argv = list (leaf .argv ) + list (extra_argv or [])
89+ run_env = {** os .environ , ** env } if env else None
8790 if leaf .interactive :
88- subprocess .run (argv + [prompt ], cwd = workdir )
91+ subprocess .run (argv + [prompt ], cwd = workdir , env = run_env )
8992 return
9093 # Headless: feed the prompt on stdin (a trailing positional would be swallowed
9194 # by a variadic --allowedTools) and tick a heartbeat, since `claude -p` prints
@@ -95,7 +98,7 @@ def _invoke(
9598 argv += ["--output-format" , "stream-json" , "--verbose" ]
9699 rc , _ = progress .run_with_heartbeat (
97100 argv , cwd = workdir , input_text = prompt , label = label , status = status ,
98- stream_json = use_stream )
101+ stream_json = use_stream , env = run_env )
99102 if rc != 0 :
100103 raise subprocess .CalledProcessError (rc , argv )
101104
@@ -358,10 +361,33 @@ def reviewer_input_paths(d: Path) -> list[Path]:
358361 "you can). Emit NEEDS-HUMAN for the always-human items (validation "
359362 "fitness-to-purpose, contested root-cause, ambiguous scope) — each NEEDS-HUMAN "
360363 "row becomes a §6 item the human must clear. Do not omit a row; use N/A with a "
361- "reason when an element does not apply."
364+ "reason when an element does not apply. "
365+ "Ground every cited path:line on the target source at $PDCA_TARGET (read-only); "
366+ "if $PDCA_TARGET is unset, ground against patch.diff alone — do NOT search other "
367+ "checkouts on the machine."
362368)
363369
364370
371+ def _reviewer_target (d : Path , cfg : Config ) -> Path | None :
372+ """The local target checkout the reviewer grounds its citations on, or None (#75).
373+
374+ Single-sourced from the brief's "Repo + branch target" via the same resolution
375+ publish uses (``_checkout_path`` — configured ``[publisher.checkouts]`` or the
376+ sibling convention). Returned only if it exists on disk; the reviewer is told to
377+ ground against ``$PDCA_TARGET`` and not to wander into other checkouts. Best-effort:
378+ any failure (no target, unresolved) yields None and the reviewer falls back to the diff.
379+ """
380+ from . import publish # lazy: publish imports leaves, avoid an import cycle
381+ try :
382+ repo_spec , _base , _slug = publish ._resolve_target (d )
383+ if not repo_spec :
384+ return None
385+ p = publish ._checkout_path (cfg , repo_spec )
386+ return p if p .exists () else None
387+ except Exception : # noqa: BLE001 — grounding access is best-effort, never fatal
388+ return None
389+
390+
365391def run_review (d : Path , cfg : Config ) -> None :
366392 inputs = reviewer_input_paths (d )
367393 assert (d / "build-notes.md" ) not in inputs , "independence contract violated"
@@ -385,12 +411,20 @@ def _run_review_sandboxed(d: Path, cfg: Config) -> None:
385411 src = d / name
386412 if src .exists ():
387413 shutil .copy2 (src , sandbox / name )
414+ # Ground citations on the brief's target checkout (#75): name it via $PDCA_TARGET
415+ # so the reviewer doesn't wander into unrelated checkouts, and grant read access
416+ # for the claude family (--add-dir). Independence holds — the target is the
417+ # upstream source, not build-notes.md.
418+ target = _reviewer_target (d , cfg )
419+ env = {"PDCA_TARGET" : str (target )} if target else None
420+ extra_argv = ["--add-dir" , str (target )] if target and cfg .reviewer .family == "claude" else None
388421 try :
389422 _invoke (
390423 cfg .reviewer , sandbox , _REVIEW_PROMPT ,
391424 label = f"Check review { d .name } " ,
392425 status = lambda : progress .bundle_activity (sandbox , ("check-review.md" ,)),
393426 stream_json = True , # Tier 3 (no-op unless the reviewer family is claude)
427+ env = env , extra_argv = extra_argv ,
394428 )
395429 except Exception as exc : # a failed reviewer (e.g. dropped connection) must
396430 _review_unavailable (d , f"reviewer leaf failed: { exc } " ) # not crash the cycle
0 commit comments