How to Write Less Messy Code With Claude Code
Six habits that keep Claude Code output clean, reviewable, and maintainable, from CLAUDE.md rules to weekly repo audits and the Ponytail skill.
Six habits that keep Claude Code output clean, reviewable, and maintainable, from CLAUDE.md rules to weekly repo audits and the Ponytail skill.
Anyone who has handed Claude Code a vague ticket and come back an hour later to a pull request full of new folders, new abstractions, and a service layer nobody asked for knows the feeling. The code works. It also doesn't look like anything a human on your team would have written, and six months from now nobody will remember why half of it exists.
This isn't a Claude Code problem specifically. It's what happens whenever you give an eager, fast, tireless coder a loosely defined task and no shared standards. The fix isn't to stop using AI coding tools. It's to treat Claude Code the way you'd treat a very fast, very literal new hire: give it rules, review its work in small pieces, and make sure the reasoning behind decisions is written down somewhere other than a Slack thread that scrolls away.
Below are six practices that consistently keep AI-assisted codebases from turning into a mess, along with a couple of tools worth trying if minimal, boring code is the goal.
A well-written prompt gets you a good first draft. It doesn't get you a codebase that stays consistent across fifty pull requests, three contributors, and four months of feature work. Prompts are ephemeral. Standards need to persist.
The core issue is that a language model has no innate sense of your project's architecture. It doesn't know that your team keeps domain logic separate from UI components, or that "helper.js" is where good intentions go to die. Unless you tell it, and keep telling it, it will happily import a database client directly into a React component because that's the fastest path to a working feature.
None of this is about distrust of AI coding tools. It's the same discipline you'd want from any fast-moving contributor: clear rules, small reviewable changes, and a paper trail for decisions.
The six habits below build a system where Claude Code produces work that looks like it was written by someone who has been on your team for a year, not someone who just joined this morning.
Claude Code automatically reads a CLAUDE.md file at the root of your repository before it starts working. This is the single highest-leverage thing you can do to reduce mess, because it turns standards you'd otherwise repeat in every prompt into something the model reads by default.
A useful CLAUDE.md isn't a wall of generic advice. It's a short, specific list of the rules your team actually enforces in code review. A few examples worth including:
/ui may only call functions from /domain, never /data directly."if (age > 18) becomes if (age > MINIMUM_ADULT_AGE).usr, cfg, and mgr are banned in favor of user, config, and manager. This sounds small but it's one of the fastest ways to make generated code readable by humans months later.Write your CLAUDE.md the way you'd write onboarding notes for a new hire, not a style guide nobody reads. Short, direct sentences with a real example beat a long list of abstract principles.
Here's a short starting point you can drop into the root of a repository and adjust to fit your project:
Project Coding Standards
These rules apply to all code written or modified in this repository. Follow them by default, without being asked.
Architecture
- Do not mix layers. `domain`, `ui`, and `data` stay separate.
- `ui` may call `domain`. It may never import from `data` directly.
- `domain` must not import anything from `ui`.
- `data` exposes functions/interfaces that `domain` calls. It never reaches back into `domain` or `ui`.
- Decouple modules. Depend on interfaces/types, not concrete implementations, especially across layer boundaries.
- Before adding a new abstraction, class, or wrapper, check whether an existing one already does the job.
Naming
- No abbreviations in identifiers. Use `user`, `config`, `manager`, `request`, not `usr`, `cfg`, `mgr`, `req`.
- Names should describe intent, not implementation (`getActiveUsers`, not `getUsers2`).
Values
- No magic numbers or strings. Any literal with business meaning gets a named constant.
- Bad: `if (age > 18)`
- Good: `if (age > MINIMUM_ADULT_AGE)`
- Put shared constants in one place per domain, not scattered inline.
Size and Structure
- Soft limit: ~300 lines per file, ~40 lines per function. Split when you cross this.
- One responsibility per function. If you need "and" to describe what it does, split it.
Before You Write Code
- State the plan (files touched, new abstractions, if any) before writing code for anything non-trivial.
- Check `/docs/adr` for existing architecture decisions before introducing a new pattern.
- Prefer the smallest correct change. Don't add flexibility, config options, or abstractions that weren't asked for.
Before You Finish
- Confirm the change matches the acceptance criteria it was meant to satisfy.
- Confirm no layer boundaries were crossed and no magic numbers or abbreviations were introduced.
You don't need to write this from scratch. Pull the rules straight out of your existing linter config, your team's PR review comments, and any architecture decision records you already have. If your team disagrees on a rule, that disagreement will show up in the generated code before it shows up anywhere else, which is actually a useful way to force the conversation.
The biggest source of mess isn't bad code, it's too much code arriving at once. When Claude Code is asked to build an entire feature in one shot, it makes dozens of small architectural decisions along the way, and by the time a human reviews the diff, unwinding a single bad choice means touching a dozen files.
The fix is to slow the loop down deliberately:
A useful habit is to literally ask Claude Code "what's the smallest version of this change that would still be correct and reviewable?" before letting it write code. It's a simple prompt that consistently produces smaller, cleaner diffs.
This is slower per feature, but it's dramatically faster in aggregate, because it eliminates the multi-hour untangling sessions that come from reviewing a 900-line PR that touched six unrelated concerns.
Small batch reviews catch local problems. They don't catch drift. A pattern that looked reasonable in isolation, repeated across twenty pull requests over a month, can quietly turn into three competing conventions for the same thing: three different date formatting utilities, two different error handling patterns, a UI layer that has slowly grown three different ways of fetching data.
A weekly full-repository review exists to catch exactly this. It doesn't need to be exhaustive. A structured pass that asks Claude Code (or a human, or both) to look for the following usually surfaces the real problems:
CLAUDE.mdWeekly is a starting cadence, not a law. Fast-moving early-stage projects sometimes need this every few days; stable, mature codebases can often stretch it to every two weeks.
Treat this review as a standing calendar event, the same way you'd treat a retro or a standup. Skipping it once is fine. Skipping it for a month is how a codebase quietly becomes unmaintainable.
When Claude Code (or a human) picks an approach that will affect how future code gets written, that reasoning needs to live somewhere durable. A short Architecture Decision Record (ADR) does the job: what was decided, what alternatives were considered, and why.
This matters more with AI-assisted development than it did before, for a specific reason: a model has no memory of a decision made three weeks and forty conversations ago. Without a written record, it will happily suggest an approach that contradicts a decision your team already made and moved on from, and nobody will notice until the two approaches collide in a merge conflict.
A lightweight ADR template is enough:
| Field | Purpose |
|---|---|
| Context | What problem forced this decision |
| Decision | What was chosen |
| Alternatives considered | What else was on the table, and why it lost |
| Consequences | What this makes easier, harder, or locked in |
Store these in a /docs/adr folder inside the repo itself, not in a wiki that lives outside version control. That way Claude Code can read them as part of the codebase, and they stay attached to the code they describe as it evolves.
Point your CLAUDE.md at the ADR folder explicitly, something like "check /docs/adr before introducing a new pattern for X." This closes the loop between the record and the model that needs to respect it.
Asking "does this implementation match the acceptance criteria?" without giving Claude the actual acceptance criteria, the relevant existing code, and any constraints that aren't obvious from the diff alone produces a review that sounds confident and catches almost nothing. A model can only check what it can see.
A thorough AC review needs:
CLAUDE.md rulesA review is only as good as the context behind it. Treat the review prompt with the same care as the coding prompt; a rushed review prompt produces a rubber-stamp review.
The habit worth building is to keep a standard "review packet" for every ticket: criteria, standards, relevant ADRs, and diff, all in one place. This turns AC review from a vague vibe check into something closer to a checklist a second engineer could run.
Even with strong standards and tight reviews, AI coding agents have a structural bias toward doing more than necessary: adding a component library for something three lines of native HTML would handle, writing a wrapper around a function that already exists in the standard library, or building "flexibility" nobody requested.
Ponytail acts as a structural defense against AI over-engineering. Before writing anything, it runs the task through a short ladder of questions: does this need to exist at all, is something equivalent already in the codebase, does the standard library cover it, can it be done in one line. It deliberately does not touch validation, error handling, security, or accessibility, so it narrows scope on complexity, not on correctness.
The philosophy behind it is simple: less is more. Smaller diffs are easier to review, smaller surface area means fewer places for bugs to hide, and code that reaches for native platform features before third-party dependencies is cheaper to maintain for years, not just easier to write today.
| Practice | Problem It Solves | Effort to Adopt |
|---|---|---|
| CLAUDE.md standards | Model doesn't know your architecture or conventions | Low, one file |
| Small batch review | Large diffs hide bad decisions | Medium, changes workflow |
| Weekly full repo review | Drift and duplication accumulate silently | Medium, needs a recurring slot |
| ADRs for architecture decisions | Reasoning gets lost between sessions | Low, one folder |
| Full context for AC review | Reviews miss what they can't see | Low, a habit |
| Ponytail / minimalism layer | Model over-builds by default | Low, install a skill |
None of these six practices is expensive on its own. The value comes from running them together, so that standards, review cadence, documentation, and a bias toward simplicity reinforce each other instead of relying on any single safeguard to catch everything.
The common thread across all six practices is that they replace one-off prompting with a system. A good prompt gets you through today's feature. A CLAUDE.md file, a documented ADR trail, a weekly review habit, and a minimalism-focused skill like Ponytail get you a codebase that still makes sense a year from now, regardless of who, or what, wrote the last hundred pull requests.
Start small if this feels like a lot at once. A single CLAUDE.md file with five real rules and a habit of reviewing in smaller batches will already cut down most of the mess. Add the weekly review and ADR habit once the first two feel natural, and layer in a minimalism skill like Ponytail once you're comfortable with the workflow. The goal isn't a perfect process on day one, it's a codebase that gets easier to work in over time instead of harder.
1. Does a CLAUDE.md file actually get read automatically by Claude Code?
Yes, Claude Code looks for a CLAUDE.md file at the root of the project and loads it as context before working on a task, so rules placed there apply without needing to be repeated in every prompt.
2. How detailed should acceptance criteria be for an AI code review to be useful?
They should be specific enough that a human reviewer could check each item against the code independently, including edge cases, performance expectations, and any non-functional requirements, not just the happy path.
3. Is a weekly full repository review necessary for small projects?
Small, low-traffic projects can often stretch the cadence to every two or three weeks, but any project with multiple contributors or frequent AI-assisted changes benefits from a regular full pass to catch drift early.
4. What's the difference between reviewing small batches and doing a weekly full repo review?
Small batch review catches problems local to a single change, like a layer violation in one file. A full repo review catches problems that only appear across many changes, like three different utilities doing the same job.
5. Does using a minimalism skill like Ponytail conflict with having detailed coding standards?
No, they complement each other. Coding standards define what correct, well-structured code looks like, while a minimalism skill narrows how much code gets written to satisfy that standard, keeping diffs smaller without lowering the bar on correctness.