From 46c1b2301da6c10c8c1bc1b4b6278f4fd6442ce3 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: Wed, 17 Jun 2026 16:44:55 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20E-S1=20=D0=A4=D0=B0=D0=B7=D0=B0=202b=20?= =?UTF-8?q?=D1=81=D1=83=D0=B4=D1=8C=D1=8F-=D0=BA=D0=B0=D1=80=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BA=D0=B8=20gate3card=20=D0=BB=D0=B8=D0=BD=D0=B7=D1=8B?= =?UTF-8?q?=20card=5Fmatches=5Fproduct/no=5Foverstatement/verify=5Fsteps?= =?UTF-8?q?=5Freal=20plus=20=D1=87=D0=B8=D1=81=D1=82=D1=8B=D0=B9=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=BE=D1=89=D0=BD=D0=B8=D0=BA=20=D0=B2=D0=B8=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20gate3CardSurfaceRecord?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D0=B4=D0=B8=D1=8F=20judge:gate3card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ate3card-judge-lenses-and-visibility-v2.md | 151 ++++++++++++++++++ tools/enforce-gate3-loop.mjs | 10 ++ tools/enforce-gate3-loop.test.mjs | 21 +++ tools/judge-engine.mjs | 1 + tools/judge-engine.test.mjs | 7 + 5 files changed, 190 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-17-gate3card-judge-lenses-and-visibility-v2.md diff --git a/docs/superpowers/plans/2026-06-17-gate3card-judge-lenses-and-visibility-v2.md b/docs/superpowers/plans/2026-06-17-gate3card-judge-lenses-and-visibility-v2.md new file mode 100644 index 0000000..0e6fda2 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-gate3card-judge-lenses-and-visibility-v2.md @@ -0,0 +1,151 @@ +# gate3card: судья-карточки (линзы) + видимость стадии — Implementation Plan (v2) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Завести набор линз судьи `gate3card` (сверка пользовательской карточки с продуктом) и чистый помощник видимости `gate3CardSurfaceRecord`, чтобы будущая живая петля приёмки (Сессия E) могла судить карточку и показывать её вердикт в снимке+баннере. + +**Architecture:** Две аддитивные правки существующих файлов. (1) В `judge-engine.mjs` добавляется ключ `gate3card` в замороженный `VOTE_LAYOUTS`; `requiredLensesFor` уже generic по любому ключу — отдельной правки логики не требует, проверяется тестом. (2) В `enforce-gate3-loop.mjs` добавляется чистый экспорт `gate3CardSurfaceRecord` — зеркало существующего `gate3SurfaceRecord`, но со стадией `judge:gate3card`. Живая проводка вызова судьи-карточки в петлю — Сессия E (вне этого плана). + +**Tech Stack:** Node ESM (`.mjs`), vitest (`vitest.config.tools.mjs`, `--no-file-parallelism`). TDD. + +**Кодовая фраза:** роутер-наставник. **Артефакт (опечатан):** gate-3 приёмка владельца v2 §u4 (сверка карточки судьёй) + видимость {#deferred}. **delivery:** internal — управляющая машинерия (линзы судьи + чистый помощник видимости); пользовательского результата владельцу не доставляется, приёмка владельца не требуется. + +**Граница (вне объёма, по замечанию наставника на спеку):** честность пометки `delivery` в судье плана (gate-2) и тест неизвестного delivery — **Сессия D**; формат предъявления карточки владельцу в Stop-хуке, таймаут-эскалация, живой вызов судьи-карточки — **Сессия E**. Здесь только линзы + чистый помощник видимости. + +**NB по треку B:** `judge-engine.mjs` параллельно правит трек B. Правлю ТОЛЬКО `VOTE_LAYOUTS` (аддитивный ключ), функции не трогаю — слияние позже бесконфликтно. + +## Структура файлов + +- Изменить (тест): `tools/judge-engine.test.mjs` — тест линз `gate3card` в `requiredLensesFor`. +- Изменить: `tools/judge-engine.mjs` — добавить ключ `gate3card` в `VOTE_LAYOUTS`. +- Изменить (тест): `tools/enforce-gate3-loop.test.mjs` — тесты `gate3CardSurfaceRecord`. +- Изменить: `tools/enforce-gate3-loop.mjs` — добавить чистый `gate3CardSurfaceRecord`. + +```skills-json +["test-driven-development"] +``` + +```steps-json +[ + {"op":"Edit","object":"tools/judge-engine.test.mjs","ref":"u4"}, + {"op":"Bash","object":"npx vitest run tools/judge-engine.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism","ref":"u4"}, + {"op":"Edit","object":"tools/judge-engine.mjs","ref":"u4"}, + {"op":"Bash","object":"npx vitest run tools/judge-engine.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism","ref":"u4"}, + {"op":"Edit","object":"tools/enforce-gate3-loop.test.mjs","ref":"u4"}, + {"op":"Bash","object":"npx vitest run tools/enforce-gate3-loop.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism","ref":"u4"}, + {"op":"Edit","object":"tools/enforce-gate3-loop.mjs","ref":"u4"}, + {"op":"Bash","object":"npx vitest run tools/enforce-gate3-loop.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism","ref":"u4"}, + {"op":"Bash","object":"npx vitest run --reporter dot --config vitest.config.tools.mjs --no-file-parallelism","ref":"u9"} +] +``` + +```verified-context-json +[{"id":"vc1","kind":"EXTRACTED","ref":"tools/judge-engine.mjs","anchor":"export const VOTE_LAYOUTS"},{"id":"vc2","kind":"EXTRACTED","ref":"tools/enforce-gate3-loop.mjs","anchor":"export function gate3SurfaceRecord"}] +``` + +--- + +### Task 1: Линзы `gate3card` в judge-engine (§u4) + +- [ ] **Step 1 (Edit `tools/judge-engine.test.mjs`): падающий тест линз** + +В блок `describe('requiredLensesFor (§7 раскладка голосов по функциям)', ...)` после теста про риск (перед закрытием `});`) добавить: + +```javascript + it('gate3card несёт 3 линзы сверки карточки с продуктом', () => { + const l = requiredLensesFor('gate3card'); + expect(l).toEqual(VOTE_LAYOUTS.gate3card); + expect(l).toContain('card_matches_product'); + expect(l).toContain('no_overstatement'); + expect(l).toContain('verify_steps_real'); + }); +``` + +- [ ] **Step 2 (Bash): прогон — тест падает (RED)** + +Run: `npx vitest run tools/judge-engine.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism` +Expected: FAIL — `VOTE_LAYOUTS.gate3card` === undefined, `requiredLensesFor('gate3card')` → `[]`. (Под Claude harness-collapse; авторитет — терминал владельца.) + +- [ ] **Step 3 (Edit `tools/judge-engine.mjs`): добавить ключ в `VOTE_LAYOUTS`** + +После строки `gate3: ['goal_achieved', 'premortem_whole', 'behavior_vs_goal'],` (перед `});`) добавить: + +```javascript + gate3card: ['card_matches_product', 'no_overstatement', 'verify_steps_real'], +``` + +- [ ] **Step 4 (Bash): прогон — тест зелёный (GREEN)** + +Run: `npx vitest run tools/judge-engine.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism` +Expected: PASS — тест gate3card + прежние тесты `requiredLensesFor`. (Авторитет — терминал владельца.) + +--- + +### Task 2: Чистый помощник видимости `gate3CardSurfaceRecord` ({#deferred}) + +- [ ] **Step 5 (Edit `tools/enforce-gate3-loop.test.mjs`): падающие тесты помощника** + +После закрытия блока `describe('gate3SurfaceRecord ...')` (перед `describe('loop marker delivery' ...)`) добавить: + +```javascript +import { gate3CardSurfaceRecord } from './enforce-gate3-loop.mjs'; + +describe('gate3CardSurfaceRecord (видимость судьи карточки, стадия judge:gate3card)', () => { + it('GO verdict → stage judge:gate3card, status GO', () => { + expect(gate3CardSurfaceRecord({ verdict: { wired: true, decision: 'GO' }, hash: 'h' })) + .toEqual({ stage: 'judge:gate3card', hash: 'h', status: 'GO', reason: '' }); + }); + it('NO-GO → status NO-GO, reason дословно', () => { + const r = gate3CardSurfaceRecord({ verdict: { wired: true, decision: 'NO-GO', reason: 'приукрашивание' }, hash: 'h' }); + expect(r.status).toBe('NO-GO'); + expect(r.reason).toBe('приукрашивание'); + }); + it('degraded (wired:false) → degraded', () => { + expect(gate3CardSurfaceRecord({ verdict: { wired: false }, hash: 'h' }).status).toBe('degraded'); + }); + it('нет verdict → skip, hash null', () => { + expect(gate3CardSurfaceRecord({}).status).toBe('skip'); + expect(gate3CardSurfaceRecord({}).hash).toBe(null); + }); +}); +``` + +- [ ] **Step 6 (Bash): прогон — тесты падают (RED)** + +Run: `npx vitest run tools/enforce-gate3-loop.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism` +Expected: FAIL — `gate3CardSurfaceRecord is not a function`. (Авторитет — терминал владельца.) + +- [ ] **Step 7 (Edit `tools/enforce-gate3-loop.mjs`): реализация `gate3CardSurfaceRecord`** + +После `gate3SurfaceRecord` (перед `runGate3Stop`) добавить: + +```javascript +/** Видимость судьи карточки (Фаза 2b): вердикт сверки карточки → стадия judge:gate3card. + * Зеркало gate3SurfaceRecord, тот же канал снимок+баннер (спека видимости {#deferred}). Чистая. */ +export function gate3CardSurfaceRecord({ verdict, hash } = {}) { + let status = 'skip'; + if (verdict && verdict.wired === false) status = 'degraded'; + else if (verdict && verdict.decision === 'GO') status = 'GO'; + else if (verdict && verdict.decision === 'NO-GO') status = 'NO-GO'; + return { stage: 'judge:gate3card', hash: hash || null, status, reason: (verdict && verdict.reason) || '' }; +} +``` + +- [ ] **Step 8 (Bash): прогон — тесты зелёные (GREEN)** + +Run: `npx vitest run tools/enforce-gate3-loop.test.mjs --reporter dot --config vitest.config.tools.mjs --no-file-parallelism` +Expected: PASS — 4 теста gate3CardSurfaceRecord + прежние gate3SurfaceRecord. (Авторитет — терминал владельца.) + +- [ ] **Step 9 (Bash): полная регрессия tools** + +Run: `npx vitest run --reporter dot --config vitest.config.tools.mjs --no-file-parallelism` +Expected: PASS — база + новые тесты, 0 регрессий (в т.ч. `gate3SurfaceRecord`, `buildJudgePrompt`, `decideGate3Closure`). (Под Claude harness-collapse; авторитетный свод — терминал владельца.) + +## Self-Review (план против спеки) + +- **§u4 линзы `gate3card`** — Task 1: `card_matches_product`/`no_overstatement`/`verify_steps_real` в `VOTE_LAYOUTS`; `requiredLensesFor` generic, проверен тестом. ✓ +- **Видимость {#deferred}** — Task 2: чистый `gate3CardSurfaceRecord` со стадией `judge:gate3card`, зеркало `gate3SurfaceRecord`. Живая запись `writeStage`/`pushVerdict` и сам вызов судьи-карточки — Сессия E. ✓ +- **DR-1** — мутирующие шаги 1,3,5,7 сопровождены Bash (2,4,6,8); каждый файл правится ровно ОДИН раз → двух Edit одного файла подряд нет; RED/GREEN — разная неопределённость. ✓ +- **Трек B** — правлю только `VOTE_LAYOUTS` (аддитивный ключ), функции judge-engine не трогаю. ✓ +- **Обратная совместимость** — оба изменения чисто аддитивные (новый ключ / новый экспорт); существующее поведение не меняется. ✓ +- **Вне объёма** — честность delivery на gate-2 + тест неизвестного delivery (Сессия D); показ карточки/таймаут/живой вызов (Сессия E). ✓ diff --git a/tools/enforce-gate3-loop.mjs b/tools/enforce-gate3-loop.mjs index 4a74a1f..e591880 100644 --- a/tools/enforce-gate3-loop.mjs +++ b/tools/enforce-gate3-loop.mjs @@ -78,6 +78,16 @@ export function gate3SurfaceRecord({ verdict, hash } = {}) { return { stage: 'judge:gate3', hash: hash || null, status, reason: (verdict && verdict.reason) || '' }; } +/** Видимость судьи карточки (Фаза 2b): вердикт сверки карточки → стадия judge:gate3card. + * Зеркало gate3SurfaceRecord, тот же канал снимок+баннер (спека видимости {#deferred}). Чистая. */ +export function gate3CardSurfaceRecord({ verdict, hash } = {}) { + let status = 'skip'; + if (verdict && verdict.wired === false) status = 'degraded'; + else if (verdict && verdict.decision === 'GO') status = 'GO'; + else if (verdict && verdict.decision === 'NO-GO') status = 'NO-GO'; + return { stage: 'judge:gate3card', hash: hash || null, status, reason: (verdict && verdict.reason) || '' }; +} + /** Чистая оркестрация хода Stop (deps инъектируются — тест без IO/модели). {block, message?}. */ export async function runGate3Stop(event, deps) { const { runtimeDir, sess, key, judgeKey, loadGreens, loadArtifact, callJudge, grants, consumed, now } = deps; diff --git a/tools/enforce-gate3-loop.test.mjs b/tools/enforce-gate3-loop.test.mjs index bfb0bc7..a25ad65 100644 --- a/tools/enforce-gate3-loop.test.mjs +++ b/tools/enforce-gate3-loop.test.mjs @@ -113,6 +113,27 @@ describe('gate3SurfaceRecord (видимость gate3)', () => { }); }); +import { gate3CardSurfaceRecord } from './enforce-gate3-loop.mjs'; + +describe('gate3CardSurfaceRecord (видимость судьи карточки, стадия judge:gate3card)', () => { + it('GO verdict → stage judge:gate3card, status GO', () => { + expect(gate3CardSurfaceRecord({ verdict: { wired: true, decision: 'GO' }, hash: 'h' })) + .toEqual({ stage: 'judge:gate3card', hash: 'h', status: 'GO', reason: '' }); + }); + it('NO-GO → status NO-GO, reason дословно', () => { + const r = gate3CardSurfaceRecord({ verdict: { wired: true, decision: 'NO-GO', reason: 'приукрашивание' }, hash: 'h' }); + expect(r.status).toBe('NO-GO'); + expect(r.reason).toBe('приукрашивание'); + }); + it('degraded (wired:false) → degraded', () => { + expect(gate3CardSurfaceRecord({ verdict: { wired: false }, hash: 'h' }).status).toBe('degraded'); + }); + it('нет verdict → skip, hash null', () => { + expect(gate3CardSurfaceRecord({}).status).toBe('skip'); + expect(gate3CardSurfaceRecord({}).hash).toBe(null); + }); +}); + describe('loop marker delivery', () => { const KEY = 'k-loop-deliv'; it('delivery в подписанной метке верифицируется и ломается при подмене', () => { diff --git a/tools/judge-engine.mjs b/tools/judge-engine.mjs index 3943b89..cacd343 100644 --- a/tools/judge-engine.mjs +++ b/tools/judge-engine.mjs @@ -25,6 +25,7 @@ export const VOTE_LAYOUTS = Object.freeze({ part_light: ['correctness', 'simplicity', 'footgun'], part_risky: ['radius_tests', 'twins', 'attacker'], gate3: ['goal_achieved', 'premortem_whole', 'behavior_vs_goal'], + gate3card: ['card_matches_product', 'no_overstatement', 'verify_steps_real'], }); export const PREMORTEM_CLASSES = Object.freeze([ diff --git a/tools/judge-engine.test.mjs b/tools/judge-engine.test.mjs index d018d39..17d7bdb 100644 --- a/tools/judge-engine.test.mjs +++ b/tools/judge-engine.test.mjs @@ -22,6 +22,13 @@ describe('requiredLensesFor (§7 раскладка голосов по функ expect(l).toContain('attacker'); expect(l).toContain('money'); }); + it('gate3card несёт 3 линзы сверки карточки с продуктом', () => { + const l = requiredLensesFor('gate3card'); + expect(l).toEqual(VOTE_LAYOUTS.gate3card); + expect(l).toContain('card_matches_product'); + expect(l).toContain('no_overstatement'); + expect(l).toContain('verify_steps_real'); + }); }); describe('buildJudgePrompt (чистая, детерминированная; слепа к переписке)', () => {