Fix or rewrite a vibe-coded app? Rewrite it from a spec, smaller.

Your app was built with Cursor, Lovable, Bolt, Replit or an agent. It works in the demo, but each fix breaks something else, and you are not sure it is safe. Patching it keeps the design that caused the problems. A rewrite from a written spec now costs less than most teams expect, and it gives you a smaller system that you can test.

Fix or Rewrite a Vibe-Coded App? Why I Rewrite From a Spec

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

Most clean-up services give the same advice. Audit the code, fix the worst problems and do not rewrite. That advice was correct when a rewrite needed a team of engineers for many months. Now it is less correct.

An agent can write a small application in days. The expensive part is no longer the code. The expensive part is to know exactly what the application must do. If you write that down first, a rewrite often costs less than a clean-up. The result is also smaller.

Thus, I offer a complete rewrite. The process has three parts:

  1. A written specification from the behavior of the current app.
  2. Tests from the specification.
  3. New code that passes the tests, with fewer parts than before.

This page explains why I use this process, how it operates and which best practices go into the new version. It also explains when I tell clients to keep the old code.

What is a vibe-coded app?

A vibe-coded app is an app that somebody built mostly with prompts to an AI tool. Common tools are Cursor, Lovable, Bolt, Replit, v0 and coding agents. The person describes a feature. The tool writes the code. The person tests the result in the browser and writes the next prompt.

This process is fast. A founder can make a working demo in a weekend. Many good products start in this way. The problems start when paying users, customer data and money come into the app.

Why is a vibe-coded app hard to fix?

Each prompt solves the problem in front of it. Nobody decides the design for the full app. The result has a pattern:

  • The same logic is in many locations. Three components each check permissions in a different way. A fix in one location leaves the other two locations broken.
  • No tests, or tests that check nothing. The generated tests pass because they test the code as it is. They do not test the behavior that the user needs.
  • Secrets and checks are in the browser. API keys are in the front end. Permission checks are in the client. Database rules are open. The app operates, so nobody looks.
  • Too many layers. Wrappers around wrappers, libraries that nothing uses, state managers for state that does not exist. A prompt added each layer. No prompt removed it.
  • The code is the only specification. Nobody wrote down what the app must do. The code is the only record, and the code contains the bugs.

The last point is the reason why fixes are slow. For each fix, a person must first find out what the code must do. Then the person must make sure that the fix did not break a different copy of the same logic. You pay for this work again for each bug. I wrote about this cost in what a slop grenade is.

What are the common security problems?

These problems occur frequently in vibe-coded apps:

  • API keys in the front-end code. Any user can open the browser tools and copy the key. Then the user can use your paid account.
  • Permission checks only in the browser. The page hides a button from normal users. But the server accepts the request from any user who sends it.
  • Open database rules. Some hosted databases let the browser read and write tables directly. If the rules are open, any user can read the data of all other users.
  • No limits on requests. One user can send thousands of requests. If each request calls a paid model, the bill increases quickly.
  • Old or unsafe packages. The AI tool added a package because it knew the name. Nobody checked if the package is maintained or safe.

A clean-up can fix each problem one at a time. But the cause stays. The design puts trust in the browser. Each new feature that a prompt adds can make the same error again.

Why rewrite instead of fix?

Fix the old code

  • Keeps the duplicate logic and the extra layers
  • Each fix needs a person to find the intent again
  • You fix security holes one at a time, when you find them
  • You end with the same design, a little safer

Rewrite from a specification

  • The specification says each rule one time
  • Tests come from the specification, not from the old code
  • Security is a part of the design: server-side checks, secrets in a vault
  • You end with a smaller system that you can test and change

A rewrite also lets you remove parts. Most vibe-coded apps have features that nobody uses. They also have parts that do the same job. These parts do not go into the specification. Thus, they do not go into the new code.

Why is a rewrite cheaper now?

Before coding agents, the cost of a rewrite was mostly the cost of the code. A team had to type each line again. For a medium app, this took months.

Now an agent can write most of the code from a clear specification. A person reviews the design and each important change. The cost moves from the code to two other items:

  • The specification. What must the app do? Which rules must it obey?
  • The tests. How do we know that the new app obeys the rules?

Both items are useful after the rewrite. The specification tells the next engineer what the app does. The tests prevent the next prompt from breaking it. A clean-up does not give you either item.

How does a rewrite from a specification operate?

The rule is simple. The specification is the source of truth, not the old code. If the agent reads the old code to decide what to build, it copies the old errors into a new language. I explain this trap in legacy code is not the spec.

Step 1: Record the behavior

Use the current app as a user. Write down each screen, each action and each result. Read the logs and the database to find what users actually do. This is the only step where the old app is the reference.

Also record the errors. If a user complained about a result, write down the correct result. The new app must give the correct result, not the old result.

Step 2: Write the specification

Write one short document. It contains these parts:

  • Users and roles. Who uses the app? What can each role see and do?
  • Features. Each feature as an input and a result.
  • Rules. Who can see which data? Which fields are necessary? What must never occur?
  • Data model. Which records exist, and how they connect.
  • Integrations. Which external services the app uses, and for what.

You review the document and correct it. Most of the decisions occur in this step. A decision in a document costs minutes. The same decision in code costs days.

Step 3: Make the scope smaller

Mark each feature as “keep”, “change” or “remove”. Use the usage data to decide. If nobody used a feature in the last three months, remove it. A smaller specification is the main reason why the new system is simpler.

Step 4: Write tests from the specification

Each rule becomes a test. Permission rules get tests that try to break them. For example: “A user from company A tries to read an invoice from company B. The server refuses.” These tests define “done”.

Step 5: Build to the tests

Agents write most of the code. A person owns the design. That person reviews each important change and keeps the structure consistent. The tests decide when a part is complete.

The person also stops the agent from adding parts that the specification does not need. An agent adds layers easily. A reviewer must remove them.

Step 6: Move the data

Write a script that moves the old data into the new data model. Operate it on a copy first. Compare the number of records and examine sample records. Fix the script until the counts agree. Then operate it on the production data.

Step 7: Change over

If possible, operate both versions for a short period. Send a small part of the users to the new version. Look at the errors. Then move all users. Keep the old version available, so that you can go back if a large problem occurs.

The old app is a witness, not a guide. Use it to learn what users do. Do not use it to decide how the new app works. Each time that the old app and the specification disagree, the specification wins, or you change the specification on purpose.

Which best practices go into the new version?

These are the defaults. The specification can change each default if there is a reason.

  • One location for each rule. The server defines permissions, validation and prices one time. All parts of the app use that definition.
  • Security on the server. No secrets in the browser. Each request checks who the user is and what the user can do. Database rules refuse access by default.
  • Fewer dependencies. Each library must have a clear reason. Fewer packages mean fewer updates and fewer points of attack.
  • Common technology. A standard framework and a standard database that many engineers know. Your next engineer must be able to read the code.
  • Tests in CI. Each change operates the tests before it can go to production.
  • Logs and error tracking. You see errors before your users report them.
  • Limits on requests. Each user and each paid API has a limit. A bug or an attack cannot make a large bill.
  • All assets in your accounts. The repository, the hosting, the database and the keys are in your name. The handover checklist lists them.
  • A short README. How to start the app, how to operate the tests and how to deploy.

If the app calls an LLM

Many vibe-coded apps also call a language model. For these parts, add these practices:

  • Fixed model versions. The code gives the exact version. The provider cannot change it without a warning.
  • An eval set for each AI feature. Thirty to fifty real inputs with known correct answers. Each change to a prompt or model must pass it.
  • A known cost for each task. You know what one user action costs in model fees.
  • A rule for wrong answers. The specification says what the app does when the model is not sure: refuse, ask a human or give a safe default.
  • Prompts in the repository. Not in a database or an admin panel. Each change has a version and a review.

When must I not rewrite?

A rewrite is the wrong decision in these conditions:

  • The app is small and mostly correct. If the problems are some exposed keys and missing checks, fix them. A clean-up of two days is better than a rewrite of two weeks.
  • Nobody can say what the app must do. If you cannot review the specification, the rewrite has no target. Find the product owner first.
  • The product is not proven. If you still look for users, the problem is not the code. Keep the old app and test the market.
  • You cannot stop new features for some weeks. A rewrite needs a short freeze on the old app. If customers need changes each week, fix the old app now. Plan the rewrite for a quieter period.

What if the risk is too high for a full change?

Some apps have many users and much data. A full change on one day is a large risk. In this case, rewrite one part at a time. Start with the part that has the most bugs or the largest security risk. Put the new part behind the same address. Move users to it. Then go to the next part.

This takes longer. But each step is small, and you can go back after each step.

How long does a rewrite take?

The duration depends on the size of the specification, not the size of the old code. Many vibe-coded apps have much more code than features.

The first step is usually one to two weeks. In that time, I record the behavior and write the specification with you. After that, the specification gives a scope. The scope gives a price that both sides can check.

What do I receive at the end?

  • The new code in your repository.
  • The specification, as a document in the repository.
  • The tests, which operate in CI on each change.
  • The data move script and the report from the move.
  • A README for the next engineer.
  • All accounts in your name.

Next step

Before you decide, write one paragraph for each main feature. Say who uses it, what goes in and what comes out. If you can do this in one day, the app is a good candidate for a rewrite. If you cannot do it, that is the first problem to solve.

Do you want me to do the rewrite? Read how my fractional AI CTO engagement operates and how I set rates. Did an agency build the app, and does the project go badly? Read how to rescue a failing AI project.

Follow in Google

Make Metaheuristic a preferred source.

One tap and posts like this one surface higher in your Top Stories.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →