Repository navigation
Releases: markerikson/diffloupe
Release list
v0.5.0
This feature release adds a diffloupe gate command that fails a check while findings are still open on the pull request, adds --max-rounds to stop reviewing a PR after a set number of rounds, lets authors and bots dispute a finding and get a ruling, adds a post setting to keep low-severity findings out of inline comments, blocks review agents from reaching the network, and recovers agent answers that were given in the reply instead of the output file.
Changelog
diffloupe gate: Fail on Findings Still Open on the PR
review --fail-on only counts the findings posted in that round. If a finding was posted in round 1 and the author pushed something unrelated, round 2 passed. That's not much of a gate, especially for PRs written by agents that will happily push until the check goes green.
diffloupe gate reads the PR's live state from GitHub and fails when an open finding is at or above the threshold:
diffloupe gate 'owner/repo#123' --fail-on blocker --status "$HEAD_SHA"A finding counts as open when it is:
- a DiffLoupe inline thread that isn't resolved and hasn't been closed out as fixed by a later review (outdated threads still count), or
- a finding in the latest summary comment.
Severity comes from a new sev field in the comment markers. Comments from older versions fall back to the severity badge in the text. Findings marked pre-existing are listed but don't fail the gate.
Options:
--jsonprints the result as JSON.--status [sha]sets adiffloupe/gatecommit status on that commit (default: the PR head).--override-label <name>passes the gate when the PR has that label.--count-author-resolvedtreats threads the PR author resolved as closed. By default, a thread the author resolved keeps counting, so an author (or their agent) can't clear a blocker by clicking "Resolve".--review-missingfails when no review has ever reached the PR. Use it when the review job failed, so a PR whose first review broke doesn't pass as clean.
The gate exits 3 when it fails and 0 when it passes.
Because the summary comment is rewritten every round, a re-review now carries forward earlier summary findings that it didn't post again (files it didn't read, or findings cut by the cap) in a "Still open from earlier reviews" section. That keeps them visible to the gate.
--max-rounds
diffloupe review --max-rounds <n> skips the review once a PR has already had n rounds. It stops before any model call. A headless run exits 4 and result.json has status skipped with skipReason: "round-limit". With --github, the payloads put a "Reviews stopped" notice at the top of the existing summary comment, saying why it stopped and that the remaining findings are left for a human.
This is meant for agent-driven PRs: review and block a few times so the agent has a chance to fix things, then stop blocking instead of holding the PR forever. The example workflow labels the PR ai-review-max-rounds and passes the gate with --override-label, so the leftover findings stay on the PR and can be tracked later.
Round Progress in result.json
result.json has a new progress object showing where the bot's earlier findings stand after this review: open, fixed, withdrawn, upheld, fixedThisRound, withdrawnThisRound and new. The same data is in run.json as roundProgress.
Answering and Disputing Findings
Authors and bots can now answer a finding with a marker in a comment:
<!-- ai-review-response fp=3f9a1c2b verdict=disputed -->
This path is only reached after the lock is held; see `acquire()` above.verdict is disputed or fixed, and action works as an alias for verdict. One comment can answer several findings. Answers are read from any account, including other bots, in inline replies and top-level comments.
The coordinator rules on each dispute once:
- Withdrawn: the inline thread gets a reply that closes it, and the finding is never raised again on that PR, in any wording.
- Upheld: the thread gets a reply saying the finding still stands and stays open for a human reviewer.
Rulings are kept in a "Disputed findings" section of the summary comment, so later rounds keep them. A fixed answer is not a ruling. It is shown to the coordinator next to the finding, and the normal close-out decides. run.json has a new disputes list (withdrawn, upheld, or unjudged), and runs stats counts them.
post Setting: Choose What Gets Posted Inline
diffloupe.json has a new post setting:
{
"post": { "minSeverity": "should-fix", "questions": "drop" }
}Findings below minSeverity get no inline comment. They go into a collapsed "Minor findings" section of the summary comment and still count toward the verdict. questions: "drop" leaves questions out of the review entirely, before the finding cap, so they don't take up slots. result.json reports below-threshold findings as findings.minor instead of findings.posted.
Settings now reset to their defaults when a key is missing, so a long-running server no longer carries one repo's surfaces, extraExcludes or post rules into the next repo's run.
Review Agents Can't Reach the Network
We saw cheaper models run curl to fetch upstream sources mid-review. The governor now refuses shell commands that reach the network: curl, wget, ssh, nc and similar; gh api/gh pr/gh issue and other gh subcommands that call the API; git fetch/pull/push/clone/ls-remote; and network code inside node -e, bun -e, python -c and other inline interpreters. It looks through pipes, $(...), sh -c/bash -lc, timeout/env/xargs/nohup wrappers, and absolute paths like /usr/bin/curl.
This check reads command lines, so it can be worked around. In CI, also restrict outbound traffic to your model provider and GitHub, and keep write tokens out of the review step.
Reading Answers From the Final Reply
Some models give their JSON answer in the final chat reply instead of writing the output file, and the stage failed. When there's no output file, DiffLoupe now reads the answer from the reply, but only if it has the fields that stage expects. Across about 3,000 saved agent runs, this would have recovered 23 of 25 intent failures, 21 of 22 grouping failures and 9 of 10 walkthrough failures.
Example Workflow
examples/github-actions/review.yml is a copyable workflow with review, post and gate jobs, --max-rounds 3, the round-limit label, and the gate as the job to make required. DiffLoupe's own review.yml now runs the same review → post → gate flow. The README's "Running in CI" section documents the gate, answers to findings, exit code 4 and the new result.json fields.
What's Changed
- Add CI gate support for agent-driven PRs: severity posting rules, dispute handling, round limits, and a
gatecommand by @markerikson in #6
Full Changelog: v0.4.1...v0.5.0
v0.4.1
This patch release lists every copy of a repeated defect on a single finding, fixes Fireworks API keys not being detected, makes reviews on cheaper models fail much less often, and fixes a working-tree snapshot that could miss a just-saved edit.
Changelog
One Finding Per Defect, With Every Location
When the same bug showed up in several places, reviewers either filed one finding per copy or mentioned the other copies only inside the collapsed evidence. Either way, it was easy to fix one copy and miss the rest.
Findings now have an also_at list of other locations with the same defect. Reviewers file one finding at the place to fix first and list the other copies there. The coordinator keeps two separate lists:
- Copies of the same defect go into
also_at. - Related locations (a caller, a consumer, the fix location, a test) go into
cross_file_refs, as before.
PR comments, the summary comment's bullets, the markdown export and the web finding card show a "Same problem at: …" line after the impact line. When a finding is carried into a later round, its also_at lines are updated against the new diff. The run schema has a new alsoAt field.
On our 25-PR test set, this cut comments from 124 to 113 at the same review cost. The judge rated 67 of the 75 listed locations as real issues.
Fireworks and NeuralWatt API Keys
DiffLoupe looked up Fireworks keys under the provider id fireworks, but OpenCode's id is fireworks-ai. As a result, FIREWORKS_API_KEY and apiKeys["fireworks-ai"] were never detected. Reviews still worked only because the agent process inherited the environment. We fixed the provider id and added NEURALWATT_API_KEY for NeuralWatt.
Fewer Failed Slices on Cheaper Models
We ran the review benchmark with several cheaper models (GLM-5.3-Flash, DeepSeek V4.1 Flash, MiniMax M3.1 Flash) and fixed the failures they kept hitting:
- Slightly malformed output files. These models sometimes write JSON with raw tabs or newlines inside strings (common when suggested code comes from a tab-indented file) or with unescaped double quotes in prose. DiffLoupe now repairs those two cases before parsing. If the repair doesn't produce valid JSON, the failure message reports the original parse error, so the position matches the file on disk.
- Running out of steps before writing. OpenCode ends an agent's session at the step cap (
REVIEW_MAX_STEPS, default 80). Slower models that make many tool calls could reach the cap without having written their findings, and the slice failed. The governor now treats the step cap like the cost and time limits. It warns the reviewer as the cap approaches. From 5 steps before the cap, it allows only the findings write.
On the 16-slice PR where DeepSeek and MiniMax each failed 3 slices, both now complete with no failures.
If you want a cheaper setup, the best one we measured runs GLM-5.3-Flash at high effort for the reviewers, walkthrough and intent, with Sonnet 5.5 as the coordinator. It was about 95% precise at roughly $0.29 per review, against $1.80 for the default Opus setup:
REVIEW_MODEL='fireworks-ai/accounts/fireworks/models/glm-5p3-flash#high' \
REVIEW_MODEL_WALK='fireworks-ai/accounts/fireworks/models/glm-5p3-flash#high' \
REVIEW_MODEL_INTENT='fireworks-ai/accounts/fireworks/models/glm-5p3-flash#high' \
REVIEW_MODEL_MERGE='anthropic/claude-sonnet-5-5' \
diffloupe review ...Set effort with #high on the model names, not REVIEW_EFFORT. REVIEW_EFFORT would also change the Sonnet coordinator's effort. Expect reviews to take about 5 minutes instead of 2–3, and the largest PRs up to about 20 minutes.
Working Tree Snapshots Missed Same-Second Edits
To review uncommitted changes, DiffLoupe stages the working tree into a copy of the git index. The copy got a new modification time, which turned off git's check for files changed in the same second as the last index write. An edit saved right after a commit or git add could be left out of the review if the file's size didn't change. The copy now keeps the real index's timestamp.
ReviewBench Harness
The repo now includes scripts/review-eval/reviewbench.ts, a harness that runs DiffLoupe over a fixed set of PRs and has a judge model score the findings for precision and recall. It is for developing DiffLoupe and is not part of the published CLI.
What's Changed
- Add
also_atso one finding lists every copy of a defect by @markerikson in #3 - Add a ReviewBench harness for scoring DiffLoupe reviews by @markerikson in #4
- Fix Fireworks key lookup and cheap-model output failures found in the RQA model sweep by @markerikson in #5
Full Changelog: v0.4.0...v0.4.1
v0.4.0
This feature release adds a CI mode for headless reviews of GitHub pull requests, puts a copy of all findings into the PR summary comment, adds finding triage (accept, or dismiss with a reason) in the CLI, server and web UI, records token usage and how often findings get acted on, and adds --strict and --json to diffloupe doctor.
Changelog
CI Mode
DiffLoupe's headless mode was built for running a review from a terminal. Running it from a CI job that comments on the pull request needed a few more things: a fixed commit to review, a machine-readable result, distinct exit codes, and a guarantee that it reads the PR's actual comments instead of whatever runs happen to be saved on the machine.
diffloupe review --headless has five new options:
--githubreviews for a bot that posts the result. DiffLoupe still posts nothing itself. It writes the GitHub API calls topayloads.json, and a later job with a write token sends them.--head <sha>pins the review to the commit the workflow was triggered for, so a push during the run doesn't change what gets reviewed. It must be a full 40-character SHA in the PR's history.--out <dir>copies the run's files there, plus aresult.json.--jsonprintsresult.jsonon stdout instead of the summary.--fail-on <severity>exits 3 when a posted finding is at or above that severity.
diffloupe review '#123' --headless --github --head "$HEAD_SHA" --out review-out --max-cost 10With --github:
- Earlier rounds come only from the PR's comments on GitHub. Runs saved on the CI machine are ignored, so a cached data directory can't make the bot reply to comments that aren't on the PR.
- If
ghfails while reading the PR description or comments, the run fails. Before, it reviewed without them and could post the same findings again. - Every round reviews the whole PR. The summary comment is rewritten on each push and holds the findings, so a round that only looked at changed files would drop the open findings on the others.
result.json has the run's status (complete, failed, cancelled or skipped), the reviewed base and head, cost and wall time, whether the cost cap stopped it early, finding counts by severity, the round, the path to payloads.json, and the artifact name the summary comment links to.
Headless runs now use distinct exit codes:
| Code | Meaning |
|---|---|
| 0 | Reviewed, or skipped because there were no code files |
| 1 | The run failed |
| 2 | The run could not start, including bad flags and unknown options |
| 3 | --fail-on matched a posted finding |
| 130 | Cancelled |
A headless run that fails to start now exits 2 instead of 1. If a script checks for exit code 1, update it.
The README has a new "Running in CI" section with the result.json shape, the files in --out, and a gate job pattern. --fail-on and the gate job count only the findings posted in that round, so a finding posted in an earlier round no longer fails the check. To block merges until every finding is dealt with, use branch protection's "Require conversation resolution before merging".
The summary comment's marker now records the head it reviewed. We also removed the leftover auto-pause text and the /ai-review resume hint from the summary. DiffLoupe never implemented auto-pause.
Copy All Findings From the Summary Comment
The PR summary comment now has a collapsed "Copy all findings as Markdown" section. It holds every finding in a fenced block, so GitHub's copy button copies them all at once, for example to paste into an agent session.
When the full text would push the comment past GitHub's size limit, findings are cut to their heading and location, starting from the end of the list, until it fits. A note then points at findings.md in the run's artifact for the full text. When even that doesn't fit, a single line points at the artifact instead. The next review round leaves this section out of what it shows the model, so earlier findings aren't fed back to the reviewers in full.
Finding Triage
Findings can now be accepted, or dismissed with a reason: wrong, not-worth-fixing, already-handled, duplicate or other, plus an optional note.
- CLI:
diffloupe runs dismiss <run> <ids...> --reason <reason> [--note <text>],runs acceptandruns reopen.runs showprints finding ids and their triage status, and--jsonincludes it. - Web UI: each finding card has Accept and Dismiss buttons. Each file has a Viewed checkbox (or press
r) that collapses it, and a "show dismissed" toggle brings dismissed findings back. Dismissed findings are left out of the verdict, the counts,n/pnavigation and Copy all. - Server:
GET /api/runs/:id/state,PUT /api/runs/:id/state/findings/:fidandPUT /api/runs/:id/state/files. They return 409 while the run is still active.
runs stats splits dismissals by reason.
Token Usage and Acted-On Rates
Each run now records token usage per review slice and per stage (coordinator, walkthrough, intent draft), including how much of each session's first request came from the prompt cache. runs show and runs stats print it. For runs saved before this release, they read it from raw/record.json.
A re-review also records which of the previous run's findings had their code edited before it ran. A control span 35 lines away is checked the same way. runs stats uses both to report an acted-on rate next to a baseline, so you can tell whether findings lead to fixes more often than unrelated nearby edits.
diffloupe doctor --strict and --json
--strict makes warnings fail too. Use it in CI: a model that reports no cost is only a warning, but --max-cost can't be enforced without one. --json prints the checks as JSON.
Other Changes
Runs now record the DiffLoupe version they ran with. Installed copies report diffloupe@0.4.0. A git checkout reports the version plus the commit, such as diffloupe@0.4.0+1a2b3c4d5e6f. Before, an installed copy recorded only diffloupe, or the commit hash of whatever git repository it happened to be installed in.
What's Changed
- Record token usage and acted-on rates, and add finding triage by @markerikson in #1
- Add CI mode for headless reviews, plus a self-review workflow by @markerikson in #2
Full Changelog: v0.3.0...v0.4.0
v0.3.0
This feature release has reviewers check each change against a drafted statement of its intent, re-reviews only what changed since the last run, tags pre-existing findings and caps them separately, adds per-repo review instructions and diffloupe runs stats, fixes walkthrough stops that pointed at the wrong lines, and fixes a long-running server carrying one run's setup into the next.
Changelog
Intent Draft Before Reviewing
Each reviewer agent only sees its own slice of the change. It often couldn't tell what the rest of the change was for, so it flagged code that was correct given the parts it couldn't see.
Before the reviewers start, a small model now reads the whole diff and writes a short draft: a title, the goal, and the changes it expects. Large diffs are compacted to fit, and every file is kept. Each slice reviewer, the coordinator and the walkthrough get the draft. It is labelled as inferred, so they check it against the code instead of trusting it. The draft is cached per target, so re-running the same target doesn't pay for it again.
The intent model is a new stage. It defaults to Claude Haiku 5.5 when the walkthrough model is an Anthropic model, and to the walkthrough model otherwise. Set it with --model intent=<id>, models.intent in the settings file, or REVIEW_MODEL_INTENT. The draft costs about $0.01 per run.
In a paid comparison on 8 review slices, slices given the draft found 12.0 good issues per sample, against 10.0 without it. We haven't confirmed that the draft adds no cost: those runs cost more per run than the baseline, but they ran a day apart from the baseline runs, and the sample is small.
--intent text is now shown to reviewers as its own block, labelled as yours, next to the PR description. Before, it was appended to the end of the PR body.
Re-Reviews Focus on What Changed
Reviewing the same target again (round 2 and later) now sends only the files that changed since the previous run's head to the reviewers. Each reviewer sees the full diff plus the diff since the last review, and is told to focus on the new lines. The cap on new findings drops from 10 to 5 on these rounds. Grouping and the walkthrough still cover the whole change.
If more than half the files changed, or the previous head is no longer available, the round falls back to a full review. Use --full to force a full review. From round 4 on, the CLI warns that later rounds mostly find smaller problems. The headless summary now shows the round number.
Pre-Existing Findings
Findings about code the change didn't add or modify are now tagged pre-existing. They have their own cap of 3, so they can't push findings about the new code out of the review. They are listed after the other findings, don't count toward merge risk, and are shown muted in the web UI and in the markdown export.
Per-Repo Review Instructions
The settings file has a new instructions field: one string or an array of lines. Every reviewer and the coordinator get it, and where it conflicts with the built-in review guidance, it wins. Use it for things the code can't tell a reviewer: what counts as breaking in this repo, invariants that a diff doesn't show, and what not to report.
{
"instructions": [
"## What failure looks like here",
"A change that breaks runs already saved on disk.",
"## Don't report",
"Styling in src/web/."
]
}We also tightened the built-in review prompts. A mistake repeated across several places is now reported once, as a pattern. Reviewers flag lines in AGENTS.md, CLAUDE.md or the README that the change made stale. There is an explicit list of things not to report, and the coordinator drops the same categories and merges repeated findings.
diffloupe runs stats
diffloupe runs stats reports on each chain of re-reviews of the same target. For each round it shows findings raised, carried forward and fixed. It also shows findings dismissed and still open, cost per fixed finding, and totals across chains: how many findings on the change get resolved, the mean number of rounds, and the share of chains that go past three rounds. Filter with --repo and --since.
Each run now records a hash of the review prompts and of the repo's instructions. --by-version splits the counts by those hashes, so you can see whether a prompt change helped.
Walkthrough Stops on the Right Lines
Walkthrough stops often pointed at lines that didn't match what the stop described. In one case, a stop about a dispose() function was anchored 63 lines above it. In the saved runs we checked, about 60% of diff-run walkthrough stops and 40% of review-run stops missed their target.
DiffLoupe placed each stop exactly where the model said. The model had been counting down from the @@ hunk headers to work out line numbers, and on long hunks, especially new files, it lost count by tens of lines. The walkthrough diff now prints each line's number in the new file, and the model cites those numbers. Each stop location also names the symbol on its line. If the cited line doesn't contain that symbol, DiffLoupe moves the stop to the nearest line in the diff that does. Re-running the same diff put every stop location on the right line.
Other Fixes
A long-running diffloupe serve reused the agent configuration from its first run. A later run against a different repository could get the wrong repository name in its prompts and the wrong scratch directory. The configuration is now reset when each run ends, so one server can review several repos.
diffloupe runs show <id> --json now prints JSON. The parent runs command had been taking the --json flag for itself.
The code map now caches parsed files by their git blob id, so runs after the first one in a repo skip re-parsing unchanged files. That takes about 5-10 seconds off the first stage.
Startup detects the OpenCode version in the path and enforces v2 as a requirement.
Full Changelog: v0.2.1...v0.3.0
v0.2.1
This bugfix release fixes reviews that failed because agents wrote their output to the wrong file, explains why a review slice failed, makes a bare branch name review the whole branch, and shows file groups sooner while a review runs.
Changelog
Failed Reviews From Mismatched Output Paths
We had a review fail with "all 2 review slices failed" even though both reviewer agents had finished, spent about $1.50, and written findings. It turned out that the agents had written their output files under a slightly different name than the one DiffLoupe was looking for.
The run tag, which names each agent's output file, used a 7-character commit SHA. The temporary checkout that the agents read from used a 12-character SHA in its directory name. The agents passed that directory path to tilth dozens of times, and then wrote their output with the 12-character SHA instead of the path the prompt gave them.
We've fixed this in two ways:
- Run tags and every displayed commit now use 12-character SHAs, so the two names match.
- DiffLoupe now refuses any agent write or edit to a file other than that agent's output file, and refuses patch tools outright. The refusal message names the correct path, so the agent can retry.
Run IDs for new runs now contain 12-character SHAs. Existing runs keep their IDs and still open normally.
Clearer Failure Messages
When every review slice failed, the error always told you to run diffloupe doctor, even when credentials were fine. DiffLoupe now reads each failed agent's output to find the cause. The cause can be a provider error such as an invalid API key, a write to the wrong path, invalid JSON, a non-zero exit, or no output at all. The error lists the cause for each slice and points to its raw output. It only suggests doctor when the cause looks like a credential or tooling problem. The error banner in the web UI now keeps those line breaks.
Bare Branch Names Review the Whole Branch
diffloupe review my-feature used to review only the tip commit of my-feature. A bare branch name now means the whole branch against its merge base with the default branch, the same as branch:my-feature. Use commit:my-feature to review just the tip commit. A bare name of the default branch still means its tip commit, since the default branch has no commits of its own to compare. The terminal log now shows how many commits a review covers.
Groups Show Up Sooner
Grouping only needs the git range, so it now runs before the registry check and the repo map. The page shows the computed groups before the group-naming model call returns. While a review is running, the summary now says that findings will appear when the review finishes, instead of "No new issues found." Failed and cancelled runs also say so in the summary.
Full Changelog: v0.2.0...v0.2.1
v0.2.0
This major release is a complete rewrite. It replaces the single-call LLM analyzer with a multi-agent review pipeline, adds a local web UI for reading reviews and grouped diffs, saves every run so you can reopen and re-review it, and adds diffloupe diff, a grouped diff viewer that needs no model calls.
Warning
0.1 was my initial intent-only review tool, and no one but me used it :) 0.2 shares almost no code with 0.1. The analyze, pr and summarize commands are gone. DiffLoupe now runs under Bun and drives its agents through OpenCode. See Breaking Changes below.
Changelog
Why the Rewrite
0.1 sent the diff and some nearby files to Claude through TanStack AI and asked for an intent summary, a risk list and an intent check. For large diffs it split the work into two passes or into "flows". It worked, but it was one model reading a diff with very little of the codebase in view. The findings were often guesses that a reviewer could have ruled out by opening one more file.
I'd been building a multi-agent code review pipeline at Replay, and it had done much better on real PRs. 0.2 ports that pipeline into DiffLoupe as the review core and builds a new CLI, server and web UI around it. I also borrowed ideas from a number of other tools along the way (see Inspirations).
Multi-Agent Reviews
diffloupe review now runs in four stages:
- Group. The changed files are grouped into review units from links in the code: a name defined in one file and used in another, a test and its source, an import, a shared directory. Generated and lock files are skipped. Docs are listed but not reviewed in depth.
- Review. Each unit gets its own reviewer agent. The agent reads the surrounding code with tilth and reports findings with evidence. Up to six reviewers run at once.
- Coordinate. A coordinator merges and deduplicates the findings, drops the ones it cannot verify, and checks the change against its stated intent: the PR description,
--intent, or the commit messages. - Walk through. A walkthrough pass orders the change into chapters and stops, with a "start here" list.
Every agent runs through OpenCode with a config that DiffLoupe writes for each run. Your own agents, plugins and MCP servers are not used. Your default model and your providers block are.
The default models are Claude Opus 5.5 for the reviewers and the coordinator, and Claude Sonnet 5.5 for the walkthrough and group naming. With those defaults, real runs so far have cost about $0.10 for a one-file commit, about $1 for a small PR, and $4–5 for a large one. Use --max-cost or maxCostUsd to set a limit. The run page and diffloupe runs show what each run cost.
review accepts many more targets than 0.1 did: worktree (the default), staged, any commit (HEAD~2, a SHA, a tag), a..b and a...b ranges, branch:<name>, and pull requests as #123 or a URL. Pull requests are reviewed in a temporary worktree at the PR head, so your checkout is not touched. --headless runs without a server and prints a summary, or the full review with --markdown.
The Web UI
review and diff start a local server, print the run URL, and stream progress to the terminal. The page layout follows pulls.review, and diffs are rendered with @pierre/diffs.
- A summary at the top, laid out like a GitHub review comment: merge risk and production risk, finding counts, findings grouped by file, whether the change matches its intent, and where to start reading.
- Groups in the sidebar and down the page, each with a short summary and its own file tree. Under each file in the tree, one row per finding and per walkthrough stop links to its line.
- Finding cards under the lines they refer to, with explanation, evidence and a suggested fix.
- Walkthrough stops as separate cards, with a Guide mode in the sidebar for stepping through them in order.
- Copy on each finding and Copy all put the findings on the clipboard as markdown, ready to paste into a coding agent.
- Keyboard navigation (
j/kfiles,n/pfindings,]/[stops,?for the full list), and browser Back/Forward between the findings you jumped to. - While a review runs, the page shows its progress and fills in the file groups as soon as grouping finishes.
The server only accepts loopback hosts and same-origin requests. If a diffloupe serve is already running, review, diff and open use it instead of starting another server.
Saved Runs and Re-Reviews
Each review is saved as a run under ~/.local/share/diffloupe/runs/. diffloupe runs lists runs, and runs show prints one, optionally as markdown. diffloupe open reopens a run in the browser. runs rm and runs prune delete old runs along with their scratch files and pinned refs.
Reviewing the same target again links the new run to the previous one. Findings that were fixed are listed as fixed, and findings that are still open are carried forward instead of being reported again.
diffloupe diff
diffloupe diff shows a target's diff in the web UI, grouped the same way as a review, with no review agents and no credentials. It takes about a second. Grouping reads files from git objects, so no worktree is created. --walkthrough adds the walkthrough guide from one model call.
owner/repo#123 and PR URLs also work outside a clone of that repository. DiffLoupe keeps a blobless copy of the repo under ~/.local/share/diffloupe/repos/ and fetches only the commits and file contents it needs. A diff run page has a Review these changes button that starts a full review of the same commits.
Models, Credentials and doctor
Each stage (review, coordinator, walkthrough) looks up its model on its own: REVIEW_* environment variables, then --model, then the per-repo --config file, then ~/.config/diffloupe/config.json, then the model in your global OpenCode config, then the built-in defaults.
For credentials, DiffLoupe checks each provider's environment variable first, then apiKeys.<provider> in its config, then an API key saved with opencode auth login. OAuth logins such as a Claude subscription are not shared with the agents. If a provider has no credential, the review stops before it starts and names the provider, the stage, and the ways to add one.
diffloupe doctor checks Bun, git, OpenCode, tilth and gh. It prints the model for each stage and where that choice came from, the source of each provider credential, and how much disk the data directory uses.
DiffLoupe no longer loads .env files, so a .env in a repo you review cannot supply or override a key.
Breaking Changes
- Removed commands:
analyze,prandsummarize. Usediffloupe review <target>instead. Pull requests are now a target (#123, a URL, orowner/repo#123fordiff). - New requirements: Bun 1.3 or later runs the CLI, even if you install with npm. OpenCode 2.x and tilth are required for reviews.
ghis needed only for pull requests. - Config:
~/.config/diffloupe/config.jsonnow holdsprovider,models,effort,apiKeys,maxCostUsd,hostandport. The config location honorsXDG_CONFIG_HOME. - Platforms: developed and tested on Linux and WSL2. macOS should work but has not been tried. Native Windows is not supported; use WSL.
Packaging
The npm package is a self-contained Bun bundle with no runtime dependencies. The web build splits out vendor chunks and drops unused Shiki themes, grammars and WASM.
Inspirations
DiffLoupe borrows ideas from a lot of other tools:
- pulls.review: the page layout, with grouped files, sticky group panels and per-group file trees
- Codiff: a diff viewer with walkthroughs
@pierre/diffs: the diff renderer- Plannotator: the local review server, browser opening and request guards
- LaReview: flow-based grouping
- GitHuman: pre-commit review
- Diffray: validation patterns and confidence scoring
- Critique: prompt engineering principles
- CodeRabbit: incremental review
Full Changelog: https://github.com/markerikson/diffloupe/compare/8ece6de31..v0.2.0