Agent-driven diff review with paint-by-numbers simplicity.
Reviewing an agent's code changes is the most time-consuming human-in-the-loop step in agent-assisted development, and the one most often skipped — because the reviewer is handed a diff in the tool's arbitrary order, with no explanation, and has to reconstruct why from what. dbn is a channel for the agent that wrote the code to walk you through it: a narrated, semantically-ordered Round you navigate yourself, comment on in place, and hand back as a list of Comments — which the agent works and puts through review again, Round by Round, until you hand off having raised nothing.
dbn holds no opinion about the code (it never decides what is shown) and enforces one thing against the agent's account of its own work: every changed line must be shown (the Coverage Ledger, derived from git — not from the agent). The agent that wrote the changes is the one that posts them, because the point is to hear the story from the one who knows it.
See CONTEXT.md for the vocabulary and docs/adr/ for the decisions behind it.
- A daemon owns the review state, exposing an MCP server on
127.0.0.1:7373, separate from any agent session so closing a window loses nothing. It starts on demand the first time an agent session connects and lets go of itself once no review needs it — you never run it by hand. - Your agent posts a Round over MCP — a Brief plus ordered Steps, each a self-contained idea built from line ranges it chose for comprehension. It posts once and ends its turn; it does not wait on you.
- You review in a terminal beside your agent session:
dbnopens the TUI, attaches to the daemon, and draws the Round. You move through Steps, select a line range to copy a self-contained Anchor into your agent chat, or raise a Comment in place: a change you want, or a question. Where the agent needs a decision from you, it asks an Agent Question, set apart from its narration, and you answer it where it is asked. - You hand off, the agent collects the Comments and your Answers with a second MCP call, responds to them, and posts a Revision Round — the full change set again, scoped by dbn to just what moved, with each of your Comments marked addressed, answered or declined, and each question given a status. Repeat until you hand off having raised nothing.
One line downloads the right prebuilt binary for your machine and puts it on your PATH — no Go toolchain needed:
curl -fsSL https://raw.githubusercontent.com/probertson/diff-by-numbers/main/install.sh | shIt installs to ~/.local/bin and verifies the download against the published
SHA-256 checksum. There are two options you can specify as environment variables:
curl -fsSL … | DBN_VERSION=v0.2.0 shinstalls a specific release instead of the latest.curl -fsSL … | DBN_INSTALL_DIR=/somewhere/bin shinstalls elsewhere.
Note the variables go on the sh side of the pipe, since that is the process that reads it.
Currently supports: macOS and Linux, on amd64 and arm64. On Windows, run it inside WSL2 (it uses the
Linux build). dbn version confirms the install.
dbn shells out to git, and needs git 2.25 or newer (January 2020) — it derives the Change Set
with pathspec files, which older git does not read. git --version confirms yours.
dbn checks once a day whether a newer release has been published, and says so in
the TUI header and under dbn version. To update:
dbn updateIt downloads the release for your platform, verifies it against the published SHA-256 checksums, and replaces the binary in place. Nothing is downloaded or replaced unless you run it.
A few things worth knowing:
- The daemon. If one is running and no review is in progress,
dbn updatestops it so it comes back on the new build (launchd/systemd restart it; an on-demand one starts next time your agent needs it). If a review is in progress the daemon is left alone — its review lives in memory — and dbn tells you to run the update again once you are done.dbn update --forcerestarts it anyway, losing the review. Agent sessions reconnect on their own. - If the binary's directory is not writable (say you installed to
/usr/local/bin), dbn will not use sudo for you. It points you at reinstalling somewhere you own with the installer above, or atsudo dbn update. - Turning the check off.
DBN_NO_UPDATE_CHECK=1disables it entirely — no network call is made.dbn updatestill works when you ask for it. - Builds you compiled yourself never check, and never update themselves.
go build -o /usr/local/bin/dbn ./cmd/dbn(Anywhere on your PATH is fine; dbn version confirms the build.)
Register dbn once. Your agent launches it per session, and it starts the shared dbn daemon on demand — so the review tools are always present, with nothing to start by hand first. For Claude Code, register it for every project:
claude mcp add -s user dbn -- dbn mcpWithout -s user, claude mcp add uses its default scope, local: dbn is
registered only for the project you ran the command in, so it is missing
everywhere else. To register it for just one project, run this from that
project's directory:
claude mcp add dbn -- dbn mcp(dbn must be on your PATH for your agent to launch it.)
Copy the review skill so your agent knows how to interact with dbn:
Via npx skills
npx skills add probertson/diff-by-numbers/skills/dbn-reviewAs a Claude Code plugin
In a Claude Code session:
/plugin marketplace add probertson/diff-by-numbers
/plugin install dbn@diff-by-numbers
(/plugin install opens the plugin panel on dbn, where you choose a scope to install it.)
Or from a terminal:
claude plugin marketplace add probertson/diff-by-numbers
claude plugin install dbn@diff-by-numbersThe skill teaches Step sizing and narrative ordering, what goes in the Brief, when an Acknowledgement is appropriate, and how to run the collect-and-revise loop.
The plugin does not auto-update by default. (Claude Code sets auto-update to "off" for third-party marketplaces by default.) To enable auto-updating for the diff-by-numbers plugin/skill:
/plugin— open the plugin panel- Switch to the "Marketplaces" tab
- Choose "diff-by-numbers"
- Choose "Enable auto-update"
Otherwise, dbn update will tell you when your skill is out of date with the
dbn you installed, and how to update it.
You do not need this: the shim starts the daemon on demand, and it stops itself once no review needs it. But if you would rather the daemon be permanently warm — so the first review of a session has nothing to start — run it on login/startup. This is purely a pre-warm; the shim simply finds and shares a daemon that is already running.
NOTE: dbn needs to be on your PATH for these. Both commands below write
the absolute path of your dbn ($(command -v dbn) — normally
~/.local/bin/dbn, where the installer puts it) into the service definition,
because neither launchd nor systemd expands ~. dbn update replaces that
binary in place, so the path stays good across updates; if you move dbn
elsewhere, rewrite the definition.
cat > ~/Library/LaunchAgents/com.probertson.dbn.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.probertson.dbn</string>
<key>ProgramArguments</key>
<array>
<string>$(command -v dbn)</string>
<string>serve</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/dbn.log</string>
<key>StandardErrorPath</key>
<string>/tmp/dbn.err.log</string>
</dict>
</plist>
EOF
launchctl load ~/Library/LaunchAgents/com.probertson.dbn.plistIt starts at login and is restarted if it exits. To stop it:
launchctl unload ~/Library/LaunchAgents/com.probertson.dbn.plistmkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/dbn.service <<EOF
[Unit]
Description=dbn review daemon (MCP server on 127.0.0.1:7373)
[Service]
ExecStart=$(command -v dbn) serve
Restart=always
RestartSec=2
[Install]
WantedBy=default.target
EOF
systemctl --user enable --now dbn.serviceTo stop it:
systemctl --user disable --now dbn.serviceTo read its logs:
journalctl --user -u dbn-
Ask your agent to "review [describe set of changes, for example 'the last two commits'] with dbn." The daemon starts automatically the first time your agent uses dbn — you do not need to start anything.
-
In a separate terminal, open the TUI:
dbnYou can open it before your agent is ready: with no daemon yet, the TUI waits for one and fills in the moment the Round is posted.
dbn opens on the Inbox: every review the daemon is holding, with what each
one is waiting on — you, or the agent. Move with the arrows, enter opens the
one under the cursor where you left it, and i puts it back down again without
handing it off, so several agent sessions can have reviews waiting at once. d
dismisses a review you don't want, after a y/n confirmation — it discards the
Comments you raised, and the agent is told you dismissed it.
Inside a review, the first screen is the Overview. Use Left/Right arrows to navigate
through screens. Select lines to copy-by-reference (for pasting to your agent, if you want
to ask questions mid-review) or to add a Comment. Press a to answer an Agent Question
where the agent asked it. When you're done, press h to hand the review off; if a
question is still unanswered, dbn lists it first and lets you answer it or hand off anyway. If your agent's harness can wait on a background command (Claude Code can), the
agent is told directly and the screen says so; otherwise it tells you to let the agent
know. Either way, it then retrieves your Comments. Allowing Bash(dbn wait:*) saves being
asked each round. Handing off is not leaving: q exits the viewer at any time without
losing anything, and dbn reopens to the same Inbox.
Other subcommands: dbn wait <review_id> blocks until you hand that review off or dismiss
it (the agent runs it in the background, from the command dbn gives it), dbn dump prints
every review the daemon holds as text, dbn version reports the build, dbn update
installs a newer one (see Updating).
go test ./... # the review core and git adapter are the tested seams
go vet ./...The review core (internal/review) owns the whole life of a Review and
knows nothing of MCP, git, or the terminal. The git adapter (internal/git)
derives changed lines; the working-tree adapter (internal/workingtree) reads
and fingerprints files. The daemon and TUI are deliberately thin.
A development build can run beside the released dbn without replacing it. Build it at the repository root, then register it once as a second MCP server on its own port, from the repository root:
go build -o ./dbn ./cmd/dbn
claude mcp add dbn-local -s user -- "$PWD/dbn" mcp -port 7374Every agent session then has both servers: dbn (the release, on 7373) and
dbn-local (this build, on 7374). Open the Reviewer's side with ./dbn -port 7374.
There is no need to run ./dbn serve -port 7374: each session's dbn-local
shim starts a daemon on 7374 from ./dbn if none is running, which is why a
hand-run one may find the port already in use. That daemon lets itself go once
no review needs it and nothing has talked to it for a minute, and the next
session to need it starts another from whatever ./dbn is then.
The installed dbn-review skill is the released one, and the wait_command a
post returns runs whichever dbn is on your PATH, which is also the release. So
tell the test session to use the development copies of both, e.g.:
Use the
dbn-localtools and follow<repo>/skills/dbn-review/SKILL.mdrather than the installeddbn-reviewskill. When you runwait_command, replace the leadingdbnwith<repo>/dbn.
where <repo> is the absolute path of this checkout. The command already
carries -port 7374, so it reaches the development daemon. To skip the
permission prompt each round, allow Bash(<repo>/dbn wait:*).
scripts/release.sh v0.1.0 # an explicit version
scripts/release.sh MINOR # or bump the latest tag: MAJOR, MINOR or PATCHA bump keyword reads the latest vX.Y.Z tag (after fetching tags from origin)
and bumps it, resetting the lower parts: MINOR takes v0.2.0 to v0.3.0. It validates (clean tree, on main, tag unused, tests pass), pushes main if
needed, then tags and pushes the tag — which triggers the release workflow that
builds and publishes the binaries. Pass SKIP_TESTS=1 to skip the test gate.