Spec-Driven Development: Shipping Features with AI Without Losing Control

The standard way most developers use AI coding assistants goes something like this: open a chat, describe what you want, paste some context, iterate until it works, merge. Fast, yes. But after a few weeks of this, codebases start showing the symptoms — scattered implementations, features that almost match what was intended, changes nobody can fully explain, and a growing gap between what was asked and what was built.

The root cause is that AI agents work best with precise, bounded context — and free-form prompting provides neither. Spec-driven development is the discipline of defining that context deliberately, before you ask the AI to build anything.

The Core Problem with Free-Form AI Development

When you describe a feature in a chat message, you are solving multiple problems simultaneously and implicitly: what should be built, what is out of scope, which files should change, what the acceptance criteria are. The AI resolves all these ambiguities on its own — silently, based on inference. Sometimes it guesses right. Often enough it doesn't, and you only find out during review or in production.

The gap that spec-driven closes: Between "describe what you want" and "AI implements it" there is a step that most teams skip — alignment. Not alignment with the AI, but alignment within the team about what exactly is being built, why, and where the boundary is. A spec makes that alignment explicit and durable.

What a Spec Is (and What It Is Not)

A spec is not a technical design document, not a story ticket, not a requirements dump. It is a focused, single-feature agreement between the developer and the AI that answers four questions: what is being built, what is not being built, which tasks constitute the implementation, and which files are expected to change.

# SPEC-003 — Add bio field to user model ## Status in-progress ## What Add an optional `bio` field (short text) to the user model, expose it in the profile endpoint, and allow the user to update it. ## Out of scope - Profile photo upload - Content moderation for bio text - Bio length validation beyond a simple max-length constraint ## Scope - [ ] Add bio field to User model - [ ] Create migration for bio column (max 280 chars, nullable) - [ ] Include bio in GET /api/users/me response - [ ] Allow PATCH /api/users/me to update bio ## Files expected to change - backend/app/models/user.py - backend/app/routes/users.py - backend/app/schemas/user.py - backend/migrations/

Notice the "Out of scope" section. This is the most underrated part of a spec. Every ambiguous feature request has a gravity well of adjacent features that the AI — and sometimes the developer — will be tempted to implement. Making scope boundaries explicit prevents scope creep before it starts, and gives the AI a clear signal to stop.

The Spec Lifecycle

✦
Draft
AI drafts from your description
→
✔
Approve
Human reviews scope & tasks
→
▶
Start
TASK.md written to project root
→
⚙
Implement
AI works from TASK.md context
→
◎
Review
AI audits against the spec
→
✓
Close
Spec archived, TASK.md removed
one spec = one feature = one bounded AI session
Each phase has a clear owner — AI drafts, human approves, AI implements, AI reviews, human closes

Phase 1 — Draft: AI as Spec Author

You describe the feature in plain language. The AI generates the spec — breaking the description into concrete tasks, inferring scope boundaries, identifying which files are likely to change, and flagging ambiguities for you to resolve. The output is a structured markdown file, not an implementation.

$ ai-kit spec new "add a bio field to the user profile so users can write a short description about themselves, visible on their public profile page" Generating spec... ✓ SPEC-005 created in .aikit-specs/draft/SPEC-005-user-bio-field.md → 4 tasks identified → 3 files expected to change → Review the spec before approving

The AI does not write any production code at this stage. It only produces the agreement. This separation is the entire point — you are deciding what to build before you start building it, with AI helping structure the decision rather than jumping straight to implementation.

Phase 2 — Approve: The Human Gate

Before implementation starts, a human reviews and approves the spec. This is not a formality — it is the highest-value step in the entire workflow. The approval gate forces an explicit answer to the questions most free-form AI development never asks:

Approval takes two to five minutes. It prevents hours of re-work caused by misaligned implementation. Teams that skip this step consistently report the largest gap between what they asked for and what was built.

Phase 3 — Start: TASK.md as the Implementation Contract

When a spec is started, a TASK.md file is written to the project root. This file is the implementation contract — a focused, single-file context that the AI reads at the beginning of every session. It contains the approved spec, the task checklist, and the list of expected files.

# TASK.md — SPEC-005 (Active) # Add bio field to user model ## Your task Implement the following spec exactly as written. Do not add features not listed in the scope. Do not modify files not listed in "Files expected to change". ## Scope (implement all of these) - [ ] Add bio field to User model - [ ] Create migration for bio column (max 280 chars, nullable) - [ ] Include bio in GET /api/users/me response - [ ] Allow PATCH /api/users/me to update bio ## Out of scope (do not implement) - Profile photo upload - Content moderation for bio text - Bio length validation beyond max-length ## Files expected to change - backend/app/models/user.py - backend/app/routes/users.py - backend/app/schemas/user.py - backend/migrations/

The TASK.md pattern solves a real problem in long AI sessions: context drift. Without a persistent anchor, an AI session that spans dozens of tool calls will gradually drift from the original intent — adding things, changing scope, touching files it shouldn't. TASK.md is that anchor. It is re-read at the start of every session and serves as the authoritative description of what is in and out of bounds.

Why this works: AI agents perform significantly better with bounded, explicit context than with open-ended instructions. A TASK.md with four tasks and three files is a far tighter prompt than "implement the bio feature". The tighter the context, the more the AI's output matches the intent — and the less you need to review and redo.

Phase 4 — Implement: AI with a Leash

Implementation is where the AI writes code — but now within a defined contract. The developer opens their AI coding assistant with the TASK.md loaded and lets the agent work through the task list. The key discipline: the AI marks tasks as done as it completes them, and the developer can track progress without having to parse a long chat history.

Implementation session — what changes: Before session: - [ ] Add bio field to User model - [ ] Create migration for bio column - [ ] Include bio in GET /api/users/me - [ ] Allow PATCH /api/users/me to update bio After session: - [x] Add bio field to User model - [x] Create migration for bio column - [x] Include bio in GET /api/users/me - [ ] Allow PATCH /api/users/me to update bio ← still pending Touched files: user.py, schemas/user.py, migrations/0042_add_user_bio.py Unexpected touches: none

The checklist also serves as a natural session boundary. When the AI session ends mid-feature, the unchecked tasks tell the next session exactly where to resume. There is no "wait, where did we leave off?" — the state is explicit.

Phase 5 — Review: Closing the Loop

After implementation, the AI reviews what was built against what was specified. This is not a general code review — it is a spec compliance check. The reviewer reads the diff, compares against the spec scope, and answers three questions:

This review is faster and more precise than a general review precisely because the spec is the acceptance criteria. You are not asking "is this good code?" — you are asking "does this match what we agreed to build?" Those are different questions, and the second is answerable in minutes.

The Audit Trail as a Side Effect

One underrated consequence of spec-driven development is the audit trail it produces without any extra effort. After a few weeks, your .aikit-specs/done/ directory contains a complete history of every feature: what was intended, what was excluded, what was implemented, and when it was closed.

.aikit-specs/ ├── active/ │ └── SPEC-005-user-bio-field.md ├── draft/ │ └── SPEC-006-notifications.md └── done/ ├── SPEC-001-auth-refresh-token.md # closed 2025-03-12 ├── SPEC-002-product-pagination.md # closed 2025-03-18 ├── SPEC-003-order-status-webhook.md # closed 2025-03-25 └── SPEC-004-email-verification.md # closed 2025-04-01

This history answers questions that normally require archaeology: "why is this field nullable?", "when did we add this endpoint?", "was the rate limiting on this route intentional?". The spec that introduced each change has the answer — and unlike commit messages, it has the full context, including what was deliberately left out.

How Specs Improve AI Performance

Beyond process benefits, specs make the AI technically better at its job. LLMs work best with high signal-to-noise ratio context. A spec reduces noise in several ways:

When One Feature Per Spec Matters Most

The "one spec, one feature" constraint is the hardest discipline to maintain under deadline pressure. It is also the most important one. Specs that contain multiple features produce implementations that are harder to review, harder to revert, and harder to understand later.

The splitting heuristic: If a spec has more than six tasks, or if any two tasks could be delivered independently and both would provide value, split into two specs. The overhead of an extra spec is five minutes. The overhead of a muddled implementation that mixes two features can be days of untangling.

Spec-Driven in Practice: A Real Session

Here is what a complete spec-driven workflow looks like end-to-end, from description to close:

# 1. Draft — describe the feature in plain language (~30 seconds) $ ai-kit spec new "users should be able to reset their password via email. Send a reset link, validate the token, and allow setting a new password." ✓ SPEC-007 drafted → .aikit-specs/draft/SPEC-007-password-reset.md 6 tasks · 4 files expected # 2. Review the draft (~3 minutes) # Edit the spec if scope needs adjusting $ ai-kit spec show SPEC-007 # 3. Approve (~10 seconds) $ ai-kit spec approve SPEC-007 ✓ SPEC-007 approved and moved to draft/ # 4. Start — writes TASK.md to project root $ ai-kit spec start SPEC-007 ✓ TASK.md written # 5. Implement — open Claude Code, it reads TASK.md $ claude # or ai-kit tui → ✦ Claude button # ... AI works through the task list ... # ... marks tasks done as it goes ... # 6. Review — AI checks implementation against spec $ ai-kit spec review SPEC-007 ✓ All 6 tasks implemented ✓ No out-of-scope files modified ⚠ Missing: rate limiting on /api/auth/reset-password (security recommendation) # Fix the finding, then close $ ai-kit spec close SPEC-007 ✓ SPEC-007 closed → .aikit-specs/done/ ✓ TASK.md removed

What Spec-Driven Is Not

Spec-driven development is not a silver bullet, and it is not appropriate for every task. It adds overhead — typically ten to fifteen minutes per feature — that is not always justified. The cases where it pays off clearly:

For a two-line bug fix or a minor style change, a spec is overkill. For anything that will be in production for more than a week and touched by more than one person, the ten-minute investment consistently pays back.

Key Takeaways

Back to all articles