# Observer factor-analysis extension — design **Версия:** 1.2 **Дата:** 2026-05-20 **Статус:** accepted — фаза 1 реализована (`f7f37fb`); фаза 1.1 (см. §11) реализована (commits `7f379bd..dc6d2dd`); **фаза 1.2 «instrument expansion» (см. §12) — в реализации** **Связано:** ADR-011 (brain governance), spec `2026-05-19-brain-governance-design.md`, Pravila §16, PSR_v1 R16, `docs/observer/`, `tools/observer-stop-hook.mjs`, `tools/observer-transcript-parser.mjs` --- ## 1. Контекст Регламент «мозга» (ADR-011) включает наблюдателя — Stop-хук пишет эпизоды в `docs/observer/episodes-YYYY-MM.jsonl`, `/brain-retro` раз в спринт строит факторный анализ. После follow-up'а 19.05.2026 (`99c7bac`) наблюдатель наполняется реальными данными из транскрипта. Но при разборе с заказчиком вскрылось: данных для **полноценного факторного анализа** не хватает. Факторный анализ объясняет разброс **исходов** через **факторы**. Текущий эпизод: - **Исход фиктивен** — `outcome` хардкод `"success"`. Объяснять нечего, если зависимая переменная константа. Настоящий исход часто известен только в следующем ходе (заказчик говорит «не то»). - **Нет провенанса решения** — эпизод пишет `node_chosen`, но не «кто решил». Заказчик ставит задачи и навязывает методы; Claude исполняет. Если заказчик навязал неверный узел → эпизод покажет `node_chosen: X, outcome: error`, и анализ обвинит роутинг, хотя причина — навязанная инструкция. - **Нет факторов среды** — уровень экономии, модель, пост-компакция, параллельные сессии, длина сессии радикально влияют на исход, но не записываются → анализ искажается смешиванием факторов. - **Эпизод ≠ задача** — одна задача = N ходов = N эпизодов; rework невидим как rework. - **Нет нормировки размера** — «узел X коррелирует с ошибками» может значить «узел X применяют на больших задачах». - **Процессные события не наполняются** — `hook_fired` / `chain_divergence` / `confusion_marker` / `time_burn` заявлены в spec v1.1, не генерируются; прерывания и ретраи не фиксируются. - **Наблюдатель может тихо пропускать** — Stop-хук при ошибке делает молчаливый `exit 0`; пропуск невидим. **Цель:** расширить эпизод и наблюдателя так, чтобы факторный анализ стал возможен; сделать дисциплину ведения наблюдателя принудительной (которую Claude не может обойти) и самоконтролируемой (пропуски наблюдателя видимы). **Граница (вне scope):** независимый LLM-agent-судья («какой узел был правильным»), `confusion_marker` как настоящее суждение, real-time in-session флаги friction. Это отдельная фаза 2 — требует agent + $/сессию + дополнительной нормативки. --- ## 2. Архитектура — 4 слоя ``` Слой 1 Схема эпизода v2 — что хранится Слой 2 Захват (parser + 1 тег) — детерминированный парсинг + routing-тег Слой 3 Принуждение (двустороннее)— 3a routing-gate, 3b самодисциплина наблюдателя Слой 4 Анализ (/brain-retro) — исход, группировка, цепочки, факторная матрица ``` --- ## 3. Слой 1 — схема эпизода v2 Эпизод приобретает `schema_version: 2`. Новые/изменённые поля поверх v1.1 (5 mandatory + 7-полевой `primary_rationale` + `events`): | Поле | Тип | Источник | |---|---|---| | `schema_version` | `2` | константа | | `decision_provenance` | `{ kind, claude_would_have_chosen }` | routing-тег (Слой 2) | | `environment` | `{ economy_level, model, post_compaction, session_turn, parallel_session }` | детерминированный парсинг | | `task_size` | `{ tool_calls, files_touched }` | детерминированный парсинг | | `task_ref` | string | sessionId + сегмент задачи | | `outcome` | enum | провизорно при записи, уточняется Слоем 4 | | `events[]` | расширенные виды | детерминированный парсинг + тег | - `decision_provenance.kind` ∈ `autonomous` | `user_directed_method`. `autonomous` — дефолт (тега нет). `user_directed_method` — тег есть. `claude_would_have_chosen` — узел-контрфактуал, не-null только при `user_directed_method`. (Случай «заказчик задал задачу» — это норма, конфлируется с `autonomous`: для факторного анализа важно только «навязан ли *метод*».) - `environment.economy_level` ∈ `0` | `5` | `100` | `null` — из маркера `=== ECONOMY MODE: N% ===` в транскрипте. - `environment.model` — из метаданных assistant-сообщений; `null` если недоступно. - `environment.post_compaction` — `true` если перед текущим ходом в транскрипте был маркер компакции. - `environment.session_turn` — порядковый номер реального user-prompt в сессии. - `environment.parallel_session` — best-effort: `true` при маркерах коллизии (foreign git index, упоминание параллельной сессии); иначе `false`. Честно: детерминированно ненадёжно. - `task_size.tool_calls` — число `tool_use` в ходе; `files_touched` — число уникальных путей в Read/Edit/Write. - `outcome` ∈ `success` | `partial` | `failure` | `rework` | `blocked` | `unknown`. При записи — `unknown` (честный дефолт вместо фиктивного `success`). Уточняется `/brain-retro`. - `events[].kind` ∈ `skill_invoked` | `tool_summary` | `error` | `hook_fired` | `interrupt` | `retry` | `time_burn` | `parse_gap`. (`confusion_marker`, `chain_divergence` — зарезервированы под фазу 2 / agent.) **Маркер-эпизод `observer_error`** — особая минимальная строка при внутреннем отказе хука: `{ schema_version: 2, observer_error: true, error_message, timestamps, task_id }`. Пишется вместо тихого пропуска. 7-полевой `primary_rationale` из v1.1 сохраняется без изменений (`triggers_matched` / `candidates_considered` / `boundaries_applied` остаются `[]` — детерминированно невосстановимы). --- ## 4. Слой 2 — захват ### 4.1. Расширение `tools/observer-transcript-parser.mjs` Все факторы среды, размер, процессные события — детерминированно из транскрипта: - `economy_level` — regex `=== ECONOMY MODE: (\d+)% ===` по транскрипту хода. - `model` — из `message.model` assistant-сообщений (если поле есть); иначе `null`. - `post_compaction` — поиск compaction-маркера (`isCompactSummary` / запись типа компакции) до начала хода. - `session_turn` — счётчик `isRealUserPrompt` до текущего. - `task_size` — подсчёт `tool_use` + уникальных файловых путей. - `events`: `hook_fired` — из записей `attachment.type === "hook_success"`/`hook_error`; `interrupt` — из маркера `[Request interrupted by user]`; `retry` — эвристика «тот же инструмент по той же цели после `error`»; `time_burn` — производное от длительности хода > порога; `parse_gap` — если доля непарсибельных строк выше порога. ### 4.2. Routing-тег (подход C) Когда заказчик навязал метод, Claude печатает в ответе одну строку: ``` ``` Парсер извлекает тег regex'ом, заполняет `decision_provenance`. Тег эмитится **только** при method-direction; на автономных ходах тега нет, провенанс по умолчанию `autonomous`. Тег — HTML-комментарий: виден заказчику (прозрачность — это сама по себе обратная связь), не шумит в рендере. --- ## 5. Слой 3 — принуждение (двустороннее) ### 5.1. 3a — routing-gate (сторона Claude) Логика в `tools/observer-stop-hook.mjs`: 1. **Всегда сначала** — записать эпизод (как Слой 1/2), exit-0-safe. 2. **Затем** — детектор «навязан ли метод» (детерминированный, консервативный): regex по последнему user-prompt — содержит ли имя узла/скила из реестра в директивном контексте (`запусти X`, `используй X`, `через X`, `/команда`, имя скила). Список известных узлов — из Tooling Прил. Н + список скилов. 3. Если метод навязан **и** в ответе нет `` тега → хук выводит `{ "decision": "block", "reason": "<инструкция: добавь routing-тег>" }`. 4. `decision: block` заставляет Claude продолжить ход; Claude добавляет тег; следующий Stop — хук перепроверяет, проходит. **Необойдёмость:** Stop-хук исполняется Claude Code детерминированно в конце каждого хода. Claude не может его пропустить, рационализировать или обойти через Bash. Завершить ход без тега (когда метод навязан и детектор сработал) — нельзя. **Честный предел:** не «обход Claude», а **покрытие детектора**. False negative (метод навязан тонко, regex не поймал) → тега нет → провенанс ложно `autonomous`. False positive (имя узла упомянуто вскользь) → лишний блок. Детектор делается консервативно-широким — false negative минимизируется ценой редких false positive. ### 5.2. 3b — самодисциплина наблюдателя - **Видимость отказа.** `observer-stop-hook.mjs` оборачивается так: при любой внутренней ошибке — записать маркер `observer_error` и `exit 0`. Принцип «не блокировать Stop» сохранён, пропуск становится видимым в данных. - **Видимость потери данных.** Парсер при доле непарсибельных строк выше порога пишет событие `parse_gap`. - **Контроль покрытия.** Расширение C3 observer-of-observer (или новый контролёр): сверяет «есть git-активность за период, но 0 эпизодов» → флаг в STATUS.md. Тихая смерть наблюдателя становится заметной. - **Целостность регистрации.** Проверка (стиль C1 L1-watcher): Stop-хук прописан в `.claude/settings.json`; `.git/hooks/post-commit` установлен. Удалили/сломали → флаг. ### 5.3. HK1 — анализ коллизии хуков (ADR-010) Stop-событие сейчас несёт: user-level economy-verifier (agent) + project-level `observer-stop-hook` (command). routing-gate встраивается **внутрь существующего** `observer-stop-hook` — нового Stop-entry не добавляется (остаётся 2). Оба способны вернуть `decision: block` — конфликта нет: Claude Code прогоняет все Stop-хуки, любой `block` ведёт к продолжению хода. observer-gate детерминированный и дешёвый. UserPromptSubmit-напоминание (осознанность) — отдельный лёгкий компонент, не Stop. HK1: коллизии нет. --- ## 6. Слой 4 — анализ (`/brain-retro`) `/brain-retro` остаётся read-only агрегатором (не мутирует JSONL). Расширения: - **Вывод настоящего исхода.** Для каждого эпизода: `interrupt`-событие → `partial`; больше `error`-событий, чем `retry` (невосстановленный сбой) → `blocked`; иначе — по первому user-prompt **следующего** эпизода той же сессии: коррекция (`не то` / `не так` / `переделай` / `откати` / `сломал` / `не работает` / `revert` / … — расширенный набор) → исход прошлого = `rework`; одобрение/новая задача (`ок` / `спасибо` / `дальше` / `готово`) → `success`; нет следующего → `unknown`. `failure` детерминированно невосстановим (суждение «работа неверна И не исправлена») — отложен в фазу 2 (agent-судья). Уточнённый исход — в retro-ноте (JSONL не трогается, append-only). - **Группировка «эпизоды → задача».** `task_ref` по sessionId; сегментация — новая задача начинается с top-level user-prompt после `success` или после паузы. - **Каузальные цепочки.** Детерминированная корреляция: эпизоды, делящие `files_touched`; `error` в N → исправление того же файла в N+1. Surface как «кандидаты цепочек». - **Факторная матрица.** Строки — факторы (`decision_provenance.kind`, `economy_level`, `model`, `post_compaction`, бакет `session_turn`, `parallel_session`, бакет `task_size`, `node_chosen`, `task_classification` — 9 осей); столбцы — распределение `outcome`. Пример вывода: «`user_directed_method`: 40% rework против `autonomous` 12%» — прямой ответ на вопрос заказчика «моя ли вина». --- ## 7. Нормативные изменения - **ADR-011** — поправка: добавить решение «observer factor-analysis extension v2» (схема v2, двустороннее принуждение). - **Pravila §16** — §16.2 расширить: схема эпизода v2; **новое правило routing-тег-дисциплины** (заказчик навязал метод → Claude обязан эмитить routing-тег; Stop-gate enforced); **правило самодисциплины наблюдателя** (каждый Stop фиксируется; внутренний отказ → `observer_error`, не замалчивается; покрытие и регистрация проверяются контролёром). - **PSR_v1 R16** — синхронизировать (brain evidence loop — расширенные события). - spec brain-governance — cross-ref на этот документ. Все правки нормативки — через pre-flight sync (Pravila §15.2, 8-файловый список); CLAUDE.md — через `claude-md-management`. --- ## 8. Тест-план - **Unit (Vitest, `tools/*.test.mjs`):** расширения парсера — извлечение `economy_level` / `model` / `post_compaction` / `session_turn` / `task_size`; события `hook_fired` / `interrupt` / `retry` / `time_burn` / `parse_gap`; парсинг routing-тега; путь `observer_error`. - **Unit:** детектор routing-gate (regex по известным узлам), логика `decision: block`. - **Unit:** `/brain-retro` — вывод исхода из следующего эпизода, группировка, факторная матрица. - **Integration:** CLI smoke — хук против реального транскрипта → v2-эпизод наполнен; симуляция method-directed хода без тега → хук выводит `decision: block`. - Полный tools Vitest GREEN; lefthook pre-commit зелёный. --- ## 9. Открытые риски - **R1 — покрытие детектора routing-gate.** Regex по именам узлов: false negative (тонкая директива) / false positive (упоминание вскользь). Митигируется консервативной широтой; остаточный риск принят. - **R2 — `model` / `parallel_session` детерминированно ненадёжны.** Записываются best-effort, `null`/`false` при недоступности. Не блокирует факторный анализ по остальным факторам. - **R3 — вывод исхода эвристичен.** Next-prompt sentiment — приближение; систематический сдвиг (заказчик не всегда реагирует словом коррекции). Принято для фазы 1; точный исход — фаза 2 (agent). - **R4 — два Stop-блокатора.** economy-agent + observer-gate. Сосуществуют (HK1 §5.3), но при одновременном блоке Claude получает два reason'а — нормально, оба отрабатываются. - **R5 — `transcript_path` в Stop-payload.** Унаследовано из Item 1 (`99c7bac`): если Claude Code не передаёт путь — хук деградирует, эпизод беднеет. Проверяется post-deploy. - **R6 — reconstructive bias контрфактуала.** `claude_would_have_chosen` пишет тот же Claude, что исполнял задачу. Сдвиг признан заказчиком осознанно; независимая проверка — фаза 2 (agent). --- ## 10. Что НЕ делаем (фаза 2, отдельный spec) Независимый agent-судья «какой узел был правильным»; `confusion_marker` / `chain_divergence` как настоящие суждения; real-time in-session флаги friction; автоматические правки нормативки по результатам факторного анализа (остаётся: `/brain-retro` только предлагает кандидатов). --- ## 11. Phase 1.1 amendment — `user_chose_from_options` (2026-05-19) ### 11.1. Контекст Live-триггер 19.05.2026 при выполнении фазы 1: при предложении пользователю A/B/C-выбора (numbered/lettered options в моём ответе) — пользователь короткими промптами выбирает один (`1 экономия 0%`, `в делаем`, `делай 2`). Routing-gate классифицирует такой ход как `user_directed_method`, но это **не** навязанный извне метод — выбор сделан из choice-space, который я сам сформулировал. Контрфактуал тоже отличается: в `user_directed_method` контрфактуал — «что бы я выбрал автономно без директивы», а здесь — «какую из *моих* опций я бы рекомендовал». Для факторного анализа эти случаи смешиваются → искажение матрицы. Третий kind `decision_provenance` устраняет смешение. ### 11.2. Схема (расширение §3) `decision_provenance.kind` расширяется до 3 значений: `autonomous` | `user_directed_method` | `user_chose_from_options`. Для `user_chose_from_options` `decision_provenance` принимает форму: ```json { "kind": "user_chose_from_options", "node": "<выбранный пользователем>", "options_offered": ["", "", ""], "claude_would_have_chosen": "<моя рекомендация — первый из options_offered по конвенции>" } ``` Поле `claude_would_have_chosen` — то же поле, что и в `user_directed_method` (semantic: «что выбрал бы Claude автономно / самостоятельно»). Для `user_chose_from_options` его значение — «моя рекомендация из предложенных опций». Tag-literal `counterfactual=` в routing-теге сохраняется (mapping `counterfactual=X` → `claude_would_have_chosen: X`). Поле `node` — для `user_directed_method` не записывалось (только `claude_would_have_chosen` контрфактуал); для `user_chose_from_options` фиксирует фактический выбор пользователя. `options_offered` — массив, появляется только при `kind: user_chose_from_options`. ### 11.3. Детектор (новый `tools/observer-choice-detector.mjs`) Pure-функция `detectChoiceProvenance(promptText, lastAssistantContent)` → `{ kind, options, chosen, counterfactual } | null`. **Шаг 1 — извлечь опции из последнего assistant-сообщения** (broad detection per user-approved scope): - `AskUserQuestion` tool_use → опции из `questions[].options[].label`. - Numbered list: `^\d+[.\)]\s+...` (минимум 2 подряд). - Lettered list: `^[A-Za-zА-Яа-я][.\)]\s+...` (минимум 2 подряд, латиница ИЛИ кириллица). - Bullet list: `^[-*]\s+...` (минимум 2 подряд). Если найдено `<2` опций — return `null`, fallback на текущую логику. **Шаг 2 — найти ссылку в user-prompt:** - Position-based: prompt начинается с числа/буквы (опционально после verb-префикса: `делай|выбираю|беру|хочу|вариант|option`), сопровождаемой пробелом/запятой/точкой/EOL. Поддержка кириллицы + латиницы (keyboard-layout collision: `B` ↔ `В`). - Substring match: prompt содержит первые 2-4 слова из label одной из опций. Если ни один сигнал не найден — return `null`. **Шаг 3 — собрать результат:** - `chosen` — label/node соответствующей выбранной опции. - `claude_would_have_chosen` — первая опция из списка (моя рекомендация по конвенции). - `options_offered` — все извлечённые опции (как массив строк). ### 11.4. Routing-gate (расширение §5.1) Существующая логика (`routingGateDecision` в `tools/observer-stop-hook.mjs`) расширяется: 1. **Сначала** — `detectChoiceProvenance` (новый). Если вернул не-null → `decision_provenance.kind = user_chose_from_options`, **gate НЕ блокирует** (collaborative-choice; tag желателен, но не обязательный — данные восстановимы детерминированно из транскрипта). 2. **Иначе** — текущий `detectMethodDirected` (как сейчас). Если directed без тега → `decision: block`. 3. Routing-тег `` Claude может эмитить добровольно (для прозрачности); парсер его примет, но детектор работает и без него. ### 11.5. Парсер (расширение §4) `tools/observer-transcript-parser.mjs` — добавляется вызов `detectChoiceProvenance` ДО `detectMethodDirected`. Извлечение `lastAssistantContent` — последняя assistant-message строка перед текущим user-turn в JSONL транскрипта. Если `detectChoiceProvenance` срабатывает — `decision_provenance` собирается из его результата; routing-тег (если есть) проверяется на consistency (warn, не fail). Если нет — старая логика (тег или autonomous). ### 11.6. /brain-retro (расширение §6) Факторная матрица: ось `decision_provenance.kind` теперь имеет 3 значения. Counterfactual-анализ: для `user_chose_from_options` отдельная строка «пользователь выбрал не первую (мою рекомендованную) опцию — N% случаев, rework-доля Y%». ### 11.7. Тест-план - Unit (`tools/observer-choice-detector.test.mjs`): извлечение опций из AskUserQuestion / numbered / lettered / bullets; position-based references (число/буква, кириллица/латиница, verb-префиксы); substring label match; negative cases (нет опций / нет ссылки → null). - Unit (расширение `observer-stop-hook.test.mjs`): `routingGateDecision` с `user_chose_from_options` — НЕ блокирует. - Unit (расширение `observer-transcript-parser.test.mjs`): сборка эпизода с `kind: user_chose_from_options`, миграция чтения `claude_would_have_chosen` → `counterfactual`. - Integration: CLI smoke — реальный транскрипт с AskUserQuestion-ходом → эпизод корректно классифицирован. ### 11.8. Out of scope (phase 1.1) - Детектор «опции в потоке текста» (например, `«вариант A — короткий», «вариант B — длинный»` в inline-прозе без списка) — out, эвристика низкой надёжности. - Распознавание «пользователь дополнил мою опцию» (выбрал B, но изменил параметр) — out, требует семантического понимания. - Refine routing-gate detector (исключение `` блоков из scope) — отдельная задача, см. memory `project_brain_governance_design.md`. --- ## 12. Phase 1.2 amendment — instrument expansion (2026-05-20) Реализованы 18 рекомендаций из brain-retro 2026-05-20 (план `docs/superpowers/plans/2026-05-20-observer-instrument-expansion.md` v1.1 REVISION). Worktree: `.claude/worktrees/observer-v2-expansion`, ветка `feat/observer-v2-expansion`. ### 12.1. Схема v2 (расширения) - **Опциональное поле `task_cost`** в episode (#2): захват `message.usage` agregat по Σ всех assistant-сообщений turn'а. Поля: `input_tokens`, `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`, `web_search_requests`, `web_fetch_requests`, `iterations` (B1-verified shape, бонус `server_tool_use` + `iterations` поверх 4 базовых). Backward-compat: `V2_FIELDS` validator не расширен — старые v2-эпизоды без `task_cost` остаются валидны. - **`outcome` enum** дополнен `soft_success` (#16): next-prompt `neutral` интерпретируется как silent success ('no objection'). Слабее explicit approval, отдельно labelled. - **Новые `events[]` kinds:** - `ask_user_question` (#4) — per AskUserQuestion question с `answer_kind` ∈ `option|custom|no_answer`. Сигнал quality предлагаемых options. - `subagent_invoked` (#12) — per Agent tool_use с `subagent_type` / `model` / `description` (80 chars). - `error` enriched (#7) — теперь несёт `tool` (имя инструмента, атрибуция через id-map) + `summary` (first 80 chars причины); раньше bare `message: 'tool_result reported is_error'`. ### 12.2. Парсер — heuristic capture (Слой 2) - **`primary_rationale` arrays больше не пусты** (#6): три pure-функции `extractTriggers` / `extractCandidates` / `extractBoundaries` сканируют assistant.text на маркеры (Pravila §N, ADR-N, PSR_v1 RX, routing-off-phase LN, hard-floor/rule + numbered/bulleted lists ≥2). Conservative-broad — false positives accepted; agent-judge остаётся out-of-scope (фаза 2 §10). - **Opt-in reasoning-tag** (#11): `` HTML-комментарий в assistant.text; semicolon-separated значения merged в heuristic arrays через Set-dedupe. - **`` strip в `promptText`** (#8): UserPromptSubmit hook injections больше не загрязняют `classifyTask` / `classifyPromptSignal` / routing detection. - **`classifyTask` vocabulary +7 classes** (#1): `analysis` / `memory-sync` / `regulatory-bump` / `release` / `cleanup` / `monitoring` / `planning`. Closes «59% other» observation. - **`classifyPromptSignal` vocabulary +** (#9): `correction` +'не совсем', 'другое', 'wrong direction'; `approval` +'класс', 'well done', 'nice'; `new_task` prefix +'теперь', 'далее', 'next', 'now'. Bug fix: JS `\b` не работает с Cyrillic (Cyrillic ≠ word char) — substring match для русских correction markers, lookahead для prefix-based new_task. - **`parallel_session` +OR pre-flight git fetch** (#13 PIVOT): additive signal — Bash command `git fetch && git log HEAD..origin/...` (Pravila §15.2 pre-flight) = strong signal для parallel sessions. Не overwrite F1 narrow-to-tool_result детектор; OR-clause. ### 12.3. Анализатор (Слой 4) - **`session_segment_turn` axis rename** (#14): factor matrix ось `session_turn` → `session_segment_turn` (turns-since-last-compaction — что фактически и было per parser). Семантика не меняется, только имя для ясности. - **`inferOutcome` neutral → soft_success** (#16) — см. §12.1. ### 12.4. STATUS.md generator - **Real PII counter** (#3 SIMPLIFIED): `sanitizeWithCount` в pii-filter - persistent `docs/observer/.pii-counters.json` (per-month aggregation, bumped on each Stop-hook write) + `countPiiMatches()` reads counter. STATUS перестаёт врать `0 PII matches`. PII patterns themselves NOT changed (F7 of parallel session already extended). - **`Last /brain-retro: N day(s) ago`** (#10): метрика читается из `docs/observer/.read-counter.json` `last_read_at`. - **`Legacy v1 episodes (not in factor analysis)`** (#18): count of pre-2026-05-19T08:06 episodes без schema_version=2 — visible. ### 12.5. /brain-retro skill - **Step 4 explicit `recordRead`** (#15): replaced abstract 'bump' instruction with `node tools/observer-of-observer.mjs record`. Atomic read-modify-write через fs. - **Step 8a STATUS auto-refresh** (#19): после save retro-note запускается status-md-generator — STATUS.md становится immediately current (`Last /brain-retro: 0 day(s) ago`, fresh episode count). ### 12.6. Ad-hoc tooling - **`tools/glob-latency-investigator.mjs`** (#17): one-off script для расследования Glob p50=12.7s аномалии из исходного ретро. Smoke-test на session 553717ec: top-5 slowest все `docs/adr/**` @ 20265ms — Glob recursive по ADR-каталогу = apparent culprit. Не production code path. ### 12.7. Infrastructure - **`npm run test:tools`** (B3-1, applied вне scope плана): canonical entry point для `tools/observer-*.test.mjs` runner. Корневой `package.json` script. ### 12.8. Not done - Phase 2 (out-of-scope, см. §10 + spec brain-governance §10): agent-judge для true vs nominal use, `confusion_marker` / `chain_divergence` как real judgments, real-time in-session friction flags, automatic Pravila/Tooling edits from factor matrix. - Task 5 (hot-file two-tier) — **SKIPPED** per REVISION v1.1: F4 of parallel session already added full exclude of `memory/*.md`; warm-tier для adjacent имел diminishing return. ### 12.9. Регрессия После всех 18 task'ов: **NNN/NNN GREEN** в `npm run test:tools` (baseline 232 → final NNN — заполнить в финальном commit Task 21). ## Amendments ### 2026-05-23 — schema v3 (parser skill/hook expand) Spec extension: forward-only bump `schema_version` 2 → 3. Two new fields: - `events[].hook_fired.scripts: { script_name: count, ... }` — reverse-lookup `.claude/settings.json` → имена хук-скриптов. Old `counts` (matcher level) preserved для backward-compat. - `primary_rationale.recommended_node: "#NN" | null` — для direct-эпизодов derived из `classification-map` + dormancy. null при использованном skill / отсутствии рекомендации / всех dormant. Analyzer фильтр `schema_version === 2` → `>= 2`; `missed-activations` фильтр `!== 2` → `< 2`. FACTOR_FNS +recommended_node_for_direct. Полный spec: `docs/superpowers/specs/2026-05-23-observer-parser-skill-hook-expand-design.md`.