,___,
(O,O)
/)_)
" "
Review agent-written code with a second model before it ships.
DiffOwl is a local code review CLI. It builds focused context from a Git diff, runs a model through OpenCode, Codex, or the Cursor SDK, and records actionable findings in your repository.
It works with changes from any coding agent or human. You choose the backend and model on your machine. DiffOwl does not require a hosted DiffOwl account.
The agent that wrote a change should not be its only reviewer. Asking it to review its own work can repeat the same assumptions that produced the bug.
DiffOwl adds an independent pass between writing code and shipping it:
- Review the last commit, staged changes, a specific commit, or a whole branch.
- Review through OpenCode, a local Codex CLI authenticated with ChatGPT, or the Cursor SDK.
- Give the reviewer bounded local context instead of dumping the entire repository into a prompt.
- Keep findings after the review ends, with stable IDs and lifecycle states.
- Inspect and disposition durable findings after the review ends.
- Run reviews automatically after commits without blocking them.
TypeScript reviews can include changed AST symbols, related tests, file excerpts, and bounded import references. The structured import-reference section is TypeScript-only; non-TypeScript changes still get diff-centered review with targeted repository exploration.
- DiffOwl reads the Git change you selected.
- It filters files and assembles relevant local context.
- A separate model reviews the change through your selected local backend.
- DiffOwl writes a Markdown report and persists findings in SQLite.
- You inspect the findings, record their disposition, or hand them to a coding agent for resolution.
The orchestration and state stay in your repository. Review context goes only to the backend and model you selected.
You need Node.js 22.14.0 or newer. OpenCode is the default backend for existing installations.
npm install --global opencode-ai
opencodeConnect a provider in OpenCode, then install and initialize DiffOwl:
npm install --global diffowl
cd your-repository
diffowl initdiffowl init reports the selected runtime and the gitignored preference path. With OpenCode selected, it lists the models from your connected providers. Use diffowl backend codex before initialization if you want Codex, then choose a bare Codex model ID. The committed .diffowl.yml contains review policy, never your backend or model choice.
Codex reviews use an existing ChatGPT login from the local Codex CLI:
codex
diffowl backend codex
diffowl model gpt-5-codexCursor reviews use the official Cursor SDK with a separate SDK sign-in:
diffowl cursor login
diffowl cursor models
diffowl backend cursor
diffowl model composer-2.5 # choose an id from your account's model listSDK sign-in creates a named, expiring API key in Cursor's credential store; it
does not reuse a Cursor CLI login. You can also provide CURSOR_API_KEY. Usage
is billed to your Cursor account. diffowl cursor status checks the local
authentication state without displaying credentials.
The adapter allows read, search, glob, and directory-list tools, disables shell,
MCP, subagents, and ambient project settings, and rejects a review if its
repository guard observes changes. This is a tool policy, not an operating-system
sandbox or a restriction on which files can be read. Cursor reasoning overrides
are not supported yet; use the model's default and clear any saved override with
diffowl backend cursor followed by diffowl reasoning --reset. If .diffowl.yml still contains deprecated
reasoning.effort, remove that reasoning block too; resetting local preferences
does not edit project policy.
Review the last commit:
diffowlOr review work before committing:
git add -p
diffowl review --stagedThe latest report is written to .diffowl/reviews/latest.md.
| Command | Reviews |
|---|---|
diffowl |
The last commit |
diffowl review --staged |
Staged changes |
diffowl review --commit <ref> |
One commit |
diffowl review --base |
Committed branch changes since the default branch |
diffowl review --base <ref> |
Committed branch changes since an explicit base |
Useful review options:
# Faster review with less context
diffowl review --staged --depth shallow
# Use a different model once
diffowl review --staged --model openai/gpt-5.6-luna
# Use Codex once without changing saved preferences
diffowl review --staged --backend codex --model gpt-5-codex
# Emit a versioned JSON document for scripts
diffowl review --base --format json
# Exit 1 when actionable findings remain
diffowl review --base --fail-on-findingsCommit review compares the selected commit with its first parent. For a merge commit, this means the changes the merge introduced to its first-parent branch. It is not the pull-request diff.
Branch review uses the merge base through HEAD, matching the committed diff in a pull request. Use --base for pull-request coverage. Neither mode includes staged or unstaged changes.
diffowl readiness --base main
diffowl readiness --base main --format jsonReadiness checks the exact committed branch against a full review and any compatible, contiguous repair reviews. Dirty files and unhandled actionable findings block handoff. Fixed and dismissed findings are allowed; deferred and regressed findings still block. A later review does not silently resolve earlier findings.
Exit codes are 0 for ready, 1 for not ready, and 2 when Git, configuration,
database, or runtime state cannot be read reliably. JSON includes the resolved
base and HEAD, coverage review IDs, uncovered commits, blocker counts, and a
deterministic next action. --depth selects the expected context depth when it
differs from project configuration.
The query does not start a review, change repository state, or migrate a database. Older reviews without coverage evidence cannot prove readiness; obtain a new full review with this version. Older databases require an explicit state-writing command to upgrade. Known actionable output without durable finding identity remains blocking because it cannot receive a lifecycle disposition.
Agents must follow the handoff workflow before declaring
completion: query, follow next_action, re-query after changes, and attach the
current proof or an explicit blocker. That page includes small adapters for
Codex, Claude Code, Cursor, and generic agents. diffowl init installs the shared
instructions in its managed AGENTS.md block when accepted.
See the readiness contract for policy and limitations.
DiffOwl stores durable findings in .diffowl/state.db. A finding stays open until someone records what happened to it. A later model review that fails to mention it does not silently mark it fixed.
# List unresolved findings
diffowl findings
# Inspect a finding by ID, ID prefix, or latest:N
diffowl findings show fnd_abc
# Record the outcome
diffowl findings fix fnd_abc --note "Added a null guard." --verified-by "pnpm run test"
diffowl findings dismiss fnd_abc --reason "The caller already validates this value."
diffowl findings defer fnd_abc --reason "Blocked by an upstream change."
diffowl findings reopen fnd_abc --reason "The bug returned in a new path."Use --format json with findings list, show, or summary when another tool needs the backlog.
Inspect a finding with diffowl findings show, then record its disposition with fix, dismiss, defer, or reopen. Run a new review when you need new model analysis.
The optional diffowl-resolve skill lets a coding agent investigate findings instead of accepting the review at face value. It verifies each candidate against the current code, fixes confirmed problems, records dismissals or deferrals, and preserves the report history.
npx skills add gutierrezje/diffowl --skill diffowl-resolveRestart or reload the agent, then ask:
Resolve the latest DiffOwl review.
You can also ask it to investigate one finding, resolve every open review, or archive reports whose findings are fully handled.
Install the non-blocking post-commit hook:
diffowl hook installThe hook queues each commit, returns control to the terminal, and writes output to .diffowl/hook.log. Failed reviews remain pending and retry after a later commit.
In a Husky repository, the first install adds a portable bridge to the tracked
.husky/post-commit file. Commit that bridge if the repository should run
DiffOwl for every contributor. Machine-specific Node and DiffOwl paths stay in
worktree-local Git hook state, so later installs and runtime upgrades do not
dirty the tracked Husky hook or interfere with another linked worktree.
diffowl hook status
diffowl hook uninstallClaude Code users can also show the current finding summary when a session starts:
diffowl agent-hook install --client claudeProject review policy lives in .diffowl.yml. Backend, model, and model-specific reasoning choices stay in the shared, gitignored .diffowl/preferences.yml. Linked worktrees use the same preference file.
context:
depth: default
retention:
failed_execution_days: 14
failed_execution_limit: 200
gate:
fail_on_findings: false
timeout: 300
min_confidence: medium
skip_doc_only: false
include:
- "src/**/*"
exclude:
- "**/*.test.*"
- "**/*.lock"
- "**/dist/**"
rules:
- "Flag hardcoded secrets."
- "Check authorization at every write boundary."Inspect or change the local backend and its model without editing project policy:
diffowl backend
diffowl backend opencode
diffowl backend codex
diffowl backend cursor
diffowl backend --reset
diffowl model
diffowl model provider/model
diffowl model --reset
diffowl reasoning
diffowl reasoning thinking
diffowl reasoning --resetReasoning names are backend-native identifiers, not a shared DiffOwl scale. A model might advertise low and high, thinking, only one value, or no selectable value. An absent preference means the backend default; DiffOwl never translates one backend's names into another's. Changing a model clears its old reasoning preference so a stale value cannot carry over. Use diffowl review --reasoning <variant> for a one-review override.
When an explicit max selection is paired with a timeout of 300 seconds or less, DiffOwl warns before starting provider work. It does not lower the reasoning effort or extend the deadline automatically. Increase timeout in .diffowl.yml when a quality-first review should be allowed to run longer.
When model metadata rejects a variant, DiffOwl uses the backend default and prints the model's advertised choices. If the model advertises no selectable variants, the warning says so explicitly.
Each backend keeps its own model choice. Switching backends does not erase the other model. A legacy preference containing only model: provider/model still selects OpenCode. Legacy .diffowl.yml reasoning.effort values remain readable for migration and produce an exact cleanup warning; DiffOwl no longer writes them to project config.
Configuration is deep-merged with defaults, so the file only needs the settings your repository changes.
Failed execution retention applies per repository database after an attempt is
persisted. By default, it removes unreferenced failed, cancelled, and
timed-out executions whose terminal update is older than 14 days, then keeps
at most the 200 most recently updated eligible executions. Equal timestamps
are ordered by insertion order. Set either limit to 0 to disable that limit;
setting both to 0 disables cleanup.
Completed, running, and interrupted executions are excluded. Executions referenced by canonical reviews or other database foreign keys (including future verification records) are protected and do not count toward the limit. Cleanup removes operations only when they have no executions and no review. Reviews, findings, observations, and lifecycle events are never trimmed, and cleanup does not compact the database.
Cleanup runs in its own transaction after the attempt commits. A cleanup failure
rolls back cleanup without losing the attempt; the next persisted attempt retries
cleanup. The optional Node diagnostics channel diffowl.state.retention publishes
{ kind: "cleanup", databasePath, deletedExecutions, deletedOperations } when rows
are removed, or { kind: "cleanup-failed", databasePath, message } on failure.
These diagnostics do not change CLI stdout or the JSON review contract.
.diffowl.yml # Committed project policy
.diffowl/preferences.yml # Gitignored backend, model, and reasoning choices
.diffowl/state.db # Authoritative findings backlog
.diffowl/reviews/review-<timestamp>.md # Immutable review snapshot
.diffowl/reviews/latest.md # Copy of the newest report
.diffowl/reviews/resolved/ # Reports archived by the resolution skill
Linked Git worktrees share the durable backlog and review reports from the primary checkout. Runtime files such as hook logs and server state remain checkout-specific.
| Command | Purpose |
|---|---|
diffowl init |
Configure DiffOwl in the current repository |
diffowl review |
Run a review |
diffowl backend |
Inspect or change the local review backend |
diffowl model |
View or change the selected model |
diffowl reasoning |
View or change model-specific reasoning |
diffowl findings |
Inspect and update durable findings |
diffowl hook |
Manage the post-commit hook |
diffowl agent-hook |
Manage supported agent client hooks |
diffowl server |
Manage the local OpenCode server |
Run diffowl <command> --help for every option.
- No models found: run
opencode, connect or re-authenticate a provider, then rerundiffowl init. - Codex runtime missing: install the Codex CLI and make sure
codexis onPATH. - Codex authentication missing: run
codexand sign in with ChatGPT. - Review timed out: retry with
diffowl review --depth shallow. - Hook review failed: run the retry command shown by the next foreground DiffOwl command, or inspect
.diffowl/hook.log. - Agent did not load
diffowl-resolve: verify it withnpx skills list, then restart or reload the agent.
git clone https://github.com/gutierrezje/diffowl.git
cd diffowl
pnpm install
pnpm run build
pnpm link --globalRun the checks:
pnpm run lint
pnpm run testAfter changing src/**, rebuild before testing the globally linked diffowl command.
MIT © Jesus Gutierrez