#!/usr/bin/env node /** * negotiation-section — парсер раздела «## Переговоры» план-файла. Возвращает круги * [{round, position}] с ДОСЛОВНЫМ текстом позиции контроллера (для наставника и арбитража). * Раздел = «## Переговоры» до следующего «## », внутри — «### Круг N» блоки. */ export function parseNegotiationSection(md) { const text = typeof md === 'string' ? md : ''; // БЕЗ флага m: `$` = конец ТЕКСТА (не строки), иначе нежадный захват обрывается на 1-й строке. // Заголовки матчим через (?:^|\n), терминатор — следующий «\n## » или конец текста. const sec = text.match(/(?:^|\n)##\s*Переговоры[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i); if (!sec) return []; const body = sec[1]; const out = []; const re = /(?:^|\n)###\s*Круг\s*(\d+)[^\n]*\n([\s\S]*?)(?=\n###\s|$)/gi; let m; while ((m = re.exec(body)) !== null) { const position = m[2].trim(); out.push({ round: Number(m[1]), position, mentor: extractAddressee(position, 'Наставнику'), judge: extractAddressee(position, 'Судье'), }); } return out; } /** Достаёт текст, адресованный стороне (`**Наставнику:**` / `**Судье:**`), до следующего * адресата или конца. Нет адресата → пустая строка (обратная совместимость с position). */ function extractAddressee(body, label) { const re = new RegExp(`\\*\\*${label}:\\*\\*\\s*([\\s\\S]*?)(?=\\n?\\*\\*(?:Наставнику|Судье):\\*\\*|$)`, 'i'); const mm = String(body || '').match(re); return mm ? mm[1].trim() : ''; } /** SP2d: контроллер просит арбитраж на ЛЮБОМ круге — маркер `**Арбитраж:** <причина>` в * артефакте (раздел «Переговоры»). Возвращает причину (truthy) или '' если маркера нет. * Карточка арбитража показывается при этом маркере независимо от круга (потолок ≤3 остаётся). */ export function arbitrationRequested(md) { const text = typeof md === 'string' ? md : ''; const m = text.match(/\*\*Арбитраж:\*\*\s*([^\n]*)/i); if (!m) return ''; return m[1].trim() || 'арбитраж запрошен контроллером'; }