Every repository we work in has a CLAUDE.md, and most of the work in them is done by an AI agent. At first we wrote that file the way you'd write onboarding docs for a new hire: what the project is, the stack, a tour of the folders, the coding conventions. Thorough and welcoming.

The agent ignored roughly half of it. So we wrote more rules, the file got longer, and compliance got worse.

The problem wasn't the amount of documentation. It was the kind. We had written a README; what the job needed was a contract.

The current CLAUDE.md in this repository is 164 lines, about ten thousand characters.

Twelve ## sections. Four of them are "things this repo does differently," three are "what the build checks and what it doesn't." The project introduction is five lines. The first version was longer, and most of that length was introduction and general advice.

1. Where Does a Given Rule Belong?

Held against each rule, it came down to four questions. The answers decide where that rule lives.

Question Answer Destination ────────────────────────────────────────────────────────────────────── Q1 Without this, would it do the opposite? no delete it Q2 Can a machine judge a violation? yes build / lint / hook Q3 Judgeable, but does it run mid-task? no put it in CI too Q4 Is it a matter of taste? yes the document, with a reason ────────────────────────────────────────────────────────────────────── All the way down as "no" one line in the document

The order matters. Skip Q1 and the file keeps inflating. Ask Q2 first and you get "write it down now, automate it later" — and later never arrives.

Here's how rules in this repository actually sorted:

Rule Q1 Q2 Outcome ──────────────────────────────────────────────────────────────────── Use descriptive variable names N - deleted Every post exists in ko and en Y Y moved to a build hook Links between posts point backward Y Y moved to a build hook Landing pages keep their inline style Y N document (with reason) Translations shouldn't read as machine work Y N document (with reason) Keep staticPages.js in sync with pages Y N document + reporting rule

The last row is the awkward one. It is checkable; we just haven't built the check. So it stays in the document, labeled explicitly as "the one part the build will not catch for you." The absence of automation is itself information worth stating.

2. Don't Write Down What the Agent Already Does

The early version contained lines like these.

- Keep functions small and focused on one thing - Use descriptive variable names - Prefer named constants over magic numbers - Write commit messages that explain what changed and why

All true. All unnecessary. An agent does these without being asked — they're general good practice, already baked in.

And they aren't free. A long file buries the rules that matter. If 27 lines out of 30 are things anyone would do anyway, the other three read like more of the same.

The test: without this line, would the agent do the opposite?

If no, delete it. What survives is the things this repository does differently — which is the only reason the file exists.

3. Keep Only the Judgment Calls

After the cut, the surviving rules mostly looked like this.

11ty only processes .njk as templates. Every other .html is copied through byte for byte — which is why the inline styles and deep-link scripts on those pages are untouched by the build. The landing and lab pages stay self-contained with an inline <style> block. That's the existing approach; do not extract it to a CSS file. Blog posts use the shared posts.css. Do not add per-post styles.

The second rule is the interesting one. General good practice says "move inline styles into a stylesheet." Here the opposite is correct. Leave it unwritten and the agent helpfully refactors — pulling the inline styles out of a passthrough-copied page and quietly breaking something.

The third rule points the other way, in the same repository. Policy splits by page type. Nothing in the code tells you whether that split is intentional or an accident.

So there are three kinds of rule worth writing down:

  • Things that contradict general practice — otherwise the agent will "fix" them.
  • Things that split within one repository — otherwise it generalizes from the first example it sees.
  • Things that leave no trace in the code — external systems, deploy procedures, agreements with other repos.

4. Every Prohibition Needs a Reason

Bare prohibitions hold poorly. A prohibition with a reason generalizes to adjacent cases.

No reason — covers exactly this one file Don't touch the search engine verification files. With a reason — now it's a judgment the agent can apply Don't change the name or contents of the search engine verification files (google*.html, naver*.html). The filename itself is the verification token, so renaming one revokes site ownership.

An agent that read the second version makes the same call later when it meets app-ads.txt or a .well-known domain-verification file. The first version says nothing about those.

For the same reason, "do X" beats "don't do Y." A prohibition with no alternative just sends the agent looking for a way around it. "Write vanilla JS; don't introduce a new client framework" held up far better than "don't use React."

5. Spell Out How to Verify

This section had the largest effect of anything we added. It defines what "done" means.

## Verification npm run build # A failing build is a failing deploy. It must pass. npm run dev # Check the actual render locally What the build catches: locale symmetry, date agreement, links between posts, template syntax errors, broken references. What the build does NOT catch — check these yourself: - Whether layout and links survive in a browser - Whether relative paths in passthrough .html match directory depth - Deep link fallback behavior (needs a real device) - Never claim you verified links you didn't open.

The second half matters far more than the first. The default assumption is that a green build means the work is done; this states explicitly what the build cannot see.

That last line earns its place surprisingly often. It's a one-sentence rule against reporting a check that never happened.

6. Rules a Document Can't Enforce Belong in Code

This is the real conclusion. The more important a rule is, the less it belongs in the document.

Every post on this site has to exist in Korean and English. Originally that was just a bold line in the rules file — and posts still shipped in one locale only. A document can go unread, and even when read it fades partway through a long task.

So we moved it into a build hook. Add a post in one locale now and the build fails.

Error: ko/en posts are not symmetric. missing en: neat-daily-activity-guide

The difference is categorical. A documented rule is something it would be good to follow; a build rule is something you cannot proceed without. An agent can skim past a paragraph. It cannot skim past a failing build.

When you find yourself bolding a rule for the third time, that's the signal to move it into code.

Needing emphasis means the rule gets broken often, and rules that get broken often keep getting broken no matter how much you emphasize them.

One more rule moved the same way. While building scheduled publishing we hit the problem that a link between posts can point into the future — another rule a document cannot enforce, because at the moment you write the link the target file is sitting right there in the repo and nothing looks wrong. Once the build started scraping every post link and comparing publish dates, that mistake stopped happening.

Moving rules into code taught us one more thing: don't delete the rule from the document when you do. Delete it and the agent works in ignorance until the build fails at the end — by which point it has spent half a day going the wrong way. So the document keeps a single line saying "the build enforces this" — not the rule in full, just the existence of the guard.

What stays in the document after the rule moves into code What the build catches: ko/en slug symmetry, matching date and category, links between posts (target exists + publishes earlier), template syntax errors. What the build does NOT catch — check these yourself: - Whether staticPages.js matches the actual pages - Whether relative paths in passthrough .html match directory depth - Deep link fallback behavior (needs a real device)

Not everything can move, of course. "Translations shouldn't read like machine translation" has no automated check. Leave that class of rule in the document and migrate everything that a check can express.

7. Reporting Rules — Say What Must Be Said

The last section we added, and one we're glad we did.

## Reporting - If you added, moved, or deleted a page, state whether you also updated the sitemap and the redirects. - If you worked in only one locale, say so and name the one left. - If you changed the deep link scheme, call out the iOS/Android work it requires.

The real risk with agent work isn't a wrong thing reported as right — that gets caught in review. It's half the work reported as all of it, which doesn't. Deciding in advance what must always be mentioned closes a good deal of that gap.

8. Five Kinds of Sentence That Shouldn't Stay

"Would this happen anyway?" wasn't the only test for cutting. A separate class of sentence is neither wrong nor obvious — it just turns harmful with time.

Type Example Write instead ────────────────────────────────────────────────────────────────────────────── A count fixed in time "currently 10 pages per locale" "ko and en map 1:1" A summary of another doc restating the README one line pointing at it Cross-reference by number "as we saw in section 3" refer to it by content An absolute with no escape "always do X" "do X; when Y, do Z" Abstract with no example "keep things consistent" one wrong / right pair

The first row is a real line in this repository's file. On the day an eleventh page appears it quietly becomes false, and one false line lowers the credibility of every other line. Counts belong in a build check, or nowhere.

The third row we learned from editing posts on this site. Point at another document by section number and adding one section there silently makes your sentence wrong. The same applies inside a rules file: the moment it references itself by section number, adding a rule turns into proofreading the whole document.

The fourth is the opposite failure — a sentence that's too strong. If a real exception exists and the file says "always," an agent that meets the exception either breaks the rule or stops working. Both are bad. Whoever knows the exception should write it down.

9. When a Rule Gets Ignored — Diagnosing Why

"The agent doesn't follow the rules" turned out not to be one phenomenon. Each symptom has a different cause and a different place to fix.

Symptom Likely cause Fix where ───────────────────────────────────────────────────────────────────────── Always breaks the same rule conflicts with practice add the reason Breaks it late in long tasks the file is too long cut sections Interprets the rule backwards the wording is vague add a wrong/right pair Follows it, but too far no stated scope narrow the subject Reports work it didn't do no definition of done write the check command Does one side and stops no reporting rule state what to report

The second row is the counterintuitive one: the rule gets broken, so you write more rules, and the writing is the cause. That is exactly where we were.

The fourth row happened here. "Keep the inline style" was written as a landing-page rule, but with no stated scope — so inline styles started appearing in blog posts too. The rule was followed and the result was wrong. Now every rule in the file leads with its subject: "landing and lab pages…", "blog posts…".

10. Common Questions

How long should the file be?
Section count tracks the experience better than line count. This repo has twelve, and no section runs past a screen. Past twenty or so, there are usually two things mixed together — rules and explanation. Move the explanation elsewhere and keep the rules, and the file halves.

In a monorepo, one file at the root or one per repository?
We run both: shared rules at the workspace root, repo-specific rules inside each repository. The essential part is stating which one wins on a conflict. That's why this repo's file opens with a single line: it assumes the shared rules and overrides them where they disagree. Without that line, an agent picks arbitrarily when two documents say different things.

Different tools want different filenames (CLAUDE.md, AGENTS.md). What then?
If the content is the same, make one canonical and have the other point at it. Maintain both and they will diverge, and after they diverge nobody knows which is right. This isn't a documentation problem — it's the ordinary cost of writing the same fact in two places.

Should rules include example code?
A wrong/right pair works far better than a single correct example, which leaves the boundary invisible. Just keep it short — examples inflate the file as surely as prose does. We hold each pair to about five lines.

How soon does editing the file change behavior?
On the next task. The file is read at the start of every session, so there's no training lag. That means the day you notice a broken rule is the day to fix the wording. Conversely, if it breaks again right after you fixed it, the problem isn't wording — go back to the diagnosis table above.

Can the agent write this file?
Drafting, yes; deciding, no. Rules files written by an agent tend to refill with general advice, because the evidence for what counts as an exception in this repo lives outside the file. The prompt that actually helped was "list the places you were unsure in this repository." That list was the list of rules worth writing.

11. In Summary

The file ended up less than half its original length, and compliance clearly improved. The principles:

  • Don't write what would happen anyway. It buries what wouldn't.
  • Keep only genuine judgment calls — against general practice, split within the repo, or invisible in the code.
  • Attach a reason to every prohibition. Reasons generalize; bare rules don't.
  • State how to verify, especially what the build misses.
  • Move checkable rules into code. The document is the fallback, not the mechanism.
  • Say what must be reported. It exposes half-finished work.
  • Never write counts fixed in time or cross-references by section number. They go quietly false.
  • Lead every rule with its subject. Without a scope, a rule can be followed and still produce the wrong result.

If your CLAUDE.md reads like a README, take another look at it. The reader isn't someone new to the project — it's a collaborator who already knows every general convention and none of your exceptions. The file only needs to carry the difference.