397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
3.8 KiB
JavaScript
87 lines
3.8 KiB
JavaScript
// tools/enforce-decomposition-detector.test.mjs
|
|
// Stream H Task 5 (H6) — wrapper tests around the pure decomposition-detector module.
|
|
import { describe, it, expect } from 'vitest';
|
|
import { decide } from './enforce-decomposition-detector.mjs';
|
|
|
|
describe('enforce-decomposition-detector wrapper (Stream H Task 5)', () => {
|
|
it('allows when history is empty', () => {
|
|
const r = decide({
|
|
history: [],
|
|
currentEntry: { primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 1 },
|
|
llmVerdict: 'NO',
|
|
});
|
|
expect(r.action).toBe('allow');
|
|
});
|
|
|
|
it('allows when overlap below threshold (only 2 prompts share keywords)', () => {
|
|
const history = [
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 1 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 2 },
|
|
];
|
|
const r = decide({
|
|
history,
|
|
currentEntry: { primary_keywords: ['unrelated', 'topic', 'words'], skill_invoked_this_prompt: false, prompt_idx: 3 },
|
|
llmVerdict: 'YES',
|
|
});
|
|
expect(r.action).toBe('allow');
|
|
});
|
|
|
|
it('hard_block_mutating when 3+ overlap, no skill, LLM YES (v4.1)', () => {
|
|
const history = [
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 1 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 2 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 3 },
|
|
];
|
|
const r = decide({
|
|
history,
|
|
currentEntry: { primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 4 },
|
|
llmVerdict: 'YES',
|
|
});
|
|
expect(r.action).toBe('hard_block_mutating');
|
|
expect(r.reason).toMatch(/decomp/i);
|
|
});
|
|
|
|
it('soft_flag when threshold met but LLM verdict NO (legit-distinct)', () => {
|
|
const history = [
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 1 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 2 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 3 },
|
|
];
|
|
const r = decide({
|
|
history,
|
|
currentEntry: { primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 4 },
|
|
llmVerdict: 'NO',
|
|
});
|
|
expect(r.action).toBe('soft_flag');
|
|
});
|
|
|
|
it('allows when threshold met but a writing-plans skill was invoked', () => {
|
|
const history = [
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: true, prompt_idx: 1 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 2 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 3 },
|
|
];
|
|
const r = decide({
|
|
history,
|
|
currentEntry: { primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 4 },
|
|
llmVerdict: 'YES',
|
|
});
|
|
expect(r.action).toBe('allow');
|
|
});
|
|
|
|
it('degraded allow when LLM verdict is missing/null (fail-open on LLM layer)', () => {
|
|
const history = [
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 1 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 2 },
|
|
{ primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 3 },
|
|
];
|
|
const r = decide({
|
|
history,
|
|
currentEntry: { primary_keywords: ['feature', 'login', 'form'], skill_invoked_this_prompt: false, prompt_idx: 4 },
|
|
llmVerdict: null,
|
|
});
|
|
expect(r.action).toBe('soft_flag');
|
|
expect(r.degraded).toBe(true);
|
|
});
|
|
});
|