// tools/registry-render.mjs // Pure script — рендерит секции реестра в Markdown auto-region. // Запуск: node tools/registry-render.mjs [--check] // --check режим: возвращает exit 1 если rendered ≠ файлу (lefthook job). import { readFileSync, writeFileSync } from 'fs'; import { join, dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; import { loadRegistry } from './registry-load.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = join(__dirname, '..'); const REGIONS = [ { file: join(REPO_ROOT, 'docs', 'Tooling_v8_3.md'), marker: 'tooling-registry-summary', renderer: renderToolingSummary, }, { file: join(REPO_ROOT, 'docs', 'routing-off-phase.md'), marker: 'routing-table', renderer: renderRoutingTable, }, ]; function renderToolingSummary(registry) { const lines = ['', '']; lines.push(`| ID | Узел | Категория | Статус |`); lines.push(`|---|---|---|---|`); for (const node of registry.nodes) { lines.push(`| ${node.id} | ${node.name} | ${node.category} | ${node.status} |`); } lines.push(''); return lines.join('\n'); } function renderRoutingTable(registry) { const lines = ['', '']; const byClass = new Map(); for (const node of registry.nodes) { if (node.status !== 'active') continue; for (const t of node.triggers) { if (!t.classification) continue; if (!byClass.has(t.classification)) byClass.set(t.classification, []); byClass.get(t.classification).push({ node, weight: t.weight ?? 1.0 }); } } lines.push(`| Классификация | Рекомендуемый узел | Вес |`); lines.push(`|---|---|---|`); for (const [cls, entries] of [...byClass.entries()].sort()) { entries.sort((a, b) => b.weight - a.weight); for (const e of entries) { lines.push(`| \`${cls}\` | ${e.node.id} ${e.node.name} | ${e.weight} |`); } } lines.push(''); return lines.join('\n'); } export function replaceRegion(content, marker, rendered) { const start = ``; const end = ``; const startIdx = content.indexOf(start); const endIdx = content.indexOf(end); if (startIdx === -1 || endIdx === -1) { throw new Error(`Markers not found`); } const before = content.slice(0, startIdx + start.length); const after = content.slice(endIdx); return `${before}\n${rendered}\n${after}`; } export function renderAll({ check = false } = {}) { const registry = loadRegistry(); const results = []; for (const region of REGIONS) { const current = readFileSync(region.file, 'utf-8'); const rendered = region.renderer(registry); const next = replaceRegion(current, region.marker, rendered); const drift = current !== next; if (check && drift) { results.push({ file: region.file, drift: true }); continue; } if (!check && drift) { writeFileSync(region.file, next, 'utf-8'); } results.push({ file: region.file, drift }); } return results; } // Точка входа CLI // Используем fileURLToPath + resolve для совместимости с кириллическими путями (Windows quirk) const __filename = fileURLToPath(import.meta.url); if (process.argv[1] && resolve(process.argv[1]) === __filename) { const check = process.argv.includes('--check'); const results = renderAll({ check }); for (const r of results) { console.log(`${r.drift ? '⚠️ drift' : '✅ ok '} ${r.file}`); } if (check && results.some(r => r.drift)) { console.error('\nDrift detected. Run `node tools/registry-render.mjs` to regenerate.'); process.exit(1); } }