Every session in my repo started by a 194,492-character CLAUDE.md. That is roughly 48,600 tokens, on every single message, and about 4.9× the size at which Claude Code starts warning you about a memory file.
Disclaimer: This guide and the accompanying scripts were generated with
the assistance of AI. The workflow was tested end-to-end on one real
repository and the numbers quoted throughout are from that run, but every
codebase differs — please read and double-check all terminal commands and
scripts before running them on your own system.
Two things worth knowing before you start. The restructure rewrites
CLAUDE.md in place and creates files under .claude/ — commit or back up
first so you can git checkout your way out. The measurement harness launches real headless Claude Code sessions, one per prompt, which consume
API quota and can take several minutes each; start with two or three prompts
before running twenty.
None of it was junk. It was two years of hard-won operational memory: the migration command that silently deletes an index, the seat index that retries the same seat forever, the colour function that serialises to a syntax both our contrast checkers were blind to. Deleting it was not an option.
I got it to 17,283 characters — a 91% reduction — with four words lost, all of them from headings I deleted on purpose. Then I measured whether the restructure actually worked, which turned out to be the most useful part and the part nobody writes about.
Here is the whole method, including the mistake I made halfway through.
"Just delete the stale bits." You cannot know which bits are stale without re-reading the code each one describes. "This trap looks obsolete" is a guess, and a wrong guess deletes the one note that would have saved the next debugging session.
"Summarise it." This is the dangerous one, and I will come back to it.
"Use @import to split it up." Imports expand inline. You get a tidier file and identical context cost.
The actual problem is not that the file is big. It is that all of it loads for all of it, including the 90% irrelevant to whatever you are doing right now. A CSS bug does not need the Stripe webhook ordering rules.
| Layer | Holds | Loads |
|---|---|---|
CLAUDE.md |
Commands, and rules true in every session | Always |
.claude/skills/<name>/SKILL.md |
One topic — an index if large | When its description matches the task |
.claude/skills/<name>/references/*.md |
The detail behind one part of a skill | When the skill reads it |
There is a fourth, underused mechanism: .claude/rules/*.md with paths: frontmatter, which loads by glob match on the files you touch. No model judgment involved — if the path matches, it loads.
I ended with a 17KB CLAUDE.md, 12 skills, and 28 reference files.
A skill loads when the model judges its description relevant. That judgment is very good, but it is a judgment. So the test is not "is this important?" — everything in the file was important. The test is:
If this rule failed to load exactly when it mattered, how bad is that?
A styling trap: you lose some time. A security rule: you ship the bug it exists to prevent.
So in my file, every security rule stayed resident — all 26 of them — along with 21 architectural invariants (one database per tenant, the append-only ledger, the partial unique index arbitrating seat contention) and the commands, which are needed constantly and are cheap.
Everything else — fees, booking, notifications, the mobile apps, the rendered pages, the test practices, the known-gaps list — moved.
To state 47 rules in a ~200-line file, the obvious move is to rewrite each into a crisp one-liner.
Do not do this. My own repo's guidance, written by past-me after getting burned, says it better than I can:
A stale claim that gets edited without being checked is laundered, not corrected. Re-read the code the line describes, or leave the line alone.
Paraphrasing 47 rules I had not re-verified would produce 47 confident-sounding claims nobody checked — and they would look freshly reviewed, which is worse than being obviously old.
The way out: your rules are probably already written with the rule as the opening sentence. Mine were. So lift that sentence verbatim and move the reasoning to a reference file. Nothing is rewritten. The rule sits in CLAUDE.md; the "why" sits one hop away.
- **The card never touches this server.** A SetupIntent hands the browser a
client secret and Stripe's own SDK collects the number, which is why no
endpoint here takes one.
One sentence resident. The remaining 400 words of reasoning in hard-rules/references/security.md.
Two of my entries opened with a finding rather than an instruction ("A capital letter moved an anonymous crawler onto a studio's members' rate bucket"). Both happened to end with the author's own "The general rule: …" sentence. I carried that too — still their words, not mine.
Line-by-line diffing is useless: moving prose re-wraps it, so every line reads as changed. Sentence matching is nearly as bad, because bold markers and headings shift the boundaries.
Word multisets are immune to both. Every word of the original must still appear, at least as many times, somewhere in the new corpus:
import collections, re
WORD = re.compile(r'[A-Za-z0-9_./:%-]+')
a = collections.Counter(WORD.findall(old_text))
b = collections.Counter(WORD.findall(all_new_text))
lost = {w: a[w] - b[w] for w in a if a[w] > b[w]}
My final run: 4 of 31,697 word occurrences missing — Traps, bitten, Invariants, simplify. All four from the two section headings I deleted. That is what done looks like.
And check that it fails when it should. I deleted one skill directory and re-ran: 2,522 losses, including the word fee forty times. A verification you have never seen fail is not a verification.
My build script appended the moved entries to two existing skill files. I ran it twice while iterating.
Result: 36 entry blocks where 28 belonged. Silent duplication — the exact failure mode every guide on this warns about, and I walked into it anyway while writing about avoiding it.
It was caught only because I counted each entry across every file and asserted it appeared exactly once. Add that check. Make your build script idempotent, or have it rewrite files rather than append to them.
Here is where I diverge from the other write-ups.
A widely-shared figure says skill activation runs around 79% versus 100% for a rule sitting directly in CLAUDE.md. That is a genuinely useful warning, and it is also one person's measurement of their own configuration. It is not a constant. I had been repeating it as if it were.
So I measured mine. The method:
claude -p). The session doing the restructure already has every skill in context; asking it "would you have loaded this?" measures nothing.session_id via --output-format json so probes can run in parallel without being confused with each other.Edit/ Write so the measurement cannot modify the repo it measures.
Count two signals, not one: the Skill tool firing, and the model reading a skill file directly. Reaching the right knowledge by opening the file is a hit.
Result: 13 of 14 — 93%. Not 79%. And on every single hit, the skill fired as tool call #1, before any file was opened.
The one miss still produced a correct answer. It just rebuilt it from scratch: 56 tool calls, against a median of 16 on the hits.
It re-read four source files and grepped the API to re-derive what the skill would have handed it in one read. Meanwhile the always-loaded layer acted as a safety net — a resident hard rule about calendar dates routed it to the right reference anyway.
That reframes the whole risk. A miss is not "the rule does not get applied." It is a session that costs 3.5× more. That is a much less frightening failure mode, and I would never have known it from reading blog posts, including this one.
Both mobile prompts hit the same skill. Only the one containing the word "Expo" fired.
The skill's description advertised tooling: "SDK pinning against the Expo Go ceiling, Metro under pnpm, driving a simulator with idb." But the skill also held screen-level traps — a Book button offered on a class the member already holds, a crashing date field. The prompt "the Booked screen crashes" matched none of the advertised words.
Write descriptions that name symptoms, not tools. What will the user actually see?
MODULE_NOT_FOUND, a missing query engine, an image far bigger than expected, or a fix that only takes effect on the second run"
I rewrote all 11 descriptions this way and re-measured with seven fresh symptom-phrased prompts — the exact style that had failed.
7 of 7, every one at tool call #1. The prompt that previously missed now hits, in 26 tool calls instead of 56.
For anything that maps onto a directory, .claude/rules/*.md removes the judgment entirely:
---
paths:
- "apps/mobile/**"
- "apps/mobile-yoga/**"
---
You are in one of the two Expo member apps.
**Read `.claude/skills/mobile-apps/SKILL.md` before changing anything here.**
Keep these as pointers, never copies — the knowledge stays in exactly one place.
Verify with a two-sided canary. A scoped rule matching nothing looks identical to one that works:
paths: ["some/dir/**"] whose body says to emit a unique token.
Step 3 is the one people skip. Without it you cannot tell "works" from "always loaded", and the second silently undoes your savings. Mine passed both ways: 1 and 0.
One warning: build the globs against your real layout. I assumed apps/api/src/fees/; the code actually keeps fees, memberships and class packs in apps/api/src/billing/. Had I not checked, the rules would have matched nothing, silently, forever.
The corpus grew 14% — 194,492 to 220,885 characters. That extra 26,393 is frontmatter, index tables and pointers. None of it is knowledge. It exists so the right file can find you.
A change now lands in more places. One file became three edits. More places to go stale. A routing table in CLAUDE.md helps; it is a mitigation, not a guarantee.
Occasional forgetfulness. You trade "always slightly bloated" for "usually lean, occasionally expensive."
I would make the trade again. But state it plainly rather than pretending the restructure is free.
I packaged the method as a Claude Code plugin — two skills: one that performs the restructure with the safety rules baked in, one that measures whether your skills actually fire.
/plugin marketplace add dhondooo/claude-md-progressive-disclosure
/plugin install progressive-claude-md
Then just say: "my CLAUDE.md is too big, restructure it", and later, "measure whether my skills actually fire."
The three-layer framing and the 79% activation warning come from a note.com post on progressive disclosure. The <150 line SKILL.md convention, the references/ layer and the "what becomes what" mapping are from cem.karaca's write-up. The memory tiers, @import caveat and compression passes are from bijit211987's guide. Shyam Verma's "Stop overfeeding your CLAUDE.md" is where the "<50% of conversations → make it a skill" heuristic comes from.
What I would add to all of them: measure your own hit rate before building anything to fix it. Mine was 93% before I touched a thing, and the one real defect was a sentence I had written badly — not the architecture.