Was live via ~/sbin symlink but never committed. Baseline for bug 5352fe72. Claude-Session: https://claude.ai/code/session_01G3SkMRBRWXyiGuMGs8WVas
8.8 KiB
review-loop-run
Purpose
Launch a detached MiniMax M3 review of a review-loop artefact (plan, spec diff, test files, code diff, or a "is this bug still real" verification) with a kind-specific prompt built from a fixed template, and report its status/severity counts — so the /review-loop skill never hand-writes prompt files, launcher scripts, or pkills.
Usage
review-loop-run launch --kind <plan|spec|tests|code|verify> --name <tag> [--round N] \
--file <path> [--file <path> ...] [--context "<text>"] [--prev <result.md>] \
[--tools "<extra allowedTools>"] [--timeout <secs>] [--dryrun|-n]
review-loop-run status [--name <tag>] [--round N]
review-loop-run counts <result.md>
review-loop-run --help
Subcommands
| Subcommand | Description |
|---|---|
launch |
Build the prompt, write it under the report dir, start ask-minimax-run detached via setsid nohup, print the prompt/stdout/result paths, exit 0 immediately. |
status |
For every <tag>-r<N> in the report dir (or the one named), print running/done exit=N, the result file's existence, and the CRITICAL/HIGH/MEDIUM/LOW counts parsed from it. Exit 0. |
counts |
Print CRITICAL=<n> HIGH=<n> MEDIUM=<n> LOW=<n> for one result file. Exit 0 when CRITICAL+HIGH == 0, else exit 4. |
Options (launch)
| Flag | Default | Description |
|---|---|---|
--kind |
required | Selects the prompt template and default best-practices set (§Templates). |
--name |
required | Tag for this artefact (e.g. spec-BC, code-D, verify-02a8436d). |
--round |
1 | Round number; appended to file names as -r<N>. Rounds ≥2 automatically add the previous round's result file (<tag>-r<N-1>-result.md, if present) to the reading list and a "do not re-raise adjudicated findings" instruction. |
--file |
≥1 required | Files the reviewer must read fully (diff files, test files, specs, item docs). Repeatable. |
--context |
— | One paragraph of task context (what the artefact closes, relevant source paths). |
--prev |
auto | Explicit previous-round result file (overrides the auto lookup). |
--tools |
kind default | Extra --allowedTools entries (comma-separated). |
--timeout |
1500 | Seconds before the sub-session is killed. |
--project |
current repo root | Repo to cd into before launching. |
--dryrun, -n |
off | Print the assembled prompt and the exact launch command; write nothing; launch nothing. |
Environment
| Var | Effect |
|---|---|
REVIEW_LOOP_RUN_DIR |
Report dir root. Default ${XDG_RUNTIME_DIR:-/tmp}/review-loop/<repo-basename>. |
REVIEW_LOOP_BP_DIR |
Best-practices repo. Default ~/dev/claude/projects/best-practices. |
Behaviour
- Resolve the repo root (
git rev-parse --show-toplevel, or--project); derive the report dir;mkdir -p. - Validate: kind in the closed set, name matches
^[A-Za-z0-9._-]+$, every--fileexists,ask-minimax-runon PATH, MiniMax profile present. - Assemble the prompt from the kind template (§Templates): header (non-interactive, write to the output file), task text,
--context, the "Files to read" list =--fileentries + the kind's default best-practices/gotcha files that exist + the previous-round result (rounds ≥2), the output path, constraints. - Write
<dir>/<name>-r<N>-prompt.txt. - Write
<dir>/<name>-r<N>-launch.sh(a tiny bash script thatcds to the repo, runstimeout <secs> ask-minimax-run --model MiniMax-M3 --allowedTools "<list>" --permission-mode acceptEdits -p "$(cat prompt)" > stdout 2>&1, then appendsexit=$?to the stdout file) and starts it withsetsid nohup <launch.sh> >/dev/null 2>&1 &. - Print the three paths and
launched.
status scans <dir>/*-r*-stdout.txt; a file whose last line matches ^exit= is done, else running; counts come from counts.
counts parses the review's own count-by-severity block first, never bare numbers scattered through the file (a finding heading like "## Finding 3 — HTTP 404 on ..." or "### MEDIUM: ..." must not be misread as a count):
- Locate the scope. Find the last line matching
/count.*severity|severity.*count|summary/iand take from that heading up to the next#-heading (or EOF). If no such heading exists, the scope is the last 40 lines of the file. - Extract per severity, within scope only. For each of CRITICAL/HIGH/MEDIUM/LOW, take the last match in scope of any of:
SEV: n,SEV = n,- SEV: n,**SEV**: n(markdown emphasis markers around/inside the label are stripped before matching, so**SEV:**also matches),| SEV | n |,SEV (n), orn SEV(word-boundary). A severity with no match in scope is 0 for this pass. - Fallback (derived). Only if scope step 2 finds no severity at all (no count block present anywhere), fall back to counting lines that START with the severity word —
^\s*(#+\s*)?(\*\*)?(CRITICAL|HIGH|MEDIUM|LOW)\bfollowed by:,—, or-— one count per finding line, never bare numbers elsewhere. Append a literal(derived)marker to the output in this case. - Missing severity → 0. Output stays
CRITICAL=n HIGH=n MEDIUM=n LOW=n(plus the optional trailing(derived)marker); exit 4 iff CRITICAL+HIGH>0, else 0.
Fixtures: tests/fixtures/review-loop-run/{bullet-list,table,inline-bold,heading-numbers,no-count-block}.md cover, respectively: a bullet-list count block, a markdown-table count block, an inline **HIGH**: 2 style block, a file with "HTTP 404" and a bare number in headings alongside a correct trailing count-by-severity table (regression case for the 2026-09-13 MEDIUM=404/HIGH=11 mis-parse), and a file with no count block at all (derived fallback from finding headings).
Templates (per --kind)
All prompts share: severity scheme CRITICAL/HIGH/MEDIUM/LOW, file:line citations, a concrete fix per finding, "no praise", a count-by-severity table at the end, "read every listed file fully; do not modify repository files".
| kind | task text | default reading set | allowlist |
|---|---|---|---|
verify |
"Is the planner item in the first file still an open defect on main? Verdict RESOLVED / STILL OPEN / PARTIAL / CANNOT DETERMINE with cited evidence; remaining work." | memory/gotchas-*.md not needed; adds git log access |
Read,Write,Glob,Grep,Bash(git log *),Bash(git show *),Bash(git grep *),Bash(git diff *) |
plan |
adversarial plan critique: missing requirements, unsafe sequencing, wrong code assumptions (verify), untestable statements, security gaps, prior gotchas | spec-driven-development, test-driven-development, security-architecture, api-design, validation, kubernetes + project memory/gotchas-*.md, memory/process-lessons.md |
Read,Write,Glob,Grep,Bash(git log *),Bash(git grep *) |
spec |
spec amendment review: ambiguity, untestable statements, contradictions with existing requirements, non-derivable test handles, ID collisions, security; the "symbol/module does not exist yet" class is out of scope for spec-ahead specs | spec-driven-development, test-driven-development, mechanical-test-generation, api-design, security-architecture, gotchas-workflow, gotchas-specs, gotchas-python | same as plan |
tests |
spec-ahead test review: every handle present and asserting normative behaviour, fixtures match pinned shapes, assertions would pass against a correct implementation, mock hygiene, fixtures that skip __init__, xfail reasons |
test-driven-development, mechanical-test-generation, gotchas-python | plan set + Bash(python3 -m pytest *) |
code |
implementation review against normative spec text and the tests: correctness, security, gotchas-python, behaviour changes to existing call sites, wire-shape changes, the branch must not touch tests/ or spec/ | security-architecture, llm-code-security, python-patterns, gotchas-python | plan set + Bash(git diff *) |
Dryrun behaviour
--dryrun prints: resolved repo and report dir, the reading list (marking any default file that is missing as (absent, skipped)), the full prompt text, the allowlist, the exact launch command line, and [dryrun] nothing launched. It creates no files.
Edge cases
- Unknown
--kind→ exit 2 with the closed set listed. - A
--filethat does not exist → exit 2 naming it (before anything is written). - Round ≥2 with no previous result → warning, proceeds without the "adjudicated" paragraph.
ask-minimax-runmissing or profile absent → exit 1.statuswith an empty report dir → printsno runsand exits 0.- Never uses
pkill; the launch script is self-contained so the user can kill by PID.
Examples
review-loop-run launch --kind spec --name spec-BC --round 2 \
--file /tmp/x/spec-BC.diff --context "WIA-17c..f close bug 02a8436d; source: controlplane/api/work_items.py"
review-loop-run status --name spec-BC
review-loop-run counts /run/user/1000/review-loop/agent-runtimes/spec-BC-r2-result.md