da6263c235
Brain-retro #5 candidate C, hole 1: enforce-classifier-match.mjs allowed the agent to bypass the rule by writing 'override: <reason>' in its own response (self-override = no enforcement). The user-vocabulary override phrases in enforce-override-vocab.json remain the only legitimate path. Added regression test asserting block on assistantText override when user prompt has no override phrase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
3.2 KiB
JavaScript
107 lines
3.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { decide } from './enforce-classifier-match.mjs';
|
|
|
|
describe('enforce-classifier-match / decide', () => {
|
|
it('allows pure conversation (no mutating tools)', () => {
|
|
expect(decide({
|
|
toolUses: [{ name: 'Read' }],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.9,
|
|
}).block).toBe(false);
|
|
});
|
|
|
|
it('allows when no recommendation', () => {
|
|
expect(decide({
|
|
toolUses: [{ name: 'Edit', input: {} }],
|
|
recommendation: null,
|
|
confidence: null,
|
|
}).block).toBe(false);
|
|
});
|
|
|
|
it('allows when confidence below threshold', () => {
|
|
expect(decide({
|
|
toolUses: [{ name: 'Edit', input: {} }],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.5,
|
|
}).block).toBe(false);
|
|
});
|
|
|
|
it('blocks when recommendation high-confidence + no matching tool', () => {
|
|
const r = decide({
|
|
toolUses: [{ name: 'Edit', input: { file_path: 'x.mjs' } }],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.9,
|
|
});
|
|
expect(r.block).toBe(true);
|
|
expect(r.message).toMatch(/writing-plans/);
|
|
});
|
|
|
|
it('allows when Skill tool invoked with matching name', () => {
|
|
const r = decide({
|
|
toolUses: [
|
|
{ name: 'Skill', input: { skill: 'superpowers:writing-plans' } },
|
|
{ name: 'Edit', input: { file_path: 'x.mjs' } },
|
|
],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.9,
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
|
|
it('matches normalized name without superpowers: prefix', () => {
|
|
const r = decide({
|
|
toolUses: [
|
|
{ name: 'Skill', input: { skill: 'writing-plans' } },
|
|
{ name: 'Edit', input: {} },
|
|
],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.9,
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
|
|
it('matches Task subagent', () => {
|
|
const r = decide({
|
|
toolUses: [
|
|
{ name: 'Task', input: { subagent_type: 'rls-reviewer' } },
|
|
{ name: 'Edit', input: {} },
|
|
],
|
|
recommendation: 'rls-reviewer',
|
|
confidence: 0.85,
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
|
|
it('blocks (not allows) when only "override:" in assistant text — self-override removed (hole 1)', () => {
|
|
const r = decide({
|
|
toolUses: [{ name: 'Edit', input: {} }],
|
|
recommendation: 'foo:bar',
|
|
confidence: 0.9,
|
|
assistantText: 'override: simpler direct edit, foo:bar overkill here\n',
|
|
override: null,
|
|
});
|
|
expect(r.block).toBe(true);
|
|
});
|
|
|
|
it('blocks when assistant text has "override: reason" but user prompt has no override phrase (hole 1)', () => {
|
|
const r = decide({
|
|
toolUses: [{ name: 'Edit', input: {} }],
|
|
recommendation: 'superpowers:writing-plans',
|
|
confidence: 0.9,
|
|
assistantText: 'override: just doing it quick',
|
|
override: null,
|
|
});
|
|
expect(r.block).toBe(true);
|
|
});
|
|
|
|
it('allows when override phrase present', () => {
|
|
const r = decide({
|
|
toolUses: [{ name: 'Edit', input: {} }],
|
|
recommendation: 'foo:bar',
|
|
confidence: 0.9,
|
|
override: { phrase: 'direct ok', suppresses: ['classifier-mismatch'] },
|
|
});
|
|
expect(r.block).toBe(false);
|
|
});
|
|
});
|