Engineering · AI
Spec-Driven Development: The Antidote to Vibe Coding
Mihajlo Petrović5 min read
When generating code takes minutes, the specification becomes the durable artifact and the code becomes an output. What a spec should contain, where it lives, and when vibe coding is still the right call.
"Vibe coding" — describing what you want, accepting whatever the model produces, and iterating on feel — is a real and useful mode. For a prototype, a throwaway script, or an unfamiliar API you're exploring, it's the fastest thing available.
It's also, at any larger scale, how you end up with a codebase nobody can maintain — including the AI, which will happily keep piling onto a structure it doesn't understand either.
The alternative isn't going back to typing everything by hand. It's moving the human effort one level up: you write the specification, the agent writes the code.
The Inversion
For sixty years the source code has been the artifact — the durable thing, the thing in version control, the thing you maintain. Prompts and design docs were scaffolding you threw away.
That's inverted for AI-assisted work. When generating a well-specified implementation takes minutes, the specification becomes the durable artifact and the code becomes an output. That sounds like a slogan until you notice its practical consequences:
- A spec is reviewable by people who won't read 800 lines of a diff.
- A spec is the context the agent needs on session two, and session ten, when the conversation is long gone.
- A wrong spec costs one sentence to fix. Wrong code costs a review cycle and the willpower to throw work away.
- When requirements change, editing the spec and regenerating is often cleaner than patching around the old assumption.
This is the same idea BMAD formalises with its PRD and architecture documents, which I went through in the agents-in-practice post. But you don't need a framework to get most of the value.
What a Spec Actually Contains
Not a novel. Half a page to two pages, and five sections:
1. Goal. One or two sentences, in terms of what the user gets. "Users can export their transaction history as CSV, filtered by the date range they're currently viewing."
2. Constraints. The things that make this your system: the stack, the patterns to follow, the file to imitate, what must not change. "Follow the pattern in user-profile.service.ts" is worth more than a paragraph of abstract guidance — always point at real code.
3. Interfaces. The types, the endpoint shape, the function signatures. Where an agent most often invents something plausible and wrong, and where fifteen lines of TypeScript eliminate all ambiguity.
4. Acceptance criteria. Concrete and checkable. "Empty result set produces a header-only file, not an error." "Date range longer than 12 months is rejected with a 400 and a message naming the limit." These double as your test list — which is exactly why they're the highest-value section.
5. Non-goals. The single most underrated part. "Not implementing scheduled exports. Not touching the existing PDF export. No new dependencies." Without this, agents helpfully expand scope, and you review a diff three times the size you wanted.
Where It Lives
In the repo, next to the code. docs/specs/csv-export.md, committed with the implementation.
This is what makes the approach compound rather than being a personal habit. Six months later, when someone asks why exports are capped at 12 months, the answer is in version control with the commit that introduced it. The next agent session reads the spec instead of reverse-engineering intent from the code. New team members read specs, which are dramatically faster to absorb than implementations.
And tests are the executable half of the same document — acceptance criteria that run in CI. When the spec and the code disagree, the tests are what tells you which one is lying.
Where Vibe Coding Is Still Correct
I want to be clear that this isn't an argument for ceremony everywhere. Skip the spec when:
- You're exploring — you don't know what you want yet, and writing a spec would be inventing certainty you don't have.
- The change is small and obvious — a bug fix, a rename, a copy change. Describing it takes longer than doing it.
- The code is disposable — a one-off migration script, a proof of concept, an experiment that will be deleted this week.
The test is roughly: will anyone, including me, need to understand this in three months? If no, vibes are fine. If yes, spend the ten minutes.
The Failure Mode to Watch For
Specs rot. A spec that no longer matches the code is worse than no spec, because people trust it.
Two habits prevent it: update the spec in the same commit as the behaviour change (treat it like a schema, not like documentation), and delete specs for features that are gone. If keeping them current feels like too much work, that's usually a sign they're too detailed — a spec that describes what and why survives refactors, a spec that describes every function does not.
Why This Is the Skill That Matters
The bottleneck in AI-assisted development isn't generation any more, and it isn't typing. It's specification — being able to state precisely what should exist, under what constraints, and how you'd know it's correct.
That's not a new skill. It's the thing senior engineers were always doing before they wrote any code; the difference is that it used to happen in their heads, and now it has to be written down where the agent can read it.
Which makes the advice unusually simple: get good at describing exactly what you want. The typing was never the hard part.
- #spec-driven development
- #vibe coding
- #ai agents
- #documentation
- #engineering practice
Written by
Mihajlo Petrović
Software engineer in Belgrade. Builds his own products and the AI automations that keep them running.