Spec-Driven Engineering: The Missing Discipline for AI-Assisted Development
A practical guide to writing specifications that produce reliable, production-quality code from AI coding tools. Move beyond vibe coding with structured specs that capture intent, constraints, and acceptance criteria — the discipline that separates AI-assisted prototypes from AI-assisted production systems.

AI coding tools have changed the economics of software development. Claude, Cursor, GitHub Copilot, and their successors can generate hundreds of lines of working code in seconds. The bottleneck has shifted — it is no longer writing code that limits delivery speed, but knowing what code to write. And this is precisely where most teams are failing.
The dominant approach to AI-assisted development in 2026 is what the industry has started calling "vibe coding" — feeding an AI tool a loose description of what you want, iterating on the output through conversational prompts, and accepting the result when it looks roughly correct. It works brilliantly for prototypes, one-off scripts, and learning exercises. It fails catastrophically for production systems, and the failure mode is insidious: the code works today, passes a cursory review, and breaks in ways that only surface under real-world conditions weeks or months later.
Spec-driven engineering is the discipline that bridges this gap. It is not a new idea — specifications have been part of software engineering since before most of today's developers were born. What is new is the context: AI tools that can execute specifications with remarkable fidelity, if and only if those specifications are written with sufficient precision. The specification has become the primary unit of engineering work, replacing the code itself as the artifact that demands the most thought and care.
This guide provides a practical framework for writing specifications that produce reliable, production-quality code from AI coding tools. It is aimed at engineering leaders and senior developers who are moving past the initial excitement of AI-assisted development and confronting the harder question: how do we make this work at scale, consistently, for production systems that need to be maintained for years?
The Problem with Vibe Coding
Vibe coding is not a pejorative — it describes a real and useful workflow. You describe what you want in natural language, the AI generates code, you try it, and you adjust through conversation. For exploratory work and rapid prototyping, this is genuinely faster than writing code manually. The problem arises when teams apply this workflow to production engineering and mistake speed-to-first-output for speed-to-reliable-system.
The failure modes of vibe coding are well-documented and predictable:
- Ambiguity compounds. A vague prompt produces code that makes assumptions. Those assumptions are invisible in the generated output — they look like deliberate design decisions. Downstream code builds on those assumptions. By the time the wrong assumption surfaces, it is embedded across multiple components and expensive to correct.
- Context window limits create drift. AI tools have finite context. In a long conversational session, early constraints are forgotten or de-prioritised as new instructions arrive. The result is code that satisfies the most recent prompt but contradicts earlier requirements — a form of specification decay that is invisible without a stable reference document.
- Edge cases are systematically ignored. Natural language prompts naturally describe the happy path. AI tools optimise for satisfying the prompt, not for identifying what the prompt fails to mention. Error handling, boundary conditions, concurrency, and failure modes are consistently underspecified in conversational workflows, producing code that works for the demo and fails for the edge case.
- Consistency degrades across sessions. A specification provides a stable reference point across multiple implementation sessions. Without one, each session starts from a slightly different understanding of the system, producing inconsistent patterns, naming conventions, and architectural choices that accumulate into maintenance burden.
- Review becomes impossible. Code review depends on understanding intent — what was this code supposed to do? Without a specification, reviewers can only assess whether the code looks reasonable, not whether it satisfies requirements that were never written down. This is why AI-generated code passes review at high rates but generates disproportionate bug reports downstream.
These are not theoretical concerns. Our work with AI-native engineering teams consistently reveals the same pattern: teams that adopted AI coding tools without changing their specification practices initially accelerated, then hit a wall at around the 3–6 month mark when accumulated technical debt from underspecified AI-generated code began consuming more time than the tools were saving.
⚠️The irony of vibe coding is that it undermines the very advantage AI tools provide. AI is excellent at translating precise intent into correct implementation. It is poor at inferring intent from vague descriptions. Vibe coding asks AI to do what it is worst at — guess what you mean — instead of leveraging what it is best at — executing exactly what you specify.
What Makes a Specification AI-Ready
An AI-ready specification is not the same as a traditional software requirements document. Traditional specs were written for human developers who could fill gaps with domain knowledge, ask clarifying questions, and make reasonable inferences from context. AI tools can do none of these things reliably. An AI-ready specification must be self-contained, precise, and structured in a way that eliminates ambiguity about both the what and the how.
The core components of an AI-ready specification are:
1. Context and Constraints
Every specification must begin with the information the AI needs to make correct architectural and implementation decisions. This is not a project overview — it is a focused declaration of the constraints that bound the implementation space.
- Technology stack: Not just "React" but the specific version, styling approach, state management library, and any project conventions. AI tools will default to their training distribution — if your project uses Zustand and the AI's training data is dominated by Redux, you will get Redux patterns unless you specify otherwise.
- Existing patterns: Reference specific files that demonstrate how similar problems have been solved in this codebase. "Follow the pattern in src/api/users.ts" is more effective than "use the project's established API pattern" because the AI can read the file and match its structure exactly.
- Non-functional requirements: Performance budgets, accessibility standards, security requirements, and compliance constraints that the implementation must satisfy. These are the requirements most likely to be ignored by vibe coding because they are not visible in functional behaviour.
- What not to do: Explicit anti-patterns are as valuable as positive guidance. "Do not add a new npm dependency for this" or "Do not modify the shared database schema" prevent the AI from taking shortcuts that create downstream problems.
2. Functional Requirements
Functional requirements describe what the code must do, specified at a level of detail that leaves no room for interpretation. The test for adequacy is: could two competent developers independently implement this specification and produce functionally equivalent code?
- Input/output contracts: Exact types, shapes, and validation rules for every input the system accepts and every output it produces. Include examples of valid and invalid inputs with the expected response for each.
- State transitions: If the feature involves state changes (user flows, data processing pipelines, multi-step operations), enumerate every valid state and the transitions between them. State machines are specifications; flowcharts are hand-waving.
- Error handling: Every failure mode that the code must handle, with the specific response for each. "Handle errors gracefully" is not a specification. "If the API returns 429, retry with exponential backoff starting at 1 second, maximum 3 attempts, then return a structured error with code RATE_LIMITED" is.
- Boundary conditions: Empty arrays, null values, maximum sizes, concurrent access, Unicode edge cases, timezone handling. These are the conditions that vibe coding never addresses and production traffic always exercises.
3. Acceptance Criteria
Acceptance criteria translate requirements into verifiable conditions. They serve a dual purpose: they tell the AI what "done" looks like, and they give the reviewer a checklist for validation.
- Behavioural criteria: "When a user submits the form with an invalid email, a validation error appears below the email field within 100ms, the field is highlighted in red, and form submission is prevented."
- Technical criteria: "The endpoint responds in under 200ms at p95 for payloads up to 10KB. Database queries use the existing user_email index. No new npm dependencies are introduced."
- Negative criteria: "The feature must not break existing API contracts. No console.log statements in production code. No inline styles."
💡Write acceptance criteria before writing the specification body. If you cannot articulate what "done" looks like, you do not yet understand the problem well enough to specify a solution. This practice also prevents scope creep — if an acceptance criterion was not in the original list, the feature it validates belongs in a separate specification.
4. Architecture Decisions
For any non-trivial feature, the specification should document the key architectural decisions and the reasoning behind them. This is where senior engineering judgment is encoded — the decisions that an AI tool cannot make on its own because they depend on context that extends beyond the immediate feature.
- Where the code lives: Which files to create, which to modify, and the directory structure. AI tools left to choose their own file organisation will create structures that make sense in isolation but conflict with the project's existing conventions.
- Data flow: How data moves through the system — from user input through validation, processing, storage, and response. This is the skeleton that the AI fleshes out with implementation detail.
- Integration points: How this feature connects to existing systems. Which interfaces it consumes, which it exposes, and the contract at each boundary. Unclear integration points are the primary source of AI-generated code that works in isolation but breaks the system.
Anatomy of an Effective Specification
Theory is useful; examples are better. The following section walks through the structure of a specification that consistently produces high-quality AI-generated code, with commentary on why each section matters and what happens when it is omitted.
The Header Block
Every specification starts with a concise header that establishes scope and context in a format the AI can process without ambiguity:
- Feature name and one-sentence description
- Target files (create/modify) with full paths
- Dependencies — other features, services, or data that must exist
- Out of scope — explicit boundaries on what this specification does not cover
The "out of scope" declaration is counterintuitively the most important part of the header. Without it, AI tools will helpfully implement related functionality that was never requested, creating code that has to be reviewed, tested, and maintained without having been planned. A clear boundary — "this specification covers the API endpoint only; the frontend form is a separate specification" — prevents this scope expansion.
The Contract Section
The contract section defines the interfaces: what goes in, what comes out, and the rules governing both. For API endpoints, this means request/response schemas. For UI components, this means props, events, and rendered output. For data processing, this means input formats, transformation rules, and output formats.
Effective contracts include:
- TypeScript type definitions for all inputs and outputs
- Validation rules with specific error messages for each violation
- Example payloads showing both valid and invalid cases
- HTTP status codes and response shapes for API endpoints
The key insight is that contracts should be specified in the language the AI will use to implement them. TypeScript type definitions are more precise than prose descriptions and directly translatable to code. If you are working in Python, use Pydantic models. In Go, use struct definitions. Let the type system carry the specification rather than relying on natural language descriptions that the AI must interpret.
The Behaviour Section
The behaviour section describes what happens in each scenario the code will encounter. This is where most specifications fail — they describe the happy path in detail and hand-wave at everything else.
Structure this section as a decision tree or scenario list:
- Happy path — the standard flow when all inputs are valid and all dependencies are available
- Validation failures — what happens for each category of invalid input
- Dependency failures — what happens when external services, databases, or APIs are unavailable
- Concurrency — what happens when multiple requests arrive simultaneously
- Idempotency — what happens when the same request is sent twice
- Authorisation — what happens for unauthenticated requests, insufficient permissions, and expired tokens
Each scenario should specify the exact outcome: the return value, the side effects (database writes, events emitted, logs generated), and the state changes. "Return an error" is not a specification. "Return HTTP 409 with body { error: 'DUPLICATE_ENTRY', message: 'A record with this email already exists', field: 'email' } and do not modify the database" is a specification.
The Testing Section
Specifications should include a testing strategy, not as an afterthought but as an integral part of the design. The testing section tells the AI what tests to write alongside the implementation, ensuring that the generated code is testable by design rather than requiring retrofitted tests.
- Unit test scenarios — one per behaviour from the behaviour section
- Integration test boundaries — which external dependencies to mock and which to test against
- Edge case test data — specific values that exercise boundary conditions
- Performance test criteria — load levels and latency thresholds if applicable
A specification without acceptance criteria is a wish. A specification without test scenarios is a hope. Production systems are built on neither.
Specification Patterns for Common Work Types
Different types of engineering work require different specification structures. A one-size-fits-all template creates friction — the overhead of specifying a simple utility function at the same level as a payment processing pipeline is not a worthwhile trade. The following patterns calibrate specification depth to task complexity.
Pattern 1: API Endpoint Specification
API endpoints are the highest-value target for spec-driven AI development because they have well-defined contracts and deterministic behaviour. A complete API endpoint specification includes:
- HTTP method, path, and authentication requirements
- Request schema with types, validation rules, and examples
- Response schemas for each status code (200, 400, 401, 404, 409, 500)
- Database operations — which tables are read/written, which indexes are used
- Side effects — events emitted, notifications sent, caches invalidated
- Rate limiting and throttling behaviour
- Idempotency guarantees
This pattern produces the most reliable AI output because every decision point is specified. The AI has no opportunity to make assumptions because the specification answers every question the implementation would raise.
Pattern 2: UI Component Specification
UI components are harder to specify because they involve visual layout, interaction behaviour, and responsive adaptation that are difficult to describe precisely in text. The specification should focus on behaviour and contracts rather than appearance:
- Props interface with TypeScript types and default values
- Events emitted and their payloads
- States — loading, error, empty, populated, disabled
- Accessibility requirements — ARIA attributes, keyboard navigation, screen reader behaviour
- Responsive breakpoints and how the component adapts at each
- Animation specifications — trigger, duration, easing, and interruptibility
For visual appearance, reference existing components rather than describing styles: "Follow the card pattern from src/components/ServiceCard.tsx" is more reliable than "rounded corners with a subtle shadow." The AI can read the referenced file and match its visual approach exactly.
Pattern 3: Data Migration Specification
Data migrations are high-risk operations where specification precision directly prevents data loss. The specification must cover:
- Source schema and destination schema with field-level mapping
- Transformation rules for each field that changes type or format
- Handling of null values, missing data, and invalid records
- Rollback procedure — how to reverse the migration if it fails partway through
- Verification queries — SQL or ORM queries that confirm the migration succeeded
- Performance constraints — maximum execution time, batch sizes, connection pool limits
Pattern 4: Refactoring Specification
Refactoring specifications must answer a question that AI tools consistently get wrong when left to infer: what is the scope of this change? Without explicit boundaries, an AI asked to "refactor the authentication module" will refactor everything it can reach, creating a sprawling changeset that is impossible to review.
- Exactly which files will be modified and which will not
- The specific pattern being replaced and the pattern replacing it
- External contract preservation — which interfaces must not change
- Incremental verification — how to confirm correctness after each step
For teams applying Amdahl's Law to their AI engineering workflow, specification writing is the serial bottleneck that determines the overall speedup AI tools can deliver. The time invested in writing a precise specification is the non-parallelisable work — but it is also the work that determines whether the parallelised execution (AI code generation) produces value or waste.
The Specification Workflow in Practice
Writing specifications is not a waterfall practice grafted onto an agile workflow. Done well, it is a lightweight, iterative discipline that takes less time than the debugging and rework it prevents. The following workflow integrates specification writing into a modern development cycle without adding ceremony.
Step 1: Understand Before You Specify
Before writing a specification, ensure you understand the problem space well enough to constrain it. This is the step most often skipped — engineers jump from a ticket description to a specification without investigating the codebase, the existing patterns, or the edge cases that the ticket implicitly requires.
- Read the existing code in the area you are modifying — understand the patterns, naming conventions, and architectural assumptions
- Identify the integration points — what existing code will call your new code, and what your new code needs to call
- List the edge cases — not just the ones you can think of, but the ones that the existing codebase already handles for similar operations
- Check for constraints — performance budgets, security policies, accessibility standards, and compliance requirements that apply to this area
This investigation typically takes 15–30 minutes and prevents hours of rework caused by specifications built on incorrect assumptions about the existing system.
Step 2: Draft the Specification
Write the specification using the pattern appropriate for the work type. Start with the acceptance criteria — what does "done" look like? — and work backwards to the implementation details.
Keep the specification focused on a single deliverable. If you find yourself writing "and then" or "additionally," you likely have two specifications that should be written and implemented separately. Small, focused specifications produce better AI output than large, comprehensive ones because they stay within the context window and provide consistent guidance throughout the implementation.
💡The ideal specification fits in a single context window — roughly 3,000 to 5,000 words for current AI tools. If your specification is longer, decompose it into smaller units. Each unit should be independently implementable and testable.
Step 3: Validate Before Executing
Before handing the specification to an AI tool, validate it against three criteria:
- Completeness — does it cover the happy path, error cases, edge cases, and acceptance criteria?
- Precision — are there any sentences that could be interpreted in more than one way?
- Testability — for each requirement, is there a clear way to verify it was implemented correctly?
A useful technique is the "new joiner test": could a competent developer who has never seen this codebase implement the specification without asking questions? If not, the specification has gaps that an AI tool will fill with assumptions.
Step 4: Execute with Checkpoints
Feed the specification to your AI tool and implement in stages, not all at once. For a typical feature, this means:
- Implement the data layer (types, schemas, database operations) and verify
- Implement the business logic and verify against the behaviour specification
- Implement the interface layer (API endpoint, UI component) and verify
- Run the test suite and verify acceptance criteria
Each checkpoint is an opportunity to catch drift — places where the AI's implementation diverges from the specification. Catching drift early, when it affects a single layer, is dramatically cheaper than catching it after the entire feature is built on a flawed foundation.
Step 5: Review Against the Specification
Code review for AI-generated code should be specification-driven. The reviewer's primary question is not "does this code look reasonable?" but "does this code satisfy the specification?" This changes the review from a subjective assessment to an objective verification — and it produces significantly more useful review feedback. Teams building production AI systems find that specification-driven review catches 3–5x more defects than traditional review of AI-generated code, because the reviewer has a reference document against which to verify every decision.
Measuring Specification Effectiveness
Spec-driven engineering is an investment, and like any investment, it should be measured. The following metrics help teams calibrate their specification practice — identifying where to invest more detail and where the current level of specification is sufficient.
First-Pass Acceptance Rate
What percentage of AI-generated implementations pass review and testing without modification? This is the primary measure of specification quality. A well-specified feature should achieve 70–80% first-pass acceptance. Below 50% indicates systematic specification gaps. Above 90% may indicate over-specification — more detail in the specification than the feature's risk level warrants.
Rework Ratio
For features that require modification after initial generation, how much of the code is rewritten? A rework ratio above 30% suggests the specification failed to communicate critical constraints. Track which sections of the code are most frequently reworked — this reveals the specification dimensions that need the most improvement for your team and codebase.
Defect Origin Analysis
When bugs are found in AI-generated code, trace them to their root cause: was the specification ambiguous, incomplete, or incorrect? Or did the AI tool fail to follow a clear specification? This distinction is critical for improvement — specification defects are solved by improving your specification practice, while AI tool defects are solved by changing tools, models, or prompting strategies.
Specification-to-Implementation Time
Track the total time from starting a specification to having a reviewed, merged implementation. Compare this to the pre-specification baseline. Teams typically see a short-term slowdown (2–4 weeks as the practice is established) followed by a sustained 30–50% improvement in total delivery time, driven primarily by reduced rework and faster reviews.
ℹ️Do not measure specification writing time in isolation. A specification that takes two hours to write but prevents eight hours of debugging and rework is a net gain of six hours. Measure the full cycle: specification + implementation + review + testing + bug fixes.
Adopting Spec-Driven Engineering Across Your Organisation
Individual specification practice produces individual benefits. Organisational adoption produces compounding benefits — shared specifications create shared understanding, reusable patterns, and a searchable archive of architectural decisions that new team members can learn from and AI tools can reference.
Start with High-Risk Work
Do not mandate specifications for all development work simultaneously. Start with the work where under-specification causes the most damage: API endpoints that external systems depend on, data migrations, security-sensitive features, and components that multiple teams consume. These areas justify the specification investment immediately and provide visible wins that build adoption momentum.
Build a Specification Library
As specifications accumulate, they become a valuable reference library. New specifications can reference existing ones — "follow the pagination pattern established in spec-user-list-endpoint" — reducing the effort of specification writing while ensuring consistency. This library also serves as onboarding material: new engineers learn the codebase's patterns and conventions by reading specifications, not by reverse-engineering production code.
Integrate with Your AI Toolchain
Modern AI coding tools support project-level context files (CLAUDE.md, .cursorrules, and similar) that are automatically included in every interaction. Your specification patterns, project conventions, and architectural constraints should live in these files, providing a persistent context layer that reduces the specification burden for individual features.
This is where specification discipline intersects with AI governance. The specifications your team writes, the patterns they encode, and the constraints they enforce are a form of governance — they determine what AI tools can and cannot do within your codebase. Organisations with mature specification practices find that AI governance becomes a natural extension of their engineering practices rather than a separate compliance burden.
Evolve the Practice
Specification practice is not static. As AI tools improve, the level of detail required in specifications will change. Today, you need to specify error handling in detail because AI tools handle it poorly by default. In two years, you may be able to specify error handling at a higher level of abstraction because the tools have improved. Regularly review your specification templates and adjust the level of detail based on observed first-pass acceptance rates and defect patterns.
Addressing Common Objections
"Writing specs takes too long — the whole point of AI is going faster"
This objection confuses speed-to-first-output with speed-to-production. AI tools produce first outputs extremely fast regardless of specification quality. The time difference is in what happens next: specified features go through review, testing, and deployment in hours. Unspecified features go through cycles of debugging, rework, and re-review that take days. The specification takes 30–60 minutes to write. The rework it prevents takes 4–8 hours on average. The maths is straightforward.
"Our codebase is too complex to specify — engineers need to explore"
Exploration and specification are complementary, not competing activities. The investigation step in the specification workflow is exactly this exploration — understanding the codebase before specifying what to build. The difference is that the exploration's findings are captured in a specification rather than held in the developer's head, making them reviewable, referenceable, and available to the AI tool.
"Specifications become outdated the moment the code changes"
Specifications are not living documentation — they are point-in-time design documents, like architectural blueprints. The specification describes the intent at the time of implementation. If the implementation needs to change later, a new specification describes the change. The original specification remains valuable as a historical record of why the code was written the way it was.
"We tried specifications before AI tools and they didn't work"
Traditional specifications often failed because the effort of writing them was not proportional to the benefit of having them. AI tools change this equation dramatically. A specification that took three hours to write and saved one hour of implementation time was a poor investment. A specification that takes one hour to write and enables an AI tool to produce three hours of correct implementation is an excellent one. The economics have inverted.
Getting Started: Your First Spec-Driven Feature
The best way to adopt spec-driven engineering is to try it on a single feature and compare the results to your current workflow. Choose a feature that is representative of your typical work — not too simple, not too complex — and apply the specification workflow described above.
- Pick a feature from your current sprint that would normally take 1–2 days to implement
- Spend 45–60 minutes writing a specification following the relevant pattern from this guide
- Implement the feature using your AI coding tool with the specification as the primary input
- Compare the first-pass acceptance rate, review time, and total delivery time to your baseline
- Document what the specification captured that a conversational prompt would have missed
Most teams find that the first specified feature takes slightly longer end-to-end than their current workflow — the specification writing time is not yet offset by reduced rework because the team is still learning the practice. By the third or fourth specified feature, the efficiency gain becomes clear, and by the tenth, the team cannot imagine working any other way.
For organisations that want structured guidance on adopting spec-driven engineering practices — including specification templates, team training, and integration with your existing AI toolchain — our AI-native engineering service includes specification practice as a core capability. We help teams move from vibe coding to spec-driven development in weeks, not months, by embedding the practice in real project delivery rather than abstract training.
If you are evaluating whether spec-driven engineering is the right investment for your team's current situation, book a conversation and we will give you an honest assessment based on your team's size, AI maturity, and the types of systems you build. Not every team needs formal specifications for every feature — the goal is calibrated rigour, not bureaucratic process.
The organisations that will thrive in the AI-assisted development era are not those with the best AI tools. They are those with the best specifications. Tools are commoditising. The ability to precisely articulate what you want built — and verify that it was built correctly — is the durable competitive advantage.
Want a second opinion on your AI initiative?
30-minute sanity check call. No pitch, no slides.
Book your call →Newsletter
This is where I share what I can't post publicly.
AI strategy for UK scale-ups. Monthly. No fluff.
Subscribe to Beyond Growth →