test(secretary): gravity / reopen / history-compression mechanics (decision #8)

This commit is contained in:
Дмитрий
2026-06-25 14:20:27 +03:00
parent 9eaca057ca
commit 5b280fc59a
+57
View File
@@ -0,0 +1,57 @@
import { describe, it, expect } from 'vitest';
import { applyTend } from './secretary-gardener.mjs';
import { applyResults } from './secretary-apply.mjs';
// Механика, которую на 2-спановых прогонах не проверяли (НАХОДКИ, решение №8). Тесты бьют по реальным
// функциям конвейера A (applyTend/applyResults), а не по воркеру — это поведение разбора, не оркестрации.
describe('mechanics: reopen / обратный каскад', () => {
it('закрытая ветка снова открывается и снимается зачёркивание (несущее решение изменилось)', () => {
const proto = { hidden: [{ id: 'СВ-1', lens: 'Л4', status: 'закрыт', struck: true, text: 'дыра', born: 3, lastTouch: 5 }] };
applyTend(proto, [{ id: 'СВ-1', action: 'reopen', why: 'несущее решение изменилось' }], 8);
const sv = proto.hidden[0];
expect(sv.status).toBe('открыт');
expect(sv.struck).toBe(false);
expect(sv.lastTouch).toBe(8);
expect(sv.born).toBe(3); // корень сохранён — не потеряли историю
});
});
describe('mechanics: сжатие длинной истории (сворачивание ≠ удаление)', () => {
it('на масштабе: закрытые ветки сворачиваются (struck), но остаются с источником (born)', () => {
// 12 живых веток; закрываем половину с пруфом.
const hidden = Array.from({ length: 12 }, (_, i) => ({
id: `СВ-${i + 1}`, lens: 'Л1', status: 'открыт', struck: false,
text: `нет выдержек кода ${i}`, born: i + 1, lastTouch: i + 1,
}));
const proto = { hidden };
const tend = hidden.filter((_, i) => i % 2 === 0).map((h) => ({ id: h.id, action: 'close', proof: `код:${h.born}` }));
applyTend(proto, tend, 20);
// Ни одна ветка не удалена — сворачивание прячет видность, не корень («потом не восстановим»).
expect(proto.hidden).toHaveLength(12);
const closed = proto.hidden.filter((h) => h.status === 'закрыт');
expect(closed).toHaveLength(6);
for (const h of closed) {
expect(h.struck).toBe(true);
expect(Number.isFinite(h.born)).toBe(true); // источник на месте
}
});
});
describe('mechanics: гравитация (кандидат → ствол)', () => {
// ПРОБЕЛ (follow-up): в A механика НЕ реализована. applyResults накапливает кандидатов (p.candidates,
// дедуп по branch), но НЕТ пути продвижения кандидата в ствол (decisions/will), когда владелец заговорил
// об идее. Это суждение РЕДАКТОРА (сопоставить кандидата с движением ствола), не детерминированное
// правило — строить спекулятивно нельзя (решение владельца; реализация = доработка редактор-промпта).
// Документируем как незакрытый хвост, не выдаём отсутствующее за готовое.
it.todo('кандидат поднимается в ствол, когда владелец заговорил об его идее');
it('сейчас: кандидат лишь накапливается в candidates, в ствол сам не уходит (фиксируем текущее поведение)', () => {
const proto = { hidden: [], candidates: [], nextSvId: 1, decisions: [] };
const d13 = { forks: [{ branch: 'вынести очередь в SQLite', trigger: 'цитата', why: 'масштаб', опора: 'догадка', релевантность: 'medium' }] };
const out = applyResults(proto, 5, null, null, d13, null);
expect(out.candidates).toHaveLength(1);
expect(out.candidates[0].branch).toBe('вынести очередь в SQLite');
expect(out.decisions || []).toHaveLength(0); // в ствол не просочился — подтверждает, что гравитации нет
});
});