From 3fb98fa51c8c7536088b0ce6e0311b8c8c04fa37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 16 Jun 2026 16:54:39 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20round-memory=20buildRoundMemory=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BC=D1=8F=D1=82=D0=B8=20=D0=BA=D1=80=D1=83=D0=B3=D0=BE=D0=B2?= =?UTF-8?q?=20SP2c-2=20=D0=B8=D0=BD=D0=BA=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- tools/round-memory-store.mjs | 22 ++++++++++++++++++ tools/round-memory-store.test.mjs | 38 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tools/round-memory-store.mjs b/tools/round-memory-store.mjs index db8ef52..349eea3 100644 --- a/tools/round-memory-store.mjs +++ b/tools/round-memory-store.mjs @@ -77,3 +77,25 @@ export function clearRoundMemory(taskId, baseDir) { return true; } catch { return false; } } + +/** SP2c-2: собрать память кругов для построителя промпта (судья J-side / наставник M-side). + * side='judge' → дорожка J, свои judge-замечания, БЕЗ замечания-при-возврате (судья холодный); + * side='mentor' → дорожка M, свои mentor-замечания + ПОСЛЕДНЕЕ замечание судьи (при возврате). + * versionDiff = diff(последняя сохранённая версия, текущая); пусто пока прошлой версии нет + * (круг 1 слеп). Fail-quiet → пустая память. */ +export function buildRoundMemory({ taskId, stage, side, currentContent = '', baseDir } = {}) { + try { + const track = side === 'judge' ? 'J' : 'M'; + const versions = getVersions(taskId, stage, baseDir); + const prev = versions.length ? versions[versions.length - 1] : null; + const versionDiff = prev != null ? diffLines(prev, String(currentContent ?? '')) : ''; + const objections = getObjections(taskId, stage, side, baseDir); + const args = getArgs(taskId, stage, track, baseDir); + let judgeObjectionOnReturn = ''; + if (side === 'mentor') { + const jo = getObjections(taskId, stage, 'judge', baseDir); + if (jo.length) judgeObjectionOnReturn = jo[jo.length - 1]; + } + return { objections, args, versionDiff, judgeObjectionOnReturn }; + } catch { return { objections: [], args: [], versionDiff: '', judgeObjectionOnReturn: '' }; } +} diff --git a/tools/round-memory-store.test.mjs b/tools/round-memory-store.test.mjs index b99614d..e977cf7 100644 --- a/tools/round-memory-store.test.mjs +++ b/tools/round-memory-store.test.mjs @@ -6,6 +6,7 @@ import { recordVersion, getVersions, diffVersions, recordObjection, getObjections, recordArg, getArgs, clearRoundMemory, + buildRoundMemory, } from './round-memory-store.mjs'; const dir = () => mkdtempSync(join(tmpdir(), 'rmem-')); @@ -56,3 +57,40 @@ describe('round-memory-store', () => { expect(getVersions('t1', 'spec', '\0bad')).toEqual([]); }); }); + +describe('buildRoundMemory (SP2c-2)', () => { + it('круг 1 слеп: нет версий → versionDiff пуст, объекции/доводы пусты', () => { + const d = dir(); + const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'v1', baseDir: d }); + expect(m).toEqual({ objections: [], args: [], versionDiff: '', judgeObjectionOnReturn: '' }); + }); + it('judge: только J-дорожка + свои judge-замечания + diff против текущей, без замечания-при-возврате', () => { + const d = dir(); + recordVersion('t1', 'plan', 'строка1\nстрока2', d); + recordObjection('t1', 'plan', 'judge', 'замечание судьи 1', d); + recordObjection('t1', 'plan', 'mentor', 'замечание наставника', d); + recordArg('t1', 'plan', 'J', 'довод судье', d); + recordArg('t1', 'plan', 'M', 'довод наставнику', d); + const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'строка1\nстрока2-изм', baseDir: d }); + expect(m.objections).toEqual(['замечание судьи 1']); + expect(m.args).toEqual(['довод судье']); + expect(m.versionDiff).toContain('строка2'); + expect(m.judgeObjectionOnReturn).toBe(''); + }); + it('mentor: M-дорожка + свои mentor-замечания + ПОСЛЕДНЕЕ замечание судьи', () => { + const d = dir(); + recordVersion('t1', 'spec', 'тело', d); + recordObjection('t1', 'spec', 'mentor', 'замечание наставника 1', d); + recordObjection('t1', 'spec', 'judge', 'замечание судьи A', d); + recordObjection('t1', 'spec', 'judge', 'замечание судьи B', d); + recordArg('t1', 'spec', 'M', 'довод наставнику', d); + const m = buildRoundMemory({ taskId: 't1', stage: 'spec', side: 'mentor', currentContent: 'тело2', baseDir: d }); + expect(m.objections).toEqual(['замечание наставника 1']); + expect(m.args).toEqual(['довод наставнику']); + expect(m.judgeObjectionOnReturn).toBe('замечание судьи B'); + }); + it('fail-quiet на битом baseDir → пустая память', () => { + const m = buildRoundMemory({ taskId: 't1', stage: 'plan', side: 'judge', currentContent: 'x', baseDir: '\0bad' }); + expect(m).toEqual({ objections: [], args: [], versionDiff: '', judgeObjectionOnReturn: '' }); + }); +});