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

4.9 KiB
Raw Blame History

Спека: Фаза 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 — вне этой спеки.

Контракт

Три обратно-совместимых дополнения (новый параметр / новая чистая функция, дефолт = текущее):

  1. tools/brain-config.mjs — новая чистая функция resolveStateDir(value) → объект { stateDir, warnedFallback }. Непустая строка → { stateDir: <trim>, warnedFallback: false }; пусто / пробелы / не-строка → { stateDir: '.claude/brain-state', warnedFallback: true }.
  2. tools/router-classifier.mjsbuildClassifierPromptStructured(userPrompt, registry, { enrichment = true, classifierContext = '<текущая строка>' } = {}): строка проекта в system строится из classifierContext. Дефолт = CRM-проекта «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3) (байт-в-байт текущая).
  3. tools/brain-retro-opus-reviewer.mjsbuildReviewPromptStructured(episode, { classifierContext = 'Лидерра' } = {}): имя проекта в первой строке system строится из classifierContext. Дефолт = Лидерра (байт-в-байт текущая).

fail-safe state_dir (§5.1)

  • Безопасное направление отказа: пустой/невалидный state_dir НЕ выключает наблюдателя/стоимость молча — резолвер возвращает дефолт .claude/brain-state + warnedFallback: true (wiring потом издаёт громкий warn и пишет в fallback, не делает тихий no-op).
  • Резолвер чист (без I/O): определяет каталог + флаг fallback. Проверка записываемости каталога — забота wiring (Task 7), не этого резолвера.

Крайние случаи и критерий

Крайние случаи (обязательны в тестах):

  • 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, чистые функции, без новых зависимостей); строковые дефолты — точная копия текущих захардкоженных строк.

[
  {"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"}
]