By
Sergio Colque Ponce— Software Engineering, Universidad Privada de Tacna.
Full source code:[github.com/srg-cp/spec-driven-development]
If you have used an AI coding agent — Copilot, Claude Code, Gemini CLI — you have probably lived this moment: you describe a feature, the agent produces code that compiles and looks right, and then it quietly does the wrong thing. The agent is not weak; the input was ambiguous. We have been treating coding agents like search engines when they behave more like very literal pair programmers.
Spec-Driven Development (SDD) is the answer to that problem: instead of jumping straight to code, you write down what you want and why, refine it, and only then let the implementation follow. The specification — not the code — becomes the center of the project.
The idea is old (anyone who has written a Product Requirements Document will recognize it), but it has become practical again thanks to tools like GitHub's open-source Spec Kit. Spec Kit organizes the work into a small set of Markdown artifacts, each feeding the next:
The workflow is usually summarized as Spec → Plan → Tasks → Implement, and the same process is meant to work regardless of language, framework, or which of the 30+ supported agents you use.
The real shift is not "more documents." It is this: when requirements change, you update the spec, regenerate the plan, and let the implementation follow — instead of patching code and hoping the intent survives. The spec is a living artifact, not a dusty Word file nobody opens.
You do not need Spec Kit to practice SDD. The underlying principle is simple and tool-agnostic:
The specification is the single source of truth. The code and the tests are derived from it — never the reverse.
The most powerful version of this is an executable specification: a machine-readable contract that your tests and CI can check the implementation against automatically. If the code drifts away from the spec, the build fails. That is what turns a spec from documentation into a guardrail.
Let me show exactly that with a small, real project you can run yourself.
We want a /shorten
endpoint: send a long URL, get back a short code. The SDD move is to write the contract first, before any logic. Here it is as an OpenAPI document (spec/openapi.yaml
):
components:
schemas:
ShortenRequest:
type: object
required: [url]
additionalProperties: false
properties:
url:
type: string
format: uri
minLength: 1
ShortenResponse:
type: object
required: [original_url, short_code]
additionalProperties: false
properties:
original_url:
type: string
short_code:
type: string
description: Exactly 7 lowercase hex characters.
pattern: '^[0-9a-f]{7}$'
Notice we already decided the shape of success: a response must contain original_url
and a short_code
of exactly seven hex characters. No code exists yet, but the rules are unambiguous.
Now the code simply honors the contract. Its job is to satisfy ShortenResponse
, not to invent its own format:
import hashlib
class InvalidURLError(ValueError):
"""Raised when the input violates the ShortenRequest schema."""
def shorten(url: str) -> dict:
if not isinstance(url, str) or not url:
raise InvalidURLError("url is required and must be a non-empty string")
short_code = hashlib.sha256(url.encode("utf-8")).hexdigest()[:7]
return {"original_url": url, "short_code": short_code}
This is where SDD stops being a slogan. The tests load the spec and validate the implementation against it. The assertions are not hand-copied from the code — they come straight from the contract:
import yaml, jsonschema
from openapi_spec_validator import validate as validate_openapi
from shortener import shorten, InvalidURLError
SPEC = yaml.safe_load(open("spec/openapi.yaml").read())
def schema(name):
return SPEC["components"]["schemas"][name]
def test_spec_is_a_valid_openapi_document():
validate_openapi(SPEC) # the contract itself must be valid
def test_response_conforms_to_the_spec():
result = shorten("https://upt.edu.pe/admision/2026")
jsonschema.validate(instance=result, schema=schema("ShortenResponse"))
If someone later changes shorten()
to return, say, an 8-character code, the pattern
in the spec no longer matches and test_response_conforms_to_the_spec
fails immediately. The spec is now enforced.
A small GitHub Actions workflow validates the contract and runs the tests on every push. The spec is checked by a machine, on every change, forever:
name: CI
on: [push, pull_request]
jobs:
spec-and-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: python -c "import yaml; from openapi_spec_validator import validate; validate(yaml.safe_load(open('spec/openapi.yaml')))"
- run: pytest -v
Running the suite locally is just as honest:
tests/test_contract.py::test_spec_is_a_valid_openapi_document PASSED
tests/test_contract.py::test_response_conforms_to_the_spec PASSED
tests/test_contract.py::test_short_code_matches_the_specified_pattern PASSED
tests/test_contract.py::test_invalid_request_is_rejected PASSED
Spec-Driven Development shines on greenfield work (starting from zero, where it is tempting to just start typing), on features with real consequences where "looks right" is not good enough, and on any project handed to an AI agent that needs unambiguous intent rather than a vague prompt. It is overkill for a throwaway script — but for anything you will maintain, the spec pays for itself the first time a requirement changes.
One honest caveat: keep a human in the loop. SDD with AI does not mean blind trust. You still review the spec, the plan, and the generated code at the critical steps.
Spec-Driven Development is less about a specific tool and more about reversing the usual order: decide what is correct, write it down where machines can check it, and let the code and tests follow. Whether you adopt the full Spec → Plan → Tasks → Implement workflow with Spec Kit, or just keep an OpenAPI contract that your CI enforces, the payoff is the same — your software does what you actually meant, and it keeps doing it after the next change.
The complete, runnable project is here: ** github.com/srg-cp/spec-driven-development**. Clone it, change the
short_code
pattern in the spec, run the tests, and watch the contract catch you.Thanks for reading!