feat: round-memory загрузка roundMemory J-side в хуке судьи SP2c-2 инкремент 3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-16 17:22:23 +03:00
parent 1289e68524
commit dc2c1a3df2
2 changed files with 43 additions and 2 deletions
+21 -2
View File
@@ -118,7 +118,16 @@ export async function runJudgeGate(event, deps = {}) {
// «Оба строго» (2026-06-12): СВОЙ ключ судьи ROUTER_JUDGE_LLM_KEY, общий не фолбэк.
const apiKey = deps.apiKey !== undefined ? deps.apiKey : resolveJudgeLlmKey();
const requiredLenses = requiredLensesFor(functionName);
const promptArgs = { product: g.product, goal: g.goal, cards: g.cards };
// SP2c-2: память кругов J-side (свои judge-замечания + J-доводы + diff; судья холодный —
// без замечания-при-возврате). roundMemoryImpl грузит из стора (await — годится sync-тест и
// async-прод); нет инъекции → круг слеп ({}). buildJudgePrompt уже рендерит roundMemory.
const stage = functionName === 'gate1' ? 'spec' : 'plan';
const rmContent = String((event && event.tool_input && event.tool_input.content) ?? '');
let roundMemory = {};
if (typeof deps.roundMemoryImpl === 'function') {
try { roundMemory = (await deps.roundMemoryImpl({ stage, content: rmContent })) || {}; } catch { roundMemory = {}; }
}
const promptArgs = { product: g.product, goal: g.goal, cards: g.cards, roundMemory };
const raw = await callJudgeModel({ functionName, requiredLenses, promptArgs, apiKey, model: deps.model, transport: deps.transport });
if (raw && raw.unavailable) {
// M7: причина недоступности протекает в вердикт → лог-WARN + seal-запись её фиксируют.
@@ -458,7 +467,17 @@ async function main() {
};
let result;
try {
result = await runJudgeTurn(event, { mode, nowMs: Date.now(), onWiredSeal: sealTurnProd, mentorApproved }); // inert/shadow/live-block внутри; nowMs → at в seal/verdict/warn (M7)
result = await runJudgeTurn(event, {
mode, nowMs: Date.now(), onWiredSeal: sealTurnProd, mentorApproved,
// SP2c-2: реальный загрузчик памяти кругов J-side из стора (taskId — тот же, что
// сохранил наставник до судьи; side='judge' холодный). Динамический импорт, fail-quiet внутри.
roundMemoryImpl: async ({ stage, content }) => {
let taskId = null;
try { taskId = loadTaskId({ sessionId: (event && event.session_id) || 'unknown', runtimeDir: runtimeDir(), fsImpl: fsDefault }); } catch { taskId = null; }
const { buildRoundMemory } = await import('./round-memory-store.mjs');
return buildRoundMemory({ taskId, stage, side: 'judge', currentContent: content, baseDir: runtimeDir() });
},
}); // inert/shadow/live-block внутри; nowMs → at в seal/verdict/warn (M7)
} catch { exitDecision({ block: mode === 'live-block' }); return; } // fail-CLOSE только в live-block
// M7 эскалация (round-control C-12): подряд идущие NO-GO судьи. allow → сброс. После 3-го подряд —
// сообщение «ЭСКАЛАЦИЯ ВЛАДЕЛЬЦУ» (судья сам выходит на владельца; продавить — escape, который судья
+22
View File
@@ -109,6 +109,28 @@ describe('runJudgeGate (async) — рубильник + детект + преф
expect(calls).toBe(1);
expect(r.wired).toBe(true);
});
// SP2c-2: судья J-side получает roundMemory (свои judge-замечания + J-доводы + diff) в промпт.
it('SP2c-2: roundMemoryImpl (plan) → J-память в промпте судьи (stage plan)', async () => {
const prompts = [];
await runJudgeGate(planEv(), {
judgeActiveImpl: () => true, apiKey: 'K', mentorApproved: () => true,
transport: async (p) => { prompts.push(p); return okText; },
roundMemoryImpl: ({ stage }) => ({ objections: [`память судьи ${stage}`] }),
});
expect(prompts).toHaveLength(1);
expect(prompts[0].user).toContain('память судьи plan');
});
it('SP2c-2: roundMemoryImpl (spec) → stage spec в промпте судьи', async () => {
const prompts = [];
const specEv = { tool_name: 'Write', tool_input: { file_path: 'docs/superpowers/specs/x-design.md', content: '## R {#r}\nтекст' } };
await runJudgeGate(specEv, {
judgeActiveImpl: () => true, apiKey: 'K', mentorApproved: () => true,
transport: async (p) => { prompts.push(p); return okText; },
roundMemoryImpl: ({ stage }) => ({ objections: [`память судьи ${stage}`] }),
});
expect(prompts[0].user).toContain('память судьи spec');
});
it('активен, но не план (Bash) → wired:false, транспорт не зовётся ($0)', async () => {
let calls = 0;
const deps = { judgeActiveImpl: () => true, apiKey: 'K', transport: async () => { calls++; return okText; } };