Skip to main content
ThunderLang
← All articles
ai-engineering

Spec-First Development vs Vibe Coding: An Honest Comparison

6 min read · 2026-08-29 · Allen Codewell

Vibe coding ships faster on day one and gets more expensive every day after. This article is about the exact inflection point where that cost curve crosses, and how to know in advance which side of it your project lives on.

The Honest Definition of Each Approach

Spec-first development means writing a formal description of intended behavior before writing any implementation. That description could be an OpenAPI schema, a property-based test suite, a structured requirements doc with acceptance criteria, or something like a ThunderLang intent declaration that gates AI-generated changes against a verifiable contract. The artifact comes before the code.

Vibe coding is what most teams actually do with LLMs today. You describe a feature in natural language, the model generates code, you eyeball it, maybe run a quick test, and merge. The feedback loop is fast. The specification lives in your head, or in a Slack thread, or nowhere.

Neither of these is new. Vibe coding is cowboy coding with a smarter autocomplete. Spec-first is TDD and design-by-contract, both of which predate LLMs by decades. What is new is the speed gap between them on day one, and the compounding cost gap after.

Why Vibe Coding Wins Early

The first week of a greenfield project with an LLM assistant is genuinely remarkable. You can get a working POST /users endpoint with JWT auth, a Postgres schema, and basic error handling in under an hour. The model holds the boilerplate in its head so you do not have to. For prototypes, hackathons, and solo projects where the feedback loop is you and maybe two other people, this workflow is hard to argue against.

The productivity is real.

But it is front-loaded in a way that matters at scale. Every vibe-coded decision that goes undocumented is a future debugging session where someone has to reverse-engineer intent from behavior. Every LLM-generated abstraction that felt clever at generation time is a dependency the next engineer has to understand without a spec to lean on.

The Compounding Cost of Undocumented Intent

Here is a concrete failure mode. A team ships a payment retry system using Claude to generate the backoff logic. The logic works. Nobody writes down why the retry window is 72 hours, or that it was a specific business decision tied to a downstream banking SLA. Six months later, someone uses Cursor to refactor the retry handler for performance and the model, seeing no documented constraint, optimizes the window away.

The regression hits production on a Friday.

This is not a theoretical scenario. The root cause in almost every case like this is the same: intent was stored in someone's head, not in an artifact the next change could be checked against. The LLM did not hallucinate. It optimized for a goal that was never recorded.

# Vibe-coded: works, but intent is invisible
def calculate_retry_window(attempt: int) -> int:
    return min(2 ** attempt * 300, 72 * 3600)  # why 72h? nobody knows

# Spec-first: intent is in the code and in an external contract
# ThunderLang intent declaration would assert:
# max_window_seconds <= 259200  # 72h: matches banking-sla-v2 contract
# retry_curve == 'exponential'
def calculate_retry_window(attempt: int, max_seconds: int = 72 * 3600) -> int:
    """Backoff bounded by banking SLA (see: docs/contracts/banking-sla-v2.md)."""
    return min(2 ** attempt * 300, max_seconds)

The difference is not the code. The difference is whether the constraint is checkable by the next change.

3 Tradeoff Moments Where Spec-First Pays for Itself

There are specific decision points where upfront specification stops being overhead and starts being load-bearing.

1. When more than two people touch the same module. The cognitive overhead of a shared spec is lower than the cognitive overhead of N engineers each building a mental model of the same undocumented system. This is true even if writing the spec takes a full day.

2. When the system has external contracts. APIs consumed by other teams, webhook payloads, data pipelines feeding downstream models: all of these are places where an undocumented change causes failures you do not see until someone else's pager goes off. An OpenAPI spec or a property test suite makes those contracts checkable on every PR. Vibe coding gives you no hook for that check.

3. When you are using AI to modify existing behavior, not generate new behavior. Generating a new CRUD endpoint from scratch is a low-risk vibe-coding task. Modifying a pricing calculation that touches revenue recognition is not. AI is great at creation under loose constraints. It is dangerous at modification under invisible constraints.

The Strongest Counterargument: Specs Become Stale Too

This is the real pushback from vibe-coding advocates, and it deserves a straight answer.

They are right. A spec that nobody maintains is worse than no spec, because it creates false confidence. If your OpenAPI doc says amount is an integer and the code returns a float, you have a new class of bug: the spec-reality gap. Teams that write specs once and never touch them again have shipped some spectacular mismatches.

But this argument proves too much. The solution to stale specs is not to have no specs. It is to make specs executable so they fail loudly when they drift. Property-based tests with hypothesis, contract tests with pact, and schema validation on every request in staging are all tools that keep specs honest. The spec-first discipline only pays if the spec is in the critical path of deployment, not in a doc folder someone opens twice a year.

Spec-first development is not about writing documents. It is about making intent machine-readable so that the next change, whether written by a human or an LLM, cannot silently violate it.

When to Pick Vibe Coding Deliberately

Spec-first is not always the right answer. There are contexts where vibe coding is the correct professional choice.

  • Pre-product-market-fit startups where the spec will be wrong tomorrow anyway. You are not engineering a system you understand yet. Write the spec after you have learned what you are actually building.
  • Throwaway scripts and one-off data migrations. If it runs once and is deleted, the cost of documentation is pure overhead.
  • Exploration spikes. Vibe-code the spike to understand the problem space. Throw it away. Write the spec for the real implementation.
  • Solo projects with a lifetime under six months. If only you will ever read this code, and not for long, the spec is for an audience that does not exist.

The heuristic I use: if the code will be modified by someone who did not write it, including future-you with no memory of the context, it needs a spec. If not, vibe away.

How AI Changes the Spec-First Economics

Here is the part most coverage misses. LLMs do not make spec-first development more expensive. They make it cheaper.

Writing a formal spec used to require significant upfront effort because the tooling was manual. Today, you can describe behavior in plain English and have Claude or GPT-4o draft an OpenAPI schema, a property test suite, or a ThunderLang intent block in minutes. The spec-writing step that once took a senior engineer a day now takes an hour, with human review on top.

This inverts the old economics. The argument that spec-first is too slow assumed that writing specs was purely human labor. When you have an LLM as a spec drafting assistant and a tool like ThunderLang to verify that AI-generated code satisfies declared intent before merge, the overhead shrinks to the review step. Which is the only part you actually need a human for.

The teams that win over the next five years are the ones that use LLMs to generate specs and to generate code, then use automated verification to close the loop between them. Vibe coding alone is a local maximum.

Measuring the Crossover Point

If you want a concrete framework, here is the one I reach for. Estimate the probability that a given piece of code will be touched by an AI assistant in the next 12 months. Multiply it by the cost of a silent regression in that code. If that number is non-trivial, the spec is worth writing.

For a payment processing function, that probability is near 1.0 and the regression cost is high. Spec it.

For a seed script that populates a local dev database, the probability is low and the cost of regression is a developer spending 20 minutes resetting their environment. Skip the spec.

This is not a precise formula. It is a forcing function that makes the tradeoff explicit instead of implicit. Most engineers making the vibe-coding choice are not making it consciously. They are defaulting to the faster workflow without modeling what they are trading away.

Gate your first AI change

If you have AI-assisted PRs merging today without a machine-readable check on intent, the regression on the Friday after a refactor is a matter of when, not if. ThunderLang lets you declare what a change must satisfy, then gates AI-written code against that intent with a verify-diff and durable proof artifacts. Try it here.

The engineers who ship reliably with AI are not the ones who prompt better; they are the ones who made intent checkable before the model ever touched the code.