Как сократить гигантский CLAUDE.md на 91% и измерить, что сломалось
Разбейте CLAUDE.md на 194K символов на скиллы, справочные файлы и правила с привязкой к путям, убедитесь, что ничего не потерялось, и измерьте, как часто скиллы на самом деле срабатывают.
Представьте, что каждая сессия в репозитории начинается с загрузки CLAUDE.md на 194 492 символа. Это примерно 48 600 токенов ещё до того, как вы что-то напечатали, и так на каждом сообщении, а сам файл почти в пять раз больше порога, после которого Claude Code начинает предупреждать о размере файла памяти. А теперь представьте, что мусора там нет. Это два года добытой потом и кровью эксплуатационной памяти: команда миграции, которая молча удаляет индекс, индекс мест, который бесконечно повторяет попытку с одним и тем же местом, функция цвета, синтаксис вывода которой не смогли прочитать два инструмента проверки контраста. Удалить всё это не вариант.
Рабочий путь сократил файл до 17 283 символов, то есть на 91%, и потерялось всего четыре слова — все из заголовков, удалённых намеренно. Дальше шла часть, до которой мало кто доходит: проверить, сработала ли реструктуризация на самом деле. Ниже весь метод, включая ошибку.
Предостережение, прежде чем пробовать. Реструктуризация переписывает CLAUDE.md на месте и создаёт файлы в .claude/, так что перед началом сделайте коммит или stash — тогда git checkout всё откатит. Тестовый стенд запускает настоящие headless-сессии Claude Code, по одной на промпт, расходуя квоту API и иногда по несколько минут на каждую. Сначала попробуйте два-три промпта, а не двадцать. И поскольку все кодовые базы разные, читайте каждую команду и скрипт, прежде чем запускать их у себя.
Почему очевидные решения не работают
Удалить устаревшее звучит разумно, но понять, что устарело, нельзя, не перечитав код, который описывает каждая заметка. «Похоже, это уже неактуально» — догадка, и неверная догадка удаляет ту самую заметку, которая спасла бы будущую сессию отладки.
Пересказ — опасный вариант, и ему ниже посвящён отдельный раздел.
Разбиение через @import тоже не помогает. Импорты раскрываются на месте, так что файл становится аккуратнее, а цена по контексту остаётся той же.
Настоящая проблема не в размере. Она в том, что всё загружается для каждой задачи, включая 90%, не имеющих отношения к текущей. Багу в CSS ни к чему правила порядка вебхуков Stripe.
Три слоя
Думайте уровнями: компактное ядро, которое загружается всегда, скиллы, которые подтягиваются, когда модель решает, что их описание подходит к задаче, и справочные файлы, которые открываются только по необходимости. Четвёртому механизму уделяют слишком мало внимания. Файлы правил в .claude/rules/ могут содержать frontmatter paths:, и тогда они загружаются, как только вы трогаете подходящие файлы. Это сопоставление по glob-шаблону, и никто ничего не решает.
Итог: CLAUDE.md на 17KB плюс 12 скиллов и 28 справочных файлов.
Что оставить загруженным всегда
Скилл загружается, когда модель решает, что его описание подходит. Это суждение очень хорошее, но всё же суждение. Поэтому вопрос «это важно?» — неправильный тест, ведь в файле важно было всё. Спросите иначе: если это правило не загрузится ровно в тот момент, когда оно нужно, насколько это будет плохо?
Ловушка со стилями стоит вам немного времени. Сбой правила безопасности означает, что вы выкатите тот самый баг, ради предотвращения которого оно существует. Поэтому все правила безопасности остались постоянными, все 26, плюс 21 архитектурный инвариант (база данных на каждого арендатора, журнал только на добавление, частичный уникальный индекс, который решает конкуренцию за места) и команды, которые нужны постоянно и стоят мало. Комиссии, бронирования, уведомления, мобильные приложения, рендеринг страниц, практики тестирования и список известных пробелов — всё это переехало наружу.
Извлекайте правила, а не переписывайте
Ужать 47 правил примерно в 200 строк — большой соблазн переписать каждое в чёткую однострочную формулировку. Не надо. Строка из собственного руководства репозитория, написанная после горького опыта, говорит об этом точно: устаревшее утверждение, которое отредактировали без проверки, не исправлено, а отмыто, так что либо перечитайте код, который описывает строка, либо не трогайте её.
Пересказ 47 непроверенных правил даёт 47 уверенных утверждений, которые никто не проверял, и выглядят они как свежепросмотренные, что хуже, чем выглядеть старыми.
Выход в том, что ваши правила, скорее всего, уже начинаются с самого правила в первом предложении. Возьмите это предложение дословно, а обоснование перенесите в справочный файл. Ничего не переписывается. Правило живёт в CLAUDE.md, а «почему» лежит на расстоянии одного перехода. Постоянная запись выглядит так:
- **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.
Одно предложение остаётся загруженным, а остальные 400 слов обоснования уходят в hard-rules/references/security.md.
Две записи начинались не с инструкции, а с находки — например, как заглавная буква переводила анонимного краулера в лимит запросов для участников студии. Каждая из них также заканчивалась предложением, начинавшимся с "The general rule" («Общее правило»), и эта строка была перенесена как есть.
Докажите, что ничего не пропало
Построчный diff ничего не даёт: перенесённый текст переформатируется, и всё выглядит изменённым. Сопоставление предложений чуть лучше, но жирное выделение и заголовки сдвигают начало и конец предложений. Подсчёт слов как мультимножества обходит обе проблемы: каждое слово из старого корпуса должно встретиться в новом не реже, чем раньше.
Финальный прогон показал, что из 31 697 вхождений слов пропали 4: Traps, bitten, Invariants и simplify, все из двух удалённых заголовков разделов. Вот так выглядит «готово».
Ещё убедитесь, что проверка падает, когда должна. Удаление одной папки скилла и повторный прогон дали 2522 потери, включая слово fee сорок раз. Проверка, которую вы ни разу не видели проваленной, — не проверка.
Ошибка
Скрипт сборки дописывал перенесённые записи в два существующих файла скиллов, и во время итераций его запустили дважды. В итоге получилось 36 блоков записей там, где должно было быть 28, — тихое дублирование, тот самый сбой, о котором предупреждает каждое руководство. Его поймали только потому, что каждую запись пересчитывали по всем файлам и проверяли, что она встречается ровно один раз. Добавьте такую проверку. Сделайте скрипт идемпотентным или пусть он перезаписывает файлы, а не дописывает в них.
Измеряйте, а не верьте цитируемым цифрам
Одна популярная цифра гласит, что скиллы срабатывают примерно в 79% случаев против 100% у правила, лежащего прямо в CLAUDE.md. Как предупреждение это полезно, но это измерение одного человека на его собственной конфигурации, а не константа. Измерьте свою:
- Напишите 14 промптов, которые мог бы набрать реальный пользователь, охватывающих все скиллы и сформулированных как симптомы, а не темы. "The Booked screen crashes with RangeError" («экран Booked падает с RangeError») подходит. "Tell me about the mobile app" («расскажи о мобильном приложении») сама подсказывает ответ.
- Запускайте каждый в свежей headless-сессии через
claude -p. Сессия, которая проводила реструктуризацию, уже держит в контексте все скиллы, так что расспрашивать её, что бы она загрузила, бессмысленно. - Сохраняйте
session_idкаждого прогона через--output-format json, чтобы параллельные проверки не перепутались. - Откройте транскрипт этой сессии и отметьте, какие скиллы были загружены.
- Заблокируйте
EditиWrite, чтобы измерение не могло изменить репозиторий, который измеряет.
Успехом считайте два события: вызов инструмента Skill или ручное открытие моделью файла скилла. Если она добралась до нужных знаний, прочитав файл, это тоже засчитывается.
Результат — 13 из 14, то есть 93%, а не 79%. И при каждом попадании скилл срабатывал самым первым вызовом инструмента, до открытия любого файла.
Промах стоит денег, а не правильности
Единственный промах всё равно дал правильный ответ. Просто знания пришлось восстанавливать с нуля: 56 вызовов инструментов против медианных 16 при попаданиях. Модель перечитала четыре исходных файла и прошлась grep по API, чтобы вывести то, что скилл дал бы за одно чтение. Постоянный слой сработал и как страховка: жёсткого правила о календарных датах хватило, чтобы всё равно направить её к нужному справочнику.
Это меняет взгляд на риск. Промах не означает, что правило не применено. Он означает сессию примерно в 3,5 раза дороже — куда менее страшный сбой.
Промах оказался ошибкой в тексте
Оба промпта о мобильном приложении целились в один и тот же скилл, но сработал только тот, в котором было слово "Expo". Описание скилла рекламировало инструменты: фиксацию версии SDK, Metro под pnpm и управление симулятором. А ещё в нём были ловушки на уровне экранов: кнопка Book для занятия, на которое участник уже записан, падающее поле даты. Промпт о падении экрана Booked не совпал ни с одним из рекламируемых слов.
Поэтому описывайте симптомы, а не инструменты. Спросите себя, что пользователь увидит на самом деле. Слабое описание перечисляет настройки pnpm, glob-шаблоны тестов Node, вывод tsc и устройство Docker-образа. Сильное срабатывает, когда сборка ломается по причинам вне самого кода, например MODULE_NOT_FOUND, отсутствующий query engine, раздутый образ или исправление, которое работает только со второй попытки.
После того как все 11 описаний переписали так и проверили семь новых промптов в формулировке симптомов, результат составил 7 из 7, и каждый раз на первом вызове инструмента. Промпт, который раньше промахнулся, теперь попал и потратил 26 вызовов вместо 56.
Правила с привязкой к путям как вторая сетка
Для всего, что соответствует какой-то папке, .claude/rules/*.md полностью убирает необходимость в суждении:
---
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
Держите их как указатели, а не копии, чтобы знание жило ровно в одном месте.
Двусторонний канареечный тест показывает, работает ли привязка на самом деле, потому что glob, который ничему не соответствует, снаружи ведёт себя точно так же, как рабочий. Создайте правило, привязанное к одной папке, с текстом, который велит Claude вывести выдуманную строку-маркер. Откройте файл в этой папке — маркер должен появиться. Откройте файл в другом месте — маркера быть не должно.
Последний шаг большинство пропускает. Без него не отличить «работает» от «загружается всегда», а второе незаметно съедает всю экономию. Здесь обе проверки повели себя как надо: 1 и 0.
Одно предупреждение: пишите glob-шаблоны под реальную структуру. Первая догадка была apps/api/src/fees/, но комиссии, абонементы и пакеты занятий на самом деле жили в apps/api/src/billing/. Без проверки эти правила не совпадали бы ни с чем — молча и навсегда.
Чего это стоит
Корпус вырос на 14%, с 194 492 до 220 885 символов. Эти лишние 26 393 символа — frontmatter, индексные таблицы и указатели, новых знаний в них нет; они нужны, чтобы правильный файл нашёл вас. Правки теперь затрагивают больше файлов: одно изменение превратилось в три. Таблица маршрутизации в CLAUDE.md снижает риск устаревания, но не устраняет его. И вы меняете файл, который всегда немного раздут, на тот, что обычно компактен, а иногда обходится дорого. Это обмен, который стоит повторять, если честно признавать, что он не бесплатный.
Готовая версия
Метод упакован в плагин для Claude Code с двумя скиллами: один выполняет реструктуризацию со встроенными правилами безопасности, второй измеряет, срабатывают ли ваши скиллы на самом деле. Установка:
/plugin marketplace add your-org/claude-md-progressive-disclosure
/plugin install progressive-claude-md
Затем скажите Claude, что ваш CLAUDE.md слишком большой, и попросите его реструктурировать, а позже попросите измерить, срабатывают ли скиллы.
Что бы вы ни вынесли из этого текста, измерьте собственный процент попаданий, прежде чем что-то строить для исправления. Здесь исходный уровень был 93% ещё до каких-либо изменений, а единственным настоящим дефектом оказалось плохо написанное предложение, а не архитектура.