Engineering Philosophy
Rescuing an AI-Generated Codebase: A Checklist
There's a sentence you hear a lot in projects that started with AI assistants: "It went really well up to a point, then everything got tangled." Here is how I bring such a project back, step by step, without rewriting it.
There's a sentence you hear a lot in projects that started with AI assistants: "It went really well up to a point, then everything got tangled." I don't blame these projects. Assistants really are fast and usually produce code that works. The problem is that each request makes sense on its own, while nobody designed the whole. A few weeks later you have three functions doing the same job, conflicting data models and packages nobody remembers adding.
The good news: most of these projects don't need to be thrown away. This is roughly the order I follow.
Stop first, then measure
The first step is to stop adding features. It sounds simple, but it's often the hardest part, because the project owner's mind is already on the next feature. Then I capture the real state of the project: does it compile, does it pass type checking, how many errors does the linter report, what do the tests (if any) say? Those numbers are both the starting point and, a few days later, the answer to "have we actually made progress?"
pnpm install --frozen-lockfile
pnpm exec tsc --noEmit # type errors
pnpm lint # style and likely bugs
pnpm build # can it actually ship?Fewer dependencies, less duplication
Assistants often suggest a new package to solve a problem. You end up with two date libraries doing the same job, three different HTTP clients and dozens of unused packages. For every dependency I ask "who uses this, and where?", delete what's unused and consolidate what's duplicated.
Next I gather the places where the same information lives more than once. It's surprisingly common to find the same user type defined three different ways in three files, or the same API URL typed by hand in ten places. Once data models, configuration and constants live in one place, every later fix gets easier.
Restructure while keeping behavior
The rule of refactoring doesn't change: change the structure, not the behavior. Without tests, I write a few end-to-end checks for the most critical flows (sign-in, payment, saving data); if that isn't possible, a short list to verify by hand. Then I move in small steps and build after each one. Along the way I keep an eye out for:
- Empty
catchblocks that silently swallow errors - Chains of
anyandas unknown asthat silence the type system - API keys and passwords embedded in code
- Similarly named functions doing the same job
- Commented-out code blocks nobody can explain
Writing the rules down
If you'll keep using the assistant after the cleanup, the rules need to be explicit: what each folder contains, how a new dependency is added, how things are named, what not to do. Both the people on the team and the assistant follow that document. Making type checking, linting and the build mandatory in CI protects the same rules automatically.
AI made writing code cheap; it didn't make understanding it cheap. Rescuing a codebase is often faster than rewriting it, as long as the order is right: stop, measure, simplify, and only then restructure.