feat(skill): refresh adversarial-review под Codex 0.132

- Зачем:
  - актуализация под новый Codex CLI (0.132 убрал флаг -a,
    ввёл -c approval_policy / -c approvals_reviewer);
  - переход на безопасные операторские дефолты
    (sandbox=workspace-write для эмпирической верификации,
    явные overrides sandbox:* и approvals:*);
  - наведение порядка в принятии решений по раунду ревью:
    review_quality + evaluation matrix + structural operator gate
    вместо «оператор сам разбирается».
- Что:
  - SKILL.md: override-граммар (sandbox:*, approvals:*, model:*,
    low/medium/high/xhigh), детект OPERATOR_LANGUAGE, runtime hint,
    conditional dirty-file warning в Step 2 (после захвата REPO_ROOT),
    dual-layer mutation snapshots (git status + sha256sum input-файлов),
    operation-aware dispatch table, evaluation matrix + structural
    operator gate (batch-pause), structured resume body
    (Applied / Re-scoped / Rejected / Specific asks), final operator
    summary на OPERATOR_LANGUAGE.
  - references/runner.md: переведён на Sonnet runner, добавлен
    Step R2.5 bwrap preflight, Step R4.5 review_quality + bounded
    triage, расширена 11-полевая JSON-схема результата.
  - docs/DESIGN.md: §4.14–§4.21 с обоснованиями новых решений,
    §7.8 refresh-era smoke checks, §8 version log с двумя раундами
    dogfood-а этого refresh-а (включая R2-корректировку
    обоснования residual gap для уже-грязных tracked-файлов).
  - README.md: таблица дефолтов, Safety considerations (честно
    задокументирован residual gap), Linux sandbox prerequisites
    (bwrap + AppArmor user namespaces), Operator language,
    Final operator summary, troubleshooting.
  - examples/review-output.md: модель в сэмпле обновлена на gpt-5.5.
  - docs/superpowers/specs/2026-05-20-...: сохранена спека дизайна
    с уточнениями после dogfood.
- Проверка:
  - dogfood: 2 раунда /adversarial-review code на этом refresh-е,
    R2 верифицировал R2#1 (ordering) и R2#2 (Option B, honest
    residual gap);
  - smoke: codex --version ≥ 0.132.0, bwrap preflight зелёный
    на reference WSL2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 09:56:08 +03:00
co-authored by Claude Opus 4.7
parent 983a7a04ff
commit 1237e168c3
6 changed files with 2085 additions and 106 deletions
+420 -63
View File
@@ -18,8 +18,12 @@ Sends current work for adversarial review through an external AI model (OpenAI C
- `/adversarial-review plan` — force plan review
- `/adversarial-review code` — force code review
- `/adversarial-review <file-path>` — review a specific file (argument contains `/` or `.`)
- Override reasoning: `/adversarial-review xhigh` or `/adversarial-review medium` (one of: `medium`, `high`, `xhigh`)
- Override model: `/adversarial-review model:gpt-5.3-codex` (argument with `model:` prefix)
- Override reasoning: `/adversarial-review xhigh` or `/adversarial-review medium` (one of: `low`, `medium`, `high`, `xhigh`)
- Override model: `/adversarial-review model:gpt-5.4` (argument with `model:` prefix)
- Override Codex sandbox: `/adversarial-review sandbox:read-only` (one of: `read-only`, `workspace-write`, `danger-full-access`, `inherit`)
- Override Codex approval policy: `/adversarial-review approvals:user` (one of: `user`, `auto_review`, `never`)
Overrides can be combined: `/adversarial-review plan xhigh sandbox:read-only`.
## Instructions
@@ -52,6 +56,40 @@ Determine what to review. Check in priority order:
| Yes | No | **code** — review code changes |
| No | No | Ask the user what to review |
#### Step 1 (continued): Capture overrides, detect operator language, emit runtime hint
**Parse the invocation arguments for overrides** (case-sensitive on the prefix). Apply them to the per-review configuration captured below. Any unrecognized token after stripping the mode and overrides is treated as an unknown argument — surface a one-line "ignoring unknown argument: <token>" warning and continue.
| Argument shape | Sets | Default if absent |
|-------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| `model:<name>` | `CODEX_MODEL = <name>` | `CODEX_MODEL = gpt-5.5` |
| `low` / `medium` / `high` / `xhigh` | `CODEX_REASONING = <value>` | `CODEX_REASONING = high` |
| `sandbox:read-only` | `CODEX_SANDBOX = read-only` | `CODEX_SANDBOX = workspace-write` |
| `sandbox:workspace-write` | `CODEX_SANDBOX = workspace-write` | (same as default) |
| `sandbox:danger-full-access` | `CODEX_SANDBOX = danger-full-access`. **Emit a one-line warning** to the operator: `⚠ sandbox:danger-full-access — reviewer can write anywhere on disk; reserve for trusted local debugging.` | (n/a — explicit-only, never default) |
| `sandbox:inherit` | `CODEX_SANDBOX = inherit`. Runner skips bwrap preflight; effective sandbox is whatever the user's Codex config selects. | (n/a — explicit-only) |
| `approvals:auto_review` (default) | `CODEX_APPROVAL_POLICY = on-request`, `CODEX_APPROVALS_REVIEWER = auto_review` | (same as default) |
| `approvals:user` | `CODEX_APPROVAL_POLICY = on-request`, `CODEX_APPROVALS_REVIEWER = null` (omit `-c approvals_reviewer` → Codex falls back to its built-in `user` reviewer, which can ask the operator for approval). Nested approvals may hang the run from the parent Claude session — use only when explicitly desired. | (n/a — explicit-only) |
| `approvals:never` | `CODEX_APPROVAL_POLICY = never`, `CODEX_APPROVALS_REVIEWER = null`. Boundary crossings fail instead of asking. | (n/a — explicit-only) |
Codex's `untrusted` approval policy is intentionally NOT exposed as an override — the skill needs predictable boundary semantics, not per-command trust prompts.
**Resume invariant.** Sandbox and approval mode are properties of the original Codex session. `codex exec resume` does NOT accept `-s` or approval-related `-c` flags. If the operator changes `sandbox:` or `approvals:` mid-review, the new value takes effect only on a fresh-exec dispatch (which consumes a round). Surface this in the warning if the operator passes a sandbox/approvals override on a re-invocation of the skill while a prior session is still live.
**No silent fallback** may change sandbox or approval semantics. If any of the captured values cannot be passed to Codex on the target host (e.g. an installed Codex CLI version that doesn't support `-c approvals_reviewer`), surface an explicit diagnostic before dispatch — do NOT downgrade silently.
**Detect operator language.** Inspect the last few human-authored messages in the current conversation. If they are predominantly in a non-English language, capture `OPERATOR_LANGUAGE = <name of language>` (e.g. `Russian`, `Spanish`, `Japanese`). If detection is ambiguous, default to `OPERATOR_LANGUAGE = English`. Runtime prose shown to the operator (warnings, summaries, intermediate updates) MUST use `OPERATOR_LANGUAGE` when practical. Repository files (this `SKILL.md`, `references/runner.md`, `README.md`, `docs/DESIGN.md`, specs under `docs/superpowers/specs/`) stay in English regardless.
**Emit the one-line runtime hint** about sandbox defaults — **once per review, before Step 2 starts**. The hint is suppressed when the operator passed an explicit `sandbox:*` override (any of `read-only`, `workspace-write`, `danger-full-access`, `inherit`):
```
workspace-write in effect; pass sandbox:read-only if sensitive ignored state lives under REPO_ROOT.
```
Translate the hint into `OPERATOR_LANGUAGE` if non-English. Do NOT repeat per round.
The conditional warning about already-dirty tracked files lives in Step 2 (it depends on a captured `REPO_ROOT`).
### Step 2: Generate Session ID, capture REPO_ROOT, determine base branch
**REVIEW_ID:** generate yourself, format `{unix_timestamp}-{random_8digit_number}`.
@@ -75,6 +113,14 @@ git rev-parse --show-superproject-working-tree
If this returns non-empty, the user is inside a git submodule. Tell the user: `You are inside a submodule. The review will be scoped to this submodule (${REPO_ROOT}), not the parent repo. If you meant to review the parent, invoke from there.` Proceed — this is a warning, not an abort.
**Conditional warning about already-dirty tracked files** (skip when `OPERATION=plan`). Now that `REPO_ROOT` is captured and validated, run `git -C "${REPO_ROOT}" status --porcelain` and inspect the output. If any line matches `^[ M][M ] ` (a tracked file already modified in working tree or index pre-review), emit this extra one-line warning (once per review):
```
<N> unstaged or staged tracked-file edit(s) detected. The porcelain mutation snapshot only detects status transitions; reviewer-side content drift of already-dirty files is NOT auto-caught. Commit/stash WIP first for stronger protection, or pass sandbox:read-only.
```
Translate to `OPERATOR_LANGUAGE`. Substitute `<N>` with the count of matching lines. Suppressed when no already-dirty tracked files exist. See `docs/DESIGN.md §4.20` and `README.md` "Safety considerations" for the residual-risk rationale.
**Determining base branch (only for `code` and `code-vs-plan` modes):**
For `plan` mode — skip base branch detection, proceed to step 3.
@@ -174,6 +220,12 @@ DO NOT comment on: formatting, wording style, speculative issues without concret
<calibration>
Prefer one strong finding over several weak ones.
If the plan is solid, say so clearly — false positives erode trust.
Judge the plan at its declared level of abstraction.
Do not demand implementation details unless their absence blocks feasibility,
safety, rollback, verification, or a public contract.
If a detail can reasonably be decided during implementation, do not count it
as a finding.
</calibration>
<output_format>
@@ -312,14 +364,41 @@ The inlined prompt bodies above contain template placeholders that main must res
Substitute `${BASE_BRANCH}` first (it appears nested inside `<unstaged changes / staged changes / ...>`), then compute the outer human-readable description based on which diffs have content. Main writes the substituted string to the Write tool — no template placeholders should remain in the body file sent to the runner.
**Capture user overrides for `CODEX_MODEL` / `CODEX_REASONING` at Step 1:**
**Append the operator-language block to the prompt body** when `OPERATOR_LANGUAGE != "English"` (captured in Step 1). Append the following block verbatim AFTER the `<output_format>` section and BEFORE any trailing content:
The skill supports overrides like `/adversarial-review xhigh`, `/adversarial-review medium`, `/adversarial-review model:gpt-5.3-codex`. At Step 1, capture:
```
<language>
Respond in the operator's language: <OPERATOR_LANGUAGE>.
Keep these machine-readable literals unchanged in English (they are parsed by the lead and must not be translated):
- [severity: critical|high|medium]
- VERDICT: APPROVED
- VERDICT: REVISE
The Summary / Findings / Verdict section headers should also stay in English so the runner's content classifier and triage rg patterns continue to match.
</language>
```
- `CODEX_MODEL` — default `gpt-5.4`. Overridden by any argument matching `^model:(.+)$`; use the capture group.
- `CODEX_REASONING` — default `high`. Overridden by any argument exactly matching `low`, `medium`, `high`, or `xhigh`.
Substitute the literal name of the detected language for `<OPERATOR_LANGUAGE>`. Do NOT translate the block itself — the reviewer reads English instructions and produces prose in the target language. When `OPERATOR_LANGUAGE = "English"`, OMIT the block entirely (default behavior).
These are passed into the runner YAML input block below.
**Reviewer permissions and approval semantics.** The Codex reviewer is an auditor, not a contributor. Append the following block to every prompt body (regardless of mode), AFTER the `<output_format>` section and AFTER the optional `<language>` block:
```
<reviewer_permissions>
You may run commands to verify findings when useful: tests, linters, build
commands, git inspection, MCP-backed doc lookups, web search, project CLI
introspection.
Do not create, edit, delete, commit, or apply fixes to project files.
Prefer commands that do not mutate the working tree.
Do not run commands likely to rewrite generated files, snapshots, migrations,
lockfiles, or configs.
If verification would require mutation, report that limitation instead.
If a command unexpectedly changes files, stop and report it.
You are an auditor, not a contributor. The lead applies fixes; you find issues.
</reviewer_permissions>
```
This is the primary safeguard against reviewer-side mutation. Workspace-level mutation detection (see "Workspace mutation snapshot" below) is the secondary safeguard.
**Write the prompt body to disk via Write tool:**
@@ -357,6 +436,22 @@ REPO=$(git rev-parse --show-toplevel 2>/dev/null) && ls "$REPO/references/runner
Save the resolved absolute path as `RUNNER_SPEC_PATH`. Do NOT attempt to extract the path from any "Base directory for this skill:" line in the conversation — that line is a system injection Claude cannot reliably read from inside its own context.
**Workspace mutation snapshot (pre-dispatch):**
Mutation detection runs at two layers: the repo tree (git-tracked + new untracked files inside `REPO_ROOT`) and the skill's `/tmp` review inputs. A third class — gitignored files already inside `REPO_ROOT` — is documented as a known, operator-mitigated risk and is NOT detected automatically (see `README.md` "Safety considerations" and the runtime hint from Step 1).
Capture both layers BEFORE every runner dispatch (initial, resume, fresh-exec):
```bash
git -C "${REPO_ROOT}" status --porcelain > /tmp/codex-git-pre-${REVIEW_ID}.txt
sha256sum /tmp/codex-body-${REVIEW_ID}.md \
/tmp/codex-plan-${REVIEW_ID}.md \
/tmp/codex-resume-body-${REVIEW_ID}.md 2>/dev/null \
> /tmp/codex-inputs-pre-${REVIEW_ID}.sha
```
The `2>/dev/null` is deliberate: not every review has all three files (plan-mode reviews skip the body, round-1 dispatches skip resume-body). Missing files are silently elided from the snapshot and are still caught later if they appear unexpectedly.
**Dispatch the runner subagent via Agent tool:**
**Do NOT Read `${RUNNER_SPEC_PATH}` in main.** Pass the path to the subagent; it reads the spec itself. This keeps runner.md (~12K) out of main's context — both the Read result AND the Agent prompt duplication. Saves ~12K per round × up to 5 rounds per review.
@@ -374,8 +469,11 @@ Read your full instruction spec at ${RUNNER_SPEC_PATH} and follow the steps ther
REVIEW_ID: 1711872000-48217593
REPO_ROOT: /home/dementev/sources/myproject
OPERATION: initial
CODEX_MODEL: gpt-5.4
CODEX_MODEL: gpt-5.5
CODEX_REASONING: high
CODEX_SANDBOX: workspace-write
CODEX_APPROVAL_POLICY: on-request
CODEX_APPROVALS_REVIEWER: auto_review
PROMPT_BODY_PATH: /tmp/codex-body-1711872000-48217593.md
RESULT_PATH: /tmp/codex-runner-result-1711872000-48217593.json
---
@@ -383,6 +481,8 @@ RESULT_PATH: /tmp/codex-runner-result-1711872000-48217593.json
Substitute the actual resolved `${RUNNER_SPEC_PATH}` (absolute path) and real values for every other placeholder. `RESULT_PATH` always follows the pattern `/tmp/codex-runner-result-${REVIEW_ID}.json`.
Use the values captured in Step 1 for `CODEX_MODEL`, `CODEX_REASONING`, `CODEX_SANDBOX`, `CODEX_APPROVAL_POLICY`, and `CODEX_APPROVALS_REVIEWER`. When `CODEX_APPROVALS_REVIEWER` is `null` (set by `approvals:user` or `approvals:never`), pass the literal string `null` as the YAML value — the runner interprets it and omits the `-c approvals_reviewer` flag.
**Do NOT run the Agent tool call in background.** Wait for the subagent to return. (Runner's own codex exec is also synchronous per runner Step R3.)
**Parse the subagent's response — two-channel protocol:**
@@ -391,7 +491,52 @@ Apply the regex `RUNNER_RESULT_AT:\s+(\S+)` (UNANCHORED — matches anywhere in
If the regex finds NO match in the subagent's response, fall back to a Glob for the deterministic path `/tmp/codex-runner-result-${REVIEW_ID}.json` — REVIEW_ID is already known to main. If Glob also returns nothing, treat as `infra_error` with `errors: "runner did not write result file at deterministic path and did not emit RUNNER_RESULT_AT line"` and abort.
Read the file at the resolved path. Parse as JSON. Extract `result`, `verdict`, `review_file`, `codex_session_id`, `errors`, `user_warning`, `archived_stdout`, `archived_stderr`.
Read the file at the resolved path. Parse as JSON. Extract `result`, `verdict`, `review_file`, `codex_session_id`, `errors`, `user_warning`, `archived_stdout`, `archived_stderr`, `review_quality`, and the `triage` object.
**Backward compatibility for `review_quality` / `triage`.** A legacy runner result that omits `review_quality` and `triage` is treated as `review_quality = "unknown"` and `triage = { status: "skipped", finding_count: 0, max_severity: "none", covered_critical: 0, covered_high: 0, covered_medium: 0, truncated: false, needs_lead_judgment: false }`. No abort, no operator prompt; the legacy result is consumed as if the runner had emitted those values explicitly.
**Workspace mutation snapshot (post-dispatch).** BEFORE consulting the dispatch table below, and BEFORE applying any fixes, capture the post-state and diff against the pre-state:
```bash
git -C "${REPO_ROOT}" status --porcelain > /tmp/codex-git-post-${REVIEW_ID}.txt
sha256sum /tmp/codex-body-${REVIEW_ID}.md \
/tmp/codex-plan-${REVIEW_ID}.md \
/tmp/codex-resume-body-${REVIEW_ID}.md 2>/dev/null \
> /tmp/codex-inputs-post-${REVIEW_ID}.sha
diff -q /tmp/codex-git-pre-${REVIEW_ID}.txt \
/tmp/codex-git-post-${REVIEW_ID}.txt
diff -q /tmp/codex-inputs-pre-${REVIEW_ID}.sha \
/tmp/codex-inputs-post-${REVIEW_ID}.sha
```
Two diffs, two cases:
1. **Tracked-file mutation** — the `git status --porcelain` diff shows new modified (`^[ M]M`) or deleted (`^[ D]D`) entries that didn't exist pre-dispatch, OR untracked files (`^\?\?`) that look like edits to real source files (not editor scratchpads). **HARD STOP** before applying any fixes. Surface an operator diagnostic:
```
❌ Reviewer or runner mutated tracked files during dispatch.
Pre-state vs post-state diff:
<output of `diff` on the two -porcelain files>
Aborting before fixes. Inspect the diff and decide whether to revert
or keep the changes manually. Re-run /adversarial-review when ready.
```
Then skip Steps 59 and exit. Do NOT proceed to apply fixes — the artifact under review may have been silently mutated, invalidating the round.
2. **Untracked generated artifacts only** — `^\?\?` entries that look benign (e.g. build caches, log files). Warn the operator but allow continuation:
```
⚠ Reviewer left untracked files behind: <list>.
Proceeding with the round, but please review whether these should be
.gitignored or removed.
```
3. **`/tmp` review-input mutation** — the `sha256sum` diff is non-empty. Treat this exactly like tracked-file mutation: hard stop and surface the diagnostic. The reviewer should NEVER modify its own prompt body or plan file.
4. **No mutation** — both diffs are empty (or only show whitespace differences from the eager pre-snapshot). Proceed to the dispatch table.
The pre/post snapshot pair is repeated for every Codex dispatch (Step 4 initial, Step 7 resume, Step 7.4 fresh-exec). It is NOT optional — skipping it forfeits the only detection of reviewer-side mutation that does not depend on the reviewer self-reporting.
**If `user_warning` is non-null, surface it as a SEPARATE short user-visible message BEFORE the Step 5 verbatim-review message.** Format:
@@ -401,17 +546,26 @@ Read the file at the resolved path. Parse as JSON. Extract `result`, `verdict`,
Emit this on its own turn — do NOT concatenate into the Step 5 `## Adversarial Review — Round N` header message (that message's body must remain the review's verbatim content, nothing else). Emit the warning FIRST, then the Step 5 message. This preserves both the pre-refactor §2.4.4 "no-op refresh" diagnostic AND the Step 5 verbatim-display contract.
Dispatch based on `result`:
**Dispatch based on `result` × `OPERATION` × `review_quality`.** The table below is operation-aware: `degraded_environmental` on the initial dispatch is terminal because there is no prior valid round to fall back to, whereas the same classification on a resume can be re-routed through the existing fresh-exec fallback chain.
| `result` value | Main thread action |
|---|---|
| `success` | Save `codex_session_id` (keep prior if `null` per §2.4.4). Surface `user_warning` if set. Proceed to Step 5. |
| `timeout` | **TERMINAL — do NOT re-dispatch.** Runner already attempted twice internally (R4.1 + R5 retry = 2 × 10min). Tell user: "Reviewer timed out after two attempts (20 minutes total)." Abort the skill. User can re-invoke `/adversarial-review` to start a fresh review. |
| `launch_failure` | **TERMINAL — do NOT re-dispatch.** The runner already retried once internally (Step R5). Show `errors` to user, abort the skill. This keeps the total-attempts-per-round invariant at 2 (matches pre-refactor: 1 initial + 1 retry). |
| `infra_error` | Show `errors` to user (infrastructure: /tmp not writable, stderr file missing, RUNNER_RESULT_AT line absent). Abort. |
| `input_error` | Bug in orchestration. Show `errors` to user. Abort. |
| `result` | `OPERATION` | `review_quality` | Main thread action |
|------------------|---------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `success` | any | `valid` | Save `codex_session_id` (keep prior if `null` per §2.4.4). Surface `user_warning` if set. Proceed to Step 5: show review verbatim, run evaluation matrix in Step 6, advance round. |
| `success` | any | `degraded_content` | Surface `user_warning`. Show review verbatim per Step 5. THEN ask the operator: "Reviewer output flagged as low-confidence — advance the round anyway, or abort?" Default to advance after a short wait if running headlessly. |
| `success` | any | `unknown` | Surface `user_warning`. Treat as `valid` for round advancement (Step 5 + Step 6). Note the unknown classification in the final operator summary (Step 8). |
| `success` | `initial` | `degraded_environmental` | Surface `user_warning`. Do NOT show review verbatim (the file does not contain a real review). Treat as **terminal infrastructure failure** — abort the skill with the operator diagnostic. No prior valid review exists to fall back to. |
| `success` | `resume` | `degraded_environmental` | Surface `user_warning`. Do NOT show review verbatim. Do NOT count this dispatch as a round. Route to the Step 7.4 fallback chain, using the **prior round's** maximum severity for the fallback decision. |
| `success` | `fresh-exec` | `degraded_environmental` | Surface `user_warning`. Do NOT show review verbatim. Treat as **terminal not-verified** (the Step 8 NOT VERIFIED branch). Fresh-exec already was the fallback chain; a second environmental failure means the environment is reliably broken. |
| `timeout` | any | n/a | **TERMINAL.** Runner already attempted twice internally (R4.1 + R5 retry = 2 × 10min). Tell user: "Reviewer timed out after two attempts (20 minutes total)." Abort the skill. User can re-invoke `/adversarial-review` to start fresh. |
| `launch_failure` | `initial` | n/a | **TERMINAL.** The runner already retried once internally (Step R5). Show `errors` to user, abort the skill. |
| `launch_failure` | `resume` | n/a | **TERMINAL for this round.** Route to the Step 7.4 fallback chain (runner already archived stdout/stderr to `-failed-resume.*` per `archived_stdout`/`archived_stderr`). |
| `launch_failure` | `fresh-exec` | n/a | **TERMINAL.** Fresh-exec was the fallback; show `errors`, abort with the not-verified terminal state. |
| `infra_error` | any | n/a | Show `errors` to user (infrastructure: `/tmp` not writable, stderr file missing, RUNNER_RESULT_AT line absent, bwrap preflight failed). Abort. |
| `input_error` | any | n/a | Bug in orchestration. Show `errors` to user. Abort. |
**Round-level attempt invariant:** exactly ONE runner dispatch per round. Every failure result is terminal at main. The runner owns the full retry budget (≤2 attempts per dispatch, internal) regardless of failure type. Total codex invocations per round ≤ 2.
Whenever the dispatch outcome says "Surface `user_warning`", emit it on its own turn BEFORE the Step 5 verbatim message (or BEFORE the abort message, when there is no Step 5). Never concatenate the warning into the Step 5 header.
**Round-level attempt invariant:** exactly ONE runner dispatch per round (except when `degraded_environmental` on resume routes through fallback without counting as a round). The runner owns the full retry budget (≤2 attempts per dispatch, internal) regardless of failure type. Total codex invocations per round ≤ 2.
> **CRITICAL — main thread does NOT read stdout/stderr/JSONL/rollout files BY CONTENT.** Those live and die inside the subagent. Main reads: the runner result JSON at `RESULT_PATH`, the review file at `review_file`, and nothing else from `/tmp/codex-*`. Archival `mv` (on resume failure) is done by the runner, not main — main never references `/tmp/codex-stdout-*` or `/tmp/codex-stderr-*` in any Bash argv.
@@ -443,7 +597,7 @@ If any check fails → this is a **launch failure** (model produced no actionabl
Message format:
```
## Adversarial Review — Round N (mode: <plan|code|code-vs-plan>, model: gpt-5.4)
## Adversarial Review — Round N (mode: <plan|code|code-vs-plan>, model: <CODEX_MODEL>)
<verbatim contents of /tmp/codex-review-${REVIEW_ID}.md>
```
@@ -454,54 +608,198 @@ Message format:
- `VERDICT: REVISE` → Step 6 (Fixes).
- Maximum rounds reached (5 rounds) → Step 8 with the max-rounds note.
### Step 6: Apply fixes
### Step 6: Evaluate findings, gate structural fixes, then apply
> **Precondition gate (check first).** Before calling any Edit, Write, or other fix-applying tool: confirm that you have already sent a user-visible message in THIS round whose body contains the verbatim review text (short `⚠ <user_warning>` diagnostic messages do NOT count). If you have not — STOP. Go back to Step 5 and send the review message now. This is the same rule that protects the "user sees the review" contract; a literal reader may otherwise slip past it.
Based on the reviewer's findings:
**External feedback = suggestions to evaluate, not orders to follow.** Reviewer findings are inputs to your decision; applying them blindly causes real damage when the reviewer is technically wrong (e.g. a critical "bug" that is actually a feature request, or a security flag based on a misread of the threat model). Use `superpowers:receiving-code-review` when available; the key principles are inlined below as the always-available fallback.
**For plan review:** fix the plan — address each finding. Update the plan file (or temp file). Show the user:
#### Step 6.1: Build the evaluation matrix
For each finding in the verbatim review, fill out one row:
| # | Severity | Verified? | Type | Action |
|---|----------|------------------------------------|-----------------|----------------------|
| 1 | high | ✓ Context7 confirms behavior | architectural | accept |
| 2 | critical | ✗ cited issue is feature request | tool-mechanic | reject with reasoning|
| 3 | medium | ✓ small repro confirms | tool-mechanic | accept |
| 4 | medium | re-scoped to docs-only fix | architectural | re-scope |
`Action` options are EQUAL first-class outcomes:
- **`accept`** — finding is valid as stated; apply the proposed fix (or a minimal variant that resolves it).
- **`reject with reasoning`** — finding is technically wrong, out of scope, or contradicts an explicit user requirement; do NOT apply, and prepare a technical counter-argument for the re-review prompt (Step 7).
- **`re-scope`** — finding is partially valid; apply a narrower fix than the reviewer proposed (e.g. clarify wording instead of restructuring the section), and explain the narrowing in the re-review prompt.
Use runner triage (`triage.finding_count`, `triage.max_severity`, `triage.needs_lead_judgment`) ONLY as a hint to prioritize. The matrix is built from the verbatim review, not from triage; triage is too cheap to be authoritative.
**Verification methods by finding type:**
| Finding type | What constitutes verification |
|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| Architectural / design | Reasoning + codebase grep, plus a pattern check against existing code |
| Tool-mechanic (DSL syntax, config parser, API contract, library behavior) | **Empirical test on the real system** — reasoning alone is not enough. Open cited upstream issues by URL. |
| Style / convention | Match against actual codebase conventions |
| Security | Reasoning + concrete threat model |
Tool-mechanic findings are the most dangerous to accept on reasoning alone — mental models of obscure tools are often wrong. If the reviewer cites an upstream issue or doc, **open it**; citations age, issues get reclassified, and the cited number may not describe what the reviewer thinks it does.
**Receiving-feedback principles** (inlined from `superpowers:receiving-code-review` for portability):
- Read every finding end-to-end before reacting.
- Restate each finding's technical claim in your own words (mentally — don't pad the response with it).
- Verify against codebase / docs / a quick run before accepting.
- Push back when wrong, with technical reasoning, not deference.
- No performative agreement ("you're absolutely right" is a violation of the discipline).
- Skip thanks. State the fix or the reasoning.
#### Step 6.2: Classify accepted/re-scoped fixes as structural vs non-structural
After every finding has an `Action`, walk the matrix once and classify each `accept` / `re-scope` row as **structural** or **non-structural**. `reject with reasoning` rows skip this step (nothing is applied).
**Structural fixes** — pause the operator before applying:
- Invocation grammar or argument semantics (new/removed arg, renamed mode, changed value semantics).
- Output format or parsed literals (e.g. the `VERDICT: APPROVED|REVISE` line, severity tags, section headers, named workflow states).
- Workflow steps, fallback semantics, or terminal states.
- Sandbox, approval, or security guarantees.
- Public configuration semantics (what an override does).
- Schema, migration, or data format changes.
- Broad architectural rewrites.
- Any fix whose scope you are uncertain about — when in doubt, classify as structural.
**Non-structural fixes** — apply without pausing:
- Wording / phrasing changes that do not change semantics.
- Correcting factual inaccuracies (wrong API name, wrong tool mechanic, wrong attribution).
- Removing outdated comments or examples.
- Adding clarifying sentences or examples that do not change observable behavior.
- Internal heuristic refinements with no externally visible effect.
#### Step 6.3: Structural operator gate (one pause per round)
Apply the **batch-pause rule**: do NOT pause once per fix. Walk the entire matrix first; then:
- If structural count is **zero** → apply everything (non-structural accepts + re-scopes) without pausing. No operator gate needed.
- If structural count is **≥ 1** AND the operator is present (a direct human message exists earlier in this session AND the host exposes a user-facing channel) → make exactly **one** pause showing:
```
### Round N — structural fixes pending operator sign-off
**Structural (need go/no-go):**
- [#N — one-line description]
- ...
**Non-structural (will auto-apply):**
- [#M — one-line description]
- ...
**Rejected with reasoning (informational):**
- [#K — one-line description]
- ...
Approve the structural batch? (yes / no / select specific items)
```
Wait for operator response before applying any structural fix. Non-structural fixes still auto-apply.
- If structural count is **≥ 1** AND the operator explicitly requested autonomous mode (e.g. `/adversarial-review` invoked from a scheduled task, or an explicit "go ahead without asking" earlier in the conversation) → apply all structural fixes without pausing, but record this fact for the final operator summary (Step 8):
```
Structural fixes applied without operator sign-off due to autonomous mode:
- [#N — description]
- ...
```
- If structural count is **≥ 1** AND no operator is reachable (headless / scheduled run with no explicit autonomous flag) → apply structural fixes anyway and record the same "applied without sign-off" note for Step 8. Refusing to apply would leave the artifact half-fixed; the operator reviews after the fact.
#### Step 6.4: Apply fixes
For each `accept` and `re-scope` row:
- **Plan review** — update the plan file (or temp file). Make the change minimal: address the specific finding, don't refactor surrounding sections.
- **Code review** — edit files, run tests if applicable, run a build if the change is non-trivial.
- **Code-vs-plan** — update whichever side is wrong (plan or code), per the matrix.
**Verify your own technical claims before publishing them.** When a fix or the re-review reply makes a claim about tool mechanics (DSL syntax, config parser, API contract, library behavior):
- If a quick test is possible, run it (a small repro, `docker run …`, a real database container) — not "I think this works".
- If a quick test is not possible, frame the claim as a hypothesis ("seems to", "needs verification") rather than as fact.
**Skip** a fix if it contradicts an explicit user requirement — note this in the re-review reply with reasoning, not silent omission.
Show the user a brief account:
```
### Fixes (Round N)
- [What was changed and why, one item per finding]
### Round N fixes
- Applied: [#1 — what changed, 1 line]
- Re-scoped: [#3 — what changed, why narrower]
- Rejected: [#2 — short reason; full reasoning goes to the reviewer in Step 7]
```
**For code review:** fix the code directly — edit files, run tests if applicable. Show the user:
#### Step 6.5: Severity-decline soft signal
After applying, glance at the round-by-round severity trajectory. Expect severity to decline across rounds:
```
### Fixes (Round N)
- [What was fixed and why, one item per finding]
R1: 3 critical, 6 high, 5 medium (typical opening)
R2: 1 high, 1 medium, 3 low (good)
R3: 1 high (closing in)
R4: APPROVED (terminal)
```
**Skip** a fix if it contradicts the user's explicit requirements — note this for the user.
If severity stays flat (e.g. `high → high → high` across three consecutive rounds), something is structurally off — the lead may not understand the technology, the reviewer may be looping on the same misunderstanding, or the artifact has a deep problem that surface fixes can't reach. Pause and surface to the operator:
```
⚠ Severity has stayed at <level> for <N> rounds. This usually means
either (a) the artifact has a structural problem the current fixes
are not addressing, or (b) the reviewer is misreading something the
lead and reviewer disagree about. Continue, switch approach, or
abort?
```
This is a soft signal, not a hard gate. Default to continuing if the operator does not respond.
### Step 7: Resubmit to Codex (Rounds 2-5)
**Resume is the primary path.** Saves tokens and preserves session context. A fresh `codex exec` without resume is an **emergency fallback** when resume itself fails.
**Step 7.1: Write the resume prompt body to disk.**
**Step 7.1: Write the structured resume prompt body to disk.**
Write `/tmp/codex-resume-body-${REVIEW_ID}.md` containing:
The re-review body is NOT a "I applied your feedback, please re-check" note. It is a structured response that lets the reviewer (a) verify the applied fixes resolve the original findings, (b) contest the rejections with reasoning, and (c) catch new issues introduced by the fixes. Write `/tmp/codex-resume-body-${REVIEW_ID}.md` containing:
```
I've revised based on your feedback.
I've evaluated the findings.
Here's what I changed:
[List of fixes from Step 6]
## Applied
- [#N]: [what was changed and why, 12 lines]
- ...
Re-review with the same adversarial stance. Focus on:
1. Whether my fixes actually resolve the reported issues
2. Any NEW issues introduced by the fixes
## Re-scoped
- [#N]: [narrower scope, with reasoning for the narrowing]
- ...
End with VERDICT: APPROVED or VERDICT: REVISE
## Rejected with reasoning
- [#N]: [technical reason for not applying — not "I disagree", but a concrete counter-argument the reviewer can engage with]
- ...
## Specific asks for re-review
1. Are my rejections technically valid? Where I rejected with reasoning, do you accept the counter-argument or push back?
2. Did the applied / re-scoped fixes resolve the original findings?
3. Did the fixes introduce any new issues?
```
Substitute the fixes list from Step 6 (one bullet per finding addressed). Do NOT include the session marker — the subagent adds it.
Substitute the lists from Step 6's evaluation matrix (one bullet per finding per section). Sections with zero items can be omitted, but `## Specific asks for re-review` is always present. Do NOT include the session marker — the subagent adds it.
The three-section shape (Applied / Re-scoped / Rejected with reasoning) gives the reviewer a chance to contest the rejections. A re-review that says "your rejection of #2 is valid; here's why" is just as useful as one that fixes new issues — both keep the loop honest.
**Sonnet triage metadata from Step 4 is NOT passed to the reviewer.** Codex sees the verbatim findings (already in its conversation context from the prior round) and the lead's structured response. The runner's `triage.*` fields are an internal hint for the lead, never forwarded to Codex.
**Step 7.2: Dispatch the runner subagent for resume.**
Same Agent tool invocation as Step 4 (bootstrap instruction with `${RUNNER_SPEC_PATH}` + YAML input block; subagent Reads the spec itself). Reuse the `RUNNER_SPEC_PATH` resolved in Step 4 (do not re-resolve). Input block:
Same Agent tool invocation as Step 4 (bootstrap instruction with `${RUNNER_SPEC_PATH}` + YAML input block; subagent Reads the spec itself). Reuse the `RUNNER_SPEC_PATH` resolved in Step 4 (do not re-resolve). Run the **pre-dispatch workspace mutation snapshot** described in Step 4 (`git status --porcelain` + `sha256sum` of the three `/tmp/codex-*-body-*` paths) BEFORE invoking the Agent tool.
Input block:
```yaml
---
@@ -516,19 +814,23 @@ CODEX_SESSION_ID: <uuid from previous round's runner result>
---
```
**Step 7.3: Parse the two-channel result.**
Sandbox and approval fields (`CODEX_SANDBOX`, `CODEX_APPROVAL_POLICY`, `CODEX_APPROVALS_REVIEWER`) are deliberately omitted from the resume YAML — `codex exec resume` does NOT accept these flags; the runner ignores them on `OPERATION=resume`. If the operator wants a different sandbox or approval policy for the rest of the review, the only path is to abort and re-invoke `/adversarial-review` with new overrides, which starts a fresh review with a fresh REVIEW_ID.
Extract `RUNNER_RESULT_AT:` line (same tolerant regex + Glob fallback as Step 4), read the JSON file, extract fields. If `user_warning` is non-null, emit it as its own `⚠ <user_warning>` message BEFORE any other action (including before the Step 5 verbatim review) — see Step 4's user_warning rule.
**Step 7.3: Parse the two-channel result and consult the dispatch table.**
| `result` value | Main thread action |
|---|---|
| `success`, verdict `APPROVED` | Read `review_file`, go to Step 5 (it will dispatch to Step 8 on APPROVED). |
| `success`, verdict `REVISE` | Save new `codex_session_id`. If the subagent returned null (zero-find resume), keep the prior id per §2.4.4 — `user_warning` will already have been surfaced. Go to Step 5. |
| `timeout` | **TERMINAL for this round** — runner already attempted twice. Route to fallback below. (Fresh-exec is a NEW round from the 5-round counter — its own ≤2-attempts budget applies.) No user-offered retry; that would compound. |
| `launch_failure` | **TERMINAL for this round** — runner already retried once internally. Route to fallback below (runner already archived stdout/stderr to `-failed-resume.*` — paths in `archived_stdout` / `archived_stderr`). |
| `infra_error` | Show `errors` to user, abort. |
Extract the `RUNNER_RESULT_AT:` line (same tolerant regex + Glob fallback as Step 4), read the JSON file, extract all 11 top-level fields. Run the **post-dispatch workspace mutation snapshot** (see Step 4) and treat tracked-file or `/tmp`-input mutation as a hard stop per the same rules.
**Round-level attempt invariant:** exactly ONE runner dispatch per resume round. Every failure result routes to fallback (not re-dispatch within the same round). Fallback's fresh-exec dispatch consumes a NEW round from the 5-round counter, which has its own independent 2-attempts-per-round budget. Total codex invocations per round ≤ 2 regardless of failure type — matches pre-refactor; closes Round-2 finding #1.
Surface `user_warning` if non-null. Then consult the **operation-aware dispatch table in Step 4** with `OPERATION=resume`. In particular:
- `success` + `review_quality=valid` and `verdict=APPROVED` → Step 8 (approved).
- `success` + `review_quality=valid` and `verdict=REVISE` → save new `codex_session_id` (keep prior if `null` per §2.4.4) and go to Step 5.
- `success` + `review_quality=degraded_environmental` → do NOT show review verbatim, do NOT count as a round, route to the Step 7.4 fallback chain using the prior round's severity.
- `success` + `review_quality=degraded_content` → show verbatim per Step 5, ask the operator whether to advance.
- `success` + `review_quality=unknown` → treat as `valid` for advancement; note in the final summary.
- `timeout` or `launch_failure` → route to the Step 7.4 fallback chain (runner already archived stdout/stderr to `-failed-resume.*` per `archived_stdout`/`archived_stderr` on `launch_failure`).
- `infra_error` or `input_error` → show `errors`, abort.
**Round-level attempt invariant:** exactly ONE runner dispatch per resume round. Every failure result routes to fallback (not re-dispatch within the same round). Fallback's fresh-exec dispatch consumes a NEW round from the 5-round counter, which has its own independent 2-attempts-per-round budget. Total codex invocations per round ≤ 2 regardless of failure type. The `degraded_environmental` outcome on resume is the one exception that does NOT count as a round — it routes through fallback without consuming the round counter, because the resume produced no usable review.
**Step 7.4: Fallback chain** — triggered by `launch_failure` or repeated `timeout` from the runner.
@@ -555,11 +857,18 @@ Options:
Dispatch the runner subagent with `OPERATION=fresh-exec` (same input schema, new PROMPT_BODY_PATH pointing at the rebuilt prompt). The fresh-exec consumes one round from the 5-round counter. Return to Step 5 with the new review.
### Step 8: Final result
### Step 8: Final result + operator summary
Every terminal state emits TWO messages, in this order:
1. **Per-state header block** (templates below) — the canonical "what happened" framing in English.
2. **Operator summary** — a separate operator-facing summary in `OPERATOR_LANGUAGE` (captured in Step 1; English by default). The summary comes AFTER the final verbatim reviewer response (if any) and does NOT replace it.
#### Terminal state templates
**Approved:**
```
## Adversarial Review — Summary (mode: <mode>, model: gpt-5.4)
## Adversarial Review — Summary (mode: <mode>, model: <CODEX_MODEL>)
**Status:** Approved after N round(s)
@@ -571,20 +880,20 @@ Dispatch the runner subagent with `OPERATION=fresh-exec` (same input schema, new
**Maximum rounds reached:**
```
## Adversarial Review — Summary (mode: <mode>, model: gpt-5.4)
## Adversarial Review — Summary (mode: <mode>, model: <CODEX_MODEL>)
**Status:** Maximum reached (5 rounds) — not fully approved
**Remaining findings:**
[Unresolved issues]
[Unresolved issues from the last round]
---
**The reviewer still has findings. Please review them and decide how to proceed.**
```
**Not verified** (resume failed and the operator chose to conclude, or headless with only medium severity):
**Not verified** (resume failed and the operator chose to conclude, headless with only medium severity, or a second `degraded_environmental` on fresh-exec):
```
## Adversarial Review — Summary (mode: <mode>, model: gpt-5.4)
## Adversarial Review — Summary (mode: <mode>, model: <CODEX_MODEL>)
**Status:** NOT VERIFIED — fixes applied, reviewer did not re-verify
@@ -598,6 +907,42 @@ Dispatch the runner subagent with `OPERATION=fresh-exec` (same input schema, new
**WARNING: This is NOT an approval. Fixes were applied but never verified by the reviewer. Manual review is required before merging.**
```
**Aborted due to environmental failure** (initial dispatch returned `success + degraded_environmental`, or a workspace-mutation hard stop, or a `bwrap` preflight failure surfaced as `infra_error`):
```
## Adversarial Review — Summary (mode: <mode>, model: <CODEX_MODEL>)
**Status:** ABORTED — environmental failure before any valid review
**Diagnostic:**
[user_warning or errors from the runner result]
---
**No review was produced. Inspect the diagnostic and the README's "Safety considerations" / "Linux sandbox prerequisites" sections, then re-invoke /adversarial-review when the environment is ready.**
```
#### Operator summary (always emitted)
After the per-state header block, emit a separate operator-facing summary in `OPERATOR_LANGUAGE`. This summary is built from per-round decision summaries already shown earlier in the conversation (the matrix in Step 6.1, the fix-account in Step 6.4, the verbatim reviews in Step 5). Do NOT re-read Codex stdout/stderr/rollout files; main never has those in context.
Include:
- **Final status** — approved, maximum rounds reached, not verified, or aborted.
- **What changed across all review rounds** — a compact list of artifact changes (one bullet per file/section, not full diffs).
- **Findings applied / re-scoped / rejected** — counts per round, with one-line descriptions only for findings the operator should pay attention to (rejections, re-scopes, structural fixes).
- **Structural changes** — whether structural fixes were applied, and whether operator sign-off was obtained or was skipped due to autonomous / headless mode.
- **Verification performed and NOT performed** — what the reviewer ran vs. what the lead verified vs. what is still unverified.
- **Remaining findings or risks** — only for non-approved terminal states; otherwise omit this section.
- **Explanation of the status** — one sentence on what the status means for what the operator should do next (especially for `NOT VERIFIED` and `ABORTED`).
Constraints on the summary:
- Do NOT include full diffs.
- Do NOT repeat full reviewer findings verbatim unless an unresolved finding still matters.
- Keep it concise and operator-useful.
- If context compaction has made the per-round history incomplete (a known limitation — see `docs/DESIGN.md §9.2`), state that limitation explicitly in the summary instead of inventing details. A summary that says "round 2 details unavailable due to compaction" beats a fabricated round-2 account.
Render the summary in `OPERATOR_LANGUAGE`. Section headers and severity tags stay in English so the operator can grep them back if needed; everything else is in the operator's language.
### Step 9: Cleanup
**Conditional on terminal state:**
@@ -623,7 +968,12 @@ rm -f /tmp/codex-plan-${REVIEW_ID}.md \
/tmp/codex-stdout-${REVIEW_ID}-failed-resume.jsonl \
/tmp/codex-stderr-${REVIEW_ID}-failed-resume.txt \
/tmp/codex-body-${REVIEW_ID}.md \
/tmp/codex-runner-result-${REVIEW_ID}.json
/tmp/codex-resume-body-${REVIEW_ID}.md \
/tmp/codex-runner-result-${REVIEW_ID}.json \
/tmp/codex-git-pre-${REVIEW_ID}.txt \
/tmp/codex-git-post-${REVIEW_ID}.txt \
/tmp/codex-inputs-pre-${REVIEW_ID}.sha \
/tmp/codex-inputs-post-${REVIEW_ID}.sha
```
If the user declined `rm` — continue without error.
@@ -644,12 +994,19 @@ Do NOT delete plan files that existed before the review (only temp files created
- **Runner is dispatched via Agent tool** with `subagent_type: general-purpose, model: sonnet`. Agent tool call is synchronous (not `run_in_background`).
- **ALL runner failure results are TERMINAL at main** (`launch_failure`, `timeout`, `infra_error`, `input_error`). Runner retries once internally on ANY failure. Main does NOT re-dispatch and does NOT offer the user a retry — those lanes would compound retries across layers. Total codex invocations per round ≤ 2 (matches pre-refactor invariant: 1 initial + 1 retry). Fresh-exec fallback is a NEW round with its own independent 2-attempts budget.
- **`user_warning` from the runner must be surfaced to the user** on a single line BEFORE any other action. This preserves the pre-refactor §2.4.4 "both tiers empty, continuing with previous ID" diagnostic.
- **`CODEX_MODEL` / `CODEX_REASONING`** in the runner input schema refer to the model codex CLI launches (e.g. `gpt-5.4`). The runner's OWN model is Sonnet, set via Agent tool's `model: "sonnet"`. Do NOT conflate.
- **`CODEX_MODEL` / `CODEX_REASONING` / `CODEX_SANDBOX` / `CODEX_APPROVAL_POLICY` / `CODEX_APPROVALS_REVIEWER`** in the runner input schema refer to the codex-exec invocation (default model `gpt-5.5`, default sandbox `workspace-write`, default approval policy `on-request` with `auto_review` reviewer). The runner's OWN model is Sonnet, set via Agent tool's `model: "sonnet"`. Do NOT conflate. Sandbox and approval fields apply to `OPERATION=initial` and `OPERATION=fresh-exec` only; `codex exec resume` ignores them because they are properties of the original session.
- **Resume is the primary path for rounds 2-5.** Fresh-exec fallback consumes one round from the 5-round counter.
- **Step 9 cleanup `rm` glob is UNCHANGED from pre-refactor.** It still covers `/tmp/codex-plan-${REVIEW_ID}.md`, `/tmp/codex-prompt-${REVIEW_ID}.md`, `/tmp/codex-resume-prompt-${REVIEW_ID}.md`, `/tmp/codex-review-${REVIEW_ID}.md`, `/tmp/codex-stdout-${REVIEW_ID}.jsonl`, `/tmp/codex-stderr-${REVIEW_ID}.txt`, `/tmp/codex-stdout-${REVIEW_ID}-failed-resume.jsonl`, `/tmp/codex-stderr-${REVIEW_ID}-failed-resume.txt`. ADD the two new paths introduced by the refactor: `/tmp/codex-body-${REVIEW_ID}.md` and `/tmp/codex-runner-result-${REVIEW_ID}.json`.
- Cleanup is **conditional on terminal state**: remove temp files on approved/max-reached/not-verified; LEAVE them on abort (diagnostic value). Skip all cleanup in Plan Mode.
- Always read-only sandbox — reviewer never writes files.
- Maximum 5 rounds to protect against infinite loops.
- **Step 9 cleanup `rm` glob covers ALL `/tmp/codex-*-${REVIEW_ID}*` files this skill writes** — initial-round body, resume-round body, prompts, review, stdout/stderr (current + archived failed-resume), runner result JSON, pre/post git-status snapshots, pre/post `sha256sum` snapshots. See the explicit list in Step 9.
- Cleanup is **conditional on terminal state**: remove temp files on approved/max-reached/not-verified/aborted-env; LEAVE them on abort due to launch failure / infra error (diagnostic value). Skip all cleanup in Plan Mode.
- **Default sandbox is `workspace-write`**, not `read-only`. The reviewer needs to run tests, build, query upstream docs, and exercise CLIs to verify findings — all write-class operations. Reviewer-side mutation is governed by (a) the `<reviewer_permissions>` prompt block (auditor, not contributor), and (b) the dual-layer mutation snapshot in Step 4 (`git status --porcelain` + `sha256sum` of `/tmp/codex-{body,plan,resume-body}-*`). Operators with sensitive ignored state in `REPO_ROOT` can opt out via `sandbox:read-only` (with the explicit trade-off that the reviewer loses empirical verification).
- **`review_quality` and `triage`** are part of the runner result schema and consumed by the operation-aware dispatch table in Step 4. Legacy runner results without these fields are treated as `review_quality=unknown` / `triage.status=skipped` and continue to work.
- **Workspace mutation snapshots are mandatory** before AND after every Codex dispatch (initial, resume, fresh-exec). Tracked-file mutation or `/tmp` review-input mutation is a hard stop before applying any fixes.
- **One-line runtime hint** about `workspace-write` and the `sandbox:read-only` opt-out is emitted exactly once per review (at Step 1, before Step 2), suppressed when the operator passed an explicit `sandbox:*` override.
- **Operator language** is detected at Step 1. Runtime prose (warnings, summaries, intermediate updates) uses `OPERATOR_LANGUAGE`; repository files stay in English; machine-readable literals (`[severity:]`, `VERDICT:`, section headers) stay in English regardless of language.
- **Reviewer findings are suggestions to evaluate, not orders to follow.** Step 6 builds an evaluation matrix with three first-class actions: `accept`, `reject with reasoning`, `re-scope`. The lead applies only accepted / re-scoped fixes; rejections go to Codex as structured counter-arguments in Step 7.
- **Structural fixes need operator sign-off** unless the operator explicitly requested autonomous mode or no operator is reachable. The batch-pause rule: exactly one operator prompt per round listing structural / non-structural / rejected. Headless runs apply structural fixes anyway but record "applied without operator sign-off" in the Step 8 operator summary.
- **Maximum 5 rounds** to protect against infinite loops.
- **Final operator summary** is emitted at every terminal state (approved, max rounds, not verified, aborted) in `OPERATOR_LANGUAGE`, AFTER the final verbatim reviewer response. Built from in-conversation per-round summaries — main never reads Codex stdout/stderr/rollout files.
- Show the user reviews and fixes for each round.
- If Codex CLI is not installed or crashed — tell the user: `npm install -g @openai/codex`.
- If a fix contradicts the user's explicit requirements — skip and explain why.
- If Codex CLI is not installed or crashed — tell the user: `npm install -g @openai/codex` (requires Codex CLI ≥ 0.132.0 for the `-c approval_policy` form; earlier versions may need different override syntax).
- If a fix contradicts an explicit user requirement — skip it, record it in the Step 7 `## Rejected with reasoning` section, and surface in the Step 8 operator summary.