My manager sent a link on a Tuesday afternoon. No preamble, just a URL and a message: "Can you take a look? We need this ready for the event — it's in five days."
The link opened a responsive web application. Registration forms, a dashboard, data persisting in real time. It worked. Built by a business head using vibe coding — iterating with an AI model, prompt by prompt, until something functional emerged — and hosted on a free platform backed by Supabase. For a solo, non-engineering build completed under deadline pressure, it was genuinely impressive.
But the event was an enterprise function. The app needed to run on our cloud, pass a security scan, and survive a Vulnerability Assessment and Penetration Test (VAPT). That changed the picture entirely.
What followed was one of the more instructive weeks I've had as an engineer: running two approaches to the same problem in parallel, under real conditions, with real security requirements. This is what I found.
The two approaches, briefly
Vibe coding is intuition-led, prompt-driven development. You describe what you want, iterate on the output, patch what breaks, and keep going until it works. The primary artefact is the working code and the prompt history that produced it. Context lives in the session. Requirements are implicit — embedded in what the model understood you to mean.
Spec-driven development inverts that. Requirements are made explicit first, captured in a structured specification document (I'll call mine SPEC.md). An AI coding tool — in this case, Claude.ai — works from that document rather than from freeform prompts. The spec is persistent context. It travels across sessions, enforces architectural decisions, and gives reviewers something to audit.
Neither is inherently superior. They optimise for different things. What this article does is show what that difference looks like when the stakes are real.
1. Reverse-engineering the original app
Before I could build the spec-driven version, I needed to understand what the original app actually did — not what it was supposed to do, but what it had been prompted into doing.
I spent a day working through it: tracing data flows, reading the Supabase schema, mapping the UI states. A few things became clear quickly.
The core functionality was sound. Users could register, submit data, and see results. The Supabase integration was functional. The UI was responsive and reasonably polished for a vibe-coded build.
What was missing was everything below the surface. Authentication logic had gaps. API calls were made without error handling in several places. Environment variables were managed in ways that wouldn't survive a security audit. There was no documentation — no README, no data model, nothing that would tell a second developer what the system was doing or why.
None of this was the builder's fault. Vibe coding optimises for getting something working. Security hygiene, error handling, and documentation aren't things a prompt naturally surfaces unless you ask for them explicitly — and even then, consistently enforcing them across a multi-session build is hard.
The reverse-engineering process became the foundation of the spec. Every gap I found was a requirement I could write down.
2. Building the spec-driven app
I wrote the SPEC.md in about 45 minutes. It covered:
- Functional requirements — The features the original app had, translated into explicit acceptance criteria
- Data model — The Supabase schema, documented and validated against the actual tables
- Authentication and authorisation — Rules for who could access what, and how sessions should be managed
- Input validation — Field-level rules for every form input
- Error handling — Expected behaviour for network failures, validation errors, and edge cases
- Security requirements — Explicit constraints on environment variable handling, API exposure, and data access patterns
- Non-functional requirements — Performance expectations, deployment target, logging
The document wasn't long. But it was the first time the app's requirements existed anywhere other than inside a series of prompts.
I fed the spec to Claude.ai and worked through the build section by section. The key difference from vibe coding was visible immediately: when the model made a decision I hadn't anticipated — a different state management pattern, an unexpected API structure — I could evaluate it against the spec and either accept or correct it. There was a reference point. Vibe coding doesn't give you that. You evaluate the output against your mental model, which degrades across sessions, and against whether it runs, which is a much lower bar.
The re-engineered app took longer to start. The spec writing, the initial setup, the structured review process — that overhead is real. But it compressed dramatically in the later stages, where the original app would have started accumulating friction.
The security test: same bar, very different results
Both apps went through identical enterprise onboarding: deployment to our cloud environment, a security scan, and a full VAPT.
The results weren't close.
| | Original app (vibe coding): Returned more than 10 high-priority findings on the security scan. The VAPT surfaced several priority-level vulnerabilities — issues that, in a production system, could expose user data or allow unauthorised access. The remediation work was significant. The app needed to be patched before it could be approved for deployment, which introduced its own risk: patching a vibe-coded app without a spec means reasoning about what each change might break, without documentation to guide you. |
| | Re-engineered app (spec-driven): Returned 2 low-priority security issues on the scan. Both were straightforward — caught early, fixed cleanly, and verified before the VAPT ran. The VAPT findings were addressed in the same cycle. The app cleared enterprise onboarding. |
To be clear: neither result was about the quality of the builder. The business head who built the original app shipped something functional in a few days with no development background. That's not a small thing. The security gaps were a predictable consequence of the approach and the context — not the person.
What the spec-driven process did was make security a first-class requirement from the start, rather than something to retrofit after the fact. That's the structural difference.
Side-by-side comparison
The table below maps both apps across all 12 dimensions. The security and VAPT rows are the ones that matter most for enterprise contexts. The others explain why those rows turn out the way they do.
|
Dimension |
Vibe coding (original app) |
Spec-driven (re-engineered app) |
|
Primary artefact |
Prompt history & source code |
Structured SPEC.md |
|
Human role |
Prompt writer & patcher |
Architect & orchestrator |
|
Quality standard |
"It almost works" |
Production-ready, reviewed |
|
Context management |
Ephemeral session memory |
Persistent SPEC.md / AGENTS.md |
|
Builder profile |
Non-technical; shipped in ~5 days on free hosting + Supabase |
Engineer; reverse-engineered requirements, rebuilt with Codegen |
|
Security scan findings |
10+ high-priority issues identified |
2 low-priority issues — both fixed |
|
VAPT outcome |
Several priority vulnerabilities; significant remediation required |
Issues found and addressed; passed enterprise onboarding |
|
Enterprise cloud readiness |
Required patching before deployment approval |
Deployed cleanly after fixing 2 issues |
|
Architectural consistency |
Degrades over sessions |
Enforced by specification |
|
Requirements provenance |
Implicit — embedded in prompts and working code |
Explicit — formalised in SPEC.md before build |
|
Scalability |
Solo experiments, prototypes |
Teams, enterprise, production |
|
Error rate |
Higher vulnerability exposure |
Systematic review & scanning |
When to use which
This is not a verdict against vibe coding. It's a map.
Vibe coding is well-suited when:
- You need a working prototype in hours or days, not a production system
- The builder is non-technical, and the goal is to prove an idea
- The app won't handle sensitive data or face external users
- You're exploring a problem space before committing to an approach
- Failure is low-stakes and reversible
Spec-driven development is the right choice when:
- The app will be deployed to an enterprise environment
- It will handle user data, authentication, or any security surface
- Multiple people need to work on it, maintain it, or hand it over
- You need to demonstrate compliance or pass a security review
- The build will span multiple sessions or weeks
The honest version of this is: the original app was the right tool for getting to “does this idea work?” in five days. It was the wrong tool for getting to “can we deploy this in production?” Spec-driven development answered the second question, but it needed the original app’s existence to know what to spec.
A hybrid approach — vibe code a rough proof of concept, then formalise a spec before any production work begins — is probably the most pragmatic path for teams that want speed and safety. The cost of writing a spec from a working prototype is low. The cost of retrofitting security into a vibe-coded app under deadline pressure is not.