import { describe, it, expect } from 'vitest'; import { decide } from './enforce-coverage-verify.mjs'; const TDD = 'superpowers:test-driven-development'; describe('enforce-coverage-verify / decide (журнал-факт K2)', () => { it('allows turn with no mutating tools (pure conversational)', () => { const r = decide({ toolUses: [{ name: 'Read', input: {} }], assistantText: 'just talking', journalSkillCalls: [] }); expect(r.block).toBe(false); }); it('blocks mutating turn with no coverage line', () => { const r = decide({ toolUses: [{ name: 'Edit', input: { file_path: 'foo.mjs' } }], assistantText: 'just did some work', journalSkillCalls: [], }); expect(r.block).toBe(true); expect(r.message).toMatch(/coverage/); }); it('blocks skill:X when Skill tool_use present this turn but X NOT in journal (acceptance)', () => { const r = decide({ toolUses: [ { name: 'Skill', input: { skill: TDD } }, { name: 'Edit', input: { file_path: 'foo.mjs' } }, ], assistantText: `coverage: skill:${TDD}\nок`, journalSkillCalls: [], }); expect(r.block).toBe(true); expect(r.message).toMatch(/журнал/i); }); it('blocks skill:X when X in journal but NOT in this turn tool_use (turn-scoping, no false-pass)', () => { const r = decide({ toolUses: [{ name: 'Edit', input: { file_path: 'foo.mjs' } }], assistantText: `coverage: skill:${TDD}`, journalSkillCalls: [TDD], }); expect(r.block).toBe(true); }); it('allows skill:X when X in BOTH this-turn tool_use AND journal', () => { const r = decide({ toolUses: [ { name: 'Skill', input: { skill: TDD } }, { name: 'Edit', input: { file_path: 'foo.mjs' } }, ], assistantText: `coverage: skill:${TDD}\nок`, journalSkillCalls: [TDD], }); expect(r.block).toBe(false); }); it('matches skill name across superpowers: prefix (tool input bare, coverage prefixed, journal prefixed)', () => { const r = decide({ toolUses: [ { name: 'Skill', input: { skill: 'test-driven-development' } }, { name: 'Edit', input: { file_path: 'foo.mjs' } }, ], assistantText: `coverage: skill:${TDD}`, journalSkillCalls: [TDD], }); expect(r.block).toBe(false); }); it('allows direct coverage (journal cannot verify non-skill channels — §4.2)', () => { const r = decide({ toolUses: [{ name: 'Edit', input: { file_path: 'memory/foo.md' } }], assistantText: 'coverage: direct:memory-sync', journalSkillCalls: [], }); expect(r.block).toBe(false); }); it('allows node coverage', () => { const r = decide({ toolUses: [{ name: 'Edit', input: { file_path: 'foo.vue' } }], assistantText: 'coverage: node:#19', journalSkillCalls: [], }); expect(r.block).toBe(false); }); it('override phrase does NOT bypass (§12 escape≠override — vocab removed)', () => { const r = decide({ toolUses: [{ name: 'Edit', input: { file_path: 'foo.mjs' } }], assistantText: 'no coverage', journalSkillCalls: [], override: { phrase: 'без скилов' }, }); expect(r.block).toBe(true); }); });