Surface tracked corrections

When a session ends on a durable correction in the user’s own words, point them at capt-hook status so the session reviewer can mine it into a hook PR.

You keep correcting Claude on the same rule — “never force-push”, “use uv, not pip” — and writing the hook by hand is friction you skip in the moment. The session reviewer already mines those corrections into hook PRs. The nudge below scores the last few transcript messages for that correction shape and, when one clears the threshold, points you at uvx capt-hook status so you can watch the lifecycle instead of hand-writing the hook.

"""Nudge toward the corrections lifecycle when a session ends on a durable correction."""

from __future__ import annotations

import re

from captain_hook import Allow, Event, Input, Signal, Signals, T, Warn, nudge

CORRECTION_SIGNALS = Signals(
    patterns=[
        Signal(pattern=r"\bnever\b", weight=2, flags=re.IGNORECASE),
        Signal(pattern=r"\b(always|from now on)\b", weight=2, flags=re.IGNORECASE),
        Signal(pattern=r"\b(use|prefer)\b.+\b(not|instead of)\b", weight=2, flags=re.IGNORECASE),
        Signal(pattern=r"\b(stop|don'?t)\b", weight=1, flags=re.IGNORECASE),
        Signal(pattern=r"\b(no,|actually,|that'?s wrong|i told you)\b", weight=1, flags=re.IGNORECASE),
    ],
    threshold=3,
    window=6,
    origin="any",
)


nudge(
    "You just took a durable correction from the user. You don't have to hand-write the hook: "
    "captain-hook's session reviewer is already mining corrections like this one. Run "
    "`uvx capt-hook status` to watch it climb toward a hook pull request.",
    signals=CORRECTION_SIGNALS,
    events=Event.Stop,
    max_fires=1,
    tests={
        Input(transcript=[T.user("No — never force-push to main, always open a pull request.")]): Warn(
            pattern="capt-hook status"
        ),
        Input(transcript=[T.user("Looks good to me, ship it.")]): Allow(),
    },
)

What it catches

"No — never force-push to main, always open a pull request."  # stacks "No —", "never", and "always" past threshold=3

What it allows

"Looks good to me, ship it."  # plain approval, no correction signals

The catch / allow split mirrors the hook inline tests, so it stays true as the hook evolves.

Run it yourself

uvx capt-hook --hooks docs/examples test

See also