Files
portal/tools/enforce-classifier-match.test.mjs
T

269 lines
8.4 KiB
JavaScript
Raw Normal View History

// Task 4: threshold 0.8→0.6 + inline router-skip override
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);
});
// Task 4 (2026-05-28): threshold lowered 0.8 → 0.6 (brain-retro #10: 0% follow-through).
// Flipped from the old 0.8-threshold contract: 0.7 and 0.75 NOW BLOCK (above 0.6).
it('BLOCKS when confidence exactly 0.7 (above new threshold 0.6)', () => {
expect(decide({
toolUses: [{ name: 'Edit', input: {} }],
recommendation: 'superpowers:writing-plans',
confidence: 0.7,
}).block).toBe(true);
});
it('BLOCKS when confidence 0.75 (above new threshold 0.6)', () => {
expect(decide({
toolUses: [{ name: 'Edit', input: {} }],
recommendation: 'superpowers:writing-plans',
confidence: 0.75,
}).block).toBe(true);
});
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);
});
it('blocks when Task subagent is spawned without matching recommendation (hole 2)', () => {
const r = decide({
toolUses: [{ name: 'Task', input: { subagent_type: 'general-purpose', prompt: 'do stuff' } }],
recommendation: 'superpowers:writing-plans',
confidence: 0.9,
assistantText: '',
override: null,
});
expect(r.block).toBe(true);
});
it('does NOT block when Task subagent matches recommendation (regression — Task should count as match when right type)', () => {
const r = decide({
toolUses: [{ name: 'Task', input: { subagent_type: 'writing-plans', prompt: '...' } }],
recommendation: 'writing-plans',
confidence: 0.9,
assistantText: '',
override: null,
});
expect(r.block).toBe(false);
});
it('does not match meta-planning to planning recommendation (hole 5)', () => {
const r = decide({
toolUses: [{ name: 'Skill', input: { skill: 'meta-planning' } }, { name: 'Edit', input: {} }],
recommendation: 'planning',
confidence: 0.9,
assistantText: '',
override: null,
});
expect(r.block).toBe(true);
});
it('matches superpowers:writing-plans to writing-plans recommendation (regression — keep working)', () => {
expect(decide({
toolUses: [{ name: 'Skill', input: { skill: 'superpowers:writing-plans' } }, { name: 'Edit', input: {} }],
recommendation: 'writing-plans',
confidence: 0.9,
assistantText: '',
override: null,
}).block).toBe(false);
});
it('matches exact-name skill regression — keep working', () => {
expect(decide({
toolUses: [{ name: 'Skill', input: { skill: 'brainstorming' } }, { name: 'Edit', input: {} }],
recommendation: 'brainstorming',
confidence: 0.9,
assistantText: '',
override: null,
}).block).toBe(false);
});
// hole 4: triggers_matched fallback — decide() contract test
it('blocks when recommendation comes from triggers_matched fallback (hole 4, null confidence)', () => {
const r = decide({
toolUses: [{ name: 'Edit', input: {} }],
recommendation: 'superpowers:writing-plans', // would-be from triggers_matched[0]
confidence: null, // no LLM, but triggers present
assistantText: '',
override: null,
});
expect(r.block).toBe(true);
});
});
describe('inline router-skip override (Task 4)', () => {
const recommendation = '#19';
const editTool = { name: 'Edit', input: { file_path: 'x.txt' } };
it('does NOT block when assistant text contains "router-skip: <50+ chars>"', () => {
const assistantText = 'router-skip: deliberately choosing direct because router recommendation #19 is irrelevant for this trivial typo fix in docs';
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.85,
assistantText,
override: null,
});
expect(result.block).toBe(false);
});
it('DOES block when "router-skip:" justification < 50 chars', () => {
const assistantText = 'router-skip: too short';
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.85,
assistantText,
override: null,
});
expect(result.block).toBe(true);
});
it('DOES block when no "router-skip:" present at all', () => {
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.85,
assistantText: 'just normal text, no skip',
override: null,
});
expect(result.block).toBe(true);
});
});
describe('lowered confidence threshold (Task 4: 0.8 → 0.6)', () => {
const recommendation = '#19';
const editTool = { name: 'Edit', input: { file_path: 'x.txt' } };
it('blocks at confidence 0.65 (above new threshold 0.6)', () => {
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.65,
assistantText: '',
override: null,
});
expect(result.block).toBe(true);
});
it('does NOT block at confidence 0.55 (below new threshold 0.6)', () => {
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.55,
assistantText: '',
override: null,
});
expect(result.block).toBe(false);
});
it('still blocks at confidence 0.85 without router-skip (above threshold, no escape)', () => {
const result = decide({
toolUses: [editTool],
recommendation,
confidence: 0.85,
assistantText: '',
override: null,
});
expect(result.block).toBe(true);
});
});