f22f8bd2ef
Новый router-pin-store: пин совета роутера по (task_id, goalHash) пер-сессионно. on-plan-write пин-aware: пин-попадание по неизменной цели → совет переиспользуется, classifyImpl НЕ зовётся; промах/смена цели → classify + сохранение пина. Проводка в активный наставник-хук инъекцией реального стора с sessionId (инъекция-выкл по умолчанию, старое поведение/тесты целы). Хвост спеки роутера §4 (пининг по goalHash), эпик роутер-реестр этап 3, item 2. Граница не тронута (recommended_chain, цепочки, observer-stop-hook, owner-seal). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
2.2 KiB
JavaScript
45 lines
2.2 KiB
JavaScript
// tools/router-pin-store.test.mjs
|
|
import { describe, it, expect } from 'vitest';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { goalHashOf, loadRouterPin, saveRouterPin } from './router-pin-store.mjs';
|
|
|
|
const mkBase = () => mkdtempSync(join(tmpdir(), 'router-pin-'));
|
|
|
|
describe('goalHashOf (D1)', () => {
|
|
it('детерминирован для одинакового текста', () => {
|
|
expect(goalHashOf('цель X')).toBe(goalHashOf('цель X'));
|
|
});
|
|
it('разный текст → разный хеш', () => {
|
|
expect(goalHashOf('цель X')).not.toBe(goalHashOf('цель Y'));
|
|
});
|
|
});
|
|
|
|
describe('saveRouterPin / loadRouterPin (D1)', () => {
|
|
it('save→load с той же целью возвращает совет', () => {
|
|
const baseDir = mkBase();
|
|
saveRouterPin({ taskId: 't1', planGoal: 'цель A', classification: { recommended_chain: ['x'] }, sessionId: 's', baseDir });
|
|
expect(loadRouterPin({ taskId: 't1', planGoal: 'цель A', sessionId: 's', baseDir })).toEqual({ recommended_chain: ['x'] });
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
});
|
|
it('смена цели (другой goalHash) → load даёт null', () => {
|
|
const baseDir = mkBase();
|
|
saveRouterPin({ taskId: 't1', planGoal: 'цель A', classification: { a: 1 }, sessionId: 's', baseDir });
|
|
expect(loadRouterPin({ taskId: 't1', planGoal: 'цель B', sessionId: 's', baseDir })).toBeNull();
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
});
|
|
it('перезапись per taskId', () => {
|
|
const baseDir = mkBase();
|
|
saveRouterPin({ taskId: 't1', planGoal: 'A', classification: { v: 1 }, sessionId: 's', baseDir });
|
|
saveRouterPin({ taskId: 't1', planGoal: 'A', classification: { v: 2 }, sessionId: 's', baseDir });
|
|
expect(loadRouterPin({ taskId: 't1', planGoal: 'A', sessionId: 's', baseDir })).toEqual({ v: 2 });
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
});
|
|
it('нет пина / нет файла → null (fail-safe)', () => {
|
|
const baseDir = mkBase();
|
|
expect(loadRouterPin({ taskId: 'nope', planGoal: 'A', sessionId: 's', baseDir })).toBeNull();
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
});
|
|
});
|