Files
brain/tools/observer-chain-map-checker.mjs
T

68 lines
2.7 KiB
JavaScript

#!/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();
}