Files
brain/tools/enforce-judge-gate-gate1.test.mjs
T

73 lines
5.0 KiB
JavaScript

// tools/enforce-judge-gate-gate1.test.mjs — Гейт-1 (судейство спек) тесты.
// Вынесено отдельным файлом: real-test-verifier блокирует import-only Edit существующего
// теста (new_string без expect/it). Имя содержит «enforce-judge-gate» → tdd-gate распознаёт.
import { describe, it, expect } from 'vitest';
import { extractGate1Product, extractGate2Product, runJudgeGate } from './enforce-judge-gate.mjs';
import { judgedHashOf, sealableArtifact } from './seal-orchestration.mjs';
describe('extractGate1Product — детект спеки (Write-only) + извлечение', () => {
const specPath = 'docs/superpowers/specs/2026-06-09-foo-design.md';
it('Write спеки → shouldJudge, product, functionName=gate1, goal, cards=[]', () => {
const ev = { tool_name: 'Write', tool_input: { file_path: specPath, content: '# Spec\n## Цель\nрешить X\n## Реш {#r}\nтекст' } };
const r = extractGate1Product(ev);
expect(r.shouldJudge).toBe(true);
expect(r.functionName).toBe('gate1');
expect(r.product).toContain('решить X');
expect(r.goal).toContain('решить X');
expect(r.cards).toEqual([]);
});
it('Write спеки с обратными слэшами → распознаётся', () => {
const ev = { tool_name: 'Write', tool_input: { file_path: 'docs\\superpowers\\specs\\x-design.md', content: 'c' } };
expect(extractGate1Product(ev).shouldJudge).toBe(true);
});
it('Edit спеки → shouldJudge:false (Δ-A: фрагмент)', () => {
const ev = { tool_name: 'Edit', tool_input: { file_path: specPath, new_string: 'кусок' } };
expect(extractGate1Product(ev).shouldJudge).toBe(false);
});
it('MultiEdit спеки → shouldJudge:false', () => {
const ev = { tool_name: 'MultiEdit', tool_input: { file_path: specPath, edits: [{ old_string: 'a', new_string: 'b' }] } };
expect(extractGate1Product(ev).shouldJudge).toBe(false);
});
it('план-путь (Write) → shouldJudge:false (это Гейт-2)', () => {
const ev = { tool_name: 'Write', tool_input: { file_path: 'docs/superpowers/plans/x.md', content: 'c' } };
expect(extractGate1Product(ev).shouldJudge).toBe(false);
});
it('не-спека путь / Bash / битое → shouldJudge:false', () => {
expect(extractGate1Product({ tool_name: 'Write', tool_input: { file_path: 'tools/foo.mjs', content: 'c' } }).shouldJudge).toBe(false);
expect(extractGate1Product({ tool_name: 'Bash', tool_input: { command: 'ls' } }).shouldJudge).toBe(false);
expect(extractGate1Product(null).shouldJudge).toBe(false);
});
it('extractGate2Product на спеке по-прежнему false (Гейт-2 = планы, регрессия)', () => {
const ev = { tool_name: 'Write', tool_input: { file_path: specPath, content: 'c' } };
expect(extractGate2Product(ev).shouldJudge).toBe(false);
});
});
const gate1Ok = JSON.stringify({ slots: { completeness: 'aaaaaaaa', premortem: 'bbbbbbbb', goal_advocate: 'cccccccc', correctness: 'dddddddd' }, objections: [] });
const gate2Ok = JSON.stringify({ slots: { spec_fidelity: 'aaaaaaaa', verifiability: 'bbbbbbbb', plan_soundness: 'cccccccc', execution_risk: 'dddddddd', step_clarity: 'eeeeeeee' }, objections: [] });
const specEv = (content) => ({ tool_name: 'Write', tool_input: { file_path: 'docs/superpowers/specs/x-design.md', content } });
describe('runJudgeGate — Гейт-1 (спека) маршрут + judged_hash от raw (SD-1)', () => {
it('активен + spec-Write + чистый вердикт → wired, functionName=gate1', async () => {
const deps = { judgeActiveImpl: () => true, apiKey: 'K', transport: async () => gate1Ok };
const r = await runJudgeGate(specEv('## Реш {#r}\nтекст'), deps);
expect(r.wired).toBe(true);
expect(r.decision).toBe('GO');
expect(r.verdict.functionName).toBe('gate1');
});
it('judged_hash gate1 = judgedHashOf(sealableArtifact(СЫРОГО content)) — SD-1', async () => {
const content = ' ## Реш {#r}\nтекст с пробелами по краям ';
const deps = { judgeActiveImpl: () => true, apiKey: 'K', transport: async () => gate1Ok };
const r = await runJudgeGate(specEv(content), deps);
expect(r.judged_hash).toBe(judgedHashOf(sealableArtifact(content)));
// SD-1 страж: trimmed дал бы ДРУГОЙ source_sha → другой хеш → печать не сошлась бы
expect(r.judged_hash).not.toBe(judgedHashOf(sealableArtifact(content.trim())));
});
it('план-путь по-прежнему gate2 (регрессия)', async () => {
const deps = { judgeActiveImpl: () => true, apiKey: 'K', transport: async () => gate2Ok };
const r = await runJudgeGate({ tool_name: 'Write', tool_input: { file_path: 'docs/superpowers/plans/x.md', content: '# П\n## Цель\nцель\nшаг' } }, deps);
expect(r.wired).toBe(true);
expect(r.verdict.functionName).toBe('gate2');
});
});