diff --git a/tools/node-graph.mjs b/tools/node-graph.mjs index 52001eb..fb38078 100644 --- a/tools/node-graph.mjs +++ b/tools/node-graph.mjs @@ -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); diff --git a/tools/node-graph.test.mjs b/tools/node-graph.test.mjs index c32d562..b8d20b1 100644 --- a/tools/node-graph.test.mjs +++ b/tools/node-graph.test.mjs @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { - buildNodeGraph, resolveNode, twinsOf, hintLinksOf, conflictsOf, checkGraphFreshness, + buildNodeGraph, resolveNode, twinsOf, conflictsOf, checkGraphFreshness, } from './node-graph.mjs'; const REG = { @@ -11,9 +11,6 @@ const REG = { { id: '#38', name: 'architecture-patterns', slug: 'architecture-patterns', subcategory: 'architecture-tooling', status: 'active' }, { id: '#17', name: 'pg_partman', slug: 'pg-partman', subcategory: null, status: 'dormant' }, ], - chains: { - L4: { name: 'diagram', sequence: ['#36', '#37'] }, - }, }; describe('buildNodeGraph', () => { @@ -24,7 +21,7 @@ describe('buildNodeGraph', () => { expect(g.bySlug.get('mermaid').id).toBe('#37'); }); it('accepts a plain registry literal (not loadRegistry-bound)', () => { - const g = buildNodeGraph({ nodes: [{ id: '#1', slug: 'x', name: 'X' }], chains: {} }); + const g = buildNodeGraph({ nodes: [{ id: '#1', slug: 'x', name: 'X' }] }); expect(g.bySlug.get('x').id).toBe('#1'); }); }); @@ -64,21 +61,12 @@ describe('twinsOf (близнецы = общий subcategory, активные)' it('несуществующий ref → []', () => { expect(twinsOf(g, 'nope')).toEqual([]); }); }); -describe('hintLinksOf (связи = со-членство в цепочке)', () => { - const g = buildNodeGraph(REG); - it('соседи по chains, без себя', () => { - expect(hintLinksOf(g, '#36').map((n) => n.id)).toEqual(['#37']); - expect(hintLinksOf(g, '#37').map((n) => n.id)).toEqual(['#36']); - }); - it('узел вне цепочек → []', () => { expect(hintLinksOf(g, '#19')).toEqual([]); }); -}); - describe('conflictsOf (явные attributes.conflicts_with)', () => { it('резолвит явные конфликт-рёбра', () => { const reg = { nodes: [ { id: '#a', slug: 'a', status: 'active', attributes: { conflicts_with: ['#b'] } }, { id: '#b', slug: 'b', status: 'active' }, - ], chains: {} }; + ] }; const g = buildNodeGraph(reg); expect(conflictsOf(g, '#a').map((n) => n.id)).toEqual(['#b']); });