docs: SP1 видимость вердикта — дизайн+план + handoff №7 arbitration epic

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-16 13:30:32 +03:00
parent 29d2dd3ebd
commit a885ed1b63
3 changed files with 626 additions and 0 deletions
@@ -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, если в первых строках текста есть подтверждение `вердикт: <outcome>`. */
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 = 'ПЕРВОЙ строкой ответа подтверди: `вердикт: <outcome>` — иначе считается пропущенным.';
/** Чистая: список вердиктов → текст 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(', ')}), но ответ его не подтвердил.`,
'ПЕРВОЙ строкой следующего ответа: `вердикт: <outcome>` (повтори исход из баннера).',
].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). Полный свод + формат-сохраняющие коммиты — терминал владельца.
@@ -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:<git>`, НО сообщение **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:<git>` (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-доки.
```
@@ -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
→ контроллер ВИДИТ баннер, ОБЯЗАН первой строкой: `вердикт: <outcome>`
конец хода
→ 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 добавляет к баннеру): «ПЕРВОЙ строкой ответа подтверди: `вердикт: <outcome>` — иначе считается пропущенным».
**Страж** (узел 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 — проброшены, не наполняются.