Ship a feature with Claude Code without rewriting it twice
The explore → plan → slice → adversarial-review loop that senior engineers actually use.
- Outcome
- A merged pull request with tests that fail without your change, and a reviewer who didn't have to ask what it does.
- Time
- 45–90 minutes for a medium feature
The guided runner is a Pro feature
Every step is fully readable and copyable below, free. Pro adds step sequencing, carried inputs, checkpoints, and resumable runs.
The default way people use a coding agent is to describe a feature and let it start editing. That works for small, local changes and degrades badly as soon as the change crosses more than two files.
The failure isn't that the model writes bad code. It's that it writes plausible code against a wrong mental model of your system, and you don't find out until review — at which point the diff is large enough that fixing it feels like starting over. So you start over. That's the two-rewrites tax.
This playbook spends its first three steps producing zero code. That's the entire trick. You are buying a shared, written model of the change before any edit happens, and you are keeping the exploration that produced it out of the context window that writes the code.
When not to use this
Skip this for changes under ~30 lines in a file you already know well — the ceremony costs more than the rewrite risk. Skip the planning steps entirely if you're spiking to learn something and intend to throw the code away; plan mode on a spike is pure overhead.
Before you start
- A test command that runs green today (you need a baseline, not full coverage)
- Write access to a branch, not main
- A rough idea of the feature — not a spec, this playbook produces the spec
Write acceptance criteria before touching the codebase
Force the ambiguity out of your own head and onto the page, while the context window is still empty and cheap. Every later step is graded against this.
Fill in
I'm about to build this feature: {{feature}}
Context: {{repo_context}}
Don't look at the code or write any yet. First, interrogate the request.
1. List the acceptance criteria as testable statements — things that are
observably true when this is done. Aim for 3–7.
2. List every ambiguity in my description that could reasonably be
resolved more than one way, and give your recommended resolution for
each with a one-line reason.
3. List what is explicitly out of scope, including the tempting adjacent
work I'd probably drift into.
Be concrete. "Handles errors gracefully" is not an acceptance criterion;
"returns 409 with an existing-export id when an export is already in
flight" is.Why it works
Two mechanisms.
First, ambiguity is cheapest to resolve before anyone has invested in an implementation. Once code exists, both you and the model start defending it — the ambiguity gets resolved implicitly, in whichever direction the existing code already leans.
Second, this produces a written artifact that survives context loss. You will clear context at least twice in this workflow. The acceptance criteria are what you re-seed the next session with, and they're a fraction of the tokens that produced them.
Checkpoint
You have 3–7 testable criteria, and at least one ambiguity was surfaced that you had not consciously decided. If the model found no ambiguities, either your description was unusually precise or the model is agreeing with you — re-read the list and check which.
Common mistakes
- ×Accepting criteria that restate the feature ("users can export") rather than specifying the observable outcome.
- ×Letting it start reading files here. Exploration in this step contaminates the framing with what's currently easy to build.
- ×Skipping the out-of-scope list. It's the cheapest scope-creep defense you'll ever write.
Follow-ups
- Which of these criteria is most likely to be quietly dropped during implementation? Why?
- Rewrite criterion N as a test name.
Explore read-only, in a context you're willing to throw away
Find the files, seams, and conventions this change touches — and get a written map out, without spending your implementation context window on the search that produced it.
Fill in
Use a subagent for this exploration so the search output stays out of our
main context. Read only — do not edit anything.
Goal: {{criteria}}
Report back with:
- The files that will need to change, and one line each on why
- The files that are adjacent and will NOT change (so I know you looked)
- The existing convention this codebase uses for this kind of thing, with
one concrete example file I can read to see it
- The seam where this change plugs in — the function, route, or module
boundary it hangs off
- Anything you found that contradicts my acceptance criteria
Keep the report under 40 lines. I want the map, not the territory.Why it works
Exploration is token-expensive and low-signal per token: grep output, file listings, dead ends. Running it in a subagent means the main thread receives the 40-line conclusion instead of the 4,000 lines of search that produced it — so the context that writes your code is dense with decisions rather than padded with directory listings.
The "files that will NOT change" request is a cheap honesty probe. A model that skipped the search can produce a plausible change list; it's much harder to fake a specific list of nearby files it correctly ruled out.
Checkpoint
You get a file list you can sanity-check against your own knowledge of the repo, and the named convention example file actually exists and actually demonstrates that convention. Open it and confirm.
Common mistakes
- ×Running exploration in the main thread, then wondering why the model gets vague 200 messages later.
- ×Not reading the convention example yourself. This is the step where a wrong convention gets locked in for the whole feature.
- ×Accepting a change list longer than ~8 files without asking whether the feature should be split.
Variation: Unfamiliar codebase
When: You've been on this repo less than a week
Same as above, but also trace one existing request end-to-end through the layers this feature will touch — entry point to persistence and back — naming each file in order. I need to see the shape of the system, not just the change surface.
Follow-ups
- What's the riskiest file in that list and why?
- Is there an existing abstraction here I should be extending instead of adding to?
Get a written plan and actually review it
Produce a step-by-step implementation plan you can red-team in two minutes — while changing your mind is still free.
Fill in
Enter plan mode. Using the map from the exploration and these acceptance
criteria:
{{criteria}}
Write an implementation plan as an ordered list of vertical slices. Each
slice must:
- leave the test suite green when it's done
- be independently reviewable
- name the files it touches and what changes in each
For each slice, state what could go wrong and how I'd know.
Then, separately: name the two decisions in this plan you're least
confident about, and what you'd need to see to resolve them.
Do not write code yet.Why it works
Vertical slices matter more than they sound. The common failure is a plan organized by layer — "first the schema, then the API, then the UI" — which produces a long stretch where nothing is verifiable and a bug in slice one surfaces during slice three, contaminated by two layers of later work. Slices that each end green mean every bug has a small, recent, obvious cause.
Asking for the two least-confident decisions works because it's easier for a model to rank its own uncertainty than to volunteer doubt unprompted. Unprompted, the plan reads as uniformly confident; ranked, the soft spots surface — and they are usually exactly where the rewrite would have come from.
Checkpoint
Every slice ends with the suite green, and you personally disagree with, or want to change, at least one thing in the plan. A plan you accept wholesale on first read usually means you skimmed it.
Common mistakes
- ×Approving the plan without reading it. This step's entire value is the two minutes you spend reading.
- ×Layer-shaped slices instead of vertical ones.
- ×Plans over ~6 slices. That's not a feature, it's a project — go back to step 1 and cut scope.
Follow-ups
- Collapse slices N and M — they're not independently useful.
- What would this plan look like if we couldn't change the database schema?
Implement one slice, verify, commit, repeat
Convert the plan into code one green checkpoint at a time, so that any breakage has exactly one recent cause.
Fill in
Implement {{slice}} from the plan. That slice only — if you find yourself
wanting to fix something in a later slice, note it and move on.
Follow the conventions in the example file we identified during
exploration. Match the surrounding code's error handling and naming rather
than introducing a new style.
When done, run `{{test_cmd}}` and show me the result. If it fails, fix it
before reporting back. If it fails twice for the same reason, stop and
tell me what you think is actually wrong — don't try a third variation.Why it works
The stop-after-two-failures rule is the load-bearing part. Left alone, an agent will keep trying variations on a broken approach, and each attempt adds failed code and error output to the context — degrading the very reasoning you need to diagnose the real problem. Two strikes forces the diagnosis up to you while the context is still clean enough to work in.
Explicitly deferring later-slice fixes prevents the diff from silently growing past what you agreed to review.
Checkpoint
{{test_cmd}} exits 0, and git diff --stat touches only files the plan
named for this slice. Commit before starting the next slice.
Common mistakes
- ×Running all slices in one message. You get one large diff and lose the whole benefit.
- ×Not committing between slices, so you can't cheaply revert one bad slice.
- ×Letting "while I was in there" edits land. They're the reason reviewers stop reading carefully.
Follow-ups
- Show me the diff for this slice only, and explain anything you did that the plan didn't call for.
Review your own diff with fresh eyes and no memory of writing it
Get a genuine critique instead of the self-congratulation an author — human or model — gives their own work.
Fill in
Clear the context first, then start here with no history of building this.
Review the diff on this branch as a skeptical staff engineer who did not
write it and is mildly annoyed at being asked. You are looking for reasons
to send it back.
These are the acceptance criteria it claims to meet:
{{criteria}}
Report, in this order:
1. Anything that is wrong — a bug, a race, an unhandled case, a wrong
assumption about the existing system
2. Any acceptance criterion that is not actually met, or is met only in
the happy path
3. Anything that will make this hard to change in six months
4. Anything that should be deleted
For each finding, give the file, the line, and the concrete input or state
that triggers the problem. If you can't name a trigger, say so and drop
the finding — I don't want stylistic opinions dressed as defects.Why it works
Context is the whole mechanism. A model that just wrote 300 lines has those lines, and its own reasoning for them, filling its window — every decision arrives pre-justified. Clearing context and re-entering as a reviewer removes the justifications and leaves only the artifact. Same model, materially better critique.
"Name the triggering input or drop the finding" is the filter that makes the output usable. Without it you get a list of twelve items, nine of which are taste, and you learn to skim the list — which defeats the step.
Checkpoint
At least one finding is real enough that you fix it. Zero real findings on a multi-file feature means the review didn't happen properly — check whether context was actually cleared.
Common mistakes
- ×Reviewing in the same session that wrote the code. This is the single most common way to waste this step.
- ×Fixing every finding. Some are wrong. Push back — a reviewer that's never contradicted is a reviewer you've stopped reading.
- ×Doing this before the last slice lands, so the review is against a moving target.
Variation: Security-sensitive change
When: The diff touches auth, payments, file uploads, or user input parsing
Same review, plus: enumerate every trust boundary this diff crosses. For each, state what is assumed about the data arriving and where that assumption is actually enforced. Flag any boundary where the enforcement is somewhere other than the boundary itself.
Follow-ups
- Which of your findings would you actually block the PR on, versus leave as a comment?
Make the tests fail without your change
Verify the tests test the feature, rather than testing that the code you wrote is the code you wrote.
Fill in
For each acceptance criterion, point me at the specific test that would
fail if that behavior regressed. Name the test, not the file.
For any criterion without such a test, write one.
Then verify it honestly: for the two most important new tests, temporarily
break the implementation they cover — revert the logic, not the signature
— run `{{test_cmd}}`, and show me the failure output. Then restore.
If a test passes while its implementation is broken, that test is
decorative. Tell me and rewrite it.Why it works
Tests written alongside an implementation tend to assert the shape of what was built rather than the requirement it was built for — mocks return what the code expects, assertions match observed output. They pass forever, including after the feature breaks.
Deliberately breaking the implementation is the only cheap way to tell the difference. It's a one-minute mutation test, run by hand, on the two assertions that matter most.
Checkpoint
You have seen real failure output from a deliberately broken implementation, and the suite is green again after restoring. Every acceptance criterion maps to a named test.
Common mistakes
- ×Taking "yes, that's covered" at face value without seeing the red.
- ×Breaking a function signature instead of its logic — that fails everything and proves nothing.
- ×Forgetting to restore. Check `git diff` before the next step.
Follow-ups
- Which criterion has the weakest test coverage right now?
Write a PR description a reviewer can act on
Spend the reviewer's attention on the risky 10% of the diff instead of making them find it.
Fill in
Write the PR description. Structure:
- **What changes and why** — two or three sentences, in terms of the user
or system, not the files
- **How to verify** — the exact commands or clicks a reviewer runs
- **Where to look hardest** — the two or three hunks that carry the real
risk, and what specifically to check about each
- **What I chose not to do** — deliberate omissions, so they don't get
raised as review comments
- **Follow-ups** — anything deferred, with enough context to file as an issue
These were the acceptance criteria: {{criteria}}
No changelog of files touched — the diff already says that. No "this PR
adds" preamble. Write it the way a senior engineer writes for a colleague
whose time they respect.Why it works
Review attention is uniform by default and risk is not. "Where to look hardest" reallocates a scarce resource to where it changes outcomes, and it's an implicit self-assessment — the act of naming your riskiest hunks is often when you notice one is riskier than you'd admitted.
"What I chose not to do" pre-empts the most common review comment class: suggestions you already considered and rejected.
Checkpoint
A reviewer who has never seen this branch knows, from the description alone, what to run and which two hunks deserve their attention.
Common mistakes
- ×Letting the model list every file it touched. Nobody reads it and it buries the risk section.
- ×Omitting the deliberate-omissions section, then relitigating those decisions in comments.
- ×Describing changes in file terms ("updated the handler") instead of behavior terms.
Follow-ups
- Draft the review comment you'd leave on your own riskiest hunk.
Did this work?
The PR merges without a round of review comments that amount to "this isn't what I asked for," and the test suite fails if you revert the implementation while keeping the tests.
Tested on claude-opus-5. Evidence status is draft; it moves to battle-tested only on recorded runs, never by hand.