# Diff review

> Attach a compact working-tree diff to an LLM gate so the model reviews the actual change before the agent stops, instead of a guess reconstructed from the transcript.

A review hook that reads only the transcript sees scattered edit blocks, and it never sees changes made outside the agent's own edits. The `diff=True` flag hands the model a single compact `ccx diff`, budget-bounded so it stays cheap, so the review is grounded in the real change. When `ccx` isn't installed it falls back to a plain `git diff`.

``` python
"""Review the working-tree diff for leftover debugging artifacts before the agent stops."""

from __future__ import annotations

from captain_hook import Allow, Block, Event, Input, T, TouchedFile, llm_gate

# `diff=True` attaches a compact `ccx vcs diff` (a plain `git diff` when ccx is absent) as a
# <diff> block, so the model reviews the actual change instead of reconstructing it from
# the transcript. Stop carries no tool input, so the cheap pre-filter is the
# transcript-history condition TouchedFile -- "did this session edit Python source?" --
# and the gate fires at most once before the agent stops.
llm_gate(
    "The working-tree diff is in <diff>. Did this change leave debugging artifacts in the "
    "code, such as a stray print/console.log/debugger, a commented-out block, or a "
    "temporary TODO marked for removal? Block only if the diff clearly adds one.",
    message="Remove the debugging leftovers before stopping: {reasoning}",
    diff=True,
    only_if=[TouchedFile("**/*.py")],
    events=Event.Stop,
    model="small",
    max_fires=1,
    tests={
        Input(
            transcript=[T.assistant(T.tool("Edit", file_path="src/app.py", old_string="a", new_string="b"))]
        ): Block(),
        Input(transcript=[T.assistant(T.tool("Edit", file_path="README.md", old_string="a", new_string="b"))]): Allow(),
    },
)
```

> **Note: What the inline tests cover**
>
> The inline tests pin the deterministic gate: a session that edited Python source reaches the model, one that didn't stays silent. Under `uvx capt-hook test` the model verdict itself is stubbed to always confirm -- exercise the real judgment by replaying live sessions.


# What it does

`diff=True` resolves to `evt.ctx.diff()`, which prefers cc-context's token-budgeted `ccx diff` and falls back to `git diff` when `ccx` is unavailable. The result is wrapped in a `<diff>` block alongside the recent-transcript window, so the model reviews the change directly.

``` python
diff=True                          # attach a compact <diff> of the uncommitted change
only_if=[TouchedFile("**/*.py")]   # cheap pre-filter: only sessions that edited Python source
events=Event.Stop                  # review once, right before the agent stops
```

Stop carries no tool input, so the pre-filter is a transcript-history condition rather than a tool-input one. Deterministic conditions run first, so the model only sees a session that edited source. Diff against something other than the working tree with `diff="staged"` or `diff="HEAD~1"`.


# Run it yourself

``` bash
uvx capt-hook --hooks docs/examples test
```


# See also

- [Write an LLM hook](../../docs/guide/llm-hooks.md)
- [Query the transcript](../../docs/guide/llm-hooks.md#query-the-transcript)
- [LLM cost control](../../docs/examples/llm-cost-control.md)
