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:
+6
-24
@@ -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);
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user