feat(router): пининг совета роутера по goalHash (переписка шагов не зовёт LLM заново)

Новый 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>
This commit is contained in:
Дмитрий
2026-06-21 08:18:15 +03:00
parent 4713a65b63
commit f22f8bd2ef
5 changed files with 150 additions and 5 deletions
+32
View File
@@ -125,6 +125,38 @@ describe('onPlanWrite возвращает результат роутера н
});
});
describe('пининг роутера по goalHash (D2)', () => {
it('пин-попадание → classifyImpl НЕ зовётся, routerClassification = пин', async () => {
let called = false;
const r = await onPlanWrite({
planSteps: STEPS, llmCall: llmOk, journalKey: 'k', nowMs: 1, planGoal: 'g',
classifyImpl: async () => { called = true; return { recommended_chain: ['fresh'] }; },
pinLoadImpl: () => ({ recommended_chain: ['pinned'] }),
});
expect(called).toBe(false);
expect(r.routerClassification).toEqual({ recommended_chain: ['pinned'] });
});
it('промах пина → classifyImpl зовётся и pinSaveImpl получает результат', async () => {
let saved = null;
const r = await onPlanWrite({
planSteps: STEPS, llmCall: llmOk, journalKey: 'k', nowMs: 1, planGoal: 'g',
classifyImpl: async () => ({ recommended_chain: ['fresh'] }),
pinLoadImpl: () => null,
pinSaveImpl: (rec) => { saved = rec; },
});
expect(r.routerClassification).toEqual({ recommended_chain: ['fresh'] });
expect(saved && saved.classification).toEqual({ recommended_chain: ['fresh'] });
});
it('без пин-имплов → classifyImpl зовётся (старое поведение)', async () => {
let called = false;
await onPlanWrite({
planSteps: STEPS, llmCall: llmOk, journalKey: 'k', nowMs: 1, planGoal: 'g',
classifyImpl: async () => { called = true; return { recommended_chain: ['x'] }; },
});
expect(called).toBe(true);
});
});
describe('оркестратор протягивает roundMemory до построителя (SP2c-2)', () => {
it('onPlanWrite → roundMemory доходит до промпта вердикта', async () => {
let capturedUser = null;