feat(brain-config): wire classifier_context в classify + reviewViaDirectApi (Task 7 follow-up #2)
Glue читает loadConfig().classifier_context; pure-fns и их тесты уже были. Дефолт = brain.local.md Лидерра-строка; greenfield без файла = generic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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`) — честно: покрыта
|
||||
чистая функция, не склейка. Коммит — терминал владельца.
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user