#!/usr/bin/env node /** Парсер объявленных в плане скилов (мерж роутер↔наставник). Зеркало parseVerifiedContext: * ищет fenced-блок ```skills-json со списком строк. Нет/битый → []. */ export function parsePlanSkills(content) { const m = String(content ?? '').match(/```skills-json\s*\n([\s\S]*?)\n```/i); if (!m) return []; let arr; try { arr = JSON.parse(m[1]); } catch { return []; } if (!Array.isArray(arr)) return []; return arr.filter((s) => typeof s === 'string' && s.trim()); } /** Цель плана для classify(): ТЕЛО секции «## Цель» / «## Goal» целиком — от строки под * заголовком до первой границы: следующий заголовок `\n## `, начало fenced-блока (` \n``` `) * или истинный конец текста (`$(?![\s\S])`). Многострочное тело возвращается полностью (раньше * multiline-`$` обрывал на первой строке). При нескольких секциях «## Цель» берётся ПЕРВАЯ * (`String.match` = первое совпадение). Фолбэк (секции цели нет): «осмысленный абзац» — первый * непустой абзац, НЕ заголовок (`#`) и НЕ fenced-блок (` ``` `); fenced-блоки (skills/steps/ * verified-context) и маркеры шапки телом цели не являются. Зеркало extractGoal судьи. */ export function extractPlanGoal(content) { const text = String(content ?? ''); const m = text.match(/^##\s*(?:Цель|Goal)[^\n]*\n([\s\S]*?)(?=\n##\s|\n```|$(?![\s\S]))/im); if (m && m[1].trim()) return m[1].trim(); const para = text.split(/\n\s*\n/).map((s) => s.trim()) .find((s) => s && !s.startsWith('#') && !s.startsWith('```')); return para || ''; } /** Пометка поставки плана: `**Delivery:** internal|user-result`. По умолчанию/мусор → 'internal' * (fail-safe: владельца не дёргаем без явной пометки результата). Зеркало parsePlanSkills. */ export function parsePlanDelivery(content) { const m = String(content ?? '').match(/(^|\n)\*\*Delivery:\*\*\s*(internal|user-result)\b/i); return m ? m[2].toLowerCase() : 'internal'; } /** D1: пометка типа плана: `**Kind:** deploy`. По умолчанию/мусор → 'normal' (fail-safe: * благословлённый ops-runbook-канал enforce-floor применяется ТОЛЬКО к явному deploy-плану). * Зеркало parsePlanDelivery. */ export function parsePlanKind(content) { const m = String(content ?? '').match(/(^|\n)\*\*Kind:\*\*\s*(deploy|normal)\b/i); return m ? m[2].toLowerCase() : 'normal'; }