Blog
ENPL

Spec-Driven Development in Claude Code — why I no longer write 'just build me X' prompts

A five-stage process for planning a feature before the first line of code. Fewer iterations, fewer bugs, less refactoring. Concrete example: the very blog you're reading.

·5 min read
Spec-Driven Development in Claude Code — why I no longer write 'just build me X' prompts

Most people use Claude Code like a chatbot. They type "add a /api/users endpoint", look at what comes out, fix it, type more. That works for small changes. On bigger things it ends with a refactor every two hours and a stack of tangled files.

Lately I work differently, with SDD (Spec-Driven Development). Instead of one prompt with the feature description, I walk the agent through five stages. Each stage is a file in .kiro/specs/<feature-name>/, each requires approval before the agent moves on.

Sounds like overhead. In practice, saves hours.

The five SDD stages

/kiro:spec-init <feature-name>       # initial intent sketch
/kiro:spec-requirements <name>       # concrete requirements (EARS format)
/kiro:spec-design <name>             # architecture, interfaces, schemas
/kiro:spec-tasks <name>              # break into actionable tasks
/kiro:spec-impl <name>               # actual TDD implementation

Each stage reads the previous one and sends its output for approval. Without a manual "go ahead" the agent doesn't move.

Stage 1: spec-init — what do we actually want to build

Output is a spec.json plus a short description. This isn't about details, it's about intent. Goal, user, success.

{
  "feature": "portfolio-blog",
  "phase": "initialized",
  "approvals": {
    "init": false,
    "requirements": false,
    "design": false,
    "tasks": false
  }
}

No approval → next stage returns an error. That's an intentional gate.

Stage 2: spec-requirements — EARS format

This is where work begins. The agent generates requirements in EARS format (Easy Approach to Requirements Syntax), one of the simplest formats for precise behavior description:

Requirement 1: Article list page
WHEN user navigates to /blog
THE SYSTEM SHALL display the list of published articles
sorted descending by publication date.
 
WHEN the list contains more than 10 articles
THE SYSTEM SHALL split results into pages of 10
and display pagination.

Every requirement has a number (1.1, 1.2, ...) and gets cited in later stages. When an architectural decision shows up in the design, it has a reference to the specific requirement justifying it.

This eliminates "maybe also X?" mid-implementation. If X isn't in the requirements, we don't build it.

Stage 3: spec-design — architecture before code

Output is design.md: schemas, type interfaces, component list, Mermaid diagram if it makes sense.

// Example interface from blog spec design
interface PostMeta {
  title: string;
  description: string;
  slug: string;
  date: string;
  tags: string[];
  readingTime: string;
  draft: boolean;
}
 
interface BlogPost extends PostMeta {
  rawContent: string;
}

At this stage the agent makes technology decisions: which MDX library, SSG or ISR, how to parse frontmatter. Every decision has rationale in research.md.

Example from the blog, picking next-mdx-remote/rsc over @next/mdx:

@next/mdx requires registering every .mdx file as a Next.js page. For a file-based blog with dynamic slugs, that's inflexible. next-mdx-remote v6 has an RSC-compatible export — we can load an MDX file at runtime from any location.

That rationale stays in the spec forever. A month later I don't remember why I chose this over that, I check and I know.

Stage 4: spec-tasks — atomic tasks with parallel markers

Now it gets interesting. The agent breaks implementation into tasks, each with a number. Tasks that can run in parallel get a (P) marker:

- [ ] 2.1 (P) Define TypeScript types for the blog
- [ ] 2.2 (P) Implement article read and parse functions
- [ ] 2.3 (P) Define MDX plugin configuration

Three tasks, independent of each other, can fire in parallel. Later:

- [ ] 4. Implement article list page /blog
  - Requires Task 2.2 and Task 3 (PostCard, Pagination) done

Task 4 depends on 2.2 + 3.x, the agent knows it doesn't move until the previous ones finish. This lets you run agents in parallel without race conditions.

Stage 5: spec-impl — implementation with TDD

The final stage. The agent walks through the task list and implements. For each task it writes code, verifies, marks [x]. If something breaks, it goes back to design, doesn't hack.

Errors that weren't visible at earlier stages show up here. Example from the blog: TypeScript couldn't infer [plugin, options] as a tuple for rehype/remark plugins. Solution, escape hatch via Record<string, any>. That got documented in the spec as a lesson learned.

What you gain, what you lose

Gains:

  • Fewer iterations during implementation. You know what's being built before it's being built.
  • Better code review. Reviewer reads spec + diff, not just diff.
  • Future-you knows the context. Half a year later you open .kiro/specs/feature/design.md and see why you did it this way and not that.
  • Safer parallelization. (P) markers let you run multiple agents concurrently.

Cost:

  • Slower start. First commit only appears after requirements + design + tasks. That's 30-60 min of planning.
  • Overkill for small things. You don't fire spec-init for a one-line change.

When SDD makes sense

My rule of thumb:

Change scaleApproach
Bug fix, single functionDirect prompt
New UI componentMaybe spec-design alone
Whole feature (new page, integration, data model)Full SDD
Refactor larger than one fileFull SDD

General heuristic: if planning takes you more than 5 minutes, use SDD. A complete spec forces that plan onto paper.

Where this lives

In the project a .kiro/specs/<feature-name>/ directory gets created with files:

.kiro/specs/portfolio-blog/
├── spec.json          # approval status
├── requirements.md    # 10 EARS requirements
├── design.md          # architecture + interfaces
├── research.md        # decision rationales
└── tasks.md           # 8 tasks with markers

I commit everything to git. The spec is part of the code, not docs on the side.


This article came out of the portfolio-blog feature planned via SDD. The full spec is in the repo, from initiation to tasks, with all the architectural decisions.

If you want to try it, Claude Code has a kiro plugin with ready-made /kiro:spec-* commands. Just install it and run /kiro:spec-init <your-feature>.