Guide

The maintenance cost of vibe-coded abstractions is set at generation time, not cleanup time.

Every guide on this topic measures the bill after the model has already shipped a sprawling project: 12% duplication, 50 to 80 percent rewrite, 70 percent of sprints in maintenance. Those numbers are real and they are also downstream. The cheaper place to spend a line of effort is the system prompt the model is reading while it generates. mk0r puts four lines there, on purpose, and exposes a second mode that has no files to maintain at all.

M
Matthew Diakonov
7 min

Direct answer (verified 2026-05-07)

The maintenance cost of vibe-coded abstractions depends almost entirely on which abstractions the generator emits in the first place, which depends on what the model is told to prefer. There are two practical levers. One: a Quick generation path that returns a single HTML file (zero cross-file abstractions, zero structural maintenance, regenerate to change). Two: a system prompt that explicitly forbids speculative abstractions, caps component size at roughly 100 lines, pushes error handling to boundaries, and turns off filler comments. mk0r ships both. The four-line policy lives at src/core/vm-claude-md.ts lines 83 through 86 in the global /root/.claude/CLAUDE.md the agent reads on every session.

What the existing playbooks measure, and what they skip

Read the top guides on this topic and they all converge on the same shape. AI generates code with high duplication. Duplication compounds. Engineers pay an onboarding tax. Production migration costs a 50 to 80 percent rewrite. Each of those claims is defensible. None of them step back to ask the cheapest question: what was the generator told to do?

That question is unromantic and structurally unsatisfying because it does not produce a chart. It is also where the cost is set. A model that defaults to extracting every two-line repetition into a util, every async call into a hook, and every config into a feature flag is going to ship a project that costs more to maintain than a model that defaults to inlining and waits for a third use before extracting. The default is decided in one place: the system prompt loaded into the agent before the first user turn.

The four-line policy, verbatim

Here is the relevant slice of the in-VM CLAUDE.md, served to the agent on every session start. Open src/core/vm-claude-md.ts in the repo and look at lines 80 through 90.

/root/.claude/CLAUDE.md (in-VM agent config)

None of those lines are clever. Anyone who has cleaned up a five-year-old codebase has thought all four. The novelty is not the rules; it is putting them in front of the generator before it writes a line. A maintenance lesson is cheap when it is read by a model with zero code on disk. The same lesson is expensive when it is read by an engineer staring at a feature flag named FF_DEPRECATED_LEGACY_USER_FLOW_V2.

Why those four, specifically

Each line maps to a category of maintenance debt that AI app builders ship by default.

Component size cap (line 83)

Models default to monolithic App.tsx files in one direction or eight-component-deep trees in the other. The cap pushes toward the middle, where each component fits in a screen. A reader can hold one component in their head; they cannot hold an eight-deep tree.

Errors at boundaries (line 84)

Defensive try-catch inside every internal helper is the most common form of generated noise. It does not change behavior; it just adds nesting and swallows real errors. Boundaries (form submit, fetch call) are where catching matters. Internal code can throw.

No speculative abstractions or feature flags (line 85)

The single most expensive habit in AI-generated code. The model sees one place that might one day need configuration and adds a flag, an env var, a hook, and a context provider. None of them are used. All of them have to be maintained. This line tells the model to wait until the second use before extracting and to never invent a flag for a hypothetical.

Only comment non-obvious logic (line 86)

Filler comments rot faster than any other code artifact. A comment that says // increment counter above count++ is wrong the moment someone changes the meaning of the variable. The directive cuts the surface area for that kind of decay.

2385

Lines in src/core/vm-claude-md.ts. The file maintains both global and project CLAUDE.md content for the in-VM agent. Anti-abstraction directives are 4 of those 2385 lines.

src/core/vm-claude-md.ts (mk0r repo, github.com/m13v/appmaker)

The cheaper exit: a mode with no files to maintain

There is a second, harder lever. If the most expensive abstraction is the one that should not exist, the cheapest project is the one with no project structure at all. mk0r's Quick generation path ships a single HTML file with inline CSS and JavaScript, streamed from Claude Haiku in under 30 seconds. No src/components/, no src/hooks/, no lib/. The maintenance unit is the file. To change anything, you re-prompt and the whole file rewrites.

That is a strict trade. You give up routes, npm packages, persistent dev server state, and visual testing. You get an artifact whose entire surface area is one file you can open in any browser, paste into any chat, and version-control as one diff. For a calculator, a landing page, a one-screen tool, a take-home demo, or any throwaway, the maintenance cost of zero abstractions is zero. The maintenance cost of even a small Vite project is not.

0directives in the system prompt
L0line of the last directive
0files to maintain in Quick mode
0places to change the policy for everyone

Read those four numbers in order. The policy is concentrated (one place to edit), the location is specific (one file, four lines), the alternative is bounded (zero structural files in Quick mode), and the leverage is real: the line that says "no speculative abstractions" affects every project the platform ever generates, in one edit, until the line changes. That is the same shape as a compiler flag. It is not the same shape as a code review.

The counterargument worth taking seriously

A reasonable objection: directives in a system prompt are soft. The model will sometimes ignore them. It will extract a third helper anyway. It will add a feature flag for a config you mentioned in passing. So what does this actually buy?

What it buys is a shifted average. With 0 lines in the system prompt the model defaults toward the cheap-to-maintain shape and reaches for the expensive shape only when the user prompt explicitly asks for it. Without those lines the model defaults the other way because most of its training data was written by engineers who already had the bigger problem solved. The directives are not a fence; they are a slope. On a slope, the work flows the right way more often than the wrong way, and that is enough on a project that may not exist next week.

The other objection is the inverse: aren't you stripping out the abstractions a long-lived codebase actually needs? Yes, on purpose, for the lifecycle these projects actually have. A vibe-coded app is closer to a prototype than to a monolith. The default that keeps a five-year codebase healthy is the wrong default for an artifact someone may delete on Sunday. If the project survives, the prompt can change.

What to copy if you do not use mk0r

The actionable part of this is portable. Whatever AI app builder you use, find where the system prompt or the per-project agent config lives. Add four lines in the spirit of the ones above. Keep them short enough that the model will not scroll past. Phrase them as preferences, not bans, because the model handles preferences better than bans. The lines do not have to be the exact mk0r ones; they have to live upstream of the generator, not in a CONTRIBUTING.md it never reads.

And if you find yourself re-prompting the same fix every session ("stop adding feature flags", "keep components small"), that is the signal that the rule belongs in the system prompt instead of in turn 14 of every conversation.

Frequently asked questions

Why is the maintenance cost of AI-generated abstractions a generation-time problem and not a cleanup-time one?

Cleanup is downstream. By the time you are reading the code, the abstraction already exists, every reference to it is duplicated, and the cost of removing it is N file edits where N is how many places used the abstraction. Upstream, the model is one prompt away from never having created the abstraction at all. The system prompt the model reads is where you have leverage. Adding a single line like 'no speculative abstractions or feature flags' to the prompt is roughly free; deleting a real feature flag from a real codebase the model already shipped is not.

What are the four directives mk0r ships into the in-VM system prompt?

They live in src/core/vm-claude-md.ts at lines 83 through 86. Verbatim: 'Keep components small (extract at ~100 lines)', 'Handle errors at boundaries (user input, fetch calls), not internally', 'No speculative abstractions or feature flags', 'Only comment non-obvious logic'. They are written into the global CLAUDE.md that the in-VM agent reads on every session. Anyone who has worked on a long-lived codebase will recognize each line as a maintenance lesson, not a style preference.

Does the model actually obey those lines?

Models follow system prompt directives more reliably than they follow style guides buried in user messages, but not perfectly. The directives shift the average. With them, the agent is more likely to inline a one-off helper than to extract a util/. Without them, it does the opposite by default because most public training data does the opposite. The honest claim is not 'this prevents abstractions' but 'this makes the agent prefer the cheap-to-maintain shape on the first try'. The first try is where the cost compounds.

How does Quick mode change the math?

Quick mode emits a single HTML file with inline CSS and JavaScript from Claude Haiku, streamed in under 30 seconds. There is no src/components/, no hooks/, no lib/. Maintenance cost on a single-file artifact is bounded by the file. Re-prompting regenerates the whole thing. There is no cross-file consistency to keep. For one-shot tools (a calculator, a landing page, a quick share) the cheapest abstraction layer is zero, and the easiest way to enforce zero is to not have a multi-file project to begin with.

What kinds of apps actually deserve the VM mode and its abstractions?

The honest cut is: anything that needs npm packages you do not want to inline, real hot-reload iteration over more than one screen, or visual verification with Playwright. Multi-file structure starts to pay rent there because the screens are real, the routes are real, and the components do get reused. The four directives still apply; the goal is small components, not no components. The dial is between 'one HTML file you regenerate' and 'a Vite project where every abstraction earned its place', and both ends are valid for different jobs.

Doesn't this lead to copy-pasted code instead of helpers?

Sometimes, on purpose. The directive says no speculative abstractions, not no abstractions. Three near-identical lines is fine; the rule is to wait until you can see the third one before extracting, not to extract on the second. The trade is real: a bit more visible duplication for far less premature indirection. On a vibe-coded app that may not survive the week, the duplication is cheap and the indirection is expensive. On a long-lived codebase the call would be different. The directives are tuned for the lifecycle of an AI-generated app, not for a 50-engineer monolith.

Is this just a style guide? Other AI tools have those too.

Style guides usually live in the user message or a CONTRIBUTING.md the model never reads. The mk0r directives live in /root/.claude/CLAUDE.md inside the sandbox VM, written there by the platform on every session. The agent reads them as part of its persistent memory, not as one-off instructions in the chat thread. That placement matters because chat-thread instructions get diluted by every subsequent turn. The CLAUDE.md content does not.

What if I want different rules?

The whole point of vibe coding is that the next prompt overrides the last one. If you want a feature flag, ask for a feature flag. If you want a sprawling util/ folder, prompt for it. The defaults are tuned for the case where you did not pick, which is most cases. The platform is not trying to lock you out of abstractions; it is trying not to default into them. Worth saying out loud because some readers will assume guardrails mean handcuffs.

Want help cutting the abstraction tax in your AI-generated codebase?

Twenty minutes with the team. We will look at where your generator is overshooting and what the smallest change to the system prompt or project shape gets you.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.