# Compact Guard

> Переживание сжатия контекста без потери состояния: что фиксировать ДО компакта и что делать СРАЗУ ПОСЛЕ. Триггеры: «контекст заполняется», «скоро компакт», предупреждение context-guard на 80/90%, момент после сжатия. Не про хендофф (`session-handoff`) и не про бутстрап роли (`workflow`).

- Skill: `vibeengineering-llc/compact-guard` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add vibeengineering-llc/compact-guard`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vibeengineering-llc/compact-guard/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: VibeEngineering-LLC (https://skillmd.com/u/vibeengineering-llc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vibeengineering-llc/compact-guard

---


# compact-guard — не терять состояние при сжатии контекста

Сжатие контекста (`/compact` вручную или автоматическое при переполнении окна) выбрасывает
большую часть диалога. Автоматическое приходит **без предупреждения**: только что шла работа —
и вот агент уже не помнит, что правил, о чём договорились и зачем запускал фоновую задачу.
Дальше он достраивает пробел по догадке и уверенно врёт оператору.

Скилл закрывает это двумя механическими хуками (они работают сами, без участия модели) плюс
дисциплиной, которую соблюдает агент.

## Как устроен механизм (уже установлен и проверен 2026-08-15)

| Слой | Что | Когда срабатывает |
|---|---|---|
| **PreCompact-хук** | `scripts/precompact_snapshot.py` | ПЕРЕД сжатием, и авто, и ручным |
| **SessionStart-хук** (matcher `compact`) | `scripts/postcompact_restore.py` | СРАЗУ ПОСЛЕ сжатия |
| **UserPromptSubmit-хук** (уже был) | `~/.claude/hooks/context_guard.py` | на 80% и 90% заполнения, когда оператор пишет промпт |

Оба новых хука зарегистрированы в `~/.claude/settings.json` (бэкап перед правкой:
`settings.json.bak-precompact-20260815`).

### Что пишет PreCompact-хук

Снапшот в `C:\Users\<you>\.claude\compact-snapshots\snap_<дата>-<время>_<сессия>.md` плюс указатель
`latest.json`. Внутри — **только механически проверяемое**, без интерпретации:

- последние указания оператора **дословно** (основной источник — user-записи со СТРОКОВЫМ
  content, они пишутся сразу при отправке; `last-prompt` — запасной, он пишется с лагом в один
  ход — см. P-003);
- файлы, которые правились (`Write`/`Edit`/`NotebookEdit`);
- последние выполненные команды;
- состояние git (ветка, HEAD, незакоммиченное) — если папка вообще git-репозиторий;
- заполнение контекста на момент сжатия, триггер (`auto`/`manual`).

Хранится 40 последних снапшотов, старые удаляются сами.

**Хук НИКОГДА не блокирует сжатие.** Автокомпакт срабатывает, когда окно уже переполнено;
заблокировать его — заклинить сессию. Любая внутренняя ошибка хука тоже проглатывается: сорванный
снапшот не должен ронять работу.

### Что делает SessionStart-хук после сжатия

Инъецирует в свежий контекст **короткий** указатель (не весь снапшот — иначе тут же забьём только
что освобождённое окно): путь к снапшоту, три последних указания оператора, файлы в работе и
требование сверить SESSION-STATE.md до продолжения.

Есть защита от протухания: снапшот старше **30 минут** игнорируется молча — иначе после
перезапуска агент получил бы чужое старое состояние и принял за своё.

## Дисциплина агента — что делать ТЕБЕ

### 1. При предупреждении context-guard (80%) — писать хендофф, не откладывать

Хук `context_guard.py` на 80% просит обновить SESSION-STATE.md. Это **не формальность**: после
80% до автокомпакта может остаться один большой `Read`. Обновляй сразу, в том же ответе.

Что писать — по скиллу **`session-handoff`** (структура, обязательные поля, владелец, правило
устаревания). Этот скилл структуру не дублирует.

### 2. Дежурная гигиена, чтобы автокомпакт не был катастрофой

- **Не держать состояние только в голове.** Договорённость с оператором, принятое решение,
  найденный факт → сразу в SESSION-STATE.md или в задачу (`TaskCreate`/`TaskUpdate`). Задачи
  переживают сжатие, рассуждения — нет.
- **Фоновые задачи — с описанием.** `run_in_background` + внятный `description`: после сжатия
  единственный след задачи — её описание.
- **Крупные чтения — ближе к началу**, а не на 85% заполнения.

### 3. Сразу после сжатия — порядок восстановления

Хук уже подсунул указатель. Дальше:

1. Прочитать SESSION-STATE.md проекта — там **смысл**: решения, план, что нельзя трогать.
2. Прочитать снапшот из `compact-snapshots\` — там **механика**: что реально правилось и запускалось.
3. **Сверить с диском фактом, а не памятью.** После сжатия память ненадёжна: файл, который «точно
   был записан», мог не записаться. Проверять командой/чтением (#SA-1).
4. Не начинать новых крупных задач, пока пункты 1-3 не закрыты.

### 4. Чего НЕ делать после сжатия (anti-patterns)

- ❌ Достраивать пробел по догадке и докладывать оператору как факт. Не помнишь — прочитай снапшот
  или скажи «не помню, проверяю».
- ❌ Считать, что раз в снапшоте файла нет, работа не делалась. Снапшот берёт хвост транскрипта
  (2 МБ) и последние 25 файлов — на очень длинной сессии ранние правки в него не попадут.
- ❌ Заново запускать долгую задачу «на всякий случай», не проверив её результат на диске.
- ❌ Просить оператора пересказать, что было. Для этого и существует снапшот.

## Границы с соседними скиллами

- **`session-handoff`** — СТРУКТУРА хендофф-документа (обязательные поля, владелец, устаревание).
  Используются вместе: там — что писать, здесь — когда и как пережить сжатие.
- **`workflow`** — бутстрап ролей/AGENTS.md для нового проекта. Не про сжатие.
- **§6/§30 глобального `CLAUDE.md`** — пороги гигиены контекста (80/90%) и правила SESSION-STATE.md.
  Этот скилл — исполнительный механизм под них.

## Проверка работоспособности

```bash
# снапшот вручную (подставить свой transcript_path)
echo '{"session_id":"probe","transcript_path":"<путь к .jsonl>","cwd":"<проект>","trigger":"manual"}' \
  | python C:/Users/<you>/.claude/skills/compact-guard/scripts/precompact_snapshot.py

# что записалось
ls C:\Users\<you>\.claude\compact-snapshots\

# что увидит агент после сжатия
echo '{"source":"compact"}' | python C:/Users/<you>/.claude/skills/compact-guard/scripts/postcompact_restore.py
```

Хуки **fail-safe**: при внутренней ошибке они молча возвращают `exit 0`. Поэтому «команда не
упала» ≠ «снапшот записан» — проверять наличие файла на диске (#SA-1; на этом уже споткнулись при
отладке 15.08: хук отвечал успехом, а папки не создавал).

## История

Создан 2026-08-15 по запросу оператора: «важно чтобы агент перед компактом фиксировал состояние
и писал хендофф». Код хуков сгенерирован через IRON-MODE (`workflow/scripts/gen_code.py`,
qwen3-coder) по спекам `scripts/_spec_*.md`; при вычитке найдено и исправлено 5 дефектов
генерации — в том числе `json.dump` без файлового дескриптора, необъявленные переменные в
`latest.json` и сбор указаний оператора. ⚠ Прежний вывод «в `type=="user"` только результаты
инструментов, промпты — в `last-prompt`» оказался ОШИБКОЙ анализа (P-003, 2026-08-15): tool_result
лежат в user-записях со СПИСКОВЫМ content, а реальные промпты — в user-записях со СТРОКОВЫМ
content, и пишутся сразу; `last-prompt` пишется лениво на следующем ходе и терял самую свежую
фразу оператора. Теперь основной источник — user-строки, `last-prompt` запасной. Правки вносить в
спеки, не в сгенерированный `.py`.

**Дефект №6 — найден на живом автокомпакте 2026-08-15, а не при вычитке.** Оператор попросил
«давай проверим на тебе» — первый реальный автокомпакт после установки хука дал `exit=0` и
`{"suppressOutput":true}` (видимость успеха), но файл снапшота на диске оказался **0 байт**, а
`latest.json` не обновился. Причина: скрипт форсировал UTF-8 только на `sys.stdout`, не на
`sys.stdin`. На cp1251-консоли оператора (CLAUDE.md §2) `sys.stdin.read()` декодировал JSON-payload
от Claude Code не тем кодеком — кириллица в `cwd` (в частности слово «Цензор») превращалась в
суррогатные code points; `f.write()` в файл со строгим `encoding="utf-8"` падал
`UnicodeEncodeError: surrogates not allowed`, исключение ковталось широким `except`, оставляя
файл открытым-и-пустым (обнулён `open(...,"w")` до записи содержимого). Поймано ТОЛЬКО потому что
проверил файл на диске после «успешного» прогона (§SA-1), не поверил коду возврата. Диагностика —
патч debug-копии скрипта (traceback в файл вместо ковтания), не десяток догадок. Фикс — добавлен
`sys.stdin.reconfigure(encoding="utf-8", errors="replace")` в обоих хуках сразу после
`stdout.reconfigure`. Перепроверено полным циклом: precompact с реальным payload → снапшот 5379
байт с корректной кириллицей и настоящими `last-prompt` → postcompact вернул корректный
`additionalContext` с правильным путём. Урок: **stdin и stdout форсировать UTF-8 СИММЕТРИЧНО**,
не только вывод — любой хук/скрипт, читающий payload через stdin на этой машине, подвержен той же
дыре, если её не закрыть явно.

