#!/usr/bin/env node /** * objection-format — дословные сводки возражений для блок-сообщений (П1). * Чистые форматтеры (презентация, не логика гейта): судья и наставник. * Fail-safe: мусор/нет возражений → пустая строка. */ /** * Дословная сводка возражений судьи. Две формы: * - результат runJudge — блокирующие в `blocking[{anchor:{ref},severity}]` (приоритет); * - сырой parseJudgeResponse — `objections[{anchor:{ref},severity}]` (fallback). */ export function formatJudgeObjection(verdict) { const blocking = verdict && Array.isArray(verdict.blocking) ? verdict.blocking : null; const objs = (blocking && blocking.length) ? blocking : (verdict && Array.isArray(verdict.objections) ? verdict.objections : []); const lines = objs .filter((o) => o && o.anchor && o.anchor.ref) .map((o) => `— [${o.severity || 'light'}] ${o.anchor.ref}`); return lines.join('\n'); } /** Дословная сводка замечания наставника (результат onPlanWrite {ok,wired,reason,verdict}). * Р7/мерж: NO-GO = decision==='NO-GO' ИЛИ сломанный вердикт (ok!==true). Доносит СУТЬ — * recommendation (что править) + reasoning (разбор) + plan_points_addressed (по пунктам, * включая скил-конкретику) + reason сбоя. GO → пустая строка (не заворот). */ export function formatMentorObjection(r) { if (!r || r.wired !== true) return ''; const v = r.verdict || {}; const isNoGo = v.decision === 'NO-GO' || r.ok !== true; if (!isNoGo) return ''; const lines = ['Замечание наставника:']; if (typeof v.recommendation === 'string' && v.recommendation.trim()) lines.push(`Что править: ${v.recommendation.trim()}`); if (typeof v.reasoning === 'string' && v.reasoning.trim()) lines.push(`Разбор: ${v.reasoning.trim()}`); const pts = Array.isArray(v.plan_points_addressed) ? v.plan_points_addressed.filter((p) => typeof p === 'string' && p.trim()) : []; if (pts.length) lines.push('По пунктам:', ...pts.map((p) => `— ${p}`)); if (typeof r.reason === 'string' && r.reason.trim()) lines.push(`(${r.reason.trim()})`); return lines.length > 1 ? lines.join('\n') : ''; }