The ticket had nine requirements. I implemented eight.
I would have sworn it was nine. I reread the ticket before opening the PR, went through the diff, felt the specific satisfaction of a job finished, and shipped it. The reviewer found the gap in under a minute: requirement six asked for a label on every resource, and I'd added it to four of five.
What bothers me isn't the miss. It's that I checked. I did the thing you're supposed to do, and the check came back clean on a piece of work that wasn't.
Self-Review Runs in the Wrong Direction
Here's what I actually do when I "review my changes against the ticket": I open the diff and read it. For each change, some part of my brain confirms yes, that's there because the ticket asked for it. Every hunk gets matched to a reason. The diff makes sense. I feel done.
Notice the direction. I'm starting from the code I wrote and walking outward to the requirements. Every line I look at has a justification, so everything checks out.
But a missing requirement produces no code. It has no line in the diff, no hunk to look at, nothing to trigger the check. Walking the diff can only ever find things that are wrong. It structurally cannot find things that are absent.
The direction that works is the opposite one: start from the requirements, take them one at a time, and go hunting in the diff for evidence that each is satisfied. Requirement six then has nowhere to hide, because you're standing on it asking "where is this?" instead of hoping to bump into it.
I know this. It's obvious once written down. I still don't do it, because after four hours of implementation my memory of the ticket has quietly been replaced by my memory of what I built — and those feel identical from the inside. The failure isn't discipline. It's that the person who wrote the code is the worst possible candidate to audit it against a spec, and that person is me, every time.
Handing It to Something That Wasn't There
So the check needs to come from something with no memory of writing the code. That's a good fit for a subagent: a fresh context, given the ticket and the diff, with no investment in either.
curl -fsSL https://ookay.dev/agents/ticket-review/install.sh | bash
That drops ticket-review.md into ~/.claude/agents/. Restart Claude Code and ask it to review:
review my staged changes against the ticket
It finds requirements from .ticket-* files in your project root, or from whatever you paste into the message. It figures out the scope from how you phrase it — staged changes, unstaged, last commit, last three commits, everything on the branch. And if it can't find requirements, it stops and asks rather than inventing them, which matters more than it sounds: an agent that guesses at requirements will happily confirm you met the ones it made up.
The Part That Makes It Useful
The agent's first move is to decompose the ticket into a numbered checklist before it looks at any code. Functional requirements, constraints, named edge cases, acceptance criteria. That ordering is the whole design. Building the rubric first means the review is driven by the spec instead of by the diff.
Then every item gets one of four verdicts:
- ✅ Done — with a citation to the specific lines that satisfy it
- ⚠️ Partial — started, but here's exactly what's missing
- ❌ Missing — not there at all
- 🔍 Needs verification — can't be confirmed from a diff alone
That last one earns its place. Plenty of requirements aren't decidable by reading code: does the alert actually fire, does the migration work against real data, is the deployed config the one in the repo. An agent that marks those ✅ because the code looks right is worse than no review, because now you have false confidence with a checkmark next to it. Explicitly separating "I verified this" from "this needs a human and a terminal" is what makes the ✅s worth anything.
The other rule I care about: cite evidence both ways. Marking something Done requires pointing at the code. Marking something Missing requires pointing at the requirement. No verdicts without receipts, in either direction.
Beyond the checklist it also sweeps for the ordinary stuff — off-by-ones, inverted conditions, typos in string literals and labels, copy-paste blocks where one substitution got skipped, and requirements that were technically implemented but not in the way the ticket meant. That last category is the sneakiest, and it's the one a checklist alone would pass.
What It Doesn't Do
It doesn't run your tests. It doesn't hit your cluster. It reads a diff against a spec, and everything outside that boundary comes back as 🔍 rather than a verdict.
It also inherits your ticket's quality. Vague requirements produce vague checklists, and a ticket that says "make it more robust" will generate a review item nobody can adjudicate. If it flags something as missing from the requirements themselves, that's usually the ticket's fault rather than yours — and worth fixing upstream, because next time it'll cost someone else the same hour.
And it is not a substitute for a human reviewer. It's the pass you run before the human, so their time goes to design and tradeoffs instead of counting whether the label made it onto all five resources.
Why This Pairs With a Handoff
I wrote about the session handoff skill recently, and these two solve opposite halves of the same problem.
The handoff captures what a session knew before that knowledge evaporates. Ticket-review checks what a session did against what it was asked to do. One is memory, the other is verification — and the thing both are working against is the same: your confidence in your own work grows faster than your accuracy does.
Requirement six is still the one I think about. It took eleven seconds to fix and I'd have caught it in ten if I'd read the ticket instead of the diff.