# Мерж роутера в наставника: единый рецензент (спека + план + выбор скилов) — простыми словами

**Дата:** 2026-06-13. **Кодовая фраза:** «роутер-наставник».
**Решения владельца:** Подход 1 (наставник зовёт классификатор как функцию) + наставник судит
спеку/план/выбор скилов и заворачивает NO-GO + видимость вердикта только на NO-GO (протокол
Р1–Р8). **Собирать со снятой стеной (maintenance) + по TDD.** Поглощает узкую спеку
`2026-06-13-mentor-nogo-decision-design.md` (удалена).

---

## 1. Две болезни, которые лечим {#problem}

**Болезнь A — наставник не заворачивает на несогласии.** У вердикта наставника нет «кнопки»
GO/NO-GO — только совет. Система засчитывала «нет» лишь при сломанном вердикте; содержательное
«переделай» проходило как GO → молча, до контроллера не доходило, круг L1 из Р3 не работал.

**Болезнь B — роутер работает в пустоту.** Классификатор (`classify()`, выбирающий скилы) запускался
только дохлым хуком `router-prehook`, который **писал результат в файл и отдавал контроллеру `{}`**
(ничего). Хуки-потребители (`chain-recommendation` / `graph-first`) в живом конфиге не
зарегистрированы. Итог: выбор скилов никто не проверял и до контроллера не доносил.

## 2. Лечение: наставник — единый рецензент, роутер живёт внутри него {#goal}

Наставник становится **единым мозгом-рецензентом**. На ревью он:

1. **зовёт `classify(цель_задачи, граф)`** — полный 3-слойный мозг роутера (prefilter → LLM →
   regex-fallback), читающий граф `docs/registry/nodes.yaml` + карточки узлов. Получает «для этой
   задачи рекомендованы скилы X, цепочка Y».
2. **выносит ОДИН вердикт** по спеке/плану **и по выбору скилов**: сверяет скилы, объявленные в
   плане, с рекомендацией роутера + оценивает уместность.
3. **GO** → одобрение (печать); **NO-GO** (план плох ИЛИ скилы не те) → дословное замечание
   доходит до контроллера через рабочий exit-2 канал + растёт счётчик круга L1.

**Гарантия:** «роутер становится функцией» НЕ значит «теряет мозг». `classify()` + граф `nodes.yaml`

- карточки + эмбеддинги/PAMYATKA — **без изменений**. Меняется только проводка: классификатор
**зовёт наставник** (а не дохлый `router-prehook`), результат идёт **в вердикт** (а не в файл).
Дохлые обёртки `router-prehook` / `chain-recommendation` / `graph-first` — выводятся из живого
потока (их работу делает наставник).

## 3. Что конкретно правим {#components}

### 3.1. Наставник обретает решение GO/NO-GO (поглощённый фикс Р7)

- В вердикт наставника добавляем **`decision: "GO" | "NO-GO"`** (как у судьи). `validateMentorVerdict`
  требует `decision ∈ {GO, NO-GO}` наряду со слотами `plan_points_addressed / reasoning /
  recommendation / confidence`. Промпты (план и спека) просят явно: «если требует переделки →
  `NO-GO` + что править».
- `runMentorVerdict` / `runMentorSpecVerdict` возвращают `decision`.

### 3.2. План объявляет выбранные скилы (новый контракт формата)

- План несёт **структурный блок объявленных скилов** (парсится, а не вольный текст), например
  fenced-блок ` ```skills-json ` со списком `["executing-plans", "test-driven-development"]` ИЛИ
  отдельная секция «## Скилы». Точный формат — в плане реализации; критерий: машинно-парсится.
- Новый чистый парсер достаёт объявленные скилы из плана (зеркало `parseVerifiedContext`).

### 3.3. Наставник зовёт классификатор и судит выбор скилов

- В `enforce-mentor-on-plan-write.mjs` (или его движке) на ревью плана: загрузить граф (`registry-load`)
  → `classify(цельПлана, граф)` → рекомендованные скилы/цепочка. Цель задачи берём из плана (секция
  «Цель»). Классификатор — async, свой транспорт; +1 LLM-вызов на ревью (приемлемо, мозг настоящий).
- Эти данные (объявленные скилы + рекомендация роутера) идут в **промпт вердикта наставника**: он
  оценивает уместность и при несоответствии ставит `decision: "NO-GO"`.
- **Скил-сверка — только для ПЛАНА (gate2).** Спека (gate1) судится по сути + `decision` (Р7), но
  БЕЗ скил-сверки: у спеки скилов нет (скилы — про реализацию, живут в плане).

### 3.4. Логика заворота/доставки/одобрения

- `blocked = wired && (decision==='NO-GO' || вердикт сломан)`.
- На NO-GO контроллеру доходит **СУТЬ**: `recommendation` + `reasoning` + `plan_points_addressed`
  **+ конкретика по скилам** («взял executing-plans, а задача — отладка бага: добавь
  systematic-debugging»). Дорабатываем `formatMentorObjection`.
- `mentor-GO` — только при `wired && ok && decision==='GO'`. На NO-GO одобрения нет → судья молчит.
- Счётчик L1 (`mentor-nogo-counter`, per task-id) растёт на NO-GO; 3 подряд → карточка арбитража.
- degraded ИИ → «не дозвонился» (без изменений). Сбой `classify()` → наставник не выдумывает скилы:
  судит план без скил-сверки (fail-safe, не ложный NO-GO «скилы не те»).

### 3.5. Что НЕ трогаем

`classify()` + граф `nodes.yaml` + карточки узлов — **код не меняется** (только новый вызыватель).
Судья (имеет свой `decision` + objections), печать, стена, finish-грант, чистое завершение,
счётчик-механика — **без изменений**. Обёртки `router-prehook`/`chain-recommendation`/`graph-first`
не включаем в живой поток (их роль у наставника); файлы не удаляем (вне scope).

## 4. Поведение на примере {#flow}

1. Я пишу план, объявляю скилы `[executing-plans, TDD]`.
2. Наставник (Post) зовёт `classify("починить парсер X")` → мозг роутера: «это bugfix живого кода →
   нужен systematic-debugging + Pest».
3. Наставник: **`decision: "NO-GO"`**, `recommendation: "взял executing-plans, а это отладка бага —
   добавь systematic-debugging до фиксов"`. blocked=true → замечание приходит мне, L1=1, mentor-GO не
   записан → судья молчит.
4. Я переписал план (добавил systematic-debugging) → наставник снова: `classify` совпало, план ок →
   **`decision: "GO"`** → mentor-GO записан → судья судит вслепую → печать.

## 5. Если что-то не так {#failures}

- `decision` не из {GO, NO-GO} / пропущен → вердикт несодержателен (`ok:false`) → заворот (fail-safe).
- `classify()` упал/недоступен → наставник судит план БЕЗ скил-сверки (не выдумывает «скилы не те»).
- degraded ИИ наставника → «не дозвонился» (без изменений).

## 6. Как проверим — максимум тестов (TDD) {#testing}

- `validateMentorVerdict`: требует `decision ∈ {GO, NO-GO}`.
- Парсер объявленных скилов из плана: достаёт список; нет блока → пусто.
- Промпты (план+спека) несут запрос `decision` + (для плана) секцию «рекомендация роутера vs
  объявленные скилы».
- `runMentorVerdict`/`runMentorSpecVerdict`: GO/NO-GO возвращаются; сбой → wired:false.
- Наставник зовёт `classify` на ревью плана (мок classify в тесте); результат влияет на вердикт.
- `decideMentorObjection`: NO-GO → block + суть (+скил-конкретика) + recordMentorGo:false; GO →
  recordMentorGo:true; сломанный → block; degraded → block+degraded.
- `classify()` бросил → наставник не ставит ложный скил-NO-GO (судит без скил-сверки).
- Счётчик L1 растёт на содержательный NO-GO.
- Регрессия: весь набор зелёный (база 3877).

## 7. Решения владельца (закрытые) {#decided}

- Подход 1 — наставник зовёт классификатор как функцию; роутер-мозг сохраняется целиком (Р8). ✅
- Наставник судит спеку + план + выбор скилов; заворачивает NO-GO на любом несогласии. ✅
- Видимость вердикта — только на NO-GO (на GO тихо). ✅
- Явное `decision: GO|NO-GO` в вердикте наставника (поглощённый Р7). ✅
- План объявляет скилы структурно (новый контракт). ✅

## Ссылки {#refs}

Протокол `docs/superpowers/session-protocol-2026-06-13.md` (Р1–Р8). Код: `mentor-verdict.mjs`,
`enforce-mentor-on-plan-write.mjs`, `on-plan-write.mjs`, `objection-format.mjs`, `router-classifier.mjs`
(`classify` — без изменений, новый вызыватель), `registry-load.mjs` (граф/карточки — без изменений),
`mentor-nogo-counter.mjs`. Судья `enforce-judge-gate.mjs` — без изменений.
