Skip to content

Specification-Driven Development (SDD)

The Power Inversion

For decades, code has been king. Specifications served code -- they were scaffolding we built and discarded once the "real work" of coding began. We wrote PRDs to guide development, created design docs to inform implementation, drew diagrams to visualize architecture. But these were always subordinate to the code itself. Code was truth. Everything else was, at best, good intentions. As the codebase moved forward, specs rarely kept pace. And because the asset and its implementation are one and the same, building a parallel implementation without starting from the existing code is difficult.

Spec-Driven Development (SDD) inverts this power structure. Specifications do not serve code -- code serves specifications. The Product Requirements Document (PRD) is not a guide for implementation; it is the source that generates implementation. Technical plans are not documents that inform coding; they are precise definitions that produce code. This is not an incremental improvement to how we build software. It is a fundamental rethinking of what drives development.

The gap between specification and implementation has plagued software development since its inception. We have tried to bridge it with better documentation, more detailed requirements, stricter processes. These approaches fail because they accept the gap as inevitable. They try to narrow it but never eliminate it. SDD eliminates the gap by making specifications -- and the concrete implementation plans derived from them -- executable. When specifications and implementation plans generate code, there is no gap, only transformation.

This transformation is now possible because AI can understand complex specifications and produce detailed implementation plans from them. But raw AI generation without structure produces chaos. SDD provides that structure through specifications and implementation plans that are precise, complete, and unambiguous enough to generate working systems. The specification becomes the primary artifact. Code becomes its expression in a particular language and framework.

In this new world, maintaining software means evolving specifications. The intent of the development team is expressed in natural language ("intent-driven development"), design assets, core principles, and other guidelines. The lingua franca of development moves to a higher level, and code is the last-mile artifact.

Debugging means fixing the specifications and implementation plans that generate incorrect code. Refactoring means restructuring specifications for clarity. The entire development workflow reorganizes around specifications as the central source of truth, with implementation plans and code as continuously regenerated output. Adding new features or creating a parallel implementation means revisiting the specification and producing new implementation plans. The process is iterative: 0 -> 1 -> (1', 1'', ...) -> 2 -> 3 -> N.

The development team focuses on creativity, experimentation, and critical thinking.

The SDD Workflow in Practice

The workflow begins with an idea, often vague and incomplete. Through iterative dialogue with AI, this idea becomes a comprehensive PRD. The AI asks clarifying questions, identifies edge cases, and helps define precise acceptance criteria. What might take days of meetings and documentation in traditional development happens in hours of focused specification work. This transforms the traditional SDLC: requirements and design become continuous activities rather than discrete phases. It naturally supports a team process where specifications are versioned, created in branches, reviewed, and merged.

When a product manager updates acceptance criteria, implementation plans automatically flag affected technical decisions. When an architect discovers a better pattern, the PRD updates to reflect new possibilities.

Throughout the specification process, research agents gather critical context. They investigate library compatibility, performance benchmarks, and security implications. Organizational constraints are discovered and applied automatically -- database standards, authentication requirements, and deployment policies integrate into every specification.

From the PRD, AI generates implementation plans that map requirements to technical decisions. Every technology choice has documented rationale. Every architectural decision traces back to specific requirements. Consistency validation happens continuously: AI analyzes specifications for ambiguity, contradictions, and gaps as an ongoing refinement, not a one-time gate.

Code generation begins as soon as specifications and their implementation plans are stable enough, but they do not have to be "complete." Early generations might be exploratory, testing whether the specification makes sense in practice. Domain concepts become data models. User stories become API endpoints. Acceptance scenarios become tests. Development and testing merge through specification -- test scenarios are not written after code; they are part of the specification that generates both implementation and tests.

The feedback loop extends beyond initial development. Production metrics and incidents do not just trigger hotfixes -- they update specifications for the next regeneration. Performance bottlenecks become new non-functional requirements. Security vulnerabilities become constraints that affect all future generations. This iterative cycle between specification, implementation, and operational reality is where the traditional SDLC transforms into continuous evolution.

Why SDD Matters Now

Three trends make SDD not just possible but necessary:

AI capabilities have reached a threshold where natural language specifications can reliably generate working code. This is not about replacing developers -- it is about amplifying their effectiveness by automating the mechanical translation from specification to implementation. AI amplifies exploration and creativity, makes starting over cheap, and supports rapid addition, removal, and rethinking of features.

Software complexity continues to grow. Modern systems integrate dozens of services, frameworks, and dependencies. Keeping all these pieces aligned with the original intent through manual processes becomes increasingly difficult. SDD provides systematic alignment through specification-driven generation. Frameworks may evolve to prioritize AI-first interfaces over human-first interfaces, or organize around reusable components.

The pace of change accelerates. Requirements change far more rapidly than ever before. Pivoting is no longer exceptional -- it is expected. Modern product development demands rapid iteration based on user feedback, market conditions, and competitive pressures. Traditional development treats these changes as disruptions. Each pivot requires manually propagating changes through documentation, design, and code. The result is either slow, careful updates that limit velocity, or fast, reckless changes that accumulate technical debt.

SDD transforms requirement changes from obstacles into normal workflow. When specifications drive implementation, pivots become systematic regenerations rather than manual rewrites. Change a core requirement in the PRD, and affected implementation plans update automatically. Modify a user story, and corresponding API endpoints regenerate. SDD also supports what-if experiments: "If we need to change the application to support a new business need, how would we implement and test for that?" This is not just about initial development -- it is about maintaining engineering velocity through inevitable changes.

Core Principles

Specifications as the Lingua Franca: The specification is the primary artifact. Code is its expression in a particular language and framework. Maintaining software means evolving specifications.

Executable Specifications: The plan and tasks derived from a specification must be precise, complete, and unambiguous enough to generate working systems. This eliminates the gap between intent and implementation.

Continuous Refinement: Consistency validation happens continuously, not as a one-time gate. AI analyzes specifications for ambiguity, contradictions, and gaps as an ongoing process.

Research-Driven Context: Research agents gather critical context throughout the specification process, investigating technical options, performance implications, and organizational constraints.

Bidirectional Feedback: Production reality informs specification evolution. Metrics, incidents, and operational learnings become inputs for specification refinement.

Branching for Exploration: Generate multiple implementation approaches from the same specification to explore different optimization targets -- performance, maintainability, user experience, cost.

Implementation Approaches

Practicing SDD today requires assembling existing tools and maintaining discipline throughout the process. The methodology can be practiced with:

  • AI assistants for iterative specification development
  • Research agents for gathering technical context
  • Code generation tools for translating specifications to implementation
  • Version control systems adapted for specification-first workflows
  • Consistency checking through AI analysis of specification documents

The key is treating specifications as the source of truth, with code as the generated output that serves the specification rather than the other way around.

Applying the Synthesis of Form to Specification-Driven Development

Christopher Alexander's Notes on the Synthesis of Form (1964) provides a mathematical framework for decomposing complex design problems. Its central argument -- that good design emerges from analyzing how a system can fail its environment rather than how it should succeed -- maps directly to software architecture. The concepts below extend SDD with Alexander's analytical rigor.

Hierarchical Decomposition

Hierarchical decomposition is the process of breaking a complex design problem into a tree of smaller, manageable sub-problems. Alexander argues that human cognition cannot handle too many interacting variables at once, making decomposition necessary for any non-trivial design.

In software terms: a system is divided into subsystems, each subsystem into components, and each component into concrete implementations. The key is that the division is derived from the problem's structure, not imposed by the designer's intuition.

The Context Gathering Phase

"To bake a pie first you must create the universe." -- Carl Sagan

The first step in designing software is defining the universe where it will operate and what its purpose will be. Systems do not exist in a vacuum. They interact with other systems and with humans, and these interactions define the behavior of their subsystems. We must define what these interactions are and what outcomes they demand. This is the context.

Below is a concrete, end-to-end example of designing a Food Delivery Checkout API, applying the principles from Notes on the Synthesis of Form to software design using SDD.

Step 1: Collecting Use Cases (Defining the Context)

In Alexander's framework, you cannot design a solution without first deeply understanding the environment that places demands on it -- the "Context." In software, we start by collecting use cases from users and the business to define this environment.

  • UC1: A customer must be able to submit a list of food items they want to buy.
  • UC2: The system must securely charge the customer's credit card.
  • UC3: The restaurant needs to receive the order immediately to start cooking.
  • UC4: The system must calculate accurate delivery fees based on the user's location.

Step 2: Identifying Misfits (Constraints and Failure Points)

Rather than just writing code to make the use cases work, Alexander's method requires us to identify all the ways the software could fail to satisfy its context. These potential failures are called "misfits."

  • Misfit A (Data Integrity): The user tries to submit an order with zero items or a negative total cost.
  • Misfit B (Inventory): The user orders an item that sold out while they were browsing.
  • Misfit C (Financial/State): The payment fails, but the order is still sent to the restaurant's kitchen.
  • Misfit D (Logistics): The delivery address is outside the restaurant's allowed radius.

Step 3: Mapping Interactions and Decomposing into Subsystems

Next, we analyze how these misfits interact. If fixing one misfit forces you to change how you fix another, they are "linked." Alexander's method groups highly linked misfits into isolated clusters (subsystems) to prevent complexity from overwhelming the design.

  • Misfit C (Financial) and Misfit D (Logistics) have almost zero interaction. A declined credit card has nothing to do with whether a delivery address is valid. They belong in separate subsystems.
  • Misfit A (Data Integrity) and Misfit B (Inventory) are both linked to the concept of the cart and must be solved together.

The Decomposition (Subsystem Boundaries):

  1. Order Subsystem: Handles item validation, cart totals, and inventory checks.
  2. Payment Subsystem: Handles credit card validation and financial state.
  3. Logistics Subsystem: Handles address verification and delivery fees.

Step 4: The Constructive Diagram (Writing the Formal Spec)

In Alexander's framework, the designer creates a "Constructive Diagram" -- a design that structurally resolves the misfits within a cluster. In SDD, this diagram is the formal specification. It acts as a contract that prevents misfits from occurring.

The constructive diagram, together with the information gathered so far, constitutes the spec. The same process applies to the system as a whole and to each subsystem within it, making it recursive: it can be applied at any level and to each feature added afterward.

The spec includes: - Constructive diagram with entity definitions - Use cases (the context) - Functional and non-functional requirements - Misfits, constraints, edge cases, and failure points - Assumptions - Scope

The system as a whole is composed of subsystems, and these may have their own subsystems as needed, following the hierarchical decomposition and interaction mapping described above.

The Plan Phase

Each subsystem will have a plan that defines its Abstract Components:

  1. Abstract Components within a subsystem are highly connected to each other but weakly connected to components in other subsystems.

  2. Each Abstract Component's functionality is defined by its behaviors. Behaviors belong to a component when they have high internal interaction (strong coupling) and low external interaction with other components (weak coupling).

  3. Each Abstract Component owns a set of entities that define its data models.

  4. Abstract Components are sets of behaviors that become concrete implementations in the Tasks phase.

Example:

Subsystem: Order Subsystem Abstract Components: ItemValidator, Cart, InventoryChecker.

All behavior related to item validation has high internal coupling but low external coupling with the Payment Subsystem (which handles credit card validation and financial state).

However, the Order Subsystem has higher external interactions with the Logistics Subsystem. Where subsystems interact, we define contracts that govern the interaction -- these could be APIs, schemas, event contracts, etc. The exact implementation method is decided in the Tasks phase, not the Plan phase. The plan defines what interactions exist between subsystems and why. The Tasks phase must not violate this by creating interactions not defined in the plan or by modifying how declared interactions work.

The Tasks Phase (Synthesis)

The final step is the actual synthesis, where the "Form" (the running software) is generated to match the spec.

Instead of relying on a developer to manually write if (items.length == 0) return error; across multiple files, the framework consumes the spec and the plan and generates:

  • Validation logic that:
  • Tests whether the system satisfies the use cases
  • Respects the requirements
  • Eliminates the identified failure points
  • Code structure that:
  • Matches the constructive diagram
  • Implements the plan's Abstract Components as concrete code that respects the behavior definitions
  • Contracts between subsystems, as defined in the plan, governing how subsystems interact
  • Data models based on the entities defined in the plan and spec

For example, the tasks phase uses TDD to verify that if a frontend app tries to send an empty cart, the system rejects it immediately with a 400 Bad Request.

The misfits are eliminated at the structural level by the decomposition. The resulting software fits its required environment without unnecessary complexity.