#!/usr/bin/env node /** * skeleton-radar (§5.7 ДР-3) — раздел-уровень: grep заголовков → Claude отчитывается по * КАЖДОМУ (touched/not-relevant/not-read). Чистое ядро (headers + reports инъектируются). * Непрорепорченный ИЛИ невалидно-прорепорченный пункт → not-reported (fail-safe: молчание * = «не отчитался», а не «не релевантно»). */ export const REPORT_STATUSES = Object.freeze(['touched', 'not-relevant', 'not-read', 'not-reported']); const VALID = new Set(['touched', 'not-relevant', 'not-read']); export function buildSkeletonRadar({ headers, reports } = {}) { const list = Array.isArray(headers) ? headers : []; const rep = (reports && typeof reports === 'object') ? reports : {}; const items = list.map((h) => { const s = Object.prototype.hasOwnProperty.call(rep, h) ? rep[h] : undefined; // F-F4 (L2-пол): пункт заморожен — машинная мутация status бросает TypeError; // отчёт живёт в reports (вход), не правкой выхода. return Object.freeze({ header: h, status: VALID.has(s) ? s : 'not-reported' }); }); const unreported = items.filter((i) => i.status === 'not-reported'); // F-F3: пустой скелет виден (headers:0 ≠ «всё отчитано») — зеркало F-F1 graph-radar. return { items, unreported, summary: { headers: list.length, unreported: unreported.length } }; }