# Session reviewer

The session reviewer closes the loop on hook authoring. The corrections you give Claude during a session -- "never force-push", "use `uv`, not `pip`", "you weakened that test" -- are exactly the rules a hook should enforce, but writing the hook by hand is friction you skip in the moment. The reviewer notices those corrections for you. When a session ends, it reads the transcript, finds the durable corrections and the hooks that misfired, and once a pattern proves itself, opens a pull request that adds or fixes a hook. You review the PR like any other.

It is on by default after `uvx capt-hook init`. This page explains the path from a session to a PR, what it takes to run, and the knobs that decide what crosses the bar.


# How it works

The reviewer runs entirely on your machine, riding captain-hook's own event dispatch:

1.  The plugin's hook wiring runs `capt-hook run <Event>` for every lifecycle event, and the **reviewer rides that dispatch natively** -- the async `SessionStart` and `SessionEnd` passes fire the enrollment-gated reviewer, and the throttled sweep rides `Stop`. There is no dedicated reviewer hook entry to wire or break. The pass detaches into the background and returns immediately, so a utility command like `claude update` never blocks on it, and a non-interactive `claude -p` session end is skipped -- it carries no corrections worth mining.
2.  The background pass **resolves the repo**, checks that you are watching it, then **scans the ended transcript** for two things: durable corrections you gave, and hooks that fired when they should not have.
3.  Each finding is **judged by an LLM** for whether it states a durable, enforceable rule.
4.  A judged finding becomes a **candidate**. The reviewer tracks it across sessions and only acts once it clears its thresholds.
5.  An eligible candidate spawns a **headless brain** (`claude -p` running the `scanning-sessions` skill) inside the repo, which drafts the hook, proves it with `uvx capt-hook test`, and opens one pull request carrying the verbatim evidence.

Candidates come in two kinds. A **create** candidate turns a recurring correction into a new hook. A **fix** candidate amends a hook that misfired, adding a regression test that reproduces the misfire. Fix candidates clear the bar faster -- a hook firing when it should not is a concrete, actionable defect.


# Turn it on

`uvx capt-hook init` arms the reviewer for the current repo. To arm a repo without re-running `init`, enable it directly:

``` bash
uvx capt-hook review enable
```

Both paths set up what the reviewer needs:

- the captain-hook plugin registered in `.claude/settings.json` -- its event wiring carries the reviewer along with every other hook, and the brain loads the plugin's `scanning-sessions` and `authoring-hooks` skills directly;
- a per-machine watching flag for this repo.

The plugin owns the event wiring; `review enable` writes no hook entries of its own. Watching is stored once per machine, and the reviewer enrolls a repo the first time it runs in it -- a plugin-wired clone is watched from its first session end with no manual step. `review enable` arms a repo immediately instead of waiting for that first session; `review disable` is the durable opt-out, which auto-enrollment never overrides.


# Prerequisites

The reviewer shells out to two CLIs, so both must be authenticated:

- **`claude`** -- the judge and the PR-drafting brain both call it. Log in with `claude` once.
- **`gh`** -- the brain opens the PR with it. Authenticate with push and pull-request scope (`gh auth login`).

The repo also needs a git remote; the reviewer keys candidates to the normalized origin.


# What crosses the bar

A correction does not become a PR the moment you say it. It has to recur, survive an LLM verdict, and clear thresholds -- so a one-off aside never turns into noise. Every threshold is an environment variable with the `HOOKS_REVIEW_` prefix:

| Setting | Default | What it gates |
|----|----|----|
| `HOOKS_REVIEW_MIN_SESSIONS` | `3` | Distinct sessions a correction must recur across before a create candidate is eligible. |
| `HOOKS_REVIEW_MIN_DAYS` | `2` | Days a create candidate must span before it is eligible. |
| `HOOKS_REVIEW_MIN_SESSIONS_FIX` | `2` | Sessions a hook-misfire fix candidate must recur across. |
| `HOOKS_REVIEW_JUDGE_TIER` | `medium` | LLM tier that judges each candidate (`small`, `medium`, `large`). |
| `HOOKS_REVIEW_MIN_JUDGE_CONFIDENCE` | `0.6` | Minimum judge confidence for a candidate to count. |
| `HOOKS_REVIEW_MAX_OPEN_PRS` | `2` | Open reviewer PRs allowed per repo at once. |
| `HOOKS_REVIEW_BRAIN_MAX_BUDGET_USD` | `5.0` | Spend cap on the PR-drafting brain per session. |
| `HOOKS_REVIEW_BRAIN_MAX_TURNS` | `80` | Turn cap on the PR-drafting brain. |

Set them in your Claude Code `settings.json` `env` block, or export them in the shell that ends the session.


# Watch the queue

`uvx capt-hook status` shows everything the reviewer is tracking -- every correction, how close each one is to a PR, and what that PR would do -- grouped by lifecycle stage:

``` bash
uvx capt-hook status
```

``` text
captain-hook  ·  github.com/you/app    [watching]    PR slots 1/2

WATCHING   building toward the bar
  #3  fix     sessions █░ 1/2
      would fix hooks.style:nudge_1 (.claude/hooks/style.py): stop firing on its own output
  #1  create  sessions ██░ 2/3   days ██ 2/2
      would add a hook: "block force-push to main"

ELIGIBLE   a PR opens next session
  #2  create  ready  ·  sessions ███ 3/3   days ██ 2/2
      would add a hook: "run the test suite before every commit"

PR OPEN   pull request awaiting your review
  #4  create  https://github.com/you/app/pull/214   ·   4d open
      would add a hook: "use uv instead of pip"

ACCEPTED   PR merged
  #5  create  https://github.com/you/app/pull/208
      would add a hook: "use loguru instead of print"
```

The same view lives under the reviewer namespace as `uvx capt-hook review status`. Read it top to bottom as a funnel -- a correction climbs from `WATCHING` to a merged hook.


## The header

The top line is the repo the reviewer keys candidates to, whether you are `[watching]` it, and the open-PR slots in use (`1/2` here). At the cap, eligible candidates wait for a slot before a new PR opens. An unwatched repo says so and tells you to run `uvx capt-hook review enable`.


## The stages

Each candidate sits in exactly one stage:

- **Watching** -- tracked, still under the bar. The bars show judge-accepted progress toward the thresholds: distinct sessions and distinct days for a `create` candidate, a lower bar for a `fix`. A correction has to recur and survive an LLM verdict before it counts, so a one-off aside never climbs. See [the thresholds](#what-crosses-the-bar) for the exact gates.
- **Eligible** -- over the bar and watched, with a free PR slot. The next session that ends drafts and opens the PR; you do nothing.
- **PR open** -- the reviewer opened a pull request, shown with its URL and age. Review it like any other.
- **Accepted / rejected / stale** -- a merged PR accepts the candidate, a closed PR rejects it, and a PR left open past the staleness cutoff goes stale and frees its slot.


## Each row

A row reads `#id kind progress`, then the one-sentence summary of what its PR would do -- the verdict the judge wrote when it accepted the correction. A `create` candidate adds a new hook; a `fix` candidate amends an attributed hook that misfired and adds a regression test. Pull the verbatim evidence and the full thresholds for one candidate with `uvx capt-hook review show <id>`.


## Refresh and manual passes

`status` prints the last-synced PR state immediately, then refreshes open PRs against GitHub in the background and updates the frame when `gh` answers. Skip the network round-trip with `--no-sync`:

``` bash
uvx capt-hook status --no-sync
```

Corrections only land after a session ends with the reviewer watching the repo, so an empty queue usually means no session has closed yet. To backfill from a transcript you already have, run the ingest and judge passes by hand:

``` bash
uvx capt-hook review scan --transcript path/to/session.jsonl
uvx capt-hook review triage
```

For one candidate, or to script against the queue, the narrower views print plain lines:

``` bash
uvx capt-hook review list             # the repo's candidates and their status
uvx capt-hook review show <id>        # one candidate's row and threshold status
uvx capt-hook review threshold-check  # which candidates currently cross the bar
uvx capt-hook review sync-prs         # fold merged/closed PR states back into candidates
```

`update` moves a candidate's status by hand -- useful when you want to override the automatic flow.


# Turn it off

The reviewer is opt-out per repo:

``` bash
uvx capt-hook review disable          # stop watching this repo (candidates stay recorded)
uvx capt-hook init --no-review        # scaffold without arming the reviewer
```

`disable` stops new sessions from being reviewed; the candidates already recorded stay, so re-enabling later picks up where it left off.


# Cost and privacy

The reviewer reads your transcripts locally and never uploads them -- it sends only the specific correction text to the judge, and the brain runs in your own `claude` session. It does spend tokens: one judge call per new candidate at the configured tier, and, when a candidate is eligible, one brain session bounded by `HOOKS_REVIEW_BRAIN_MAX_BUDGET_USD`. The `max_open_prs` cap keeps it from flooding your repo with pull requests while you catch up.

For the kind of correction signal the reviewer mines -- and a hook that points you back here when you give one -- see [Surface tracked corrections](../../docs/examples/corrections-nudge.md).
