diff --git a/tools/secretary-reconcile.mjs b/tools/secretary-reconcile.mjs index 44881dd..a8e088b 100644 --- a/tools/secretary-reconcile.mjs +++ b/tools/secretary-reconcile.mjs @@ -92,7 +92,11 @@ function allTexts(p) { /** Куски записи без дословных повторов: split(' — ') от «text — why», dedup по первому вхождению. */ function canonicalClauses(text, why) { - const raw = (why != null && String(why).trim() !== '') ? `${text}${SEP}${why}` : String(text || ''); + // Срез служебной метки зачёркивания, впечённой моделью в текст (запрос рисует «[зачёркнуто] X», + // слабая модель копирует строку в поле text). Только ведущий наш маркер — реальный текст с него + // не начинается; марочные клоны после среза дают тот же канон и сливаются с чистой записью. + const cleanText = String(text || '').replace(/^(?:\[зачёркнуто\]\s*)+/, ''); + const raw = (why != null && String(why).trim() !== '') ? `${cleanText}${SEP}${why}` : cleanText; const seen = new Set(); const out = []; for (const part of raw.split(SEP).map((s) => s.trim()).filter(Boolean)) { diff --git a/tools/secretary-reconcile.test.mjs b/tools/secretary-reconcile.test.mjs index 005a6b0..cfdbf0b 100644 --- a/tools/secretary-reconcile.test.mjs +++ b/tools/secretary-reconcile.test.mjs @@ -231,6 +231,17 @@ describe('collapseProtocol — детерминированное схлопыв const p = { open: [{ text: 'Хайку или Sonnet? — Хайку или Sonnet?', struck: false, turns: [1] }] }; expect(collapseProtocol(p).open[0].text).toBe('Хайку или Sonnet?'); }); + it('срезает впечённую метку «[зачёркнуто]» из текста и сливает с чистой записью', () => { + const p = { decisions: [ + { text: 'Выбрано X', why: 'причина', struck: true, turns: [3] }, + { text: '[зачёркнуто] [зачёркнуто] Выбрано X', why: 'причина', struck: true, turns: [31] }, + ] }; + const out = collapseProtocol(p); + expect(out.decisions).toHaveLength(1); + expect(out.decisions[0].text).toBe('Выбрано X'); + expect(out.decisions[0].turns).toEqual([3, 31]); + expect(JSON.stringify(out)).not.toContain('[зачёркнуто]'); + }); it('hidden / steps / nextSvId не трогаются', () => { const p = { decisions: [], hidden: [{ id: 'СВ-1' }], steps: [{ turn: 1 }], nextSvId: 5 }; const out = collapseProtocol(p);