Writing Plans That Work
Here's what works and here's what breaks.
Plan Structure
Every hardened plan has these mandatory sections. The plan-hardener agent adds them automatically during Step 2, but you should understand what each does and how to edit them:
| Section | Required? | Purpose |
|---|---|---|
| Scope Contract | Yes | In-scope paths, out-of-scope, forbidden actions |
| MUST Criteria | Yes | Non-negotiable outcomes (checkboxes) |
| SHOULD Criteria | Optional | Best-effort goals |
| Execution Slices | Yes | Checkpointed work chunks with gates |
| Branch Strategy | Recommended | Git branch name and merge approach |
| Rollback Plan | Recommended | How to undo if things go wrong |
Writing a Good Scope Contract
The scope contract is the most important section. It tells the AI exactly what files it can touch — and what's off-limits.
Good: Tight Scope
## Scope Contract
**In Scope**: src/services/UserService.cs, src/repositories/UserRepository.cs,
tests/services/UserServiceTests.cs, tests/repositories/UserRepositoryTests.cs
**Out of Scope**: frontend/**, deployment/**, docs/** (except this plan)
**Forbidden Actions**:
- Do NOT modify src/database/migrations/ (migration is a separate phase)
- Do NOT change AppSettings.json connection strings
- Do NOT add NuGet packages without explicit approval
Bad: Loose Scope
## Scope Contract
**In Scope**: anything related to users
**Out of Scope**: nothing specific
**Forbidden Actions**: don't break things
"Anything related to users" gives the AI free rein to refactor 20 files. "Don't break things" isn't enforceable. Be specific about paths, and list forbidden actions as concrete file patterns. That's how you get lasagna code — clean layers, each with a purpose — instead of spaghetti where everything touches everything.
Slicing Strategy
Slices are 30–120 minute chunks of work. Each slice should produce a commit-worthy change — the "one PR" rule.
Rules of Thumb
- One layer per slice — don't mix database migration with API controller in the same slice
- Build on foundations — create the model/migration first, then the repository, then the service, then the controller
- Tests with the code — include tests in the same slice as the code they test (not a separate "add tests" slice at the end)
- 30 minutes minimum — slices shorter than this have too much gate overhead
- 120 minutes maximum — slices longer than this accumulate too much risk before the next checkpoint
Example: 6-Slice Plan
Slice 1 — Database migration + model [30 min]
Slice 2 — Repository + unit tests [45 min]
Slice 3 — Service layer + business logic tests [60 min]
Slice 4 — API controller + integration tests [45 min]
Slice 5 — Error handling + edge case tests [30 min]
Slice 6 — Documentation + cleanup [30 min]
Validation Gates
Gates are the quality checkpoints between slices. A gate must be a concrete, executable command — not a human judgment call.
Good Gates
**Gate**:
dotnet build # zero errors
dotnet test --filter "UserProfile" # 6+ tests pass
grep -rn "string interpolation" src/ # zero hits (security)
Bad Gates
**Gate**: "tests pass" ← Which tests? How many?
**Gate**: "code looks clean" ← Not executable
**Gate**: "review the changes" ← Human-dependent, blocks automation
Parallel Execution
Mark slices that can run concurrently with the [P] tag. Add dependency declarations when slices must run in order:
### Slice 1 — Database Migration [30 min]
...
### Slice 2 — Repository Layer [P] [depends: Slice 1] [scope: src/repos/**]
...
### Slice 3 — Service Layer [P] [depends: Slice 1] [scope: src/services/**]
...
### Slice 4 — API Controller [depends: Slice 2, Slice 3]
...
Slices 2 and 3 both depend on Slice 1 (the migration) but are independent of each other — they run in parallel. Slice 4 waits for both to finish. The orchestrator builds a DAG (directed acyclic graph) and schedules accordingly.
[P] when slices touch different [scope: ...] paths.
Stop Conditions
Stop conditions tell the AI when to halt instead of trying to work around a failure:
**Stop if**: Build fails with compilation error
**Stop if**: Any existing test regresses (not just new tests)
**Stop if**: Migration produces data loss warning
**Stop if**: Security scan finds HIGH or CRITICAL vulnerability
Without stop conditions, the AI may try to "fix" a build failure by removing code, or skip a failing test by commenting it out. Stop conditions force it to report the problem instead of hiding it.
Context Files
Each slice can list which instruction files are relevant. Don't load all 18 — load only what's needed:
### Slice 1 — Database Migration
**Context**: database.instructions.md, security.instructions.md
### Slice 4 — API Controller
**Context**: api-patterns.instructions.md, auth.instructions.md, errorhandling.instructions.md
This keeps the AI's context window focused. A database slice doesn't need caching instructions; a controller slice doesn't need migration patterns.
Common Mistakes
| Mistake | What Happens | Fix |
|---|---|---|
| Scope too loose | AI refactors 20 files instead of 3 | List specific file paths, not categories |
| Scope too tight | AI can't create necessary helper files | Include reasonable wildcards: src/services/** |
| No stop conditions | AI works around failures silently | Add "Stop if" to every slice |
| Vague gates | Gate "passes" without actually validating | Use executable commands with expected counts |
| Tests in last slice | 5 slices of code, then discover it's untestable | Include tests alongside each code slice |
| Giant slices | 120+ min of work before first checkpoint | Break into 30–60 min focused chunks |
| Missing rollback | Panic when something breaks in production | Add rollback plan with specific git revert commands |
Plan Templates
Eight language-specific plan examples ship with Plan Forge. Use them as starting points:
| Stack | File | Features Demonstrated |
|---|---|---|
| .NET | Phase-DOTNET-EXAMPLE.md | RLS, Dapper, Blazor, GraphQL, 12 slices |
| TypeScript | Phase-TYPESCRIPT-EXAMPLE.md | Express, Prisma, Vitest |
| Python | Phase-PYTHON-EXAMPLE.md | FastAPI, SQLAlchemy, Pytest |
| Java | Phase-JAVA-EXAMPLE.md | Spring Boot, JPA, JUnit |
| Go | Phase-GO-EXAMPLE.md | Chi router, sqlx, testing |
| Swift | Phase-SWIFT-EXAMPLE.md | Vapor, Fluent, XCTest |
| Rust | Phase-RUST-EXAMPLE.md | Axum, sqlx, Cargo test |
| PHP | Phase-PHP-EXAMPLE.md | Laravel, Eloquent, PHPUnit |
All examples live in docs/plans/examples/.
📄 Full reference: AI-Plan-Hardening-Runbook.md