Skip to content
Back to skills

Rig Review

ASecurity

Local code review, both halves. `find` (default): walk the REVIEWER.md catalog + scope invariants against a diff, two adversarial passes, return triaged P0–P3 findings — read-only. `fix`: drive review feedback to convergence, from a PR review bot OR local `find` results (spawn coder → recheck until clean). Triggers on: 'review my changes', 'review the diff', 'pre-PR review', 'review gate', 'check the diff before pushing', 'review fix', 'address the review', 'apply review feedback', 'fix the b...

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsrustgobashreactgitapisecurity

Works with

  • cursor
  • api

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add agent-rig/rig --skill rig-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rig Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rig Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agent-rig-rig-review/badge)](https://www.skillsdirectory.com/skills/agent-rig-rig-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rig-review
description: "Local code review, both halves. `find` (default): walk the REVIEWER.md catalog + scope invariants against a diff, two adversarial passes, return triaged P0–P3 findings — read-only. `fix`: drive review feedback to convergence, from a PR review bot OR local `find` results (spawn coder → recheck until clean). Triggers on: 'review my changes', 'review the diff', 'pre-PR review', 'review gate', 'check the diff before pushing', 'review fix', 'address the review', 'apply review feedback', 'fix the bot', 'address codex review', 'fix the findings', 'review loop'."
argument-hint: "[find | fix] [<PR>] [--source bot|local] [--rounds N] [--base <ref>] — default 'find' (read-only gate); 'fix' drives feedback to convergence"
allowed-tools: [Bash, Read, Agent]
---

# rig-review — find and fix, in one skill

Local code review with two verbs:

- **`find`** (default) — walk the project's `REVIEWER.md` catalog **and** the
  applicable per-scope invariants against a diff with a fresh-context `reviewer`
  agent (two passes), and return a triaged P0–P3 finding list. **Read-only** —
  it reports, it doesn't edit.
- **`fix`** — take review feedback and loop a `coder` agent over it until the
  review comes back clean (or a round budget is hit). Two feedback sources: a PR
  **review bot** on an open PR, or **local** `find` results.

The point is to **pre-empt the PR review bot**: every finding caught and fixed
locally is one you don't pay PR round-trip latency on. Implement/epic-style
flows call `rig-review find` then `rig-review fix --source local` so the loop
lives in one place.

## Configuration

Reads `.rig/config.json`:

- `review.patternsFile` — the P0–P3 catalog the reviewer walks (default
  `.claude/REVIEWER.md`). If absent, fall back to the kit's generic categories
  (correctness, security/trust-boundary, error-handling, concurrency,
  API/contract, tests, style) and say so.
- `review.bot` — PR review bot to poll/re-trigger: `codex`, `claude`, or `none`
  (default `none`). **`none` forces the local-only loop** for `fix`.
- `review.botRetrigger` — comment that re-triggers the bot (e.g. `@codex
  review`). Required when `bot` is not `none`.
- `review.maxRounds` — max fix↔recheck rounds before handing to a human
  (default `5`); `--rounds N` overrides.
- `vcs.baseRef` — diff base when no `<base>` is given (default `origin/main`).
- `project.repo` — `owner/name` for every `gh api` call (bot source). Never
  hardcode; derive from the git remote if unset.
- `agents.reviewer` / `agents.coder` — the project's names for those roles
  (default `rig-<role>`).
- `style.guideFile` — the writing style for findings (default
  `.claude/STYLE.md`).

**Scope invariants (per-subsystem `REVIEWER.md`).** Beyond the root catalog, a
subsystem may carry its own `REVIEWER.md` colocated with its code — concrete
correctness invariants it "learned the hard way," each ideally citing the PR
where it was found. `scripts/scope-reviewer.ts` resolves which apply to a diff
(ancestor-walk + any `governs:` globs a scope declares). `find` collects them
and has the reviewer assert each as a **P1**. No-op if the project ships none.

## Verbs & arguments

`$ARGUMENTS` begins with an optional verb, then args:

- **`find [<base>]`** (default when no verb) — diff against `<base>` or
  `vcs.baseRef`. For a stacked/epic child, pass the integration branch.
- **`fix [<PR>] [--source bot|local] [--rounds N] [--base <ref>]`**
  - `<PR>` implies `--source bot` unless `review.bot` is `none` or `--source
    local` is set.
  - `--source` default: `bot` if a PR is given and `review.bot != none`, else
    `local`.
  - `--rounds` default `review.maxRounds`; `--base` (local only) default
    `vcs.baseRef`.

If invoked from another skill, the caller may thread `{base}`, `{ticket-id}`,
`{worktree-path}`, and `{run-id}` — use them.

## Run state

A `fix` loop spans several rounds and a lot of bot output, so keep which
findings are already fixed on disk rather than in the transcript. `<STATE>` =
`bun <SCRIPTS>/rig-state.ts`. Resolve `<SCRIPTS>` from the repo root:
`.claude/scripts/` then `.rig/scripts/`, first hit wins, as in
[`/rig-worktree`](../rig-worktree/SKILL.md). See
[`docs/state.md`](../../docs/state.md). Skip these calls if the script is
absent.

- **Called with a `{run-id}`** (the usual case — `/rig-task` Step 4.5 and Step 6
  pass theirs): patch that run's `review` block after each round —
  `<STATE> patch {run-id} --step review-round-<n> --json '{"review":{"source":"local|bot","round":<n>,"p0":<n>,"p1":<n>,"p2":<n>,"open":["<finding, one line>"],"outcome":"pending|clean|actionable|timeout|unresolved"}}'`.
  The caller reads it back instead of re-running `find`.
- **Invoked directly on a PR**: open your own run, `pr-<N>` —
  `<STATE> init rig-review pr-<N> --json '{"phase":"fix","pr":<N>,"source":"bot","maxRounds":<review.maxRounds>,"round":1}'`
  — and patch `findings` and `round` each pass. Two guards apply: `outcome:
  clean` is rejected while any finding is still `open`, and `round` may not
  exceed `maxRounds`. Both rejections mean the same thing — hand back to a
  human rather than declaring victory.

Record the finding, not the bot's prose: severity, `file:line`, one line of
summary, and its status.

---
# `find` — the read-only gate

1. **Resolve the base** and confirm there's a diff:
   ```bash
   git fetch origin
   git diff --stat {base}...HEAD
   ```
   If empty, report `clean — no changes vs {base}` and stop.

   Then **collect the scope invariants** for the changed files:
   ```bash
   bun scripts/scope-reviewer.ts {base}    # or your TS runner; --files <paths> also works
   ```
   Capture as `{scope-invariants}`. Informational, never fails; if it prints "No
   scope REVIEWER.md apply", **drop the scope-invariant paragraph** below.

2. **Spawn the `reviewer` agent** (via `agents.reviewer`) against the diff,
   working in `{worktree-path}` if a caller passed one:

   > "Review the diff `git diff {base}...HEAD`{ticket-suffix}. Read
   > `{patternsFile}` first and walk **every** pattern against the diff. (If it
   > doesn't exist, walk the generic categories: correctness,
   > security/trust-boundary, error-handling, concurrency, API/contract, tests,
   > style — and say so.) Worktree path: {worktree-path}. Be specific — file and
   > line refs. Tag each finding with its pattern number and severity
   > (P0/P1/P2/P3). If clean against every pattern, say so explicitly.
   >
   > This diff touches scopes with recorded invariants — assert the diff against
   > **every** one; a violation is a **P1** tagged `scope-invariant`.
   > Invariants:\n{scope-invariants}"

   (`{ticket-suffix}` = ` for {ticket-id}` when passed, else empty. **Omit the
   scope-invariant paragraph** when step 1 found none.)

3. **If pass 1 has any P0/P1, stop here** and report (step 5) — no value hunting
   for more before known blockers are fixed. The caller re-invokes `find` after
   `fix`, and the adversarial pass runs on that next iteration.

4. **Adversarial second pass (only when pass 1 is clean of P0/P1).** Spawn a
   **second, independent** `reviewer` call — fresh context, framed to default to
   skepticism:

   > "A prior reviewer found this diff clean of blocking issues: `git diff
   > {base}...HEAD`{ticket-suffix}. Assume that verdict is wrong and find what it
   > missed. Read `{patternsFile}` and walk every pattern independently — don't
   > anchor on 'a reviewer already passed this.' Worktree path: {worktree-path}.
   > File/line refs, tagged with pattern number and severity. Also assert the
   > diff against the recorded scope invariants below independently; a violation
   > is a **P1** tagged `scope-invariant`. Invariants:\n{scope-invariants} If you
   > also find it clean, say so; don't invent findings to justify a second
   > opinion."

   (Omit the scope-invariant lines when step 1 found none.) Merge any P0/P1 the
   second pass surfaces into the list.

5. **Classify and report** (this verb finds, it does not edit):
   - `clean — 0 findings vs {base} (2 passes)`
   - `findings — N P0/P1, M P2/P3` — each with file:line, pattern number,
     severity, and `[pass 1]`/`[pass 2]`.

   Each finding is one entry in one order — severity, location, the defect, the
   consequence, the fix — per `style.guideFile` (default `.claude/STYLE.md`):

   > P1 — `api/invoices.ts:88`: the lookup uses the raw request ID, so any tenant
   > can read another tenant's invoice. Scope it to `session.tenantId` and add a
   > negative test.

   Address the code, not the author. Don't hedge a P0 into a suggestion, and
   don't pad a clean report to look thorough — if the diff is clean, say so in
   one line and stop.

   P0/P1 block (don't push). P2/P3 ship-with-a-note. To fix them, hand off to
   `fix --source local`.

---
# `fix` — drive feedback to convergence

## Source: `local` (findings from `find`)

1. **Get findings.** Use the caller's `find` output if passed; else run the
   `find` verb (`rig-review {base}`) first.
2. **Classify:** no P0/P1 → return `clean` (surface P2/P3); any P0/P1 → fix round.
3. **Fix round** (while `round <= N`):
   - Spawn **coder** (via `agents.coder`) in `{worktree-path}`:
     > "Address these review findings on {ticket-id}. Worktree: {worktree-path}.
     > Findings:\n{find_output}\n\nFor each, either apply the fix or explain in
     > one line why the finding is wrong. Don't silently skip."
   - Re-run the `find` verb (fresh context). If still P0/P1, increment `round`.
   - Patch the run state with the round's counts and which findings closed.
4. Return the outcome, and patch it (`"outcome": "clean" | "unresolved"`).

**Outcomes (local):**
- `clean — 0 P0/P1 vs {base}` (P2/P3, if any, listed)
- `unresolved — N P0/P1 after {N} rounds, leaving for your review`

## Source: `bot` (review bot on a PR)

Drives whatever bot `review.bot` names — mechanics are identical; only the
author login and re-trigger phrase differ. Set `REPO="$(project.repo)"` and
`RETRIGGER="$(review.botRetrigger)"` up front.

> **Headless in CI.** This same `fix` loop can run in GitHub Actions on each
> posted review (out of any local session) via `ci/workflows/auto-review-fix.yml`
> — one round per invocation, re-fired by the bot's re-review. Setup:
> `ci/README.md#review-bot-bundle` + `docs/auto-fix-app.md`.

**Identifying the bot.** Match the author with a case-insensitive regex on the
login, not an exact string — most bots post under more than one identity (a
`...-connector` for reviews, a `...[bot]` for inline threads); exact match
misses half the surface and stalls polling.
- `bot: codex` → regex `chatgpt-codex-connector`.
- `bot: claude` → regex `claude`.
- `bot: bugbot` → regex `cursor` (Cursor Bugbot posts as `cursor[bot]`; re-trigger
  comment is `bugbot run`).

**Clean signal differs by bot.** codex/claude announce a clean run with an
`APPROVED` review or a `+1` reaction. **Bugbot does not** — on a clean PR it posts
no review and no reaction; it completes a GitHub **check-run** (`Cursor Bugbot`,
app `cursor`) with `conclusion: success`. So for `bot: bugbot` the poll must read
that check-run — keying on review/reaction alone makes every clean PR time out and
the merge gates never go green.

Loop, `round` starting at 1:

1. **Wait for the bot.** Poll ~5 min (15 × 20s). Watch review state AND inline
   comments AND reactions in one snapshot:
   ```bash
   PR=<N>
   PUSH_TIME=$(gh pr view $PR --repo "$REPO" --json commits -q '.commits[-1].committedDate')
   BOT='<login-regex for review.bot>'
   REVIEW=$(gh pr view $PR --repo "$REPO" --json reviews -q "
     [.reviews[] | select((.author.login | test(\"$BOT\"; \"i\")) and .submittedAt > \"$PUSH_TIME\")] | last | .state // \"none\"")
   REACTION=$(gh api "repos/$REPO/issues/$PR/reactions" -q "
     [.[] | select((.user.login | test(\"$BOT\"; \"i\")) and .content == \"+1\" and .created_at > \"$PUSH_TIME\")] | length")
   INLINE=$(gh api "repos/$REPO/pulls/$PR/comments" -q "
     [.[] | select((.user.login | test(\"$BOT\"; \"i\")) and .created_at > \"$PUSH_TIME\")] | length")
   # bugbot only: its verdict is a check-run, not a review/reaction. Filter
   # server-side by name (so it can't fall off page 1 of an unfiltered list),
   # take the NEWEST run by started_at (a push auto-starts one and `bugbot run`
   # starts another on the same SHA — an older success must not win over a newer
   # in-progress run), and guard the no-run-yet case (`// "none"`) so the jq
   # concat doesn't error on early polls before Bugbot has created its run.
   SHA=$(gh pr view $PR --repo "$REPO" --json headRefOid -q .headRefOid)
   CHECK=$(gh api "repos/$REPO/commits/$SHA/check-runs?check_name=Cursor%20Bugbot" -q "
     [.check_runs[] | select(.name | test(\"bugbot\"; \"i\"))] | sort_by(.started_at) | last
     | ((.status // \"none\") + \":\" + (.conclusion // \"\"))")
   [ -z "$CHECK" ] && CHECK="none:"   # gh/network error → treat as not-yet-run
   ```
2. **Classify** (codex/claude use the review/reaction signals; bugbot uses `CHECK`):
   - **`bot: bugbot`** (classify off `CHECK`, the newest run for the head SHA):
     - `completed:success` → **clean**, exit.
     - `completed:` with any other conclusion (`neutral`/`action_required`/`failure`):
       - `INLINE > 0` → **actionable**, go to 3.
       - `INLINE == 0` → **race window** (inline threads lag the check conclusion,
         like the codex `COMMENTED` case): keep polling. If inlines never populate
         within budget, treat `neutral` as **clean** (body-only, no actionable
         threads) and `failure`/`action_required` as **actionable** to stay safe.
     - anything not `completed:` (`none:`/`queued:`/`in_progress:`) → keep polling.
   - `REACTION > 0` or `REVIEW == "APPROVED"` → **clean**, exit.
   - `REVIEW == "COMMENTED"` AND `INLINE > 0` → **actionable**, go to 3.
   - `REVIEW == "COMMENTED"` AND `INLINE == 0` → **race window**: review state can
     flip before inline threads are API-visible. Keep polling. If inlines never
     populate within budget AND the review body is boilerplate-only, treat as
     **clean** (a header-only review is a soft +1).
   - Timeout (5 min, nothing) → **timeout**, exit.

   **Never classify on review-state alone** — `COMMENTED` with empty inlines is a
   transient race, not "no findings."
3. **Fix round** (only if `round <= N`):
   - **Read and classify the bot's findings.** Pull inline threads (same regex),
     keep each comment's `id`:
     ```bash
     gh api "repos/$REPO/pulls/$PR/comments" \
       -q ".[] | select(.user.login | test(\"$BOT\"; \"i\")) | {id, path, line, body}"
     ```
     Bucket each by severity yourself (blocking = correctness/security/data-loss;
     advisory = style/nit). *(If the project ships a P1-gate classifier script,
     run it with `REVIEW_BOT_LOGIN` set to the bot's login regex — see step 4 —
     and use its output as authoritative.)*
   - Spawn **coder** (via `agents.coder`) with the review body + inline comments
     (each with `id`) and the instruction to apply fixes in `{worktree-path}`.
     Tell coder: "Address each actionable suggestion. If one is wrong, **push
     back in that comment's own inline thread — reply to it, don't open a
     top-level comment** — with the technical reason; don't silently skip":
     ```bash
     gh api --method POST "repos/$REPO/pulls/$PR/comments/<comment_id>/replies" -f body="<reason>"
     ```
   - Stage, commit, push (`fix({ticket-id}): address review`).
   - **Resolve the threads you addressed.** If merge gates on unresolved blocking
     threads, resolve each the coder actually fixed; for a blocker coder pushed
     back on, reply with rationale, resolve as explicit dismissal, and surface to
     the user — never leave an applicable blocker unresolved. Use the project's
     thread-resolution tooling if it ships one. A resolve *without* a push
     doesn't re-run a gate check — re-run the required check or let the next push
     re-trigger it.
   - Re-trigger: `gh pr comment $PR --repo "$REPO" --body "$RETRIGGER"`.
   - Increment `round`; update `PUSH_TIME` to the new commit; patch the run
     state with the new round and finding statuses; loop to 1.
4. **After N rounds** (or `clean`/`timeout`): the merge-queue gate is the
   authority — converge to it. If the project ships a P1-gate script, run it
   **with `REVIEW_BOT_LOGIN` set to the bot's login regex** (the same one used
   above — e.g. `REVIEW_BOT_LOGIN=cursor` for bugbot) so it matches the right
   author; left unset it defaults to a placeholder that matches nothing, so the
   gate falsely passes and can wave through an unresolved blocker. (exit 0 →
   `clean`; non-zero → `actionable` with blocking titles + URLs.) Else use the
   final snapshot + tracked unresolved blockers.

**Outcomes (bot)** — stacked/epic flows read this to decide auto-merge:
- `clean — PR <URL> reviewed by <bot>, no unresolved blockers`
- `actionable — PR <URL> still has N unresolved blocker(s) after {N} rounds. Findings: <titles + URLs>`
- `timeout — PR <URL> open, <bot> did not respond within 5 min, leaving for your review`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.