JulienAu/hunkproof

Receipts for pull requests written by Claude Code: every hunk stamped with the last test, lint or build the agent ran after writing it, and the hunks it never ran. Reads the transcripts already on your machine. 0 LLM tokens, nothing uploaded. Try: npx hunkproof --pr <n>

See the code

See what people are saying

SourceMessageScoreDate

I measured how often Claude Code commits a file it never ran after the last edit. On my laptop: 68.9% of commits ! (r/ClaudeAI)

[hunkproof explanation](https://reddit.com/link/1x6jjxm/video/3ys75v9pdmvh1/player) I built hunkproof, a small CLI for people who **review pull requests written by Claude Code**. It reads the transcripts Claude Code already keeps in \~/.claude/projects and, for each hunk of a PR, shows the last…

1

Oct 15, 2026

README

hunkproof

git diff shows what the agent changed. hunkproof shows what it ran after each hunk, and the hunks it never ran. Reads the Claude Code transcripts already on your disk. 0 LLM tokens, nothing leaves your machine.

On the laptop that built it, 69% of the agent's commits that followed its own edits (270 of 392, over 380 transcripts) included a file edited after the last run. npx hunkproof stats prints yours (snapshot).

hunkproof HTML report, 8 seconds: the Unrun chip pressed, 6 of 26 hunks remain, then a scroll from CHANGELOG.md +18,3 to README.md +31,3, each with its diff lines, its edit time, a red unrun badge and the line "no run after the last edit"

fixtures/unrun-example: the 6 of 26 hunks with no run after their last edit, the README and CHANGELOG lines written after the last npm test (still image).

Install

npx hunkproof --pr <n>
claude plugin marketplace add JulienAu/hunkproof && claude plugin install hunkproof@hunkproof

Line 1: the CLI, Node 22 or later, from the repository. Line 2: the Claude Code plugin, with the skill, /hunkproof:receipt and the Stop hook. Or paste into Claude Code: "Install github.com/JulienAu/hunkproof and run it on this branch"; docs/install/agent.md is the page it follows.

test

What you get

  • One status per hunk: ran, passed, ran, failed, ran, outcome not parsed, unrun (no run after its last edit) or unattributed (no session edited that file), decided by the last run after the hunk's last edit.
  • Terminal: one row per hunk with the file, the line range, the status and that run's timestamp, runner and outcome.
  • HTML (--open): the diff lines, the sentence the agent wrote with the edit when there is one, the rewrites, the timeline of runs.
  • Markdown (--post): the unrun hunks with links and the run that decided each hunk, sent to the PR with gh after you type y.
  • Written to <repo>/.hunkproof/ (add it to .gitignore) and to a git note on the head commit. npx hunkproof --base main when there is no PR yet.

What an unrun hunk looks like

npx hunkproof --base 559fee6 --head 7fbda28 on the pull request that prepared v0.1, taken before anything else ran: its docs commits edited the README and the CHANGELOG after the last npm test. Output kept in fixtures/unrun-example. Line-matched: the hunk's added lines were found in a session edit; file-level: the file was edited but the lines were not found, so the timing is relative to the file's last edit.

hunkproof · 559fee6...7fbda28 · 20 of 23 line-matched hunks had a run after their last edit (20 passed, 0 failed, 0 outcome not parsed) · 3 file-level (lines not matched to an edit) · 6 unrun · 0 unattributed · 6 sessions · hunkproof used 0 LLM tokens

The 6 unrun rows of the terminal output:

CHANGELOG.md                +18,3  unrun               none after edit at 17:53:47
README.md                   +9     unrun               none after edit at 17:51:34
README.md                   +31,3  unrun               none after edit at 17:53:42
README.md                   +47    unrun (file-level)  none after edit at 17:53:42
README.md                   +61,0  unrun (file-level)  none after edit at 17:53:42
README.md                   +72    unrun (file-level)  none after edit at 17:53:42

The 20 other hunks had npm test run after them at 17:45:10; the 6 unrun ones (3 line-matched, 3 README hunks at file level) are the lines written after it. The next npm test in that session stamps them ran, passed: a receipt says what had run when it was written, so take it before the review, not after.

Receipt: hunkproof on its own PR

npx hunkproof --pr 2 --base 1af692a4 --head 102eae0 on the private repository where v0.1 was built, 2026-10-14, output kept in fixtures/self-pr; test/fixture.test.js holds these numbers and rebuilds the HTML and Markdown from the saved evidence byte for byte.

hunkproof · PR #2 · 64 of 64 line-matched hunks had a run after their last edit (64 passed, 0 failed, 0 outcome not parsed) · 3 file-level (lines not matched to an edit) · 0 unrun · 1 unattributed · 9 sessions · hunkproof used 0 LLM tokens
hunksattributedrun after last editunrununattributedsessionselapsedLLM tokens used by hunkproof
6864640193.5 s0

The 3 file-level hunks are CONTRACT.md edits whose exact lines were not found in an edit; the unattributed hunk is a .gitignore line edited before the branch base. A merged PR is diffed from its merge commit and transcript events after the merge are left out.

Stop hook

  • Installed with the plugin (hooks/hooks.json). When the agent is about to hand back, it prints hunkproof: 2 hunks edited in this session have no run after their last edit, then one file:line per hunk, 10 at most.
  • It reads this session's transcripts only and lists the hunks whose lines this session wrote. Nothing to say (no git repository, no diff, no transcript, every hunk run) prints nothing; HUNKPROOF_DEBUG=1 names the reason on stderr. It writes nothing.
  • HUNKPROOF_STOP=block returns the hook's block decision instead, with the same list and the project's checks (npm test, make check, whichever exist), so the agent runs the unrun hunks before it stops. HUNKPROOF_STOP=off turns it off.
  • HUNKPROOF_BASE=<ref> picks the base (default: origin's HEAD, main or master).
  • Without the plugin, add it to .claude/settings.json: the snippet, the version to pin, the timing (1.4 to 1.7 s on this repository) and how Claude Code shows a block are in docs/stop-hook.md.

Compared with

shows what changedshows what ran after the changeper hunkLLM tokensleaves your machine
hunkproofyesthe last run after each hunk's last edit, with its outcomeyes0no (transcripts); gh fetches the PR metadata, --post sends the comment after you type y
git diffyesnoyes0no
GitHub PR reviewyesCI status on the head commit, not per hunkcomments per line0the PR is already there
Entire CLIyes: transcript, prompts, files touched, tool calls per checkpoint?per commit; per line with entire blameoptional, for summaries written by an agent at commit timeyes: transcripts live in git refs with the repository; anonymous usage stats unless --telemetry=false
git-aiyes: agent, model and prompt per line?per line (git ai blame)none mentionedno: offline, no login
claude-replaythe whole session, with a diff view per editthe commands in session order, not tied to a changenonone mentionedno, but the HTML embeds the full transcript
mindwalkfiles touched, on a 3D mapone session-level signal, "edits after the last verify"no, per file0 to view; the optional evaluation calls your claude or codex CLInothing while viewing; a session summary when you run the evaluation

Filled from each project's README on 2026-10-14: Entire CLI, git-ai, claude-replay, mindwalk. A "?" means the README does not say. The git diff and GitHub rows describe them as used, there is no README to cite. Also nearby: Anthropic's receipts plugin (a personal usage report from the same transcripts) and pr-provenance-stamp (a provenance note in the PR body at creation time).

How it works

  • git diff -U0 base...head gives the hunks: one contiguous block of added or removed lines each, identified by file and first new line.
  • Sessions are found in ~/.claude/projects: transcripts that mention the PR, edited the diff's files in the branch's time window, or ran on its branch or in its directory, subagents included.
  • Each hunk's added lines are looked up in those sessions' edits (Edit, Write, MultiEdit, NotebookEdit, shell commands with a recorded diff); the chain explaining most lines wins, and the sentence written in the same turn is kept as the intent.
  • Shell commands are classified as runs (test runners, linters, type checkers, builds, direct script calls) and their outcome is parsed from the output: pass, fail, or unparsed.
  • A hunk's status is decided by the last run after its last edit whose outcome was parsed, targeted runs first; a run in another checkout never decides. No run after the last edit means unrun.
  • The result is one evidence document, rendered three ways (terminal, HTML, Markdown) with no network call; --post sends the redacted Markdown to the PR only after you type y.

More

  • Definitions: hunk, attribution, run, status, targeted, cost, as printed in every report footer.
  • Privacy: what is read, written and sent, and the redaction rules.
  • Limitations: what a run after an edit says and does not say, shell edits, the 30-day transcript window, worktrees, intent.
  • Two-minute film with sound: why reviewing agent code is slow, what the transcript already knows, what the stamps look like.
  • CHANGELOG and CONTRIBUTING, which has the ground rules.
  • Roadmap: parsers for Codex and other agents' session logs, behind the same event schema; a GitHub Action that replays a committed evidence document (or the git note) into a check run.
  • License: MIT.
ai-agents
claude-code
claude-code-plugin
cli
code-review
developer-tools
git
provenance
pull-request

JulienAu/hunkproof

Receipts for pull requests written by Claude Code: every hunk stamped with the last test, lint or build the agent ran after writing it, and the hunks it never ran. Reads the transcripts already on your machine. 0 LLM tokens, nothing uploaded. Try: npx hunkproof --pr <n>

See the code

See what people are saying

SourceMessageScoreDate

I measured how often Claude Code commits a file it never ran after the last edit. On my laptop: 68.9% of commits ! (r/ClaudeAI)

[hunkproof explanation](https://reddit.com/link/1x6jjxm/video/3ys75v9pdmvh1/player) I built hunkproof, a small CLI for people who **review pull requests written by Claude Code**. It reads the transcripts Claude Code already keeps in \~/.claude/projects and, for each hunk of a PR, shows the last…

1

Oct 15, 2026

README

hunkproof

git diff shows what the agent changed. hunkproof shows what it ran after each hunk, and the hunks it never ran. Reads the Claude Code transcripts already on your disk. 0 LLM tokens, nothing leaves your machine.

On the laptop that built it, 69% of the agent's commits that followed its own edits (270 of 392, over 380 transcripts) included a file edited after the last run. npx hunkproof stats prints yours (snapshot).

hunkproof HTML report, 8 seconds: the Unrun chip pressed, 6 of 26 hunks remain, then a scroll from CHANGELOG.md +18,3 to README.md +31,3, each with its diff lines, its edit time, a red unrun badge and the line "no run after the last edit"

fixtures/unrun-example: the 6 of 26 hunks with no run after their last edit, the README and CHANGELOG lines written after the last npm test (still image).

Install

npx hunkproof --pr <n>
claude plugin marketplace add JulienAu/hunkproof && claude plugin install hunkproof@hunkproof

Line 1: the CLI, Node 22 or later, from the repository. Line 2: the Claude Code plugin, with the skill, /hunkproof:receipt and the Stop hook. Or paste into Claude Code: "Install github.com/JulienAu/hunkproof and run it on this branch"; docs/install/agent.md is the page it follows.

test

What you get

  • One status per hunk: ran, passed, ran, failed, ran, outcome not parsed, unrun (no run after its last edit) or unattributed (no session edited that file), decided by the last run after the hunk's last edit.
  • Terminal: one row per hunk with the file, the line range, the status and that run's timestamp, runner and outcome.
  • HTML (--open): the diff lines, the sentence the agent wrote with the edit when there is one, the rewrites, the timeline of runs.
  • Markdown (--post): the unrun hunks with links and the run that decided each hunk, sent to the PR with gh after you type y.
  • Written to <repo>/.hunkproof/ (add it to .gitignore) and to a git note on the head commit. npx hunkproof --base main when there is no PR yet.

What an unrun hunk looks like

npx hunkproof --base 559fee6 --head 7fbda28 on the pull request that prepared v0.1, taken before anything else ran: its docs commits edited the README and the CHANGELOG after the last npm test. Output kept in fixtures/unrun-example. Line-matched: the hunk's added lines were found in a session edit; file-level: the file was edited but the lines were not found, so the timing is relative to the file's last edit.

hunkproof · 559fee6...7fbda28 · 20 of 23 line-matched hunks had a run after their last edit (20 passed, 0 failed, 0 outcome not parsed) · 3 file-level (lines not matched to an edit) · 6 unrun · 0 unattributed · 6 sessions · hunkproof used 0 LLM tokens

The 6 unrun rows of the terminal output:

CHANGELOG.md                +18,3  unrun               none after edit at 17:53:47
README.md                   +9     unrun               none after edit at 17:51:34
README.md                   +31,3  unrun               none after edit at 17:53:42
README.md                   +47    unrun (file-level)  none after edit at 17:53:42
README.md                   +61,0  unrun (file-level)  none after edit at 17:53:42
README.md                   +72    unrun (file-level)  none after edit at 17:53:42

The 20 other hunks had npm test run after them at 17:45:10; the 6 unrun ones (3 line-matched, 3 README hunks at file level) are the lines written after it. The next npm test in that session stamps them ran, passed: a receipt says what had run when it was written, so take it before the review, not after.

Receipt: hunkproof on its own PR

npx hunkproof --pr 2 --base 1af692a4 --head 102eae0 on the private repository where v0.1 was built, 2026-10-14, output kept in fixtures/self-pr; test/fixture.test.js holds these numbers and rebuilds the HTML and Markdown from the saved evidence byte for byte.

hunkproof · PR #2 · 64 of 64 line-matched hunks had a run after their last edit (64 passed, 0 failed, 0 outcome not parsed) · 3 file-level (lines not matched to an edit) · 0 unrun · 1 unattributed · 9 sessions · hunkproof used 0 LLM tokens
hunksattributedrun after last editunrununattributedsessionselapsedLLM tokens used by hunkproof
6864640193.5 s0

The 3 file-level hunks are CONTRACT.md edits whose exact lines were not found in an edit; the unattributed hunk is a .gitignore line edited before the branch base. A merged PR is diffed from its merge commit and transcript events after the merge are left out.

Stop hook

  • Installed with the plugin (hooks/hooks.json). When the agent is about to hand back, it prints hunkproof: 2 hunks edited in this session have no run after their last edit, then one file:line per hunk, 10 at most.
  • It reads this session's transcripts only and lists the hunks whose lines this session wrote. Nothing to say (no git repository, no diff, no transcript, every hunk run) prints nothing; HUNKPROOF_DEBUG=1 names the reason on stderr. It writes nothing.
  • HUNKPROOF_STOP=block returns the hook's block decision instead, with the same list and the project's checks (npm test, make check, whichever exist), so the agent runs the unrun hunks before it stops. HUNKPROOF_STOP=off turns it off.
  • HUNKPROOF_BASE=<ref> picks the base (default: origin's HEAD, main or master).
  • Without the plugin, add it to .claude/settings.json: the snippet, the version to pin, the timing (1.4 to 1.7 s on this repository) and how Claude Code shows a block are in docs/stop-hook.md.

Compared with

shows what changedshows what ran after the changeper hunkLLM tokensleaves your machine
hunkproofyesthe last run after each hunk's last edit, with its outcomeyes0no (transcripts); gh fetches the PR metadata, --post sends the comment after you type y
git diffyesnoyes0no
GitHub PR reviewyesCI status on the head commit, not per hunkcomments per line0the PR is already there
Entire CLIyes: transcript, prompts, files touched, tool calls per checkpoint?per commit; per line with entire blameoptional, for summaries written by an agent at commit timeyes: transcripts live in git refs with the repository; anonymous usage stats unless --telemetry=false
git-aiyes: agent, model and prompt per line?per line (git ai blame)none mentionedno: offline, no login
claude-replaythe whole session, with a diff view per editthe commands in session order, not tied to a changenonone mentionedno, but the HTML embeds the full transcript
mindwalkfiles touched, on a 3D mapone session-level signal, "edits after the last verify"no, per file0 to view; the optional evaluation calls your claude or codex CLInothing while viewing; a session summary when you run the evaluation

Filled from each project's README on 2026-10-14: Entire CLI, git-ai, claude-replay, mindwalk. A "?" means the README does not say. The git diff and GitHub rows describe them as used, there is no README to cite. Also nearby: Anthropic's receipts plugin (a personal usage report from the same transcripts) and pr-provenance-stamp (a provenance note in the PR body at creation time).

How it works

  • git diff -U0 base...head gives the hunks: one contiguous block of added or removed lines each, identified by file and first new line.
  • Sessions are found in ~/.claude/projects: transcripts that mention the PR, edited the diff's files in the branch's time window, or ran on its branch or in its directory, subagents included.
  • Each hunk's added lines are looked up in those sessions' edits (Edit, Write, MultiEdit, NotebookEdit, shell commands with a recorded diff); the chain explaining most lines wins, and the sentence written in the same turn is kept as the intent.
  • Shell commands are classified as runs (test runners, linters, type checkers, builds, direct script calls) and their outcome is parsed from the output: pass, fail, or unparsed.
  • A hunk's status is decided by the last run after its last edit whose outcome was parsed, targeted runs first; a run in another checkout never decides. No run after the last edit means unrun.
  • The result is one evidence document, rendered three ways (terminal, HTML, Markdown) with no network call; --post sends the redacted Markdown to the PR only after you type y.

More

  • Definitions: hunk, attribution, run, status, targeted, cost, as printed in every report footer.
  • Privacy: what is read, written and sent, and the redaction rules.
  • Limitations: what a run after an edit says and does not say, shell edits, the 30-day transcript window, worktrees, intent.
  • Two-minute film with sound: why reviewing agent code is slow, what the transcript already knows, what the stamps look like.
  • CHANGELOG and CONTRIBUTING, which has the ground rules.
  • Roadmap: parsers for Codex and other agents' session logs, behind the same event schema; a GitHub Action that replays a committed evidence document (or the git note) into a check run.
  • License: MIT.
ai-agents
claude-code
claude-code-plugin
cli
code-review
developer-tools
git
provenance
pull-request

Project health

Latest release

0.2.0 · Oct 15, 2026

Releases this year

2