Ahnii!
There's a lot of energy right now spent bashing vibe coding. I think most of it is aimed at the wrong target. The MVP you stand up with AI doesn't have to generalize. That's not what it was for. The interesting craft is what you do next to take that prototype from "it works on my laptop" to something a stranger can run, debug, and trust. That's a craft worth respecting. This post is the playbook I'm currently running on a real public repo, with six specific moves and an artifact for each.
The wrong fight
The bashing usually goes like this: "Look at this AI-generated MVP, look at how brittle it is, look at how much it cost in tokens to get it half-right." Sure. Now compare it to the alternative: an empty directory and a developer who hasn't started. The point of the prototype isn't to be production code. The point of the prototype is to find out whether the idea works at all. To answer a question. To make something move on a screen that wasn't moving yesterday.
Once it moves, you have new information. You know which parts are load-bearing. You know which gotchas bit you. You know which assumptions held up. You're now in a much better position to do the second pass, and the second pass is where the craft lives. The craft is the set of moves you make to take a working prototype to a state where someone else can run it, debug it, and trust it.
Those moves are not mysterious. They have names. They are the rest of this post.
The repo on the bench
The worked example is the OIATC application, a public PHP repo I build alongside my own modern PHP framework,
It powers is the runbook. It opens with a Strategy folder pointer: a separate workspace outside the repo tracks every live page on oiatc.ca against its canonical Twig source, last-updated date, and analytics. The CLAUDE.md says, plainly, that if the two disagree, the repo wins.
Below that pointer it documents the architecture (Access/, Controller/, Domain/, Entity/, Provider/, Support/), the ServiceProvider DI methods with full signatures (singleton, bind, resolve, tag, entityType), the queue Job pattern with tries, timeout, retryAfter, and the frontend template families (site shell vs. longform documents vs. news). An Operations section, added in commit 896ec8f, covers deploy, Raspberry Pi access, and secrets.
The runbook is the operating manual you wish someone had handed you. The OIATC one is dense because it earned every line. The mistakes that produced those lines aren't generic; they're specific to this app, this framework version, this deployment target. That's exactly what makes them worth writing down.
Step 3. Pin the world
Vibe-coded prototypes love composer install and git clone main. That works on Tuesday. It breaks on Friday when upstream cuts a release. The OIATC repo pins the world three different ways.
First, composer.lock is committed and treated as the source of truth. Lock-file drift gets its own entry in the upstream-notes (entry 003 walks through a drift caused by a post-hash php: >=8.5 constraint and the fix). Second, the deploy contract is a docker compose run that calls bin/waaseyaa db:init before bringing the app up:
docker compose run --rm oiatc-app bin/waaseyaa db:init
docker compose up -d
db:init is idempotent. Fresh volume gets migrations. Current schema is a no-op. Safe to invoke on every deploy. That's what makes a deploy step a contract instead of a tradition.
Third, the app lives on a specific waaseyaa/framework alpha version (currently alpha.188). Upgrades happen as deliberate events with their own branch and their own entry in the upstream-notes, not as quiet drift. When upstream cuts a release that breaks something, you have a working baseline to compare against.
Step 4. Name the graveyard
The repo has a literal graveyard directory: . It's a running log of framework quirks, bugs, breakages, and missing pieces hit while building on an alpha release of waaseyaa/framework. Each entry uses a fixed format:
## NNN — short title
- **Date / version:** YYYY-MM-DD · waaseyaa/framework alpha.NNN
- **Doing:** what we were doing when we hit it
- **Symptom:** the observable problem
- **Workaround:** what we did to get unblocked
- **Likely upstream fix:** the proper change in waaseyaa/framework
There are 16+ entries. Stale VERSION files. Ambiguous class resolution between the metapackage and split mirrors. Lock-file drift after a platform requirement bump. Hard ext-sodium dependency via the OIDC stack. Each one is recon for the framework itself.
The point of the upstream-notes is not to complain. The point is to keep app-level hacks out of the consumer code. Every entry is a decision: do we patch around it here, do we file the upstream fix now, do we wait until the next alpha. Without the log, those decisions get re-litigated every time someone hits the same wall.
This is also the move I think AI tooling makes most useful. When you hit a quirk, write the entry first. The structured shape forces you to articulate what you actually saw, what you're guessing, and what would fix it upstream. That writing is exactly the input the model needs to either help you work around it cleanly or propose an upstream change.
Step 6. Layer specs on what's next
The hardest current work on Anokii doesn't get vibe-coded. The topic-confidence gate that decides whether a citation is worth showing (65de562), the relevance gate that ensures only genuinely relevant passages are cited (8359e37), the climate-companion variant for the Massey Solar resource cluster (c8956f6), the shared relational-graph instance per community (928191d) — all of those run through docs/superpowers/specs and docs/superpowers/plans. Each change has a spec articulating the intent, a plan decomposing the work, and reviewable PRs landing the implementation.
Why specs and not just commits: a relevance gate that filters citations is the kind of thing where a hallucinated implementation looks fine until a user gets a confidently-wrong answer. The spec gives you a contract for what the gate is supposed to do. The plan gives reviewers something to evaluate against. The implementation has a referenceable target.
This is the handoff move. Vibe coding got the chat answering. The runbook captured the patterns. The pinned world made the deploys reproducible. The graveyard remembered the dead ends. The upstream-notes captured what the framework still owes us. And now spec-driven work takes over for the parts that are too big or too risky to vibe through. Each layer earned the right to the next layer.
What this looks like together
The OIATC repo is not a clean codebase. It's a layered one. Some Anokii code still reads like the experiment it started as, because it still works and the cost of replacing it is higher than the cost of keeping it. The newer pieces — the keyword-RAG retrieval, the relevance gate, the topic-confidence gate — were built with specs and reviewed PRs because each of them is a place where a quiet bug becomes a confidently-wrong answer to a real person looking for real community resources.
Vibe coding was the first move. The six moves above are the second, third, fourth, fifth, sixth, and seventh. None of them are mysterious. None of them are expensive. They are the boring craft that turns a working prototype into something a stranger can run, debug, and trust.
If you've been bashing vibe coding, you're aiming at the start of a process and ignoring the rest of it. If you've been vibe-coding without any of the rest, you're going to keep losing days to the same gotchas, and the next person who touches your prototype is going to lose them too.
There's no shame in vibe coding. There's a lot of value in what comes next.
Baamaapii
SOCIAL SHARE CARD GENERATOR