it's me, a dev mostly

The Spec Is the Deliverable. The Code Is Build Output.

8 min read
contents

Here’s the claim, and I’ll defend it the rest of the way down: when you’re building with an AI in the loop, the spec is the artifact that matters. The code is build output. Treat it that way and a lot of the second-order problems with AI-assisted development get quieter. Treat it the old way (code as the source of truth, spec as the thing you wrote once for the kickoff deck) and you’re going to have a bad time about six months in.

I know how that sounds. Bear with me.

What I mean by “spec”

Not a Word doc. Not a Confluence page that nobody updates. I mean a living, precise, markdown-shaped description of what the system does, what it doesn’t do, what the invariants are, what the failure modes look like, and how you’d know it was broken. The kind of thing that, handed to a competent dev team, lets them rebuild the thing without playing twenty questions with you. Detailed enough to generate from. Detailed enough to test against. Detailed enough that you’d notice if it was wrong.

Most teams don’t write that document. They write something shorter and call it a spec, then let the code drift, and the code becomes the only truthful description of the system. That’s been a problem forever. AI just makes the problem more obvious, and a lot more expensive to ignore.

Why this matters more with AI in the loop

The old workflow was simple: spec is approximate, code is precise, code wins. Fine. A senior dev reads the code, reconstructs intent, makes the change. Slow but honest.

The new workflow is different. You prompt, the model writes, you review, you ship. The code is now downstream of two things: the prompt and the model. Change either one and the code changes. So what’s actually persistent across the project? Not the code. The code is a snapshot of what the model produced for a particular prompt on a particular Tuesday. The thing that persists, the thing you can hand to another dev (or to another model in eighteen months) and get something equivalent back, is the spec.

Code becomes a build artifact. The spec becomes the source.

Right? It feels backwards. Let me earn it.

The honest concession, up front

The strongest argument against everything I just said: LLM output is non-deterministic. If I regenerate from the same spec tomorrow, I get similar code, not identical code. So the tested, reviewed code in the repo is the real source of truth, because that’s the only artifact you can actually prove things about. The spec is just a wish.

That argument is correct. I want to be very clear about this, because I’ve seen people get themselves in trouble pretending otherwise. You will sometimes hear “specs make regeneration almost deterministic.” That phrase is doing a lot of work, and most of it is bad work. The gap between “deterministic” and “almost deterministic” is exactly where the silent failures live: the small percentage of regenerated code that’s subtly different, ships unreviewed, and breaks something in production three weeks later. “Almost deterministic” is a comfort phrase. Delete it from your vocabulary.

So I’m not claiming the spec lets you regenerate the system on demand. I’m claiming something narrower and, I think, more useful.

A bigger caveat while I’m being honest. I haven’t run this on a real client engagement yet. The four projects I’m drawing on are private, small-to-medium things I built on my own time. It works surprisingly well at that scale, well enough that I’m writing this post about it, but I want you to weight the evidence accordingly. I’m telling you what I’ve seen, not what I’ve shipped to a paying customer with an SLA. The shape of the argument should hold under client conditions (the failure modes I’m describing get worse at scale, not better, which means the discipline matters more, not less) but I haven’t proven that, and I’m not going to pretend I have.

With that on the table: the stability I got out of AI-assisted coding did not come from the spec magically reproducing the code. It came from senior judgment. Mine. Encoded as specs. Enforced by TDD. Backed by code review I’d have done anyway. The spec wasn’t a substitute for that judgment. It was the format I used to encode it so the AI could play along.

So the defensible version of the claim is this. Disciplined senior practice makes AI output stable, and the spec is the artifact that carries that discipline through the handoff. The generated code gets regenerated at dev time, reviewed hard, then frozen and version-pinned at handoff. Nobody regenerates code in production. (If your CI is calling out to Claude to rebuild a service, we need to have a different conversation.)

The spec is not a regeneration button. It’s the thing that means the code in the repo isn’t an illegible black box six months from now.

Where this earns its keep: POCs and MVPs that don’t stay POCs

I want to scope this honestly. I’m talking about POC and MVP work. Greenfield. Small teams. Tight loops. This isn’t a claim about refactoring a fifteen-year-old monolith. That’s a different problem and probably a different post.

But here’s the thing about MVPs: a lot of them are lying about being MVPs. The “throwaway prototype” gets a customer. The customer pays. The thing is now load-bearing, and nobody is going to give you a three-month rewrite window to do it properly. You’re maintaining the prototype now. Congratulations.

This is where the spec earns out. When the MVP quietly graduates to production (which is most of the time, in my experience) the receiving team inherits two things. They inherit the code, which the AI wrote and which a senior reviewed. And they inherit the spec, which describes what the code is supposed to do, what invariants matter, and what the tests are protecting. Without the spec, all they have is a pile of plausible-looking TypeScript and a prayer.

To be clear about the handoff audience: I’m not claiming the spec is something you hand to a non-technical client. The client gets demos and outcomes. The spec is for the dev team who picks it up next. People who can read a precise document and act on it. That’s a real audience, and it’s underserved.

What this looks like in practice

Light on the tooling, because the argument doesn’t depend on it. But for grounding: markdown specs in the repo, alongside the code, version-controlled together. Tests written from the spec, not the implementation. Code generated with Claude or Cursor against the spec and the tests. Review with the assumption that the AI is a fast junior who sometimes lies confidently. Freeze at release, pin the model version in the spec metadata if you regenerated anything material, and move on.

The discipline isn’t exotic. It’s stuff senior devs already do. The only real shift is where you spend your effort. You spend it on the spec and the tests, because that’s the leverage point. You spend less on the code itself, because the code is, increasingly, a derivable output.

Vibe coding, briefly

I should say what I’m not arguing against. “Vibe coding” has a literal version (don’t read what the AI produced, just ship it if it runs) and I think that version is bad and will hurt you. But people also use “vibe coding” to mean any iterative, conversational, prompt-driven workflow, and that’s fine. That’s actually good. I do that. The difference is whether there’s a spec underneath the conversation, anchoring it. Prompt iteratively, sure. Just have something durable to iterate against.

The thing I genuinely don’t know

Here’s where I get to be uncomfortable in public. I cannot yet tell you whether any of this works for someone who isn’t a senior architect.

My honest worry is that the engine here is judgment, and the spec is just how I serialize my judgment. If that’s true, then a less experienced dev with the same workflow gets faster, more confident slop. Output that looks like mine but isn’t load-bearing in the same way, because the spec they wrote doesn’t encode the right invariants. They didn’t know which invariants to encode.

I’m credible on the rest of this post precisely because I’ve been both the decider and the implementer on these projects. That’s also why I can’t answer this question yet. I can’t separate “this works” from “this works when I do it.” I haven’t run the experiment where I hand the method to a mid-level dev and watch what happens over six months without me in the loop. (And honestly, I haven’t run it on a real client engagement either, which is the other shoe I’m waiting to drop.)

That’s what I’m watching for next. Whether the spec is a vehicle for judgment or a substitute for it is the question that decides whether any of this scales past the people who already know what they’re doing. I have a guess. I’d rather not publish it until I’ve seen it.

Until then: write the spec. Treat the code as build output. Don’t regenerate in production. And be honest with yourself about who, exactly, is the source of the stability.