Files
brain/tools/self-debrief-detector.test.mjs
T

128 lines
6.7 KiB
JavaScript

// tools/self-debrief-detector.test.mjs
import { describe, it, expect } from 'vitest';
import {
SELF_DEBRIEF_PATTERNS, extractSkillCallsLastNTurns, detectSelfDebrief,
} from './self-debrief-detector.mjs';
const noSkills = [];
describe('detectSelfDebrief — patterns', () => {
it('blocks "я проанализировал свои паттерны" without recent self-retro', () => {
const r = detectSelfDebrief('Я проанализировал свои обходные паттерны и понял...', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('blocks "Generalisable lesson" without self-retro', () => {
const r = detectSelfDebrief('Generalisable lesson: always check first.', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('blocks "Lesson v3.7:" version pattern', () => {
const r = detectSelfDebrief('Lesson v3.7: the boundaries had holes.', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('allows plain response with no self-debrief language', () => {
const r = detectSelfDebrief('Я починил баг в парсере, тесты зелёные.', noSkills);
expect(r.action).toBe('allow');
});
});
describe('detectSelfDebrief — self-retro escape', () => {
it('allows debrief content when brain-retro skill was invoked recently', () => {
const transcript = [{ type: 'tool_use', name: 'Skill', input: { skill: 'brain-retro' }, turn: 1 }];
const r = detectSelfDebrief('Я обобщил выводы по эпизодам.', transcript, { recentTurns: 30 });
expect(r.action).toBe('allow');
});
});
describe('SELF_DEBRIEF_PATTERNS — each pattern', () => {
it('pattern 2/5: blocks "self-retrospect анализ"', () => {
const r = detectSelfDebrief('self-retrospect анализ выполнен', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 3: blocks "обобщаю опыт сессии"', () => {
const r = detectSelfDebrief('обобщаю опыт сессии и делаю выводы', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 4: blocks "я заметил паттерн в своих решениях"', () => {
const r = detectSelfDebrief('я заметил паттерн в своих решениях', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 5: blocks "делаю self-retro по эпизодам"', () => {
const r = detectSelfDebrief('делаю self-retro по эпизодам', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 6: blocks "брэйн-ретро показал"', () => {
const r = detectSelfDebrief('брэйн-ретро показал интересные результаты', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 7: blocks "generalized lesson from this"', () => {
const r = detectSelfDebrief('generalized lesson from this experience', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
it('pattern 8: blocks "урок v4.1: границы имели дыры"', () => {
const r = detectSelfDebrief('урок v4.1: границы имели дыры', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
});
describe('detectSelfDebrief — cyrillic false-positive prevention (unicode lookbehind)', () => {
it('allows "Судья проанализировал свои паттерны поведения." — судья ends in я, not a sentence start', () => {
const r = detectSelfDebrief('Судья проанализировал свои паттерны поведения.', noSkills);
expect(r.action).toBe('allow');
});
it('allows "Семья заметил паттерн." — семья ends in я, mid-word match prevented', () => {
const r = detectSelfDebrief('Семья заметил паттерн.', noSkills);
expect(r.action).toBe('allow');
});
it('still hard_blocks "я проанализировал свои паттерны" at string start — lookbehind at pos 0 succeeds', () => {
const r = detectSelfDebrief('я проанализировал свои паттерны', noSkills);
expect(r.action).toBe('hard_block_next_mutating');
});
});
describe('extractSkillCallsLastNTurns', () => {
it('transcript without turn fields — 1 Skill call returned (whole transcript scope)', () => {
const transcript = [{ type: 'tool_use', name: 'Skill', input: { skill: 'brain-retro' } }];
const result = extractSkillCallsLastNTurns(transcript, 30);
expect(result).toHaveLength(1);
expect(result[0].skill_name).toBe('brain-retro');
});
it('Skill call at turn 5, n=30, maxTurn=40 — turn 5 ≤ 10 excluded → []', () => {
const transcript = [
{ type: 'tool_use', name: 'Skill', input: { skill: 'brain-retro' }, turn: 5 },
{ type: 'tool_result', name: 'other', turn: 40 },
];
const result = extractSkillCallsLastNTurns(transcript, 30);
expect(result).toHaveLength(0);
});
it('Skill call at turn 35, n=30, maxTurn=40 — turn 35 > 10, included', () => {
const transcript = [
{ type: 'tool_use', name: 'Skill', input: { skill: 'self-retrospect' }, turn: 35 },
{ type: 'tool_result', name: 'other', turn: 40 },
];
const result = extractSkillCallsLastNTurns(transcript, 30);
expect(result).toHaveLength(1);
expect(result[0].skill_name).toBe('self-retrospect');
});
});
describe('detectSelfDebrief — self-retro skill not matching', () => {
it('transcript with Skill(superpowers:writing-plans) only → debrief text → hard_block_next_mutating', () => {
const transcript = [{ type: 'tool_use', name: 'Skill', input: { skill: 'superpowers:writing-plans' }, turn: 1 }];
const r = detectSelfDebrief('self-retrospect анализ выполнен', transcript, { recentTurns: 30 });
expect(r.action).toBe('hard_block_next_mutating');
});
it('transcript with Skill(self-retrospect) → debrief text → allow', () => {
const transcript = [{ type: 'tool_use', name: 'Skill', input: { skill: 'self-retrospect' }, turn: 1 }];
const r = detectSelfDebrief('Generalisable lesson: always check.', transcript, { recentTurns: 30 });
expect(r.action).toBe('allow');
});
it('custom opts.selfRetroSkills:[custom-retro] + Skill(custom-retro) → allow', () => {
const transcript = [{ type: 'tool_use', name: 'Skill', input: { skill: 'custom-retro' }, turn: 1 }];
const r = detectSelfDebrief('Generalisable lesson: check.', transcript, {
recentTurns: 30,
selfRetroSkills: ['custom-retro'],
});
expect(r.action).toBe('allow');
});
});