Strona główna / Artykuły / Shrinking a Giant CLAUDE.md by 91% and Measuring What Broke

Artykuł opublikowany po angielsku.

Shrinking a Giant CLAUDE.md by 91% and Measuring What Broke

Split a 194K-character CLAUDE.md into skills, references and path-scoped rules, verify nothing was lost, and measure how often skills actually fire.

1905 słów

Imagine every session in a repo opening by loading a CLAUDE.md of 194,492 characters. That's roughly 48,600 tokens before you've typed a word, repeated on every message, and nearly five times the size at which Claude Code starts warning about a memory file. Now imagine none of it is junk. It's two years of hard-won operational memory: the migration command that silently drops an index, the seat index that keeps retrying the same seat forever, the color function whose output syntax two contrast checkers couldn't read. Deleting it isn't an option.

The workable route got that file down to 17,283 characters, a 91% cut, with four words lost, all from headings removed on purpose. Then came the part few people bother with: measuring whether the restructure actually worked. What follows is the full method, mistake included.

A caution before you try it. The restructure rewrites CLAUDE.md in place and creates files under .claude/, so commit or stash before starting, and git checkout can undo it. The testing harness spawns genuine headless Claude Code sessions, one for each prompt, burning API quota and sometimes minutes apiece. Try two or three prompts first rather than twenty. And since every codebase differs, read every command and script before running it on your own system.

Why the obvious fixes fail

Deleting stale parts sounds sensible, but you can't know what's stale without rereading the code each note describes. "This looks obsolete" is a guess, and a wrong guess removes the one note that would have saved a future debugging session.

Summarizing is the dangerous option, and it gets its own section below.

Splitting with @import doesn't help either. Imports expand inline, so you get a tidier file and the same context cost.

The real problem isn't size. It's that everything loads for every task, including the 90% irrelevant to the one you're doing. A CSS bug has no use for the Stripe webhook ordering rules.

Three layers

Think in tiers: a lean always-loaded core, skills pulled in when the model decides their description fits the task, and reference files opened only when needed. A fourth mechanism gets too little attention. Rule files under .claude/rules/ can carry paths: frontmatter, so they load whenever you touch matching files. That's glob matching, with nobody deciding anything.

Where it landed: a 17KB CLAUDE.md, plus 12 skills and 28 reference files.

Deciding what stays loaded

A skill loads when the model decides its description fits. That judgment is very good, but it's still judgment. So "is this important?" is the wrong test, since everything in the file mattered. Ask this instead: if this rule failed to load at exactly the moment it counted, how bad would that be?

A styling trap costs you some time. A security rule failing means you ship the bug it exists to prevent. So every security rule stayed resident, all 26 of them, plus 21 architectural invariants (a database per tenant, an append-only ledger, a partial unique index that settles seat contention) and the commands, which are needed constantly and cost little. Fees, booking, notifications, mobile apps, rendered pages, test practices and the known-gaps list all moved out.

Extract the rules, don't rewrite them

Squeezing 47 rules into about 200 lines tempts you to rewrite each as a crisp one-liner. Don't. A line in the repo's own guidance, written after getting burned, puts it well: a stale claim that gets edited without being checked is laundered rather than corrected, so either reread the code the line describes or leave the line alone.

Paraphrasing 47 unverified rules produces 47 confident claims nobody checked, and they'd look freshly reviewed, which is worse than looking old.

The way out is that your rules probably already open with the rule itself as the first sentence. Lift that sentence word for word and move the reasoning into a reference file. Nothing gets rewritten. The rule lives in CLAUDE.md and the "why" sits one hop away. A resident entry looks like this:

- **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 stays loaded, and the remaining 400 words of reasoning go to hard-rules/references/security.md.

Two entries began with a finding instead of an instruction, such as a capital letter moving an anonymous crawler into a studio's members' rate bucket. Each also closed with a sentence beginning "The general rule", and that line was carried over as written.

Prove nothing disappeared

Diffing line by line gets you nowhere, because moved prose rewraps and everything appears modified. Matching sentences fares only slightly better, since bold markers and headings change where sentences begin and end. Counting words as a multiset sidesteps both: each word from the old corpus has to turn up in the new one at least as often as before.

The final run showed 4 of 31,697 word occurrences missing: Traps, bitten, Invariants and simplify, all from two deleted section headings. That's what done looks like.

Also confirm that the check fails when it should. Deleting one skill directory and rerunning produced 2,522 losses, including the word fee forty times. A verification you've never watched fail isn't a verification.

The mistake

The build script appended moved entries to two existing skill files, and it got run twice during iteration. The outcome was 36 entry blocks where 28 belonged, silent duplication, the very failure every guide warns about. It was caught only because each entry was counted across all files and asserted to appear exactly once. Add that check. Make the script idempotent, or have it rewrite files instead of appending.

Measure, don't trust a quoted number

One widely shared figure says skill activation runs about 79%, against 100% for a rule sitting directly in CLAUDE.md. As a warning it's useful, but it's one person's measurement of their own setup, not a constant. Measure yours:

  1. Write 14 prompts a real user might type, covering all the skills and phrased as symptoms instead of topics. "The Booked screen crashes with RangeError" works. "Tell me about the mobile app" names the answer for you.
  2. Run each in a fresh headless session with claude -p. The session that performed the restructure already holds every skill in its context, so quizzing it about what it would have loaded tells you nothing.
  3. Capture each run's session_id through --output-format json, so parallel probes don't get mixed up.
  4. Open that session's transcript and note which skills were loaded.
  5. Block Edit and Write so the measurement can't modify the repo it measures.

Treat two events as a success: the Skill tool being called, or the model opening a skill file by hand. If it gets to the right knowledge by reading the file, that counts too.

The result was 13 of 14, or 93%, not 79%. And on every hit, the skill fired as the very first tool call, before any file was opened.

A miss costs money, not correctness

The single miss still gave a correct answer. It just rebuilt the knowledge from scratch: 56 tool calls against a median of 16 on hits. It reread four source files and grepped the API to derive what the skill would have supplied in one read. The resident layer also worked as a safety net: a hard rule about calendar dates was enough to point it at the correct reference regardless.

That reframes the risk. A miss doesn't mean the rule goes unapplied. It means a session costing about 3.5 times more, a far less frightening failure.

The miss was a writing bug

Both mobile prompts targeted the same skill, but only the one containing the word "Expo" triggered it. The skill's description advertised tooling, such as SDK pinning, Metro under pnpm and driving a simulator. It also held screen-level traps: a Book button shown for a class the member already holds, a crashing date field. A prompt about the Booked screen crashing matched none of the advertised words.

So describe symptoms, not tools. Ask what the user will actually see. A weak description lists pnpm settings, Node test globs, tsc output and Docker image layout. A strong one fires when a build breaks for reasons outside the code itself, for example MODULE_NOT_FOUND, no query engine present, a bloated image, or a fix that only works on the second attempt.

After rewriting all 11 descriptions this way and testing seven new symptom-phrased prompts, the result was 7 of 7, every one at tool call number one. The prompt that missed before now hit, using 26 tool calls rather than 56.

Path-scoped rules as a second net

For anything that maps to a directory, .claude/rules/*.md removes judgment altogether:

---
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.**m

Keep these as pointers, never copies, so the knowledge lives in exactly one place.

A two-sided canary test tells you whether scoping really works, because a glob that matches nothing behaves just like a working one from the outside. Create a rule scoped to one directory, with a body instructing Claude to print a made-up marker string. Open a file in that directory and you should see the marker. Open one elsewhere and the marker should be absent.

Most people skip the last step. Without it you can't tell "works" from "always loaded", and the latter silently erases your savings. Here, both probes behaved: 1 and 0.

One warning: write globs against your real layout. The first guess was apps/api/src/fees/, but fees, memberships and class packs actually lived in apps/api/src/billing/. Unchecked, those rules would have matched nothing, silently, forever.

What it costs

The corpus grew 14%, from 194,492 to 220,885 characters. That extra 26,393 characters is frontmatter, index tables and pointers, with no new knowledge in them; they're there so the right file can find you. Edits also touch more files now, since one change became three. A routing table in CLAUDE.md eases the staleness risk without eliminating it. And you trade a file that's always a little bloated for one that's usually lean and sometimes costly. It's a trade worth repeating, as long as you admit it isn't free.

Packaged version

The method has been bundled as a Claude Code plugin with two skills: one performs the restructure with the safety rules built in, and one measures whether your skills actually fire. Install it with:

/plugin marketplace add your-org/claude-md-progressive-disclosure
/plugin install progressive-claude-md

Then tell Claude your CLAUDE.md is too big and ask it to restructure it, and later ask it to measure whether your skills fire.

Whatever else you take from this, measure your own hit rate before building anything to fix it. The baseline here was 93% before anything changed, and the one real defect was a badly written sentence, not the architecture.