Spec-driven development makes the spec the source of truth, not your chat log. Here's the real Spec Kit + Claude Code workflow and where it pays off.
If you've spent the last year prompting an agent, watching it produce something close, then re-prompting five times until it lands, you already know the failure mode. The agent doesn't remember why it made a choice on line 40 when it rewrites line 120. Your intent lives in a chat log that vanishes when you close the tab. Spec-driven development flips that: you write the spec once, in the repo, and the agent implements against it instead of against a sentence you typed at 4pm.
This isn't a rebranded PRD. The phrase doing the rounds on the GitHub and AWS engineering blogs is "the spec is the prompt" — a versioned, reviewable markdown file that the agent reads on every run. GitHub open-sourced the reference toolkit, Spec Kit, in September 2025; by early 2026 it had crossed 80k stars and DeepLearning.AI shipped a course on it. That's usually the signal a workflow has left the experimental phase. I've been running it on a real .NET + Next.js codebase for a couple of months, so here's what it actually looks like, where it earns its keep, and the two places it will waste your afternoon if you're not careful.
Say I want to add saved searches to an internal admin tool — a .NET 9 Web API backend, a Next.js 15 frontend. The vibe-coding version is one prompt:
"Add a saved searches feature. Users can save the current filter
set with a name, see a list of saved searches, and re-apply one.
Backend is .NET Web API with EF Core, frontend is Next.js."
The agent will produce something. It'll invent a table schema, pick an endpoint shape, guess at whether saved searches are per-user or shared, decide on its own whether names must be unique, and skip auth on the delete endpoint because you didn't mention it. Every one of those is a decision you now have to reverse-engineer from the diff. When you ask for a change — "actually, make searches shareable" — it rewrites half of what it just built, because none of the original reasoning was written down.
Spec Kit installs a set of slash commands into Claude Code (or Copilot, Cursor, Codex, Gemini CLI — it's agent-agnostic). You run specify init once in the repo and get /constitution, /specify, /clarify, /plan, /tasks, and /implement. The flow is deliberately staged. You don't jump to code.
First, /specify turns a rough description into a structured spec that talks about what and why, not how. The output lands in a file, and this is the part that matters — it's a diff you review like any other:
# Feature: Saved Searches
## User stories
- As an admin, I can save the current filter set under a name so I
can re-run it later.
- As an admin, I can share a saved search with my team.
- As an admin, I can delete a saved search I own.
## Requirements
- A saved search belongs to one owner (the creator).
- Ownership is enforced on delete; only the owner may delete.
- Names are unique per owner, not globally.
- Shared searches are read-only to non-owners.
## Non-goals
- No scheduling / no email digests in this iteration.
- No versioning of saved searches.
## Acceptance criteria
- Saving a filter set with an existing name for the same owner
returns 409, not a silent overwrite.
- A non-owner calling DELETE gets 403.
Notice there's no SQL, no endpoint list, no React here. That separation is the whole point: the spec is stable, the implementation is disposable. If the agent botches the code, you regenerate from a spec you already trust instead of re-litigating requirements.
Then /plan produces the technical design — the schema, the endpoints, the component tree — grounded in that spec. This is where I feed it the stack constraints:
/plan Use .NET 9 minimal APIs, EF Core with SQL Server, and a
repository per aggregate. Frontend: Next.js 15 App Router, server
actions for mutations, TanStack Query for the list. Auth is a
per-request ClaimsPrincipal; owner id comes from the "sub" claim.
Only after the plan is reviewed does /tasks break it into ordered, independently reviewable units, and /implement works through them one at a time. Each task is small enough to read in a single sitting.
If you take one thing from this, take this. The command that saved me the most time wasn't /implement — it was /clarify. It reads the spec back and surfaces the ambiguities before any code exists. On the saved-searches feature it came back with:
Ambiguities found:
1. "Names are unique per owner" - does renaming an existing search
to a taken name return 409, or is uniqueness only checked on
create? (spec covers create, not rename)
2. When an owner deletes a search that others have shared-access to,
do those references disappear silently or is delete blocked?
3. Is the filter set stored as opaque JSON or as typed columns?
Affects whether old saved searches survive a filter-schema change.
Every one of those is a bug I would have shipped with vibe coding and found in QA. Number 3 in particular — storing the filter set as opaque JSON versus typed columns — is an architecture decision with a migration cost attached. Catching it at the spec stage costs a sentence. Catching it after the table's in production costs a data migration. That's the real return here, and it has nothing to do with typing speed.
Spec Kit is great, but the mechanism underneath is just "a markdown file the agent reads every run." You can hand-roll a lightweight version with a single SPEC.md and a custom slash command. In Claude Code, drop this in .claude/commands/build-to-spec.md:
---
description: Implement the next unchecked item in SPEC.md
---
Read SPEC.md and CLAUDE.md. Find the first requirement under
"## Requirements" that is not yet implemented in the codebase.
Before writing code:
1. Restate the requirement in one sentence.
2. List any ambiguity in the spec. If found, STOP and ask.
Then implement only that one requirement. Do not touch anything
the requirement doesn't call for. When done, verify against the
matching "## Acceptance criteria" line and report which criteria
you checked.
Now /build-to-spec gives you the discipline — restate, flag ambiguity, one unit at a time, verify against acceptance criteria — without adopting a whole toolchain. For a small team, that's often the right amount of ceremony. Scale up to Spec Kit's /constitution and multi-phase flow when you have several people and several agents touching the same specs.
First: spec-driven becomes waterfall with extra steps. The failure pattern is writing a 600-line spec for a two-file change. If the feature is small enough that you can hold it in your head, skip the ceremony and just prompt. Specs earn their cost on features with real branching — ownership rules, state machines, permission edges — not on "add a column and a form field." I use a rough gate: if I can't name three decisions the agent could get wrong, the feature doesn't need a spec.
Second: specs rot. The moment the code says one thing and the spec says another, the spec is worse than useless — it lies to the next agent run, which faithfully re-implements the stale requirement. A spec is only source-of-truth if you treat it like source. That means the spec change and the code change land in the same PR, and your review checks that they still agree. Some teams wire an /analyze pass into CI to diff spec against implementation, which is a reasonable guardrail, but the cultural fix matters more than the tooling one: an out-of-date spec is a bug, and it gets a bug's urgency.
There's a subtler trap too. It's tempting to let the agent write the spec and the plan and the code with no human gate between them. Then you've just moved vibe coding one level up the stack — you're now vibe-approving specs you didn't read. The value is entirely in the review at each seam. If you rubber-stamp the spec, you inherit whatever the agent assumed, which is exactly the problem you were trying to escape.
The interesting shift isn't "AI writes code faster." It's that the artifact you version, review, and argue about moves up a level — from the diff to the spec. Code review starts to look like spec review plus a spot-check of the implementation, because the implementation is increasingly regenerable and the spec is the thing with the load-bearing decisions in it. That's a genuinely different way to spend your attention, and on features with real complexity it's a better one. Requirements you'd have discovered in QA, you now discover in a markdown file that costs a sentence to fix.
Start small. Take the next feature that has more than three decisions in it, write the spec by hand or with /specify, run a /clarify pass, and read what it flags. If it surfaces one ambiguity you'd otherwise have shipped, you'll understand the appeal without reading another word about it.