As a software architect and senior software engineer, I find myself in the middle of a change that I can only describe as epochal. The way we think about software development and enterprise architecture is shifting, and so are the roles that humans and "intelligent systems" play today and will play tomorrow.
My angle is Spec-Driven Development (SDD) with GitHub Spec Kit, seen from where I work every day: software architecture. In this first article I stay on the theory, with a small example of SDD to make it concrete. In the next ones I build a real application with Spec Kit. GitHub Spec Kit is not the only option: alternatives include Kiro, OpenSpec and BMAD. For now, though, GitHub Spec Kit is the one that best fits my preferences and the way I like to work. This is a personal choice, not a claim that one approach is universally better than the others. I wrote this article to share my view and to set a personal starting point that I can come back to (and probably correct) while things evolve. BMAD is one I may explore in a future article, or perhaps a short series of its own.
What Is Spec-Driven Development?
Let's start from the problem. Ask an AI agent to "add user registration to my app" and it has to guess: which fields, which password rules, is the email verified, how are accounts stored? The result is often plausible-looking code resting on dozens of unstated assumptions, many of them wrong. This is what people call vibe coding.
To me, SDD is the answer. It is an approach where a detailed, human- or AI-validated specification becomes the single source of truth before any code is written. Instead of documentation as an afterthought, the process is inverted: the specification is an executable blueprint that drives the AI coding agents.
Not every SDD approach keeps the spec alive for the same amount of time. I've found a classification featured in numerous articles to be incredibly helpful to distinguish three levels of rigor:
- Spec-first: a well-thought spec is written before coding to guide the initial implementation. Once the code exists, the spec may be discarded or drift: its value is the initial clarity, which makes it a good fit for AI-assisted first builds and prototypes.
- Spec-anchored: the spec is kept and evolves with the software, through automated tests and contracts. A change in behavior updates both, and tests derived from the spec (BDD scenarios, or OpenAPI with contract testing) fail when they drift. It suits most production systems well.
- Spec-as-source: the spec is the main source file and agents generate or update code directly from its changes. Humans edit only the spec and never the generated code, so drift is eliminated by design. It needs mature, trusted generation tooling and today is practical mainly where generation is well defined, such as server stubs from OpenAPI.
My rule of thumb: use the minimum level of rigor that removes ambiguity in your context. Spec-first for AI-assisted initial development, spec-anchored for long-lived production systems, spec-as-source only when the generation tooling is mature and trusted.
The Specification as a Contract
This is the idea I want at the center of this series.
In a code-centric flow, requirements documents drift and design diagrams rot, so the code ends up being the de facto truth. Asking "what should this function do?" means reading the implementation. SDD flips the relationship: the specification becomes the authoritative description that humans and machines use to build and maintain the system. The spec declares the intent, the code realizes it.
I think three properties turn a specification into a contract and not just documentation:
- Each phase constrains the next. Specify answers what, plan answers how, then come tasks, implementation and validation. Each phase produces an artifact that guides the following one, creating a chain of accountability from intent to implementation.
- The spec stays the authority. When validation finds a gap, you either fix the code or revise the spec. Either way, the specification remains the reference point.
- It is checked, not just read. Traditional design documents are read by developers who then write code that hopefully matches them. A contract is meant to be verified: if the code diverges, something should fail.
Contracts are not new in software. API specifications such as OpenAPI have long played this role: once the API contract is agreed, frontend and backend teams can work in parallel, and any implementation that matches the contract is valid by definition. SDD brings the same logic to the whole build process.
Mental Model: the spec declares the intent, the code is an implementation detail. And I would be careful with false confidence: a passing check only proves that the code matches the spec. If the spec is wrong, the code will faithfully implement the wrong thing.
GitHub Spec Kit in a Nutshell
Spec Kit is the open source toolkit from GitHub for spec-driven development. You interact with it through commands in your coding agent, and it plugs into many of them (Copilot, Claude, Gemini, Codex and others). It can also be extended with extensions, presets, workflows and bundles.
The core process is:
Specify -> Plan -> Tasks -> Implement -> Converge
The constitution, the project-wide principles and constraints, is defined once per project. Each phase produces a Markdown artifact that feeds the next one. For production features, three optional quality gates are added.
- clarify: Asks targeted questions about anything underspecified and folds your answers back into the spec, so you're not planning on top of ambiguity. Run it before planning, optionally with a focus area.
- checklist: Generates a custom quality checklist These custom checklists are reviewer-owned
- analyze: Reports conflicts, gaps, and ambiguities across spec.md, plan.md, and tasks.md
A Simple Example: a Todo List
Theory is easier with a concrete case. Take the simplest idea I can think of, a todo list, and look at what goes where. The example is mine, written just to show the separation. The syntax is the real one from the Spec Kit docs, in GitHub Copilot's default skills mode (/speckit-*). You type these commands in the chat of your coding agent, not in the terminal. Other agents may invoke them in a different way, so check the docs for your setup.
Step 1: Constitution (project-wide principles, written once):
/speckit-constitution Todo List is an adaptive web application.
Every API change starts from the REST contract.
Business logic lives in the backend, never in the UI.
Every feature comes with automated tests.
The UI must work on both phone and desktop.
This creates or updates the project constitution: the guiding principles against which every later phase is evaluated. A few lines from the file look like this:
<prj_root>/.specify/memory/constitution.md
## Core Principles
### I. Adaptive Todo Application
The product MUST be an adaptive web application for managing todo items.
...
### II. REST Contract First
Every API change MUST begin with an update to the REST contract.
...
## Governance
Amendments MUST be reviewed and recorded in the constitution before dependent work proceeds.
Spec Kit also keeps dependent templates in sync. Create the file once at the beginning of the project, then update it whenever your principles change.
Step 2: Specify (what and why, no technology):
/speckit-specify Develop a todo list application where a user can add a task,
mark it as done, delete it and filter tasks by status.
A task needs a non-empty title.
Completed tasks stay visible until they are deleted.
Out of scope for now: sharing tasks between users and notifications.
The generated spec contains four user stories: Add a Task, Mark a Task Done, Delete a Task, and Filter Tasks by Status. Its built-in requirements checklist has 16 items, all checked in this example. A few excerpts from the generated files:
<prj_root>/specs/001-todo-list/spec.md
## User Scenarios & Testing
### User Story 1 - Add a Task (Priority: P1)
...
## Requirements
- **FR-002**: The system MUST reject an empty or whitespace-only title...
...
## Success Criteria
- **SC-004**: Users can complete the add, complete, delete, and filter journeys on both phone and desktop form factors.
<prj_root>/specs/001-todo-list/checklists/requirements.md
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
The spec focuses on the what and why: user-facing behavior, goals, and outcomes, not the tech stack. The built-in checklist reviews the spec's quality and completeness before planning.
Step 3: Plan (how, the technology belongs here):
/speckit-plan Use an Angular web application, a REST API and a backend component (Python)
that holds the business logic, on top of a relational database (SQLite).
Data model: Task(id, title, status, createdAt).
Expose create, read, update and delete endpoints on /tasks.
This creates the implementation plan and four supporting design artifacts under <prj_root>/specs/001-todo-list/. The excerpts below are copied from those files.
plan.md (Most important file for technical requirements):
<prj_root>/specs/001-todo-list/plan.md
## Summary
Deliver an adaptive todo-list web application for adding, completing, filtering, and deleting tasks.
The Angular client uses a contract-first REST API; a Python FastAPI service owns validation and
business rules and persists tasks to a file-backed SQLite database through Python's standard
`sqlite3` module.
research.md (technical discovery file):
<prj_root>/specs/001-todo-list/research.md
### SQLite from Python
- **Decision**: Use Python 3.12's built-in `sqlite3` module with one connection per request, created
and closed on the request's worker thread. Keep each mutation in a short transaction and commit
only after all statements succeed; roll back on failure.
data-model.md (file used for data model):
<prj_root>/specs/001-todo-list/data-model.md
## Task
| Field | Type | Required | Rules |
|---|---|---:|---|
| `id` | UUID | Yes | Generated by the backend; unique and immutable. |
| `title` | String | Yes | Must contain at least one non-whitespace character. Valid submitted values are preserved. |
contracts/openapi.json (REST interface spec):
<prj_root>/specs/001-todo-list/contracts/openapi.json
"paths": {
"/tasks": {
"get": {
"operationId": "listTasks",
"summary": "List tasks, optionally filtered by status",
quickstart.md (setup frontend and backend):
<prj_root>/specs/001-todo-list/quickstart.md
## Start the Backend
From the repository root in PowerShell:
```powershell
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e "backend[test]"
```
Step 4: Tasks (break the work down):
/speckit-tasks
No arguments here: the command reads the design artifacts and creates <prj_root>/specs/001-todo-list/tasks.md. This file contains 40 tasks: Setup (3), Foundational (8), Add a Task (7), Mark a Task Done (6), Delete a Task (6), Filter Tasks by Status (7), and Polish (3). Each story has a goal, an independent test, and its own test and implementation tasks. In this example, the tests are included because the constitution requires automated tests for every feature.
The file defines the task format and traceability labels:
<prj_root>/specs/001-todo-list/tasks.md
## Format: `[ID] [P?] [Story] Description`
- **[P]** marks work that touches separate files and has no dependency on an incomplete task.
- **[Story]** identifies the user story for story-phase tasks; setup, foundation, and polish tasks have no story label.
- Every task names the exact file or files to create or modify.
Here is its User Story 1 section. The [X] markers shown are the current state, after /speckit-implement completed the tasks:
## Phase 3: User Story 1 - Add a Task (Priority: P1, MVP)
**Goal**: Create a task from a non-empty title and show it in the All task list.
**Independent Test**: Submit a valid title and confirm exactly one active task with the submitted title is returned and displayed; empty and whitespace-only submissions create no task and show a title-required message.
### Tests for User Story 1
- [X] T012 [P] [US1] Add POST `/tasks` and default GET `/tasks` request/response contract tests, including 201, Task response fields, and 422 Problem Details, in `backend/tests/contract/test_create_task_contract.py`.
Parallel work and phase dependencies are also recorded in the same file:
### Parallel Opportunities
- Setup tasks T001-T003 touch separate backend, frontend, and root configuration files.
- Within each story, its three test tasks use separate contract, integration, and UI test files and can be authored in parallel before story implementation.
Before implementation, /speckit-analyze can check the consistency of spec, plan, and tasks; it is read-only and reports conflicts and gaps.
Step 5: Implement (build it):
/speckit-implement
This is the command that writes the code. It executes the tasks in tasks.md, running each phase in dependency order and respecting the parallel markers. Before starting, it reads the state of the checklists as a gate: if some items are unchecked it asks before proceeding, and it never changes the checklist markers. For a small feature like our todo list you can run it once and build everything. For a larger one, you can scope each run with an argument (for example only the Setup and Foundational phases), validate the result and then continue.
If you catch yourself writing "use PostgreSQL" in the specification, move it to the plan. This is the whole point: the spec says what, the plan says how. And it is exactly what makes me curious about changing only the plan later.
Step 6: Converge (check that nothing was missed):
/speckit-converge
This assesses the codebase against the spec, the plan and the tasks to confirm that nothing was missed. It is append-only: it never edits or deletes code, and its only possible write is adding tasks to tasks.md. You run it only after implement has run on the current tasks.md. It first prints a summary of the findings graded by severity, then ends in one of two ways:
- Converged: no gaps found. tasks.md stays unchanged and you can move on to the review or the pull request.
- Tasks appended: gaps found. The new tasks are added under a Convergence section of tasks.md, you run implement again to complete them and then converge once more, until it reports converged.
The Role of the Human
SDD does not take people out of the loop: I think it moves the place where their judgment is applied. Accepting generated changes with minimal resistance is just vibe coding with more files. Machines take over the mechanical work: generating code, keeping artifacts consistent, detecting drift. Humans keep authority over intent, domain meaning, risk tolerance and the legal and ethical constraints that no execution trace can reveal. Those constraints have to be written explicitly into the specification, which is where human judgment is encoded and then enforced.
In practice, for me this means explicit approval boundaries: a breaking schema change, a policy shift, a refactoring proposed by the AI or a compatibility downgrade should never pass without a person's decision. Spec Kit points in the same direction. Every phase produces an artifact you review before moving on, and in its own agentic SDLC tests and scans stay deterministic automation while merge decisions stay with the maintainers.
For the engineer, I believe, this changes where value comes from: no longer the speed of writing lines of code, but the quality of the specifications they refine and validate, as an orchestrator and validator who defines the what upstream and approves the how at the boundaries.
Mental Model: bounded autonomy, not full automation. Enforcement is automated, meaning stays human.
Takeaways
If I have to summarize my starting point in a few lines:
- The specification is a contract. Each phase constrains the next, the spec stays the authority, and it is checked and not just read.
- GitHub Spec Kit gives you the workflow: Specify -> Plan -> Tasks -> Implement -> Converge, with clarify, checklist and analyze as quality gates.
- Humans move upstream, and their responsibility grows. People decide specifications, guidelines and architectures: engineers and architects evolve from writing code to owning what the system must do and why.
- Keep a reality check. Less context drift, fewer hallucinations and no technical debt from chaotic prompting are the declared goals of SDD, not guaranteed results.
In the upcoming articles, we move from theory to practice: an adaptive Angular web application communicating via REST with a backend component holding the business logic on top of a relational database, fully specified and built using Spec Kit.
Next, I challenge myself by porting the backend to a different technology stack. The core idea here is to primarily adapt the plan, which dictates the technical specifications and stack changes. The REST contract, tests, data model, and Angular application act as our fixed anchor points: if they remain untouched, we prove just how powerfully specification-driven planning can handle technology shifts. The code will be on my GitHub (rosidotidev)
What has your experience with Spec-Driven Development been like? I'd love to hear your thoughts, questions, or feedback in the comments.



Top comments (0)