Skip to content

Spec-Driven Development — The AI-Native Engineering Workflow I Use Daily

Posted on:February 10, 2025 at 10:00 AM

In early 2025, I changed how I build software. Not the stack, not the language — the process. I stopped starting with code and started starting with specifications.

I call it Spec-Driven Development (SDD). It’s not a formal methodology with a website and a certification program. It’s a workflow I’ve converged on after using AI coding tools daily for two years. And it’s made me measurably more productive on both sides of the ledger: I ship faster and I write better code.

Table of contents

Open Table of contents

What SDD Is (and Isn’t)

Spec-Driven Development is not:

SDD is: writing a precise, unambiguous specification of what you want to build before writing a single line of code, then using that spec as the source of truth for AI-assisted implementation, testing, and review.

The key insight: the quality of AI output is bounded by the quality of your spec, not by the AI’s capability. A vague request gets vague output. A precise spec gets precise implementation.

The Spec Format

A good SDD spec has four sections:

1. Context (1-3 sentences)

What is this? Where does it fit? What already exists?

Context: This is a REST endpoint handler for the transaction validation service.
It sits between the API gateway and the PostgreSQL write path. The service currently
has handlers for /transactions/create and /transactions/list.

2. Requirements (numbered, testable)

What must the implementation do? Each requirement should be independently testable.

Requirements:
1. Accept POST /transactions/validate with JSON body
2. Validate: amount (positive number), currency (ISO 4217 code), merchant_id (UUID)
3. Return 422 with field-level errors if validation fails (don't stop on first error)
4. Return 200 with a validation token (UUID) if valid — token expires in 5 minutes
5. Store the validation token in Redis with TTL=300
6. Rate limit: max 10 validate requests per IP per minute (return 429 if exceeded)
7. Log all requests with: ip, currency, amount_bucket (0-100, 100-1000, 1000+), latency_ms

3. Constraints (technology, non-functional)

What must the implementation use or avoid? Performance bounds?

Constraints:
- Use existing Express.js middleware stack (types in src/middleware/types.ts)
- Redis client is already initialized in src/lib/redis.ts
- Response time P99 < 50ms (validation is sync, Redis is local)
- No external API calls in the hot path
- TypeScript strict mode (no any)

4. Non-requirements (explicit exclusions)

What is explicitly out of scope? This prevents over-engineering.

Non-requirements:
- Do NOT implement the actual transaction processing (separate service)
- Do NOT add authentication (handled by API gateway before this service)
- Do NOT add database writes (validation only, storage happens post-validation)

A Real Example: 30 Minutes from Spec to Merged PR

Here’s a spec I wrote recently for a financial data normalization function:


Context: Python function to normalize time series data from the ECB reporting format. Input comes from CSV exports with inconsistent decimal separators (comma vs period), negative values encoded as trailing minus (123-), and dates in both YYYY-MM-DD and DD/MM/YYYY format.

Requirements:

  1. Accept a pandas Series of mixed-format strings representing numeric values
  2. Normalize decimal separator: both 1.234,56 (European) and 1,234.56 (US) → 1234.56
  3. Normalize trailing minus: 1234--1234
  4. Return float64 Series; non-parseable values → NaN (no exceptions)
  5. Vectorized implementation (no .apply() or loops)
  6. Accept a pandas Series of date strings (mixed formats) → datetime64[D] Series

Constraints: NumPy and pandas only (no regex library, no external parsers). P50 < 5ms for 10k values.

Non-requirements: Do not handle times/timezones. Do not validate values against business rules.


I gave this spec to Claude with the instruction “implement, then write pytest tests that verify each requirement”. The output:

Total time from spec to tests passing: 28 minutes. Without SDD (starting with code): I’d estimate 90-120 minutes with similar test coverage.

Why SDD Works: The Cognitive Science

Writing forces clarity. The act of writing a spec makes you confront ambiguities you’d otherwise defer until they become bugs. “Validate amount (positive number)” — wait, should I allow zero? What’s the maximum? The spec forces these questions before implementation.

AI is better at translation than design. AI tools excel at translating a clear specification into code. They struggle with open-ended design questions (“should I use Redis or in-memory cache here?”). SDD plays to AI’s strengths.

Specs are durable, prompts aren’t. The spec lives in your repository. When a bug surfaces three months later, you have the specification to check against. A prompt you typed in a chat window is gone.

Requirements become tests. Each numbered requirement directly maps to a test. Requirement 4 → test that non-parseable values return NaN. The spec is the test spec.

The Workflow: Step by Step

1. Write spec (15-30 minutes)
→ forces clarity, surfaces design questions
2. Review spec with a colleague or rubber duck (5 minutes)
→ "does this make sense to someone else?"
3. Paste spec to Claude/Copilot/Cursor with:
"Implement this spec. Then write tests that verify each requirement."
(5 minutes)
4. Review the generated code against your spec
→ does it satisfy each requirement? Any edge cases missed?
(10-20 minutes)
5. Iterate on spec if requirements were wrong/incomplete
→ update spec FIRST, then regenerate
(5-10 minutes)
6. Commit: spec (in /specs or /docs), implementation, tests
→ spec is the documentation

SDD vs TDD

The comparison everyone makes. Key differences:

TDDSDD
What you write firstTestsSpecification
AI roleAI completes implementation between red/greenAI generates implementation + tests from spec
Design focusTest-driven designExplicit design before any code
Iteration unitSingle test → single behaviorEntire feature spec
Best forSmall, incremental changesNew features, endpoints, services

SDD and TDD are compatible. After SDD generates the implementation and tests, you’re at “green” in TDD. You can then add edge-case tests iteratively (TDD style) for the behaviors the AI missed.

When Not to Use SDD

SDD has overhead — writing a good spec takes 15-30 minutes. It’s not worth it for:

SDD shines for anything that would take >1 hour to implement “from scratch” — new API endpoints, data transformation pipelines, state machine implementations, complex validation logic.

The Meta-Point

The reason SDD improves output quality isn’t the AI. It’s that writing a precise specification makes you think more carefully about what you’re building.

Engineers who’ve adopted SDD consistently report the same experience: “I write fewer bugs now, not because the AI catches them, but because writing the spec forces me to think through the edge cases before I’ve written any code.”

The AI is a multiplier on clarity. SDD provides the clarity.

I track my implementation time for features I’ve built SDD vs non-SDD over the past year. SDD features average 40% less time to first passing tests and 60% fewer bug reports in the first two weeks. The sample size isn’t statistically rigorous — but the direction is consistent enough that I can’t imagine going back.

Write the spec first. The code will follow.