Files
brain/tools/secretary-distill.test.mjs
T

69 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import { distillSpan } from './secretary-distill.mjs';
import { EMPTY_PROTOCOL } from './secretary-protocol.mjs';
describe('distillSpan — честная пометка спана в шаге (без модели)', () => {
it('передаёт note в шаг, когда спан помечен продолжением', async () => {
const proto = EMPTY_PROTOCOL();
const spanEx = { user: 'настоящая просьба длинная', assistant: 'докончил', actions: [] };
const out = await distillSpan(proto, spanEx, { start: 3, end: 4, note: '(связь прерывалась — продолжено)' }, { callModel: null });
const step = out.steps.find((s) => s.turn === 3);
expect(step.text.endsWith('(связь прерывалась — продолжено)')).toBe(true);
});
});
describe('distillSpan — разбор одного завершённого спана (reconcile + аудит)', () => {
it('добавляет шаг спана, применяет reconcile и аудит', async () => {
const proto = EMPTY_PROTOCOL();
const spanEx = { user: 'реши А или Б', assistant: 'беру А', actions: [{ tool: 'Read', input: 'x', result: 'y' }] };
let call = 0;
const callModel = async () => {
call++;
if (call === 1) return JSON.stringify({ subject: 'дело', decisions: [{ text: 'A', struck: false }], alternatives: [], consequences: [], will: [], open: [], doneNext: [], step: { user: 'реши', assistant: 'взял A' } });
return JSON.stringify({ new: [{ text: 'почему A?', lens: 'Л2' }], ops: [], resolved: [] });
};
const out = await distillSpan(proto, spanEx, { start: 3, end: 4 }, { callModel, session: 's' });
expect(out.steps[0].text).toContain('Ход (промпт) 3 [вобрал ходы 3-4]');
expect(out.decisions.map((d) => d.text)).toContain('A');
expect(out.hidden.map((h) => h.text)).toContain('почему A?');
});
it('без модели — только детерминированный шаг, категории пусты', async () => {
const out = await distillSpan(EMPTY_PROTOCOL(), { user: 'вопрос достаточно длинный', assistant: 'ответ', actions: [] },
{ start: 5, end: 5 }, { callModel: null, session: 's' });
expect(out.steps[0].text).toContain('Ход (промпт) 5');
expect(out.decisions).toEqual([]);
});
});
describe('distillSpan — новый конвейер «пушистое дерево» за флагом', () => {
it('при fluffy=on: ветка из диагностики + кандидат из брейншторма, аудит не зовётся', async () => {
const proto = EMPTY_PROTOCOL();
const spanEx = { user: 'предложи улучшение наставнику', assistant: 'разобрал по коду', actions: [] };
let auditCalled = false;
const callModel = async ({ system }) => {
if (/секретарь-редактор/.test(system)) return JSON.stringify({ subject: 'дело', decisions: [{ text: 'A', struck: false }], alternatives: [], consequences: [], will: [], open: [], doneNext: [], step: { user: 'u', assistant: 'a' } });
if (/сборщик скрытых веток/.test(system)) return JSON.stringify({ lenses: [{ lens: 'Л4', verdict: 'finding', text: 'без пруфа', опора: 'внутр', ref: 'x:1', тяжесть: 'важное' }] });
if (/ловец/.test(system)) return JSON.stringify({ dropped: [] });
if (/брейншторма/.test(system)) return JSON.stringify({ forks: [{ branch: 'идея', trigger: 't', опора: 'догадка', релевантность: 'low' }] });
if (/САДОВНИК/.test(system)) return JSON.stringify({ tend: [] });
if (/аудитор|скрытые вопросы/i.test(system)) { auditCalled = true; return '{"new":[]}'; }
return '{}';
};
const out = await distillSpan(proto, spanEx, { start: 15, end: 16 }, { callModel, session: 's', flags: { fluffy: true } });
expect(out.hidden.some((h) => h.text === 'без пруфа')).toBe(true);
expect((out.candidates || []).map((c) => c.branch)).toContain('идея');
expect(auditCalled).toBe(false);
});
it('флаг ВЫКЛ (по умолчанию) — старый путь, candidates нет', async () => {
const proto = EMPTY_PROTOCOL();
const spanEx = { user: 'вопрос достаточно длинный', assistant: 'ответ', actions: [] };
const callModel = async ({ system }) => {
if (/секретарь-редактор/.test(system)) return JSON.stringify({ subject: 'd', decisions: [], alternatives: [], consequences: [], will: [], open: [], doneNext: [], step: { user: 'u', assistant: 'a' } });
return JSON.stringify({ new: [], ops: [], resolved: [] });
};
const out = await distillSpan(proto, spanEx, { start: 5, end: 5 }, { callModel, session: 's', flags: { fluffy: false } });
expect(out.candidates).toBeUndefined();
});
});