#!/usr/bin/env node /** * Brain governance controller C6 — chain-map sync checker. * Verifies tools/observer-chain-map.json against the L1-L13 table in * docs/routing-off-phase.md. Sync is checked by L-number sets (both * directions), not by node names — node_chosen values (skill-id) differ * from the human display names in the .md table. Pure fs/regex, no LLM. */ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); const MD_PATH = join(__dirname, '..', 'docs', 'routing-off-phase.md'); const JSON_PATH = join(__dirname, 'observer-chain-map.json'); /** Extract the set of L-numbers ("L1".."L13") from the routing-off-phase.md table. */ export function parseChainsFromMd(md) { const set = new Set(); for (const line of md.split(/\r?\n/)) { const m = /^\|\s*(L\d+)\s*\|/.exec(line.trim()); if (m) set.add(m[1]); } return set; } /** Compare JSON L-numbers against the md set, both directions. */ export function checkSync(jsonMap, mdSet) { const jsonSet = new Set(); for (const [node, chains] of Object.entries(jsonMap)) { if (node === '_note') continue; if (Array.isArray(chains)) for (const c of chains) jsonSet.add(c); } const jsonOnly = [...jsonSet].filter((c) => !mdSet.has(c)); // ссылки на несуществующие L const mdOnly = [...mdSet].filter((c) => !jsonSet.has(c)); // потерянные цепочки return { ok: jsonOnly.length === 0 && mdOnly.length === 0, jsonOnly, mdOnly }; } /** CLI entry — exit 1 on drift with a human-readable message. */ function main() { const md = readFileSync(MD_PATH, 'utf8'); const jsonMap = JSON.parse(readFileSync(JSON_PATH, 'utf8')); const mdSet = parseChainsFromMd(md); if (mdSet.size === 0) { console.error( '[chain-map-checker] не нашёл ни одной L-строки в routing-off-phase.md — формат таблицы изменился?' ); process.exit(1); } const res = checkSync(jsonMap, mdSet); if (res.ok) { console.log(`[chain-map-checker] OK — ${mdSet.size} chains in sync`); process.exit(0); } console.error('[chain-map-checker] дрейф маппинга chain-map <-> routing-off-phase.md:'); if (res.jsonOnly.length) console.error(` JSON ссылается на отсутствующие в .md цепочки: ${res.jsonOnly.join(', ')}`); if (res.mdOnly.length) console.error( ` В .md есть цепочки без записи в JSON: ${res.mdOnly.join(', ')} — добавьте узлы в tools/observer-chain-map.json` ); process.exit(1); } if (process.argv[1]?.endsWith('observer-chain-map-checker.mjs')) { main(); }