Свернуть разросшуюся автопамять: слить дубли, снять отработавшее, сжать индекс MEMORY.md. Брать при "сверни память", когда файлов десятки и индекс распух. Не для обычного сохранения факта.
Installs into .claude/skills of the current project.
Are you the author of Memory Compact?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/dewil-memory-compact)
---
name: memory-compact
description: Свернуть разросшуюся автопамять: слить дубли, снять отработавшее, сжать индекс MEMORY.md. Брать при "сверни память", когда файлов десятки и индекс распух. Не для обычного сохранения факта.
---
# memory-compact: свертка памяти проекта
Автопамять растет линейно и никогда не сжимается сама. Пока записей десяток, это незаметно; на сотне появляются две проблемы сразу:
- **индекс `MEMORY.md` грузится в контекст каждую сессию целиком** - это постоянный расход входных токенов, растущий с каждым новым фактом (замерено: 140 файлов дают индекс на 22 КБ, 215 файлов - на 25 КБ). Это происходит всегда, независимо от того, есть в памяти дубли или нет;
- **близкие записи могут расходиться**. Один сюжет - решение, инцидент, поведение системы - иногда обрастает несколькими файлами, написанными в разные недели, и позже они противоречат друг другу. Но это **гипотеза, которую проверяет аудит, а не данность**: на живой памяти в 140 файлов таких групп нашлось две-три (см. "Грабли").
У скилла три рычага, и обещание у них разное. **Слияние уплотняет изложение, а не выбрасывает знание**: слитый блок обязан содержать все, что было в исходных записях, включая почему так решили. **Снятие - осознанная потеря**: отработавший факт удаляется намеренно, и именно поэтому каждое снятие называется пользователю в плане и подтверждается им, а не решается молча. **Сжатие индекса** ничего не удаляет из файлов и работает даже там, где сливать нечего, - на большой памяти обычно оно и дает основной эффект.
## Когда применять
- В памяти проекта больше ~40 файлов или индекс длиннее ~50 строк.
- Видны группы файлов об одном сюжете (близкие имена, один и тот же компонент в описании).
- Есть записи с маркерами незавершенности ("ждет реализации", "временно", "планируется") о том, что давно сделано или отменено.
- Пользователь прямо просит свернуть или почистить память.
## Когда НЕ применять
- Память маленькая (десяток файлов) - свертка только запутает: искать по коротким конкретным записям проще, чем по блокам.
- Нужно сохранить новый факт - это штатная автопамять, скилл тут не нужен.
- Пользователь просит "забыть" что-то конкретное - это точечное удаление, не свертка.
- Идет активная работа по сюжету, о котором записи. Свернешь - потеряешь оперативные детали, которые еще нужны. Свертка для отстоявшегося.
## Порядок
### 1. Аудит (только чтение)
Собрать картину, ничего не меняя:
- сколько файлов, какого типа (`user` / `feedback` / `project` / `reference`), какой длины индекс;
- **группы-кандидаты**: записи об одном сюжете. Ориентиры - общий префикс имени, один компонент/система в описании, перекрестные ссылки `[[...]]` между собой;
- **кандидаты на снятие**: маркеры незавершенности о завершенном, факты про удаленный код, ссылки на несуществующие ресурсы, решения, отмененные более поздними записями. Ищутся грепом по маркерам ("ждет", "планируется", "временно", "в работе"), но перед вопросом пользователю **сверь маркер с телом записи**: на живой памяти самый частый случай оказался не "запись устарела", а **тело обновили, а описание в шапке нет** - индекс годами показывал "гейты OFF, ждет enable+prod" о том, что в теле той же записи помечено раскатанным месяц назад. Такое идет в план **отдельной строкой** ("описание записи X разошлось с телом - переписать по телу, ничего не снимая"): это не снятие и вопроса пользователю не требует, но и правится оно на общих основаниях - после "ок" на шаге 2, а не молча в аудите. Шаг 1 остается чтением без исключений. Дальше - **отработала запись или нет, из памяти не видно**: "ждет деплоя" сегодня может значить и "уже задеплоено полгода назад", и "все еще ждет". Это знание вне памяти, поэтому аудит только собирает список с цитатой маркера, а решение по каждому пункту принимает пользователь. На большой памяти таких кандидатов бывает два десятка - выкладывай их пачкой одним списком, а не по одному вопросу за раз;
- **что трогать нельзя**: записи типа `user` (кто такой пользователь) - их не сливают ни между собой, ни с записями других типов: профиль пользователя это не сюжет проекта. Также свежие записи текущей работы и единственная запись по сюжету.
**Нечего сворачивать - так и сказать.** Аудит не нашел ни групп, ни отработавшего: ответить одной строкой ("память компактна: N файлов, индекс M строк, сливать нечего") и остановиться. Ни плана, ни резервной копии, ни правок - повторный запуск на свернутой памяти обязан быть безобидным.
**Замер до выводов.** Аудит начинается с трех чисел: сколько файлов, сколько весит `MEMORY.md` (байты и строки), сколько нашлось **настоящих** групп - записей об одном сюжете, а не с общим словом в имени. Числа решают, каким рычагом работать, и уберегают от свертки ради процесса.
Проверено на большой памяти (140 файлов, индекс 22 КБ / 139 строк): настоящих групп там оказалось **две-три пары**, а не десяток - остальное 140 разных фактов с общими словами в именах (`cactus`, `llm`, `mihomo` - это домены, а не сюжеты). Слияние дало бы минус три строки индекса из ста тридцати девяти. Отсюда прямое следствие: **много файлов не значит много дублей**, и на такой памяти основной эффект дают другие два рычага - снятие отработавшего и сжатие индекса.
### 2. План свертки - пользователю, до единой правки
Показать таблицей: какие файлы в какой блок сливаются, какие снимаются и почему, сколько станет файлов и строк индекса. Если работаешь третьим рычагом - показать **сами новые строки индекса** (было / стало), а не только их число: пользователь читает этот индекс каждую сессию, и одобрять счетчик вслепую ему нечего. Длинный список - достаточно десятка показательных строк плюс полный вариант в файле рядом. **Ждать "ок".**
Свертка необратима по смыслу (слитый текст обратно не разложится), поэтому без явного подтверждения не начинать - как и всякая необратимая правка.
**Есть поиск по памяти - план обязан включать замер до и после, а не только "было / стало" по строкам.** До появления поиска проверить последствия свертки было нечем: оставалось верить, что уплотненный индекс не хуже прежнего. Поиск дает дешевый гейт - набор вопросов с известными правильными ответами, прогнанный до правок и после (порядка тридцати вопросов, десятки секунд, без моделей-судей и внешних сервисов; метрики hit@1 / hit@3 / MRR). Просело - откат из резервной копии, которую скилл и так требует сделать шагом ниже.
**Набор обязан представлять реальную смесь запросов, иначе он врет убедительно.** Прецедент: первая версия набора состояла из одних смысловых вопросов и уверенно советовала выбросить лексический движок как ухудшающий выдачу; добавление второй половины - точных токенов, имен и терминов - перевернуло вывод. Нерепрезентативный набор дает не шум, а **правдоподобно неверную рекомендацию, неотличимую по цифрам от верной**: те же метрики, та же уверенность. Поэтому в наборе должны быть обе половины - вопросы, сформулированные не словами записей, и запросы точной лексикой.
### 3. Резервная копия
Перед первой записью скопировать всю папку памяти (`memory.backup-<дата>/`). Это дешево и снимает цену ошибки: не понравился результат - вернули как было. Сказать пользователю, где копия.
**Папка памяти часто оказывается симлинком** - когда память проекта живет в vault или в синкаемой папке, `~/.claude/projects/<slug>/memory` это указатель, а не каталог. Тогда обычный `cp -r` копирует **сам симлинк**: рядом появляется второй указатель на ту же папку, копии нет, и правки идут в живую память. Выглядит это как успешный бэкап - каталог на месте, файлы читаются, - поэтому ошибка обнаруживается только при попытке откатиться.
Порядок, который от этого защищает:
```bash
REAL=$(readlink -f "<папка памяти>") # разыменовать
DEST="<куда-то вне папки памяти>"
cp -rL "$REAL" "$DEST"
stat -c '%i %n' "$REAL/MEMORY.md" "$DEST/MEMORY.md" # inode обязаны отличаться
```
Кавычки не для красоты: память проекта часто лежит по пути с пробелами (`.../Work/2024-12 Hiddify/...`), и незаквоченный путь развалится ровно там, где нужен больше всего.
Проверка inode - не паранойя, а единственный дешевый способ отличить копию от второго имени того же файла. Совпали - копии нет, останавливаться и разбираться, а не править память. Копию класть **вне папки памяти**: она сама может синкаться, и лишние полторы сотни файлов уедут на все устройства (`rules/docs-maintenance.md`).
### 4. Слияние
На группу - один файл:
- **имя** нового файла отражает сюжет, а не первый попавшийся файл группы;
- **тип** - самый строгий из слитых: `feedback` перебивает `project` и `reference` (указание, как работать, важнее описания состояния - потерять его дороже). `project` и `reference` вместе сливают редко: первый про наше решение, второй про внешний ресурс; если сюжет один - берется `project`. Тип `user` в слияние не входит вовсе (см. шаг 1);
- **содержимое** - связный текст по сюжету, не склейка абзацев подряд. Хронология решений сохраняется: "решили X (дата), потом уточнили Y (дата)" - именно она объясняет, почему сейчас так;
- **противоречия не сглаживать молча**: если ранняя запись говорит одно, а поздняя другое, в блоке остается актуальное решение и строка о том, что было раньше и почему изменилось;
- **относительные даты разворачивать в абсолютные** ("на прошлой неделе" через полгода бессмысленно).
### 5. Ссылки
Слияние ломает `[[имя]]`, указывающие на исчезнувшие файлы. После слияния пройти по всей памяти и заменить ссылки на старые имена ссылкой на новый блок. Оборванная ссылка - это тихая потеря связи, которую потом никто не восстановит.
### 6. Индекс
Пересобрать `MEMORY.md`: строка на файл, описание - о чем блок, чтобы по нему можно было решить, читать файл или нет. Ради этого все и затевалось: индекс должен стать короче, а не просто другим.
**Сжатие индекса - самостоятельный рычаг, а не следствие слияния.** Он работает, даже когда сливать нечего, и на большой памяти дает больше остальных. В проверенном случае строка индекса была вдвое длиннее необходимого: 139 строк с медианой 101 символ = 22 КБ, которые грузятся в контекст **каждую сессию**; ужать описания до ~60 символов - минус треть, без единого удаления. Насколько это типично, покажет замер конкретной памяти - он и так первый шаг аудита.
Что при этом можно терять, а что нельзя: строка индекса отвечает на единственный вопрос - **читать этот файл сейчас или нет**. Значит в ней остается предмет ("бэкап restic не покрывал часть путей"), а уходят детали, выводы и цифры - они в самом файле, и именно за ними его открывают.
Два обязательных сохранения, иначе сжатие обернется потерей: **имена собственные** (компонент, система, сервис - `mihomo`, `restic`, имя ноды) и **отличающее слово сюжета**. По ним файл ищут грепом, и вырезанное ради длины имя делает запись ненаходимой при формально понятной строке. Проверка после сжатия: по строке понятно, о чем файл; ее нельзя спутать с соседней; поиск по характерному термину по-прежнему приводит к ней.
**Цена честного сжатия - решение на каждую строку**, что в ней несущее, а что деталь. На памяти в 200+ записей это отдельная работа, а не хвост свертки, и принимать эти решения пачкой в конце сессии нельзя: именно там рождается обрезка на полуслове и потеря имени, по которому запись потом ищут. Прецедент (09.09.2026, 245 записей, индекс 25 КБ): план был ужать до 17 КБ, вышло 3% - и почти все дало слияние, а не сжатие строк; медиана длины строки осталась 94 символа, одна строка оказалась обрезана на полуслове. Отсюда два правила:
- **автоматическая обрезка по длине запрещена.** Усечение хвоста не отличает деталь от отличающего слова сюжета, а именно оно делает строку находимой; сжатие - переписывание строки, не отрезание;
- **нет времени на решение по строке - не сжимать вовсе.** В ветке с машинным поиском (ниже) то же самое, если нет времени снять регрессионный набор до правок. Три процента не окупают ни одной обрезанной строки; индекс, оставленный длинным, лучше индекса, испорченного механически.
Сжатие индекса не трогает файлы памяти, поэтому оно обратимо и безопаснее слияния - но так же требует "ок" в плане (шаг 2): пользователь читает этот индекс сам.
**Порог зависит от того, есть ли на устройстве поиск по памяти.** Все написанное выше исходит из того, что единственный способ дотянуться до факта - строка индекса, загруженная в контекст. Там, где поднят машинный поиск (векторный индекс по `~/.claude/projects/*/memory/`, хук recall), это перестает быть верным, и развилка такая:
- **поиска нет** - как выше: строка индекса самодостаточна, имена собственные и отличающее слово обязательны;
- **поиск есть** - индекс может стать **картой тем**, а детали достаются запросом; целевое состояние меняется с "строка на файл покороче" на "строка на сюжет".
Второй вариант дешевле по контексту, но у него своя цена, и платить ее надо **до** правок, а не после:
- **снять регрессионный набор контрольных вопросов заранее** - десяток запросов, которые точно должны находить нужные записи, с зафиксированными ответами. После сжатия прогнать тот же набор и сверить. Без этого ухудшение доставаемости необратимо и обнаружится через недели, когда восстанавливать уже нечего;
- **убедиться, что индексатор видит проект**: память, не подключенная к `~/.claude/projects/*/memory/`, в поиск не попадает вовсе, и сжатый до тем индекс тогда просто теряет информацию;
- **имена собственные не выкидывать все равно.** Поиск гибридный, и точная лексика тянет попадание там, где смысловая близость промахивается - на одном замере точные токены давали ту же оценку, что и верные попадания, то есть порогом не отсекались.
**Сверка индекса с диском - в обе стороны, из двух источников.** После пересборки список файлов берется с диска, список записей - из индекса, и сравниваются оба направления: **файл без строки** делает запись невидимой при загрузке в контекст, **строка без файла** ведет читателя в никуда - это разные дефекты с разной ценой, и одно число "столько файлов, столько строк" их не различает (245 и 245 сходятся и при одном лишнем файле плюс одной лишней строке). Имена сверяются **как есть, без нормализации регистра в шаблоне**: на живой памяти `[a-z_0-9]` не матчил заглавную букву в имени, из-за чего существующую запись трижды сочли потерянной и дважды завели дубль.
### 7. Отчет
Коротко: было столько файлов и строк индекса - стало столько; сверка файлов с индексом (шаг 6) расхождений не нашла - либо какие остались и почему; что слито; что снято; где копия. Если что-то оставлено намеренно (например, сюжет в активной работе) - назвать.
## Грабли
- **Ожидание, что большая память = много дублей.** Интуиция подсказывает, что на сотне файлов сюжеты обязательно продублированы; на живой памяти это оказалось неверно (см. замер в шаге 1). Общее слово в именах - чаще домен, а не сюжет: десять файлов про один продукт могут быть десятью разными фактами о нем. Слияние по такому признаку разрушит различимость и ничего не сократит.
- **Свертка ради цифры.** Цель - чтобы память оставалась полезной, а не чтобы файлов стало меньше. Два несвязанных факта, слитые в один файл "разное", хуже двух отдельных.
- **Потеря "почему".** Самое ценное в памяти - обоснование решения, а не его формулировка. При слиянии первым выпадает именно оно: оно длинное. Сохранять.
- **Снятие того, что просто давно не трогали.** Старое не равно неактуальное. Снимать по признаку "отработало" (сделано, отменено, объект удален), а не по дате.
- **Расхождение двух сессий по синкаемой памяти - это объект в полете, а не чья-то ошибка счета.** Память в vault общая для нескольких машин, и удаление файла на одной еще не доехало до другой: сессия свертки видела 245 файлов против 245 строк, проверявшая независимо - 246 против 245, и обе были правы на свой момент. Разбирать надо не "кто прав", а "на какой момент каждый смотрел": обе стороны называют время наблюдения, а улика (`stat`, копия с сохранением атрибутов - `cp -p`) снимается **до** того, как что-то трогают. В прецеденте файл скопировали без атрибутов и затерли `mtime`, по которому сценарии "остался" и "удаление в полете" различались.
- **Свертка чужой памяти по своей инициативе.** Скилл запускается по решению пользователя или с его подтверждения на аудит - молча переписывать память нельзя.