"""Reviewer worktree ownership and safe-reuse proof verifier (#312).""" from __future__ import annotations import re from typing import Any from reviewer_worktree import REVIEW_WORKTREE_RE _SESSION_OWNED_RE = REVIEW_WORKTREE_RE _WORKTREE_PATH_RE = re.compile( r"(?:review worktree path|worktree path|session-owned worktree)\s*:\s*(\S+)", re.IGNORECASE, ) _BRANCHES_PATH_RE = re.compile(r"/branches/|\bbranches/", re.IGNORECASE) _MAIN_CHECKOUT_RE = re.compile( r"main checkout|not (?:the )?main checkout|outside branches/", re.IGNORECASE, ) _SAFE_REUSE_RE = re.compile(r"safe[- ]reuse proof", re.IGNORECASE) _REUSE_POLICY_RE = re.compile( r"(?:project policy|policy) (?:allows|allowing) (?:reuse|reset)", re.IGNORECASE, ) _CLEAN_TRACKED_RE = re.compile( r"(?:clean tracked state|tracked state\s*:\s*clean|no uncommitted tracked)", re.IGNORECASE, ) _CLEAN_UNTRACKED_RE = re.compile( r"(?:clean untracked state|untracked state\s*:\s*clean|no untracked files)", re.IGNORECASE, ) _NOT_OTHER_SESSION_RE = re.compile( r"not owned by (?:another|other) (?:active )?(?:task|session)", re.IGNORECASE, ) _BRANCH_BEFORE_RESET_RE = re.compile( r"(?:branch/head before reset|head before reset|branch before reset)\s*:\s*(\S+)", re.IGNORECASE, ) _RESET_TARGET_RE = re.compile( r"(?:reset target sha|reset target)\s*:\s*([0-9a-f]{7,40})", re.IGNORECASE, ) _DESTRUCTIVE_CMD_RE = re.compile( r"\bgit\b(?:\s+(?:-C\s+\S+\s+)?)?" r"(?:reset\s+--hard|clean(?:\s+-[A-Za-z]+)*|checkout\s+(?!HEAD\s+--)|switch\s+)", re.IGNORECASE, ) _WORKSPACE_NONE_RE = re.compile(r"workspace mutations\s*:\s*none", re.IGNORECASE) _WORKTREE_MUTATION_RE = re.compile( r"(?:worktree(?:/index)? mutations|destructive reset)\s*:\s*(?!none\b).+", re.IGNORECASE, ) _RESET_IN_REPORT_RE = re.compile(r"reset\s+--hard", re.IGNORECASE) def _command_text(entry) -> str: if isinstance(entry, dict): return str(entry.get("command") or "").strip() return str(entry or "").strip() def _destructive_commands(command_log: list | None) -> list[str]: return [ cmd for cmd in (_command_text(entry) for entry in (command_log or [])) if cmd and _DESTRUCTIVE_CMD_RE.search(cmd) ] def _extract_worktree_path(text: str, session: dict) -> str: path = (session.get("worktree_path") or "").strip() if path: return path match = _WORKTREE_PATH_RE.search(text or "") return match.group(1).strip() if match else "" def _is_session_owned_path(path: str) -> bool: normalized = (path or "").replace("\\", "/") return bool(_SESSION_OWNED_RE.search(normalized)) def _safe_reuse_fields_present(text: str) -> list[str]: missing: list[str] = [] if not _WORKTREE_PATH_RE.search(text): missing.append("exact worktree path") if not _BRANCHES_PATH_RE.search(text): missing.append("worktree inside branches/") if not _MAIN_CHECKOUT_RE.search(text): missing.append("worktree is not the main checkout") if not _NOT_OTHER_SESSION_RE.search(text): missing.append("worktree not owned by another active session") if not _CLEAN_TRACKED_RE.search(text): missing.append("clean tracked state") if not _CLEAN_UNTRACKED_RE.search(text): missing.append("clean untracked state") if not _BRANCH_BEFORE_RESET_RE.search(text): missing.append("branch/head before reset") if not _RESET_TARGET_RE.search(text): missing.append("reset target SHA") if not _REUSE_POLICY_RE.search(text): missing.append("explicit project policy allowing reuse/reset") return missing def assess_worktree_ownership_report( report_text: str, *, ownership_session: dict | None = None, command_log: list | None = None, ) -> dict[str, Any]: """Prove reviewer worktree ownership before reset or validation (#312).""" text = report_text or "" session = dict(ownership_session or {}) reasons: list[str] = [] worktree_path = _extract_worktree_path(text, session) destructive = _destructive_commands(command_log or session.get("command_log")) if not destructive and session.get("destructive_reset"): destructive = ["git reset --hard (session)"] session_owned = bool( session.get("session_owned") or (worktree_path and _is_session_owned_path(worktree_path)) ) safe_reuse = bool(session.get("safe_reuse") or _SAFE_REUSE_RE.search(text)) main_checkout = bool(session.get("main_checkout")) dirty_tracked = session.get("dirty_tracked") dirty_untracked = session.get("dirty_untracked") other_session_owned = bool(session.get("other_session_owned")) if worktree_path or destructive or session.get("validation_ran"): if not worktree_path: reasons.append("report missing exact review worktree path") elif main_checkout or "branches/" not in worktree_path.replace("\\", "/"): reasons.append("review worktree must be inside branches/, not the main checkout") elif not _BRANCHES_PATH_RE.search(worktree_path.replace("\\", "/")): reasons.append("review worktree path must be under branches/") if other_session_owned and not safe_reuse: reasons.append( "reused worktree appears owned by another active task/session; " "safe-reuse proof required" ) if dirty_tracked: reasons.append( "worktree has uncommitted tracked changes before reset or validation" ) if dirty_untracked and safe_reuse: reasons.append("safe-reuse proof requires clean untracked state") if safe_reuse and not session_owned: reasons.extend( f"safe-reuse proof missing {field}" for field in _safe_reuse_fields_present(text) ) if destructive: if not session_owned and not safe_reuse: reasons.append( "destructive worktree commands forbidden without session-owned " "worktree or safe-reuse proof" ) if _WORKSPACE_NONE_RE.search(text) and ( _RESET_IN_REPORT_RE.search(text) or any("reset" in c.lower() for c in destructive) ): reasons.append( "final report must not claim 'Workspace mutations: none' when " "git reset --hard occurred" ) if not _WORKTREE_MUTATION_RE.search(text) and not _RESET_IN_REPORT_RE.search(text): reasons.append( "destructive reset must be reported under Worktree/index mutations " "or destructive reset operations" ) if ( worktree_path and not session_owned and not safe_reuse and (destructive or session.get("validation_ran")) and not _is_session_owned_path(worktree_path) ): reasons.append( "reused branch-named worktree requires safe-reuse proof before reset or validation" ) proven = not reasons return { "proven": proven, "block": not proven, "reasons": reasons, "worktree_path": worktree_path or None, "session_owned": session_owned, "safe_reuse": safe_reuse, "destructive_commands": destructive, "safe_next_action": ( "use a fresh session-owned review worktree under branches/review-pr-* " "or document full safe-reuse proof before destructive reset" if reasons else "proceed" ), }