Capa do artigo: Intention Before Code: Spec-Driven Development, Tests, and AI in an Engineering Pipeline

Intention Before Code: Spec-Driven Development, Tests, and AI in an Engineering Pipeline

Introduction

Code without tests is not reliable. No matter how well written it is. Without an automated safety net, any change is a risky operation. It was to solve this problem that Kent Beck systematized test-driven development (TDD), inverting the conventional order of work. The engineer first writes a test that describes the desired behavior, then writes the minimum code to make it pass, and then reorganizes the internal structure with tests as protection. This cycle, known as red-green-refactor, became one of the foundations of professional software engineering.

As architectures evolved from monoliths to distributed systems composed of multiple independent services, the problem changed shape. Within each service, the test pyramid remains valid. But the boundaries between services introduced a different class of failures: network calls subject to timeouts, implicit contracts between teams, and behavioral divergences that unit tests cannot detect. Formal interface specifications and executable contracts between services became necessary where there was previously no formal boundary to test.

With the arrival of artificial intelligence agents in the engineering workflow, a third layer of complexity was added. Agents are capable of generating functional code from natural language descriptions, which created a practice known as vibe coding, where the engineer vaguely describes what they want, iterates over the generated code, and accepts what seems correct. The problem is that language models fill in what was not said with statistically plausible choices. Undescribed edge cases, ambiguous business rules, and implicit behaviors become the model's decisions, not the engineer's. The generated code looks correct, creating an illusion of correctness that makes these problems invisible until they reach production.

This text proposes a response to this set of problems. A shift-left engineering pipeline that begins with structured specification, moves through executable behavioral scenarios and verified tests, and only then delivers to the agent the task of implementing. An approach where intention precedes code and where automation operates within boundaries defined by people.


1. The Evolution of Test-Driven Development

The Extreme Programming movement emerged as a response to the late-integration and technical debt accumulation problems that characterized software projects in the nineties. Kent Beck proposed an inversion of the conventional order: before writing code, the engineer writes a test that describes the desired behavior. The minimum production code to make the test pass comes next. With tests green, the internal structure can be safely reorganized. This cycle became known as red-green-refactor.

The most relevant effect of TDD on engineering practice was not test coverage itself. It was the change in how engineers think about design before writing code. A module that requires complex setup to test exposes implicit coupling in the interface. A test that needs many auxiliary objects to run suggests the module is taking on too many responsibilities. Writing the test first makes these considerations visible when changes are still cheap.

Adoption was gradual and often partial. Many teams adopted automated testing without adopting the cycle in its strict form, producing suites written after the code that reflected the programmer's implementation biases rather than describing expected behavior. Even in this partially-adopted form, tests transformed how software teams work. Continuous integration and the concept of the fast feedback loop central to XP influenced how continuous delivery pipelines were conceived.

The context in which these practices operate, however, became more complex. Software in the nineties was predominantly monolithic. Boundaries between components were code boundaries, not network boundaries. Testing the behavior of a system meant exercising functions and classes within a single process. As architectures became distributed, this set of practices needed to be extended to cover service boundaries as well.


2. The Growth of Systemic Complexity

The transition from monolithic architectures to distributed systems was not a purely technical choice. It occurred due to the pressures of scale, team autonomy, and delivery speed that monoliths made difficult to sustain. As companies like Amazon, Netflix, Google, and Uber grew beyond the capacity of a single database and a single process to serve all traffic, new architectures became necessary.

Decomposing systems into smaller, independent services changed how large systems were developed and operated. Different teams could work on different parts of the system without constant conflicts over shared codebases. Each service could be scaled independently as usage patterns demanded. Failures in one service could be isolated so the rest of the system kept running.

These gains came with forms of complexity that did not exist in monolithic systems. When two modules of a monolith communicate, the call happens in the same memory and has predictable cost. When two services communicate over a network, the communication can fail in dozens of different ways, each with distinct implications for the behavior of the system as a whole.

"A distributed system is one in which the failure of a computer you didn't even know existed can render your own computer unusable." β€” Leslie Lamport, 1987

Partial failures are an inherent property of distributed systems with no equivalent in monolithic systems. In a monolith, a component fails and the entire process can terminate predictably. In a distributed system, a service may respond slowly without failing completely, producing timeouts that propagate through the chain of dependencies and manifest as failures in services that are functioning but depend on a degraded service.

Eventual consistency, formally described by Werner Vogels and widely adopted by Amazon's engineering team, describes the behavior of systems that allow different replicas of the same data to temporarily diverge in favor of availability. This model is appropriate for many use cases, but makes it much harder to reason about the observable behavior of the system at any specific moment.

The diagram below represents a system composed of multiple services communicating through both synchronous HTTP APIs and an asynchronous event bus.

graph TD
    Client[HTTP Client] --> Gateway[API Gateway]
    Gateway --> OrderService[Order Service]
    Gateway --> UserService[User Service]
    OrderService --> PaymentService[Payment Service]
    OrderService --> EventBus[Event Bus]
    EventBus --> InventoryService[Inventory Service]
    EventBus --> NotificationService[Notification Service]
    OrderService --> OrderDB[(Order Database)]
    PaymentService --> PaymentDB[(Payment Database)]
    InventoryService --> InventoryDB[(Inventory Database)]

Ensuring that this system works as a whole cannot depend only on tests within each isolated service. A test that verifies in isolation whether the order service generates the correct request to the payment service does not detect what happens when the payment service is temporarily unavailable, when the network shows higher-than-expected latency, or when the event published to the bus is not consumed in the expected order by the inventory service.

This gap created space for a different class of engineering artifacts. If the behavior of a service relative to the services it depends on can be formally described, that description can be used to verify, in any environment, whether the service is behaving according to the contract. This idea is at the origin of what came to be called contract-driven development and, more broadly, specification-driven engineering.


3. Specifications as Executable Engineering Artifacts

The word specification has a long history in software engineering. In waterfall-based development models, specifications were textual documents that described what a system should do before any code was written. These documents were often voluminous, difficult to keep updated, and disconnected from the code eventually produced. The distance between specification and implementation made conformance verification a manual and error-prone process.

The emergence of standardized formats for service interface descriptions changed this situation. An OpenAPI specification describes in a structured and verifiable way which endpoints an HTTP API exposes, which parameters each endpoint accepts, which responses it can produce, and which data schemas are involved. It is a tool-processable artifact that can be used for multiple purposes at once.

The excerpt below presents a fragment of an OpenAPI specification for an order creation endpoint, with behavioral constraints embedded in the schemas.

openapi: "3.1.0"
info:
  title: Order Service API
  version: "1.0.0"
paths:
  /orders:
    post:
      summary: Creates a new order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
      responses:
        "201":
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "400":
          description: Invalid request
components:
  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items]
      properties:
        customerId:
          type: string
          format: uuid
        items:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/OrderItem"
    OrderItem:
      type: object
      required: [productId, quantity]
      properties:
        quantity:
          type: integer
          minimum: 1
    Order:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, confirmed, processing, shipped, delivered]
        createdAt:
          type: string
          format: date-time

From this specification, tools like Dredd or Schemathesis can execute automated tests against an implementation, verifying whether it behaves according to the described contract. Libraries like Ajv verify requests and responses at runtime using the schemas as reference. Swagger UI generates interactive documentation with no additional work.

For systems based on asynchronous communication, AsyncAPI plays a role analogous to OpenAPI. An AsyncAPI specification describes which events a service publishes, which events it consumes, and the data schemas of each message. This makes explicit the contract between event producers and consumers, a boundary that in systems without formal specification often exists only implicitly in the code of both sides.

The concept of contract-first development describes the workflow where the interface specification is written and approved before any implementation begins. Once the specification is stable, different teams can begin working in parallel. The team responsible for the provider service implements the described behavior. The consumer team writes the code that will consume the API using mocks generated automatically from the specification. The dependency between teams is replaced by dependency on the contract.

There is, however, an important limit at this level of specification. Formal interface specifications capture the communication contract between services, but not the business rules that govern the internal behavior of each service. The logic that determines how an order moves from pending to confirmed, which validations are applied before processing a payment, or how the system should behave when inventory is insufficient is beyond what an OpenAPI specification can describe. For this level of behavior, another mechanism is needed.

This gap gained specific weight with the popularization of vibe coding in corporate system development. Without structured specification that documents intent before code exists, business rules remain implicit in generated behavior, agent sessions leave no auditable artifact, and the only record of what the system should do becomes the code itself. The generated code looks correct enough to systematically reduce the level of human review, a phenomenon that security literature describes as automation bias. In long-lived systems, where teams change and context is lost, this drift directly conditions the team's ability to safely evolve the system.


4. Spec-Driven Development β€” What It Is and Where It Came From

Spec-Driven Development operates in a different layer than the contract-first API development described in the previous section. While an OpenAPI specification describes the communication interface between services, SDD describes the complete behavior of a feature before any code is written, including business rules, edge cases, system constraints, and the intent that justifies each decision.

To understand where SDD came from, it is necessary to understand the problem it solves. With the popularization of AI agents capable of generating code from natural language descriptions, a practice began to be observed in development teams. Engineers vaguely describe what they want to an agent and iterate over the generated code without a clear specification of expected behavior. This pattern became known as vibe coding, a term Andrej Karpathy introduced in 2025 to describe development guided by intuition and iterative adjustments rather than structured specification.

The GitHub Spec Kit documentation starts from a simple principle. Without clear specification, the model fills in what was not said with what is statistically most probable from its training. When an engineer asks an agent to build a discount module without specifying rounding rules, expected behavior for empty carts, handling of percentages above 100%, or what happens when the discount exceeds the total, the agent fills these gaps with the most statistically probable choices. These choices may be technically coherent and still be completely wrong with respect to what the system needs to do. The specification does not exist to limit AI. It exists to make the engineer's intent explicit in a form that AI can use as reference.

The GitHub Spec Kit, launched by Microsoft in 2025, formalized an SDD cycle composed of four distinct phases. The specify phase captures the what and the why, describing the expected behavior of the feature, the audience that uses it, business constraints, and relevant edge cases. The plan phase transforms the specification into technical decisions, choosing technologies, defining architecture, and identifying dependencies. The tasks phase divides the plan into implementable units, each small enough to be independently verified. The implement phase is where the agent executes each task using the specification as constant reference.

The principle that differentiates SDD from conventional documentation is simple. When a specification fails, you fix the specification, not just the code. The specification is the source of truth, and changes to it propagate intent to all future code generations. This inverts the common dynamic where documentation persists outdated while code silently drifts from the original intent.

The idea of making expected behavior explicit and verifiable has important antecedents in software engineering. Bertrand Meyer, in "Object-Oriented Software Construction" published in 1988 and revised in 1997, formalized the concept of Design by Contract, where preconditions, postconditions, and invariants describe formal contracts that each software component must satisfy.

Meyer argued that each module should explicitly declare what it requires from its clients (preconditions), what it guarantees in return (postconditions), and which properties always remain true about its state (invariants). The programmer and the module's client enter into a contract with obligations and benefits for both sides.

A closer line of influence comes from Behavior-Driven Development (BDD), developed by Dan North starting in 2003. BDD proposed that tests should describe behavior in language close to the business, using the "given that... when... then..." structure to make scenarios readable by both engineers and non-technical stakeholders. In the context of AI agent development, BDD gains a role that goes beyond documentation. It functions as the layer that converts the spec into executable acceptance criteria. The SDD spec describes intent in structured language, BDD scenarios translate that intent into verifiable behavior, and the agent implements against those scenarios. When a behavior described in the spec cannot be expressed as an unambiguous "given / when / then" scenario, that is a signal that the spec needs to be refined before implementation begins.

The division of responsibilities between the three practices is clear. BDD validates the external behavior of the system, what it does from the perspective of those who use it. TDD validates the internal correctness of each module. Edge cases, data structures, contracts between internal functions, and regression are its territory. SDD provides the business context that guides both BDD scenarios and unit tests. None of the three practices replaces the others. Each answers a question the others do not cover.

SDD can be understood as a practical instantiation of Meyer's and North's ideas in the context of AI-assisted development. Unlike Meyer's formalisms, SDD does not require mathematical notation or specialized formal verification tooling. The specification can be written in structured natural language, as long as it is explicit enough that both engineers and AI agents can verify conformance.

Four layers now answer distinct questions in this workflow. SDD asks what the system should do and why. BDD asks how the system should behave externally, from the perspective of those who use it. Interface specification (OpenAPI, AsyncAPI) asks how services communicate with each other. TDD asks how we know the internal implementation is correct. Each layer covers a level of intent the others do not.


5. SDD in Practice β€” From Description to Verified Spec

To make concrete what it means to develop with SDD, this article presents an end-to-end example. The discount application feature in a shopping cart is simple enough to fit in a few tests, but has enough edge cases to make visible what a structured specification adds compared to a vague description delivered directly to an AI agent.

The flow begins with the engineer describing the feature to the agent, which generates the spec in the GitHub Spec Kit format. The resulting spec is reviewed by the team before any scenario or test exists. It serves as context for future agent generations, as a review document, and as an audit reference after implementation.

## Specify

Feature: Shopping cart discount application

Business context:
The shopping cart module needs to support applying discounts
to the total value before order finalization. Discounts are granted
through marketing campaigns, loyalty coupons, or rules configured
by the commercial team.

Expected behavior:
- Percentage discounts reduce the total by the informed percentage
- Fixed-value discounts reduce the total by the absolute value informed
- The final value can never be negative
- The percentage rate cannot exceed 100%
- Fixed discount greater than total results in zero, never negative
- Discounts are calculated on the gross total, before taxes and shipping

Edge cases:
- Empty cart (zero total) with discount applied
- Percentage discount of exactly 100%
- Fixed discount exactly equal to the total
- Fractional values with floating-point precision

Constraints:
- Pure and deterministic function: same input, same output
- No external side effects
- Result with precision of two decimal places

## Plan

Technical decisions:
- Implement as a stateless pure function in TypeScript
- Typed interface to distinguish percentage discount from fixed value
- Throw explicit error for percentage above 100%
- Use Math.max for protection against negative result

## Tasks

Task 1: Implement applyDiscount with percentage discount support
Task 2: Add fixed-value discount support
Task 3: Implement protection against negative result
Task 4: Validate percentage above 100% case
Task 5: Tests for all spec edge cases

With the spec approved, the agent generates BDD scenarios that translate intent into externally verifiable behavior. Each scenario corresponds to a behavior described in the spec and can be reviewed by the team before any code exists.

Feature: Shopping cart discount application

  Scenario: Percentage discount reduces the total
    Given a cart with a total of $100.00
    When a 10% discount is applied
    Then the final value should be $90.00

  Scenario: Fixed discount greater than total results in zero
    Given a cart with a total of $50.00
    When a fixed discount of $100.00 is applied
    Then the final value should be $0.00

  Scenario: Percentage above 100% is rejected
    Given a cart with a total of $100.00
    When a percentage discount of 101% is requested
    Then the system should return a validation error

  Scenario: Empty cart with discount results in zero
    Given a cart with a total of $0.00
    When any discount is applied
    Then the final value should be $0.00

With BDD scenarios approved, the agent generates unit tests that cover the internal correctness of each module. None of these steps requires the engineer to produce artifacts from scratch. Spec, BDD scenarios, and unit tests can all be generated by the agent from natural language descriptions. What cannot be delegated is validation. Each generated artifact needs to be reviewed and approved by the engineer before advancing to the next step. Only after spec, scenarios, and tests are validated does the agent receive the instruction to generate the code. The central point is that these artifacts are traceable back to explicit rules in the specification, and the engineer's review has an objective reference rather than depending on interpretation.

describe('applyDiscount', () => {
  describe('percentage discounts', () => {
    it('applies 10% discount to the total', () => {
      // Spec: percentage discounts reduce the total by the informed percentage
      expect(applyDiscount(100, { type: 'percentage', value: 10 }))
        .toBeCloseTo(90, 2);
    });

    it('applies 100% discount resulting in zero', () => {
      // Spec: percentage rate cannot exceed 100%
      expect(applyDiscount(100, { type: 'percentage', value: 100 }))
        .toBeCloseTo(0, 2);
    });

    it('throws error when percentage exceeds 100%', () => {
      // Spec: the percentage rate cannot exceed 100%
      expect(() =>
        applyDiscount(100, { type: 'percentage', value: 101 })
      ).toThrow('Discount percentage cannot exceed 100%');
    });
  });

  describe('fixed-value discounts', () => {
    it('applies fixed-value discount to the total', () => {
      // Spec: fixed-value discounts reduce the total by the absolute value
      expect(applyDiscount(100, { type: 'fixed', value: 30 }))
        .toBeCloseTo(70, 2);
    });

    it('fixed discount greater than total results in zero', () => {
      // Spec: fixed discount greater than total results in zero value
      expect(applyDiscount(50, { type: 'fixed', value: 100 }))
        .toBeCloseTo(0, 2);
    });

    it('fixed discount exactly equal to total results in zero', () => {
      // Spec: fixed discount exactly equal to total results in zero value
      expect(applyDiscount(100, { type: 'fixed', value: 100 }))
        .toBeCloseTo(0, 2);
    });
  });

  describe('edge cases', () => {
    it('empty cart with discount results in zero', () => {
      // Spec: empty cart (zero total)
      expect(applyDiscount(0, { type: 'percentage', value: 50 }))
        .toBeCloseTo(0, 2);
    });

    it('maintains precision with fractional values', () => {
      // Spec: fractional values with floating-point precision
      expect(applyDiscount(99.99, { type: 'percentage', value: 15 }))
        .toBeCloseTo(84.99, 2);
    });
  });
});

The diagram below represents the flow from specification to verified implementation.

flowchart TD
    A[Engineer describes feature to agent] --> B[Agent generates SDD Spec]
    B --> C[Team reviews the spec]
    C --> D{Spec approved?}
    D -->|No| B
    D -->|Yes| E[Agent generates BDD scenarios from spec]
    E --> F[Engineer reviews scenarios against spec]
    F --> G{Scenarios capture the intent?}
    G -->|No| E
    G -->|Yes| H[Agent generates unit tests]
    H --> I[Engineer validates test coverage]
    I --> J{Tests cover the spec?}
    J -->|No| H
    J -->|Yes| K[Agent implements production code]
    K --> L[Run full suite - GREEN]
    L --> M[Refactor safely]
    M --> N[Verified and traceable implementation]

What changes compared to the cycle without SDD is visible at review time. The engineer analyzing BDD scenarios has a clear reference, the specification. The question becomes whether each scenario covers a behavior described in the spec and whether all described behaviors have at least one corresponding scenario. This traceability between spec, scenario, and test is what transforms review from an interpretation activity into a verification activity.


6. SDD, Pact, and the Service Boundary

When the specified feature involves communication between services, SDD connects naturally with the consumer-driven contract testing that Pact implements, in which expected behavior of an interaction must be explicit before implementation and conformance can be verified automatically.

Pact operates at two distinct moments. On the consumer side, tests define expected interactions with the provider service and verify that the consumer can generate the correct requests and process responses. As a result of these tests, a contract file is generated describing exactly what the consumer expects. On the provider side, this contract is used to verify whether the implementation satisfies the consumer's expectations, without needing a full integration environment.

The connection with SDD happens before contract tests even exist. An SDD spec that describes a feature with service integration includes in the specify phase the expected behavior of the communication, describing which data is sent, which responses are expected, and which system states exist before and after the interaction. From this spec, an AI agent can generate consumer contract tests as one of the tasks in the SDD pipeline.

The example below shows how a Pact contract test for the payment service could be generated from an SDD spec that describes the integration between the order service and the payment service.

import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const { like, string } = MatchersV3;

describe('OrderService β†’ PaymentService contract', () => {
  const provider = new PactV3({
    consumer: 'OrderService',
    provider: 'PaymentService',
    dir: './pacts',
  });

  it('processes payment for a valid order', () => {
    // SDD Spec: when a confirmed order is sent for payment,
    // the payment service should return confirmation with transactionId
    return provider
      .given('payment service available')
      .uponReceiving('payment request for a valid order')
      .withRequest({
        method: 'POST',
        path: '/payments',
        body: {
          orderId: like('order-uuid-123'),
          amount: like(150.00),
          currency: string('USD'),
        },
      })
      .willRespondWith({
        status: 201,
        body: {
          transactionId: like('txn-uuid-456'),
          status: string('approved'),
          processedAt: like('2026-03-07T10:00:00Z'),
        },
      })
      .executeTest(async (mockServer) => {
        const client = new PaymentClient(mockServer.url);
        const result = await client.processPayment({
          orderId: 'order-uuid-123',
          amount: 150.00,
          currency: 'USD',
        });
        expect(result.status).toBe('approved');
      });
  });
});

The diagram below represents the complete flow from the SDD spec to contract verification between services.

flowchart TD
    A[SDD Spec describes OrderService β†’ PaymentService integration] --> B[Agent generates consumer Pact tests]
    B --> C[Tests generate JSON contract file]
    C --> D[Contract published to PactBroker]
    D --> E[PaymentService verifies conformance with contract]
    E --> F{Verification passed?}
    F -->|No| G[PaymentService fixes implementation]
    G --> E
    F -->|Yes| H[Integration validated without full environment]

The integration of AI in the contract generation process is a direction that tools like PactFlow have been exploring, recognizing that maintaining contracts at scale is one of the friction points that inhibits adoption in larger organizations. Generating contract tests from SDD specs reduces that friction without eliminating human review of what was generated.

What makes this combination coherent is that Pact operates on the same principle that underpins SDD. Both require that expected behavior be made explicit before implementation, and that conformance can be verified automatically. The SDD spec that describes the integration between two services is the natural input for the contract tests that verify that integration. The pipeline closes a cycle. The intent documented in the spec transforms into executable contracts that survive implementation changes on both sides of the boundary.


7. The Complete Pipeline: SDD, BDD, TDD, and AI in a Coherent Flow

The elements discussed so far form an engineering pipeline when combined in a planned way. What makes this pipeline coherent is not the number of tools, but the clarity about what each step produces and what the next step consumes.

The pipeline starts with the SDD spec. Spec, BDD scenarios, and unit tests can all be generated by the agent from natural language descriptions. What is irreplaceable is human validation at each step. The spec captures intent, and the approval that it describes the correct intent can only come from the engineer or the team that understands the problem. The approved spec feeds the generation of BDD scenarios, which convert intent into executable acceptance criteria. The approved scenarios feed the generation of unit tests, which cover the internal correctness of each module. Only after spec, scenarios, and tests are validated does the agent implement the code, operating within the already-defined boundaries. At the end, Pact contract tests validate the boundaries between services.

flowchart TD
    A[Engineer describes to agent] --> B[Agent generates SDD Spec]
    B --> C[Spec review and approval]
    C --> D[Agent generates BDD scenarios]
    D --> E[Scenarios review and approval]
    E --> F[Agent generates unit tests]
    F --> G[Tests review and approval]
    G --> H[Agent implements code]
    H --> I[Agent generates Pact tests for integrations]
    I --> J[Contracts published to PactBroker]
    J --> K[Providers verify conformance]
    K --> L[CI pipeline runs entire suite]
    L --> M[Validated deploy]

The human effort in this pipeline is asymmetric because the validation artifacts carry different weight. Approving a spec is confirming that what will be built solves the right problem. Approving BDD scenarios is confirming that acceptance criteria cover the behaviors that matter. Approving unit tests is confirming that the internal edges of the module are addressed. At each of these points, the engineer evaluates intent captured in verifiable form, activities that require domain judgment and that no agent can replace. Spec, scenarios, tests, and code are generated by the agent. The agent implements within the boundaries these three artifacts collectively delimit. The spec defines what the system should do. The scenarios define how behavior will be verified. The tests define what constitutes internal correctness. Code that passes the entire suite is the direct consequence of intent documented with enough precision to be tested.

This property repositions the artifacts in the engineering process. In a pipeline with SDD, BDD, and TDD, spec and scenarios become the documents the team maintains, versions, and reviews rigorously. The agent-generated code that satisfies the entire suite expresses these contracts in executable form. What the team audits when something changes are the spec and scenarios, the code follows. Without a stable reference of correct behavior, engineer and agent iterate without an objective criterion for knowing when to stop. With spec and approved scenarios as fixed points, the space where the agent can diverge is delimited before any implementation begins.


8. The Risks of Vibe Coding in Corporate Environments

The risk of development without structured specification does not appear at the moment the code is generated. It accumulates in what the code cannot document. The intent that remained implicit, the edge cases no one specified, and the decisions made in closed sessions have no representation in any artifact.

Vibe coding is not a problem in short-lived contexts. Prototyping, experiments, and disposable internal scripts can benefit from the speed that informal descriptions offer. The risk emerges when this style becomes the development standard for long-lived production systems. The pressure for speed and the models' ability to produce plausible code from vague descriptions make this pattern tempting. The generated code looks correct, which systematically reduces the level of human review. This phenomenon is described in the security and engineering literature as automation bias, the tendency to trust automated outputs beyond what the available evidence justifies. The problem extends beyond the code generated at the time of the session. The cost accumulates in code that no one can understand, modify, or audit afterward, because the intent that generated it was not documented in any artifact that survives the original session's context.

Academic research published at the IEEE Symposium on Security and Privacy in 2022, conducted by Hammond Pearce and collaborators under the title "Asleep at the Keyboard? Assessing the Security of GitHub Copilot's Code Contributions", analyzed code generation in scenarios based on known vulnerabilities catalogued as CWEs (Common Weakness Enumeration) and found that GitHub Copilot generated code containing vulnerabilities in approximately 40% of tested scenarios. The rate varied according to scenario complexity and the security domain evaluated. The study was conducted with early versions of Copilot, in 2021 and 2022. Later research indicates improvement in more recent models, but the pattern of generating vulnerable code in specific scenarios has not disappeared. These numbers reinforce the need for verification mechanisms that detect divergences between intent and implementation before code reaches production.

The problem of specification drift is equally relevant. When specs and tests do not accompany generated code, the codebase accumulates implicit behaviors that no document describes. Over time, the only artifact that knows what the system does is the code itself. New team members need to reconstruct intent from implementation, an expensive activity prone to interpretation errors. When this process repeats with AI-assisted generation, the drift accelerates. The model learns patterns from the existing code and reproduces them, including implicit behaviors no one explicitly intended.

The probabilistic mechanisms of language models represent a specific risk in corporate environments. A model that generates different code for the same description in different sessions, or that produces functionally distinct implementations depending on the available context, creates an environment where reproducibility cannot be assumed. Without the spec as an anchor for intent, each generation is a fresh starting point without a stable reference.

The SDD spec is the mechanism that makes AI-generated code auditable. It documents the intent that precedes the code and allows any engineer, at any time, to verify whether what is in the repository corresponds to what was specified. Without the spec, the generated code is an artifact without provenance of intent. With the spec, it is an artifact that can be verified, challenged, and evolved based on explicit criteria.

The loss of institutional knowledge is accelerated in environments where generated code has no corresponding spec. In software engineering, this process is described as knowledge evaporation, the gradual degradation of collective understanding of why the system was built the way it was. Artifacts like ADRs (Architecture Decision Records), specifications, and tests that capture expected behavior exist precisely to contain this degradation. AI sessions are ephemeral by nature and do not replace these artifacts. When a senior engineer leaves the team, they carry with them the understanding of why certain decisions were made. In systems developed with TDD and adequate documentation, part of that knowledge survives in tests and commit messages. In systems developed by vibe coding without corresponding spec, that knowledge has nowhere to persist. The SDD spec is the artifact that preserves the reasoning in a form accessible to whoever comes next.


9. Calibrating the Automation β€” SDD, BDD, and TDD Without Overhead

A legitimate objection to the model described so far is that generating and reviewing structured specs for every feature can become bureaucracy that slows delivery without proportional value. This objection deserves serious treatment, because poorly calibrated documentation processes are a verifiable cost in software engineering.

Calibration starts with a distinction about what an SDD spec needs to contain. It does not need to be an extensive document with multiple sections and formal approvals for every feature. An effective spec captures the what, the why, the constraints, and the edge cases. For most features, this fits on one screen. The length of the spec should be proportional to the complexity of the behavior it describes.

SDD adds more value in specific contexts. Features with complex business rules, where the logic is not obvious from the code, are natural candidates. The spec documents the reasoning that governs behavior and allows any engineer to verify whether the implementation is correct without needing to reconstruct that reasoning from scratch. Service integrations that need to be validated independently benefit from the spec as context for Pact contract generation. Code that multiple people will need to understand and modify over time gains from the spec as a reference document that survives team turnover.

On the other hand, not all code needs a full SDD spec. Short-lived prototypes and experiments that will be discarded, scaffolding and configuration code that carries no business logic, and trivial features with completely obvious behavior are cases where the spec would add overhead without adding clarity. The decision about when a spec is necessary can be guided by the question of whether a new team member could understand the expected behavior just from the code and existing tests.

Human review of generated tests is the most efficient control point in the pipeline. Reviewing whether a set of tests correctly captures the intent of a spec requires judgment about expected behavior, not just code syntax. This is a high-reasoning, low-volume activity for an experienced engineer. In contrast, reviewing every line of implementation code requires effort proportional to the volume of code without the same return in terms of intent guarantee.

In practice, teams that adopt this model in a healthy way tend to distribute responsibilities clearly. The developer defines what the system should do and how to verify it is correct before any code exists, following the cycle described in section 5. The agent executes within these boundaries.

This model does not eliminate the need for human judgment at the center of the process, and it should not. No agent can decide what constitutes sufficient coverage for a business behavior, which edge cases are relevant for the company's context, or whether a BDD scenario faithfully captures the stakeholder's intent. These decisions belong to the developer. The agent can suggest additional cases, but the approval that the spec and tests describe the correct behavior is human and non-transferable.

This sequence does not add steps to the process. It replaces the phase of implicit discovery, where engineers discover expected behavior while implementing, with a phase of explicit specification that happens first.

Organizing specs in the repository involves two independent decisions: how many files to create and where to store them.

A feature's spec can be maintained in a single file with the specify, plan, and tasks sections together, or divided into separate files. The single file is the natural starting point for most cases, since the three sections are read in sequence and belong to the same review context. Separation makes sense when specify and plan have reviewers with different profiles, such as when a product owner needs to approve the business intent before technical decisions are made, or when the feature is complex enough for the three documents to exceed what allows fluid reading in a single file. The tasks file tends to be ephemeral. Created during refinement, consumed by the agent during implementation, and discarded when the feature is delivered.

On locality, the decision depends on the team's primary tool. For teams using GitHub Copilot, Microsoft publishes the GitHub Spec Kit as the official reference. The model uses .prompt.md files inside .github/: .github/specify.prompt.md, .github/plan.prompt.md, and .github/tasks.prompt.md. These files activate special behavior in Copilot when referenced with #file: in the chat and follow the platform's reusable prompts convention. For teams that do not use Copilot as their primary tool, there is no consolidated market convention. Standardization is up to the organization or team. The most common approach is to co-locate each spec with the code it describes: discount.spec.md in the same folder as discount.ts. Any agent can find the file as long as the architectural context instructs that search. This limitation is inherent to the centralized model. Specs that do not sit next to the code do not accompany the module when it changes location, recreating the drift that SDD aims to prevent.

The structure below represents the agnostic model, with specs co-located with the code.

project/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ cart/
β”‚   β”‚   └── discount/
β”‚   β”‚       β”œβ”€β”€ discount.spec.md          # SDD Spec β€” intent, rules, and edge cases
β”‚   β”‚       β”œβ”€β”€ discount.feature          # BDD scenarios generated from the spec
β”‚   β”‚       β”œβ”€β”€ discount.test.ts          # Unit tests generated from the scenarios
β”‚   β”‚       └── discount.ts               # Implementation generated by the agent
β”‚   └── orders/
β”‚       β”œβ”€β”€ create-order/
β”‚       β”‚   β”œβ”€β”€ create-order.spec.md      # Spec includes integration with PaymentService
β”‚       β”‚   β”œβ”€β”€ create-order.feature
β”‚       β”‚   β”œβ”€β”€ create-order.test.ts
β”‚       β”‚   └── create-order.ts
β”‚       └── contracts/
β”‚           └── order-payment.pact.test.ts  # Pact contract tests generated from spec
β”œβ”€β”€ pacts/
β”‚   └── orderservice-paymentservice.json    # Contract generated by Pact tests
β”œβ”€β”€ .agent/
β”‚   └── skills/
β”‚       β”œβ”€β”€ cloud/
β”‚       β”‚   └── SKILL.md                    # Cloud infrastructure patterns
β”‚       └── security/
β”‚           └── SKILL.md                    # Domain security policies
β”œβ”€β”€ .github/
β”‚   └── copilot-instructions.md             # Architectural context for GitHub Copilot
└── CLAUDE.md                               # Architectural context for Claude

Each feature folder contains the four cycle artifacts in the order they were produced: spec, scenarios, tests, implementation. Someone arriving at the code for the first time reads the spec before any TypeScript. Contract tests live in a separate subdirectory because they describe the boundary between services, not the internal logic of a feature. The contracts generated by the tests live in pacts/ at the root, where PactBroker or CI can consume them directly.

For teams that adopt the native GitHub Spec Kit flow, the model uses .prompt.md inside .github/. Specs are centralized and integrate with the Copilot Chat #file: flow, but do not accompany the code when modules change location.

project/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ cart/
β”‚   β”‚   └── discount/
β”‚   β”‚       β”œβ”€β”€ discount.test.ts
β”‚   β”‚       └── discount.ts
β”‚   └── orders/
β”‚       └── create-order/
β”‚           β”œβ”€β”€ create-order.test.ts
β”‚           └── create-order.ts
β”œβ”€β”€ .github/
β”‚   β”œβ”€β”€ copilot-instructions.md             # Architectural context
β”‚   β”œβ”€β”€ specify.prompt.md                   # Specify phase template (reusable)
β”‚   β”œβ”€β”€ plan.prompt.md                      # Plan phase template
β”‚   β”œβ”€β”€ tasks.prompt.md                     # Tasks phase template
β”‚   β”œβ”€β”€ discount-specify.md                 # Discount feature spec
β”‚   └── create-order-specify.md             # Order creation feature spec
└── CLAUDE.md

Agents do not read files automatically. For the spec to be used as implementation context, it needs to be explicitly referenced in the prompt or the agent needs to be instructed to look for it. The architectural context file is the right place for this instruction. A rule like "before implementing any feature, check whether a .spec.md file exists in the same directory and read it before generating any code" in CLAUDE.md or .cursorrules ensures the agent performs this search. In the .github/ model, explicit reference in the prompt with #file:.github/discount-specify.md replaces this automatic search. Both approaches produce the same effect. The spec reaches the agent's context before any code is generated.

Adopting SDD with AI agents involves two independent configuration planes. The first configures the agent's environment with the project's architectural context and the domain skills the agent loads automatically when relevant. This plane answers the question of how any generated code should be structured. The second is the per-feature engineering pipeline, composed of spec, BDD scenarios, unit tests, and Pact contracts. This plane answers the question of what a specific module should do. The two planes are complementary. Without the environment configured, the agent produces correct code for the feature but incoherent with the project. Without the pipeline, the agent produces code coherent with the project but without guarantee that it implements the right intent.

Environment Plane (per project)
β”œβ”€β”€ CLAUDE.md / .cursorrules     β†’ how the agent should work
└── .agent/skills/               β†’ domain knowledge (progressive disclosure)

Pipeline Plane (per feature)
β”œβ”€β”€ .spec.md                     β†’ intent and business rules
β”œβ”€β”€ .feature                     β†’ verifiable behavior (BDD)
β”œβ”€β”€ .test.ts                     β†’ internal correctness (TDD)
└── Pact contract                β†’ service boundary

Output
└── .ts                          β†’ generated implementation

Code agents need architectural context to generate code coherent with project standards. These files live at the project root because they apply to all agent generations, regardless of feature. GitHub Copilot uses .github/copilot-instructions.md, Cursor uses .cursorrules, Claude uses CLAUDE.md. They describe code conventions, architectural patterns, preferred technologies, and design constraints the agent must follow in all generations. The spec describes the behavior of a specific feature. The architectural context file describes how every feature should be structured. Without this context, the agent may generate functionally correct but structurally inconsistent code.

Between the project's architectural context and a feature spec, there is a third level of context that teams with specialized domains need to manage. The Agent Skills format, specified by Anthropic as an open standard, defines this level. A skill is a folder containing a SKILL.md file with metadata and instructions, plus optionally scripts, references, and assets. The agent uses progressive disclosure to manage context efficiently: at session start it loads only the name and description of each available skill, enough to recognize when a skill is relevant to the task. When the task matches the description, the agent loads the full SKILL.md instructions. This behavior is automatic and native in Claude.

A skill like cloud/ does not describe what a specific feature should do nor the project's general conventions. Its SKILL.md documents cloud infrastructure patterns, retry policies, throttling limits, egress cost criteria, and error taxonomy that any feature operating in that domain must respect. The distinction from the spec is one of scope. The spec describes the intent of a feature. The skill describes how any feature in that domain should be built.

Applied to the payment processing spec from the earlier example, the cloud/ skill changes what the agent produces even if the spec does not change. The spec defines BDD scenarios, business rules, and edge cases. The skill ensures the implementation includes circuit breaker for external calls, that timeouts follow the policy defined by the platform team, and that throttling errors are handled with exponential backoff. Without the skill, the agent may generate an implementation that satisfies all scenarios in the spec and still ignores constraints any team engineer would presuppose as obvious.

Teams with multiple specialized domains, such as cloud, security, database, and async events, maintain a skill for each. The agent automatically selects the relevant skills for each task without need for manual instruction. The Antigravity Awesome Skills ecosystem provides a library of pre-built skills compatible with Claude Code, Cursor, GitHub Copilot, and other agents, becoming a market reference for portable skills across tools.

Versioning specs is an integral part of the model. Specs stored alongside code, in the same repository and subject to the same pull request review process, ensure that spec and implementation evolve together. When a behavior change is needed, the pull request includes the spec update, the test update, and the implementation change. Reviewers verify coherence across the three layers. This pattern prevents the drift where code evolves but specs remain describing old behavior.

This ensures coherence between spec and code, but says nothing about the proportion of features that have a corresponding spec. Pipeline health can be tracked through objective metrics, even if the tooling to automate them is evolving alongside SDD practices themselves. Specification coverage measures the proportion of features with a corresponding spec. Test traceability measures how many tests have an explicit reference to a spec item. The average time between spec approval and verified implementation measures cycle speed. Teams that track these metrics can identify where the process is being followed superficially before accumulated outdated specs become a maintenance problem.


10. Implications for Architects and Staff Engineers

The practices described throughout this text concretely alter the responsibilities of engineers who make structural decisions in large-scale platforms. When SDD specs become first-class artifacts in the engineering process, the quality of those specs has direct impact on what automation can produce and on the team's ability to safely evolve the system over time.

The quality of a spec is a high-impact architectural factor. A spec that captures the correct behavior, relevant constraints, and important edge cases governs what the AI agent can and cannot do. A vague or incomplete spec produces code that satisfies the letter but not the spirit of the intent. Staff engineers and architects who understand this dynamic begin to exercise influence over quality not only by reviewing pull requests, but by defining standards for how specs should be structured, which information is mandatory, and how edge cases should be documented.

The quality of specifications carries the same type of architectural importance as the quality of interfaces in object-oriented design. A spec that mixes responsibilities, omits business constraints, or does not describe relevant edge cases creates the same long-term problems that poorly designed interfaces create in object-oriented code. The difference is that the problems of a poorly defined spec materialize in the behavior of AI-generated code, which is harder to trace back to the original cause than a conventional bug.

The question of technical governance in teams that adopt this model deserves specific attention. Where specs are stored, how they evolve when behavior changes, how to ensure specs and code remain synchronized, and who has authority to approve changes to specs that affect multiple teams are governance questions with direct technical implications. Teams that store specs close to each service's code have different coordination patterns than teams that maintain specs in centralized repositories. The choice depends on the team's organizational dynamics and the platform's change patterns.

The role of the experienced human reviewer shifts from feedback generation to judgment validation. The question moves from whether the code is syntactically correct to whether the spec captures the correct intent, and whether the generated tests cover the behaviors that matter. This shift amplifies the impact an experienced engineer can have on platform quality, because judgment about intent and behavior is far scarcer than the ability to review code line by line.

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." β€” Martin Fowler, Refactoring: Improving the Design of Existing Code, 1999

The central argument of this text can be closed with the same premise it started from. Code is still made to be read by humans. Legibility, traceability of intent, and auditability are properties that corporate code needs to have regardless of how it was generated. This statement does not contradict the use of AI in code generation. It defines the condition that use must satisfy. AI-generated code that no one can understand, audit, or safely modify accumulates technical debt until the cost of understanding it exceeds the cost of rewriting it.

SDD, BDD, TDD, and AI answer distinct questions that an engineering workflow needs to answer. SDD asks what the system should do and why. BDD asks how the system should behave externally, from the perspective of those who use it. TDD asks how we know the internal implementation is correct. AI executes within the boundaries these three layers define. Experienced engineers are those who define these boundaries with enough precision for automation to produce code that not only works, but carries documented intent, can be verified against an explicit reference, and can be evolved by people who were not present when it was generated. That is the difference between using AI as an engineering amplifier and using it as a substitute for engineering.