Test it, then ship it

The same cases, as inline tests.

Every preset you clicked in this tutorial is a test case, and hooks carry their tests inline. The widget below shows page one’s guard again — this time with its tests block.

uvx capt-hook test runs every hook’s cases through the real dispatch — the same engine the widgets here were verified against:

from captain_hook import Allow, Block, Input, block_command

block_command(
    ["git", "stash"],
    reason="Stashing loses uncommitted agent work",
    hint="Commit to a WIP branch instead",
    tests={
        Input(command="git stash"): Block(),
        Input(command="cd /tmp && git stash"): Block(),
        Input(command='echo "git stash"'): Allow(),
        Input(command="git status"): Allow(),
        Input(command='git commit -m "wip"'): Allow(),
    },
)

A hook without tests is a regex you’re hoping about. A hook with tests fails your CI when someone loosens the pattern — run uvx capt-hook test --json in a workflow and a regressed guard breaks the build, not the incident review.

One last thing: hooks can ask a model. When no pattern can decide — “is this SQL migration destructive?” — llm_gate hands the verdict to an LLM with your rubric. Below is a recorded verdict, and you can run the same gate live on-device: Chrome’s built-in model, WebGPU, or CPU WASM, whichever this browser supports. The model downloads once, and nothing you type leaves the page:

from captain_hook import Event, Runs, Tool, llm_gate

llm_gate(
    "You review database migrations for irreversible data loss. A migration is destructive when it "
    "drops or truncates a table, or deletes rows, with no backup, transaction guard, or reversible "
    "down-migration. Block only when the migration is irreversibly destructive against production data.",
    message=(
        "This migration is irreversibly destructive against production data. "
        "Snapshot the table, or add a reversible down-migration, before running it."
    ),
    events=Event.PreToolUse,
    only_if=[Tool("Bash"), Runs("psql")],
    agent=False,
    transcript=False,
)

That’s the tour. The real thing is one command away:

uvx capt-hook init

init scaffolds .claude/hooks/ with a starter hook and wires Claude Code’s settings. The quickstart takes it from there — same five minutes, your repo this time.