✓ Prove It

Lesson 1 · Done means proven

You ask for a change. The agent edits three files and says "Done! The late fee is implemented and handles all edge cases." It reads well. Then you run the tests, or you open the page, and it's almost right: the fee is off for invoices that are one day late, and the README line you asked for isn't there.

That gap is the most common complaint about AI coding tools. In the Stack Overflow 2025 Developer Survey, 66% of developers cited "AI solutions that are almost right, but not quite" as a frustration, more than any other. And the habit that would catch it is rare: in Sonar's 2026 State of Code survey, 96% of developers said they don't fully trust AI-generated code, yet only 48% said they always verify it before committing.

So most of us know we should check, and about half of us do it every time. This lesson makes the agent do the first round of checking for you, every time, and show its work.

The idea: "done" is a claim that needs evidence

When a colleague says "it's done", you trust it because you know what they did to get there. With an agent, you see a confident summary and a diff. The summary is generated text. It can say "all tests pass" whether or not a test ran.

So we change the definition of done. Done means:

  1. the checks ran after the last edit, and passed, and
  2. every part of the request points to evidence, or says plainly that it isn't done or wasn't checked.

Two pieces of Prove It work together on this. A hook enforces what code can check: the agent cannot finish after editing code until a check has passed since its last edit, and its reply ends with a Proof block. A skill shapes what goes in that block: one line of evidence per requirement, so the reader can verify it.

The Stop hook

Claude Code runs a Stop hook whenever the agent is about to finish its turn. The hook can block that and send the agent back with a reason. Prove It's stop-needs-proof.mjs uses a small event log that another hook, track.mjs, keeps for each session:

When the agent tries to stop, the hook looks at what happened since the last time it was allowed to stop:

Prove It: you changed src/fees.js and no check has run since the last edit.
Run the project's checks (found: npm test) and show the result, then end with
a "## Proof" block. If you cannot run checks, say exactly what you did not
verify under "Not verified:".
Prove It: the checks passed (`npm test`), but your reply has no Proof block.
End it with a "## Proof" section (plain markdown, not inside a code block) in
the prove-it skill's format: one line per requirement -> its evidence (command,
exit code, the output line that proves it), a "Checks:" line, and a
"Not verified:" line for anything you did not check.

The hook counts a Proof block when the reply has a markdown heading whose text is "Proof" (## Proof, ### proof:, any level, any case), or **Proof** alone on its line, outside code blocks. "Here is proof that it works" doesn't count, and neither does a ## Proof the agent quotes inside a code block. If you don't want this rule, set "requireProof": false in .prove-it.json; the check rules stay on.

Two rules keep it from becoming a nuisance. Each reason blocks at most once per stop: the agent can be sent back to run the checks and then once more for the Proof block, but never twice for the same thing, so it can never loop. And there's an honest way out: a real Not verified: section in the last message. Sometimes the agent genuinely can't run the checks, because the database isn't there or you told it not to. Then it should say what is unverified, and the hook accepts that instead of a passing check. Not verified: none doesn't count. Once the checks have passed, there's nothing to escape from: the Not verified: line goes inside the Proof block.

A detail that matters more than it looks: npm test | tail -20 reports the exit code of tail, not of the tests, and agents pipe output like that all the time. So when a command has a pipe, ; or ||, track also reads the output for the usual failure lines (not ok, # fail 1, 2 failed, FAIL) before it counts the run as passing.

The prove-it skill

The hook makes sure something ran and that the reply has a Proof block. The prove-it skill makes that block useful. Claude loads a skill when it judges it relevant, so it may not load on its own; the hook's message names it, and you can always ask for it with /prove-it:prove-it. It asks the agent to:

  1. list each requirement from your request, including the small ones;
  2. find and run the project's checks after its last edit;
  3. map each requirement to evidence: the command, the exit code, and the line of output that proves it;
  4. put everything it didn't do or couldn't check under "Not verified:", with the reason.

The output is a fixed block at the end of the reply:

## Proof
- Late fee is 2% per started month -> `node --test test/fees.test.js` exit 0: "ok 2 - one day late is one month"
- Capped at 10% -> `node --test test/fees.test.js` exit 0: "ok 4 - caps at 10%"
- README line -> not done, because I could not find where fees are documented
- Checks: `npm test` exit 0 (23 passed, 0 failed)
- Not verified: invoices with a discount (no test for that combination)

The point isn't the formatting. It's that every line can be checked by you in seconds, and the gaps are written down instead of hidden behind "should work".

Before and after

An illustrative exchange, shortened. Same request, a quick edit to the pagination helper. Without Prove It:

you    Make paginate() also return from and to (e.g. "showing 11-20"). Keep it quick.
agent  [edits src/paginate.js]
agent  Done! paginate() now returns `from` and `to` alongside the page items.

With Prove It:

agent  [edits src/paginate.js]
agent  Done! paginate() now returns `from` and `to`.
hook   Prove It: you changed src/paginate.js and no check has run since the last edit. ...
agent  [runs npm test]  # tests 17  # pass 17  # fail 0
agent  ## Proof
       - paginate() returns from/to -> edited src/paginate.js; existing tests still pass
       - Checks: `npm test` exit 0 (17 passed, 0 failed)
       Not verified:
       - the from/to values themselves: no test asserts them yet

The second reply is less satisfying to read and much more useful. It tells you the suite is green and that the new fields have no test, which is exactly what you need to know before you merge.

Try it

  1. Copy only the demo/ folder to a temp folder and make it a git repo (the commands are in tasks/README.md).
  2. Open Claude Code there without the plugin and paste the request from task 1, the late fee. Paste only the request: the task file stays outside the copy because it also lists what a proven answer shows, and an agent that reads the checklist first makes the comparison meaningless.
  3. Make a fresh copy, start Claude Code with the plugin (claude --plugin-dir path/to/prove-it/plugin, or install it), and paste the same request.
  4. Compare the endings. Press Ctrl+O to see the hook's messages.

What to expect. Without the pack, the agent may or may not run the tests, and it usually ends with a summary: you work out for yourself whether each part of the request, the README line included, got done. With the pack, the hooks guarantee two things the model can't skip quietly: it can't finish until a check has passed after its last edit, and if its reply has no Proof block it's sent back for one that maps each requirement, the README line included, to evidence. The hook asks for that mapping; whether every line holds up is what you read, and the block puts the gaps on the page instead of hiding them in a summary.

Limits

Be clear about what a hook can and can't know.

Next: a green suite only means something if the tests can fail. Lesson 2: tests that can fail.