#!/usr/bin/env node /** round-memory-store — транзиентная память кругов переговоров по задаче (SP2a). * Хранит версии артефакта, дословные замечания (по стороне mentor|judge), доводы контроллера * (по дорожке M|J). Ключ (taskId, stage∈{spec,plan}). Fail-quiet: любая ошибка → no-op/пусто. * Структура файла — зеркало verdict-surface-store (один JSON на задачу в runtime). */ import { homedir } from 'node:os'; import { join } from 'node:path'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { diffLines } from './version-diff.mjs'; function baseOf(baseDir) { return baseDir || join(homedir(), '.claude', 'runtime'); } function memPath(taskId, baseDir) { return join(baseOf(baseDir), `round-memory-${taskId || 'unknown'}.json`); } function readMem(taskId, baseDir) { try { const p = memPath(taskId, baseDir); if (!existsSync(p)) return {}; const v = JSON.parse(readFileSync(p, 'utf8')); return (v && typeof v === 'object') ? v : {}; } catch { return {}; } } function writeMem(taskId, baseDir, obj) { try { mkdirSync(baseOf(baseDir), { recursive: true }); writeFileSync(memPath(taskId, baseDir), JSON.stringify(obj)); return true; } catch { return false; } } function pushInto(taskId, baseDir, section, key, value) { const m = readMem(taskId, baseDir); m[section] = m[section] || {}; if (!Array.isArray(m[section][key])) m[section][key] = []; m[section][key].push(value); return writeMem(taskId, baseDir, m); } function readArr(taskId, baseDir, section, key) { const m = readMem(taskId, baseDir); const arr = m[section] && m[section][key]; return Array.isArray(arr) ? arr : []; } const str = (t) => String(t == null ? '' : t); export function recordVersion(taskId, stage, text, baseDir) { return pushInto(taskId, baseDir, 'versions', stage, str(text)); } export function getVersions(taskId, stage, baseDir) { return readArr(taskId, baseDir, 'versions', stage); } export function diffVersions(taskId, stage, baseDir) { const v = getVersions(taskId, stage, baseDir); if (v.length < 2) return ''; return diffLines(v[v.length - 2], v[v.length - 1]); } export function recordObjection(taskId, stage, side, text, baseDir) { return pushInto(taskId, baseDir, 'objections', `${stage}:${side}`, str(text)); } export function getObjections(taskId, stage, side, baseDir) { return readArr(taskId, baseDir, 'objections', `${stage}:${side}`); } export function recordArg(taskId, stage, track, text, baseDir) { return pushInto(taskId, baseDir, 'args', `${stage}:${track}`, str(text)); } export function getArgs(taskId, stage, track, baseDir) { return readArr(taskId, baseDir, 'args', `${stage}:${track}`); } export function clearRoundMemory(taskId, baseDir) { try { const p = memPath(taskId, baseDir); if (existsSync(p)) writeFileSync(p, '{}'); 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: '' }; } }