Skip to content
 
 

Repository files navigation

diffity

npm version License: MIT

Diffity is an agent-agnostic, GitHub-style diff viewer and code review tool — with a live loop where the reader asks questions or requests changes on any line and a parked agent answers.

This is Natural Cycles' fork of nilbuild/diffity by Kamran Ahmed. The review loop, the live agent protocol, session carry-forward and the idle lifecycle are this fork's own; the viewer it grew from is upstream's.

npm install -g @naturalcycles/diffity
diffity skills install

The binary is diffity. skills install puts the agent skills into ~/.claude/skills — run it again after an update (diffity update says when the skills changed). Any skill directory named diffity-* is treated as diffity's and may be replaced or removed there; diffity-dev-* and everything else is never touched. Had upstream's diffity installed? npm uninstall -g diffity first, or npm refuses the colliding binary. It works with Claude Code, Cursor, Codex, and any AI coding agent.

What can you do? Description
See your diffs View changes in working area, across commits, branches, tags, etc
AI code review Let your agent review code and leave comments on the diff
Browse project files Explore your repo and comment on any file for AI to resolve
Guided code tours Walk through your codebase step by step with highlighted code
Learn any topic Project-driven learning for programming languages, tools, and frameworks
GitHub PRs Pull down a PR, review it locally, submit one review back
Reading order Read a diff in the order it makes sense, not alphabetically
Attention Highlight what matters, dim what a rule can prove is mechanical
Review state See when a review is still running, and what has already been sent
Multiple projects Run it in multiple repos at once, each gets its own port

See your diffs

Run diffity inside any git repo — your browser opens with a GitHub-style, syntax-highlighted diff.

# everyday use
diffity                                    # review all uncommitted changes
diffity HEAD~1                             # review your last commit
diffity HEAD~3                             # review your last 3 commits

# branch workflows
diffity main                               # compare current branch against main
diffity main..feature                      # compare feature branch against main
diffity main feature                       # same as above, shorthand syntax
diffity --base main --compare feature      # same as above, explicit flags

# releases and tags
diffity v1.0.0 v2.0.0                     # compare two releases
diffity v1.0.0                             # what changed since v1.0.0

# specific commits
diffity abc1234                            # changes since a specific commit
diffity abc1234..def5678                   # changes between two commits

# filter by change type
diffity work                               # all changes (staged + unstaged + untracked)
diffity staged                             # only staged changes (git add'd)
diffity unstaged                           # only unstaged modifications

The --base/--compare flags use the same terminology as GitHub PRs — base is what you're comparing against, compare is the branch with changes. You can also use range syntax (main..feature) or just pass two positional args (diffity main feature).

You can leave comments on any diff — working tree changes, branch comparisons, commit ranges. Your agent can also review and leave its own comments. Either way, run /diffity-resolve and your agent reads all open comments (yours or its own) and makes the code changes for you.

AI code review

Install the skills for your coding agent (diffity skills install), then use the slash commands:

/diffity-diff

Opens the diff viewer in your browser. Accepts the same refs as the CLI, plus natural language:

/diffity-diff                          # working tree changes
/diffity-diff main                     # current branch against main
/diffity-diff main..feature            # branch diff
/diffity-diff HEAD~1                   # last commit
/diffity-diff last 3 commits           # natural language works too

Leave comments on any line — when you're done, run /diffity-resolve to have your agent fix them.

/diffity-review

Your agent reviews the diff and leaves inline comments in the viewer, prefixed with severities so you can triage by importance — P1/P2/P3 by default, or whatever the project configures (see Review standards). It reads the project's own standards first, records a reading order for the diff, and announces itself as running so you do not approve while findings are still arriving. Supports refs, focus areas, and natural language:

/diffity-review                             # the branch's pull request, or the working tree
/diffity-review main                        # review what you're merging into main
/diffity-review main..feature               # review what you're merging into main
/diffity-review identify security issues    # focus on security issues
/diffity-review performance in src/lib      # focus on performance in specific dir
/diffity-review last 3 commits              # natural language works too

/diffity-resolve

Reads all open comments and makes the requested code changes. Works with both your comments and AI review comments:

/diffity-resolve                       # resolve all open comments
/diffity-resolve abc123                # resolve a specific thread by ID

A typical workflow: run /diffity-review to get AI feedback, check the comments in the browser, then run /diffity-resolve to apply the fixes.

A live agent on the diff

Every comment box carries Ask and Act next to the plain reply. Ask hands the agent a question — it answers in the thread, or amends the finding the question was about. Act hands it a change request — it edits the code and replies with what it did. A question never turns into an edit, and on somebody else's pull request the agent is told not to touch code.

The agent parks on the review with diffity agent await (the diffity-live skill drives the loop): it sleeps until you press one of the buttons, acts, and re-arms. The page shows whether an agent is listening, working, or absent — a request made with nobody listening says so, and is picked up when an agent next arms. When the review page closes, the parked agent is told and stops rather than waiting on a window nobody has open; the server itself stops a few minutes after its reader leaves, and everything is in SQLite, so running diffity again picks the review back up.

Browse project files

Run diffity tree to open a full file tree browser — no diff required. Browse your repo, read files with syntax highlighting, and leave comments on any file or folder.

diffity tree

The tree view supports the same commenting and resolve workflow as the diff viewer. Leave comments on specific lines, files, or folders, then have your agent resolve them.

/diffity-tree

Opens the file tree browser:

/diffity-tree

/diffity-resolve-tree

Reads open comments from the tree browser and makes the requested code changes:

/diffity-resolve-tree                  # resolve all open comments
/diffity-resolve-tree abc123           # resolve a specific thread by ID

Guided code tours

Create narrated, step-by-step walkthroughs of your codebase. Tours open in the browser with a sidebar showing the narrative and highlighted code sections.

/diffity-tour

Your agent researches the codebase, then builds a tour with highlighted code regions and rich markdown explanations:

/diffity-tour how does authentication work?
/diffity-tour explain the request lifecycle
/diffity-tour how are comments stored and retrieved?
/diffity-tour closures
/diffity-tour async/await patterns
/diffity-tour walk me through this branch before I merge
/diffity-tour https://github.com/owner/repo/pull/123

Works for features ("how does auth work?"), concepts ("closures", "generics"), and pre-merge reviews. For concepts, the agent finds real examples in your codebase and teaches the concept progressively from simple to complex. For reviews, it walks the user-facing flows end-to-end and ends with a "things to flag in the PR conversation" list — you can pass a branch, a ref range, or a GitHub PR URL.

Each tour has an intro (step 0) with an architectural overview, followed by numbered steps that highlight specific code regions and explain them in detail. The agent follows the actual execution path, not file order — foundations (schemas, config, helpers) are introduced just-in-time when the flow first touches them.

Tour steps can include sub-highlights — clickable focus links in the narrative that narrow the highlight to a specific sub-range within the step. Useful for walking through large functions section by section.

Learn any topic

Start a project-driven learning journey for any programming language, tool, or framework. Your agent becomes a tutor — it builds teaching projects that open as guided tours in the browser, gives you challenges to complete, reviews your code with inline feedback, and adapts to your pace.

/diffity-learn

Kick off a learning journey. Run it in an empty directory where you want to keep your learning files — the agent creates a learn-<topic>/ folder with lessons, projects, and progress tracking.

mkdir ~/learning && cd ~/learning

Then start learning:

/diffity-learn Rust
/diffity-learn Go
/diffity-learn Docker
/diffity-learn SQL
/diffity-learn TypeScript
/diffity-learn Kubernetes

Each lesson follows a loop: your agent builds a small project and opens it as a Diffity tour explaining the concepts, then gives you a challenge to build yourself. When you're done, it reviews your code with inline Diffity comments and decides what to teach next.

Progress is saved to learn.json — come back anytime and pick up where you left off. The agent tracks what you've mastered, what you're struggling with, and adjusts the curriculum accordingly.

GitHub PRs

Pass a GitHub PR URL to view and review pull requests locally:

diffity https://github.com/owner/repo/pull/123

This checks out the PR and opens the diff against the commit the pull request is based on, so the file and line counts match what the forge shows rather than drifting with your local base branch. A merged pull request works too: its branch is usually deleted, so the head is fetched from refs/pull/<n>/head. Requires the gh CLI installed and authenticated (gh auth login), and the current repo must match the PR's repository.

Above the diff you get the pull request's description and every review already on it, so you are not re-deriving intent from the code or repeating a point someone else has made.

When the checkout cannot name its pull request — a detached worktree at the PR head, which is what the review inbox prepares — pass the number and the commit the pull request is based on:

diffity --pr 123 <base-sha>

The diff is pinned to that base, and the description, the reviews and the submit dialog appear as they do for a URL.

Submitting a review

The forge dialog is a composer, not a push button:

  • a checkbox per finding, so you send the ones you agree with. Everything open starts selected; deselecting is remembered, so a finding your agent writes while the dialog is open cannot slip into a set you have already curated
  • replies on a finding are folded into the one comment the forge will hold
  • general comments seed the summary, which you can edit
  • Comment, Approve or Request changes — and an approval needs nothing attached, since a verdict stands on its own. Approve and Request changes are disabled on your own pull request, which the forge refuses anyway
  • everything goes as one review: one notification for the author, a summary that has somewhere to live, and no half-posted review if something fails

A comment on a line the pull request does not touch is caught before anything is sent, because the whole review is a single request and one unpostable line would reject all of it. Findings already sent are marked already on the pull request and left unselected.

Existing inline comments can be pulled into the viewer from the same dialog.

The skills work with PR URLs too:

/diffity-diff https://github.com/owner/repo/pull/123
/diffity-review https://github.com/owner/repo/pull/123
/diffity-tour https://github.com/owner/repo/pull/123

Passing a PR URL to /diffity-tour locks it to review mode — the agent reads the PR's description, commits, and diff to build a guided walkthrough that you can use before approving or merging.

Reading a diff in the order it makes sense

A diff arrives alphabetically, which is rarely the order it should be read in. When a walkthrough exists for the change, the file list is reordered to follow it: the file that explains the rest first, the mechanical ones last, everything the walkthrough does not mention below a divider. Each file carries the walkthrough's one-line note on why it is read at that point, a stepper walks the stops, and A-Z in the sidebar header returns to the alphabetical tree.

A walkthrough is recorded by an agent (/diffity-review does it as its last step, and /diffity-tour builds one on request), so this costs you nothing to use.

What gets your attention

Two mechanisms, with the decider deliberately different for each.

Highlighted — the lines a walkthrough points at are tinted. An agent can only ever add attention this way, never take it away.

Dimmed — decided by rules, never by a model, because dimming asserts that something needs less attention. A hunk recedes when every line it touches is an import, when its added and removed lines are the same lines with different whitespace, or when the file is generated. It comes back on hover, stays selectable and commentable, and carries the reason, so you can always find out why rather than having to trust it.

Files where indentation is syntax — .py, .yml, .yaml, .md, Makefile and friends — are never whitespace-dimmed, because a reindent there can change behaviour.

Whitespace hiding is on by default and remembered: it is the formatter's business, not yours. Because a filtered diff shows fewer lines than the forge does, the header says so and names the amount — whitespace hidden (2 files, 18 lines suppressed).

Commenting

Click a line to comment on it. Shift-click a second line to extend the comment across the span, the way the forge does it — within one file and one side, since a range spanning both sides of a diff is not something that can be commented on. Dragging down the gutter also selects a range.

While a review is running

An agent announces a review before it starts writing and again when it finishes. While one is open the page carries a banner with the count of findings so far, and submitting is blocked — the difference between "nothing found" and "not finished looking" is the difference between approving a change and approving it too early.

A finding survives the commits you make in response to it: when HEAD moves, open findings and the walkthrough follow into the new session, and a finding whose code merely moved is re-anchored to it. A finding whose code was edited keeps its old position rather than being guessed onto something it was not written about.

The agent CLI

Skills drive these; they are listed because they are the whole interface an agent needs.

diffity agent standards [--json]        # the project's severities and standards document
diffity agent diff                      # the unified diff for this session
diffity agent list [--status open|resolved|dismissed] [--json]
diffity agent review-start [--note <text>]
diffity agent review-done
diffity agent comment --file <path> --line <n> [--end-line <n>] [--side new|old] --body <text>
diffity agent general-comment --body <text>
diffity agent reply <id> --body <text>
diffity agent resolve <id> [--summary <text>]
diffity agent dismiss <id> [--reason <text>]
diffity agent tour-start --topic <text> [--body <text>] [--json]
diffity agent tour-step --tour <id> --file <path> --line <n> [--end-line <n>] --body <text> [--annotation <text>]
diffity agent tour-done --tour <id>
diffity agent tour-delete <id>          # correct a walkthrough instead of adding another
diffity agent tour-delete --all         # or clear the session's finished ones

Every --body also takes --body-file <path>, and --body-file - reads stdin — a quoted heredoc needs no escaping, and the text lands exactly as typed.

Every command follows the running server's own session. diffity agent --session <id> <command> (canonically between agent and the command) addresses another session by id or 8-char prefix.

A comment's line range is trimmed to the file's length, and you are told when that happens: a range running past the end would otherwise be counted and highlighted with nothing to show.

The review inbox

diffity inbox watches the pull requests awaiting your review and prepares each one ahead of time, so the review is ready the moment you look. It polls GitHub (gh search prs --review-requested=@me), and for each pull request worth your attention it cuts a worktree at the PR head, runs a diffity session over the diff, has an agent prepare a review with a walkthrough, and saves the result as a bundle. New commits redo a stale review; a merged, closed, or no-longer-requested PR is retired. The daemon prepares up to maxPrepared of them from the queue on its own — the rest wait, smallest first — and a prepared review leaves the inbox once you have posted it (GitHub withdraws the request) or dismissed it from the page — a dismissal holds until the pull request gets new commits, and dismissed pull requests stay listed at the bottom so one can be brought back. A queued, skipped, failed or dismissed pull request has a ↑ button: prepare this one next, ahead of the queue and past the limit, with the title patterns and the filter set aside; the daemon picks it up at once. Each prepared review shows its findings by severity ("1 P1 · 2 P2"), and the page can notify you when one is ready — click "Turn on notifications" once to allow it, and ⟳ in the header polls GitHub now instead of at the next interval; localhost counts as a secure context, so this works from the pinned tab with nothing else set up.

The daemon never posts your prepared reviews to GitHub — they are local drafts you open and submit yourself — and it runs the review agent with your GitHub credentials stripped from its environment. Its own git calls run with hooks disabled, so a checkout's hook scripts — the author's code — never run with your credentials. That said, the agent executes the pull request's own repository code (see the warning below), so treat the "never posts" behaviour as the daemon's design, not a sandbox.

diffity inbox              # run the watcher and a small status server
diffity inbox --once       # run a single poll-and-prepare pass, then exit
diffity inbox status       # print the current inbox without starting the daemon
diffity inbox status --json
diffity inbox runs         # every agent run of the last 7 days, and what it spent
diffity inbox runs --since 30 --json

On first run it writes ~/.diffity/inbox/config.json:

Key Meaning
pollMinutes How often GitHub is polled (default 5). Editable from the page's Settings panel, like every key below marked so.
port The status server's port (default 5390).
reposDir Where your base clones live, one directory per repository name.
worktreesDir Where each pull request gets its worktree.
skipTitles Regular expressions matched against the title, one per line in the page — \(payments\), Release$. A match skips the pull request before any agent runs, and ↑ overrides it; the match is re-decided at every poll, so a retitle brings the pull request back to the queue. For what a regex cannot express, filter. Editable from the page's Settings panel.
filter Your own words on what does and doesn't need your attention, handed to the agent — it answers with a skip instead of reviewing when a PR matches (e.g. "Skip payments-focused PRs"). The agent has to load the skill and read the diff to decide, so every skip costs an agent run; skipTitles above costs nothing. Editable from the page's Settings panel.
alertWhen Your own words on what needs you now. The agent judges each prepared review against them and flags the ones that match; the page notifies for those only — empty means every prepared review. Editable from the page's Settings panel.
alertPaths Globs against the pull request's changed paths, one per line in the page — packages/shared/src/model/**, **/dbref/**. A changed file matching one marks the review as needing you now, whatever the agent made of alertWhen. Editable from the page's Settings panel.
agent.model --model for the review agent; null leaves its own default. Editable from the page.
agent.effort --effort: low, medium, high, xhigh or max; null leaves its own default. Editable from the page.
agent.mcpAllow The exact MCP tool names the agent may call, e.g. mcp__claude_ai_Atlassian__getJiraIssue. Empty (the default) means no MCP servers at all. Editable from the page.
agent.extraArgs Appended verbatim to the built command, for a flag this table does not cover.
agent.maxBudgetUsd --max-budget-usd for one run; null leaves it uncapped. A run that hits it is a failed attempt with that reason. Editable from the page.
validate.model The model that checks the drafted P1 and P2 findings before you see them; null (the default) runs no second pass. Editable from the page.
validate.timeoutMinutes How long the check may take before it is stopped and the draft goes out unchecked (default 15). Editable from the page.
validate.maxBudgetUsd --max-budget-usd for the checking run; null leaves it uncapped. Editable from the page.
waitForCi Whether a pull request waits for green CI before an agent is spent on it (default false). Editable from the page.
prepareTimeoutMinutes How long one preparation may take before it's abandoned. Editable from the page.
maxPrepared How many the daemon prepares from the queue on its own (default 5); a bumped pull request is prepared past it. Each preparation is an agent run; the rest of the queue waits until a prepared review is posted or dismissed. Editable from the page, as "Auto-prepare from queue".
live Whether opening a prepared review also parks a live agent on it (default true). Questions asked in the page — the Ask button on a finding — each run the agent once to answer; the agent may answer and amend findings, never edit code, and never reaches GitHub. Editable from the page.
liveTimeoutMinutes How long one answer may take before the agent is stopped (default 10). Editable from the page.

The command itself is not configurable: the daemon builds claude -p --output-format json with the flags the review depends on. It runs with --setting-sources "", so the agent gets none of your Claude settings — no MCP servers, no memory, no CLAUDE.md, none of your installed skills. Listing tools in agent.mcpAllow brings your MCP servers back and adds a PreToolUse hook (diffity inbox mcp-gate) that refuses every MCP call but those, by name; the prompt then tells the agent it may read the ticket or document the pull request refers to, and nothing else outside the checkout. A deny list keeps it off gh pr review, gh pr comment, gh pr merge and gh api, and off pnpm, npm, npx, yarn, bun and make — CI has already built and tested this head. The skill shipped with this build goes into the agent's system prompt — diffity-review for a preparation, diffity-live for an answer — so neither depends on what you have installed.

What CI made of the head goes into the prompt: every check that reported, with its verdict, a count of the ones a workflow skipped, and the instruction not to install, build, typecheck, lint or test — CI has done that, and the checkout is the author's code — so the agent reasons from the source and says in its summary when a check failed or is still running. Each card carries the same verdict as a dot before its title: green for passing, red for failing, amber while checks are still going, and no dot at all when nothing has decided — no checks, or only skipped and cancelled ones. With waitForCi on, a pull request whose checks are still running waits in the queue ("waiting: CI running (3 checks)") and one whose checks failed is set aside ("CI failed: check-job, pr-mgmt-job") — both re-decided at every poll, so passing checks bring one back to the queue on their own, unlike a filter skip. ↑ prepares it whatever CI says, and a review already prepared for an older head stays openable while its refresh waits.

Name a model in validate.model and a second pass checks the draft before you see it. It runs only when the draft holds a P1 or P2 — the findings that would hold up a merge — and only on those: for each one it reads the lines the finding points at and what they depend on, then leaves it, rewrites it (agent amend) if the text or the severity is off, or dismisses it (agent dismiss) if it does not hold; it adds a finding only where checking one of these revealed another, and amends the general summary when its verdict or counts no longer stand. It gets no review skill and is told not to re-review the diff, and like the drafting pass it runs without the forge's credentials and behind the same deny list. A pass that times out, ends without its VALIDATED line, hits its budget or runs into your session limit does not lose you the review: the draft is exported as it stands and its card says "1 P1 · unchecked", so you know the findings are the first pass's alone. When a checking model is set, questions asked in the page are answered with it too — a question is about a finding, which is that model's job.

Every agent run is logged: the pull request and head it was for, which pass it was (prepare for a preparation, validate for a check of its findings, answer for a question asked in the page), the models it actually used, how long it took, its turns, its cost and its tokens, and how it ended (prepared, skipped, validated, answered, failed, timeout, rate-limited). diffity inbox runs prints that log with totals — the record of what the inbox costs you. A prepared review's card carries its own share of it, "· 8 min · $1.20", with each run behind that head listed on hover, and the page's footer keeps a running total for today and for the last seven days.

A run that ends on your Claude session limit is not the pull request's fault, so it is waited out rather than retried: the row goes back in the queue as "waiting: Claude session limit until 14:00", no failed attempt is counted against it, and no further preparation starts until the limit lifts. The reset time is read out of the agent's own message ("resets 2pm (Europe/Stockholm)"), or set half an hour ahead when the message names none. Polling and reconciling carry on meanwhile, so the page stays current and says how long the pause has left; the pause is kept with the inbox, so restarting the daemon does not spend another run rediscovering the limit.

If your config still has a prepare key from an earlier version, delete it — the daemon refuses to start with it, and the built command takes its place. A flag you were passing belongs in agent.extraArgs.

⚠️ The agent runs inside a checkout the pull request's author controls, so it can execute their repository code. The daemon runs it without the forge's credentials in its environment, with the deny list above, and — unless you list MCP tools in agent.mcpAllow, which loads your settings for the servers behind the gate — with none of your Claude settings. That is defence in depth, not a sandbox.

Multiple projects

Diffity supports running multiple projects simultaneously. Each gets its own port automatically:

# Terminal 1 — starts on :5391
cd ~/projects/app && diffity

# Terminal 2 — starts on :5392
cd ~/projects/api && diffity

If you run diffity in a repo that already has a running instance, it opens the existing one instead of starting a new server. Use --new to kill the existing instance and start fresh.

diffity list               # show all running instances
diffity list --json        # machine-readable output

Options

--base <ref>       Base ref to compare from (e.g. main, HEAD~3, v1.0.0)
--compare <ref>    Ref to compare against base (default: working tree)
--port <port>      Custom port (default: auto-assigned from 5391)
--no-open          Don't open browser
--dark             Dark mode
--unified          Unified view (default: split)
--quiet            Minimal terminal output
--new              Stop existing instance and start fresh
--repo <path>      Repository to work on, when the current directory is not one

--repo is for the common case where a project directory holds several worktrees as subdirectories rather than being a repository itself. It must come before a positional argument.

Environment variables

Variable Description
DIFFITY_HOST Hostname used in the printed URL (default: localhost).
DIFFITY_BIND Interface the server listens on (default: 127.0.0.1).
DIFFITY_DATA_DIR Where review notes are kept (default: ~/.diffity/<repo-hash>).
DIFFITY_SYNC_DEV_SKILLS Set to 1 to have a build install the diffity-dev-* skills into ~/.claude/skills.

Useful when running diffity inside a VM or container and opening it from another machine:

DIFFITY_BIND=0.0.0.0 DIFFITY_HOST=diffity.local diffity

The server has no authentication: anything that can reach it can read the diff, the repository's files and the review comments. Only widen DIFFITY_BIND on a network you trust.

Where review notes live

Review threads, walkthroughs and sessions are kept in a SQLite database. By default that is ~/.diffity/<repo-hash>/reviews.db, one per repository.

A project can keep its own instead, which is what you want when several worktrees of the same repository each need their own notes, or when the notes should travel with the project rather than the machine. Commit a .diffity.json at the repository root:

{ "dataDir": "../.diffity" }

Relative paths resolve against the repository root, absolute paths are used as given, and DIFFITY_DATA_DIR overrides both. A directory chosen this way is used as-is — no hashed subdirectory, since there is nothing to disambiguate.

Point it outside the working tree, or add it to .gitignore. Otherwise the notes show up as untracked files in the very diff you are reviewing; diffity warns on startup when that happens. The database quotes the code under review, so it is created readable only by you.

Review standards

An agent reviewing a diff can be told what this project reviews against, so the standards live with the code rather than in one person's agent configuration:

{
  "review": {
    "severities": ["P1", "P2", "P3"],
    "standards": ".claude/skills/code-review/SKILL.md"
  }
}

severities are the labels findings are prefixed with, most severe first, defaulting to P1/P2/P3. standards is a repository-relative path to a document the agent reads before reviewing. diffity agent standards prints both, and the review skill reads it first.

License

MIT — © Kamran Ahmed (the upstream diffity), with this fork's changes by Natural Cycles.

About

GitHub-style diff viewer for reviewing code changes. Works with Claude Code, Cursor and other AI tools.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages