Build: plan.md, then implement
The build stage turns an accepted spec.md into a committed plan.md and then into an implementation in an isolated worktree. Plan mode lets the agent read the codebase without editing it. A named engineer accepts the plan before application code changes, and repository instructions, hooks, and a verifier keep the result checkable.
This page is for the developer who runs the agent and the tech lead who sets the team’s plan gate. You ask for a partial-refund endpoint and get back a 900-line diff. It edits the frozen v1/ package, bumps a dependency the platform team owns, and tests only the happy path. A five-minute read of a plan would have caught all three, but there was no plan, only the diff.
Traditional: an engineer reads the design and starts coding. How the change will be made stays in their head, and the reviewer first sees the finished diff, when rework is slow.
AI-native: the agent writes the plan in a read-only mode, the engineer corrects it while changing course is still editing a document, and the accepted version is committed as plan.md. Review later checks the diff against that file.
What a plan-first build gives you
Section titled “What a plan-first build gives you”- A
plan.mdcontract with files, order, risks, rollback, and proof commands, and a named owner for each open decision. - Three copy-paste prompts: draft the plan read-only, attack it, and execute it in a worktree.
- The exact plan-mode and worktree commands for Claude Code, Codex, and Cursor.
- A rule for where knowledge belongs: instruction file, skill, hook, or CI.
- Checks that prove the build without a human reading every line.
Before you start the build stage
Section titled “Before you start the build stage”- An accepted
intent.mdfrom Plan and an acceptedspec.mdfrom Design. A small change can skip both, but not the plan. - A project instruction file the agent reads at session start:
CLAUDE.mdorAGENTS.md, or a Cursor project rule. - One command that runs the tests or the build locally. See Test for the feedback loop.
- A clean branch. The artifact chain describes where
intent.md,spec.md, andplan.mdlive in the repository.
What goes into an acceptable plan.md?
Section titled “What goes into an acceptable plan.md?”A plan is acceptable when an engineer who never saw the conversation could implement the change from it alone. Reject any plan that does not name:
- the problem and non-goals, inherited from
intent.mdandspec.md; - the files and symbols to inspect or change;
- ordered implementation steps and the dependencies between them;
- data migration, compatibility, security, and rollback risks;
- the deterministic checks that prove it, plus any visual or manual proof and who does it;
- open decisions and the person who must make each one.
A skeleton you can adopt as-is:
# Plan: PAY-412 partial refunds
Spec: intent/PAY-412/spec.md · Approver: Dana Kowal (routine)
## Non-goals- No change to the v1/ refund endpoint (frozen).
## Files- src/main/java/com/acme/payments/v2/RefundController.java (new endpoint)- src/main/java/com/acme/payments/v2/RefundService.java (partial amount rule)- src/itest/java/com/acme/payments/v2/RefundIT.java (new)- src/main/resources/db/migration/V42__refund_amount.sql (new)
## Steps1. Add the Flyway migration V42__refund_amount.sql (additive column only).2. Add RefundService.partialRefund: BigDecimal validation, and an idempotency key on retry so a repeated request refunds once.3. Expose POST /v2/refunds/{id}/partial.
## Risks and rollback- Migration is additive; rollback is a code revert, the column stays.- Double refund on retry: idempotency key checked in step 2.
## Proof- make test && make itest (exit 0)- RefundIT covers: full, partial, over-refund rejected, retry is idempotent.
## Open decisions- Currency rounding mode for JPY: owner Finance (Ola Nowak).Keep the plan beside the change, not only inside the tool. A plan that lives only in a chat or in a tool’s private plan store cannot serve CI, reviewers, or the next agent.
Plan in read-only mode, then commit plan.md
Section titled “Plan in read-only mode, then commit plan.md”-
Start the session in plan mode, so the agent can read and explore but cannot edit files.
-
Give the agent
intent.mdandspec.md, and ask for a plan that follows the contract above. -
Interrogate the plan. Ask what the change could break, which step carries the most risk, and which alternatives the agent rejected and why.
-
Run the challenge prompt below, ideally in a fresh session, so the critique is not coloured by the reasoning that produced the plan.
-
Iterate until the plan names real files, symbols, and proof commands. Then accept it.
-
Make the first write after acceptance
plan.md, and commit it before any application code. -
Implement. When the implementation has to depart from the plan, stop, update
plan.mdwith the reason, and get the approver to accept the new direction. Commit the updated plan with the code that needed it.
The tools differ in how you enter plan mode and in what the agent can do while it is there.
Enter plan mode with Shift+Tab (the status bar shows ⏸ plan mode on), or run /plan followed by your request, which switches the session into plan mode. You can also start the session in it:
claude --permission-mode planClaude reads files and can run read-only exploratory commands. It runs them without prompting only when auto mode is available. It does not edit your source until you approve the plan. When the plan is ready, Claude asks how to proceed: Yes, and use auto mode (when auto mode is available; otherwise Yes, auto-accept edits), Yes, manually approve edits, or No, keep planning. Press Ctrl+G to edit the proposed plan in your text editor before you answer.
Because edits stay blocked while planning, Claude cannot save plan.md during plan mode. Choose Yes, manually approve edits and make the first approved edit plan.md; switch to a less interrupting mode once the plan is committed.
To make plan mode the starting mode for a project’s terminal sessions, set "defaultMode": "plan" under permissions in .claude/settings.json. Checked against Claude Code 2.1.283.
Type /plan in the Codex CLI to switch to Plan mode, then paste the drafting prompt. For a scripted, non-interactive planning run, keep the sandbox read-only:
codex exec -s read-only "Read intent.md and spec.md and write the implementation plan to stdout. Do not edit files."-s is the legacy sandbox flag, still accepted in 0.157.1; the beta permission-profile equivalent is -c default_permissions=":read-only". Do not combine the two: OpenAI says they do not compose.
Plan mode and approvals are separate controls. -a on-request or -a never decides when Codex asks before running a command; it does not make the agent plan first. The update_plan checklist tool is off by default since 0.152.0 and is not a substitute for Plan mode. Checked against Codex CLI 0.157.1.
Switch the agent to Plan Mode, which “creates detailed implementation plans before writing any code”, attach spec.md, and paste the drafting prompt. When you accept the plan, ask the agent to save the final version as plan.md in the repository, next to spec.md, so reviewers, CI, and other tools can read it.
Cursor behaviour on this page was checked on 2026-08-28.
Run the implementation in an isolated worktree
Section titled “Run the implementation in an isolated worktree”A worktree gives each session its own checkout and branch, so two sessions cannot overwrite each other’s files. Split work that touches different files across sessions. Work that shares files runs in one session, one step after another.
claude -w pay-412-partial-refundsThe worktree is created under .claude/worktrees/ on a new branch. A worktree is a fresh checkout, so gitignored files such as .env are missing; list them in a .worktreeinclude file at the project root to copy them in automatically. Use a repository helper instead when setup also needs separate ports or local database state.
codex --worktree--worktree runs the session in a new managed Git worktree, and /worktree does the same from inside the TUI (stable and on by default since 0.156.0).
Use Cursor’s Worktrees, which “let Agent work in isolated Git checkouts”, or start a Cloud Agent per independent task when the work should run on a remote machine.
After the plan is accepted, run the session in the least permissive mode that can complete the approved slice. Permissions and sandboxes compares the modes, and Git worktrees covers cleanup and port collisions.
Encode what the team knows in files the agent reads
Section titled “Encode what the team knows in files the agent reads”Knowledge that used to sit in people’s heads becomes a file the agent reads at the start of every session. /init drafts one in Claude Code and in Codex; in Cursor, ask the agent to draft a project rule from the repository.
-
Generate a starting file, then cut it to what a new joiner needs on day one: build, test, and lint commands, the conventions that matter, and the mistakes the agent keeps making.
-
Commit it at the repository root so the whole team shares one version.
-
When the agent makes the same mistake twice, add the correction.
-
Remove every line the agent would follow anyway. Pruning CLAUDE.md and AGENTS.md shows how to test which lines earn their place.
# Payments service
## Commands- Build: make build- Test: make test (unit), make itest (integration, needs Docker)- Lint: make lint
## Conventions- Java 21, Spring Boot 3. No new Lombok.- Money is always BigDecimal, never double.- Every endpoint needs an integration test in src/itest.
## Things the agent gets wrong- Do not bump dependency versions; the platform team owns them.- The v1/ package is frozen; changes go in v2/.Codex reads AGENTS.md. Claude Code reads CLAUDE.md. From v2.1.277 (the latest channel on 2026-09-26; v2.1.281 on Bedrock, Google Cloud, Foundry and LLM gateways), Claude Code reads AGENTS.md when a project has no CLAUDE.md. Cursor reads its Rules. AGENTS.md and CLAUDE.md covers keeping one file for several tools.
Where does a rule belong: instruction file, skill, hook, or CI?
Section titled “Where does a rule belong: instruction file, skill, hook, or CI?”An instruction file and a skill are advisory: the agent reads them and usually complies. A hook or a CI check runs as code and holds for the calls it matches. Put each rule at the cheapest layer that makes it hold.
| The rule is… | Put it in | Example |
|---|---|---|
| A fact the agent needs every session | CLAUDE.md / AGENTS.md / project rule | “Test with make test” |
| A procedure applied only to some tasks | A skill (SKILL.md) | The team’s API error format; a security checklist |
| A constraint that must never be broken during editing | A hook | No edits under v1/; format after every edit |
| A constraint on the merged result | CI and branch protection | Tests pass; coverage does not drop |
Skills follow the open Agent Skills standard (skill-name/SKILL.md); in Claude Code a project skill lives in .claude/skills/NAME/. See agent skills for writing and sharing them.
Add build-time hooks that make rules hold
Section titled “Add build-time hooks that make rules hold”During implementation, hooks block edits to protected paths, run the formatter and linter after file edits, and keep credentials out of the diff. Keep build-phase hooks fast and scoped to the file that changed; heavier checks belong at commit or in CI. A hook that asks a human for approval belongs in Deploy, because an approval prompt during build puts a person back on the critical path of every parallel session.
Hooks are configured in .claude/settings.json. This PreToolUse hook blocks edits to the frozen package:
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-frozen.sh" } ] } ] }}#!/bin/bash# .claude/hooks/protect-frozen.sh (chmod +x; needs jq)FILE=$(jq -r '.tool_input.file_path // empty')case "$FILE" in */com/acme/payments/v1/*) echo "v1/ is frozen; put the change in v2/ (see plan.md)" >&2 exit 2 ;;esacexit 0Exit code 2 blocks the tool call and shows the message to Claude, so the agent corrects course instead of stopping. The Edit|Write matcher does not see shell writes: a Bash call such as sed -i or cp into v1/ passes. Back the rule with the CI check below or a sandbox rule that denies writes to the path.
Codex CLI 0.157.1 has hooks with 12 events, including PreToolUse, PostToolUse, and Stop; /hooks lists them in the TUI. Codex reads project hooks from .codex/hooks.json in the same JSON shape as Claude Code. It edits files through the apply_patch tool and passes the whole patch in tool_input.command, not a file_path, so the script reads the patch’s file headers:
{ "hooks": { "PreToolUse": [ { "matcher": "apply_patch", "hooks": [ { "type": "command", "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/protect-frozen.sh\"", "timeout": 10 } ] } ] }}#!/bin/bash# .codex/hooks/protect-frozen.sh (chmod +x; needs jq)jq -r '.tool_input.command // empty' \ | sed -nE 's/^\*\*\* (Add File|Update File|Delete File|Move to): (.*)$/\2/p' \ | grep -q 'com/acme/payments/v1/' \ && { echo "v1/ is frozen; put the change in v2/ (see plan.md)" >&2; exit 2; }exit 0On macOS and Linux, Codex runs a hook command through your login shell ($SHELL -lc, falling back to /bin/sh) in the session’s working directory, so $(git rev-parse --show-toplevel) resolves the repository root even when the session starts in a subdirectory. As in Claude Code, exit code 2 with a reason on stderr blocks the call. The apply_patch matcher does not see shell commands, so a sed -i or cp into v1/ passes; back the rule with the CI check below or a sandbox write-deny rule. Project hooks do not run until you trust them: open /hooks, review the hook, and trust it again after anyone changes hooks.json. An administrator can restrict a fleet to managed hooks with allow_managed_hooks_only = true in requirements.toml. Checked against Codex CLI 0.157.1.
Cursor Hooks are processes that exchange JSON over stdio and run before or after stages of the agent loop, where they “can observe, block, or modify behavior” (Cursor hooks docs, checked 2026-08-28). Cursor’s hook event names and config file were not re-verified after 2026-08-28, so this page gives no Cursor config to copy: port the logic of the Claude Code script and use the block response documented on the Cursor hooks page.
A hook guards the session; CI guards the merge. This check fails any pull request that touches the frozen package, whichever tool wrote the change:
git diff --name-only origin/main...HEAD | grep -q 'com/acme/payments/v1/' \ && { echo "v1/ is frozen; put the change in v2/"; exit 1; }exit 0Check out with fetch-depth: 0 so that origin/main...HEAD resolves; a shallow clone has no merge base, and this check and the plan.md check below both fail.
Use hooks as deterministic guardrails has more handlers and the team rollout.
Run parallel sessions and subagents
Section titled “Run parallel sessions and subagents”A parallel session is another full agent in its own worktree. A subagent runs inside one session with its own context window and tool limits: a verifier, a researcher, a simplifier. Start with two or three parallel sessions; the ceiling is how many streams one person can accept plans for and review. Running many agents at once covers the tooling that supervises them, and Spec-driven frameworks compared shows how Spec Kit and OpenSpec formalise the plan.md contract.
Turn repeated jobs into subagents checked into Git. In Claude Code they are Markdown files in .claude/agents/, and Explore and Plan are built in; Codex has /subagents; Cursor has Subagents. An example verifier for Claude Code:
---name: verifierdescription: Runs the app and checks the change against plan.md before the session reports donetools: Bash, Read---Start the app with make run. Exercise the behaviour plan.md describesand the two nearest neighbouring flows. Report what you ran, what yousaw, and any behaviour that does not match plan.md. Do not fixanything; report only.Bash lets the verifier write files, so “report only” is an instruction, not a guarantee. For a hard limit, run it in a sandbox that denies writes; see Permissions and sandboxes.
The verifier is not the feedback loop. The loop runs throughout the task; the verifier is a fresh context at the end, so its verdict is not coloured by the assumptions that produced the code. Custom subagents covers tool limits and model choice.
How do you prove the build without reading every line?
Section titled “How do you prove the build without reading every line?”The plan is what makes the diff checkable: every claim a reviewer needs to test is written down before the code exists.
-
Plan before code.
plan.mdappears in Git history before, or in the same commit as, the first application change. A CI step can check that the pull request touches aplan.md. Run it only when the pull request carries your non-trivial label. Review still checks the commit order:Terminal window git diff --name-only origin/main...HEAD | grep -q 'plan\.md$' \|| { echo "Non-trivial change without a committed plan.md"; exit 1; } -
Proof ran in the session. The execution report lists every proof command from
plan.mdwith its exit code. Missing commands mean missing evidence, not passing tests. -
A fresh verifier agreed. The verifier subagent’s report is attached to the pull request.
-
The diff matches the plan. A review agent compares the changed files with the plan’s Files section and flags anything unlisted. Deviations must appear in
plan.mdwith a reason.
Who signs off. The engineer accepts routine plans. Higher-risk work (migrations, auth, money, shared interfaces) goes to a tech lead or architect before implementation starts. Accepting a plan grants no authority to merge or deploy; those stay behind branch protection and the production gate described in Governance and autonomy. Everything a session does is attributed to the engineer who ran it.
Leading indicators: share of changes that merge from the first implementation pass, and time from plan acceptance to merged pull request. Lagging indicators: rework cycles per change, and how often the merged diff still matches plan.md. Metrics for an AI-native SDLC defines both.
What breaks in a plan-first build, and how to recover
Section titled “What breaks in a plan-first build, and how to recover”The plan is generic. It says “update the service layer” instead of naming files. Reject it and rerun the drafting prompt; a plan without paths and symbols cannot be checked against a diff.
Claude Code cannot save plan.md while planning. Plan mode blocks edits, including the plan file. Approve with Yes, manually approve edits, write plan.md first, and commit it before any other edit.
The plan lives only inside the tool. A plan in a chat, or in a tool’s own plan store, disappears for CI and reviewers. Save it into the repository as plan.md before the first application commit.
The plan goes stale mid-implementation. The agent discovers a second caller or a missing index. Stop the session, update plan.md with the reason, get the approver to accept the change, then continue. The execution prompt above makes the agent stop instead of improvising.
Planning is mistaken for permission. Plan mode controls when the agent may edit; it does not govern commands after approval, credentials, merge, or deploy. Configure the sandbox, approvals, branch protection, and production gates separately.
The new worktree does not build. A fresh checkout lacks .env and local state. Add the files to .worktreeinclude in Claude Code, or use a repository setup script that copies them and assigns ports.
Parallel sessions collide. Two sessions edited the same file and one merge undoes the other. Re-split the work by file ownership in plan.md, and run shared-file steps in one session.
A build hook slows every edit. A full test run on each file edit stalls the loop. Keep the hook to the changed file; move whole-suite checks to commit or CI.
The tool has no plan mode in the current context. Some surfaces or headless runs do not offer it. Use the read-only drafting prompt with a read-only sandbox, such as codex exec -s read-only (see the Codex tab above for the permission-profile equivalent).