// 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'); }