diffwalk turns AI-generated Git changes into ordered browser walkthroughs, with every explanation attached to its exact diff.
The CLI supports the maintained Node.js 22 and 24 LTS releases. Development commands below also require Bun 1.3 or newer and pnpm.
Install Diffwalk globally from npm:
npm install --global diffwalkOr run it without a global installation:
npx diffwalk inspectInstall the dependencies, build the executable, and run the test suite:
pnpm install
pnpm build
pnpm testPull requests targeting main run pnpm check. Passing same-repository branches are
rebased automatically and deleted after merge.
Inside the Git working tree whose changes you want to explain:
diffwalk inspectThis captures staged, unstaged, renamed, deleted, and untracked UTF-8 files relative to
HEAD and creates a current walk such as
.diffwalk/20260831T063842Z-a7c9e4f2/. The name combines the capture time in ISO 8601
basic format with the first eight characters of the content-derived captureId.
The walk contains two authoring files:
capture.json— machine-owned capture data (source, full file snapshots, change blocks, and acaptureId). Never edit it by hand.explanations.yaml— a small authoring skeleton on first use. This is the only file you edit.
To capture only part of the working tree, add --staged to read the index instead of the
working tree, or list the paths after -- to limit the capture to them:
diffwalk inspect --staged # only changes staged in the index
diffwalk inspect -- src/a.ts src/b.ts # only the named paths
diffwalk inspect --staged -- src/a.ts # bothPath limiting applies only to working-tree captures; committed revisions are always captured whole. The capture keeps the same content-derived identity and validation guarantees as a full working-tree capture.
.diffwalk/current selects the default walk for later commands. An unchanged re-inspect
reuses it; changed contents create a new walk without deleting the earlier pair. Ignore
the entire local workspace when it should not enter version control:
.diffwalk/To explain committed changes without checking out either revision, pass one commit or an explicit range:
diffwalk inspect <commit> # commit relative to its first parent
diffwalk inspect --from main --to feature # any two committed revisionsThese forms read only committed Git objects, record both the revision labels and resolved commit hashes in the capture source, and ignore staged, unstaged, and untracked files. Single-commit inspection uses first-parent semantics. A root commit cannot be inspected in single-commit form because it has no first parent; use a range whose starting revision is a committed parent when one exists.
Name the change set, then order the sections array. Each section is a title over an
ordered list of steps, and a step carries text, changes, or both, so prose and
diffs interleave in the order you write them:
captureId: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4
title: Keep the greeting concise
summary: |
Optional opening, shown above the review map.
<figure><svg viewBox="0 0 640 180" role="img">...</svg></figure>
metadata:
explainedBy: Claude Code
sections:
- title: Keep the greeting concise
steps:
- text: |
The extra phrase is no longer needed.
changes:
- change-001
- title: Add a farewell
steps:
- text: Why a farewell belongs here at all.
- text: A small module that says goodbye.
changes:
- change-002title is required: it becomes the review heading and the browser tab, which is how two
shared links tell themselves apart. summary is optional. metadata.explainedBy is an
optional, self-reported author shown in every local view and export.
Every change must be shown at least once. Showing one in more than one step is allowed and reported, because re-showing a hunk is how an argument gets built. Validate, then read or share:
diffwalk check
diffwalk view
diffwalk export html
diffwalk publishThe default workflow stays terse; every command also accepts explicit overrides:
diffwalk inspect --base main
diffwalk inspect abc1234
diffwalk inspect --from main --to feature
diffwalk check --input path/to/capture.json --explanations path/to/explanations.yaml
diffwalk view --input path/to/capture.json --explanations path/to/explanations.yaml
diffwalk export html --output review.html
diffwalk export json --output document.json
diffwalk publish --service https://review.exampleCapture data is machine-owned, so read it through focused commands instead of opening
capture.json:
diffwalk changes # concise human summary of every change block
diffwalk changes --json # structured IDs, paths, coordinates, before, after
diffwalk change change-001 # one captured block with its contents
diffwalk file src/a.ts --before # the exact captured old side of a file
diffwalk file src/a.ts --after # the exact captured new sidechanges --json never includes full captured file contents. change rejects unknown
IDs and file rejects unknown paths or an invalid --before/--after selection.
capture.json holds a captureId that identifies the captured code contents, not the
capture timestamp: identical captures pair consistently, and changed contents produce a
different identity. explanations.yaml names the captureId it was authored against.
diffwalk inspect never overwrites an authored explanations.yaml: unchanged contents
reuse the current walk, while changed contents create a new timestamped walk and leave
the earlier authoring pair intact.
diffwalk check reads capture plus explanations and rejects stale captureId
pairing, malformed YAML, unknown change IDs, changes left unexplained, and any change
block that no longer materializes to an exact patch. On success it reports section,
step, change, and file counts, and names any change shown in more than one place.
The explanations file is parsed as strict safe YAML 1.2: custom tags, duplicate keys,
and anchors or aliases are rejected, and YAML 1.1-style coercions (yes, on) stay
plain strings.
diffwalk view materializes the review, starts a temporary loopback-only server, and
opens it in the default browser. It writes no HTML file. The server remains available
until you press Ctrl+C.
diffwalk export htmlwrites diffwalk.html inside the current walk by default. The review is one portable file: it embeds
the document data, the Markdown-rendered explanations, the @pierre/diffs runtime that
parses and renders each exact diff, and all styles. It works offline as a local file
with JavaScript enabled and requests no CDN or external assets.
text and summary are rendered as Markdown, and inline HTML passes through, so a
diagram can sit exactly where the argument needs it. That makes authored text trusted
input: build reviews only from documents you or a trusted agent authored.
Embed every image as an inline <svg> or a data: URI. A remote image URL renders in
the local file but is blocked on the hosted review, so the same document would look
different through a link.
diffwalk publishmaterializes the same document export html renders, uploads it to the review service, and
prints an unlisted link. The service stores only that JSON and renders it with its own
shared renderer, so no HTML file is uploaded and every review reuses one cached copy of
the renderer instead of carrying its own.
Publishing is anonymous and unlisted, not private. The link cannot be guessed, but anyone holding it can read the review without signing in. Treat the link as the secret, and do not publish a document you would not hand to everyone who might receive it.
A hosted report can show attribution: explainedBy from the explanations, publishedBy
from git config user.name when one is configured, and a publishedAt timestamp the
service stamps when it accepts the upload. These names are self-reported attribution, not
verified identity. Publishing adds them to the uploaded document only; it never rewrites
explanations.yaml or capture.json, and a missing value is simply omitted.
Publishing retains the review ID, link, service, and revocation token in the current
walk's published.json (or next to an explicit --input/--explanations pair). The
file stays local and is never uploaded, but it holds the revocation token, so keep it
out of version control.
diffwalk publish --updatereplaces the content behind that same link. The review keeps its ID, URL, and revocation token, so a reader who already has the link sees the revised review instead of a new one.
diffwalk unpublish <review-id> --token <revocation-token>A revocation token removes exactly one review and cannot touch another. Losing both the
token and the local published.json means the review stays published. To keep the old
link while revising, edit explanations.yaml and run diffwalk publish --update; to
publish a revision at a separate link instead, run diffwalk publish again.
The trusted-text boundary from diffwalk export html still applies: authored markup is served
verbatim, so publish only what you or a trusted agent authored. The review origin is kept
powerless on purpose — no cookies, no inline scripts, no outbound connections — but that
contains bad markup rather than sanitizing it.
The service is one Cloudflare Worker with a private R2 bucket and its shared assets:
export CLOUDFLARE_API_TOKEN=... # zone WAF and ruleset edit
export CLOUDFLARE_ZONE_ID=...
./infra/setup.sh # bucket, r2.dev off, WAF, rate limits
pnpm deploywrangler.jsonc owns the Worker, its Static Assets, and its R2 binding. infra/setup.sh
owns the zone-level settings wrangler does not manage, and re-running it is a no-op. Point
the CLI at another deployment with --service or DIFFWALK_SERVICE_URL.
diffwalk export json materializes capture plus explanations and writes diffwalk.json
inside the current walk by default. It is the portable ExplainDocument JSON (format
version 1) for integrations or archiving. An authored metadata.explainedBy is
preserved; a local export never claims a publisher or publication time. View, HTML
export, and publish do not require it; they validate and materialize directly from the
authoring files.
capture.json contains full file contents from your working tree and base commit.
Treat it as potentially sensitive and do not publish or send it without authorization.
The repository includes an Agent Skill that teaches compatible coding agents how to
capture changes, author ordered sections, and validate with Diffwalk without
hand-writing patches. Its source lives at skills/diffwalk/SKILL.md.
Install the skill for your project:
npx skills add minipai/diffwalk --skill diffwalkAdd -g to install globally instead. From a local checkout of this repository:
npx skills add ./skills/diffwalk --skill diffwalkSkill installation is separate from installing the CLI with npm install --global diffwalk.
Start a new agent session after installing the skill so it can be discovered.