Files
portal/docs/superpowers/specs/2026-05-19-observer-factor-analysis-design.md
T
Дмитрий f4602b4aa5 docs(observer): brain-retro template +hook breakdown + recommended_node
aggregation-template.md gets two new sections (Hook script breakdown,
Recommended-node candidates) + paragraph in Missed Activations.
factor-analysis spec gets a v3 amendment cross-ref to the 2026-05-23 spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:43:28 +03:00

383 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 печатает в ответе одну строку:
```
<!-- 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. **Всегда сначала** — записать эпизод (как Слой 1/2), exit-0-safe.
2. **Затем** — детектор «навязан ли метод» (детерминированный, консервативный): regex по последнему user-prompt — содержит ли имя узла/скила из реестра в директивном контексте (`запусти X`, `используй X`, `через X`, `/команда`, имя скила). Список известных узлов — из Tooling Прил. Н + список скилов.
3. Если метод навязан **и** в ответе нет `<!-- routing: -->` тега → хук выводит `{ "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": ["<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):
- `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-тег `<!-- 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) — отдельная задача, см. 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): `<!-- 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.
- **`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`.