feat: round-memory протяжка roundMemory через производителя и оркестратор наставника SP2c-2 инкремент 2a
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -74,12 +74,12 @@ export function buildMentorVerdictPrompt({ plan = null, verifiedContext = [], ne
|
||||
* llmCall (инъект; живой транспорт — активация владельца) → parse → validateMentorVerdict →
|
||||
* при валидном wired:true + verdict с plan_hash (binding нах.F4: freeze-gate сверит
|
||||
* verdict.plan_hash === planId(steps) — stale/чужой вердикт не пройдёт). Сбой → wired:false
|
||||
* (SE-R6-6: не суд).
|
||||
* (SE-R6-6: не суд). roundMemory (SP2c-2) — память кругов M-side, прокидывается в построитель.
|
||||
*/
|
||||
export async function runMentorVerdict({ plan = null, verifiedContext = [], negotiationLog = [], graphSection = null, planHash = null, skillContext = null, llmCall }) {
|
||||
export async function runMentorVerdict({ plan = null, verifiedContext = [], negotiationLog = [], graphSection = null, planHash = null, skillContext = null, roundMemory = {}, llmCall }) {
|
||||
let v;
|
||||
try {
|
||||
v = await llmCall({ buildPrompt: () => buildMentorVerdictPrompt({ plan, verifiedContext, negotiationLog, graphSection, skillContext }) });
|
||||
v = await llmCall({ buildPrompt: () => buildMentorVerdictPrompt({ plan, verifiedContext, negotiationLog, graphSection, skillContext, roundMemory }) });
|
||||
} catch (e) {
|
||||
// Smoke 2026-06-12: тихий catch не давал отличить 401 (ключ) от сети — деталь
|
||||
// обязана доехать до вердикт-файла/журнала (усечённая; ключ в message не попадает).
|
||||
@@ -119,12 +119,13 @@ export function buildMentorSpecVerdictPrompt({ specContent = '', verifiedContext
|
||||
/**
|
||||
* Фаза 3 (Р6): производитель вердикта наставника по СПЕКЕ. Зеркало runMentorVerdict, но
|
||||
* spec-промпт; binding plan_hash = specHash (хеш артефакта спеки — judgedHashOf(sealableArtifact),
|
||||
* тот же, чем судья печатает gate1). Сбой → wired:false (SE-R6-6, не суд).
|
||||
* тот же, чем судья печатает gate1). Сбой → wired:false (SE-R6-6, не суд). roundMemory (SP2c-2)
|
||||
* — память кругов M-side спеки, прокидывается в построитель.
|
||||
*/
|
||||
export async function runMentorSpecVerdict({ specContent = '', specHash = null, verifiedContext = [], negotiationLog = [], graphSection = null, llmCall }) {
|
||||
export async function runMentorSpecVerdict({ specContent = '', specHash = null, verifiedContext = [], negotiationLog = [], graphSection = null, roundMemory = {}, llmCall }) {
|
||||
let v;
|
||||
try {
|
||||
v = await llmCall({ buildPrompt: () => buildMentorSpecVerdictPrompt({ specContent, verifiedContext, negotiationLog, graphSection }) });
|
||||
v = await llmCall({ buildPrompt: () => buildMentorSpecVerdictPrompt({ specContent, verifiedContext, negotiationLog, graphSection, roundMemory }) });
|
||||
} catch (e) {
|
||||
const detail = String((e && e.message) || e).slice(0, 200);
|
||||
return { ok: false, wired: false, reason: `сбой вызова наставника-вердикта (спека): ${detail}`, verdict: null };
|
||||
|
||||
@@ -177,3 +177,17 @@ describe('runMentorVerdict (§6.1 производитель + binding нах.F4
|
||||
expect(r.verdict.plan_hash).toBe('PH2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('производители прокидывают roundMemory в построитель (SP2c-2)', () => {
|
||||
const GOODV = { plan_points_addressed: ['ок'], reasoning: 'r', recommendation: 'rec', confidence: 0.8, decision: 'GO' };
|
||||
it('runMentorVerdict → roundMemory доходит до промпта', async () => {
|
||||
let captured = null;
|
||||
await runMentorVerdict({ plan: {}, planHash: 'PH', roundMemory: { objections: ['замечание прошлого круга M'] }, llmCall: async ({ buildPrompt }) => { captured = buildPrompt(); return GOODV; } });
|
||||
expect(captured.user).toContain('замечание прошлого круга M');
|
||||
});
|
||||
it('runMentorSpecVerdict → roundMemory доходит до промпта', async () => {
|
||||
let captured = null;
|
||||
await runMentorSpecVerdict({ specContent: '# с', specHash: 'SH', roundMemory: { objections: ['замечание прошлого круга спеки'] }, llmCall: async ({ buildPrompt }) => { captured = buildPrompt(); return GOODV; } });
|
||||
expect(captured.user).toContain('замечание прошлого круга спеки');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export function recommendedChainOf(c) {
|
||||
/**
|
||||
* @returns {{taskId, taskIdPersisted, ok, wired, verdict, reason?, journal, journalOk}}
|
||||
* verdict.plan_hash === planId(planSteps) (нах.F4) — caller отдаёт его freeze-gate.
|
||||
* roundMemory (SP2c-2) — память кругов M-side, протягивается до построителя вердикта.
|
||||
*/
|
||||
export async function onPlanWrite({
|
||||
planSteps = [],
|
||||
@@ -47,6 +48,7 @@ export async function onPlanWrite({
|
||||
registry = null,
|
||||
declaredSkills = [],
|
||||
planGoal = '',
|
||||
roundMemory = {},
|
||||
} = {}) {
|
||||
const planHash = planId(planSteps);
|
||||
// ✅O17: существующий task-id побеждает (re-issue не сбрасывает); первый план — якорь.
|
||||
@@ -67,7 +69,7 @@ export async function onPlanWrite({
|
||||
const skillContext = renderSkillContext({ declared: declaredSkills, recommendedChain, registry });
|
||||
// Производитель вердикта (C T5b): сбой → ok:false/wired:false (SE-R6-6, не суд).
|
||||
const r = await runMentorVerdictImpl({
|
||||
plan: { steps: planSteps }, planHash, verifiedContext, negotiationLog, graphSection, skillContext, llmCall,
|
||||
plan: { steps: planSteps }, planHash, verifiedContext, negotiationLog, graphSection, skillContext, roundMemory, llmCall,
|
||||
});
|
||||
// SE10 (A4): журнал — best-effort; throw ловится, круг не падает. Обоснование непустое
|
||||
// всегда (F-C2/ДР-6): из вердикта либо из reason сбоя.
|
||||
@@ -91,7 +93,8 @@ export async function onPlanWrite({
|
||||
* Фаза 3 (отдельный spec-путь, Р6) — оркестратор записи СПЕКИ. Зеркало onPlanWrite, но
|
||||
* вердикт по спеке (runMentorSpecVerdict, binding specHash — хеш артефакта спеки). task-id
|
||||
* анкорится specHash (спека первая в стэке спека+план → план переиспользует тот же task-id).
|
||||
* Журнал переговоров — best-effort (SE10). I/O/llmCall инъектируются.
|
||||
* Журнал переговоров — best-effort (SE10). I/O/llmCall инъектируются. roundMemory (SP2c-2)
|
||||
* — память кругов M-side спеки, протягивается до построителя вердикта.
|
||||
*/
|
||||
export async function onSpecWrite({
|
||||
specContent = '',
|
||||
@@ -107,6 +110,7 @@ export async function onSpecWrite({
|
||||
verifiedContext = [],
|
||||
negotiationLog = [],
|
||||
graphSection = null,
|
||||
roundMemory = {},
|
||||
} = {}) {
|
||||
// ✅O17: существующий task-id побеждает; спека анкорит стэк (спека+план) по specHash.
|
||||
const taskId = deriveTaskId({ existingTaskId, firstPlanHash: specHash });
|
||||
@@ -114,7 +118,7 @@ export async function onSpecWrite({
|
||||
if (taskId && typeof persistTaskIdImpl === 'function') {
|
||||
try { persistTaskIdImpl(taskId); taskIdPersisted = true; } catch { taskIdPersisted = false; }
|
||||
}
|
||||
const r = await runMentorSpecVerdictImpl({ specContent, specHash, verifiedContext, negotiationLog, graphSection, llmCall });
|
||||
const r = await runMentorSpecVerdictImpl({ specContent, specHash, verifiedContext, negotiationLog, graphSection, roundMemory, llmCall });
|
||||
let journal = null;
|
||||
let journalOk = false;
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// tools/on-plan-write.test.mjs
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { onPlanWrite } from './on-plan-write.mjs';
|
||||
import { onPlanWrite, onSpecWrite } from './on-plan-write.mjs';
|
||||
import { planId } from './plan-lock.mjs';
|
||||
|
||||
const STEPS = [{ n: 1, op: 'Write', object: 'tools/a.mjs' }];
|
||||
@@ -100,3 +100,26 @@ describe('onPlanWrite — скил-сверка через classify (мерж)',
|
||||
expect(capturedUser).not.toMatch(/#4(?! — )/); // голого #4 без имени нет
|
||||
});
|
||||
});
|
||||
|
||||
describe('оркестратор протягивает roundMemory до построителя (SP2c-2)', () => {
|
||||
it('onPlanWrite → roundMemory доходит до промпта вердикта', async () => {
|
||||
let capturedUser = null;
|
||||
await onPlanWrite({
|
||||
planSteps: STEPS,
|
||||
roundMemory: { objections: ['память круга плана'] },
|
||||
llmCall: async ({ buildPrompt }) => { capturedUser = buildPrompt().user; return goodVerdict; },
|
||||
journalKey: 'k', nowMs: 1,
|
||||
});
|
||||
expect(capturedUser).toContain('память круга плана');
|
||||
});
|
||||
it('onSpecWrite → roundMemory доходит до промпта вердикта спеки', async () => {
|
||||
let capturedUser = null;
|
||||
await onSpecWrite({
|
||||
specContent: '# Спека', specHash: 'SH',
|
||||
roundMemory: { objections: ['память круга спеки'] },
|
||||
llmCall: async ({ buildPrompt }) => { capturedUser = buildPrompt().user; return goodVerdict; },
|
||||
journalKey: 'k', nowMs: 1,
|
||||
});
|
||||
expect(capturedUser).toContain('память круга спеки');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user