Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 KiB
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.modelassistant-сообщений (если поле есть); иначе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 печатает в ответе одну строку:
<!-- routing: provenance=user_directed_method node=<выбранный> counterfactual=<что выбрал бы сам> -->
Парсер извлекает тег regex'ом, заполняет decision_provenance. Тег эмитится только при method-direction; на автономных ходах тега нет, провенанс по умолчанию autonomous. Тег — HTML-комментарий: виден заказчику (прозрачность — это сама по себе обратная связь), не шумит в рендере.
5. Слой 3 — принуждение (двустороннее)
5.1. 3a — routing-gate (сторона Claude)
Логика в tools/observer-stop-hook.mjs:
- Всегда сначала — записать эпизод (как Слой 1/2), exit-0-safe.
- Затем — детектор «навязан ли метод» (детерминированный, консервативный): regex по последнему user-prompt — содержит ли имя узла/скила из реестра в директивном контексте (
запусти X,используй X,через X,/команда, имя скила). Список известных узлов — из Tooling Прил. Н + список скилов. - Если метод навязан и в ответе нет
<!-- routing: -->тега → хук выводит{ "decision": "block", "reason": "<инструкция: добавь routing-тег>" }. 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 противautonomous12%» — прямой ответ на вопрос заказчика «моя ли вина».
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 принимает форму:
{
"kind": "user_chose_from_options",
"node": "<выбранный пользователем>",
"options_offered": ["<node-1>", "<node-2>", "<node-3>"],
"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):
AskUserQuestiontool_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) расширяется:
- Сначала —
detectChoiceProvenance(новый). Если вернул не-null →decision_provenance.kind = user_chose_from_options, gate НЕ блокирует (collaborative-choice; tag желателен, но не обязательный — данные восстановимы детерминированно из транскрипта). - Иначе — текущий
detectMethodDirected(как сейчас). Если directed без тега →decision: block. - Routing-тег
<!-- routing: provenance=user_chose_from_options node=X counterfactual=Y -->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 (исключение
<system-reminder>блоков из scope) — отдельная задача, см. memoryproject_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.usageagregat по Σ всех 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_FIELDSvalidator не расширен — старые v2-эпизоды безtask_costостаются валидны. outcomeenum дополненsoft_success(#16): next-promptneutralинтерпретируется как 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).errorenriched (#7) — теперь несётtool(имя инструмента, атрибуция через id-map) +summary(first 80 chars причины); раньше baremessage: 'tool_result reported is_error'.
12.2. Парсер — heuristic capture (Слой 2)
primary_rationalearrays больше не пусты (#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):
<!-- reasoning: triggers="..." candidates="..." boundaries="..." -->HTML-комментарий в assistant.text; semicolon-separated значения merged в heuristic arrays через Set-dedupe. <system-reminder>strip вpromptText(#8): UserPromptSubmit hook injections больше не загрязняютclassifyTask/classifyPromptSignal/ routing detection.classifyTaskvocabulary +7 classes (#1):analysis/memory-sync/regulatory-bump/release/cleanup/monitoring/planning. Closes «59% other» observation.classifyPromptSignalvocabulary + (#9):correction+'не совсем', 'другое', 'wrong direction';approval+'класс', 'well done', 'nice';new_taskprefix +'теперь', 'далее', '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 commandgit 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_turnaxis rename (#14): factor matrix осьsession_turn→session_segment_turn(turns-since-last-compaction — что фактически и было per parser). Семантика не меняется, только имя для ясности.inferOutcomeneutral → 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).
- persistent
Last /brain-retro: N day(s) ago(#10): метрика читается изdocs/observer/.read-counter.jsonlast_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 withnode 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.mjsrunner. Корневойpackage.jsonscript.
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→ имена хук-скриптов. Oldcounts(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.