# Дизайн: наставник зовёт роутер мимо prefilter + корректный маппинг рекомендации

## Проблема

Мерж роутер↔наставник (Р8): наставник на ревью плана зовёт `classify()` как функцию, чтобы
сверить объявленные в плане скилы с рекомендацией роутера. На практике сверка пустая.

## Корень (подтверждён чтением кода + эмпирикой)

1. `classify()` (Layer 1) начинается с `prefilter()`. Если prefilter вернул не-null —
   `classify` возвращает его **до** LLM-слоя (`router-classifier.mjs:629-630`).
2. `prefilter` заточен под промпты пользователя. `detectMicro` ищет ключевые слова
   **подстрокой** (`MICRO_KEYWORDS` содержит `'format'`/`'формат'`). Цель плана с именем
   модуля `format-bytes` → `includes('format')` = true → `{task_type:'micro', node:'direct'}`.
   Эмпирика: `prefilter(goalПлана)` вернул именно это. LLM-роутер не вызывается (нет вызова в логах).
3. Даже когда prefilter отрабатывает, `on-plan-write.mjs:52` читает `c.recommended_chain` /
   `c.recommended_node`, а prefilter-выход несёт поле **`node`** → `recommendedChain = []`.

Итог: роутер-мозг (LLM + граф) до сверки скилов не доходит, рекомендация теряется.

## Решение (A + B)

### A. Опция `skipPrefilter` для `classify`, наставник её включает {#A}

`classify(prompt, registry, options)` получает `options.skipPrefilter`. При `true` Layer 1
prefilter пропускается — управление сразу идёт в cache → LLM → regex-fallback. Опция
**аддитивна**: умолчание (`skipPrefilter` отсутствует/false) — прежнее поведение, прод-роутер
не меняется. Наставник (`enforce-mentor-on-plan-write.mjs`) зовёт
`classify(goal, registry, { skipPrefilter: true })` — цель плана всегда судится мозгом роутера.

Цена: один вызов классификатора на каждую запись плана. Приемлемо (это и есть смысл сверки Р8).

### B. Устойчивый маппинг рекомендации {#B}

Чтение результата `classify` в `on-plan-write.mjs` выносится в чистый экспортируемый помощник
`recommendedChainOf(c)`: если есть `recommended_chain` (LLM-схема) — использовать его; иначе
взять `recommended_node` либо `node`, но **не** `'direct'` (это «скил не нужен» → пустой список).
Так prefilter/regex-выходы (поле `node`) больше не теряются молча.

### Вне scope

`detectMicro` НЕ редактируется. С (A) наставник его обходит; живых других потребителей
prefilter-пути нет (хуки обычного роутера в settings.json не зарегистрированы). Сужение
`detectMicro` до границ слов — отдельная задача, если понадобится.

## Тестирование (TDD)

- `classify(goal, {skipPrefilter:true, llmCall:mock})` на micro-цели → prefilter обойдён,
  `llmCall` вызван, возвращён его результат; `classify(goal, {llmCall:mock})` (умолчание) на той
  же цели → prefilter короткозамыкает, `llmCall` НЕ вызван.
- `recommendedChainOf`: `{recommended_chain:['#18']}`→`['#18']`; `{recommended_node:'#18'}`→`['#18']`;
  `{node:'#18'}`→`['#18']`; `{node:'direct'}`→`[]`; `{}`/`null`→`[]`.
- Полная регрессия tools-only зелёная, 0 регрессий.
