397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
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);
|
|
});
|
|
});
|