The prototype works. People understand the demo. Then somebody opens the repository and says the whole thing must be rewritten before launch.

Perhaps they are right. Usually the word “whole” is doing more work than the evidence.

Keep the code that proves the product. Replace the code that makes the promise unsafe.

A rewrite can remove serious risk. It can also spend three months recreating behavior nobody wanted, introduce new failures, and postpone the customer evidence that would have changed the design anyway.

Do not decide from shame, elegance, or the fact that AI wrote it. Decide from consequence, understanding, and the cost of change.

Decide what kind of artifact you have

A prototype demonstrates an idea under controlled conditions. A product delivers a result when the founder is absent.

The same code can move from one category to the other, but the standard changes.

Ask what the current build has actually proved:

  • A target user understands the problem.
  • They can complete one useful job.
  • The output changes a real decision or workflow.
  • They return when the problem recurs.
  • They will pay, share data, invite a colleague, or make another commitment.

Then ask what remains protected only by the demo conditions:

  • the founder chooses the safe input;
  • every account belongs to the team;
  • the database can be reset;
  • no real payment moves;
  • secrets sit in a local file;
  • failures are retried by hand;
  • the happy path is the only path shown;
  • the founder explains every confusing screen.

YC's practical MVP guide distinguishes a prototype from a product by what happens outside the curated test. That distinction matters more than whether the code looks modern.

Write the launch promise

Name one user, one job, and one result the public version must deliver.

For example:

An independent founder submits a public website, receives a private launch-readiness report, and can delete that report without another user seeing it.

Now list the consequences attached to that promise:

  • identity and access;
  • private input and output;
  • money or usage cost;
  • destructive actions;
  • external messages or publication;
  • legal or operational claims;
  • recovery when the job fails.

This defines the review boundary. A rewrite proposal that cannot name which promise it protects is an aesthetic preference with a delivery estimate.

Ignore harmless ugliness

Early code may contain repeated components, long files, inconsistent names, unused abstractions, awkward CSS, and comments written during a confused afternoon.

These can slow future work. They do not all block launch.

Keep an ugly section for now when:

  • its behavior is understood;
  • the data and permission boundary is sound;
  • a failure is contained and recoverable;
  • tests cover the important outcomes;
  • the code changes rarely;
  • replacing it would not teach you about the user;
  • an owner can operate it when it fails.

Add a specific note, not “clean this up.” State what makes the area costly, what event should trigger repair, and what current product work outranks it.

An untidy pricing card can wait. A polished cross-account data leak cannot.

Replace dangerous shortcuts before exposure

Some prototype shortcuts become liabilities the moment strangers arrive.

Treat these as replacement or hardening candidates before launch:

  • provider secrets shipped to the browser;
  • authorization enforced only by hidden buttons;
  • every database query filtered by whichever account ID the client sends;
  • payment completion trusted from a return page instead of a signed server event;
  • destructive requests without ownership checks or confirmation;
  • schema changes applied without a backup or migration path;
  • public file URLs used for private material;
  • background work with no idempotency, timeout, or retry ceiling;
  • agent loops with no tool, time, or cost boundary;
  • logs containing tokens, private records, or full prompts;
  • default administrative routes left public;
  • dependencies that cannot run or be maintained in production.

Do not bury these inside a general refactor. Give each risk an invariant and a proof.

For example:

Account A cannot read, update, export, or delete an object owned by account B.

Test it with two real accounts across page routes, APIs, files, jobs, and support tools. The AI-code review guide explains how to trace an invariant from request to storage instead of asking for a general quality score.

Classify every important area

Use four buckets.

Keep

The current implementation is understood, bounded, and good enough for the next exposure. Preserve it even if you would design it differently today.

Isolate

The implementation is uncertain but can sit behind a narrow interface, feature flag, queue, manual review, or small beta. Limit who can reach it and what it can affect.

Replace before launch

The area can charge, expose, erase, impersonate, publish, or trap users, and you cannot prove the required boundary. Replace the smallest responsible section before widening access.

Rewrite after evidence

The area creates maintenance cost but does not threaten the launch promise. Record it and wait for real usage to reveal the correct shape.

This is more useful than one verdict for the repository. A codebase is rarely safe or unsafe as a single object.

Require evidence for “rewrite”

A rewrite candidate should come with a concrete claim:

Current failure: What behavior or change is unsafe or prohibitively costly? Affected promise: Which user result or boundary does it threaten? Reproduction: How can another person observe the problem? Smallest replacement: Which component or seam must change? Migration: How do existing data and users cross safely? Proof: Which tests and production signals show the replacement works? Rollback: How can exposure return to the previous safe state? Opportunity cost: Which customer learning will wait?

“This code is spaghetti” may describe real pain. It does not yet answer those questions.

Hacker News discussions about startup technical debt often return to a practical tension: founders need room for refactoring, but product work and debt reduction share the same limited time. Make the tradeoff visible instead of pretending cleanup is free.

Know when a broad rewrite is justified

A larger rewrite may be the honest choice when several of these are true:

  • nobody can explain the data model or request path;
  • identity and ownership rules cannot be enforced at a stable server boundary;
  • small changes routinely break unrelated product behavior;
  • production cannot be reproduced or rolled back;
  • the chosen runtime cannot support required libraries or workloads;
  • the storage model contradicts the core customer job;
  • a critical dependency is abandoned, unsafe, or impossible to replace locally;
  • tests cannot observe important behavior without recreating the system;
  • every new user or tenant multiplies exception logic;
  • migration later becomes materially more dangerous with each customer.

Even then, define the replacement around the launch promise. “New stack” is not a user requirement.

The community debate around whether prototypes should be rewritten is useful because both sides expose a truth: prototypes discover the problem, while production makes somebody accountable for the failure modes. The decision belongs at that accountability boundary.

Harden one vertical slice

Do not begin with the folder tree. Begin with the customer journey.

Choose one complete slice:

  1. visitor reaches the public page;
  2. account is created and verified;
  3. user provides the smallest real input;
  4. server confirms identity and permission;
  5. work runs once within time and cost limits;
  6. result is stored under the right owner;
  7. user receives and can revisit the result;
  8. failure leaves a recoverable state;
  9. support can diagnose the request safely;
  10. deletion removes or schedules removal of the right data.

Harden this slice end to end. Leave secondary features behind flags or out of the first release.

YC's guide to shipping early and often argues for a v0 that is just fit for purpose because early products contain enormous uncertainty. Selective hardening respects that uncertainty while refusing to gamble with consequence.

Put seams around generated code

Vibe coding is fast partly because the model can cross several layers in one answer. That same speed can blur ownership.

Create explicit seams around:

  • authentication and session lookup;
  • authorization decisions;
  • database access;
  • payment and webhook handling;
  • model and external API calls;
  • email and public side effects;
  • file storage;
  • background jobs;
  • logging and error reporting.

Give each seam a narrow contract. The rest of the app should ask, “Can this user read this report?” rather than reimplementing three ownership filters in three routes.

This does not require enterprise architecture. It requires one place where the important decision can be understood, tested, and changed.

When asking an AI tool to replace an area, provide the invariant, current contract, failure case, migration constraint, and tests. Do not ask it to “make the backend production ready.” Broad prompts create broad confidence and unclear responsibility.

Write tests from behavior, not from the old code

A rewrite that copies implementation details can preserve the defect with cleaner names.

Build a characterization set from product behavior:

  • ordinary successful job;
  • wrong owner;
  • missing identity;
  • duplicate submission;
  • maximum permitted input;
  • provider timeout after work begins;
  • partial database failure;
  • stale or deleted object;
  • payment or webhook replay;
  • account deletion with dependent records.

Record expected user-visible result, stored state, side effects, logs, and retry behavior.

Run the same set against the current and replacement paths where safe. Differences must be deliberate. If you cannot state which behavior should change, the rewrite is not ready to merge.

Migrate behind a narrow switch

Prefer a replaceable seam over a flag day.

A safe sequence can look like this:

  1. Put the current behavior behind one interface.
  2. Add observation around inputs, outcomes, latency, and failures.
  3. Build the replacement against the same contract.
  4. Replay sanitized or synthetic cases offline.
  5. Send a small internal or beta group to the new path.
  6. Compare outcomes and operational burden.
  7. Increase exposure in controlled steps.
  8. Keep one tested rollback during the transition.
  9. Remove the old path only after the new one survives real use.

Avoid dual writes unless you can reconcile them. Writing important data to two systems creates a third problem: deciding which copy is true after one succeeds and the other fails.

If the data model must change, rehearse the migration on a realistic copy without exposing customer data. Back up first. Measure duration, lock behavior, failure recovery, and rollback.

Launch smaller when certainty is expensive

The choice is not always rewrite or publish to the world.

Reduce exposure:

  • invite five known users;
  • restrict the supported input;
  • remove payment from the first test;
  • review outputs before delivery;
  • cap stored data and job cost;
  • keep consequential actions manual;
  • use a closed beta with clear limits;
  • publish the marketing page while the product remains gated.

YC's Wizard of Oz guidance recommends using human work to test whether customers need the difficult technology before making the full investment. Manual operation is not permission to fake automation. Tell users what the service does and protect their data accordingly.

Use the closed-versus-open beta guide to match exposure to consequence and support capacity.

Use a pre-launch rewrite decision

For each important area, record:

Customer job: Current implementation: What has been proved: Worst plausible failure: Invariant: Current evidence: Decision: Keep, isolate, replace before launch, or rewrite after evidence. Smallest safe change: Test that can fail: Owner: Rollback: Review trigger:

Then run the production checklist for an AI-built app against the hardened vertical slice on the real public domain.

Ship the proof, not the debt story

Users do not care whether the prototype was vibe coded, hand written, outsourced, or rescued from a weekend project. They care whether the result arrives and whether the product keeps its promises.

Do not rewrite because experienced engineers might laugh at the repository. Do not ship because a founder essay said embarrassment is healthy. Neither audience carries the consequence.

Keep what you can explain. Isolate what you are still learning. Replace what can harm the user. Rewrite the rest when evidence, not discomfort, earns the time.

That is not slower than shipping. It is how a prototype becomes a product without pretending it was one all along.