fix(secretary): выключение не затирает модельные «Шаги» (слияние по ходу)
Task 5/5 плана. Ветка off prompt-hook звала buildStepsFromRaw, перезатирая модельные формулировки шагов детерминированными. Новая mergeStepsPreservingText: существующий шаг сохраняется, из сырья достраиваются только пропущенные ходы. Свод секретаря 112/112. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,14 @@ export function buildStepsFromRaw(rawText, session) {
|
||||
});
|
||||
}
|
||||
|
||||
// Слияние «Шагов» при выключении: на КАЖДЫЙ ход из сырья берём существующий шаг (модельная
|
||||
// формулировка) если он есть, иначе достраиваем детерминированно из сырья. Порядок — по сырью
|
||||
// (хронология); модельный текст переживает выключение/нарезку.
|
||||
export function mergeStepsPreservingText(existingSteps, rawText, session) {
|
||||
const have = new Map((Array.isArray(existingSteps) ? existingSteps : []).map((s) => [s.turn, s]));
|
||||
return buildStepsFromRaw(rawText, session).map((r) => (have.has(r.turn) ? have.get(r.turn) : r));
|
||||
}
|
||||
|
||||
// Человекочитаемая строка шага для раздела «Шаги (Слой 1)»: «Ход N — я: … · ты: … · делал: …».
|
||||
// Суть — первая фраза реплики; служебные строки (экономия/coverage/вердикт) отброшены;
|
||||
// «делал» — имена инструментов из действий хода. Название файла полного хода добавляет рендер.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { buildRawRecord, buildStepLine, splitRawIntoTurns, turnFileName, prepareTurnFiles, buildStepsFromRaw, writeFileAtomic } from './secretary-layer1.mjs';
|
||||
import { buildRawRecord, buildStepLine, splitRawIntoTurns, turnFileName, prepareTurnFiles, buildStepsFromRaw, writeFileAtomic, mergeStepsPreservingText } from './secretary-layer1.mjs';
|
||||
|
||||
describe('обезвреживание маркеров на записи (от самозагрязнения лога)', () => {
|
||||
it('маркеры внутри текста реплик/действий не дают лишних структурных совпадений', () => {
|
||||
@@ -112,6 +112,20 @@ describe('buildStepLine', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeStepsPreservingText — выключение не затирает модельный текст', () => {
|
||||
const raw = [
|
||||
'=== ХОД turn=1 · t · session=s ===', '[ЮЗЕР]', 'привет', '[АССИСТЕНТ]', 'хай', '=== КОНЕЦ ХОДА ===',
|
||||
'=== ХОД turn=2 · t · session=s ===', '[ЮЗЕР]', 'вопрос', '[АССИСТЕНТ]', 'ответ', '=== КОНЕЦ ХОДА ===', '',
|
||||
].join('\n');
|
||||
it('существующий шаг сохраняется, пропущенный достраивается из сырья', () => {
|
||||
const existing = [{ turn: 2, session: 's', text: 'Ход 2 — я: МОДЕЛЬНЫЙ · ты: ТЕКСТ · делал: —' }];
|
||||
const out = mergeStepsPreservingText(existing, raw, 's');
|
||||
expect(out.map((s) => s.turn)).toEqual([1, 2]);
|
||||
expect(out.find((s) => s.turn === 2).text).toBe('Ход 2 — я: МОДЕЛЬНЫЙ · ты: ТЕКСТ · делал: —');
|
||||
expect(out.find((s) => s.turn === 1).text).toContain('Ход 1 — я: привет');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeFileAtomic — запись через temp + rename (защита от полузаписи при параллельных сессиях)', () => {
|
||||
it('пишет во временный файл, затем переименовывает в целевой', () => {
|
||||
const calls = [];
|
||||
|
||||
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from
|
||||
import { join, dirname } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { detectSecretaryCommand, secretaryModeFileName, resolveCaseActivation } from './secretary-flag.mjs';
|
||||
import { prepareTurnFiles, buildStepsFromRaw } from './secretary-layer1.mjs';
|
||||
import { prepareTurnFiles, buildStepsFromRaw, mergeStepsPreservingText } from './secretary-layer1.mjs';
|
||||
import { renderProtocol } from './secretary-protocol.mjs';
|
||||
|
||||
function readStdin() { try { return readFileSync(0, 'utf-8'); } catch { return ''; } }
|
||||
@@ -76,7 +76,7 @@ function main() {
|
||||
const raw = readFileSync(rawFile, 'utf-8');
|
||||
const proto = JSON.parse(readFileSync(protoJson, 'utf-8'));
|
||||
// Шаги — на КАЖДЫЙ ход из Слоя 1 (не только вкл-ходы), затем нарезка + ссылки.
|
||||
proto.steps = buildStepsFromRaw(raw, session);
|
||||
proto.steps = mergeStepsPreservingText(proto.steps, raw, session);
|
||||
const { files, steps } = prepareTurnFiles(raw, proto);
|
||||
const hodyDir = join(workDir, 'ходы');
|
||||
mkdirSync(hodyDir, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user