Engineering Management ·

Auditing AI-Generated Codebases: How to Refactor and Productionize Vibe-Coded Software

A practical guide for founders and CTOs who built a working product with Cursor, Claude Code, or Copilot and now need to take it to production quality. Covers systematic audit checklists, prioritization frameworks, refactoring strategies that preserve working behavior, and how to onboard engineers onto an AI-generated codebase.

Auditing AI-Generated Codebases: How to Refactor and Productionize Vibe-Coded Software

You built something that works. Users are signing up. Revenue is coming in. The product was built in three weeks with Cursor or Claude Code, and it does what it needs to do.

Now you need to hire engineers, pass a security review, handle ten times the traffic, or survive due diligence. And the codebase that got you here is not going to get you there.

This is not a failure. This is the expected trajectory. Vibe-coded software serves the same function as a hand-built prototype in hardware: it proves the concept. The mistake is not building it this way. The mistake is assuming it can stay this way.

This guide walks through the systematic process of auditing an AI-generated codebase and bringing it to production quality without breaking the thing that is already working.


What AI-generated codebases look like

Before you start fixing things, it helps to know what patterns you are looking for. AI coding tools produce remarkably consistent anti-patterns across languages, frameworks, and problem domains.

Duplicated logic everywhere. AI tools solve each prompt in isolation. If you asked for user validation in three different features, you got three different implementations. They all work. None of them share code. When you need to change the validation rules, you need to find and update all three (and the two you forgot about).

Inconsistent error handling. One endpoint returns { error: "something went wrong" } with a 200 status. Another throws an unhandled exception. A third returns a proper error response with the correct HTTP status code. The AI generated whatever seemed reasonable for each individual prompt.

Missing abstractions. There is no service layer, no repository pattern, no clear separation between business logic and infrastructure. Database queries live in route handlers. Third-party API calls are scattered across components. Configuration is imported directly where it is used rather than injected.

Hardcoded configuration. API keys in source files. URLs pointing to localhost. Magic numbers that control business logic. Environment-specific values baked into the code rather than read from environment variables or configuration files.

No testing, or tests that test nothing. Either the codebase has zero tests, or the AI was asked to “add tests” and generated tests that assert the code does exactly what the code does. These tests will never catch a regression because they were generated from the implementation, not from requirements.

Implicit assumptions about scale. In-memory session storage. Synchronous processing of tasks that will eventually need to be queued. Single-database queries that work at 100 rows but will not work at 100,000.

None of these are bugs. The software works. That is the tricky part: everything functions correctly at the current scale and the current usage patterns. The problems emerge when any of those conditions change.


The systematic audit

Auditing an AI-generated codebase is different from auditing a traditionally-written one. In a traditional codebase, you can assume some intentional architecture even if it degraded over time. In a vibe-coded codebase, you should assume no intentional architecture until proven otherwise.

Security review

Start here. Everything else can wait.

Walk through every route, endpoint, or API handler. For each one, ask:

  • Is there authentication? Is it applied consistently, or did some routes get missed?
  • Is there authorization? Does the code check that the authenticated user has permission to access this specific resource?
  • Are database queries parameterized, or is there string concatenation building SQL or NoSQL queries?
  • Are secrets (API keys, database credentials, JWT secrets) in the source code, in environment variables, or in a secrets manager?
  • Is user input validated and sanitized before use?
  • Are there file upload endpoints? What are the size limits and type restrictions?
  • Is CORS configured, and does the configuration make sense?

AI tools frequently generate code that handles authentication at the top of a file and then adds a new endpoint at the bottom without the auth middleware. They also tend to generate overly permissive CORS configurations because those are the ones that work immediately during development.

Run npm audit or your language’s equivalent. AI tools pull in dependencies based on training data, and those training data dependencies are often outdated. Check for known vulnerabilities and check for dependencies you do not actually need.

Architecture assessment

Map the actual architecture. Not what you think it should be, but what it is.

  • How many distinct data stores are there, and what is stored where?
  • What are the external service dependencies?
  • Where does business logic live? (It is probably everywhere.)
  • What is the deployment model? Single process, multiple services, serverless functions?
  • Where are the boundaries between components, if any?

Draw this on a whiteboard or in a diagram tool. You will reference this map repeatedly during refactoring.

Data integrity review

This is often where the worst surprises are.

  • Are there database migrations, or was the schema created ad-hoc?
  • Are there foreign key constraints, or is referential integrity enforced only in application code (or not at all)?
  • Is there any data validation at the database level, or only in the application?
  • What happens to data during error conditions? Are there partial writes that could leave the database in an inconsistent state?
  • Is there any audit trail for data changes?

AI-generated database code frequently skips constraints and transactions. The application code handles the happy path correctly, but there is no safety net for the unhappy path.

Performance baseline

Before you change anything, establish a performance baseline.

  • What are the current response times for key endpoints under normal load?
  • What does database query performance look like? Enable slow query logging.
  • What is the memory and CPU usage pattern over a day?
  • Are there any N+1 query patterns? (There almost certainly are.)

You need this baseline so that you can verify your refactoring does not make things worse. This is critical: the goal is to improve the codebase without breaking the working product.


Prioritization: what to fix first

You cannot fix everything at once. The temptation is to rewrite, but that is almost always wrong (more on that below). Instead, prioritize fixes in this order:

1. Security vulnerabilities. Anything that could lead to unauthorized access, data exposure, or injection attacks. Fix these immediately, even before you set up proper testing. A SQL injection vulnerability does not care that your test suite is not ready yet.

2. Data integrity risks. Missing transactions around multi-step writes. Missing constraints that could allow corrupted data. Missing validation that could allow invalid data to enter the system. Corrupted data is harder to recover from than downtime.

3. Scalability bottlenecks. In-memory storage that will not survive a restart. Synchronous processing that will block under load. Database queries that will degrade as data grows. Prioritize these based on your growth trajectory. If you are doubling users monthly, these matter now. If growth is steady, you have more time.

4. Code quality and maintainability. Duplicated logic, missing abstractions, inconsistent patterns. These matter for your ability to hire and move fast, but they are not emergencies. Fix them incrementally as you work in each area of the code.

This ordering is not arbitrary. It follows the cost of being wrong. A security breach can kill the company. Data corruption can lose customers permanently. Performance problems cause churn. Bad code quality slows you down. Each level is serious, but the consequences decrease as you go down the list.


Refactoring strategies that preserve working behavior

The cardinal rule: the product works today. Every change you make must preserve that. Here is how.

Characterization tests

Before refactoring any module, write tests that capture its current behavior. These are not tests based on requirements or specifications. They are tests based on what the code actually does right now, including any bugs.

// Before refactoring the pricing module, capture its actual behavior
describe("pricing module (characterization)", () => {
  it("calculates monthly price for basic plan", () => {
    // This is what the code currently returns.
    // We are not asserting it is correct. We are asserting it does not change.
    expect(calculatePrice("basic", "monthly")).toBe(29);
  });

  it("applies discount for annual billing", () => {
    expect(calculatePrice("basic", "annual")).toBe(290);
    // Note: this is a 17% discount, not 20%. Might be a bug.
    // Document it, do not fix it during refactoring.
  });

  it("returns null for unknown plan", () => {
    // Probably should throw, but currently returns null.
    // Preserve this behavior during refactoring.
    expect(calculatePrice("nonexistent", "monthly")).toBeNull();
  });
});

Write these tests for every module you plan to refactor. Run them after every change. If a characterization test breaks, your refactoring changed behavior, and you need to understand why before proceeding.

Extract, do not rewrite

The safest refactoring pattern for AI-generated code: extract shared logic without changing the call sites initially.

  1. Identify duplicated logic (three validation functions that do almost the same thing).
  2. Write a new shared function that handles all the cases.
  3. Write tests for the new shared function.
  4. Replace one call site at a time, verifying behavior after each change.
  5. Delete the old duplicated functions only after all call sites are migrated.

This is slower than rewriting, and that is the point. Each step is small, testable, and reversible.

Strangler fig for architecture changes

For larger structural changes (introducing a service layer, separating concerns, adding proper dependency injection), use the strangler fig pattern:

  1. Build the new structure alongside the old one.
  2. Route new features through the new structure.
  3. Migrate existing features one at a time.
  4. Remove the old structure when nothing uses it.

This keeps the application working at every step. There is never a moment where half the codebase is refactored and half is not (or rather, there is, but both halves work independently).


When to rewrite vs. refactor

Rewriting is almost always the wrong choice. But “almost always” is not “always.”

Refactor when:

  • The core data model is reasonable.
  • The application structure, while messy, can be incrementally improved.
  • The business logic is complex and encoding real-world rules that took time to discover.
  • Users depend on current behavior, including its quirks.

Rewrite when:

  • The security model is fundamentally broken (no authentication layer at all, and one cannot be retrofitted).
  • The data model is so wrong that every feature is a workaround on top of a workaround.
  • The application is a single monolithic file with no modularity whatsoever (some AI-generated codebases are literally one 5,000-line file).
  • The technology choice is wrong for the domain (a real-time collaboration tool built on synchronous request/response with polling, for example).

If you rewrite, do it module by module, not all at once. Run the old and new versions in parallel. Compare their outputs. This is the strangler fig pattern applied at a larger scale.

Most AI-generated codebases fall into the refactor category. The code works, the structure is messy but not fundamentally wrong, and the business logic encoded in the code is valuable even if the implementation is inelegant.


Setting up CI/CD for a codebase that has none

AI-generated codebases typically have no CI/CD pipeline. Adding one is the highest-leverage infrastructure investment you can make during this process.

Week one: linting and formatting. Add ESLint (or your language’s equivalent) and Prettier. Run them on the entire codebase. Fix all auto-fixable issues. Commit the result as a single formatting commit so that git blame remains useful for everything else. Configure CI to run these on every push.

Week two: type checking and static analysis. If the codebase is JavaScript, add TypeScript incrementally (start with allowJs: true and strict: false). Add a static analysis tool appropriate to your stack. Configure CI to run these checks.

Week three: test infrastructure. Set up the test runner, configure test databases, add a CI step that runs tests. Even if you only have five characterization tests at this point, the infrastructure is ready for more.

Week four: deployment pipeline. Automate deployments. Even a simple pipeline (push to main triggers deploy to staging, manual promotion to production) is vastly better than SSH-ing into a server and running git pull.

Each week builds on the previous one. By the end of the month, you have a pipeline that catches formatting issues, type errors, test failures, and deploys automatically. This pipeline will catch the majority of regressions introduced during ongoing refactoring.


Onboarding engineers onto an AI-generated codebase

Hiring engineers into a vibe-coded codebase requires honesty and structure.

Be upfront in the hiring process. Tell candidates the codebase was AI-generated and needs to be refactored to production quality. This is not a red flag for good engineers. It is an interesting problem. The red flag is hiding it and letting them discover it on day one.

Create an architecture document. This does not need to be comprehensive. A single page that describes the system architecture (using that diagram you drew during the audit), the data model, the key external dependencies, and the known problem areas. Update it as you refactor.

Define coding standards before they start. The worst experience for a new engineer is joining a codebase with no conventions and no guidance on what the conventions should be. Before the first hire starts, establish: the error handling pattern you want, the testing approach, the directory structure, the naming conventions. Write these down. They do not need to be perfect. They need to exist.

Assign ownership of modules. Give each engineer responsibility for specific modules or services. Ownership creates accountability and builds the deep understanding that the original vibe-coding process skipped. The owner is responsible for writing characterization tests, refactoring toward the agreed patterns, and being the expert that others can consult.

Pair on the first refactoring. Have a new engineer pair with someone who has been through the audit process for their first refactoring task. This transfers context that no document can capture: why certain things are the way they are, which behaviors are intentional versus accidental, where the hidden dependencies lurk.


The 90-day plan

Bringing this together into a concrete timeline:

Days 1 through 14: Security and data integrity. Complete the security review. Fix critical vulnerabilities. Add database constraints and transactions where they are missing. Set up linting and formatting.

Days 15 through 45: Infrastructure and testing. Set up CI/CD. Write characterization tests for the most critical business logic. Add type checking. Establish monitoring and alerting so you know when things break.

Days 46 through 90: Systematic refactoring. Begin the module-by-module refactoring. Extract shared logic. Introduce proper abstractions. Replace hardcoded configuration with environment variables. Onboard your first engineers and assign module ownership.

This is not fast. It is not supposed to be. The product that took three weeks to vibe-code will take three months to productionize. That ratio (roughly 4:1) is consistent across every AI-generated codebase audit we have been involved with. Plan for it.


The right mindset

The vibe-coded prototype got you to product-market fit faster than any traditional development process could have. That has real value. The code quality problems are the cost of that speed, and it is a cost worth paying when the alternative was spending six months building something nobody wanted.

The work now is different. It is slower, more methodical, and less exciting than the rapid iteration that built the prototype. But it is the work that turns a working demo into a product that can scale, survive an outage, pass a security audit, and support a team of engineers building on top of it.

Approach the audit with respect for what the AI-generated code accomplished and clear-eyed honesty about what it cannot sustain. That combination of respect and honesty is the right foundation for the work ahead.

More in Engineering Management

The AI Productivity Paradox: Why Your Team Ships More Code but Delivers Less
Engineering Management ·

The AI Productivity Paradox: Why Your Team Ships More Code but Delivers Less

AI coding tools create an illusion of velocity at the individual level while degrading team-level delivery, quality, and maintainability. The core mechanism is a 5x+ senior/junior productivity split that aggregate metrics hide entirely.

The AI Productivity Paradox: Why Your Team Ships More Code but Delivers Less Value
Engineering Management ·

The AI Productivity Paradox: Why Your Team Ships More Code but Delivers Less Value

93% of developers use AI coding tools, yet DORA metrics haven't improved proportionally. Individual output rises while bug rates, review times, and deployment instability climb. Here is why individual AI productivity gains create organizational drag, and how to fix it with architecture-level guardrails.

Why Your Engineering Team Is Shipping Slower Than 6 Months Ago
Engineering Management ·

Why Your Engineering Team Is Shipping Slower Than 6 Months Ago

Engineering velocity declines at seed-to-Series-A startups for predictable, diagnosable reasons. Process debt, unclear ownership, hiring mistakes, burnout, and architectural bottlenecks all compound. Here is a diagnostic framework you can run in one afternoon, plus a tradeoffs table for each intervention.

The AI Ratchet Effect: Why Giving Your Engineering Team AI Tools Made Them Work Harder, Not Smarter
Engineering Management ·

The AI Ratchet Effect: Why Giving Your Engineering Team AI Tools Made Them Work Harder, Not Smarter

67% of engineers who adopted AI tools in 2025 worked more hours by year-end, not fewer. This is the AI ratchet effect: management converts every productivity gain into a permanently higher baseline. Here is how it happens, why it is worse at startups, and what a sustainable AI adoption cadence actually looks like.