Files
brain/docs/superpowers/plans/2026-06-15-task56-statedir-classifiercontext-plan-v2.md
T

200 lines
12 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 параметр — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans (инлайн под стеной — субагенты запрещены, VA-4). Steps — checkbox (`- [ ]`).
**Goal:** Добавить чистый fail-safe резолвер `resolveStateDir` в `brain-config` и параметр `classifierContext` в два prompt-builder'а (`router-classifier`, `brain-retro-opus-reviewer`), не меняя поведение claude-brain (backward-compat: дефолт = текущая строка/каталог).
**Architecture:** Чистые pure-seam'ы — новые параметры со значением по умолчанию / новая чистая функция. Ни один файл не gated (не discipline-source / не normative). Подключение в `main()` — отдельная задача (wiring).
**Tech Stack:** Node.js ESM (`tools/`), vitest (`vitest.config.tools.mjs`).
**Спек:** `docs/superpowers/specs/2026-06-15-task56-statedir-classifiercontext-spec-v2.md` (§D1 контракт, §D2 fail-safe state_dir, §D3 крайние случаи + критерий).
## Цель
Закрыть два чистых config-seam ключа Фазы 1 (state_dir резолвер + classifier_context) на уровне
pure-функций, backward-compat (дефолт = текущее значение). Wiring и project_url_whitelist — отдельно.
## Переговоры
Позиция контроллера по типовым замечаниям ревью к правке существующих файлов:
1. **Edit, не Write-overwrite.** Все три файла прочитаны в этой сессии; `old_string` каждого Edit —
байт-точная подстрока текущего состояния. Правки аддитивны (новый параметр со значением по
умолчанию / новый экспорт) — существующие сигнатуры/вызовы с одним аргументом не ломаются.
2. **Бэкап — git.** Файлы под версионным контролем; откат — `git restore` / `git show HEAD:<путь>`.
3. **Backward-compat доказывается тестами.** Каждая правка: RED→GREEN + кейс «дефолт = текущая
строка байт-в-байт» + полный регресс. Авторитетный полный свод — в терминале владельца.
4. **fail-safe направление.** `resolveStateDir` при пустом/невалидном входе возвращает безопасный
дефолт + `warnedFallback: true` (не тихий no-op) — §5.1.
```skills-json
["test-driven-development"]
```
```steps-json
[
{"op":"Edit","object":"tools/brain-config.test.mjs","ref":"D1"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Edit","object":"tools/brain-config.mjs","ref":"D1"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Edit","object":"tools/router-classifier.test.mjs","ref":"D2"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Edit","object":"tools/router-classifier.mjs","ref":"D2"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Edit","object":"tools/brain-retro-opus-reviewer.test.mjs","ref":"D3"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Edit","object":"tools/brain-retro-opus-reviewer.mjs","ref":"D3"},
{"op":"Edit","object":"tools/brain-retro-opus-reviewer.mjs","ref":"D3"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"},
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs","ref":"D3"}
]
```
```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"}
]
```
---
## Task A: `resolveStateDir` в brain-config (fail-safe §5.1)
**Files:** Modify `tools/brain-config.mjs`, `tools/brain-config.test.mjs`
- [ ] **Step 1 (Edit test, RED):** в `tools/brain-config.test.mjs` добавить в конец (отдельный import + describe):
```javascript
import { resolveStateDir } from './brain-config.mjs';
describe('resolveStateDir fail-safe (§D2)', () => {
it('непустая строка → как есть, без fallback', () => {
expect(resolveStateDir('docs/observer')).toEqual({ stateDir: 'docs/observer', warnedFallback: false });
});
it('пусто / пробелы → безопасный дефолт + warnedFallback', () => {
expect(resolveStateDir('')).toEqual({ stateDir: '.claude/brain-state', warnedFallback: true });
expect(resolveStateDir(' ')).toEqual({ stateDir: '.claude/brain-state', warnedFallback: true });
});
it('не-строка → безопасный дефолт + warnedFallback (не падает)', () => {
expect(resolveStateDir(null)).toEqual({ stateDir: '.claude/brain-state', warnedFallback: true });
expect(resolveStateDir(undefined)).toEqual({ stateDir: '.claude/brain-state', warnedFallback: true });
});
});
```
- [ ] **Step 2 (Bash, verify FAIL):** `npx vitest run --config vitest.config.tools.mjs`
Expected: новый тест FAIL (`resolveStateDir is not a function`). Авторитетно — терминал владельца.
- [ ] **Step 3 (Edit impl):** в `tools/brain-config.mjs` добавить функцию в конец (после `loadConfig`; `old_string` = последние строки `loadConfig` — `return resolveConfig(parseBrainConfig(md));\n}`):
```javascript
return resolveConfig(parseBrainConfig(md));
}
/** fail-safe резолвер state_dir (§5.1): непустая строка → как есть; иначе → безопасный дефолт
* .claude/brain-state + warnedFallback (НЕ тихий no-op — wiring издаёт warn и пишет в fallback). */
export function resolveStateDir(value) {
const v = typeof value === 'string' ? value.trim() : '';
if (v.length > 0) return { stateDir: v, warnedFallback: false };
return { stateDir: '.claude/brain-state', warnedFallback: true };
}
```
- [ ] **Step 4 (Bash, verify PASS):** `npx vitest run --config vitest.config.tools.mjs`
Expected: PASS (новые + существующие).
---
## Task B: `classifierContext` в router-classifier
**Files:** Modify `tools/router-classifier.mjs`, `tools/router-classifier.test.mjs`
- [ ] **Step 5 (Edit test, RED):** в `tools/router-classifier.test.mjs` добавить describe (импорт `buildClassifierPromptStructured` уже есть в файле — иначе добавить `import { buildClassifierPromptStructured } from './router-classifier.mjs';` рядом):
```javascript
describe('buildClassifierPromptStructured classifierContext (config-seam §D1)', () => {
const reg = { nodes: [], chains: {} };
it('дефолт → текущая строка «Лидерра»', () => {
expect(buildClassifierPromptStructured('p', reg).system).toContain('«Лидерра»');
});
it('classifierContext инъектируется', () => {
expect(buildClassifierPromptStructured('p', reg, { classifierContext: 'ТестПроект XYZ' }).system)
.toContain('ТестПроект XYZ');
});
});
```
- [ ] **Step 6 (Bash, verify FAIL):** `npx vitest run --config vitest.config.tools.mjs`
Expected: FAIL (инъекция-кейс — параметр игнорируется).
- [ ] **Step 7 (Edit impl):** в `tools/router-classifier.mjs` заменить начало `buildClassifierPromptStructured` (`old_string` = строки от сигнатуры до строки `const system = `Ты классификатор задач для CRM-проекта «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3).`):
```javascript
export function buildClassifierPromptStructured(userPrompt, registry, { enrichment = true, classifierContext = 'CRM-проекта «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3)' } = {}) {
const pamyatka = enrichment ? `\n\n${PAMYATKA}\n` : '\n';
const nodesBlock = buildNodesBlock(registry);
const chainsBlock = buildChainsBlock(registry);
const system = `Ты классификатор задач для ${classifierContext}.
```
- [ ] **Step 8 (Bash, verify PASS):** `npx vitest run --config vitest.config.tools.mjs`
Expected: PASS (дефолт = байт-в-байт текущая строка; `buildClassifierPrompt` вызывает без `classifierContext` → дефолт).
---
## Task C: `classifierContext` в brain-retro-opus-reviewer
**Files:** Modify `tools/brain-retro-opus-reviewer.mjs`, `tools/brain-retro-opus-reviewer.test.mjs`
- [ ] **Step 9 (Edit test, RED):** в `tools/brain-retro-opus-reviewer.test.mjs` добавить describe (импорт `buildReviewPromptStructured` уже есть — иначе добавить):
```javascript
describe('buildReviewPromptStructured classifierContext (config-seam §D1)', () => {
it('дефолт → Лидерра', () => {
expect(buildReviewPromptStructured({ schema_version: 4 }).system).toContain('Лидерра');
});
it('classifierContext инъектируется', () => {
expect(buildReviewPromptStructured({ schema_version: 4 }, { classifierContext: 'ProjZ' }).system)
.toContain('ProjZ');
});
});
```
- [ ] **Step 10 (Bash, verify FAIL):** `npx vitest run --config vitest.config.tools.mjs`
Expected: FAIL (инъекция-кейс).
- [ ] **Step 11 (Edit impl — сигнатура):** в `tools/brain-retro-opus-reviewer.mjs` заменить строку сигнатуры (`old_string` = `export function buildReviewPromptStructured(episode) {`):
```javascript
export function buildReviewPromptStructured(episode, { classifierContext = 'Лидерра' } = {}) {
```
- [ ] **Step 12 (Edit impl — строка system):** заменить захардкоженную строку (`old_string` = ` 'You are the independent reviewer of routing decisions for the Лидерра brain-governance experiment.',`):
```javascript
`You are the independent reviewer of routing decisions for the ${classifierContext} brain-governance experiment.`,
```
- [ ] **Step 13 (Bash, verify PASS):** `npx vitest run --config vitest.config.tools.mjs`
Expected: PASS (дефолт `Лидерра` = байт-в-байт).
---
## Task D: финальный регресс
- [ ] **Step 14 (Bash, полный регресс):** `npx vitest run --config vitest.config.tools.mjs`
Expected: PASS весь свод. Авторитетный прогон — в терминале владельца.
---
## Self-Review
- **Покрытие спека:** §D1 — Task A (resolveStateDir) + B (classifierContext router) + C (classifierContext reviewer); §D2 fail-safe — тесты resolveStateDir (пусто/не-строка → fallback+warned); §D3 крайние случаи — дефолт байт-в-байт + инъекция + null/undefined; критерий — Step 14.
- **Заглушек нет:** каждый impl-шаг несёт полный код; тесты полные; команды vitest точные.
- **Согласованность имён:** `resolveStateDir` / `classifierContext` единые; дефолты = точная копия текущих строк (backward-compat инвариант).
- **Стена:** brain-config / router-classifier / brain-retro-opus-reviewer — НЕ discipline-source и НЕ normative → нормативный гейт не engage; правки проходят как обычные шаги запечатанного плана (стена М2 + content-floor + TDD-gate govern). Bash floor-safe (`npx vitest`). MultiEdit не используется (brain-retro — два отдельных Edit-шага 11/12). project_url_whitelist + wiring — вне scope.