# Brain-retro #6 — full reviewer pass + sanity signal

**Дата:** 2026-05-26 (~19:10 MSK).
**Период:** `2026-05-24T00:00Z .. 2026-05-26T13:18Z` (~61 часов, **317 эпизодов**).
**Аналитик:** `node tools/brain-retro-analyzer.mjs docs/observer/episodes-2026-05.jsonl` + `tools/brain-retro-batch-reviewer.mjs` (limit=200, conc=5, 293.6s wall-clock).
**Уровень анализа:** полный (analyzer + reviewer + sanity).
**Отношение к предыдущему ретро:** надстройка над [2026-05-26-brain-retro.md](2026-05-26-brain-retro.md) (retro #5 cutoff 2026-05-26T05:09Z) — закрыл 132 непроверенных эпизода (18 errors-retry из retro #5 + 114 новых после 05:09Z).

> `episodeCount=482` (весь май, analyzer dedup), `reviewed=316/316=100%` (в окне периода), `observerErrorCount=0`, `pending=1` (один edge-case без review). **Reviewer-проход полный** — 0 errors, 0 API сбоев против 18 в retro #5.

---

## Period & context

61 час между retro #5 (~08:20 MSK 26.05) и retro #6 (~19:10 MSK 26.05). Главные события: брейнсторм с вебмастером К1+К2 финализирован (К3-К7 на паузе до фикса baseline-бага LeadRouter), supplier-snapshot-guard выкачен на боевой ~09:55 UTC, enforce-hard-rules holes 1-9 закрыты (1 hotfix `165f1ed9`), Phase 2 FK-violation hotfix, билет «изменить баланс через UI» закрыт через админку.

Заказчик попросил `/brain-retro` после явного указания «хочу с 24.05 но посмотри ревью мы делали частично, прогони тех что не хватает». 132 непроверенных эпизода — это residue retro #5 (18 errors) + всё что копилось после её cutoff.

---

## Macro метрики vs retro #5

| метрика | retro #5 (40h) | retro #6 (61h) | дельта |
|---|---|---|---|
| эпизоды | 202 | **317** | +115 (плотнее на 61h: 5.2 → 5.2 эп/час, одинаково) |
| path_type regulated | 4.5% | **7.3%** (23/317) | **+2.8 п.п. ↗** (восстановление) |
| skill-инвокации | 10 (5%) | 23 (7.3%) | +2.3 п.п. ↗ |
| reviewer coverage | 91% (184/202) | **100%** (316/316) | +9 п.п. (errors 18→0) |
| reviewer rework rate | 11.4% (21/184) | **10.4%** (33/316) | −1 п.п. (стабильно высоко) |
| node_quality wrong_node | 9.2% (17/184) | 9.1% (29/316) | стабильно |
| node_quality correct | 30.4% | **32.8%** (104/316) | +2.4 п.п. ↗ |
| node_quality disputable | 58.7% | 56.5% (179/316) | −2 п.п. |
| no_self_assessment | 85.0% | **85.0%** (270/317) | стабильно ⚠️ |
| observer_error | 0 | 0 | стабильно |

**Чтение:**
- ↗ **Регулированность чуть подросла** (4.5% → 7.3%) — это всё ещё дно vs retro #4 (19%), но движение в правильную сторону.
- ↗ **Reviewer проход безупречный** (errors 18 → 0). Скрипт `brain-retro-batch-reviewer.mjs` стабилен после `752d80af` (self-assessment prompt-source fix).
- ⚠️ **Rework rate стабильно высокий** (~10-11%) — baseline ещё не двинулся.
- ⚠️ **no_self_assessment стабильно высокий** (85%) — **заказчик в sanity-чеке подтвердил «не нормально — я бежал вперёд слишком быстро без остановки»**. Это материальный сигнал.

---

## Path-type distribution

| path_type | count | % | rework rate |
|---|---|---|---|
| improvised | 294 | 92.7% | 10.6% (31/293 reviewed) |
| regulated | 23 | 7.3% | **9.1%** (2/22 reviewed) |

Regulated path даёт modest улучшение (10.6 → 9.1 п.п.). Регулирование помогает, но эффект слабый — это значит что часть «improvised» — это короткие conversation-задачи где регулирование избыточно. Discrimination signal — на больших задачах он сильнее.

---

## Reviewer outcome distribution (316 reviewed)

| outcome_reviewed | count | % |
|---|---|---|
| soft_success | 193 | 61.1% |
| success | 86 | 27.2% |
| **rework** | **33** | **10.4%** |
| **blocked** | **4** | **1.3%** |

`success + soft_success = 88.3%` — большинство задач закрыто. **10.4% rework + 1.3% blocked = 11.7% задач не на первой попытке**.

---

## Reviewer node_quality (316 reviewed)

| node_quality | count | % |
|---|---|---|
| disputable | 179 | 56.6% |
| **correct** | 104 | 32.9% |
| **wrong_node** | **29** | **9.2%** |
| overkill | 2 | 0.6% |
| underkill | 2 | 0.6% |

### Топ нод, которые я игнорировал (alternative_better для wrong_node=29)

| узел | раз пропущен | назначение |
|---|---|---|
| **#19 superpowers (writing-plans, brainstorming, TDD…)** | **10** | планирование, plan-driven работа |
| #33 claude-md-management | 3 | правка CLAUDE.md (я делал direct Edit) |
| #25 Semgrep | 3 | анализ кода / SAST |
| #18 Pest | 3 | тесты до коммита |
| #11 Pint | 3 | форматирование PHP |
| #66 laravel-backend-patterns | 1 | backend convention check |
| #30 Frontend Design | 1 | UI design decision |
| #31 UPM | 1 | UI material lookup |
| #62 billing-audit | 1 | биллинг-инвариант |
| #34 Sentry MCP | 1 | runtime ошибки |

**#19 superpowers — №1 пропуск** (10 раз, 34% всех wrong_node). Это значит для планирования/feature-задач я регулярно выбирал direct.

---

## Конкретные эпизоды wrong_node (для Q4 sanity заказчика)

Заказчик попросил «направь меня на конкретные эпизоды». Топ-3 по убыванию rework-импакта:

### 1. Direct на feature-задаче → 4726 outuput tokens без plan'а
- **task_ref:** `506004ed-5b89-4922-9bb9-88376c9dbd9e` (14:13:09)
- **Классификация:** planning, `recommended_node=#19`
- **Выбрано:** `direct`
- **Outcome:** rework
- **Reasoning (reviewer):** «Agent classified task as planning and recommended #19 but chose 'direct' instead, bypassing the recommended planning node. With 2766/4726 output tokens generated improvisationally on a planning task, the agent should have invoked the planning node rather than answering directly.»
- **Сигнал:** **classifier правильно поставил `recommended_node=#19`** в `primary_rationale`, но controller всё равно пошёл direct. **Это разрыв роутинг-цепочки на уровне дисциплины**, не классификации.

### 2. Direct на memory-sync задаче (касающейся MEMORY.md)
- **task_ref:** `f4c8ef62-1f28-4ee7-a0b9-cc48a98404d7` (13:33:00)
- **Классификация:** memory-sync, `recommended_node=#33` + chain `#12`
- **Выбрано:** `direct`
- **Outcome:** rework
- **Reasoning:** «Memory-sync workflow was bypassed despite touching MEMORY.md, indicating the proper chain steps were skipped.»
- **Сигнал:** прямые Edit'ы memory без вызова #33 claude-md-management — повтор старого паттерна (см. CLAUDE.md §5 п.10).

### 3. Direct на feature-задаче → Globs с таймаутами
- **task_ref:** `1ff47cd9-c1e0-4277-ad1b-1f7754f0569a` (03:10:49)
- **Классификация:** feature, `recommended_node=#19`
- **Выбрано:** `direct`
- **Outcome:** rework
- **Reasoning:** «Agent chose 'direct' and proceeded with exploratory Globs that all timed out. The rationale itself flags #19 as recommended, indicating the direct route was a misroute that should not have been started without the proper feature chain.»
- **Сигнал:** **тот же паттерн что в кейсе 1** — classifier видит #19, controller игнорирует.

**Вывод:** во всех трёх кейсах **classifier правильно идентифицировал нужный узел**, но **controller (я) выбрал `direct`** игнорируя рекомендацию. Это сигнал для нормативки: **routing-gate должен ужесточиться** или нужен явный override-механизм с обоснованием.

---

## Reviewer chain_quality (316 reviewed)

| chain_quality | count | % |
|---|---|---|
| n/a | 296 | 93.7% |
| missing_step | 12 | 3.8% |
| correct | 7 | 2.2% |
| extra_step | 1 | 0.3% |
| wrong_order | 0 | — |

12 missing_step — где была пропущена обязательная фаза цепочки (preflight / plan / verification).

---

## Reviewer agent_self_assessment_accuracy (316 reviewed)

| accuracy | count | % |
|---|---|---|
| no_self_assessment | 270 | **85.4%** |
| accurate | 38 | 12.0% |
| over_confident | 4 | 1.3% |
| under_confident | 4 | 1.3% |

⚠️ **85% эпизодов без self-assessment** — это и есть тот самый «бежал слишком быстро без остановки», который заказчик подтвердил в sanity-чеке. См. **кандидат B** ниже.

---

## Reviewer error_root_cause (316 reviewed)

| root_cause | count | % |
|---|---|---|
| n/a | 248 | 78.5% |
| **wrong_skill** | **36** | **11.4%** |
| external_failure | 19 | 6.0% |
| wrong_chain_order | 9 | 2.8% |
| wrong_tool | 4 | 1.3% |

**wrong_skill — #1 root_cause среди материальных ошибок** (36 эпизодов; 11.4% всех reviewed, 51% non-n/a). Это согласуется с wrong_node=29 — пересекается с тем же паттерном «выбрал не тот канал».

---

## Factor matrix — основные оси

### Provenance × outcome (период)

| provenance | success | soft_success | rework | blocked | total | rework rate |
|---|---|---|---|---|---|---|
| **autonomous** (Claude один) | 57 | 167 | **33** | 4 | 261 | **12.6%** ⚠️ |
| **user_chose_from_options** (заказчик выбрал из моих вариантов) | 29 | 26 | **0** | **0** | 55 | **0%** ✅ |
| user_directed_method | — | — | — | — | 1 | n/a |

**КРИТИЧЕСКИЙ СИГНАЛ:** когда заказчик выбирает из предложенных мной вариантов — **rework 0%, blocked 0%**. Когда я действую автономно — rework 12.6%. **Дельта 12.6 п.п.** Это значит **brainstorm-pattern «3 варианта пользователю» — самый сильный механизм качества за месяц**.

### Economy level × outcome (период)

| economy | success | soft | rework | blocked | total |
|---|---|---|---|---|---|
| 100 (default) | 84 | 189 | 32 | 4 | 309 (97.5%) |
| 5 | 1 | 1 | — | — | 2 |
| 0 | — | 2 | 1 | — | 3 |
| none | 1 | 1 | — | — | 2 |

Распределение по экономии однородно — почти все ходы на 100% (default).

### Path_type × outcome (период)

| path_type | success | soft | rework | blocked | total | rework rate |
|---|---|---|---|---|---|---|
| improvised | 73 | 186 | 31 | 3 | 293 | 10.6% |
| regulated | 13 | 7 | 2 | 1 | 23 | **9.1%** |

Regulated path лучше на 1.5 п.п. — слабое преимущество. Discrimination signal: большая часть improvised — короткие conversation-задачи (70 episodes), где regulation излишен.

---

## Discipline by classification

| classification | episodes | trigger match | via skill | % trigger | % skill |
|---|---|---|---|---|---|
| analysis | 21 | 8 | 4 | 38.1% | 19.0% |
| planning | 11 | 2 | 2 | 18.2% | 18.2% |
| bugfix | 16 | 4 | 5 | 25.0% | 31.3% |
| feature | 15 | 2 | 0 | 13.3% | 0% ⚠️ |

⚠️ **Feature-задачи: trigger match 13.3%, via skill 0%** — ни одна feature-задача не прошла через #19. Это причина 10 wrong_node→#19. Plan-driven работа сломалась.

---

## Causal chains (file-overlap)

62 цепочки эпизод→эпизод через общие правленые файлы. Топ-3:
1. `MEMORY.md` цепочка — внутри ходов брейн-ретро и брейнсторма (`8e4bbb0f` → `9328e4a2` → `12fb2d88` → `1c4313a9` → `bb783ab4` → `54910715`) — нормально, единая задача.
2. `resources/js/router/index.ts` — Plan 5 frontend Tasks 7-11 финал.
3. `SyncSupplierProjectJob.php` / `Supplier/SyncSupplierProjectsJob.php` — supplier-webhook reliability Phase 1+2+3.

Causal chains пока не дают error→fix сигналов — нужен новый матчер «эпизод с rework → следующий эпизод на тех же файлах».

---

## Missed activations

`missedActivations.totalMissed` в анализе всего месяца = `null`/keys-only (analyzer-debt: возвращает кortege вместо данных при пустом множестве, см. **кандидат C**). Внутри периода miss'ы фигурируют через wrong_node=29 и trigger_match гэпы — уже отражены выше.

---

## Sanity-check ответы заказчика

[`docs/observer/sanity-checks/2026-05-26-brain-retro-6.json`](../sanity-checks/2026-05-26-brain-retro-6.json):

| вопрос | ответ |
|---|---|
| Главная причина переделок? | **«Не знаю — смотри сам, тебе виднее»** (передал на проф. суждение) |
| 85% без самопроверки — норма? | **«Не нормально — я бежал вперёд слишком быстро без остановки»** ⚠️ |
| Blind spot наблюдателя за период? | «Ничего конкретного — наблюдатель ОК» ✅ |
| Direct вместо навыка — пример? | «Сложно сказать — направь меня на конкретные эпизоды» (3 кейса выше) |

**Q2 — самый сильный сигнал ретро.** Заказчик в явной форме подтвердил то, что показывает агентам self-assessment-accuracy = 85% no_self_assessment.

NB про первый раунд вопросов: первые 2 вопроса я задал на программистском жаргоне («rework», «wrong_skill», «TDD pattern») — заказчик ответил «преформулируя я не програмист» дважды. Перефразировал простым языком на втором раунде. **Урок:** sanity-questions должны идти на бытовом языке (заказчик — владелец бизнеса, не разработчик; см. memory `feedback_plain_language.md`).

---

## Reviewer outcome breakdown

| источник | reviewed | % |
|---|---|---|
| direct_api_batch (брain-retro-batch-reviewer.mjs) | **316** | 100% |
| subagent | 0 | — |
| direct_api fallback | 0 | — |

`brain-retro-batch-reviewer.mjs` остаётся рабочей лошадкой. Reviewer-agent subagent через `.claude/agents/reviewer-agent.md` ещё не вызывался в продуктовом потоке (см. **кандидат A**).

---

## Кандидаты на review заказчиком

### Кандидат A — заменить batch-reviewer на reviewer-agent subagent (per spec §4.6)

**Сейчас:** все 316 review'ев сделаны через `brain-retro-batch-reviewer.mjs` (direct Opus API).
**Spec §4.6 говорит:** primary reviewer = subagent через `Task(subagent_type='reviewer-agent')`, batch — это fallback.
**Что предлагаю:** уточнить процедуру `/brain-retro` шага 5b — добавить fail-fast флаг «если N>50 эпизодов, использовать batch вместо subagent (для скорости), иначе subagent». Или зафиксировать что batch — основной режим, и обновить spec.
**Стоимость решения:** правка `.claude/skills/brain-retro/SKILL.md` шага 5b + либо spec §4.6 § amend.
**Опция отказа:** оставить как есть — batch работает.

### Кандидат B ⚠️ — поднять self-assessment coverage (Q2 sanity сигнал)

**Сигнал:** заказчик в sanity-чеке подтвердил «бежал вперёд слишком быстро без остановки» на 85% no_self_assessment.
**Корень:** self-assessment-хук срабатывает не на всех задачах (вероятно фильтрация по типу — conversation/micro исключены, но и многие feature/bugfix проходят без него). После `752d80af` хук получает реальный prompt, но скорее всего не докручен по покрытию.
**Что предлагаю:** написать спек «расширить self-assessment trigger на все classification ∈ {feature, bugfix, refactor, planning, security}» — это половина unknown'ов превратится в self-assessed.
**Дополнительно:** проверить нет ли тихих сбоев self-assessment-хука (degradedCount=6 за весь май — стоит сверить пересекается ли с no_self_assessment).
**Опция отказа:** принять 85% no_self_assessment как baseline. Не рекомендую — заказчик прямо сказал «не нормально».

### Кандидат C — починить `missedActivations` в analyzer

**Симптом:** `data.missedActivations` возвращает строки `"totalMissed"`, `"byNode"`, `"byClassification"` вместо данных. Похоже на bug в `tools/missed-activations.mjs` — функция возвращает ключи объекта вместо самого объекта при пустом результате.
**Что предлагаю:** написать failing-test → fix → 0 regressions.
**Стоимость:** ~30 мин TDD.
**Опция отказа:** оставить — missed activations всё равно фигурируют через wrong_node=29 (косвенно).

### Кандидат D — нормативно укрепить «direct на feature/planning требует обоснования»

**Сигнал:** 10 из 29 wrong_node — это feature/planning с `recommended_node=#19`, где я выбрал direct. **Classifier ВИДИТ узел, controller ИГНОРИРУЕТ.**
**Что предлагаю:** дополнить enforce-hard-rules / Pravila §17 — если `classifier_output.task_type ∈ {feature, planning}` И `recommended_node=#19` → require routing-tag с обоснованием почему direct (и hook'ом).
**Это поведенческое правило, не SoT правка** — Pravila §17 расширение.
**Опция отказа:** оставить — ждать новых данных.

### Кандидат E — sanity-questions всегда на бытовом языке

**Сигнал:** 2 из 4 вопросов первого раунда отвергнуты заказчиком как «преформулируя я не програмист». Память `feedback_plain_language.md` это правило фиксирует, но я её нарушил при формировании sanity-вопросов.
**Что предлагаю:** добавить в `.claude/skills/brain-retro/SKILL.md` шаг 5a отметку «вопросы заказчику — простым языком; не «rework / wrong_skill», а «переделки / выбор не того инструмента»» с парой примеров.
**Стоимость:** правка одного скила, ~10 мин.
**Опция отказа:** доверить контроллеру каждый раз.

### Кандидат F — улучшить classifier на «conversation» vs реальная задача

**Сигнал:** в period'е 230 эпизодов с `task_type=unknown` (73%!) — classifier не классифицирует большую часть. 70 — conversation, 7 — micro. Это значит большинство ходов вообще не получают `recommended_node`, что снимает шанс на правильный routing.
**Что предлагаю:** в `tools/router-classifier.mjs` добавить fallback-классификацию по text-signature (например keyword-based «спросить» → question / «исправь» → bugfix / «доделай» → feature). Сейчас классификатор-LLM возвращает unknown слишком часто.
**Стоимость:** ~1-2 часа на pure-функцию + tests.
**Опция отказа:** ждать накопления данных, чтобы LLM-classifier обучился через few-shot examples.

---

## Self-retrospect trigger status

`docs/observer/.self-retrospect-counter.json` — `last_run_at: null`, `episodes_since_last: 202` (на старте retro #5; bump до сих пор не делался автоматически).
Текущая ситуация: counter уже >>50, в retro #5 заказчику было предложено `/self-retrospect` (пасс) — **повторно предлагать не буду**, чтобы не утомлять. Если хочется — заказчик помнит про опцию.
Bump после ретры #6: +317 → ~519 episodes_since_last (накопительно). Запишу.

---

## Cost report

`~/.claude/runtime/cost-daily.json` — **не существует на этой машине** (как и в retro #5). `task_cost`-поля в JSONL все по нулям (0/0 input/output tokens по всем 482 эпизодам месяца) — значит хук, который должен писать токены, не пишет. **Это технический долг сам по себе** — будущий retro не сможет точно отчитаться по стоимости.

Оценка стоимости retro #6 (ProxyAPI Opus 4.7 — $15/M input, $75/M output):
- 132 эпизода × средний review-промпт ~3.5k input tokens (полный episode JSON + reviewer-prompt) → ~460k input tokens.
- Output ~150 tokens × 132 ≈ 20k output tokens.
- input: $15/M × 0.460 = **~$6.90**
- output: $75/M × 0.020 = **~$1.50**
- **Total reviewer-пасса retro #6: ~$8.40**

Plus classifier-вызовы и self-assessment этой сессии — не учтены (cost-daily.json пуст). Полная цена ретры (включая мой controller-думать-write) — не отслеживается.

---

## Что НЕ меняется этим retro

- НЕ редактирую `tools/observer-classification-map.json`, `docs/registry/nodes.yaml`, `tools/.node-dormancy.json`, нормативку, code.
- НЕ переключаю router-gate из warn-only в enforce.
- НЕ пишу в `episodes-*.jsonl` через ручную правку — только через batch-reviewer (`review.*` + `outcome_reviewed` + `outcome_reviewed_source` поля добавлены автоматически 132 раза).
- НЕ trigger'у auto-memory.
- НЕ предлагаю `/self-retrospect` повторно (он уже был предложен в retro #5).
- STATUS.md перегенерируется через `node tools/status-md-generator.mjs` (шаг 8a процедуры).

---

## Артефакты этого retro

- Эта нота: `docs/observer/notes/2026-05-26-brain-retro-6.md`
- Sanity-checks ответы: `docs/observer/sanity-checks/2026-05-26-brain-retro-6.json`
- Episodes (всё инкрементально, in-place 132 review'нуты): `docs/observer/episodes-2026-05.jsonl`
- Read-counter bump: `docs/observer/.read-counter.json` (`last_read_at: 2026-05-26T13:03:40.766Z`, `read_count_last_period: 4`)
- STATUS.md regen: следующий шаг процедуры.
