From 03a1f2c995101e2407e0b374569c01d9e35fbd86 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 04:25:02 +0300 Subject: [PATCH] =?UTF-8?q?docs(brain-config):=20=D0=B4=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D0=B9=D0=BD+=D0=BF=D0=BB=D0=B0=D0=BD=20normative=5Ffiles-?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20+=20handoff-5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...06-15-normative-files-config-model-plan.md | 188 ++++++++++++++++++ ...06-15-brain-as-plugin-session-handoff-5.md | 90 +++++++++ ...-15-normative-files-config-model-design.md | 78 ++++++++ 3 files changed, 356 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-15-normative-files-config-model-plan.md create mode 100644 docs/superpowers/specs/2026-06-15-brain-as-plugin-session-handoff-5.md create mode 100644 docs/superpowers/specs/2026-06-15-normative-files-config-model-design.md diff --git a/docs/superpowers/plans/2026-06-15-normative-files-config-model-plan.md b/docs/superpowers/plans/2026-06-15-normative-files-config-model-plan.md new file mode 100644 index 0000000..5b7c895 --- /dev/null +++ b/docs/superpowers/plans/2026-06-15-normative-files-config-model-plan.md @@ -0,0 +1,188 @@ +# normative_files config-model + cross-ref/l1 wiring — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans (под стеной — escape-per-step, +> наставник H4 печать не ставит). Steps — checkbox (`- [ ]`). + +**Goal:** Развести `normative_files` (проектные доки) и универсальные CLAUDE.md/MEMORY.md; прокинуть +cross-ref (config ∪ universal) и l1 (`tool_registry_path`) в config, сохранив поведение claude-brain. + +**Architecture:** Спека `2026-06-15-normative-files-config-model-design.md`. cross-ref строит набор +version-tracked = config `normative_files` ∪ встроенные `{CLAUDE.md, MEMORY.md}`; l1 берёт один +`tool_registry_path` (fail-safe skip при отсутствии). Дефолты = текущие → backward-compat байт-в-байт. + +**Tech Stack:** Node ESM (.mjs), vitest, brain-config (loadConfig). + +--- + +## File Structure + +- Modify: `tools/brain-config.mjs` — добавить дефолт `tool_registry_path`. +- Modify: `.claude/brain.local.md` — добавить `tool_registry_path: docs/Tooling_v8_3.md`. +- Modify: `tools/cross-ref-checker.mjs` (+ `.test.mjs`) — `UNIVERSAL_VERSION_TRACKED` + `buildNormativeMap` + `loadFiles` param + CLI wiring. +- Modify: `tools/l1-watcher.mjs` (+ `.test.mjs`) — `loadInputs` param `toolRegistryPath` + `toolingPresent` skip + CLI wiring. + +--- + +## Task 1: brain-config дефолт `tool_registry_path` + +- [ ] **Step 1:** В `tools/brain-config.mjs`, в `DEFAULTS` (Object.freeze) добавить строку после `protected_paths: []`: + +```javascript + tool_registry_path: 'docs/Tooling_v8_3.md', +``` + +- [ ] **Step 2 (verify):** Владелец: `npx vitest run --config vitest.config.tools.mjs tools/brain-config.test.mjs` — все зелёные (новый дефолт не ломает; при желании добавить assert `resolveConfig({}).tool_registry_path === 'docs/Tooling_v8_3.md'`). + +--- + +## Task 2: cross-ref-checker — config ∪ universal + +**Files:** `tools/cross-ref-checker.mjs`, `tools/cross-ref-checker.test.mjs` + +- [ ] **Step 1 (RED test):** В `cross-ref-checker.test.mjs` добавить: + +```javascript +import { buildNormativeMap } from './cross-ref-checker.mjs'; +describe('buildNormativeMap (Task 7 follow-up)', () => { + it('config-список ∪ встроенные CLAUDE/MEMORY; дефолт claude-brain = 5', () => { + const m = buildNormativeMap(['docs/Pravila_raboty_Claude_v1_1.md', 'docs/Plugin_stack_rules_v1.md', 'docs/Tooling_v8_3.md']); + expect(m.Pravila).toBe('docs/Pravila_raboty_Claude_v1_1.md'); + expect(m.PSR_v1).toBe('docs/Plugin_stack_rules_v1.md'); + expect(m.Tooling).toBe('docs/Tooling_v8_3.md'); + expect(m.CLAUDE).toBe('CLAUDE.md'); // universal built-in + expect(m.MEMORY).toBe('MEMORY.md'); // universal built-in + }); + it('пустой список → только universal', () => { + expect(buildNormativeMap([])).toEqual({ CLAUDE: 'CLAUDE.md', MEMORY: 'MEMORY.md' }); + }); +}); +``` + +- [ ] **Step 2:** Run RED — `npx vitest run --config vitest.config.tools.mjs tools/cross-ref-checker.test.mjs` → FAIL «buildNormativeMap is not a function». + +- [ ] **Step 3 (impl):** В `cross-ref-checker.mjs` после `PATH_TO_NAME` добавить: + +```javascript +// Универсальные version-tracked доки (есть у любого проекта; не в настройке). +const UNIVERSAL_VERSION_TRACKED = Object.freeze({ CLAUDE: 'CLAUDE.md', MEMORY: 'MEMORY.md' }); + +// Собрать {name:path} из проектного списка (config) ∪ universal. Имя — из PATH_TO_NAME, +// иначе basename без .md (greenfield; regex-имена — отдельный заход, см. дизайн §4.1/§7). +export function buildNormativeMap(normativeFilesList = []) { + const map = {}; + for (const p of (Array.isArray(normativeFilesList) ? normativeFilesList : [])) { + const name = PATH_TO_NAME[p] || String(p).split('/').pop().replace(/\.md$/, ''); + map[name] = p; + } + return { ...map, ...UNIVERSAL_VERSION_TRACKED }; +} +``` + +- [ ] **Step 4:** Run GREEN — тот же прогон → PASS (buildNormativeMap). + +- [ ] **Step 5 (loadFiles param):** В `cross-ref-checker.mjs` `loadFiles` принять карту параметром: + +```javascript +function loadFiles(root = process.cwd(), normativeMap = NORMATIVE_FILES) { + const out = {}; + for (const [, path] of Object.entries(normativeMap)) { + const abs = join(root, path); + if (existsSync(abs)) out[path] = readFileSync(abs, 'utf-8'); + } + return out; +} +``` + +- [ ] **Step 6 (CLI wiring):** В CLI-блоке (`if (process.argv...)`) заменить начало: + +```javascript + let normativeMap = NORMATIVE_FILES; + try { + const { loadConfig } = await import('./brain-config.mjs'); + normativeMap = buildNormativeMap(loadConfig().normative_files); + } catch { /* brain-config недоступен → дефолт NORMATIVE_FILES */ } + const files = loadFiles(process.cwd(), normativeMap); + const m = detectMismatches(files, { normativeFiles: normativeMap }); +``` + +(top-level await допустим в CLI-блоке ESM; backward-compat: config-3 ∪ universal = те же 5.) + +- [ ] **Step 7 (verify):** Владелец: `npx vitest run --config vitest.config.tools.mjs tools/cross-ref-checker.test.mjs` → PASS (новые + 20 прежних). + +--- + +## Task 3: l1-watcher — `tool_registry_path` + skip + +**Files:** `tools/l1-watcher.mjs`, `tools/l1-watcher.test.mjs` + +- [ ] **Step 1 (RED test):** В `l1-watcher.test.mjs` добавить: + +```javascript +it('loadInputs: tool_registry_path отсутствует → toolingPresent:false (skip-сигнал)', () => { + const r = loadInputs(process.cwd(), ''); + expect(r.toolingPresent).toBe(false); +}); +``` + +(нужен `import { loadInputs } from './l1-watcher.mjs'` — добавить, если нет.) + +- [ ] **Step 2:** Run RED → FAIL (`toolingPresent` undefined). + +- [ ] **Step 3 (impl):** В `l1-watcher.mjs` `loadInputs`: + +```javascript +export function loadInputs(projectRoot = process.cwd(), toolRegistryPath = 'docs/Tooling_v8_3.md') { + const userSettings = JSON.parse(loadFileMaybe(join(homedir(), '.claude', 'settings.json')) || '{}'); + const projectSettings = JSON.parse(loadFileMaybe(join(projectRoot, '.claude', 'settings.json')) || '{}'); + const merged = { + enabledPlugins: { ...(userSettings.enabledPlugins || {}), ...(projectSettings.enabledPlugins || {}) }, + }; + const toolingRaw = toolRegistryPath ? loadFileMaybe(join(projectRoot, toolRegistryPath)) : null; + const tooling = toolingRaw || ''; + const toolingPresent = toolingRaw !== null; + const aliasesRaw = loadFileMaybe(join(projectRoot, 'tools', '.l1-watcher-aliases.txt')); + const aliases = parseAliases(aliasesRaw); + return { settings: merged, tooling, aliases, toolingPresent }; +} +``` + +- [ ] **Step 4:** Run GREEN. + +- [ ] **Step 5 (CLI wiring + skip):** В CLI-блоке заменить: + +```javascript + let toolRegistryPath = 'docs/Tooling_v8_3.md'; + try { + const { loadConfig } = await import('./brain-config.mjs'); + toolRegistryPath = loadConfig().tool_registry_path; + } catch { /* дефолт */ } + const { settings, tooling, aliases, toolingPresent } = loadInputs(process.cwd(), toolRegistryPath); + if (!toolingPresent) { + console.log('[l1-watcher] OK — справочник инструментов не задан/не найден (skip)'); + process.exit(0); + } + const drift = detectDrift(settings, tooling, aliases); +``` + +- [ ] **Step 6 (verify):** Владелец: `npx vitest run --config vitest.config.tools.mjs tools/l1-watcher.test.mjs` → PASS (новый + 12 прежних). + +--- + +## Task 4: brain.local.md — `tool_registry_path` + +- [ ] **Step 1:** В `.claude/brain.local.md`, во frontmatter после `state_dir: docs/observer` добавить: + +```yaml +tool_registry_path: docs/Tooling_v8_3.md +``` + +- [ ] **Step 2 (verify):** Владелец: полный свод `npx vitest run --config vitest.config.tools.mjs` — мои файлы GREEN; 3 пре-существующих deepseek-провала — вне scope. + +--- + +## Self-Review + +- **Spec coverage:** §3 модель (Task 1 ключ дефолт + Task 4 brain.local.md); §4.1 cross-ref (Task 2 buildNormativeMap ∪ universal + loadFiles + CLI); §4.2 l1 (Task 3 toolRegistryPath + skip); §5 fail-safe (Task 2 пустой→universal, Task 3 absent→skip). Граница §4.1 (regex-имена дефолтные) — соблюдена (не трогаем CROSS_REF_RE). §7 follow-up — вне scope. +- **Placeholders:** нет; весь код реальный. +- **Consistency:** `buildNormativeMap`/`UNIVERSAL_VERSION_TRACKED`/`toolingPresent`/`tool_registry_path` — единые имена. +- **Стена:** исполнение escape-per-step (печать H4 не встаёт); каждый Edit/Write — owner escape; полный свод + коммит — терминал владельца. diff --git a/docs/superpowers/specs/2026-06-15-brain-as-plugin-session-handoff-5.md b/docs/superpowers/specs/2026-06-15-brain-as-plugin-session-handoff-5.md new file mode 100644 index 0000000..c48cca5 --- /dev/null +++ b/docs/superpowers/specs/2026-06-15-brain-as-plugin-session-handoff-5.md @@ -0,0 +1,90 @@ +# Brain-as-plugin — handoff сессии №5 (закрытие 2026-06-15) + +**Кодовая фраза стены:** «роутер-наставник». **Канон дизайна:** `2026-06-15-brain-as-plugin-design-v6.md`. +**План Фазы 1:** `2026-06-15-brain-plugin-phase1-config-seam.md`. Предыдущие: handoff-4/3/2/handoff. +Эта сессия — **Task 7 (финальный wiring loadConfig в хуки)**: исполнено по живым хукам + status-md; +остаток (CLI normative_files + router classifier_context) вынесен в follow-up из-за design-вопроса. + +--- + +## 1. Что сделано + +- **Батч A-C (живые хуки) — DONE, коммит `165ff3a`** (8 files, в терминале владельца): + - `cost-stop-hook.mjs` — `currentMonthFile(now, repoRoot, stateDir='docs/observer')` + `main()` читает + `resolveStateDir(loadConfig(repoRoot).state_dir)` через динамический import + try/catch fallback. + - `observer-stop-hook.mjs` — `appendEpisode`/`bumpPiiCounter` получили `stateDir`; CLI основной + + error-marker пути читают config; ноль хардкодов `docs/observer` кроме дефолтов. + - `enforce-mcp-classification.mjs` — `decide({urlWhitelist})` → `classifyMcpTool`; `main()` читает + `project_url_whitelist` (fail-CLOSED при сбое импорта). + - `enforce-normative-content-rules.mjs` — `main()` прокидывает `protected_paths` в `isNormativePath` + (база всегда защищена; augment=[] → поведение не меняется). + - `.claude/brain.local.md` — настройка консьюмера (значения = текущие лидерровские; `state_dir: docs/observer`). + - Тесты: cost-stop + observer + mcp (config-seam). **Авторитетный свод: 3981 passed** (мои файлы GREEN). +- **status-md-generator.mjs — DONE (коммит dcc14f83)**: module-level `STATE_DIR` (дефолт `docs/observer`), + CLI читает `state_dir` из config (top-level await import); все 8 хардкодов `docs/observer` → `STATE_DIR`. + +--- + +## 2. УРОКИ ПРО СТЕНУ (НОВОЕ — для GUIDE) + +1. **escape ≠ печать — ПОДТВЕРЖДЕНО кодом.** `enforce-judge-gate.sealOnWiredGo` (стр. 337) печатает + ТОЛЬКО при `verdict.wired===true && decision==='GO'`. Escape-ветка (316-325) возвращает `block:false`, + но `sealOnWiredGo` НЕ зовёт → печати нет. Значит escape на запись плана разблокирует судью, но режим + исполнения не открывает. Печать плана дополнительно требует валидный `mentor-GO` (freeze-gate 350-353). +2. **Баг наставника H4 — структурный, не сходится.** Наставник (`onPlanWrite`) видит только `steps-json` + (`{op,object,ref}`), НЕ тело плана/шаблоны/код. Поэтому требует «покажи шаблон/код» (формат не несёт) и + «создай заглушки для отсутствующих модулей» — даже когда модули СУЩЕСТВУЮТ (выполнить = перезаписать + рабочее). 4 круга L1-арбитража, требования растут. Раздел `## Переговоры` парсится + (`parseNegotiationSection`), но вердикт наставника от него не меняется. Лечение — **owner escape-per-step**. +3. **Escape-per-step — рабочий канал исполнения без печати.** Супрем-гейт (М2) чтит escape-грант в + разговорном режиме: на КАЖДЫЙ Edit/Write/Bash — отдельный одноразовый грант (`canonicalAction`, + окно 5 мин, гасится `floor-escape-consume`). Метка `FLOOR-ESCAPE: write:<путь>` / `bash:<команда>` в + опции AskUserQuestion → клик владельца подписывает. `replace_all`-батчинг сокращает число грантов. +4. **Вердикты читаются Grep'ом, не Read.** `~/.claude/runtime/{judge-verdicts,seal-attempts}.jsonl` — + hard-deny на Read (`enforce-read-path-deny §3.1`), но Grep их видит. `seal-attempts.jsonl` несёт + `wired/decision/sealed/reason/at` на каждую попытку — точная диагностика GO / NO-GO / degraded. +5. **deepseek-v4-pro миграция (НЕ моя, пре-существующая).** `tools/router-config.mjs` изменён + (`CLASSIFIER_MODEL='deepseek-v4-pro'`, `HEAVY_LLM_TIMEOUT_MS=300000`). 3 теста свода красные + (`router-config.test` ждёт sonnet; `enforce-mentor-on-plan-write` ждёт timeout 90000; + `enforce-judge-gate` parse). Это незавершённая миграция модели — обновить тесты под deepseek (follow-up). + +--- + +## 3. Остаток Task 7 — FOLLOW-UP (design-вопрос) + +| Потребитель | Ключ | Почему follow-up | +|---|---|---| +| `cross-ref-checker` | normative_files | Нужен map из **5** version-tracked файлов (+CLAUDE.md +MEMORY.md); config = плоский список 3 → прокинуть наивно = регрессия (перестанет проверять CLAUDE/MEMORY). | +| `l1-watcher` | normative_files | Нужен **один** Tooling-путь, не список. | +| `registry-render` / `observer-transcript-parser` / `shell-content-rules` | normative_files | Свои нужды; менять список влияет на всех. | +| `router-classifier` / `brain-retro-opus-reviewer` | classifier_context | В пути стены (mentor/judge зовут classify); дефолт уже Лидерра (backward-compat сохранён). Правка callers рискованна. | + +**Design-вопрос:** ключ `normative_files` смоделирован плоским списком, но потребители ждут РАЗНЫЕ +формы/подмножества. Нужно решение модели: (а) расширить config (отдельные ключи tooling_path / +version_tracked_files), или (б) оставить эти CLI на своих дефолтах (claude-brain-контекст), или +(в) per-consumer view над одним списком. **НЕ решать наугад** (правило #1/#6). + +Плюс: обновить 3 теста под deepseek-v4-pro миграцию (см. §2 п.5). + +--- + +## 4. Скилл-цепочка (как раньше) + +using-superpowers → writing-plans (спека+план) → executing-plans ИНЛАЙН + test-driven-development → +escape для памяти/правок → systematic-debugging для трения судьи → verification-before-completion. +В этой сессии печать не использовалась (наставник H4) — исполнение через owner escape-per-step. + +--- + +## 5. Состояние config-seam Фазы 1 + +| Ключ | Live-хуки | CLI | +|---|---|---| +| `state_dir` | ✅ cost-stop, observer-stop | ✅ status-md-generator | +| `project_url_whitelist` | ✅ enforce-mcp-classification | (commit-scanner — мёртв в claude-brain) | +| `protected_paths` | ✅ enforce-normative-content-rules | — | +| `normative_files` | (norm-content использует protected_paths) | ⏳ cross-ref, l1 (follow-up §3) | +| `classifier_context` | ⏳ router-classifier (путь стены, follow-up) | ⏳ brain-retro-opus-reviewer | +| `registry_path` | уже параметр (loadRegistry) | wiring потребителей — follow-up | + +**Коммиты сессии:** `165ff3a` (батч A-C) + status-md (dcc14f83). Оба в терминале владельца. diff --git a/docs/superpowers/specs/2026-06-15-normative-files-config-model-design.md b/docs/superpowers/specs/2026-06-15-normative-files-config-model-design.md new file mode 100644 index 0000000..fcd4800 --- /dev/null +++ b/docs/superpowers/specs/2026-06-15-normative-files-config-model-design.md @@ -0,0 +1,78 @@ +# Дизайн: модель `normative_files` + wiring cross-ref/l1 (Task 7 follow-up) + +**Дата:** 2026-06-15 +**Статус:** Approved (brainstorming, владелец одобрил) +**Контекст:** Task 7 (config-seam Фазы 1) закрыл живые хуки + status-md (`165ff3a` + `dcc14f83`). +Остаток — CLI-потребители `normative_files`, которые вскрыли смысловую тонкость (handoff №5 §3). + +--- + +## 1. Проблема + +Ключ `normative_files` в `.claude/brain.local.md` смоделирован плоским списком (3 пути: +Pravila/PSR/Tooling), но потребители ждут РАЗНОЕ: + +| Потребитель | Нужный набор | +|---|---| +| `cross-ref-checker` | 5 version-tracked файлов (3 ядра + `CLAUDE.md` + `MEMORY.md`) — сверка версий | +| `l1-watcher` | ОДИН файл-справочник инструментов (`docs/Tooling_v8_3.md`) | +| `shell-content-rules` / `observer-transcript-parser` | 3 ядра, по regex-именам (greenfield-hardening — отложено) | + +Наивно прокинуть config-список (3) в cross-ref → регрессия (перестанет сверять CLAUDE/MEMORY). + +## 2. Корневое решение: два рода документов + +- **Проектные** — Pravila/PSR/Tooling: у каждого проекта свои → живут в настройке (`normative_files`). +- **Универсальные** — `CLAUDE.md`/`MEMORY.md`: есть у ЛЮБОГО проекта, имена фиксированы → **встроены + в проверки**, НЕ в настройке. + +## 3. Модель настройки + +- `normative_files` — список проектных нормативных доков. Дефолт claude-brain = 3 (уже есть). +- `tool_registry_path` — **новый ключ**, один путь к справочнику инструментов. Дефолт `docs/Tooling_v8_3.md`. + +## 4. Компоненты + +### 4.1. cross-ref-checker +- Константа `UNIVERSAL_VERSION_TRACKED = ['CLAUDE.md', 'MEMORY.md']` (встроена). +- `loadFiles(root, normativeFiles)` и CLI строят набор = `normative_files` (config) **∪** universal. +- `detectMismatches` уже принимает `normativeFiles` (config-шов с Task 4) — CLI передаёт собранный набор. +- Для claude-brain: 3 ∪ 2 = те же 5 → **поведение байт-в-байт** (backward-compat инвариант). +- **Граница (явная):** `CROSS_REF_RE`/`PATH_TO_NAME` (имена для матча cross-ref'ов в тексте) пока + остаются дефолтными (5 известных имён). cross-ref становится config-driven по *путям-файлам*; + генерация regex-имён из config (для greenfield с другими именами) — отложенный заход (§7). + +### 4.2. l1-watcher +- `loadInputs(projectRoot, toolRegistryPath)` читает `tool_registry_path` (дефолт Tooling). +- **Fail-safe:** путь пуст / файла нет → l1 **пропускает** (возвращает «нет дрейфа»), НЕ флагует все + включённые плагины как «отсутствующие в справочнике». Иначе greenfield без справочника ложно падал бы. + +### 4.3. brain.local.md +- Добавить `tool_registry_path: docs/Tooling_v8_3.md`. `normative_files` (3) — без изменений. + +## 5. Fail-safe (спек §5.1 стиль) + +| Ключ | При отсутствии/пустоте | Направление | +|---|---|---| +| `normative_files` | пусто → cross-ref сверяет только universal (CLAUDE/MEMORY); shell/observer — дефолт | safe degrade | +| `tool_registry_path` | пусто / файла нет → l1 **skip** (нет ложного дрейфа) | safe skip | + +## 6. Тесты (TDD) + +- `cross-ref-checker.test.mjs` — `loadFiles`/CLI собирают `normative_files ∪ universal`; кастомный + `normativeFiles` даёт кастомный набор; дефолт = 5 (backward-compat). +- `l1-watcher.test.mjs` — `loadInputs` принимает `toolRegistryPath`; пустой/несуществующий → skip + (`missingInTooling` пуст независимо от enabledPlugins). + +## 7. Follow-up (вне этой спеки) + +- Генерация regex-имён cross-ref / `shell-content-rules` / `observer-transcript-parser` из config + (greenfield с произвольными именами доков). Подход #2 из brainstorming. +- `classifier_context` wiring (`router-classifier`/`brain-retro-opus-reviewer`) — путь стены. +- deepseek-v4-pro тест-дрейф (3 теста, пре-существующая миграция). +- Затем Фаза 2 (plugin packaging, design v6 §14). + +## 8. Канон + +design v6 + план Фазы 1 + handoff №5 (`...session-handoff-5.md`). Исполнение — под стеной +escape-per-step (наставник H4 структурный, печать не встаёт; handoff №5 §2).