Imported from eworthing/agent-skills (
peer-plan-review/SKILL.md). Install upstream withnpx skills add eworthing/agent-skills --skill peer-plan-review. Copyright stays with the author.
Peer Plan Review
Pressure-test a plan before execution. The host agent owns the plan and revises it between rounds. The reviewer critiques only; it never edits files or runs the host workflow.
Contents
- Bundled resources
- Require a plan source
- Parse reviewer arguments
- Review stance
- Preflight
- Create a review session
- Domain context
- Round 1
- Read the result
- Revise and re-review
- Handle failures
- Finalize
- Rules
Bundled resources
scripts/run_review.py— provider-specific CLI invocation, resume, output capture, model normalization, metadata extraction. Do not reimplement it.
After choosing the reviewer, read exactly one provider reference from the corresponding row below.
| Open when you need to... | Read |
|---|---|
| understand runner CLI flags, session-file contracts, and environment variables | references/adapter-cli.md |
| configure or troubleshoot the Codex CLI reviewer (auth, sandbox, isolation) | references/codex.md |
| configure or troubleshoot the Claude Code CLI reviewer (plan mode, tools, effort) | references/claude-code.md |
| configure or troubleshoot the Copilot CLI reviewer (tool permissions, output) | references/copilot.md |
| configure or troubleshoot the OpenCode CLI reviewer (deny policy, format) | references/opencode.md |
configure or troubleshoot the Antigravity (agy) reviewer (experimental, model naming) |
references/antigravity.md |
| configure or troubleshoot the Gemini CLI reviewer (legacy/enterprise, thinking budget) | references/gemini.md |
| copy the structured output template for prompt assembly | references/output-format.md |
| author an optional project-specific domain context block (criteria-based rules) | references/domain-context.md |
| run a single-round, skeptical, adversarial review | references/adversarial.md |
For available models prefer:
python3 <skill-dir>/scripts/run_review.py --list-models --reviewer <provider>
(aliases for claude/gemini/opencode/agy; known models for codex/copilot).
Require a plan source
Before starting, confirm one of: a plan already in the session, a plan pasted by the user, or a file path. If none, ask.
Parse reviewer arguments
Normalize to:
reviewer— required; the provider acting as reviewer for this run. One of:codex,gemini,claude,copilot,opencode,agy(Antigravity —antigravityis accepted and normalized toagy). (The--reviewer <provider>CLI flag uses the same values; "reviewer" names the role, "provider" names the CLI tool fulfilling it.)agyis the one non-read-only reviewer — see Rules andreferences/antigravity.md.model— optional; pass-through if not known aliaseffort— optionallow | medium | high | xhigh
Parsing rule: accepted forms are reviewer, reviewer <effort>, reviewer <model>, or reviewer <model> <effort>. The first token after reviewer is effort iff it is one of those four literals (then model uses the provider default); otherwise it is model, and the next token, if present, is effort. If model omitted, tell user once provider default used. If effort omitted, no effort flag is injected — each provider uses its own persisted config or built-in default.
Review stance
- Standard (default): cooperative, iterative loop (up to 5 rounds) — refine a plan toward approval.
- Adversarial: deliberately skeptical single-round review — surface major flaws fast, no revise loop. See
references/adversarial.md. Trigger on pressure-test, break the plan, adversarial review, find holes in.
Preflight
- Resolve
<skill-dir>relative to thisSKILL.md. - Read
references/<provider>.md(for theclaudeprovider, the file isreferences/claude-code.md; foragy, it isreferences/antigravity.md). - Verify CLI:
python3 <skill-dir>/scripts/run_review.py --self-check --reviewer <provider>. - If user supplied unfamiliar shorthand model, warn once and continue — runner passes unknown values through as raw IDs.
Create a review session
REVIEW_ID=$(python3 -c "import uuid; print(uuid.uuid4().hex[:12])")
eval "$(
python3 <skill-dir>/scripts/ppr_paths.py \
--review-id "$REVIEW_ID" \
--format shell
)"
Canonical temp files:
${TMPDIR}/ppr-${REVIEW_ID}-plan.md($PLAN_FILE)${TMPDIR}/ppr-${REVIEW_ID}-prompt.md($PROMPT_FILE)${TMPDIR}/ppr-${REVIEW_ID}-review.md($OUTPUT_FILE)${TMPDIR}/ppr-${REVIEW_ID}-session.json($SESSION_FILE)${TMPDIR}/ppr-${REVIEW_ID}-events.jsonl($EVENTS_FILE)${TMPDIR}/ppr-${REVIEW_ID}-errors.jsonl($ERROR_LOG) (retained after cleanup)${TMPDIR}/ppr-${REVIEW_ID}-codex-homes.list(Codex isolation manifest; reclaimed at Finalize)${TMPDIR}/ppr-${REVIEW_ID}-runner.log(written byppr_launch.sh; runner stdout+stderr)${TMPDIR}/ppr-${REVIEW_ID}-exit.code(written byppr_launch.sh; true runner exit code)
Keep the REVIEW_ID in your own working context across rounds and reconstruct
every path with ppr_paths.py (the eval above also exports
$CODEX_HOME_MANIFEST). Do not use ad hoc inline Python that reads PROMPT_FILE
or related path vars from the environment before they have been exported.
Concurrency: several peer reviews can run side by side against one codebase —
each generates its own random REVIEW_ID, so their temp files never collide.
Never share one id-file path between concurrent reviews; a shared pointer is the
one thing that would make them step on each other. (Concurrent Codex runs are
also session-isolated per run — see references/codex.md.)
Snapshot the plan into plan.md before each round. Treat the snapshot as immutable for that round. Number every line (cat -n style) so the reviewer can cite specific lines — include the numbered plan in the prompt.
Domain context (optional)
Default off. Add a domain-context block only when the plan's correctness depends on a project-specific rule a strong general model would not already apply — a bespoke architecture contract, a private framework invariant, a custom design-token system. Skip well-known platform idioms (idiomatic SwiftUI, REST, common security practice): a capable reviewer already enforces those, so a block is prompt weight for no gain. When in doubt, omit it; the user may also opt out per run (e.g. a maximally independent adversarial pass).
Only you know which host skills are active — there is no API to enumerate them — so when you do
add a block, author it yourself as prose following
references/domain-context.md (the authoring rule, placement, and
the two-pass output variant). It is never runner logic.
Round 1
Build prompt with:
- Verdict contract: final non-empty line must be
VERDICT: APPROVEDorVERDICT: REVISE. - Line-numbered plan.
- (optional) Domain context block — see Domain context. Placed between the plan and the output template.
- Structured output template from
references/output-format.md. If a Domain context block is included, use its two-pass variant.
Write the assembled prompt to $PROMPT_FILE.
Run the round through the canonical launcher — one command, backgrounded:
bash <skill-dir>/scripts/ppr_launch.sh \
--review-id "$REVIEW_ID" --reviewer <provider> \
[--model <m>] [--effort <e>] [--timeout <s>] [--resume]
Launch it with run_in_background: true (or the host's equivalent). The
launcher derives every path from the review id, always pairs the runner's
coupled flags, tees runner output to ${TMPDIR}/ppr-${REVIEW_ID}-runner.log,
writes the true runner exit code to ${TMPDIR}/ppr-${REVIEW_ID}-exit.code, and
exits with that code — so the host's completion notification reflects real
success or failure. Unrecognized flags pass through to run_review.py (see
references/adapter-cli.md). Omit --resume on round 1. --timeout defaults
to 1200 s; the trigger for raising it is reasoning depth (codex/gemini at
xhigh), not plan size.
After launching, the round is over for now: end the turn. The harness
re-invokes you with a completion notification when the runner exits — that
notification is the wait mechanism. (Red flag: a foreground sleep/poll loop
is killed at the host's shell ceiling — in Claude Code the Bash tool defaults
to 120 s, max 600 s, both shorter than a real review — which is the exact
failure background launch exists to avoid.)
Read the result
Gate on the exit code first: read ${TMPDIR}/ppr-${REVIEW_ID}-exit.code. Only
when it is 0 and both $OUTPUT_FILE and $SESSION_FILE are non-empty,
dump them. Otherwise read …-runner.log and …-errors.jsonl, treat the round
as failed (see Handle failures), and never parse a verdict
from absent files. If -runner.log contains a WARNING: resume degraded
line, relay it to the user — a requested resume silently fell back to a fresh
exec.
Dump both files with cat — never with read, and never with inline Python
that prints a dict. read VAR treats a leaked quote/space as a variable name
and fails with read: '': not a valid identifier; printing parsed JSON as a
Python dict corrupts it into single-quoted non-JSON. SESSION_FILE is already
JSON; emit it verbatim.
# re-export paths first — env exports do not survive across Bash tool calls
eval "$(python3 <skill-dir>/scripts/ppr_paths.py --review-id "$REVIEW_ID" --format shell)"
if [ "$(cat "${TMPDIR}/ppr-${REVIEW_ID}-exit.code")" = "0" ] \
&& [ -s "$OUTPUT_FILE" ] && [ -s "$SESSION_FILE" ]; then
echo "=== SESSION ==="; cat "$SESSION_FILE"
echo "=== REVIEW ==="; cat "$OUTPUT_FILE"
else
echo "=== RUNNER LOG (round failed) ==="
cat "${TMPDIR}/ppr-${REVIEW_ID}-runner.log"; cat "$ERROR_LOG"
[ -s "$OUTPUT_FILE" ] && { echo "=== PARTIAL REVIEW ==="; cat "$OUTPUT_FILE"; }
fi
- Read session file; extract actual
model,effort,effort_source(and Geminithinking_tokens). - Read review output file.
- Parse verdict from last non-empty line, search upward.
- Call
parse_structured_review()from the vendored_common.sessionmodule (from _common.session import parse_structured_review, withscripts/onsys.path). Scopes to### Blocking Issues/### Non-Blocking Issuesonly; extracts[B<n>]/[N<n>]tags with confidence, section/line refs, recommendations. On success, present findings in severity-ordered summary table before full review text. On empty result, present raw review — graceful degradation. - Header:
## Peer Review - Round N (reviewer: <provider>, model: <actual>, effort: <actual>)
If no valid verdict: treat as REVISE, say verdict parse failed.
If model or effort missing: fall back to requested → provider default →
"default".
Revise and re-review
Standard stance only. Adversarial skip direct to Finalize after round 1.
On REVISE (use fresh per-round finding IDs each round — B1, B2, N1, …; do not carry IDs forward):
- Address each finding.
- Rewrite plan snapshot with updated full plan.
- Write short
Changes since last roundbullet list. - Rebuild prompt in this order: verdict contract → previous reviewer feedback (with finding IDs) →
Changes since last round→ updated numbered plan → structured output template fromreferences/output-format.md(two-pass variant if a domain context block is present). - Re-run the runner with
--resumeadded.
Stop after approval or five rounds, whichever first.
Handle failures
--resumeis a request, not a guarantee. The runner auto-falls back to a fresh execution once if resume fails (nonzero exit, timeout, or no usable output).resume_requested,resume_attempted,resume_fallback_usedrecord this. Do not submit a second manual retry on top of the automatic fallback.- Runner non-zero + no output: report, ask user to retry, switch reviewers, or stop.
- Runner non-zero + some output: try extract review anyway.
- Retrying a failed round (bad flag, timeout): reuse the same
REVIEW_ID— the runner writes fresh output files and the error log is append-only, so log/session continuity is preserved. Mint a new id only for a new review. - Binary missing: fail fast, quote install command from provider reference.
Finalize
- Approved → present final revised plan; note reviewer approval.
- Round limit (standard) → present latest plan + unresolved concerns.
- Adversarial round ended
REVISE→ present findings as-is. - In all cases, stop here — presenting findings for the user to triage is the whole point of peer review. Do not begin implementing changes, editing code, or modifying files. Ask the user which findings, if any, they want addressed.
- Reclaim any per-run Codex homes (no-op for other reviewers):
python3 <skill-dir>/scripts/ppr_paths.py --cleanup --review-id "$REVIEW_ID". It validates each recorded home before removing it and is safe to re-run. - Remove seven session temp files (plan, prompt, review, session, events, runner.log, exit.code). No globs. Error log intentionally retained for post-mortem. If a removal fails, report the failed path and leave the rest for manual removal — the files are sensitive per Rules.
Rules
- Keep the reviewer read-only. Never ask it to modify files or execute the host workflow. (
agyis the one exception — it is not read-only; see Parse reviewer arguments andreferences/antigravity.md.) - Capture the full output each round; no tail-scraping.
- Verify a Gemini blocking finding against in-repo evidence before adopting
it — Gemini can under-reason and confabulate HIGH findings on niche or
temporal facts (see
references/gemini.md); prefer ≥2 providers so one reviewer's confabulation can't force a false REVISE. - Never use transcript-sharing flags (
--share,--share-gist). - Treat the prompt/review/session/events files as sensitive (they contain plan text).
- Show the actual model/effort from the session file, not guessed values.