Skip to content
Back to skills

Review Branch

ASecurity

Converges a LOCAL branch with no pull request through a bounded review-apply-simplify loop. Dispatches the branch-reviewer agent, which runs the same impact graph, finders, verifier, and confidence/severity gates as pr-reviewer, but carries findings in a local findings.jsonl instead of GitHub review threads — so a run makes zero GitHub API calls, posts nothing, and works on an un-pushed branch or offline. Stops when every finding is applied or honestly declined AND the repo's own fast checks ...

  • 13 stars
  • 0 votes
  • 0 copies
  • 11 views
  • Added September 20, 2026
ai-agentsgobashgitapi

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 2, 2026

npx -y skills add mthines/agent-skills --skill review-branch --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Review Branch?

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

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

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: review-branch
description: >
  Converges a LOCAL branch with no pull request through a bounded
  review-apply-simplify loop. Dispatches the branch-reviewer agent, which runs
  the same impact graph, finders, verifier, and confidence/severity gates as
  pr-reviewer, but carries findings in a local findings.jsonl instead of GitHub
  review threads — so a run makes zero GitHub API calls, posts nothing, and
  works on an un-pushed branch or offline. Stops when every finding is applied
  or honestly declined AND the repo's own fast checks are green; a finding it
  can neither fix nor honestly decline stays flagged and visible, never quietly
  resolved. Use it to iterate before opening a PR, or when the comment round
  trip on your own PR is pure overhead. Once a PR exists use /review-loop
  instead — threads are the right bus when other people read them. Invoke with
  /review-branch [--base <ref>] [--cap N] [--effort high] [--no-simplify]
  [--no-checks] [--report] [--include-untracked].
disable-model-invocation: false
argument-hint: '[--base <ref>] [--head <ref>] [--cap N] [--effort high] [--no-simplify] [--no-checks] [--report] [--include-untracked]'
license: MIT
metadata:
  author: mthines
  version: '1.0.1'
  workflow_type: command
  tags:
    - review
    - code-quality
    - convergence
    - local
    - pre-pr
    - orchestrator
---

# review-branch — local convergence, no PR required

Drive a branch to a clean state by iterating **review → apply → simplify → re-review**, with every
finding carried in a local file instead of a GitHub review thread.

This is [`review-loop`](../review-loop/SKILL.md) with the GitHub round trip removed. The findings
are the same findings — the [`branch-reviewer`](../../../agents/branch-reviewer.md) agent runs the
same detection core `pr-reviewer` does, by reference rather than by copy — but nothing is posted,
nothing is fetched, and the loop's state lives in
[`.agent/{branch}/findings.jsonl`](./rules/findings-bus.md).

**When to use which:**

| | `review-branch` | [`review-loop`](../review-loop/SKILL.md) |
| --- | --- | --- |
| Needs a PR | no | yes |
| Bus | local `findings.jsonl` | GitHub review threads |
| GitHub calls | **zero** | many |
| Green gate | the repo's own fast checks | CI |
| Right when | iterating on your own work, pre-PR, offline | a PR exists and other people read the threads |

The thread is not overhead once someone else is reading it — it is the record, and it is how a human
reviewer sees what the agent already handled. Reach for `review-loop` the moment a PR exists.

## Modes

| Flag | Effect |
| --- | --- |
| `--base <ref>` | Base of the comparison. Default: upstream → `origin/HEAD` → `main`, and the loop announces which rung answered. |
| `--head <ref>` | Head of the comparison. Default: **the working tree**, so uncommitted work is reviewed. |
| `--cap N` | Iteration cap. Default 5. |
| `--effort high` | Forces the `deep` tier and raises diversify-then-vote from 3 finders to 5. |
| `--no-simplify` | Skip sub-step C (`polish simplify`). |
| `--no-checks` | Skip sub-step D. Convergence then means findings-clean only, and the report says so. |
| `--report` | Report-only. Forces `CAP=1`, skips B/C/D, applies nothing. The local counterpart of `review-loop`'s `--no-feedback`. |
| `--include-untracked` | Treat untracked, non-ignored files as added. Off by default. |

## Dispatch mechanics

`branch-reviewer` is an **agent**. Dispatch it with the harness's sub-agent dispatch tool; never
`Skill("branch-reviewer", …)`, which errors with `Unknown skill`.

**The check is a capability, never a tool name.** `Task` is the Claude Code CLI's spelling and
`Agent` is the Claude Agent SDK's; a tool taking a `subagent_type` (or equivalent) parameter is the
capability whatever it is called.

```text
# WRONG — a name check. Skips the review on every harness that spells it `Agent`.
if "Task" not in available_tools: skip

# RIGHT — a capability check, name-agnostic.
if no available tool dispatches a sub-agent (Task, Agent, or another spelling): skip
```

**Absent dispatch is a skip, not a fallback.** Reviewing in this context would be a self-review
wearing a reviewer's label — the same reasoning [`pr-review`](../pr-review/SKILL.md) applies. Report
it and stop:

```markdown
- [TIMESTAMP] review-branch — skipped (sub-agent dispatch unavailable; branch-reviewer requires it)
```

One absent-dispatch return is **conclusive**; never retry. And this loop, like `review-loop`, must
run at the **top level** — its first sub-step is a delegation, so dispatching the loop itself into a
sub-agent spends the budget one rung too high and leaves it nothing to review with.

## Procedure

### Step 0 — resolve the range and preconditions

```bash
BRANCH=$(git rev-parse --abbrev-ref HEAD)
BASE=${BASE_FLAG:-$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null \
      || git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null \
      || echo main)}
BUS=".agent/${BRANCH}/findings.jsonl"
mkdir -p "$(dirname "$BUS")"
```

Abort before any work if the range is empty — `git diff --quiet "$BASE"` — with
`review-branch: no changes against <base>.` **An empty range is never a pass**; reporting one as a
converged branch is the local spelling of green-washing.

Do **not** create the bus file yet. Its absence is how iteration 1 knows `FINDING_OVERLAP` is 0.

### Step 1 — the loop

```text
ITERATION = 0
APPLIED = DECLINED = FLAGGED = 0
CHECKS  = "unread"
STOP_REASON = "cap-reached"    # only correct if the WHILE CONDITION ends the loop;
                               # every break below overwrites it. Never re-derive a
                               # stop reason from the iteration count — a report-only
                               # run and a last-iteration convergence look identical.

while ITERATION < CAP:
    ITERATION += 1

    # A — review. Always first, so a pass validates the previous iteration's fixes
    #     before anything else touches the tree.
    <dispatch>(subagent_type="branch-reviewer",
               prompt="--base $BASE --out $BUS <flags>")
    # The FULL range every iteration, never narrowed to "since the last pass".
    # That is what lets this pass validate the previous one's fixes, and the bus —
    # keyed by fingerprint, not by line — is what keeps the re-review from
    # re-reporting what it already saw. A narrowed range would do the opposite:
    # hide a regression the last iteration's fix introduced outside the new delta.
    NEW = findings raised this pass that are not already in the bus

    if REPORT_ONLY:
        STOP_REASON = "report-only"; break

    if NEW == 0 and open_findings(BUS) == 0 and checks_are_green():
        STOP_REASON = "converged"; break

    open_before = open_findings(BUS)

    # B — apply. In-context, one commit per finding, highest severity first.
    #     Each finding leaves `open` through a fix OR a written rationale.
    apply_findings(BUS)          # → appends `applied` / `declined` / `flagged`

    # C — simplify
    if not NO_SIMPLIFY:
        Skill("polish", "simplify")

    # D — the local green gate
    if not NO_CHECKS:
        CHECKS = run_fast_checks()        # green | red | none
        if CHECKS == "red":
            fix the regression this loop just introduced, then re-run once
            if still red: STOP_REASON = "checks-red"; break

    if APPLIED_THIS_ITER == 0 and DECLINED_THIS_ITER == 0
       and open_findings(BUS) >= open_before:
        STOP_REASON = "no-progress"; break
```

### Sub-step B — apply, in this context

Unlike `review-loop`, the apply runs **here**, not in a worker sub-agent. There is no bus to hand a
worker and no thread for it to resolve, and the round trip buys nothing once the findings are
already on disk.

That is safe in a way the *review* is not: applying a finding is not judging it. The judgment
already happened in an isolated context, and its verdict, score, and severity are in the bus and
[reviewer-immutable](./rules/findings-bus.md#the-record).

Work highest severity first. Per finding:

1. Read the finding's `path:line` and its `evidence[]` **in the code**, not from the record.
2. Apply the smallest change that addresses the claim. Never widen it.
3. Commit it alone, so a bad fix reverts without taking a good one with it.
4. Append the new state — `applied`, `declined` with a `note`, or `flagged` — per
   [`findings-bus.md`](./rules/findings-bus.md#the-lifecycle).

**A finding you disagree with is `declined` with a rationale, never deleted.** A finding that is real
and out of scope is `flagged`, never declined. If you cannot write an honest rationale, that is the
signal you should be flagging it.

### Sub-step D — the local green gate

CI's local analogue. Run the repo's own fast checks — the ones a contributor runs before pushing —
discovered in this order, first hit wins:

1. A `check` / `verify` / `precommit` script in `package.json`, `Makefile`, `justfile`, or `Taskfile`.
2. The lint + typecheck + test targets the repo documents in `CLAUDE.md` / `AGENTS.md` / `README.md`.
3. Nothing found ⇒ `CHECKS = "none"`, which counts as green for convergence **and is reported as
   `none` rather than `green`** — a loop that ran no checks must not claim a clean build.

Scope them to the touched packages where the repo supports it; run them unscoped at least once
before the loop exits, because a scoped pass cannot see a consumer the edit broke elsewhere. That is
the same lesson [`implement-suggestion`](../../workflow/implement-suggestion/SKILL.md)'s Phase 6
pre-push gate encodes.

**Red checks are this loop's own mess to clean.** There is no `ci-auto-fix` handoff here: nothing
was pushed, so a red check is a regression this loop just wrote. Fix it, or revert the commit that
caused it. Never weaken a check, skip a test, or push past it — every
[`ci-auto-fix` anti-pattern](../../delivery/ci-auto-fix/rules/anti-patterns.md) holds.

### Step 2 — report

```text
review-branch on <branch> (<base>..<head>)

Iterations: <N> of <CAP>
Stop reason: <converged | no-progress (flags remain) | cap-reached | checks-red
              | report-only (--report) | skipped (sub-agent dispatch unavailable)>
Review: branch-reviewer, tier <deep|standard|quick>, intent <checked vs plan.md | not run>

Per-iteration:
  1: <F found>, <A applied>, <D declined>, <S simplify recipes>, <O still open>
  2: ...

Findings: <A> applied · <D> declined · <G> flagged · <K> deferred
Flagged (needs you):
  - <path:line> — <title> — <why the loop could not resolve it>

Checks at exit: <green | red (<failing>) | none found | skipped (--no-checks)>
Bus: .agent/<branch>/findings.jsonl
Head: <sha>
```

Report the `STOP_REASON` the loop set. Never infer it from the iteration count — `1 of 1` is what
report-only, a first-iteration convergence, and a `--cap 1` run all look like from outside.

**Surface every `flagged` finding prominently.** They are the reason a human is still needed, and a
run that buries them has converted the safety valve back into a green-wash.

## Hard rules

- **Zero GitHub calls.** Not `gh`, not `mcp__github__*`, in this skill or the agent it dispatches.
  That promise is the whole point; a run that quietly reaches for `gh` is a `review-loop` run with a
  misleading name.
- **The review is dispatched; the apply is not.** Reviewing in this context is a self-review wearing
  a reviewer's label. Applying in this context is fine — the judgment already happened elsewhere.
- **The dispatch precondition tests a capability, never the literal name `Task`.**
- **Absent dispatch is a skip, never an in-context review**, and one such return is conclusive.
- **Never green-wash.** A finding leaves `open` only via a fix that landed or a rationale that was
  written. Never by deletion, never at the cap, never to reach zero.
- **`flagged` is reported, always.** Converging with flags is an honest outcome; hiding them is not.
- **An empty diff is not a pass.** Report *no changes* and exit.
- **Never rewrite the bus.** Append; the last record for an `fp` is its state
  ([`findings-bus.md`](./rules/findings-bus.md#append-only-and-why)).
- **Never edit a reviewer-written field.** Claim, score, severity, and verdict are immutable to this
  loop — the same discipline `checks.yaml` applies to the executor.
- **Only `Skill("polish", "simplify")`.** Any other `polish` mode dispatches a reviewer pass and
  creates a cycle — the same anti-circularity guarantee `review-loop` holds.
- **Cap is a hard limit.** Surface what is open and stop; never extend it silently.
- **Hand off at the PR boundary.** Once a PR exists, `review-loop` owns convergence. Do not re-run
  this loop against a branch whose PR is open and being read.

## Relationship to other skills

| Skill | Relationship |
| --- | --- |
| [`branch-reviewer`](../../../agents/branch-reviewer.md) | Sub-step A — the PR-less reviewer, dispatched per iteration. Composes the detection core by reference. |
| [`review-loop`](../review-loop/SKILL.md) | **Sibling, never nested.** Same loop shape over a different bus. This one runs pre-PR; that one runs once a PR exists. |
| [`polish`](../polish/SKILL.md) | Sub-step C (`simplify` only). `polish`'s own review pass needs a PR; this loop is what makes a pre-PR review possible at all. |
| [`create-pr`](../../delivery/create-pr/SKILL.md) | **Caller**, at its pre-push Step 5.5 — default-on in both modes (`--no-pre-review` opts out), and load-bearing under `--split`, where its post-draft `review-loop` cannot run at all and the slot otherwise gets `polish simplify` with no review. Converge here, then open the PR with less left for the post-draft loop to find. |
| [`pr-review`](../pr-review/SKILL.md) | The one-shot read-only counterpart, for a PR. `--report` is this skill's equivalent for a branch. |
| [`findings-bus.md`](./rules/findings-bus.md) | Owns the record, the lifecycle, and the convergence predicate. Read it; never restate it. |

Files in this skill

  • SKILL.md13.9 KB
  • rules/findings-bus.md10 KB

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.