Spec-Driven Development (SDD): How to Use spec.md to Keep AI Coding Agents From Hallucinating
Why conversational prompting fails on large codebases, and how to use version-controlled spec.md files to anchor AI coding agents to strict architectural contracts.
If you use AI coding editors like Cursor, Windsurf, or Claude Code, you have likely witnessed the “agentic drift” disaster. You open Composer mode, type a three-sentence prompt asking to add a new checkout payment step, and press submit.
The agent scans your repo, edits seven files across four directories, refactors your user database schema without asking, and installs a deprecated payment SDK from 2022.
The issue isn’t that the model is incompetent. The issue is that conversational chat windows are the worst possible medium for communicating complex software architecture. Chat transcripts are ephemeral, unstructured, and suffer from severe context degradation as token counts rise.
In 2026, leading engineering teams have abandoned pure prompt-and-pray workflows in favor of Spec-Driven Development (SDD).
What is Spec-Driven Development (SDD) in AI pair programming?
To understand why SDD transforms software stability, compare the two execution workflows:
CONVERSATIONAL PROMPTING (Fragile & Ad-Hoc):
User Prompt -> AI Guesses Architecture -> Edits 8 Random Files -> Manual Untangling
SPEC-DRIVEN DEVELOPMENT (Deterministic & Contained):
spec.md Contract -> AI Confirms Boundaries -> Atomic File Edits -> Automated Verification
| Criteria | Ad-Hoc Conversational Prompting | Spec-Driven Development (SDD) |
|---|---|---|
| Source of Truth | Ephemeral, scrolling chat window | Version-controlled spec.md in repository |
| Scope Boundary | Undefined (AI edits whatever it wants) | Explicit whitelist of allowed and forbidden files |
| Data Contracts | Implicit assumptions about API shapes | Explicit Zod schemas and TypeScript interfaces |
| Session Memory | Resets every time you start a new chat | Persistent across days, weeks, and team members |
| Reproducibility | Zero (same prompt gives different code) | High (structured constraints anchor the agent) |
The Anatomy of a Production-Grade spec.md File
A specification for an AI agent should not read like a 40-page corporate document. It is a lean, highly structured contract designed for token efficiency and unambiguous parsing. Every robust spec.md contains four essential sections:
# Feature Specification: [Feature Name]
## 1. Intent & Context
- Why are we building this?
- What user problem does it solve?
- What tech stack primitives are available?
## 2. The Boundary Box (Crucial)
- Files allowed to modify: [List paths explicitly]
- Files FORBIDDEN to touch: [Config, auth handlers, package.json]
- External dependencies: Do NOT install any new npm libraries.
## 3. Data Contracts & Schemas
- Explicit input and output shapes defined with TypeScript/Zod.
## 4. Acceptance Criteria Checklist
- [ ] Criterion 1: Specific observable behavior
- [ ] Criterion 2: Error handling scenario
- [ ] Criterion 3: Automated test passing verification
How do you configure Cursor to follow a spec.md contract?
Here is the exact setup you can drop into your repository right now:
Step 1: Create Your .cursorrules Anchor
Add this rule to your root .cursorrules file to train Cursor’s AI models to respect specifications by default:
# Spec-Driven Development Guardrail
When an instruction references `@spec.md` or any feature specification:
1. Treat `spec.md` as the definitive, immutable source of truth.
2. Read the "Boundary Box" section first. Never modify, rename, or delete any file listed as FORBIDDEN.
3. If an implementation detail requires altering a forbidden file or installing new dependencies, HALT and request user approval before proceeding.
4. Implement tasks incrementally, verifying each item against the Acceptance Criteria checklist.
Step 2: The Practical Walkthrough: Building an API Webhook Handler
Let’s see an example in action. Suppose you need to build a Stripe webhook endpoint to update user subscription status. Instead of typing “Build me a Stripe webhook”, you create docs/specs/stripe-webhook.md:
# Feature: Stripe Subscription Webhook Handler
## Boundary Box
- Allowed to edit: `src/pages/api/webhooks/stripe.ts`
- Forbidden: `src/lib/auth.ts`, `src/config/database.ts`
- Libraries: Use existing `@stripe/stripe-js` already in package.json.
## Data Contract
Validate the incoming payload with this Zod schema:
```typescript
import { z } from 'zod';
export const StripeSubscriptionEventSchema = z.object({
type: z.enum(['customer.subscription.created', 'customer.subscription.deleted']),
data: z.object({
object: z.object({
customer: z.string(),
status: z.string(),
}),
}),
});
Acceptance Criteria
- Verify Stripe webhook signature using
STRIPE_WEBHOOK_SECRET - Reject invalid signatures with HTTP 400 and error message
- Parse valid events through
StripeSubscriptionEventSchema - Return HTTP 200 within 200ms of payload receipt
### Step 3: Triggering Cursor Composer
Open Cursor Composer (`Cmd+I` on macOS, `Ctrl+I` on Windows) and submit this prompt:
> *"Execute the tasks defined in `@docs/specs/stripe-webhook.md`. Implement `src/pages/api/webhooks/stripe.ts` strictly adhering to the Boundary Box and Acceptance Criteria. Do not touch any other files."*
Because the AI's attention heads are anchored to the specification file rather than random tokens in your chat history, it produces clean, surgically targeted code that compiles and passes tests on the very first attempt.
---
## The 3 Cardinal Rules of Spec-Driven Engineering
1. **Specs Are Versioned, Not Disposable:** Check your `spec.md` files into Git alongside your code. When a teammate or future AI agent needs to modify the feature six months later, the original intent and architectural boundaries remain documented.
2. **Never Let the AI Edit the Spec and the Code Simultaneously:** Write the spec first, review it yourself, and commit it. Then instruct the AI to code against it. Letting the AI write its own specification defeats the purpose of the constraint.
3. **Use the Spec as a Testing Blueprint:** Each item in your Acceptance Criteria checklist should map directly to an automated test case (using Vitest or Playwright). A feature is only finished when the automated tests verify every checkbox.
By adopting Spec-Driven Development, you eliminate the cognitive fatigue of prompt micromanagement and build software that remains robust as your codebase scales into tens of thousands of lines.Related Technical Guides
The Vibe Coding Hangover: Why Naive AI Codebases Collapse After 1,000 Lines (And How to Fix It)The Vibe Coding Hangover: Why Naive AI Codebases Collapse After 1,000 Lines (And How to Fix It)
Why rapid AI prompting creates fragile spaghetti code at scale, and the architectural principles needed to keep your vibe-coded application stable, secure, and maintainable.
What Is Vibe Coding? The Non-Programmer's Guide to Building Software with Natural LanguageWhat Is Vibe Coding? The Non-Programmer's Guide to Building Software with Natural Language
An honest, zero-jargon breakdown of vibe coding in 2026: how anyone can ship functional web apps using natural language, what tools to use, and where the limits lie.
Comments are powered by GitHub Discussions and will appear here once connected.