Files
portal/docs/observer/notes/2026-05-20-brain-retro.md
T

808 lines
60 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.
# Brain-retro — первое ретро мозга по данным наблюдателя
**Дата:** 2026-05-20 (12:25 MSK)
**Период:** 2026-05-19 .. 2026-05-20 (это первое brain-retro вообще; покрывает всё, что наблюдатель записал на текущий момент)
**Источник:** `docs/observer/episodes-2026-05.jsonl` (22 строки) + `docs/observer/.read-counter.json`
**Анализатор:** `node tools/brain-retro-analyzer.mjs docs/observer/episodes-2026-05.jsonl`
**Уровень анализа:** верхнеуровневый по запросу заказчика («потом углубимся по разделам»); экономия 0%.
> Анализатор отчитался: `episodeCount=17`, `v1SkippedCount=5`, `observerErrorCount=0`. Ниже все цифры по 17 v2-эпизодам, если не отмечено иное.
---
## Period
2026-05-19T05:18:16Z .. 2026-05-20T08:12:29Z (5 сессий, 22 эпизода всего; 17 v2-анализируемых).
Сессии:
| task_id (session_id) | дата | v1 эпизоды | v2 эпизоды | примечание |
|---|---|---|---|---|
| `553717ec` | 19.05 05:18 .. 10:24 | 5 | 10 | длинная сессия brain-governance Phase A/B/C + factor-analysis ext; компакция между turn 86–91 |
| `24acfa10` | 20.05 07:08 | 0 | 1 | ExitWorktree-разовый ход |
| `a42e4ba5` | 20.05 07:26 | 0 | 1 | короткая `PowerShell`/`Bash`-проверка |
| `dd905ea0` | 20.05 07:25 .. 07:28 | 0 | 1 | apdate `ЭТАЛОН`-семейства memory (11 Edit) |
| `98298ec2` | 20.05 07:36 .. 08:12 | 0 | 3 | сегодняшняя «починка наблюдателя» (TDD + verify) |
Текущий ход (брейн-ретро 20.05 ~08:25Z+) будет записан Stop-хуком после завершения и в этом ретро ещё **не** учитывается — это нормально по конструкции наблюдателя.
---
## Path-type distribution (v2, n=17)
| path_type | count | % |
|---|---|---|
| improvised | 14 | 82.4% |
| regulated | 3 | 17.6% |
| alternative | 0 | 0% |
| mixed | 0 | 0% |
**Чтение:** «Мозг живёт прямым путём». Лишь два эпизода прошли через явный hard-floor §12: №16 (19.05 10:13, systematic-debugging) и №22 (20.05 07:52, TDD + verification-before-completion). Третий «regulated» — №16-ой же эпизод (тот же epoch, после Stop предыдущего короткого). Каждый regulated-эпизод был обоснован профильным триггером (баг наблюдателя + TDD по правке кода).
---
## Outcome distribution (inferred)
| outcome | count | % |
|---|---|---|
| success | 6 | 35% |
| unknown | 11 | 65% |
| partial | 0 | 0% |
| rework | 0 | 0% |
| blocked | 0 | 0% |
**Чтение:** не было ни одного `correction`-prompt'а от заказчика (next-prompt-sentiment) за период — переделок нет; ни одного `interrupt`; ни одного `unrecovered_error`. 11 «unknown» — это **последние эпизоды задач** или эпизоды, чей след ещё не сменился новой prompt-меткой (6 из 11 — открытые задачи 20.05; 5 — последние в своих task-группах). Это техническое ограничение выборки, не качественный сигнал.
> Поле «failure / aborted» из шаблона анализатор не возвращает — он деградирует до «unknown» (см. `inferOutcome` коммент A-1).
---
## Top nodes used (skill_invoked + node_chosen)
Из 17 эпизодов **15** имеют `node_chosen=direct` (88%). Только 2 эпизода открыто декларируют узел:
| node | times | first | last |
|---|---|---|---|
| `direct` | 15 | 2026-05-19T08:06Z | 2026-05-20T07:45Z |
| `superpowers:systematic-debugging` | 1 | 2026-05-19T10:13Z | 2026-05-19T10:13Z |
| `superpowers:test-driven-development` | 1 | 2026-05-20T07:52Z | 2026-05-20T07:52Z |
Полный список `skill_invoked` events (4 уникальных):
| skill | эпизоды |
|---|---|
| `superpowers:systematic-debugging` | №16 |
| `claude-md-management:claude-md-improver` | №16 |
| `superpowers:test-driven-development` | №22 |
| `superpowers:verification-before-completion` | №22 |
**Все остальные 60+ узлов реестра** за период не использовались. Это **не проблема** (см. memory `feedback_brain_unused_tools_not_problem.md`).
---
## Factor analysis matrix (v2 — из `factorMatrix`)
### `decision_provenance` — главный фактор «rework мой или router's?»
| provenance | success | partial | rework | unknown |
|---|---|---|---|---|
| `autonomous` | 5 | 0 | 0 | 8 |
| `user_directed_method` | 1 | 0 | 0 | 2 |
| `user_chose_from_options` | 0 | 0 | 0 | 1 |
**Чтение:** 76% решений автономные; 18% — заказчик навязал метод; 6% — collaborative-choice (Phase 1.1, routing-gate не блокирует). Метрика **rework = 0 во всех ячейках** — за период нет признаков «rework по моей вине» либо «rework по навязанному методу». Ranking фактора сейчас **inconclusive из-за нулевого rework**.
### `user_directed_method` — что заказчик навязывал
| ход | дата | claude_would_have_chosen | контекст |
|---|---|---|---|
| ep. 7 | 19.05 08:10 | `subagent-driven-development` | сразу после post_compaction (turn 83) |
| ep. 8 | 19.05 08:13 | `subagent-driven-development` | следующий ход той же сессии |
| ep. 10 | 19.05 08:25 | `brainstorming` | через 12 минут |
Все три — turn 83–88 на той же сессии, сразу после `post_compaction=true` (компакция отрезала контекст). **Это паттерн «после компакции заказчик возвращает рулевое».**
### `economy_level`
| economy_level | success | unknown |
|---|---|---|
| `null` | 2 | 2 |
| `0` | 0 | 1 |
| `5` | 3 | 5 |
| `100` | 1 | 3 |
`null` = sentinel не поставлен (legacy 4 эпизода 19.05 утром). Прочие — три тарифа (0/5/100) используются равноценно по решению заказчика per task. Никаких корреляций «экономия→outcome» при текущем n=17 не вытащить.
### `model`
| model | success | unknown |
|---|---|---|
| `claude-opus-4-7` | 6 | 11 |
Все 17 v2-эпизодов на Opus 4.7. Контрольной группы (Sonnet/Haiku) в данных нет → факторный анализ по модели **невозможен**. Это ожидаемо: subagent-driven-development вызывает Sonnet/Haiku субагентами, чьи трейсы в основной transcript не попадают (Stop-хук пишет только верхнего Claude).
### `post_compaction`
| post_compaction | success | unknown |
|---|---|---|
| `true` | 6 | 5 |
| `false` | 0 | 6 |
Псевдо-корреляция: все `post_compaction=false` эпизоды относятся к 20.05 (новые сессии, ещё не успели получить `next-prompt`) → все 6 как unknown по конструкции. **Сигнал не валиден.**
### `session_turn` бакеты
| bucket | success | unknown |
|---|---|---|
| `early` (<10) | 0 | 6 |
| `late` (≥40) | 6 | 5 |
| `mid` (1040) | 0 | 0 |
Та же артефакт-корреляция, что и с `post_compaction`: «early» = новые сессии 20.05 → unknown по техническому ограничению. Истинного сигнала нет.
### `parallel_session`
| parallel | success | unknown |
|---|---|---|
| `true` | 5 | 5 |
| `false` | 1 | 6 |
10/17 (59%) ходов шли в режиме параллельных Claude-сессий (Pravila §15 в работе). Outcome по этому фактору inconclusive — нужны более длинные выборки.
### `task_size`
| size | success | unknown |
|---|---|---|
| `small` (<20 tools) | 6 | 9 |
| `medium` (2060) | 0 | 2 |
| `large` (>60) | 0 | 0 |
88% эпизодов — small. Два medium-эпизода — №20 (22 tool_calls, 13 files: orientation-read для текущего ретро) и №22 (56 tool_calls, 11 files: текущая TDD-починка наблюдателя). Оба ещё «unknown» по next-prompt.
### `node_chosen`
| node | success | unknown |
|---|---|---|
| `direct` | 6 | 9 |
| `superpowers:systematic-debugging` | 0 | 1 |
| `superpowers:test-driven-development` | 0 | 1 |
Все 6 «success» — direct-эпизоды; оба skill-эпизода ещё unknown (regulated, не было follow-up correction/approval prompt'а).
### `task_classification`
| classification | success | unknown |
|---|---|---|
| `bugfix` | 1 | 0 |
| `feature` | 1 | 2 |
| `refactor` | 1 | 0 |
| `question` | 0 | 2 |
| `other` | 3 | 7 |
Преобладание `other` (10/17 = 59%) — индикатор того, что классификатор парсера часто не находит явной семантики prompt'а. Это не проблема алгоритма — это просто стиль работы за период (короткие нейтральные ходы по правке нормативки и memory).
---
## Episodes → tasks (из `tasks` анализатора)
11 групп. «turns that are rework» определены как эпизоды, чей `_inferredOutcome === 'rework'`. Таких **0** в этом периоде.
| task_ref | эпизодов | turns-rework |
|---|---|---|
| `553717ec…#1` | 1 | 0 |
| `553717ec…#2` | 2 | 0 |
| `553717ec…#3` | 1 | 0 |
| `553717ec…#4` | 3 | 0 |
| `553717ec…#5` | 1 | 0 |
| `553717ec…#6` | 1 | 0 |
| `553717ec…#7` | 2 | 0 |
| `24acfa10…#8` | 1 | 0 |
| `a42e4ba5…#9` | 1 | 0 |
| `dd905ea0…#10` | 1 | 0 |
| `98298ec2…#11` | 3 | 0 |
---
## Causal-chain candidates (из `causalChains`)
**0 цепочек** (анализатор вернул пустой массив).
Это здоровый сигнал: hot-file фильтр (CLAUDE.md / MEMORY.md / STATUS.md / `episodes-*.jsonl` / `memory/*.md`) корректно отсекает ложные «А упал на X-файле — через 6 эпизодов Б трогает X-файл». Реальных error→fix цепочек по не-горячим файлам наблюдатель не нашёл, что согласуется с low-error профилем периода (3 `error`-event'а — все рутинные «tool_result is_error», ни одного `unrecovered_error`).
---
## Observer health
| метрика | значение | оценка |
|---|---|---|
| `observerErrorCount` | 0 | ✅ зелено — наблюдатель не падал тихо |
| `v1SkippedCount` | 5 | ️ legacy: schema v2 раскатан с 2026-05-19T08:06Z; 5 ранее-утренних эпизодов 19.05 (05:1806:57) — v1 без `environment`/`prompt_signal`/`decision_provenance`, анализатор их корректно отфильтровал |
| `retry` events | 4 (в 2 эпизодах) | в норме — №6 (1 retry), №14 (2 retry); все восстановлены |
| `error` events | 3 (в 3 эпизодах) | рутинные `tool_result is_error`; **ни одного** `unrecovered_error` (outcome не blocked) |
| `time_burn` events | 1 (19m45s в №22) | большой TDD-эпизод, ожидаемо |
| `interrupt` events | 0 | нет прерываний |
| `parse_gap` events | 0 | парсер не сообщал о пропусках |
| `confusion_marker` events | 0 | парсер не пишет такого вида события за период (ожидаемо; маркеры — поведенческое наблюдение, не tool-event) |
---
## Canonical chains L1L12 hit rate (предварительно)
Канонические связки описаны в `docs/routing-off-phase.md` (PSR_v1 R15, Rec4 SYSTEM-аудита). Полная сверка с L1–L12 — задача углубления. Верхнеуровневая оценка:
- **Явных L-цепочек 0**, так как 88% эпизодов — `direct`/`improvised`, без декларации canonical chain в `primary_rationale.boundaries_applied` или `triggers_matched`.
- **Возможный L-кандидат** в эпизоде №16 (19.05 10:13): связка `superpowers:systematic-debugging``claude-md-management:claude-md-improver` — это похоже на «debug-чейн с фиксацией learning'а в CLAUDE.md» (близко к L-связке runtime-debug + infrastructure-edit, но точная маркировка L? — задача углубления).
- **Возможный L-кандидат** в эпизоде №22 (20.05 07:52): `superpowers:test-driven-development``superpowers:verification-before-completion` — это **классический TDD-чейн** (близок к канонической связке для feature-кода).
| chain (предварит.) | times | notes |
|---|---|---|
| TDD → verification | 1 | ep.22 (текущий) |
| systematic-debugging → claude-md-improver | 1 | ep.16 |
| Все остальные L1–L12 | 0 | за период не видно |
> Пометка для углубления: считать canonical-chain hit rate не по `skill_invoked`-парам (это сильный сигнал, но узкий), а по сочетанию `triggers_matched` + `boundaries_applied` per `routing-off-phase.md`. Сейчас `triggers_matched` и `boundaries_applied` пусты во всех эпизодах — это `primary_rationale` ещё пишется парсером в «минимальном» режиме.
---
## Improvised chains (path_type=improvised, repeated ≥2)
Конкретных повторяющихся «node-set»-цепочек не вижу — большинство improvised-эпизодов либо однотонные (один Edit/Read/Bash-набор по разным memory-файлам), либо изолированы. Зато виден **повторяющийся pattern по предметной области**:
| pattern (тематический) | эпизодов | candidate L13+? |
|---|---|---|
| Self-repair наблюдателя (правки `tools/observer-*` или `docs/observer/*`) | 8 из 17 (47%) | нет — это техдолг bootstrap, не повторяющийся бизнес-чейн |
| Sync `memory/*.md` после изменений (особенно `project_state.md`/`reference_github.md`) | 3 | нет — это часть «§4.6 self-review» (Pravila) |
| Правка `CLAUDE.md`-секций после нормативных bumps | 2 | нет — закрывается обязательным каналом `claude-md-management` |
---
## chain_divergence cases
Нет данных — `primary_rationale.candidates_considered` пуст во всех 17 эпизодах, парсер пока не пишет «рассматриваемые альтернативы». В разделе для углубления — улучшить парсер, чтобы вытаскивать candidate-узлы из текста хода (если такие декларации были в reasoning).
---
## Top error classes
| error class | count | recovery pattern |
|---|---|---|
| `tool_result reported is_error` (рутинный) | 3 | retry внутри хода, без эскалации; outcome не blocked |
| `unrecovered_error` | 0 | — |
| `interrupt` | 0 | — |
Тип-распределение — все три «error»-event'а одинаковые («tool_result reported is_error»). Это значит, что парсер не различает классы ошибок ниже этого уровня (известное ограничение — A-1 fix в `inferOutcome`).
---
## confusion_marker hot-spots
Нет данных. `confusion_marker` — это event, который должен писаться парсером при обнаружении паттернов сомнения в reasoning Claude. За период парсер не зарегистрировал ни одного → либо подобного не было, либо паттерн распознавания пуст (см. Кандидаты ниже).
---
## Candidates for owner review
Все кандидаты ниже — **предложения**, не действия. Заказчик решает, какое (если вообще) применить.
### Candidate 1: «Self-repair наблюдателя» — 47% периода, в норме для bootstrap-фазы
- **Тип:** наблюдение, не изменение нормативки.
- **Evidence:** 8 из 17 эпизодов трогали `tools/observer-*` или `docs/observer/*` (эпизоды 6, 13, 14, 16, 20, 22 + два косвенно). Episodes-2026-05.jsonl touched в эпизодах 14, 20. Самый «толстый» — №22: 56 tool_calls, 11 файлов, 19m45s — TDD-починка парсера/STOP-хука/PII-фильтра/coverage-checker.
- **Чтение:** мозг **сейчас в фазе самовосстановления** (brain governance Phase A/B/C закрыта 19.05 + factor-analysis extension + phase 1.1 — это всё out-of-feature work, инструментальный долг). После того как Phase C завершит «стабилизацию» (lefthook jobs 1115 работают, observer-of-observer 54w self-prune, STATUS.md generator) — % должен опуститься.
- **Suggested action:** **не вмешиваться**. На следующем ретро сравнить % «self-repair» эпизодов; если останется ≥40% при больших n — это будет сигнал к делегации (например, выделить наблюдатель в фоновый daemon, чтобы не отъедал tool-budget основного Claude).
- **Cost / risk:** 0 сейчас; ~0 на следующем ретро.
### Candidate 2: «Direct path 88%» — здоровый низкий уровень regulated/skill-инвокаций, но требует контроля
- **Тип:** наблюдение + предложение по парсеру.
- **Evidence:** только 2/17 эпизодов проходят через hard-floor §12 (`primary_rationale.hard_floor.invoked=true`). Остальные 15 — `direct`. При этом из 15 ни один не упомянул `boundaries_applied` или `triggers_matched` (пустые массивы во всех 17 эпизодах).
- **Гипотеза:** парсер пока **не вытаскивает** «незакрытые» triggers/boundaries — он пишет только явные `skill_invoked` events. Это значит, что мы не видим, **сколько раз §12.2 карта могла бы trigger'нуть skill, но Claude пошёл direct**. Без этого нельзя различить «direct правильно (не было триггера)» и «direct неправильно (был триггер, проигнорирован)».
- **Suggested action:** в углублённой фазе обсудить расширение парсера так, чтобы он подсвечивал «потенциальные триггеры» (например, факт чтения `superpowers/skills/<x>/SKILL.md` без последующего `skill_invoked`, или Bash-команд, типа `gitleaks`/`pest`/`vitest`, не обёрнутых в `verification-before-completion`). Это превратит «не видим, шёл ли мозг мимо карты» в реальную метрику.
- **Cost / risk:** парсер изменения ≈ 1 рабочий день; риск ложных alert'ов — мерится по follow-up ретро.
### Candidate 3: Паттерн «post_compaction → user_directed_method 3×»
- **Тип:** наблюдение + кандидат на memory-фидбэк.
- **Evidence:** 3 из 3 эпизодов с `user_directed_method` — на turn 83, 84, 87 одной сессии **сразу после post_compaction=true** (компакция была между turn 82 и 83). Заказчик навязал: `subagent-driven-development` (×2) и `brainstorming` (×1).
- **Чтение:** post-compaction Claude теряет «свежее» направление из последних 5–10 ходов; заказчик это компенсирует, явно указывая метод. Это не bug, это компенсация известного quirk.
- **Suggested action:** **возможный** новый memory-факт типа `feedback_post_compaction_router_recovery.md` — «после `<context_compaction>`-маркера ход N+1 часто требует явной маршрутизации заказчиком; не сопротивляться»; либо расширить уже существующее `feedback_post_compaction_loss.md` (если есть). Решает заказчик.
- **Cost / risk:** 1 memory-файл / 10 строк; риск нулевой.
### Candidate 4: 5 v1-эпизодов 19.05 утра — archive или конверсия
- **Тип:** инфраструктурное предложение.
- **Evidence:** эпизоды 15 в JSONL (05:1806:57) — без `schema_version`. Анализатор тихо их пропускает (v1SkippedCount=5). Они **есть в файле**, но не участвуют в факторном анализе.
- **Чтение:** schema v2 раскатан с 08:06 (после первой утренней работы); до этого парсер записал v1-сырьё (path_type, outcome, primary_rationale, events — без `environment` и `decision_provenance`). Один из них (эпизод 5, 06:32) — большой (`Read:17, Grep:14, Glob:5` — это явно дебаггер где-то).
- **Suggested action:** **возможный** конвертер v1→v2 с заполнением неизвестных полей null (как делает анализатор) либо архивный suffix-файл (`episodes-2026-05.v1-archive.jsonl`). Сейчас оба варианта живы в одном файле без помех.
- **Cost / risk:** 0 если оставить; ~2 часа если делать конвертер.
### Candidate 5: `confusion_marker` / `parse_gap` / `unrecovered_error` — все по 0 за период
- **Тип:** валидационное замечание парсера.
- **Evidence:** в JSONL 17 v2-эпизодов; ни в одном нет `confusion_marker`, `parse_gap`, `unrecovered_error`, `interrupt`. Только `tool_summary` / `error` (3) / `retry` (2 ep'а) / `hook_fired` / `skill_invoked` / `Stop` / `time_burn`.
- **Чтение:** возможны 2 объяснения: **(a)** парсер ещё не реализовал распознавание этих 4 kind'ов (или реализовал, но их паттерны были слишком строгие за период); **(b)** период действительно был чистым.
- **Suggested action:** в углублённой фазе провести «red-team тест» парсера на синтетических примерах:
1. Намеренная неисправимая ошибка → должен записаться `unrecovered_error`;
2. Намеренная фраза «не уверен» / «может быть» в reasoning → должен записаться `confusion_marker`;
3. Намеренный пропуск (assistant сообщение с пустым tool_use_id) → должен записаться `parse_gap`;
4. Пользовательский `[ESC]` → должен записаться `interrupt`.
- **Cost / risk:** небольшое (5–10 синтетических transcript-фикстур); риск нулевой.
### Candidate 6: `candidates_considered` / `triggers_matched` / `boundaries_applied` всегда пусты
- **Тип:** наблюдение по полноте `primary_rationale`.
- **Evidence:** во всех 17 v2-эпизодах эти три массива — `[]`. Только `node_chosen` и `task_classification` заполнены.
- **Чтение:** парсер пишет только финальное решение, не аргументацию. Это известный gap (см. spec §6 «routing_decision события»).
- **Suggested action:** в углублённой фазе спроектировать парсер-расширение, которое:
1. Извлекает declared triggers из preamble assistant-сообщения (например, «триггеры: TDD на код / debug-задача»);
2. Извлекает candidates_considered из текстов вида «варианты: brainstorming / subagent-driven» или AskUserQuestion-options;
3. Извлекает boundaries_applied из явных упоминаний ADR/Pravila §N.
- **Cost / risk:** среднее; есть риск false-positives — закрывается тестами на zachrest-фикстурах.
### Candidate 7: NLP-фактор «task_classification=other 59%»
- **Тип:** наблюдение по классификатору.
- **Evidence:** 10 из 17 (59%) — `other`. `bugfix` 1, `refactor` 2, `feature` 3, `question` 2, остальные `other`.
- **Чтение:** классификатор парсера задаёт grobые корзины. «Other» становится свалкой для «короткие memory-edit'ы», «диалоговые ответы», «нормативные правки», «обновление эталона», «проверка состояния» и т.п.
- **Suggested action:** в углублённой фазе подумать о расширении classifier-словаря (например, добавить `docs-edit`, `memory-sync`, `state-check`, `regulatory-bump`). Это уменьшит «other»-bucket и сделает факторный анализ острее.
- **Cost / risk:** 1 рабочий день парсер-правок; риск ошибок классификации — мерится по retro.
---
## Informational metrics (NOT alerts)
- **Узлов реестра использовано как минимум 1 раз за период:** 4 (`superpowers:systematic-debugging`, `superpowers:test-driven-development`, `superpowers:verification-before-completion`, `claude-md-management:claude-md-improver`) из 60+ формализованных позиций (канон счётчиков — `docs/Tooling_v8_3.md` §0).
- **Узлов реестра не использовано ни разу:** ~56+ из 60+. **Это не проблема** per `feedback_brain_unused_tools_not_problem` (память). Capability-readiness — осознанная стратегия заказчика.
- **Hot-files** (по `task_size.files`, с дедупом): `CLAUDE.md` (3 раза), `project_brain_governance_design.md` (3), `reference_github.md` (3), `episodes-2026-05.jsonl` (2), `2026-05-19-observer-factor-analysis-design.md` (2), `tools/observer-transcript-parser.mjs` (2), tools/observer-transcript-parser.test.mjs (2). Остальные 30+ файлов — один раз.
- **Самый длинный эпизод:** №22 (20.05 07:52 → 08:12, 19m45s, 56 tool_calls, 11 файлов, TDD-починка наблюдателя, regulated path).
- **Самый короткий эпизод:** №2 (19.05 06:0706:08, 1m, 0 tool_calls — диалоговый ответ).
---
## Что **не** делалось в этом ретро (явные ограничения)
1. **Не сверял с `docs/routing-off-phase.md` L1L12 канонические связки** — пометил для углубления.
2. **Не читал v1-эпизоды для извлечения качественных уроков** — анализатор их пропускает по конструкции, но сами факты в них есть (особенно ep.5 — большой Read+Grep дебаг-эпизод 19.05 06:32).
3. **Не оценивал latency хуков** — данные есть (`hook_fired.counts`), но требуют отдельной агрегации с переводом в миллисекунды (нет timestamp'ов on per-hook basis в JSONL).
4. **Не запускал других контролёров C1–C5** для cross-check'а (l1-watcher / cross-ref-checker / observer-of-observer / coverage-checker) — это lefthook-jobs 1115, не /brain-retro responsibility.
5. **Не правил никакую нормативку** — read-only, как требует procedure step 7.
6. **Не делал predictions** — данных n=17 категорически мало для статистически значимых выводов; всё, что я выше отметил как «корреляция» — это либо артефакт выборки, либо описательное наблюдение.
---
## Подпись и провенанс ретро
Анализатор — `tools/brain-retro-analyzer.mjs` (commit `e6d6bab` в текущем checkout).
Шаблон — `.claude/skills/brain-retro/references/aggregation-template.md`.
Read-counter — обновлён до `last_read_at=2026-05-20T11:25:00+03:00`, `read_count_last_period=1`.
Эта нота — read-only вывод; никакая нормативная правка не выполнена.
---
# АДДЕНДУМ — углубления по 5 разделам (по запросу заказчика, экономия 5%)
> Закрытие 5 явных gaps, отмеченных в верхнеуровневом отчёте. Read-only,
> никаких правок нормативки. Данные собраны 2026-05-20 ≈11:35 MSK.
## A. L1L12/L13 классификация двух regulated-эпизодов
Источник истины — [docs/routing-off-phase.md](../../routing-off-phase.md) v1.2 (20.05.2026,
12 канонических + L13 finance-tooling).
**Эпизод №16 (2026-05-19T10:13Z, 19m, regulated, opus-4-7):**
- `skill_invoked`: `superpowers:systematic-debugging` + `claude-md-management:claude-md-improver`.
- Файлы: `tools/observer-transcript-parser.{mjs,test.mjs}` + `CLAUDE.md` + transcript-jsonl сессии.
- **Сверка с L1L13:**
- L8 (`systematic-debugging + Sentry + Redis`) — **частично**: `systematic-debugging`
был invoked, но Sentry/Redis MCP не использовались (баг был в локальном tool-коде,
не в production runtime).
- L12 (`claude-md-management + revise-claude-md`) — **частично**: `claude-md-improver`
invoked. Это один из двух entry-skill'ов L12. `/claude-md-management:revise-claude-md`
в этом эпизоде не вызывался (improver делал targeted update CLAUDE.md, не capture
session-learnings).
- **Вердикт:** эпизод **не маппится 1:1** на каноническую L-связку. Это **гибрид
Pravila §12.2 hard-floor** (systematic-debugging при unexpected behavior) **+ §5 п.10
обязательного канала** (правка CLAUDE.md только через claude-md-management). Оба
правила вне off-phase routing → L1L13 не покрывают такую цепочку **by design**
(off-phase routing не дублирует Pravila §12.2/§5 п.10 hard-rules — это явно в
routing-off-phase §11.5 «Hard-rules перевешивают routing-аид»).
**Эпизод №22 (2026-05-20T07:52Z, 19m45s, regulated, opus-4-7):**
- `skill_invoked`: `superpowers:test-driven-development` + `superpowers:verification-before-completion`.
- Файлы: 11 `tools/observer-*` + `.gitleaks.toml` (test-fixture seam с false-positive).
- **Сверка с L1L13:** **ни одна** L-связка off-phase routing не покрывает
«TDD + verification». L1L13 — это off-phase инструменты (#31#63), TDD/verify —
это Pravila §12.2 hard-floor (Superpowers). Тоже не дублируется в routing-аиде.
- **Вердикт:** **чистый Pravila §12.2 hard-floor двух skill'ов в одной задаче**
(«TDD на любой код» + «verification перед claim 'готово'»). L-маркировка
неприменима.
**Сводный вывод по A:** Из 17 v2-эпизодов **0 канонических L-связок** off-phase
сработало. Все 2 regulated-эпизода прошли через **Pravila §12.2 hard-floor** (skill
из обязательной карты §12.2), не через каноническую цепочку L1–L13. **Это здоровый
сигнал**: за период не было задач, попадающих в off-phase кейсы (ML-эпик, security-аудит,
финансы, BPMN, ADR — ни одного). Все turn'ы — самообслуживание наблюдателя и
правки нормативки/memory.
| chain | times | вердикт |
|---|---|---|
| L1 discovery→brainstorm→writing-plans→subagent-driven | 0 | за период не было фич, требующих full discovery loop |
| L2 discovery (SYSTEM) + audit-portal | 0 | не был audit-режим |
| L3 process-analysis ↔ process-modeling | 0 | не было процесс-задач |
| L4 mermaid ← adr-kit/process-modeling/operations | 0 | не было новых диаграмм |
| L5 adr-kit + architecture-patterns + deptrac | 0 | не было новых ADR |
| L6 security слой 4 узла | 0 | не было security-эпиков |
| L7 openapi-mcp + api-docs + Boost | 0 | не было API-интеграций |
| L8 systematic-debugging + Sentry + Redis | 0 (но partial в ep.16) | debug по локальному tool-коду; Sentry pending Б-1 |
| L9 CCPM + product-management + GitHub MCP | 0 | не было новых эпиков |
| L10 promptfoo + Data Scientist + claude-api | 0 | не было LLM-фич |
| L11 skill-creator + hookify + plugin-dev | 0 | не было новых скилов/хуков |
| L12 claude-md-management + revise-claude-md | 0 (partial в ep.16) | improver, не revise |
| L13 billing-audit + Pest + Boost + Sentry/Redis → ru-tax | 0 | finance-tooling узлы введены только 20.05 |
---
## B. Уроки из 5 v1-эпизодов 19.05 утра (05:1806:57)
Анализатор пропускает v1 (нет `schema_version=2`), но фактический контент в JSONL
есть. Сводка (по содержанию эпизодов 1–5 из episodes-2026-05.jsonl):
| ep | старт | длит. | path | outcome | task_class | tool-нагрузка | сигнал |
|---|---|---|---|---|---|---|---|
| 1 | 05:18:16 | **47m 39s** ⚠️ | improvised | success | refactor | AskUserQuestion×5, TodoWrite×2 | «refactor planning через множественные ветки выбора с заказчиком». Самый длинный эпизод всего периода. |
| 2 | 06:07:06 | 1m 15s | improvised | success | other | **0 tool_calls** | диалоговый турн (ответ без действий). |
| 3 | 06:10:13 | 5m 58s | improvised | success | other | Write×1, Bash×2, Edit×3, TodoWrite×1, **1 error** | мелкая правка с одной recovered ошибкой. |
| 4 | 06:20:40 | 2m 28s | improvised | success | other | Bash×2, Read×1, Edit×2 | чистая short-edit задача. |
| 5 | 06:32:15 | **24m 47s** ⚠️ | improvised | success | **bugfix** | Read×17, Grep×14, Glob×5, ToolSearch×1, TodoWrite×4, Write×1 | **debug-эпизод через интенсивную навигацию** без skill_invoked. |
**Качественные уроки:**
1. **Ep.1 (47m, refactor planning)** — массовое использование AskUserQuestion (5 раз
за один турн). Это classic-паттерн «дерево вариантов с заказчиком» **без
`superpowers:brainstorming` skill'а** — Pravila §16 (brain governance) с
обязательным routing-gate был ещё **не enforced** на этот момент (Phase A/B/C
закрыты commit'ами `2ef4ac4` / `4382de3` / `a70d5a4` именно 19.05). Поэтому
§12.2 ещё не флагировал «brainstorm-задачу без skill'а». **Не violation**
по правилам **на момент турна**.
2. **Ep.5 (24m, bugfix через 17 Read + 14 Grep + 5 Glob)** — **самый показательный
урок**: классический debug-эпизод (поиск по огромному файловому surface) прошёл
`direct`, без `superpowers:systematic-debugging`. Сейчас (после раскатки v2
schema + Stop-хук routing-gate с 08:06 того же дня) **такой эпизод флагировался
бы** как `decision_provenance.kind=autonomous` + `node_chosen=direct` при
bugfix-классификации → именно тот сигнал, который Phase B/C поднимала. В v1
мы видим **факт без сигнала**.
3. **Ep.2 (1m, 0 tool_calls)** — короткий диалоговый turn'а полезен как **baseline
minimum** для калибровки «что значит epizod с 0 tools»: это просто текстовый
ответ. Это согласуется с фактом, что парсер пишет эпизод **на каждый Stop**, не
только на «значимые» (важно для статистики покрытия).
4. **Все v1 outcome=success написаны парсером напрямую**, не inferred next-prompt
sentiment'ом (как делает анализатор для v2). Это значит: **v1 outcome
ненадёжен** для сравнения с v2 — там «success» это «не упал», а в v2
«success» это «next prompt был approval/new_task». Разные семантики.
5. **Историческая важность:** ep.1 + ep.5 показывают, что **brain governance был
реальным улучшением, а не косметикой** — два паттерна (5×AskUserQuestion без
brainstorm + bugfix без systematic-debugging) теперь **видимы** в v2-данных.
**Кандидат-наблюдение (НЕ действие):** не конвертировать v1→v2 ретроактивно — это
введёт ложные `decision_provenance=autonomous` метки в данные периода, когда
routing-gate ещё не работал. Лучше оставить v1 как `v1SkippedCount=5` для
**прозрачности bootstrap-эпохи**.
---
## C. Latency-замер: tool round-trip из транскрипта 553717ec
Источник: `~/.claude/projects/.../553717ec-bf55-43dc-8b9c-b9812711023a.jsonl`
(22 400 строк, 68.7 МБ; вся сессия 19.05 — 5h+, 24-25 турнов).
**Метод:** для каждого `tool_use_id` в `assistant.content` нашёл соответствующий
`tool_result` в следующих `user.content` блоках; разница `timestamp` = round-trip
latency (включает hook overhead + network + tool execution + Claude reply parsing).
Outliers ≥600 000ms отброшены. **N=2857 tool-roundtrip'ов** по 14 разным tools.
| tool | n | p50 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | max (ms) | чтение |
|---|---|---|---|---|---|---|---|
| Bash | 932 | 2491 | 13 489 | 17 251 | 48 746 | 114 335 | включает время самих команд (compose/pest/lefthook) |
| Read | 586 | **444** | 736 | 876 | 11 873 | 11 873 | IO-only, близко к hook floor |
| Edit | 554 | **1401** | 1516 | 1674 | 5173 | 48 927 | **+1s vs Read** — тяжёлый PostToolUse-pipeline |
| TodoWrite | 237 | **400** | 459 | 526 | 602 | 731 | in-memory list-state, **тоньше всех** — почти чистый hook floor |
| Write | 125 | 1400 | 1661 | 5401 | 31 531 | 31 531 | тот же PostToolUse-pipeline что Edit |
| Grep | 119 | 492 | 605 | 998 | 2914 | 2914 | ripgrep IO + parse |
| Agent | 107 | 135 883 | 213 723 | 263 840 | 387 336 | 387 336 | время субагентов, не hook overhead |
| AskUserQuestion | 69 | 50 064 | 323 325 | 534 449 | 534 449 | 534 449 | время реакции заказчика |
| Skill | 52 | 485 | 825 | 875 | 941 | 941 | скил-load |
| Glob | 32 | 12 690 | 20 265 | 20 265 | 20 265 | 20 265 | неожиданно медленно (большие patterns?) |
| ToolSearch | 27 | **394** | 457 | 457 | 518 | 518 | in-memory схема-резолв |
| PowerShell | 9 | 2365 | 2600 | 2600 | 2600 | 2600 | время команды PS |
| EnterPlanMode | 4 | 436 | 436 | 436 | 436 | 436 | вход в plan-mode |
| ExitPlanMode | 4 | 92 988 | 92 988 | 92 988 | 92 988 | 92 988 | время согласования плана заказчиком |
| **ALL** | **2857** | **1317** | 13 515 | 50 064 | 204 784 | 534 449 | overall |
**Выводы по latency:**
1. **Hook floor ≈ 400500ms** — это p50 для «легчайших» tools (TodoWrite 400, ToolSearch
394, EnterPlanMode 436, Read 444, Grep 492, Skill 485). Эти tools делают минимум
работы своих, поэтому 400–500ms — это network round-trip (~150ms) + hook chain
(~250350ms). Конкретнее: CLAUDE.md шапка v2.17 упоминала «B4 latency-замер хуков
закрыт (~34 мс median/хук)» (memory `feedback_superpowers_hard_rule.md`); при
~6–8 хуков на tool × 34ms ≈ 200ms — совпадает с порядком оценки.
2. **Edit/Write — самые «дорогие» легковесные tools (p50 ~1400ms)**, то есть +~1s
к hook floor TodoWrite (400ms). Этот **+1s** — это PostToolUse-цепочка для
write-операций: `markdownlint --fix` (если .md), `gitleaks protect --staged` (если
staged), плюс возможно l1-watcher (если settings.json/Tooling). Это **информативно**,
не алерт.
3. **Bash p50 = 2.5s, p99 = 49s** — нормально для команд с собственной работой
(composer/pest/lefthook). Hook overhead — лишь ~500ms из этого.
4. **Glob p50 = 12.7s****аномалия**: ripgrep-glob делается быстро (msec); 12s
на p50 указывает на тяжёлые patterns (`**/*` через большие subagent-каталоги
в `~/.claude/projects/`). См. кандидат C-1 ниже.
5. **AskUserQuestion / ExitPlanMode** — медианы 50s и 93s — это реальное время
реакции заказчика, не hook'и. Это **полезная метрика UX-задержки**: за период
~7% времени Claude тратится в ожидании ответа заказчика.
**Кандидат C-1 (НЕ действие):** Glob с p50=12.7s — посмотреть, не запускается ли
Glob на огромных subagent-логах `~/.claude/projects/.../subagents/*/*.jsonl`
(их там 100+). При следующем углублении — отфильтровать patterns по
содержанию и проверить.
**Кандидат C-2 (НЕ действие):** Edit p99 = 48s, max 48.9s — один эпизод где
PostToolUse-цепочка зависла (вероятно gitleaks при огромном diff). Точечная
investigation потенциально полезна, но **n=1**, можем ждать повторения.
---
## D. N=17 — план «когда меряем фактор-анализ серьёзно»
**Текущий лимит:** 17 v2-эпизодов недостаточно для статистически значимых
заключений. «Корреляции» в верхнеуровневом отчёте (`post_compaction` / `session_turn` /
`parallel_session`) — артефакты выборки.
**Минимально достаточно для serious factor-analysis:**
| фактор | минимальная выборка для p<0.05 (одна ячейка матрицы) | оценка времени накопления |
|---|---|---|
| `decision_provenance` (3 значения × 4 outcome) | n≥120 эпизодов (≥10 в каждой ячейке) | при текущем темпе ~17 эп/неделя — **~7 недель** |
| `economy_level` (4 × 4) | n≥160 | **~9 недель** |
| `model` (нужно ≥2 модели, сейчас только Opus) | требует Sonnet/Haiku-эпизодов | возникнет с subagent-driven-development в `claude_would_have_chosen` |
| `post_compaction` (2 × 4) | n≥80 | **~5 недель** |
| `parallel_session` (2 × 4) | n≥80 | **~5 недель** |
| `node_chosen` (тонкий хвост) | n≥300 | **~17 недель** |
**Практическая рекомендация:** следующее brain-retro имеет смысл проводить
**не раньше чем через 4–6 недель** (или ~80–100 v2-эпизодов). До этого
аккумулировать данные. Если заказчик хочет интервалы — еженедельные «лёгкие»
ретро (только Observer health + path-type + L-chains) без полной матрицы.
**Текущее накопление 1× /brain-retro — это baseline-замер, не factor-analysis.**
---
## E. Cross-check контролёров C1–C5 — все GREEN
Запущено вручную параллельно (lefthook jobs 1115 + post-commit job 14):
| контролёр | команда | exit | результат |
|---|---|---|---|
| **C1 l1-watcher** (job 11) | `node tools/l1-watcher.mjs` | 0 | `OK — 0 drift` (settings.json ↔ Tooling Прил. Н sync) |
| **C2 cross-ref-checker** (job 12) | `node tools/cross-ref-checker.mjs` | 0 | `OK — 0 drift in 4 files` (Pravila / PSR_v1 / Tooling / CLAUDE.md / MEMORY.md cross-refs) |
| **C3 observer-of-observer** (job 13) | `node tools/observer-of-observer.mjs check` | 0 | `OK — last read 0 week(s) ago` (я только что обновил read-counter; 54-week pre-prune защита не сработает) |
| **C4 status-md** (post-commit) | `node tools/status-md-generator.mjs` | 0 | пересоздал `docs/observer/STATUS.md` (новый timestamp `2026-05-20T08:31:22.723Z`) |
| **C5 observer-coverage** (job 15) | `node tools/observer-coverage-checker.mjs` | 0 | `OK — 24 episode(s) this month · Stop-hook + post-commit OK` |
**Расхождение с anal'изером:** C5 сейчас видит **24 эпизода**, мой анализатор
(запущенный в начале ретро) обработал **22**. Дельта = 2 — это эпизоды, которые
Stop-хук записал ЗА ВРЕМЯ выполнения брейн-ретро (моя текущая сессия 98298ec2,
turns 1–2 после начала ретро). **Не дрейф**: это нормально (наблюдатель пишет
в реальном времени).
STATUS.md обновлён и **показывает все 5 контролёров ✅**. Если бы один из них
flag'нул — после следующего git commit (post-commit hook job 14) STATUS.md
auto-обновился бы; обходного канала не нужно.
**Кандидат E-1 (НЕ действие):** rerun `node tools/brain-retro-analyzer.mjs
docs/observer/episodes-2026-05.jsonl` через несколько турнов / в конце сессии —
факторная матрица станет на 1–2 эпизода полнее. Имеет смысл встроить в
сам /brain-retro skill «final pass перед save» — кандидат для **углублённой
итерации скила**.
---
## Итог по углублениям
| # | Раздел | Status | Главное наблюдение |
|---|---|---|---|
| A | L1L12/L13 hit rate | ✅ verified | 0 канонических связок отработало; оба regulated-эпизода — чисто Pravila §12.2 hard-floor |
| B | v1-эпизоды (5 шт.) | ✅ verified | brain governance enforcement добавляет видимость — два showcased паттерна (ep.1 без brainstorm, ep.5 без systematic-debugging) теперь были бы флагированы |
| C | latency-замер | ✅ verified | hook floor ≈ 400500ms; Edit/Write +~1s; Bash p50=2.5s; Glob аномально 12.7s (потенциальная investigation) |
| D | N=17 → план серьёзного анализа | ✅ verified | следующее полное ретро через ~5–7 недель (80–100 v2-эпизодов) |
| E | C1C5 cross-check | ✅ verified | все 5 контролёров GREEN, STATUS.md обновлён, расхождение «22 vs 24» — Stop-хук пишет в реальном времени |
**Файлы, тронутые в этом углублении:** только `docs/observer/notes/2026-05-20-brain-retro.md`
(этот же файл, аддендум) + `docs/observer/.read-counter.json` (на step 4 базовой части) +
`docs/observer/STATUS.md` (через C4 status-md generator). Никаких изменений в нормативке,
коде, тестах.
---
# АДДЕНДУМ B — пре-имплементационные верификации (по запросу заказчика, экономия 5%)
> Закрытие 3 actionable «не верифицировал» из ответа-обзора 19 пунктов «что улучшить
> в наблюдателе». Read-only verification над реальными артефактами;
> никакого кода не правил; никакая нормативка не тронута.
## B1. `message.usage` в Claude Code transcript — присутствует, формат подтверждён
**Метод:** `grep -c '"usage"'` + sample первых 3 occurrences по реальному transcript
`~/.claude/projects/.../553717ec-bf55-43dc-8b9c-b9812711023a.jsonl` (сессия 19.05, 22 400 строк, 68.7 МБ).
**Результат:**
- **6 372 occurrences** в одной сессии.
- **Формат фиксированный** (наблюдался идентичный во всех 3 samples):
```json
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 107333,
"cache_read_input_tokens": 32087,
"output_tokens": 4518,
"server_tool_use": {
"web_search_requests": 0,
"web_fetch_requests": 0
}
}
```
**Импликации для рекомендации #2 «token-usage capture»:**
- Все **4 поля**, которые я предлагал захватывать (`input_tokens` / `output_tokens` /
`cache_read_input_tokens` / `cache_creation_input_tokens`) — **подтверждены присутствующими**.
Реализация безопасна.
- **Bonus-фактор обнаружен:** `server_tool_use.web_search_requests` +
`server_tool_use.web_fetch_requests` — счётчики использования server-side
инструментов (WebSearch / WebFetch). Можно добавить в `task_cost` как доп.
фактор «использовал ли web-инструменты».
**Уточнённая спецификация #2:**
```js
export function extractTokenUsage(turn) {
let input=0, output=0, cache_read=0, cache_creation=0, web_search=0, web_fetch=0;
for (const e of turn) {
const u = e?.message?.usage;
if (!u) continue;
input += u.input_tokens || 0;
output += u.output_tokens || 0;
cache_read += u.cache_read_input_tokens || 0;
cache_creation += u.cache_creation_input_tokens || 0;
web_search += u?.server_tool_use?.web_search_requests || 0;
web_fetch += u?.server_tool_use?.web_fetch_requests || 0;
}
return {
input_tokens: input,
output_tokens: output,
cache_read_input_tokens: cache_read,
cache_creation_input_tokens: cache_creation,
web_search_requests: web_search,
web_fetch_requests: web_fetch,
};
}
```
## B2. `attachment.type` структура — `hook_success` подтверждён, `hook_error` 0 occur
**Метод:** `grep` по `"attachment":{...}` + по полям `type` / `hookName`.
**Результат:**
- **11 685 occurrences** `"attachment"` в той же сессии.
- **Виды type, встреченные:** `hook_success` ✅ + `hook_additional_context` + `deferred_tools_delta` + `queue-operation`.
- **`hook_error` = 0 occurrences** во всей сессии 5h+.
- Структура `hook_success`:
```json
"attachment": {
"type": "hook_success",
"hookName": "SessionStart:startup",
"toolUseID": "12a033f0-...",
"hookEvent": "SessionStart",
"content": "",
"stdout": "{ ... }"
}
```
**Импликации для парсера ([parser.mjs:295-300](../../tools/observer-transcript-parser.mjs#L295-L300)):**
- Код парсера **корректен** — он матчит на `attachment.type === 'hook_success' || 'hook_error'`.
`hook_success` приходит; `hook_error` имеет 0 occurrences, потому что хуки
**физически не падали** за период. Парсер пишет `errors: 0` в `hook_fired` event —
это **здоровый сигнал**, не bug.
- **Уточнение к будущей правке per-hook timing:** в `attachment.hook_success` нет
полей `started_at`/`ended_at` — per-hook timing **не извлекаем** из transcript
напрямую. Требуется инструментация самих хуков (записывать timestamp в `stdout`
или внешний log). Это **vendoring или fork** хука, нетривиально. Рекомендация:
оставить per-hook timing **out-of-scope** для текущего раунда; ограничиться
tool-round-trip latency (как делал в C-аддендуме первой части ноты через timestamp
diff между `tool_use` и `tool_result`).
- **Дополнительные attachment-типы** (`hook_additional_context` / `deferred_tools_delta` /
`queue-operation`) — НЕ обрабатываются парсером, и это OK: они информационные,
не connect'ятся к hook execution.
**Дополнительная находка:** `attachment.toolUseID` присутствует в `hook_success` —
связывает hook с конкретным tool_use. Если эта связь сохранится и в `hook_error`
(когда таковой появится), можно атрибутировать ошибку хука к конкретному tool, не
только к hook-name. Это **future capability**, не текущая правка.
## B3. tools/observer-*.test.mjs — 232/232 GREEN
**Метод:** `cd app && npx vitest run --config vitest.config.tools.mjs --reporter=verbose`.
Раннер найден в [app/vitest.config.tools.mjs](../../app/vitest.config.tools.mjs):
```js
include: ['../tools/*.test.mjs'],
exclude: ['../tools/ruflo-*.test.mjs', '../tools/subagent-prompt-prefix.test.mjs'],
```
**Результат:**
```
Test Files 13 passed (13)
Tests 232 passed (232)
Start at 11:49:45
Duration 1.21s (transform 1.11s, setup 0ms, import 1.82s, tests 350ms, environment 4ms)
```
**13 тестовых файлов:**
| Файл | Покрывает |
|---|---|
| `brain-dashboard-core.test.mjs` | Brain Dashboard UI logic (19 тестов) |
| `brain-retro-analyzer.test.mjs` | Анализатор этого скила (15) |
| `cross-ref-checker.test.mjs` | C2 контролёр (14) |
| `l1-watcher.test.mjs` | C1 контролёр (12) |
| `observer-coverage-checker.test.mjs` | C5 контролёр (8) |
| `observer-of-observer.test.mjs` | C3 контролёр (5) |
| `observer-pii-filter.test.mjs` | PII фильтр (15) |
| `observer-routing-detector.test.mjs` | Routing-gate детектор (8) |
| `observer-stop-hook.test.mjs` | Stop-hook + appendEpisode + routingGateDecision (18) |
| `observer-transcript-parser.test.mjs` | Парсер транскрипта — главный (53) |
| `status-md-generator.test.mjs` | C4 генератор (5) |
| + 2 ещё через include glob | ~60 (subtotal до 232) |
**Импликации:**
- Текущая observer-инфраструктура **стабильна**: 232 теста за 1.21 сек, 0 регрессий.
- Любая правка из рекомендаций #1–#19 имеет **прочную регрессионную сеть** —
добавлять тесты на расширения, не трогая существующие.
- Раннер закрепляется как канонический способ запуска tools-тестов:
`cd app && npx vitest run --config vitest.config.tools.mjs`.
**Кандидат B3-1 (новая микро-правка):** добавить в корневой [package.json](../../package.json) одну строку:
```json
"test:tools": "cd app && npx vitest run --config vitest.config.tools.mjs"
```
Это **отдельная** микро-правка, не часть рекомендаций #1–#19; стоимость 1 минута;
ROI — улучшение DX (тесты `tools/*` найти теперь сложно без чтения source-кода
конфига).
---
## Сводка по верификациям
| # | Что проверял | Результат | Влияние на рекомендации |
|---|---|---|---|
| B1 | `message.usage` структура | ✅ 6372 occur, формат подтверждён + bonus `server_tool_use` фактор | #2 token-usage capture — реализуема, спека уточнена с web_search/web_fetch |
| B2 | `attachment.type='hook_success'`/`hook_error` | ✅ hook_success есть; hook_error 0 occur (хуки не падают) | парсер корректен; per-hook timing требует инструментации хуков (out of scope) |
| B3 | `tools/observer-*.test.mjs` запуск | ✅ 232/232 GREEN за 1.21s | прочная регрессионная сеть; новые правки безопасны |
**0 регрессий, 0 правок кода/нормативки.** Готов реализовать рекомендации #1#19
(любые) — заказчик решает, какие применить и в каком порядке.