From a16023743c9c8370d708de6b03f3495a3356a7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 16 Jun 2026 07:25:51 +0300 Subject: [PATCH] =?UTF-8?q?feat(brain-config):=20wire=20classifier=5Fconte?= =?UTF-8?q?xt=20=D0=B2=20classify=20+=20reviewViaDirectApi=20(Task=207=20f?= =?UTF-8?q?ollow-up=20#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glue читает loadConfig().classifier_context; pure-fns и их тесты уже были. Дефолт = brain.local.md Лидерра-строка; greenfield без файла = generic. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...26-06-16-classifier-context-wiring-plan.md | 87 +++++++++++++++++++ tools/brain-retro-opus-reviewer.mjs | 11 ++- tools/router-classifier.mjs | 8 ++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-06-16-classifier-context-wiring-plan.md diff --git a/docs/superpowers/plans/2026-06-16-classifier-context-wiring-plan.md b/docs/superpowers/plans/2026-06-16-classifier-context-wiring-plan.md new file mode 100644 index 0000000..710fc11 --- /dev/null +++ b/docs/superpowers/plans/2026-06-16-classifier-context-wiring-plan.md @@ -0,0 +1,87 @@ +# classifier_context wiring — findings + план (Task 7 follow-up #2) + +**Дата:** 2026-06-16 +**Статус:** investigation done, ожидает решения по способу исполнения (стена/TDD-gate) +**Контекст:** второй пункт остатка Фазы 1 (handoff №5 §6). `classifier_context` — последний +config-ключ без wiring (handoff §5 таблица ⏳). + +--- + +## 1. Находка: pure-part уже сделан (Task 6) + +Чистые функции уже принимают `classifierContext` параметром + тесты зелёные: + +| Функция | Дефолт (хардкод) | Тест | +|---|---|---| +| `router-classifier.mjs:291` `buildClassifierPromptStructured(..., {classifierContext})` | `'CRM-проекта «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3)'` | `router-classifier.test.mjs:658-664` | +| `brain-retro-opus-reviewer.mjs:45` `buildReviewPromptStructured(ep, {classifierContext})` | `'Лидерра'` | `brain-retro-opus-reviewer.test.mjs:118-123` | + +`brain-config.mjs:47` дефолт `classifier_context: 'generic project (no profile configured)'`; +`.claude/brain.local.md` = `CRM-проект «Лидерра» (Laravel 13 + Vue 3 + Vuetify 3)`. + +## 2. Остаток: только caller-wiring (2 места) + +Вызывающий код пока зовёт чистые функции БЕЗ `classifierContext` → используется хардкод-дефолт. +Нужно: читать `loadConfig().classifier_context` и передавать (паттерн Task 7 — `await import` + try/catch, ++ override через `options.classifierContext` для тестов/вызывающих). + +### 2.1. router-classifier.mjs — `classify` (стр. 652, внутри `llmCall`-closure) + +```javascript + let classifierContext = options.classifierContext; + if (classifierContext === undefined) { + try { + const { loadConfig } = await import('./brain-config.mjs'); + classifierContext = loadConfig().classifier_context; + } catch { /* дефолт в buildClassifierPromptStructured */ } + } + const structured = buildClassifierPromptStructured(prompt, registry, { + enrichment: options.enrichment ?? true, + ...(classifierContext !== undefined ? { classifierContext } : {}), + }); +``` + +### 2.2. brain-retro-opus-reviewer.mjs — `reviewViaDirectApi` (стр. 141) + +```javascript + let classifierContext = options.classifierContext; + if (classifierContext === undefined) { + try { + const { loadConfig } = await import('./brain-config.mjs'); + classifierContext = loadConfig().classifier_context; + } catch { /* дефолт buildReviewPromptStructured */ } + } + const structured = buildReviewPromptStructured( + episode, classifierContext !== undefined ? { classifierContext } : {}, + ); +``` + +Legacy sync-обёртки (`buildClassifierPrompt:277`, `buildReviewPrompt:101`) — НЕ трогаем (не боевой путь, +sync — нельзя await; остаются на дефолте для тестов). + +## 3. Backward-compat + +`brain.local.md` value кормит ОБА промпта: +- классификатор: `Ты классификатор задач для CRM-проект «Лидерра» (...)` (было «проекта» — падеж). +- reviewer: `for the CRM-проект «Лидерра» (...) brain-governance experiment` (было короткое `Лидерра`). + +Изменения формулировок минимальны, классификацию не меняют; проектный контекст остаётся Лидерра +(handoff «дефолт уже Лидерра»). Greenfield без brain.local.md → `generic project` (желаемое). + +## 4. Стена / TDD-gate (способ исполнения — решает владелец) + +Оба файла — production (`isProductionCodePath` true для `tools/*.mjs`). `enforce-tdd-gate` (PreToolUse +Edit/Write) требует свежий test-edit + vitest-**RED** в ходе; floor-escape его НЕ снимает (отдельный хук). +Полный/одиночный vitest через Claude-Bash рушит сборку (harness-collapse) → чистого RED не достать. +Это glue вокруг уже-протестированных pure-fns (новой логики нет) — gate over-fires. Варианты: + +- **(a)** Владелец даёт TDD-gate override (`ремонт инфраструктуры` + `ремонт: <причина>`, или `срочно`) → + Edit-инструментом точно + floor-escape на стену. Надёжно для multi-line кода. +- **(b)** Владелец применяет в своём терминале (PowerShell `.Replace`) — multi-line код фрагильнее. +- **(c)** Отложить. + +## 5. Верификация + +Авторитетный свод — терминал владельца (`npx vitest run --config vitest.config.tools.mjs`): pure-fn тесты +зелёные, регрессий нет. Wiring-glue сам по себе unit-тестом не покрыт (I/O `await import`) — честно: покрыта +чистая функция, не склейка. Коммит — терминал владельца. diff --git a/tools/brain-retro-opus-reviewer.mjs b/tools/brain-retro-opus-reviewer.mjs index 54a761a..50f67e4 100644 --- a/tools/brain-retro-opus-reviewer.mjs +++ b/tools/brain-retro-opus-reviewer.mjs @@ -138,7 +138,16 @@ export async function reviewViaDirectApi(episode, options = {}) { const { callAnthropicAPI } = await import('./router-classifier.mjs'); const apiKey = options.apiKey ?? process.env.ROUTER_LLM_KEY; if (!apiKey) return null; - const structured = buildReviewPromptStructured(episode); + let classifierContext = options.classifierContext; + if (classifierContext === undefined) { + try { + const { loadConfig } = await import('./brain-config.mjs'); + classifierContext = loadConfig().classifier_context; + } catch { /* дефолт buildReviewPromptStructured */ } + } + const structured = buildReviewPromptStructured( + episode, classifierContext !== undefined ? { classifierContext } : {}, + ); try { const text = await callAnthropicAPI(structured, { apiKey, diff --git a/tools/router-classifier.mjs b/tools/router-classifier.mjs index deed42a..7c9070c 100644 --- a/tools/router-classifier.mjs +++ b/tools/router-classifier.mjs @@ -649,8 +649,16 @@ export async function classify(prompt, registry, options = {}) { const llmCall = options.llmCall || (async ({ onMetrics } = {}) => { const apiKey = process.env.ROUTER_LLM_KEY; if (!apiKey) return null; + let classifierContext = options.classifierContext; + if (classifierContext === undefined) { + try { + const { loadConfig } = await import('./brain-config.mjs'); + classifierContext = loadConfig().classifier_context; + } catch { /* дефолт в buildClassifierPromptStructured */ } + } const structured = buildClassifierPromptStructured(prompt, registry, { enrichment: options.enrichment ?? true, + ...(classifierContext !== undefined ? { classifierContext } : {}), }); const text = await callAnthropicAPI(structured, { apiKey,