Spec-Driven Development
Write explicit behavioral and architectural contracts before any code to achieve predictable AI output.
What you'll accomplish
Create comprehensive specs that dramatically improve AI code generation quality.
Prerequisites
- Understanding of the execution loop
- Experience with AI coding assistants
Why Specs Transform AI Output
The difference between good and great AI output often isn't the model - it's the prompt quality. And the best prompt for a coding task is a spec.
Without a spec:
"Build a user authentication system"
The AI guesses at every decision point. Email? OAuth? Sessions? Tokens? Each guess has a 50% chance of being wrong.
With a spec:
"Implement auth per AUTH_SPEC.md:
- Email/password with bcrypt
- JWT tokens (15min) with refresh tokens (7d)
- Rate limiting: 5 attempts/minute
- Store sessions in Redis"
The AI executes. Decisions are already made.
The Three Layers of Specs
Layer 1: Behavioral Specs
What should happen from the user's perspective.
markdown# Feature: Shopping Cart ## User Stories **As a** shopper **I want to** add items to my cart **So that** I can purchase multiple items together ## Acceptance Criteria ### Adding Items - [ ] Clicking "Add to Cart" adds item with quantity 1 - [ ] If item exists, quantity increments - [ ] Cart badge updates with total item count - [ ] Toast notification confirms addition ### Updating Quantities - [ ] Quantity can be changed with +/- buttons - [ ] Quantity can be typed directly - [ ] Minimum quantity is 1 - [ ] Maximum is available inventory ### Removing Items - [ ] "Remove" button removes item completely - [ ] Swipe left on mobile triggers remove - [ ] Confirmation only for quantities > 1 ## Edge Cases - Empty cart shows "Your cart is empty" state - Out-of-stock items show disabled "Add to Cart" - Price changes update in real-time - Guest carts merge on login
Layer 2: Architectural Specs
How should it be built.
markdown# Architecture: Shopping Cart Service ## Component Structure
src/ ├── features/cart/ │ ├── components/ │ │ ├── CartItem.tsx │ │ ├── CartSummary.tsx │ │ └── CartBadge.tsx │ ├── hooks/ │ │ └── useCart.ts │ ├── services/ │ │ └── cartService.ts │ └── types/ │ └── cart.ts
## Data Flow
1. User clicks "Add to Cart"
2. `useCart.addItem()` called
3. Optimistic update to local state
4. API call to `/api/cart/items`
5. On success: confirm state
6. On failure: rollback + show error
## State Management
- Use Zustand for cart state
- Persist to localStorage for guests
- Sync with server for logged-in users
- Cart ID stored in cookie (httpOnly)
## API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/cart | Get current cart |
| POST | /api/cart/items | Add item |
| PATCH | /api/cart/items/:id | Update quantity |
| DELETE | /api/cart/items/:id | Remove item |
Layer 3: Technical Constraints
Non-functional requirements and boundaries.
markdown# Constraints: Shopping Cart ## Performance - Cart operations complete in < 200ms - Cart loads in < 100ms (cached) - No layout shift on quantity changes ## Security - Cart ID is UUID, not sequential - Rate limit: 100 operations/minute - Validate item existence server-side ## Compatibility - Works without JavaScript (SSR fallback) - Supports offline mode (queue operations) - Mobile responsive (touch-friendly) ## Integration Boundaries - Cart service talks only to Inventory service - Pricing handled by Pricing service - No direct database access - API only
Writing Effective Specs
Be Explicit, Not Implicit
Bad:
Handle errors appropriately
Good:
Error Handling:
- Network errors: Show toast, enable retry
- Validation errors: Inline field messages
- Auth errors: Redirect to login
- 500 errors: Log to Sentry, show generic message
Include the "Why"
Bad:
Use Redis for sessions
Good:
Use Redis for sessions because:
- Fast reads for auth middleware
- Built-in TTL for session expiry
- Cluster support for horizontal scaling
Note: Not SQLite - need distributed access
Reference Existing Code
Bad:
Follow existing patterns
Good:
Follow patterns from:
- Auth: See src/services/auth/authService.ts
- API routes: See src/pages/api/users/[id].ts
- Error handling: See src/lib/errors/ApiError.ts
Spec Templates
API Endpoint Spec
markdown# Endpoint: POST /api/cart/items ## Request ```json { "productId": "prod_123", "quantity": 2, "variant": "blue-xl" }
Success Response (201)
json{ "cartId": "cart_456", "item": { "id": "item_789", "productId": "prod_123", "quantity": 2, "price": 29.99, "total": 59.98 }, "cartTotal": 59.98 }
Error Responses
- 400: Invalid quantity (< 1)
- 404: Product not found
- 409: Insufficient inventory
- 429: Rate limited
Notes
- Creates cart if none exists
- Idempotent for same product/variant
- Price calculated at add time
### Component Spec
```markdown
# Component: CartItem
## Props
```typescript
interface CartItemProps {
item: CartItem;
onQuantityChange: (id: string, qty: number) => void;
onRemove: (id: string) => void;
disabled?: boolean;
}
Behavior
- Displays product image, name, price
- Quantity controls (+/- buttons)
- Remove button (trash icon)
- Shows line total (qty × price)
States
- Default: Interactive
- Loading: Disable controls, show spinner
- Error: Show error message, enable retry
- Removing: Fade out animation
Accessibility
- Quantity input is number type
- Remove button has aria-label
- Focus management on quantity change
## The Spec Workflow
- Think → 2. Spec → 3. Review → 4. Execute ↑__________________________|
### 1. Think
Before writing the spec:
- What problem are we solving?
- What are the constraints?
- What already exists?
### 2. Spec
Write the spec with:
- User perspective (behavioral)
- Developer perspective (architectural)
- Constraints and boundaries
### 3. Review
Before implementation:
- Does the spec answer all questions?
- Are edge cases covered?
- Is it actionable?
### 4. Execute
Give the spec to the AI:
Implement the shopping cart feature per CART_SPEC.md. Start with the CartItem component.
## Common Mistakes
<InlineFAQ>
<FAQItem question="Specs feel like extra work">
Compare:
- 30 minutes writing a spec
- 3 hours of AI back-and-forth without one
Specs front-load thinking. That thinking happens anyway - specs just make it explicit.
</FAQItem>
<FAQItem question="How detailed should specs be?">
Detailed enough that you wouldn't need to answer follow-up questions.
If you're clarifying the same things repeatedly, add them to the spec.
</FAQItem>
<FAQItem question="What if requirements change?">
Update the spec first. Then ask the AI to update the implementation.
The spec becomes the source of truth. Changes are tracked. Decisions are documented.
</FAQItem>
<FAQItem question="Should I spec everything?">
Focus on:
- New features
- Complex changes
- Integrations
- Anything you'll revisit later
Skip for:
- Bug fixes with clear reproduction
- Trivial changes
- Explorations/spikes
</FAQItem>
</InlineFAQ>
<VerifyChecklist
items={[
"Write a behavioral spec with acceptance criteria",
"Create an architectural spec with data flow",
"Document technical constraints",
"Explain why specs improve AI output"
]}
/>
---
**Next:** Creating long-lived system documents.