diff --git a/docs/superpowers/plans/2026-06-16-verdict-visibility-sp1-plan.md b/docs/superpowers/plans/2026-06-16-verdict-visibility-sp1-plan.md new file mode 100644 index 0000000..85dca66 --- /dev/null +++ b/docs/superpowers/plans/2026-06-16-verdict-visibility-sp1-plan.md @@ -0,0 +1,475 @@ +# SP1 — громкая видимость вердикта — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans (ИНЛАЙН — субагенты стена +> запрещает, VA-4). Steps — checkbox (`- [ ]`). Под стеной H4: новые файлы — escape+override; врезки — +> терминал владельца (F-K). Полный свод + коммит — терминал владельца. + +**Goal:** Громко показывать контроллеру ВСЕ исходы вердикта судьи/наставника (GO/NO-GO/degraded/skip) на +следующем ходе — баннер в UserPromptSubmit.additionalContext + обязательное подтверждение `вердикт:` + +Stop-страж, ловящий пропуск. + +**Architecture:** 4 новых файла `tools/` (чистые verdict-line + I/O стор + UPS-хук показа + Stop-хук +подтверждения) + врезки `pushVerdict` в существующие судью/наставника. Паттерны brain: Stop-хук — +`exitDisciplineDecision`+чистая `decide` (как `enforce-coverage-verify`); эмиссия — +`hookSpecificOutput`; стор — `~/.claude/runtime` пер-сессия, fail-quiet. + +**Tech Stack:** Node ESM (`.mjs`, стиль `tools/`), vitest. Без внешних зависимостей. + +**Спек:** `docs/superpowers/specs/2026-06-16-verdict-visibility-sp1-design.md`. + +--- + +## File Structure + +- Create `tools/verdict-outcome-line.mjs` — чистые: `classifyJudgeOutcome`, `buildVerdictBanner`, `parseVerdictAck`. +- Create `tools/verdict-surface-store.mjs` — I/O: `pushVerdict`/`drainVerdicts` + `markSurfaced`/`readPendingAck`/`clearPendingAck`. +- Create `tools/enforce-verdict-surface.mjs` — UPS-хук: `buildSurfaceOutput` (чистая) + `main`. +- Create `tools/enforce-verdict-ack.mjs` — Stop-хук: `decide` (чистая) + `main`. +- Create соответствующие `.test.mjs` к каждому. +- **Терминал (F-K):** врезки `pushVerdict` в `enforce-judge-gate.mjs` (`runJudgeGate`) + mentor-пути; регистрация хуков в `settings.json`. + +--- + +## Task 1: `verdict-outcome-line.mjs` — чистые verdict-line функции + +**Files:** Create `tools/verdict-outcome-line.mjs`, `tools/verdict-outcome-line.test.mjs` + +- [ ] **Step 1: Failing tests** + +```javascript +import { describe, it, expect } from 'vitest'; +import { classifyJudgeOutcome, buildVerdictBanner, parseVerdictAck } from './verdict-outcome-line.mjs'; + +describe('classifyJudgeOutcome', () => { + it('GO / NO-GO / degraded / skip', () => { + expect(classifyJudgeOutcome({ wired: true, decision: 'GO' })).toBe('GO'); + expect(classifyJudgeOutcome({ wired: true, decision: 'NO-GO' })).toBe('NO-GO'); + expect(classifyJudgeOutcome({ wired: false })).toBe('degraded'); + expect(classifyJudgeOutcome(null)).toBe('skip'); + }); +}); +describe('buildVerdictBanner', () => { + it('баннер с гейтом и дословной причиной', () => { + const b = buildVerdictBanner({ outcome: 'NO-GO', gate: 'judge', round: 1, version: 1, reason: 'шаг 3 не проверяем' }); + expect(b).toContain('🚫 NO-GO [judge] круг 1 v1'); + expect(b).toContain('причина (дословно): шаг 3 не проверяем'); + }); + it('опускает круг/версию если не заданы', () => { + const b = buildVerdictBanner({ outcome: 'GO', gate: 'mentor' }); + expect(b).toContain('✅ GO [mentor]'); + expect(b).not.toContain('круг'); + }); +}); +describe('parseVerdictAck', () => { + it('ловит `вердикт:` первой строкой', () => { + expect(parseVerdictAck('вердикт: NO-GO\nдалее...')).toBe(true); + expect(parseVerdictAck('обычный ответ')).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Run — verify FAIL** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/verdict-outcome-line.test.mjs` +Expected: FAIL «is not a function». + +- [ ] **Step 3: Implement** + +```javascript +#!/usr/bin/env node +/** verdict-outcome-line — чистые функции отображения вердикта (SP1, спек 2026-06-16-verdict-visibility). */ + +const EMOJI = { 'GO': '✅', 'NO-GO': '🚫', 'degraded': '⚠', 'skip': '⤳' }; +const BAR = '━'.repeat(36); + +/** Сырой вердикт судьи/наставника → исход. wired:false = degraded; иначе по decision. */ +export function classifyJudgeOutcome(verdict) { + if (!verdict || typeof verdict !== 'object') return 'skip'; + if (verdict.wired === false) return 'degraded'; + const d = String(verdict.decision || '').toUpperCase().replace(/\s+/g, ''); + if (d === 'GO') return 'GO'; + if (d === 'NO-GO' || d === 'NOGO') return 'NO-GO'; + return 'skip'; +} + +/** Баннер. round/version опускаются при null/undefined (SP1 до памяти кругов). reason — дословно. */ +export function buildVerdictBanner({ outcome, gate, round, version, reason } = {}) { + const oc = EMOJI[outcome] ? outcome : 'skip'; + const head = [ + `${EMOJI[oc]} ${oc}`, + gate ? `[${gate}]` : '', + round != null ? `круг ${round}` : '', + version != null ? `v${version}` : '', + ].filter(Boolean).join(' '); + const lines = [BAR, head]; + if (reason) lines.push(`причина (дословно): ${reason}`); + lines.push(BAR); + return lines.join('\n'); +} + +/** true, если в первых строках текста есть подтверждение `вердикт: `. */ +export function parseVerdictAck(text) { + return /^\s*вердикт:\s*(GO|NO-GO|NOGO|degraded|skip)\b/im.test(String(text || '')); +} +``` + +- [ ] **Step 4: Run — verify PASS** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/verdict-outcome-line.test.mjs` +Expected: PASS. + +- [ ] **Step 5: Commit (терминал владельца, формат-сохраняющий)** + +```bash +git add tools/verdict-outcome-line.mjs tools/verdict-outcome-line.test.mjs +git commit -m "feat(verdict-surface): чистые verdict-line функции (SP1 Task 1)" +``` + +--- + +## Task 2: `verdict-surface-store.mjs` — пер-сессионный стор + pending-ack + +**Files:** Create `tools/verdict-surface-store.mjs`, `tools/verdict-surface-store.test.mjs` + +- [ ] **Step 1: Failing tests** (стор + pending-ack, через временный baseDir) + +```javascript +import { describe, it, expect } from 'vitest'; +import { mkdtempSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { pushVerdict, drainVerdicts, markSurfaced, readPendingAck, clearPendingAck } from './verdict-surface-store.mjs'; + +const dir = () => mkdtempSync(join(tmpdir(), 'vsurf-')); + +describe('verdict-surface-store', () => { + it('push → drain one-shot (drain чистит)', () => { + const d = dir(); + pushVerdict('s1', { outcome: 'NO-GO', gate: 'judge' }, d); + expect(drainVerdicts('s1', d)).toHaveLength(1); + expect(drainVerdicts('s1', d)).toHaveLength(0); + }); + it('пер-сессия: чужие не видны', () => { + const d = dir(); + pushVerdict('s1', { outcome: 'GO' }, d); + expect(drainVerdicts('s2', d)).toHaveLength(0); + }); + it('pending-ack: mark → read → clear', () => { + const d = dir(); + markSurfaced('s1', ['NO-GO'], d); + expect(readPendingAck('s1', d)).toEqual(['NO-GO']); + clearPendingAck('s1', d); + expect(readPendingAck('s1', d)).toBeNull(); + }); + it('fail-quiet: битый baseDir не кидает', () => { + expect(() => drainVerdicts('s1', '\0bad')).not.toThrow(); + expect(drainVerdicts('s1', '\0bad')).toEqual([]); + }); +}); +``` + +- [ ] **Step 2: Run — verify FAIL** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/verdict-surface-store.test.mjs` +Expected: FAIL. + +- [ ] **Step 3: Implement** + +```javascript +#!/usr/bin/env node +/** verdict-surface-store — пер-сессионный транзиентный стор вердиктов + pending-ack маркер (SP1). + * Fail-quiet: любая ошибка → безопасный no-op/пусто. Дефолт baseDir = ~/.claude/runtime. */ +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; + +function baseOf(baseDir) { return baseDir || join(homedir(), '.claude', 'runtime'); } +function queuePath(sid, baseDir) { return join(baseOf(baseDir), `verdict-surface-${sid || 'unknown'}.json`); } +function ackPath(sid, baseDir) { return join(baseOf(baseDir), `verdict-pending-ack-${sid || 'unknown'}.json`); } +function readJson(p) { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } } +function ensureDir(p) { try { mkdirSync(baseOf(p), { recursive: true }); } catch {} } + +export function pushVerdict(sid, obj, baseDir) { + try { + ensureDir(baseDir); + const p = queuePath(sid, baseDir); + let arr = existsSync(p) ? readJson(p) : []; + if (!Array.isArray(arr)) arr = []; + arr.push(obj); + writeFileSync(p, JSON.stringify(arr)); + return true; + } catch { return false; } +} + +export function drainVerdicts(sid, baseDir) { + try { + const p = queuePath(sid, baseDir); + if (!existsSync(p)) return []; + const arr = readJson(p); + try { writeFileSync(p, '[]'); } catch {} + return Array.isArray(arr) ? arr : []; + } catch { return []; } +} + +export function markSurfaced(sid, outcomes, baseDir) { + try { ensureDir(baseDir); writeFileSync(ackPath(sid, baseDir), JSON.stringify(outcomes || [])); return true; } + catch { return false; } +} + +export function readPendingAck(sid, baseDir) { + try { + const p = ackPath(sid, baseDir); + if (!existsSync(p)) return null; + const v = readJson(p); + return Array.isArray(v) ? v : null; + } catch { return null; } +} + +export function clearPendingAck(sid, baseDir) { + try { const p = ackPath(sid, baseDir); if (existsSync(p)) writeFileSync(p, 'null'); return true; } + catch { return false; } +} +``` + +- [ ] **Step 4: Run — verify PASS** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/verdict-surface-store.test.mjs` +Expected: PASS. + +- [ ] **Step 5: Commit (терминал)** + +```bash +git add tools/verdict-surface-store.mjs tools/verdict-surface-store.test.mjs +git commit -m "feat(verdict-surface): пер-сессионный стор + pending-ack (SP1 Task 2)" +``` + +--- + +## Task 3: `enforce-verdict-surface.mjs` — UPS-хук показа + +**Files:** Create `tools/enforce-verdict-surface.mjs`, `tools/enforce-verdict-surface.test.mjs` + +- [ ] **Step 1: Failing test** (чистая `buildSurfaceOutput`) + +```javascript +import { describe, it, expect } from 'vitest'; +import { buildSurfaceOutput } from './enforce-verdict-surface.mjs'; + +describe('buildSurfaceOutput', () => { + it('пусто → null (тихо)', () => { + expect(buildSurfaceOutput([])).toBeNull(); + }); + it('баннер(ы) + императив подтверждения', () => { + const out = buildSurfaceOutput([{ outcome: 'NO-GO', gate: 'judge', reason: 'x' }]); + expect(out).toContain('🚫 NO-GO [judge]'); + expect(out).toContain('ПЕРВОЙ строкой ответа подтверди'); + expect(out).toContain('`вердикт:'); + }); +}); +``` + +- [ ] **Step 2: Run — verify FAIL** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/enforce-verdict-surface.test.mjs` +Expected: FAIL. + +- [ ] **Step 3: Implement** (чистая + main, эмиссия как subagent-prompt-prefix) + +```javascript +#!/usr/bin/env node +/** enforce-verdict-surface — UserPromptSubmit. Дренит вердикты сессии → громкий баннер + + * императив подтверждения в additionalContext. Помечает pending-ack для Stop-стража. + * Fail-quiet: ошибка → пустой вывод, никогда не роняет ход. (SP1) */ +import { drainVerdicts, markSurfaced } from './verdict-surface-store.mjs'; +import { buildVerdictBanner } from './verdict-outcome-line.mjs'; + +const IMPERATIVE = 'ПЕРВОЙ строкой ответа подтверди: `вердикт: ` — иначе считается пропущенным.'; + +/** Чистая: список вердиктов → текст additionalContext, либо null если пусто. */ +export function buildSurfaceOutput(verdicts) { + if (!Array.isArray(verdicts) || verdicts.length === 0) return null; + return verdicts.map(buildVerdictBanner).join('\n') + '\n' + IMPERATIVE; +} + +async function readStdin() { let b = ''; for await (const c of process.stdin) b += c; return b; } +function emitNone() { process.stdout.write('{}'); } + +async function main() { + try { + const ev = JSON.parse(await readStdin()); + const sid = ev.session_id || ev.sessionId || 'unknown'; + const verdicts = drainVerdicts(sid); + const out = buildSurfaceOutput(verdicts); + if (!out) return emitNone(); + markSurfaced(sid, verdicts.map((v) => v.outcome)); + process.stdout.write(JSON.stringify({ + hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: out }, + })); + } catch { emitNone(); } +} + +const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-verdict-surface.mjs'); +if (isCli) main(); +``` + +- [ ] **Step 4: Run — verify PASS** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/enforce-verdict-surface.test.mjs` +Expected: PASS. + +- [ ] **Step 5: Commit (терминал)** + +```bash +git add tools/enforce-verdict-surface.mjs tools/enforce-verdict-surface.test.mjs +git commit -m "feat(verdict-surface): UPS-хук показа баннера + императив (SP1 Task 3)" +``` + +--- + +## Task 4: `enforce-verdict-ack.mjs` — Stop-хук подтверждения + +**Files:** Create `tools/enforce-verdict-ack.mjs`, `tools/enforce-verdict-ack.test.mjs` + +- [ ] **Step 1: Failing test** (чистая `decide`) + +```javascript +import { describe, it, expect } from 'vitest'; +import { decide } from './enforce-verdict-ack.mjs'; + +describe('enforce-verdict-ack decide', () => { + it('есть pending-ack + нет `вердикт:` → block', () => { + const r = decide({ pendingAck: ['NO-GO'], assistantText: 'обычный ответ' }); + expect(r.block).toBe(true); + expect(r.message).toContain('вердикт'); + }); + it('есть pending-ack + есть `вердикт:` → ok', () => { + expect(decide({ pendingAck: ['NO-GO'], assistantText: 'вердикт: NO-GO\n...' }).block).toBe(false); + }); + it('нет pending-ack → тихо', () => { + expect(decide({ pendingAck: null, assistantText: 'что угодно' }).block).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Run — verify FAIL** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/enforce-verdict-ack.test.mjs` +Expected: FAIL. + +- [ ] **Step 3: Implement** (паттерн `enforce-coverage-verify`: exitDisciplineDecision + decide; маркер чистится при ack) + +```javascript +#!/usr/bin/env node +/** enforce-verdict-ack — Stop-хук. Если на ходе был surfaced-вердикт (pending-ack), а в ответе нет + * строки `вердикт:` — громкая претензия (маркер остаётся → нагнетает до подтверждения). При ack — + * чистит маркер. Fail-quiet через exitDisciplineDecision. (SP1) */ +import { readStdin, parseEventJson, readTranscript, lastAssistantText, exitDisciplineDecision } from './enforce-hook-helpers.mjs'; +import { parseVerdictAck } from './verdict-outcome-line.mjs'; +import { readPendingAck, clearPendingAck } from './verdict-surface-store.mjs'; + +/** Чистая: решение по наличию pending-ack и факту подтверждения в тексте. */ +export function decide({ pendingAck, assistantText }) { + if (!pendingAck || pendingAck.length === 0) return { block: false }; + if (parseVerdictAck(assistantText)) return { block: false, acked: true }; + return { + block: true, + message: [ + `[enforce-verdict-ack] на ходе был показан вердикт (${pendingAck.join(', ')}), но ответ его не подтвердил.`, + 'ПЕРВОЙ строкой следующего ответа: `вердикт: ` (повтори исход из баннера).', + ].join('\n'), + }; +} + +async function main() { + const ev = parseEventJson(await readStdin()); + const sid = ev.session_id || ev.sessionId || 'unknown'; + await exitDisciplineDecision( + () => { + const transcript = readTranscript(ev.transcript_path); + const assistantText = lastAssistantText(transcript); + const pendingAck = readPendingAck(sid); + const r = decide({ pendingAck, assistantText }); + if (r.acked) clearPendingAck(sid); + return r; + }, + { label: 'enforce-verdict-ack' }, + ); +} + +const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-verdict-ack.mjs'); +if (isCli) main(); +``` + +- [ ] **Step 4: Run — verify PASS** + +Run: `npx vitest run --config vitest.config.tools.mjs tools/enforce-verdict-ack.test.mjs` +Expected: PASS. + +- [ ] **Step 5: Commit (терминал)** + +```bash +git add tools/enforce-verdict-ack.mjs tools/enforce-verdict-ack.test.mjs +git commit -m "feat(verdict-surface): Stop-страж подтверждения вердикта (SP1 Task 4)" +``` + +--- + +## Task 5: Врезки + регистрация хуков — ТЕРМИНАЛ ВЛАДЕЛЬЦА (F-K) + +**Files (терминал, не под стеной):** Modify `tools/enforce-judge-gate.mjs`, mentor-пути (`tools/mentor-verdict.mjs`/`tools/enforce-mentor-on-plan-write.mjs`), `.claude/settings.json` + +> **Канал — терминал (F-K):** правка discipline-source (`enforce-judge-/mentor-`) под стеной рубится +> `enforce-normative-content-rules §6` и floor-escape не зачитывается (Лидерра, 3 провала). Применять +> node string-replace по якорям + **сразу коммит** (иначе git restore откатит — инцидент Лидерры). + +- [ ] **Step 1: Врезка в судью.** В `enforce-judge-gate.mjs`, в точке, где вычислен `verdict` (рядом с + `sealOnWiredGo`/возврат вердикта в `runJudgeGate`), добавить: + +```javascript +import { classifyJudgeOutcome } from './verdict-outcome-line.mjs'; +import { pushVerdict } from './verdict-surface-store.mjs'; +// ...после вычисления verdict, перед возвратом: +try { + pushVerdict(sessionId, { + outcome: classifyJudgeOutcome(verdict), + gate: 'judge', + round: verdict.round ?? null, // SP2 наполнит; SP1 → null + version: verdict.version ?? null, + reason: verdict.reason || verdict.objection || '', + }); +} catch { /* fail-quiet */ } +``` + +- [ ] **Step 2: Врезка в наставника** (симметрично, `gate: 'mentor'`) в mentor-verdict/onPlanWrite/onSpecWrite пути. + +- [ ] **Step 3: Регистрация хуков в `.claude/settings.json`** — `enforce-verdict-surface` на `UserPromptSubmit`, `enforce-verdict-ack` на `Stop`. Команды `node tools/enforce-verdict-surface.mjs` / `node tools/enforce-verdict-ack.mjs`. (В Фазе 2 плагина → перенос в `hooks/hooks.json` с `${CLAUDE_PLUGIN_ROOT}`.) + +- [ ] **Step 4: Полный свод + интеграционная проверка живым** (терминал): `npx vitest run --config vitest.config.tools.mjs`; затем спровоцировать вердикт (план/спека) → на следующем ходе должен всплыть баннер + требование `вердикт:`. + +- [ ] **Step 5: Commit (терминал)** + +```bash +git add tools/enforce-judge-gate.mjs tools/mentor-verdict.mjs tools/enforce-mentor-on-plan-write.mjs .claude/settings.json +git commit -m "feat(verdict-surface): врезки judge/mentor + регистрация хуков (SP1 Task 5)" +``` + +--- + +## Self-Review (выполнено при написании) + +- **Покрытие спека:** §2.1 classifyJudgeOutcome → T1; §2.2 баннер → T1; §2.3 стор → T2; §2.4 UPS-показ → T3; + §2.5 Stop-страж → T4; врезки §2.6 → T5; громкость (баннер+императив+страж) §4 → T1/T3/T4; все исходы §1 → + T1 классификатор; verbatim причина §4 → buildVerdictBanner; SP2-ready round/version → T1/T5 (null в SP1); + fail-safe §5 → fail-quiet во всех I/O; F-K §7 → T5 терминал. +- **Заглушки:** реальный код во всех new-file шагах; врезки — точный сниппет + точка (T5 owner-terminal, + без точных номеров строк — discipline-source правит владелец по якорю). +- **Согласованность имён:** `pushVerdict`/`drainVerdicts`/`markSurfaced`/`readPendingAck`/`clearPendingAck`/ + `classifyJudgeOutcome`/`buildVerdictBanner`/`parseVerdictAck`/`buildSurfaceOutput`/`decide` — едины T1-T5; + маркер `вердикт:` един (surface императив / ack parser / страж message). +- **Граница:** round/version проброшены, но в SP1 = null (память кругов — SP2). + +## Execution Handoff (под стеной) + +Субагенты запрещены (VA-4) → только ИНЛАЙН через `executing-plans`. T1-T4 — новые файлы: тесты `.test.mjs` +(floor-escape), production `.mjs` (override `ремонт инфраструктуры` + floor-escape per-file). T5 — терминал +владельца (F-K). Полный свод + формат-сохраняющие коммиты — терминал владельца. diff --git a/docs/superpowers/specs/2026-06-16-brain-as-plugin-session-handoff-7.md b/docs/superpowers/specs/2026-06-16-brain-as-plugin-session-handoff-7.md new file mode 100644 index 0000000..8daa999 --- /dev/null +++ b/docs/superpowers/specs/2026-06-16-brain-as-plugin-session-handoff-7.md @@ -0,0 +1,69 @@ +# Brain-as-plugin + arbitration-machinery — handoff сессии №7 (закрытие 2026-06-16) + +**Кодовая фраза стены:** «роутер-наставник». **Каноны:** `2026-06-15-brain-as-plugin-design-v6.md` (§14 фазы); +полный цикл переговоров — `2026-06-16-full-negotiation-cycle-design-for-brain.md`; журнал Лидерры — +`2026-06-16-arbitration-fixes-worklog-for-brain.md`. Предыдущий handoff: №6. + +--- + +## 1. Что сделано (сессия №7) + +| # | Что | Коммит | +|---|---|---| +| #3-observer | `classifyFilePath(path, normativeStems)` config-driven (закрыл весь greenfield #3) | `318add8` (свод 224 passed) | +| Фаза 2 Спек 1 | `.claude-plugin/plugin.json` + `marketplace.json` + `agents/reviewer-agent.md` (neutral) + GUIDE-cleaning | `29d2dd3` | +| Память | `project-brain-plugin-phase1-progress` обновлена (Фаза 1 закрыта, Фаза 2 старт) | — | +| SP1 дизайн+план | спек+план видимости вердикта (НЕ исполнено) | этот коммит | + +**Коммиты через Claude** в claude-brain работают: floor-escape `bash:`, НО сообщение **paren-free** +(floor режет `()` → `feat:` вместо `feat(scope):`), явные пути `git commit -- <пути>`. Формат-сохраняющий +коммит — терминал владельца. + +## 2. НОВЫЙ ЭПИК — машинерия переговоров/арбитража в плагин + +Владелец: «реализовать ВСЁ в плагине» (машинерия стены = ядро движка, слой 1). Brain на **до-фиксовом +базовом** состоянии — отсутствуют `round-memory-store`/`seal-override`/`verdict-surface-*`/`controller-negotiation`, +нет priorObjections-врезок. Нужно перенести/построить целиком. **Декомпозиция (согласована):** + +- **SP1 — громкая видимость вердикта** (дизайн+план готовы, см. §4). Standalone, первый. +- **SP2 — полный цикл переговоров** (`full-negotiation-cycle-design`): M/J-дорожки, версии+diff, M-после-J + возврат, потолки ≤3, **симметрия спека/план вкл. спека-путь наставника** (тот «не реализованный момент»). +- **SP3 — Ф3 owner-seal арбитраж** (`seal-override` + `sealOnWiredGo` owner-ветка + arbitration-card AskUser + выход на любом круге). +- **SP4 — баги/enablers:** F-J (рассинхрон указателя supreme-gate при со-хук-блоке), F-K (правка + discipline-source под стеной недостижима + helper `print-canonical-action`). + +## 3. Уроки стены №7 + +1. **Коммит через Claude в claude-brain доступен** floor-escape `bash:` (router-gate не подключён, + criterion-gate чтит escape) — но **paren-free** сообщение. Формат `feat(scope):` сохраняет только терминал. +2. **F-K: правку discipline-source (`enforce-judge-/mentor-/judge-engine`) под стеной не сделать** — + рубит `enforce-normative-content-rules §6`, floor-escape `write:` не зачитывается (Лидерра, 3 провала). + → врезки в машинерию = **терминал владельца** (node string-replace по якорю + СРАЗУ коммит, иначе git restore откатит). +3. **plugin-dev/executing-plans грузятся через `skill:`-escape**; brainstorming/writing-plans/TDD — свободно. +4. Схема плагина: компоненты — папки авто-дискавери; минимальный `plugin.json` = `name`; docs едут файлами неявно. + +## 4. Остаток (порядок) + +1. **SP1 исполнить** (план `plans/2026-06-16-verdict-visibility-sp1-plan.md`): T1-T4 под стеной (новые файлы — + override `ремонт инфраструктуры` + floor-escape per-file, TDD), **T5 врезки+регистрация — терминал (F-K)**. + 5 узлов: `verdict-outcome-line` (classifyJudgeOutcome/buildVerdictBanner/parseVerdictAck) + + `verdict-surface-store` (push/drain + pending-ack) + `enforce-verdict-surface` (UPS) + `enforce-verdict-ack` (Stop) + врезки. +2. **SP2 → SP3 → SP4** (каждый: brainstorming-адопция дизайна Лидерры → план → исполнение; машинерия — терминал). +3. **Фаза 2 Спек 2/2B** плагина (провод компонентов, группы §6, `${CLAUDE_PLUGIN_ROOT}`, инлайн-хуки, `/brain-migrate`). +4. Фазы 3-5 (persist улик / dogfood / greenfield). + +## 5. Промт для сессии №8 (копировать целиком) + +``` +Продолжаем claude-brain. Кодовая фраза стены — «роутер-наставник», ПО ПРАВИЛАМ (authoring-канал для новых .md в docs/superpowers/{specs,plans}; escape/память — только с моего разрешения; субагенты под стеной запрещены — инлайн; коммит paren-free через floor-escape ИЛИ формат-сохраняющий в моём терминале; полный свод — в моём терминале). + +Прочитай (разговорный режим): handoff №7 (specs/2026-06-16-brain-as-plugin-session-handoff-7.md), память project-brain-plugin-phase1-progress, дизайн+план SP1 (specs/2026-06-16-verdict-visibility-sp1-design.md + plans/2026-06-16-verdict-visibility-sp1-plan.md), full-negotiation-cycle-design + arbitration-fixes-worklog, router-mentor-wall-GUIDE. + +Контекст: новый эпик — машинерия переговоров/арбитража в плагин (brain на до-фиксовом состоянии). Декомпозиция SP1 видимость / SP2 полный цикл / SP3 owner-seal / SP4 F-J/F-K. SP1 дизайн+план готовы. + +ВАЖНО F-K: правка discipline-source (enforce-judge-/mentor-/judge-engine) под стеной недостижима (§6 не чтит escape) → врезки = ТЕРМИНАЛ. Новые файлы tools/*.mjs — под стеной (override `ремонт инфраструктуры` + floor-escape per-file, vitest-RED недостижим). Тесты .test.mjs — floor-escape. + +Сделай в начале: спроси канал памяти (escape/claude-md-management) и берём ли SP1 исполнять (T1-T4 под стеной, T5 терминал) или иное. + +Не пломбируй/не коммить без клика. Находки — в authoring-доки. +``` diff --git a/docs/superpowers/specs/2026-06-16-verdict-visibility-sp1-design.md b/docs/superpowers/specs/2026-06-16-verdict-visibility-sp1-design.md new file mode 100644 index 0000000..ec8f720 --- /dev/null +++ b/docs/superpowers/specs/2026-06-16-verdict-visibility-sp1-design.md @@ -0,0 +1,82 @@ +# Дизайн: SP1 — громкая видимость вердикта судьи/наставника (для brain-плагина) + +**Дата:** 2026-06-16 +**Статус:** Approved (brainstorming, владелец одобрил с уточнением «громко, чтобы не пропустить») +**Эпик:** перенос/реализация машинерии переговоров в brain-плагин. Под-проект **SP1** (декомпозиция: SP1 видимость / SP2 полный цикл / SP3 owner-seal / SP4 F-J/F-K). +**Источник:** Лидерра-Фикс 1 (worklog `2026-06-16-arbitration-fixes-worklog-for-brain.md`, проверен живым) + дизайн полного цикла `2026-06-16-full-negotiation-cycle-design-for-brain.md`. + +--- + +## 1. Цель и контракт + +Контроллер сейчас **слеп** к вердиктам судьи/наставника на исходах GO/skip/degraded — текст всплывает только при block (exit2); allow→`{}`, GO/skip/degraded тихие (worklog F-G: `exitDecision` отдаёт текст только при block). Из-за этого «почему печать не встала» (degraded/NO-GO наставника) контроллеру не видно. + +**Контракт:** ВСЕ исходы вердикта (`GO | NO-GO | degraded | skip`) показываются контроллеру на следующем ходе **громко** — баннер в `UserPromptSubmit.additionalContext` + **обязательное подтверждение первой строкой** + **Stop-хук-страж**, ловящий пропуск. Канал brain уже задействован (экономия/coverage инжектятся так же — проверенный, надёжный). Механизм fail-quiet: никогда не роняет UPS/Stop. + +**Граница SP1/SP2:** SP1 показывает ОДИН вердикт за раз (текущий механизм без кругов памяти). Поля `round`/`version` в строке проброшены (дефолт 1/null), наполнятся в SP2 (память кругов). SP1 НЕ трогает память/диалог/оркестрацию — только делает громким то, что уже считается. + +## 2. Архитектура — 5 узлов + врезки + +| # | Узел | Тип | Ответственность | +|---|---|---|---| +| 1 | `classifyJudgeOutcome(verdict)` | чистая | сырой вердикт судьи/наставника → enum `GO\|NO-GO\|degraded\|skip` | +| 2 | `verdict-outcome-line.mjs` `buildVerdictBanner({outcome,gate,round,version,reason})` | чистая | баннер (см. §4); `round`/`version` SP2-ready (дефолт 1/null → опускаются) | +| 3 | `verdict-surface-store.mjs` | I/O | пер-сессионный транзиентный стор: `pushVerdict(sessionId,obj)` + `drainVerdicts(sessionId)` (читает+чистит, one-shot) | +| 4 | `enforce-verdict-surface.mjs` | UPS-хук | дренит вердикты сессии → баннер + императив подтверждения в additionalContext | +| 5 | `enforce-verdict-ack.mjs` | Stop-хук | если на ходе был свежий вердикт, а в ответе нет строки `вердикт:` → громкая претензия на следующем ходе (паттерн `enforce-coverage-verify`) | +| — | **врезки** в `enforce-judge-gate` (`runJudgeGate`) + mentor-пути (`mentor-verdict`/`enforce-mentor-on-plan-write`) | правка | после вычисления вердикта зовут `classifyJudgeOutcome` + `pushVerdict`. ⚠️ **discipline-source → F-K** (см. §7) | + +**Изоляция:** узлы 1-2 чистые (без I/O, тривиально тестируются); 3 — единственный, кто знает про файловую систему стора; 4-5 — хук-обёртки (читают стор, не считают вердикт). Врезки минимальны: одна строка `pushVerdict(...)` на точку вычисления вердикта. + +## 3. Поток данных + +``` +судья/наставник считает вердикт + → врезка: classifyJudgeOutcome(verdict) → pushVerdict(sessionId, {outcome,gate,round,version,reason}) + → [стор: runtime, пер-сессия, gitignore] +следующий user-prompt + → enforce-verdict-surface (UPS): drainVerdicts(sessionId) → баннер(ы) + императив в additionalContext + → контроллер ВИДИТ баннер, ОБЯЗАН первой строкой: `вердикт: ` +конец хода + → enforce-verdict-ack (Stop): был свежий вердикт без `вердикт:`-подтверждения? → претензия +``` + +## 4. Формат баннера + подтверждение + страж + +**Баннер** (узел 2) — не строчка, а заметный блок: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚫 NO-GO [judge] круг 1 v1 +причина (дословно): <замечание судьи verbatim> +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +Эмодзи по исходу: `✅ GO` / `🚫 NO-GO` / `⚠ degraded` / `⤳ skip`. `причина` — **дословно** (verbatim, без пересказа — принцип доков §2/§3). `круг N vV` опускается, если round/version неизвестны (SP1 до памяти кругов). + +**Императив** (узел 4 добавляет к баннеру): «ПЕРВОЙ строкой ответа подтверди: `вердикт: ` — иначе считается пропущенным». + +**Страж** (узел 5): на Stop — если за ход был свежий surfaced-вердикт (по стору/маркеру), а в ответе контроллера нет строки `вердикт:` → на следующем ходе громкая претензия (как `enforce-coverage-verify`). Делает пропуск невозможным молча. + +## 5. Жизненный цикл и fail-safe + +- **One-shot:** `drainVerdicts` читает И чистит — баннер не повторяется. +- **Пер-сессия:** ключ — sessionId; чужие вердикты не видны. +- **Транзиент:** стор в state-dir brain (`loadConfig().state_dir`, дефолт), gitignore — потеря безопасна. +- **Fail-quiet:** любой сбой стора/хука → пустой additionalContext, НИКОГДА не роняет UPS/Stop (exit 0). Нет вердикта → нет баннера → нет требования подтверждения (страж молчит). +- **Ack-маркер:** факт surfacing фиксируется (для стража), подтверждение `вердикт:` гасит требование. + +## 6. Тестирование (TDD) + +Чистые узлы — RED→GREEN: `classifyJudgeOutcome` (каждый исход + неизвестный→safe); `buildVerdictBanner` (формат, verbatim-причина, опускание round/version). Стор: `pushVerdict`/`drainVerdicts` (one-shot clear, пер-сессия, fail-quiet). Хуки: `enforce-verdict-surface` (дрен→additionalContext, императив, пусто→тихо); `enforce-verdict-ack` (есть вердикт без ack→претензия; с ack→тихо; нет вердикта→тихо). Врезки — интеграционно (judge/mentor пушат верный outcome). Полный свод — терминал владельца (harness-collapse). + +## 7. Исполнение под стеной (F-K) + +Узлы 1-5 — **новые файлы** (`tools/*.mjs`); production-код → `enforce-tdd-gate` (нужен owner-override `ремонт инфраструктуры`) + floor-escape per-file. Но **врезки (узел 6)** правят **существующий discipline-source** (`enforce-judge-gate`/mentor) → их рубит `enforce-normative-content-rules §6` (ЗАКОН), и floor-escape `write:` этим гейтом НЕ зачитывается (Лидерра F-K, 3 провала) → под стеной недостижимо. **Канал врезок — терминал владельца** (как делала Лидерра: node string-replace по якорям + сразу коммит, иначе git restore откатит). Регистрация хуков 4-5 в settings.json — шаг владельца. Полный план разнесёт: новые файлы (под стеной, escape+override) vs врезки/регистрация (терминал). F-K как код-фикс — отдельный SP4 (опционально, чтобы врезки делались под стеной). + +## 8. Self-review (выполнено при написании) + +- **Покрытие:** все исходы (§1 контракт) → классификатор §2.1 + баннер §4; громкость → императив+страж §4; не-пропуск → узел 5. Verbatim-причина → §4. SP2-готовность → round/version проброшены §2.2. +- **Заглушки:** компоненты конкретны (имена/сигнатуры/ответственность); формат баннера показан; точки врезки названы. +- **Согласованность:** `pushVerdict`/`drainVerdicts`/`classifyJudgeOutcome`/`buildVerdictBanner` — единые имена; `вердикт:` — единый маркер подтверждения (узлы 4/5). +- **Граница:** SP1 не лезет в память кругов (SP2); round/version — проброшены, не наполняются.