feat: round-memory врезки записи памяти кругов в живую оркестрацию SP2c-1 ч2

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-16 16:41:05 +03:00
parent 8f25ce32e1
commit edea1ea40c
4 changed files with 53 additions and 4 deletions
+11
View File
@@ -472,6 +472,17 @@ async function main() {
let taskId = null;
try { taskId = loadTaskId({ sessionId: (event && event.session_id) || 'unknown', runtimeDir: runtimeDir(), fsImpl: fsDefault }); } catch { taskId = null; }
const n = bumpJudgeNoGo({ taskId, sessionId: (event && event.session_id) || 'unknown', blocked: isNoGo });
// SP2c-1: дословное замечание судьи на NO-GO в дорожку judge. Best-effort, fail-quiet.
if (isNoGo) {
try {
const fp = String((event && event.tool_input && event.tool_input.file_path) || '');
const stage = SPEC_PATH_RE.test(fp) ? 'spec' : (PLAN_PATH_RE.test(fp) ? 'plan' : null);
if (stage) {
const rm = await import('./round-memory-record.mjs');
rm.recordSideObjection(taskId, stage, 'judge', formatJudgeObjection(result.verdict));
}
} catch { /* fail-quiet */ }
}
if (isNoGo && n >= JUDGE_ESCALATE_AFTER) {
const planContent = String((event && event.tool_input && event.tool_input.content) ?? '');
result = { ...result, message: buildJudgeArbitrationMessage(result.verdict, planContent, n) };
+11
View File
@@ -232,6 +232,17 @@ async function main() {
// Фаза 1 (Р2): на NO-GO/degraded — ПОЛНЫЙ текст доходит до контроллера через рабочий
// exit-2 канал (подтверждён Фазой 0). На 3-м NO-GO — карточка арбитража.
const planContent = String((event.tool_input && event.tool_input.content) ?? '');
// SP2c-1: память кругов — снимок версии (наставник раньше судьи в цепочке) +
// дословное замечание наставника на NO-GO. Best-effort, fail-quiet.
try {
const fp = String((event.tool_input && event.tool_input.file_path) || '');
const stage = SPEC_PATH_RE.test(fp) ? 'spec' : (PLAN_PATH_RE.test(fp) ? 'plan' : null);
if (stage) {
const rm = await import('./round-memory-record.mjs');
rm.recordArtifact(res.taskId, stage, planContent);
if (blocked) rm.recordSideObjection(res.taskId, stage, 'mentor', formatMentorObjection(res));
}
} catch { /* fail-quiet */ }
const decision = decideMentorObjection({ res, planContent, n });
// Способ B (Task 2.2): наставник НЕ печатает. На GO — записывает подписанное одобрение
// (mentor-GO, binding plan_hash); печать сделает судья (хук ПОСЛЕ) при валидном mentor-GO.
+12 -2
View File
@@ -2,8 +2,8 @@
/** round-memory-record — orchestration-помощник записи памяти кругов (SP2c-1).
* Цель — минимизировать врезки в discipline-source: одна точка пишет снимок версии артефакта
* и доводы контроллера последнего круга «## Переговоры» по дорожкам M/J. Объекция стороны
* (NO-GO) пишется отдельным вызовом recordObjection прямо из стора. Fail-quiet. */
import { recordVersion, recordArg } from './round-memory-store.mjs';
* (NO-GO) пишется через recordSideObjection. Fail-quiet. */
import { recordVersion, recordArg, recordObjection } from './round-memory-store.mjs';
import { parseNegotiationSection } from './negotiation-section.mjs';
/** На запись артефакта: снимок версии + доводы ПОСЛЕДНЕГО круга по дорожкам M/J.
@@ -20,3 +20,13 @@ export function recordArtifact(taskId, stage, content, baseDir) {
return true;
} catch { return false; }
}
/** На объекцию стороны (NO-GO): дословное замечание в дорожку side∈{mentor,judge}.
* Пустой текст не пишется. Любая ошибка → no-op (false), не кидает. */
export function recordSideObjection(taskId, stage, side, text, baseDir) {
try {
const t = String(text == null ? '' : text);
if (!t) return false;
return recordObjection(taskId, stage, side, t, baseDir);
} catch { return false; }
}
+19 -2
View File
@@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { recordArtifact } from './round-memory-record.mjs';
import { getVersions, getArgs } from './round-memory-store.mjs';
import { recordArtifact, recordSideObjection } from './round-memory-record.mjs';
import { getVersions, getArgs, getObjections } from './round-memory-store.mjs';
const dir = () => mkdtempSync(join(tmpdir(), 'rmrec-'));
@@ -35,3 +35,20 @@ describe('recordArtifact', () => {
expect(() => recordArtifact('t1', 'plan', null, '\0bad')).not.toThrow();
});
});
describe('recordSideObjection', () => {
it('пишет дословное замечание стороны в дорожку', () => {
const d = dir();
recordSideObjection('t1', 'plan', 'judge', 'замечание судьи', d);
expect(getObjections('t1', 'plan', 'judge', d)).toEqual(['замечание судьи']);
});
it('пустой текст не пишется', () => {
const d = dir();
recordSideObjection('t1', 'plan', 'mentor', '', d);
recordSideObjection('t1', 'plan', 'mentor', null, d);
expect(getObjections('t1', 'plan', 'mentor', d)).toEqual([]);
});
it('fail-quiet на мусоре не кидает', () => {
expect(() => recordSideObjection('t1', 'plan', 'judge', 'x', '\0bad')).not.toThrow();
});
});