Files
brain/tools/round-memory-store.mjs

102 lines
4.5 KiB
JavaScript

#!/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: '' }; }
}