Files
brain/docs/superpowers/specs/2026-06-15-task56-statedir-classifiercontext-spec.md
T

69 lines
4.9 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.
# Спека: Фаза 1 config-seam — `state_dir` резолвер + `classifier_context` параметр
**Дата:** 2026-06-15
**Статус:** Draft (Фаза 1 config-seam, чистые pure-seam'ы из остатка Tasks 5/6)
**Канон:** design v6 §3.3 / §5 / §5.1; план Фазы 1; [[project-brain-plugin-phase1-progress]].
## Цель
Закрыть два «чистых» config-seam ключа Фазы 1 на уровне pure-функций, не меняя поведение
`claude-brain` (backward-compat: дефолт = текущее значение), без подключения в `main()` (wiring —
отдельная задача): (1) fail-safe резолвер `state_dir` (§5.1) в `brain-config`; (2) параметр
`classifier_context` в двух prompt-builder'ах (`router-classifier`, `brain-retro-opus-reviewer`),
где сейчас захардкожена строка проекта «Лидерра».
NB: `registry_path` уже параметризован (`loadRegistry({ registryPath })`) — извлекать нечего.
`project_url_whitelist` (домены вплетены в regex) и wiring — вне этой спеки.
## Контракт {#D1}
Три обратно-совместимых дополнения (новый параметр / новая чистая функция, дефолт = текущее):
1. `tools/brain-config.mjs` — новая чистая функция `resolveStateDir(value)` → объект
`{ stateDir, warnedFallback }`. Непустая строка → `{ stateDir: <trim>, warnedFallback: false }`;
пусто / пробелы / не-строка → `{ stateDir: '.claude/brain-state', warnedFallback: true }`.
2. `tools/router-classifier.mjs` — `buildClassifierPromptStructured(userPrompt, registry,
{ enrichment = true, classifierContext = '<текущая строка>' } = {})`: строка проекта в `system`
строится из `classifierContext`. Дефолт = `CRM-проекта «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3)`
(байт-в-байт текущая).
3. `tools/brain-retro-opus-reviewer.mjs` — `buildReviewPromptStructured(episode,
{ classifierContext = 'Лидерра' } = {})`: имя проекта в первой строке `system` строится из
`classifierContext`. Дефолт = `Лидерра` (байт-в-байт текущая).
## fail-safe `state_dir` (§5.1) {#D2}
- Безопасное направление отказа: пустой/невалидный `state_dir` НЕ выключает наблюдателя/стоимость
молча — резолвер возвращает дефолт `.claude/brain-state` + `warnedFallback: true` (wiring потом
издаёт громкий warn и пишет в fallback, не делает тихий no-op).
- Резолвер чист (без I/O): определяет каталог + флаг fallback. Проверка записываемости каталога —
забота wiring (Task 7), не этого резолвера.
## Крайние случаи и критерий {#D3}
Крайние случаи (обязательны в тестах):
- `resolveStateDir('docs/observer')` → `{ stateDir: 'docs/observer', warnedFallback: false }`.
- `resolveStateDir('')` / `' '` / `null` / `undefined` → `{ stateDir: '.claude/brain-state',
warnedFallback: true }` (без исключения).
- `buildClassifierPromptStructured(p, reg)` (без опции) → `system` содержит `«Лидерра»` (дефолт
байт-в-байт); с `{ classifierContext: 'X' }` → `system` содержит `X`.
- `buildReviewPromptStructured(ep)` (без опции) → `system` содержит `Лидерра`; с
`{ classifierContext: 'X' }` → содержит `X`.
Критерий приёмки:
- Полный свод tools проходит: `npx vitest run --config vitest.config.tools.mjs` (авторитетно — в
терминале владельца; сессионный Bash рушит воркеры под нагрузкой).
- Регрессия: ни один существующий тест не падает (дефолты сохранили поведение байт-в-байт).
Конвенция: имена `resolveStateDir` / `classifierContext`; стиль `tools/` (ESM, чистые функции, без
новых зависимостей); строковые дефолты — точная копия текущих захардкоженных строк.
```verified-context-json
[
{"id":"D1","kind":"EXTRACTED","ref":"tools/brain-config.mjs","anchor":"const DEFAULTS = Object.freeze({"},
{"id":"D2","kind":"EXTRACTED","ref":"tools/router-classifier.mjs","anchor":"export function buildClassifierPromptStructured"},
{"id":"D3","kind":"EXTRACTED","ref":"tools/brain-retro-opus-reviewer.mjs","anchor":"export function buildReviewPromptStructured"}
]
```