refactor(router): Этап B1 сноса цепочек L — node-graph без chains/hintLinksOf

Этап 3 эпика «роутер-реестр» (B1): из tools/node-graph.mjs убрана поддержка chains
(в buildNodeGraph) и функция hintLinksOf (живого импортёра нет); тест приведён в
соответствие (без hintLinksOf-блока и литералов chains). twinsOf/conflictsOf/resolveNode/
checkGraphFreshness сохранены. Полный свод зелёный (263 файла, 4414 тестов — прогон в чистом
терминале владельца; под Bash сессии воркеры коллапсируют от контеншна).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-20 16:15:10 +03:00
parent 0103388bc0
commit 4249535eea
2 changed files with 9 additions and 39 deletions
+6 -24
View File
@@ -2,17 +2,15 @@
/**
* node-graph — стабильный граф УЗЛОВ из реестра (3.1/3.2/3.3 + 3.6). Узлы =
* скилы/MCP/агенты из docs/registry/nodes.yaml; рёбра-ПОДСКАЗКИ (не рецепты):
* близнецы (общий subcategory) / связи (со-членство в chains) / конфликты
* (явные attributes.conflicts_with). Резолв скила в узел детерминированный
* (ОВ-Д2: выдумка → null). Без LLM. Потребляет registry-объект {nodes, chains} —
* обычно от loadRegistry, но принимает любой совместимый литерал (развязка для
* тестов/инвариантов).
* близнецы (общий subcategory) / конфликты (явные attributes.conflicts_with).
* Резолв скила в узел детерминированный (ОВ-Д2: выдумка → null). Без LLM.
* Потребляет registry-объект {nodes} — обычно от loadRegistry, но принимает
* любой совместимый литерал (развязка для тестов/инвариантов).
*/
/** Построить граф из registry ({nodes, chains}). Индексы id/slug/name + субкатегории. */
/** Построить граф из registry ({nodes}). Индексы id/slug/name + субкатегории. */
export function buildNodeGraph(registry) {
const nodes = (registry && registry.nodes) || [];
const chains = (registry && registry.chains) || {};
const byId = new Map(), bySlug = new Map(), byName = new Map();
const subcategoryIndex = new Map();
for (const n of nodes) {
@@ -24,7 +22,7 @@ export function buildNodeGraph(registry) {
subcategoryIndex.get(n.subcategory).push(n);
}
}
return { nodes, chains, byId, bySlug, byName, subcategoryIndex };
return { nodes, byId, bySlug, byName, subcategoryIndex };
}
/**
@@ -63,22 +61,6 @@ export function twinsOf(graph, ref) {
.filter((n) => n !== self && n.status === 'active');
}
/** Связи-подсказки — узлы, со-встречающиеся с этим в любой цепочке (без себя), дедуп. */
export function hintLinksOf(graph, ref) {
const self = resolveNode(graph, ref);
if (!self) return [];
const seen = new Set(), out = [];
for (const chain of Object.values(graph.chains || {})) {
const members = (chain.sequence || []).map((s) => resolveNode(graph, s)).filter(Boolean);
if (!members.includes(self)) continue;
for (const m of members) {
if (m === self || seen.has(m.id)) continue;
seen.add(m.id); out.push(m);
}
}
return out;
}
/** Конфликты — явные attributes.conflicts_with (массив ref'ов), резолвятся в узлы. Free-text не парсим. */
export function conflictsOf(graph, ref) {
const self = resolveNode(graph, ref);