"""Explicit worktree and cwd proof for PR review validation (#398).""" from __future__ import annotations import re from typing import Any _FULL_SHA = re.compile(r"^[0-9a-f]{40}$", re.IGNORECASE) _PWD_RE = re.compile( r"(?:^|\n)\s*(?:pwd|working\s+directory|cwd)\s*:\s*(\S+)", re.IGNORECASE, ) _HEAD_RE = re.compile( r"(?:git\s+rev-parse\s+head|observed\s+head\s+sha)\s*:\s*([0-9a-f]{7,40})", re.IGNORECASE | re.MULTILINE, ) _EXPECTED_HEAD_RE = re.compile( r"(?:expected\s+(?:pr\s+)?head\s+sha|candidate\s+head\s+sha|pinned\s+head)\s*:\s*([0-9a-f]{7,40})", re.IGNORECASE, ) _STATUS_RE = re.compile( r"git\s+status\s+(?:--short\s+--branch|--short|-sb)\s*:\s*(.+)", re.IGNORECASE, ) _VALIDATION_CMD_RE = re.compile( r"validation\s+command\s*:\s*(.+)", re.IGNORECASE, ) _GIT_C_CMD_RE = re.compile(r"git\s+-C\s+\S+", re.IGNORECASE) _CD_CMD_RE = re.compile(r"(?:^|&&\s*)cd\s+\S+", re.IGNORECASE) _BASELINE_CWD_RE = re.compile( r"baseline\s+(?:worktree|working\s+directory|cwd)\s*:\s*(\S+)", re.IGNORECASE, ) _BASELINE_SHA_RE = re.compile( r"baseline\s+(?:target\s+)?sha\s*:\s*([0-9a-f]{7,40})", re.IGNORECASE, ) _BASELINE_CMD_RE = re.compile( r"baseline\s+validation\s+command\s*:\s*(.+)", re.IGNORECASE, ) def _normalize_path(path: str) -> str: return (path or "").replace("\\", "/").rstrip("/") def _path_under_branches(path: str, project_root: str | None = None) -> bool: normalized = _normalize_path(path) if not normalized: return False if "/branches/" in f"{normalized}/": return True if normalized.endswith("/branches"): return True if project_root: root = _normalize_path(project_root) if normalized.startswith(f"{root}/"): rel = normalized[len(root) + 1 :] return rel == "branches" or rel.startswith("branches/") return False def _expand_sha(sha: str) -> str: return (sha or "").strip().lower() def _sha_matches(expected: str, observed: str) -> bool: exp = _expand_sha(expected) obs = _expand_sha(observed) if not exp or not obs: return False if len(exp) == 40 and len(obs) == 40: return exp == obs return obs.startswith(exp) or exp.startswith(obs) def _command_has_explicit_cwd(command: str, cwd: str) -> bool: text = (command or "").strip() if not text: return False if _GIT_C_CMD_RE.search(text): return True if _CD_CMD_RE.search(text): return True if cwd and cwd in text: return True return False def assess_validation_cwd_proof_report( report_text: str, *, validation_session: dict | None = None, project_root: str | None = None, ) -> dict[str, Any]: """Require cwd/HEAD proof before reviewer validation claims (#398).""" text = report_text or "" session = dict(validation_session or {}) reasons: list[str] = [] violations: list[str] = [] claims_validation = bool( session.get("validation_ran") or _VALIDATION_CMD_RE.search(text) or session.get("command") ) if not claims_validation: return { "proven": True, "block": False, "claims_validation": False, "reasons": [], "violations": [], "safe_next_action": "proceed", } expected_head = ( session.get("expected_head_sha") or session.get("candidate_head_sha") or "" ).strip() if not expected_head: match = _EXPECTED_HEAD_RE.search(text) expected_head = (match.group(1) if match else "").strip() observed_head = (session.get("observed_head_sha") or "").strip() if not observed_head: match = _HEAD_RE.search(text) observed_head = (match.group(1) if match else "").strip() cwd = ( session.get("working_directory") or session.get("cwd") or session.get("pwd") or "" ).strip() if not cwd: match = _PWD_RE.search(text) cwd = (match.group(1) if match else "").strip().rstrip(",.;") command = (session.get("command") or "").strip() if not command: match = _VALIDATION_CMD_RE.search(text) command = (match.group(1) if match else "").strip().rstrip(".;") if not cwd: reasons.append( "validation claimed without pwd/working-directory proof (#398)" ) elif not _path_under_branches(cwd, project_root): violations.append( f"validation cwd {cwd!r} is not under branches/ (#398)" ) reasons.append( "reviewer validation must run from a branches/ worktree, " "not the main checkout (#398)" ) if not observed_head: reasons.append( "validation claimed without git rev-parse HEAD / observed HEAD SHA " "proof (#398)" ) elif expected_head and not _sha_matches(expected_head, observed_head): violations.append( f"observed HEAD {observed_head} does not match expected " f"PR head {expected_head} (#398)" ) reasons.append("validation HEAD SHA must match pinned PR head (#398)") if not _STATUS_RE.search(text) and session.get("git_status") is None: reasons.append( "validation claimed without git status --short --branch proof (#398)" ) if command and cwd and not _command_has_explicit_cwd(command, cwd): if session.get("tool_working_directory") is not True: reasons.append( "validation command must use git -C , " "cd && ..., or tool-provided cwd metadata (#398)" ) baseline_ran = bool( session.get("baseline_validation_ran") or _BASELINE_CMD_RE.search(text) ) if baseline_ran: baseline_cwd = (session.get("baseline_worktree_path") or "").strip() if not baseline_cwd: match = _BASELINE_CWD_RE.search(text) baseline_cwd = (match.group(1) if match else "").strip().rstrip(",.;") if not baseline_cwd or not _path_under_branches(baseline_cwd, project_root): reasons.append( "baseline validation claimed without baseline worktree cwd " "under branches/ (#398)" ) baseline_sha = (session.get("baseline_target_sha") or "").strip() if not baseline_sha: match = _BASELINE_SHA_RE.search(text) baseline_sha = (match.group(1) if match else "").strip() if not baseline_sha: reasons.append( "baseline validation claimed without baseline target SHA (#398)" ) baseline_cmd = (session.get("baseline_command") or "").strip() if not baseline_cmd: match = _BASELINE_CMD_RE.search(text) baseline_cmd = (match.group(1) if match else "").strip() if not baseline_cmd: reasons.append( "baseline validation claimed without exact baseline command (#398)" ) proven = not reasons and not violations return { "proven": proven, "block": bool(violations) or not proven, "claims_validation": True, "expected_head_sha": expected_head or None, "observed_head_sha": observed_head or None, "working_directory": cwd or None, "reasons": reasons, "violations": violations, "safe_next_action": ( "before validation record pwd, git rev-parse HEAD, git status, " "expected PR head SHA; run commands with git -C or cd in the same line" if not proven else "proceed" ), }