How to Make AI Write Clean Code Every Time
Most developers get mediocre code from AI. Here's why — and the system that fixes it.
The problem nobody talks about
AI will write you working code almost instantly. It will also write you unmaintainable, inconsistent, security-naive code just as fast — and it will do so cheerfully, confidently, and without a word of warning.
The problem isn't that AI can't write clean code. It can. The problem is that it won't unless you build a system that makes it.
Here's what happens without a system: you start a session, describe what you want, get code that works. Next session, different conventions. Third session, the AI sees the patterns already in the codebase and mirrors them — including the bad ones. By month three, you have a codebase that looks like it was written by six different developers with six different opinions, because effectively it was.
"Without a mechanism to enforce standards across sessions, standards don't get enforced."
This isn't a criticism of AI. It's a description of how AI works. It has no persistent memory. It optimises for satisfying the current request. It defers to whatever it sees in context. The fix is a system.
Why AI code quality degrades over time
Three forces work against you:
None of this is malicious. It's the default behaviour of a tool optimising for the wrong thing. The system below changes what it optimises for.
The methodology: four steps before you write code
The CLAUDE.md file
CLAUDE.md is the most important file in your repository. It lives at the repo root. Every AI session reads it before doing anything. It is how you get consistent behaviour from a tool that has no memory.
A good CLAUDE.md contains:
What the project is — one table, each module, who uses it, what it does. Two minutes to read, eliminates ten minutes of context-setting per session.
Import rules — where things live, and what is allowed to import from what. This is the rule that makes modularity actually work:
# The rule that makes plug-and-play possible packages/shared/ ← imports from nowhere else apps/client/ ← imports from shared only apps/api/ ← imports from shared only # Apps never import from each other. Ever. # If app A needs something from app B, it belongs in shared.
The explicit prohibitions table — this is more effective than positive rules alone. A table that says "use any" in one column and "defeats the type system, define a proper type" in the next is harder to ignore than a style guide paragraph.
Before every commit checklist — a literal checklist the AI runs before finishing any task. Zero TypeScript errors. No hardcoded values. No empty catch blocks. No imports across app boundaries. This is the gate.
The clean code rules
These go verbatim into CLAUDE.md. They are not a style guide — they are enforced by the commit checklist.
| Rule | What it means in practice |
|---|---|
| Intention-revealing names | If a comment is needed to explain a name, rename it. d is not a name. clientRecord is. |
| One responsibility | If you can extract a meaningful sub-function, the original does more than one thing. |
| Max 3 parameters | Beyond 3, group into a typed object. No boolean flag parameters — split into two functions. |
| Throw, don't return null | Empty catch blocks are bugs. Fail loudly. A clear error is better than silent wrong behaviour. |
| Comments explain why | Never what. If code needs a comment to explain what it does, rewrite the code. |
| No any in TypeScript | Ever. Use unknown and narrow, or define the type. |
| Boy Scout Rule | Leave every file cleaner than you found it. |
The single test for any line of code
Read the code aloud. If it doesn't sound like clear prose describing what it does, it needs work.
What this actually changes
With this system in place, every AI session opens by reading CLAUDE.md. It knows the structure, the rules, the prohibited patterns. It knows what shared means and why apps don't import from each other. It knows that any is a blocker, that empty catch blocks are bugs, that sensitive data doesn't get hardcoded.
It still makes mistakes. You still need to review. But the mistakes are smaller, the patterns are consistent, and the codebase accumulates quality instead of debt.
The methodology is the memory the AI doesn't have. You build it once per project. It compounds across every session after that.
Getting started
Get the full CLAUDE.md template, clean code rules reference, and project setup checklist from the methodology guide.
Define what each app does, the auth model, data rules, and any domain-specific prohibitions before writing a line of code.
Create packages/shared with schema.ts and types.ts. Enforce import rules in tsconfig. Do this before any app code.
"Read CLAUDE.md before you start." Four words. Run the commit checklist before finishing. That's the whole system.
Scan your codebase first.
See what violations are already there before setting up the system to prevent new ones.