Skip to content

Repository files navigation

diffwalk

diffwalk turns AI-generated Git changes into ordered browser walkthroughs, with every explanation attached to its exact diff.

The CLI requires Node.js 20 or newer. Development commands below also require Bun 1.3 or newer and pnpm.

Installation

Install Diffwalk globally from npm:

npm install --global diffwalk

Or run it without a global installation:

npx diffwalk inspect

Development

Install the dependencies, build the executable, and run the test suite:

pnpm install
pnpm build
pnpm test

Quick start

Inside the Git working tree whose changes you want to explain:

diffwalk inspect

This 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 a captureId). Never edit it by hand.
  • explanations.yaml — a small authoring skeleton on first use. This is the only file you edit.

.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 revisions

These 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>
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-002

title is required: it becomes the review heading and the browser tab, which is how two shared links tell themselves apart. summary is optional.

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 publish

The 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.example

Inspecting what was captured

Capture 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 side

changes --json never includes full captured file contents. change rejects unknown IDs and file rejects unknown paths or an invalid --before/--after selection.

How the authoring files pair

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.

Validation

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.

Local preview

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.

HTML reviews

diffwalk export html

writes 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.

Hosted reviews

diffwalk publish

materializes 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.

Publishing prints a revocation token once. Keep it: it is the only way to take that review down.

diffwalk unpublish <review-id> --token <revocation-token>

A revocation token removes exactly one review and cannot touch another. Losing it means the review stays published.

Reviews are immutable. Publishing a revision creates a separate review at a separate link, and the earlier link keeps serving the earlier review until it is revoked.

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.

Running the service

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 deploy

wrangler.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.

JSON export

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. View, HTML export, and publish do not require it; they validate and materialize directly from the authoring files.

Captured data sensitivity

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.

Agent skill

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 .agents/skills/diffwalk.

Link it into the shared user-level Agent Skills directory to make it available from other repositories:

mkdir -p "$HOME/.agents/skills"
ln -s "$(pwd)/.agents/skills/diffwalk" "$HOME/.agents/skills/diffwalk"

Agents that use another skill directory can point that directory at the same SKILL.md. Start a new agent session after installing the skill so it can be discovered.

About

Turn AI-generated code changes into ordered browser walkthroughs with explanations attached to exact diffs.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages