Files
brain/tools/registry-load.mjs
T
Дмитрий 612eabc228 refactor(registry): Этап A сноса цепочек L — schema/nodes/registry-load без chains
Этап 3 эпика «роутер-реестр» (фундамент): убран реестр именованных цепочек L1-L17
из schema.json (required/свойство chains/chain_membership/определение chain), nodes.yaml
(153 строки chain_membership + секция chains), registry-load.mjs (chains/findChainsByNode)
и его теста. recommended_chain и прочие границы D5 не тронуты. Полный свод зелёный
(263 файла, 4412 тестов). node-graph/router-classifier (Этап B) устойчивы к отсутствию chains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:51:49 +03:00

72 lines
2.4 KiB
JavaScript

// tools/registry-load.mjs
// Pure module — загрузка и индексация реестра узлов.
// Никаких сетевых вызовов, никакого фс-исключения кроме чтения.
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import yaml from 'js-yaml';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DEFAULT_REGISTRY_PATH = join(__dirname, '..', 'docs', 'registry', 'nodes.yaml');
const DEFAULT_SCHEMA_PATH = join(__dirname, '..', 'docs', 'registry', 'schema.json');
let _cached = null;
export function loadRegistry({ registryPath = DEFAULT_REGISTRY_PATH, schemaPath = DEFAULT_SCHEMA_PATH, useCache = true } = {}) {
if (useCache && _cached) return _cached;
const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
const data = yaml.load(readFileSync(registryPath, 'utf-8'));
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
const validate = ajv.compile(schema);
if (!validate(data)) {
throw new Error(`Registry schema violation:\n${JSON.stringify(validate.errors, null, 2)}`);
}
const indexById = new Map();
for (const node of data.nodes) indexById.set(node.id, node);
const indexByTrigger = new Map();
for (const node of data.nodes) {
if (node.status !== 'active') continue;
for (const t of node.triggers || []) {
const key = t.classification ? `cls:${t.classification}` :
t.keyword ? `kw:${t.keyword.toLowerCase()}` :
t.file_pattern ? `fp:${t.file_pattern}` : null;
if (!key) continue;
if (!indexByTrigger.has(key)) indexByTrigger.set(key, []);
indexByTrigger.get(key).push({ node, weight: t.weight ?? 1.0 });
}
}
const result = {
nodes: data.nodes,
version: data.version,
indexById,
indexByTrigger,
};
if (useCache) _cached = result;
return result;
}
export function clearCache() { _cached = null; }
export function findByClassification(registry, classification) {
return (registry.indexByTrigger.get(`cls:${classification}`) || [])
.sort((a, b) => b.weight - a.weight);
}
export function findByKeyword(registry, keyword) {
return (registry.indexByTrigger.get(`kw:${keyword.toLowerCase()}`) || [])
.sort((a, b) => b.weight - a.weight);
}
export function findActiveNodes(registry) {
return registry.nodes.filter(n => n.status === 'active');
}