"""NEXT SESSION + paste-ready regeneration for governance-sync (§GSP).""" from __future__ import annotations import re from dataclasses import dataclass from pathlib import Path from adapters.config import OverseerConfig from cli.docs_paths import join_docs_rel from tools.governance_hygiene.parse import ( compact_step_id, normalize_status, parse_queue_rows, phase_tokens, ) from tools.freeze_authorization.resolve import freeze_authorization_state from tools.freeze_reviewer.artifact import ( artifact_digest, extract_existing_stamp, parse_artifact, ) from tools.adversarial_freeze.authorize import ( adversarial_authorization_state, aff_hold_bypassed, author_loop_complete, frv_authorizing_freeze_paths, ) from tools.governance_hygiene.types import QueueRow # Closed vocabulary from policy/model-labels.yaml ``labels[].display`` (frozen for GS-PASTE). MODEL_DISPLAY_LABELS = frozenset( { "Thinking", "Auto", "Thinking → Auto", "Operator + Auto", } ) SPLIT_MODEL = "Thinking → Auto" REASON_ZERO = "zero_open_rows" REASON_MULTIPLE = "multiple_open_rows" REASON_INVALID_MODEL = "invalid_model_label" REASON_SPLIT = "split_undetermined" REASON_WORKSPACE_MARKER = "workspace_marker_absent" REASON_LAND_A_WAIT = "land_a_in_progress" REASON_LAND_PHASE_UNREADABLE = "land_phase_unreadable" REASON_FREEZE_NOT_SUBSTANTIVE = "freeze_not_substantive" ADVISORY_MECHANICAL_ONLY = "mechanical_only" ADVISORY_OPERATOR_BLOCK = "operator_block" ADVISORY_OPERATOR_BLOCK_MALFORMED = "operator_block_malformed" ADVISORY_LEDGER_CHAIN_BROKEN = "ledger_chain_broken" ADVISORY_FORGED_SUBSTANTIVE_GATE = "forged_substantive_gate" ADVISORY_UNREADABLE_GATE = "unreadable_gate" ADVISORY_ADVERSARIAL_FREEZE_PENDING = "adversarial_freeze_pending" ADVISORY_ADVERSARIAL_FREEZE_SKIP = "adversarial_freeze_skip" AUTO_MODEL = "Auto" OPERATOR_AUTO_MODEL = "Operator + Auto" OPEN_STATUSES = frozenset({"TODO", "NEXT", "WIP"}) DONE_STATUSES = frozenset({"DONE", "MERGED"}) NEXT_MARKER_RE = re.compile( r"", re.IGNORECASE, ) # --- PMHF land protocol (§PMHF.3 / §PMHF.4) --- LAND_PHASE_A = "land-a" LAND_PHASE_B = "land-b" LAND_PHASE_UNREADABLE = "unreadable" # §PMHF.4.2 closed vocabulary — legacy handovers without the marker attribute. # Frozen: bare "open PR" / "Tier 3" alone must NOT count as land-a. LAND_A_VOCABULARY = ( "land-phase: land-a", "wait for merge", "awaiting merge", "stop for tier 3 merge", "→ main (land-a)", "(land-a)", ) LAND_B_VOCABULARY = ( "land-phase: land-b", "land-b (post-merge sync)", ) # §PMHF.5.2 step 7 frozen remediation string (prefix). LAND_B_REMEDIATION = ( "land-b required: ok governance-sync --dry-run then apply; " "paste land-b; do not re-paste land-a" ) _LAND_PHASE_ATTR_RE = re.compile(r"\bland-phase=([^\s>]+)", re.IGNORECASE) _PASTE_FENCE_RE = re.compile( r"### Paste-ready prompt[^\n]*\n+```[a-zA-Z]*\n([\s\S]*?)```", ) _LAND_ID_LINE_RE = re.compile(r"^\s*ID:\s*(.+?)\s*$", re.MULTILINE | re.IGNORECASE) _NEXT_ID_CELL_RE = re.compile(r"\|\s*\*\*ID\*\*\s*\|\s*(.+?)\s*\|", re.IGNORECASE) _LAND_PARENTHETICAL_RE = re.compile(r"\s*\((?:land-[ab])\)\s*$", re.IGNORECASE) _PASTE_PR_RE = re.compile(r"\bPR #(\d+)\b") # Tokens too generic to identify a land slice (§PMHF.3.3 rejected match rule). _GENERIC_LAND_TOKENS = frozenset({"", "→", "->"}) def extract_paste_fence_body(handover_text: str) -> str | None: """Return the paste-ready fence body, or None when no fence exists (§PMHF.4.2).""" match = _PASTE_FENCE_RE.search(handover_text) return match.group(1) if match else None def resolve_land_phase(handover_text: str) -> str | None: """Resolve land posture per §PMHF.4. Returns ``"land-a"`` | ``"land-b"`` | ``None`` (no land posture) | ``"unreadable"`` (unknown attribute value or conflicting vocabulary). The marker HTML attribute beats vocabulary fallback when present. """ marker_match = NEXT_MARKER_RE.search(handover_text) if marker_match: attr = _LAND_PHASE_ATTR_RE.search(marker_match.group(0)) if attr: value = attr.group(1).strip().lower() if value in {LAND_PHASE_A, LAND_PHASE_B}: return value return LAND_PHASE_UNREADABLE body = extract_paste_fence_body(handover_text) if body is None: return None lowered = body.lower() is_land_a = any(token in lowered for token in LAND_A_VOCABULARY) is_land_b = any(token in lowered for token in LAND_B_VOCABULARY) if is_land_a and is_land_b: return LAND_PHASE_UNREADABLE if is_land_a: return LAND_PHASE_A if is_land_b: return LAND_PHASE_B return None def extract_land_id(handover_text: str) -> str | None: """Land ID from paste ``ID:`` line, falling back to the NEXT ``| **ID** |`` cell.""" body = extract_paste_fence_body(handover_text) if body is not None: match = _LAND_ID_LINE_RE.search(body) if match: return match.group(1).strip() cell = _NEXT_ID_CELL_RE.search(handover_text) if cell: return cell.group(1).strip().strip("*").strip() return None def strip_land_parenthetical(land_id: str) -> str: """Strip a trailing ``(land-a)`` / ``(land-b)`` so tokens align with queue rows (§PMHF.3.3).""" return _LAND_PARENTHETICAL_RE.sub("", land_id).strip() def _meaningful_land_tokens(label: str, main_branch: str) -> set[str]: generic = set(_GENERIC_LAND_TOKENS) | {main_branch.lower()} return {token.lower() for token in phase_tokens(label)} - generic def land_queue_conflict( roadmap_text: str, land_id: str, *, main_branch: str = "main", ) -> bool: """§PMHF.3.3: DONE/MERGED **land** queue row matching the current land-a ID. A candidate row must be land-shaped (``{slice} → main``) and share a slice-identifying token with the land-a ID (never just ``→ main`` — the frozen rejected match rule protects historical other-slice land rows). Hyphen-split fragments alone are not slice-identifying: land-a ``GSW-FIX → main`` must not conflict with historical ``GFG-D2-FIX → main`` solely on the fragment ``FIX``. """ id_tokens = _meaningful_land_tokens(strip_land_parenthetical(land_id), main_branch) if not id_tokens: return False land_row_re = re.compile(r"(?:→|->)\s*" + re.escape(main_branch), re.IGNORECASE) for row in parse_queue_rows(roadmap_text): if normalize_status(row.status) not in DONE_STATUSES: continue if not land_row_re.search(row.phase_label): continue shared = id_tokens & _meaningful_land_tokens(row.phase_label, main_branch) if shared and not _only_hyphen_fragments(shared, id_tokens): return True return False def _only_hyphen_fragments(shared: set[str], id_tokens: set[str]) -> bool: """True when every shared token is only a hyphen/space fragment of a longer id token. Compound id tokens (containing ``-`` or an arrow) supply the fragment set. A shared token that is itself compound is never treated as a bare fragment. """ compounds = {token for token in id_tokens if "-" in token or "→" in token or "->" in token} if not compounds: return False fragment_parts: set[str] = set() for compound in compounds: for part in re.split(r"[\s/—\-→>]+", compound): cleaned = part.strip().lower() if cleaned and cleaned not in _GENERIC_LAND_TOKENS: fragment_parts.add(cleaned) for token in shared: if token in compounds or "-" in token or "→" in token or "->" in token: return False if token not in fragment_parts: return False return True def extract_paste_pr_number(handover_text: str) -> int | None: """First ``PR #`` named in the paste-ready fence (§PMHF.5.3).""" body = extract_paste_fence_body(handover_text) if body is None: return None match = _PASTE_PR_RE.search(body) return int(match.group(1)) if match else None def set_marker_land_phase(handover_text: str, land_phase: str | None) -> str: """Set or clear the ``land-phase=`` attribute on the existing NEXT marker (§PMHF.4.1).""" match = NEXT_MARKER_RE.search(handover_text) if not match: return handover_text marker = match.group(0) updated = re.sub(r"\s+land-phase=[^\s>]+", "", marker, flags=re.IGNORECASE) if land_phase: updated = re.sub(r"\s*-->$", f" land-phase={land_phase} -->", updated) return handover_text.replace(marker, updated, 1) @dataclass(frozen=True) class LandBPlan: """Planned land-b emission for the slice currently mid-land (§PMHF.3.4).""" slice_id: str land_a_id: str def _slice_from_land_id(land_id: str, main_branch: str) -> str: base = strip_land_parenthetical(land_id) base = re.sub( r"\s*(?:→|->)\s*" + re.escape(main_branch) + r"\s*$", "", base, flags=re.IGNORECASE, ) return base.strip() def plan_land_b( handover_text: str, *, d1: str | None, freshness_state: str | None = None, merged_pr_signal: bool = False, main_branch: str = "main", ) -> LandBPlan | None: """Return a land-b plan when NEXT is land-a and closeout is post-merge incomplete. §PMHF.3.4 rule 1: emit land-b for the same slice — never re-emit land-a. """ if resolve_land_phase(handover_text) != LAND_PHASE_A: return None post_merge_incomplete = ( d1 == "drifted" or freshness_state in {"drifted", "stale_marker"} or merged_pr_signal ) if not post_merge_incomplete: return None land_id = extract_land_id(handover_text) or "" slice_id = _slice_from_land_id(land_id, main_branch) if land_id else "" return LandBPlan(slice_id=slice_id or "land", land_a_id=land_id) def render_land_b_next_session( plan: LandBPlan, *, config: OverseerConfig, sync_date: str, ) -> str: """Render the land-b NEXT SESSION body (frozen shape §PMHF.3.2).""" main = config.vcs.git.main_branch or "main" lines = [ f"## NEXT SESSION — {plan.slice_id} land-b (post-merge sync)", "", f"**Date:** {sync_date} ", f"**Current position:** {plan.slice_id} land-a → land-b ", "**Model:** Auto", "", "### THE ONE NEXT STEP — **Model: Auto**", "", f"Post-merge governance closeout for **{plan.slice_id}**: sync ROADMAP + HANDOVER to " f"merged `{main}` so NEXT and paste no longer point at the pre-merge posture.", "", "| | |", "| --- | --- |", f"| **ID** | **{plan.slice_id} land-b (post-merge sync)** |", f"| **Repo** | **{config.repo.name}** |", f"| **Read first** | {_docs_read_first(config)} |", f"| **Hard stops** | no silent commits to `{main}`; no Cursor-only dependency; " "no freeze/BV redesign |", ] return "\n".join(lines) def render_land_b_paste(plan: LandBPlan, *, config: OverseerConfig) -> str: """Render the frozen land-b paste (§PMHF.3.2) inside the paste-ready anchor.""" main = config.vcs.git.main_branch or "main" fence_lines = [ "Model: Auto", f"ID: {plan.slice_id} land-b (post-merge sync)", "land-phase: land-b", "", "Deliver:", f"1. Fetch/pull latest {main} (regime-appropriate)", "2. ok governance-sync --dry-run then apply when the plan is correct", "3. Regenerate NEXT + paste so they no longer say wait-for-merge / land-a", "4. Feature-branch commit bundling ROADMAP + HANDOVER (SD-17); open docs PR if needed", "5. ok status --exit-code → 0 and ok land-closeout → 0 before claiming land complete", "", f"Hard stops: no silent commits to {main}; no Cursor-only dependency; " "no freeze/BV redesign.", ] return "\n".join( [ f"### Paste-ready prompt — {plan.slice_id} land-b", "", "```text", "\n".join(fence_lines), "```", ] ) HARD_STOPS = ( "No merge to `main` without Tier 3 · no secrets · no live posture flips · " "no inventing NEXT when ambiguous" ) BUILD_VERIFICATION_LINES = ( "Governance gates (mandatory — remind only; silence is not pass):", "- Freeze review: /freeze-review-loop before Thinking freeze → DONE; " "ok review --freeze when CLI green", "- Build verification: /build-verification-review after every Auto " "{step}b before ROADMAP DONE", ) @dataclass(frozen=True) class NextRegenDecision: """Outcome of unambiguous-NEXT selection + emission planning.""" row: QueueRow | None reason: str | None emit_model: str | None step_label: str | None is_step_b: bool advisory: str | None = None def normalize_model_cell(model: str) -> str: """Strip surrounding markdown bold from a queue Model cell.""" text = model.strip() if text.startswith("**") and text.endswith("**") and len(text) > 4: text = text[2:-2].strip() return text def select_unambiguous_next_row( roadmap_text: str, ) -> tuple[QueueRow | None, str | None]: """Return the sole open queue row, or ``(None, reason)`` when ambiguous. Reasons: ``zero_open_rows`` | ``multiple_open_rows`` | ``invalid_model_label``. Split detection is separate (§GSP.5.2) and may add ``split_undetermined``. """ rows = parse_queue_rows(roadmap_text) open_rows = [row for row in rows if normalize_status(row.status) in OPEN_STATUSES] if len(open_rows) == 0: return None, REASON_ZERO if len(open_rows) > 1: return None, REASON_MULTIPLE row = open_rows[0] model = normalize_model_cell(row.model) if not model or model not in MODEL_DISPLAY_LABELS: return None, REASON_INVALID_MODEL return row, None def last_done_row(roadmap_text: str) -> QueueRow | None: """Last DONE/MERGED queue row by table order, or None.""" done: QueueRow | None = None for row in parse_queue_rows(roadmap_text): if normalize_status(row.status) in DONE_STATUSES: done = row return done def _path_under_docs(repo_root: Path, candidate: Path) -> Path | None: """Return resolved path if it stays under ``repo_root/docs``; else None.""" docs = (repo_root / "docs").resolve() try: resolved = candidate.resolve() resolved.relative_to(docs) except (OSError, ValueError): return None if not resolved.is_file(): return None return resolved def discover_freeze_candidates( repo_root: Path, step_id: str, deliverable: str, ) -> list[Path]: """Basename-only freeze discovery under ``docs/`` (§GSP.5.2 / §GSP.9).""" docs = repo_root / "docs" if not docs.is_dir(): return [] tokens: set[str] = set() compact = step_id.strip() if compact: tokens.add(compact) base = re.sub(r"-[ab]$", "", compact, flags=re.IGNORECASE) if base: tokens.add(base) found: list[Path] = [] seen: set[Path] = set() # Cap: only top-level docs/*.md names (no recursive walk). for path in sorted(docs.glob("*.md")): name_lower = path.name.lower() if not name_lower.startswith("phase-"): continue for token in tokens: if token.lower() in name_lower: resolved = _path_under_docs(repo_root, path) if resolved is not None and resolved not in seen: seen.add(resolved) found.append(resolved) break # Deliverable-cited path under docs/ for match in re.finditer(r"`?(docs/[^`\s|]+\.md)`?", deliverable): rel = match.group(1) if ".." in Path(rel).parts: continue candidate = repo_root / rel resolved = _path_under_docs(repo_root, candidate) if resolved is not None and resolved not in seen: seen.add(resolved) found.append(resolved) return found def decide_split_emission( row: QueueRow, repo_root: Path, *, config: OverseerConfig, ) -> tuple[str | None, str | None, bool, str | None]: """Return ``(emit_model, reason_or_None, is_step_b, advisory)`` (§FRV.6.5 / §FRV.6.5.1). Gates both ``Thinking → Auto`` and plain ``Auto``. Excludes ``Operator + Auto``. ``mechanical_only`` travels as advisory, never as reason (§FRV.6.6). """ model = normalize_model_cell(row.model) if model not in {SPLIT_MODEL, AUTO_MODEL}: return model, None, False, None step_id = compact_step_id(row.phase_label) candidates = discover_freeze_candidates(repo_root, step_id, row.deliverable) if model == AUTO_MODEL: if not candidates: return "Auto", None, False, None auths = [ freeze_authorization_state(repo_root, path, phase_id=step_id, config=config) for path in candidates ] if any(a.state == "blocked_by_operator" for a in auths): blocked = next(a for a in auths if a.state == "blocked_by_operator") return None, REASON_FREEZE_NOT_SUBSTANTIVE, False, blocked.advisory if any(a.state == "substantive" for a in auths): return "Auto", None, False, None return None, REASON_FREEZE_NOT_SUBSTANTIVE, False, None # Thinking → Auto if not candidates: return "Thinking", None, False, None auths = [ freeze_authorization_state(repo_root, path, phase_id=step_id, config=config) for path in candidates ] if any(a.state == "blocked_by_operator" for a in auths): blocked = next(a for a in auths if a.state == "blocked_by_operator") return "Thinking", None, False, blocked.advisory or ADVISORY_OPERATOR_BLOCK has_substantive = any(a.state == "substantive" for a in auths) has_non_pass = any(a.state == "non_pass" for a in auths) if has_substantive and has_non_pass: return None, REASON_SPLIT, False, None if has_substantive and normalize_status(row.status) in OPEN_STATUSES: return "Auto", None, True, None if any(a.state == "mechanical_only" for a in auths) and not has_substantive: return "Thinking", None, False, ADVISORY_MECHANICAL_ONLY return "Thinking", None, False, None def check_workspace_next_marker( handover_text: str, config: OverseerConfig, ) -> str | None: """Return ambiguity reason when workspace is set and NEXT marker is absent.""" if config.workspace is None: return None if NEXT_MARKER_RE.search(handover_text): return None return REASON_WORKSPACE_MARKER def plan_next_regen( *, roadmap_text: str, handover_text: str, config: OverseerConfig, repo_root: Path, ) -> NextRegenDecision: """Full §GSP.4 + §GSP.5.2 + §GSP.5.4 + §AFF.7 decision for NEXT/paste regen.""" row, reason = select_unambiguous_next_row(roadmap_text) if row is None: return NextRegenDecision(None, reason, None, None, False) marker_reason = check_workspace_next_marker(handover_text, config) if marker_reason: return NextRegenDecision(None, marker_reason, None, None, False) emit_model, split_reason, is_step_b, advisory = decide_split_emission( row, repo_root, config=config ) if split_reason: return NextRegenDecision(None, split_reason, None, None, False) step_id = compact_step_id(row.phase_label) if normalize_model_cell(row.model) == SPLIT_MODEL: step_label = f"{re.sub(r'-[ab]$', '', step_id, flags=re.IGNORECASE)}-{'b' if is_step_b else 'a'}" else: step_label = step_id emit_model, is_step_b, advisory = _apply_adversarial_freeze_hold( row=row, repo_root=repo_root, config=config, emit_model=emit_model, is_step_b=is_step_b, advisory=advisory, step_id=step_id, ) return NextRegenDecision(row, None, emit_model, step_label, is_step_b, advisory=advisory) def _apply_adversarial_freeze_hold( *, row: QueueRow, repo_root: Path, config: OverseerConfig, emit_model: str | None, is_step_b: bool, advisory: str | None, step_id: str, ) -> tuple[str | None, bool, str | None]: """Apply AFF Trigger A / Trigger B after FRV split emission (§AFF.7.1).""" model = normalize_model_cell(row.model) if model == OPERATOR_AUTO_MODEL: return emit_model, is_step_b, advisory if aff_hold_bypassed(config): return emit_model, is_step_b, advisory if emit_model is None: return emit_model, is_step_b, advisory candidates = discover_freeze_candidates(repo_root, step_id, row.deliverable) # Trigger A — FRV would emit Auto if emit_model == "Auto" or is_step_b: if not candidates: return emit_model, is_step_b, advisory authorizing = frv_authorizing_freeze_paths( repo_root, candidates, phase_id=step_id, config=config ) if not authorizing: return emit_model, is_step_b, advisory states = [ adversarial_authorization_state(repo_root, path, config=config) for path in authorizing ] if all(s.state in {"pass", "skipped", "off"} for s in states): if any(s.state == "skipped" for s in states): return emit_model, is_step_b, ADVISORY_ADVERSARIAL_FREEZE_SKIP return emit_model, is_step_b, advisory # pending or absent → hold return "Thinking", False, ADVISORY_ADVERSARIAL_FREEZE_PENDING # Trigger B — Thinking emission after author freeze-review-loop complete if emit_model != "Thinking": return emit_model, is_step_b, advisory if not candidates: return emit_model, is_step_b, advisory completed_states: list[str] = [] for path in candidates: frv = freeze_authorization_state( repo_root, path, phase_id=step_id, config=config ) stamp_digest = None current_digest = "" try: rel = path.resolve().relative_to(repo_root.resolve()).as_posix() parsed = parse_artifact(path, rel_path=rel) current_digest = artifact_digest(parsed) stamp = extract_existing_stamp(parsed) if isinstance(stamp, dict): raw = stamp.get("artifact_digest") stamp_digest = raw if isinstance(raw, str) else None except Exception: continue if not author_loop_complete( frv.state, stamp_digest=stamp_digest, current_digest=current_digest, ): continue aff = adversarial_authorization_state(repo_root, path, config=config) if aff.state == "off": continue completed_states.append(aff.state) if not completed_states: return emit_model, is_step_b, advisory if any(s in {"pending", "absent"} for s in completed_states): return "Thinking", False, ADVISORY_ADVERSARIAL_FREEZE_PENDING # pass / skipped / off — leave Thinking emission (do not rewrite to Auto) return emit_model, is_step_b, advisory def _branch_value(config: OverseerConfig, phase_id: str) -> str: pattern = config.vcs.git.feature_branch_pattern if not pattern or "{slug}" not in pattern: return "`unknown`" slug = re.sub(r"[^a-z0-9]+", "-", phase_id.lower()).strip("-") if not slug: return "`unknown`" return f"`{pattern.replace('{slug}', slug)}`" def _docs_read_first(config: OverseerConfig) -> str: roadmap = join_docs_rel(config.repo.root_relative_docs, config.docs.roadmap) handover = join_docs_rel(config.repo.root_relative_docs, config.docs.handover) return f"`{roadmap}`; `{handover}`" def _landed_table_row(roadmap_text: str) -> str: done = last_done_row(roadmap_text) if done is None: return "| _(none)_ | Queue has no DONE/MERGED row yet |" slice_id = compact_step_id(done.phase_label) deliverable = done.deliverable.strip() or "_(no deliverable)_" return f"| **{slice_id}** | {deliverable} |" def _current_position(roadmap_text: str, open_row: QueueRow) -> str: done = last_done_row(roadmap_text) open_id = compact_step_id(open_row.phase_label) if done is None: return f"→ {open_id}" return f"{compact_step_id(done.phase_label)} → {open_id}" def ensure_primary_next_marker(handover_text: str, config: OverseerConfig) -> str: """Insert PRIMARY next marker above NEXT SESSION when absent and allowed.""" if NEXT_MARKER_RE.search(handover_text): return handover_text if config.workspace is not None: return handover_text marker = "" heading = "## NEXT SESSION" if heading not in handover_text: return handover_text return handover_text.replace(heading, f"{marker}\n{heading}", 1) def render_next_session( *, decision: NextRegenDecision, roadmap_text: str, config: OverseerConfig, sync_date: str, ) -> str: """Render ``next-session`` anchor body (§GSP.5.3) — no paste fence.""" assert decision.row is not None assert decision.emit_model is not None assert decision.step_label is not None row = decision.row model = decision.emit_model step_id = decision.step_label title = phase_tokens(row.phase_label)[0] if phase_tokens(row.phase_label) else step_id branch = _branch_value(config, step_id) read_first = _docs_read_first(config) position = _current_position(roadmap_text, row) landed = _landed_table_row(roadmap_text) if decision.advisory == ADVISORY_ADVERSARIAL_FREEZE_PENDING: one_next = ( "Adversarial freeze review (attack posture) — different chat from " "the author; try to kill the freeze before Auto may start." ) else: one_next = row.deliverable.strip() or f"Advance {step_id}." lines = [ f"## NEXT SESSION — {title}", "", f"**Date:** {sync_date} ", f"**Current position:** {position} ", f"**Model:** {model}", "", "### What just landed", "", "| Slice | Deliverable |", "| --- | --- |", landed, "", f"### THE ONE NEXT STEP — **Model: {model}**", "", one_next, "", "| | |", "| --- | --- |", f"| **ID** | **{step_id}** |", f"| **Branch** | {branch} |", f"| **Repo** | **{config.repo.name}** |", f"| **Read first** | {read_first} |", f"| **Hard stops** | {HARD_STOPS} |", ] return "\n".join(lines) def render_paste_ready( *, decision: NextRegenDecision, config: OverseerConfig, ) -> str: """Render ``paste-ready-prompt`` anchor body (§GSP.5.3 / H16 / §AFF.7.2).""" assert decision.row is not None assert decision.emit_model is not None assert decision.step_label is not None row = decision.row model = decision.emit_model step_id = decision.step_label branch = _branch_value(config, step_id).strip("`") read_first = _docs_read_first(config) title = phase_tokens(row.phase_label)[0] if phase_tokens(row.phase_label) else step_id if decision.advisory == ADVISORY_ADVERSARIAL_FREEZE_PENDING: cited = None for match in re.finditer(r"`?(docs/[^`\s|]+\.md)`?", row.deliverable): cited = match.group(1) break artifact_line = cited or (row.deliverable.strip() or step_id) fence_lines = [ f"{step_id} — adversarial freeze review ({config.repo.name}).", "", "Model: Thinking", f"Repo: {config.repo.name}", f"Branch: {branch}", f"Step: {step_id}", "Authority: authoritative", "Posture: attack — try to kill the freeze; do not rubber-stamp close", "", "This MUST be a different chat from the author freeze-review-loop session.", "If this is the author session, stop. Prefer a different model than the author", "(preference only; the kit does not gate model labels).", "", f"Read the freeze artifact: `{artifact_line}`", f"Also read: {read_first}.", "", "Cite every finding as path:line.", "", "Mandatory Review-record / restamp / FRV-rebind / AFF-append transaction:", "1. Finish the review and decide pass|findings|blocked WITHOUT appending yet.", "2. Write the adversarial round into the freeze artifact Review-record table;", " complete every other artifact edit now.", "3. Run ok review --freeze ; require mechanical pass; stamp digest", " must equal current FRV artifact_digest D.", "4. If Auto requires FRV authorization, append freeze_review pass for the same", " frozen_spec + artifact_digest D; verify the ledger.", "5. Append adversarial_freeze as the final binding operation with the", " actual verdict and same frozen_spec + artifact_digest D.", " On pass: §AFF.5.2 fields only — actor_role: verifier,", " actor_session_id: , phase_id, round,", " reviewer_model, frozen_spec, artifact_digest,", " aff_verdict: pass, aff_posture: attack, and", " producer_session_id: ", " (do not scrape IDE session ids). Verify the ledger again.", "6. Do not edit the freeze artifact after step 5.", "", "On findings/blocked: append that negative verdict as the final step;", "never omit a negative append merely to preserve an older pass.", "Under suggest, operator skip is a separate owner append (aff_verdict: skip)", "— the reviewer chat does not skip for the operator.", "", "The kit performs no model call and holds no key.", "No merge to main.", "", "Hard stops: No merge to main without Tier 3 · no secrets · no live posture", "flips · no model call from the kit CLI · no redesign of the freeze", ] else: fence_lines = [ f"{step_id} — {title} ({config.repo.name}).", "", f"Model: {model}", f"Repo: {config.repo.name}", f"Branch: {branch}", f"Step: {step_id}", "Authority: authoritative", "", f"Read first: {read_first}.", "", "Deliverables:", f"- {row.deliverable.strip() or step_id}", "", f"Hard stops: {HARD_STOPS}", "", "Governance sync: update roadmap + handover on completion.", ] if model == "Auto" or decision.is_step_b: fence_lines.append("") fence_lines.extend(BUILD_VERIFICATION_LINES) fence_body = "\n".join(fence_lines) return "\n".join( [ f"### Paste-ready prompt — {step_id}", "", "```text", fence_body, "```", ] ) def format_next_regen_token(decision: NextRegenDecision) -> str: """Plan/result token for dry-run and change-log (§GSP.4.3 / §GSP.6.2 / §FRV.6.6).""" if decision.row is not None and decision.reason is None: if decision.advisory: return f"next_regen: regenerated (advisory={decision.advisory})" return "next_regen: regenerated" reason = decision.reason or "unknown" return f"next_regen: human_authorship_required ({reason})" def format_change_log_fragment(decision: NextRegenDecision) -> str: """Additive change-log fragment (§GSP.6.2 / §FRV.6.6).""" if decision.row is not None and decision.reason is None: if decision.advisory: return f"next_regen=regenerated:advisory={decision.advisory}" return "next_regen=regenerated" reason = decision.reason or "unknown" return f"next_regen=human_authorship_required:{reason}"