397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
2.7 KiB
JavaScript
83 lines
2.7 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,
|
|
chains: data.chains,
|
|
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');
|
|
}
|
|
|
|
export function findChainsByNode(registry, nodeIdOrSlug) {
|
|
const chains = [];
|
|
for (const [chainId, chain] of Object.entries(registry.chains)) {
|
|
if (chain.sequence.some(s => s === nodeIdOrSlug)) {
|
|
chains.push({ chainId, ...chain });
|
|
}
|
|
}
|
|
return chains;
|
|
}
|