0258b9d2a7
Эпик роутер-реестр, спека v2 §2, этап 1. Зонтик product-management развёрнут в 8 карточек-навыков + 8 под-узлов #42a..#42h. - 8 карточек: write-spec, roadmap-update, metrics-review, product-brainstorming, sprint-planning, stakeholder-update, synthesize-research, competitive-brief - nodes.yaml: узел #42 → #42a..#42h; триггеры (planning/prd→write-spec, роадмап→roadmap-update, метрики→metrics-review); ссылка L9 обновлена - product-management:competitive-brief отличён от marketing:competitive-brief (та самая коллизия имён, ради которой имя файла несёт плагин) - зонтик убран; registry-load.test: 117 узлов / 109 active Регрессия (без 5 pre-existing node:test файлов): 4361 passed, exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
// tools/registry-load.test.mjs
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { loadRegistry, clearCache, findByClassification, findByKeyword, findActiveNodes, findChainsByNode } from './registry-load.mjs';
|
|
|
|
describe('registry-load', () => {
|
|
beforeEach(() => clearCache());
|
|
|
|
it('loads registry (117 nodes: разворачивание #33/#19/#57/#36/#42 комков 18.06.2026)', () => {
|
|
const r = loadRegistry();
|
|
expect(r.nodes).toHaveLength(117);
|
|
expect(r.version).toBe('0.1.0');
|
|
});
|
|
|
|
it('indexes by classification', () => {
|
|
const r = loadRegistry();
|
|
const features = findByClassification(r, 'feature');
|
|
expect(features).toHaveLength(1);
|
|
expect(features[0].node.id).toBe('#19a'); // feature → superpowers:brainstorming после разворачивания #19
|
|
expect(features[0].weight).toBe(1.0);
|
|
});
|
|
|
|
it('returns empty array for unknown classification', () => {
|
|
const r = loadRegistry();
|
|
expect(findByClassification(r, 'nonexistent')).toEqual([]);
|
|
});
|
|
|
|
it('indexes by keyword case-insensitive', () => {
|
|
const r = loadRegistry();
|
|
expect(findByKeyword(r, 'tdd')).toHaveLength(1);
|
|
expect(findByKeyword(r, 'TDD')).toHaveLength(1);
|
|
});
|
|
|
|
it('excludes historic/dormant from trigger index', () => {
|
|
const r = loadRegistry();
|
|
// #1 PostgreSQL MCP — historic, не должен попасть в индексы
|
|
for (const entries of r.indexByTrigger.values()) {
|
|
expect(entries.find(e => e.node.id === '#1')).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it('includes historic in nodes array (full reference)', () => {
|
|
const r = loadRegistry();
|
|
expect(r.indexById.get('#1').status).toBe('historic');
|
|
});
|
|
|
|
it('findActiveNodes excludes non-active (nodes.yaml registry)', () => {
|
|
const r = loadRegistry();
|
|
const active = findActiveNodes(r);
|
|
// 117 nodes total; #1 historic, #17 dormant, #44/#50/#54/#67/#82/#83 deferred;
|
|
// развёрнуты комки: #33→2, #19→14, #57→7, #36→2, #42 pm→8 (active +1+13+6+1+7) → 109 active
|
|
expect(active).toHaveLength(109);
|
|
expect(active.map(n => n.id)).toContain('#18');
|
|
expect(active.map(n => n.id)).toContain('#19a');
|
|
expect(active.map(n => n.id)).not.toContain('#1');
|
|
expect(active.map(n => n.id)).not.toContain('#17');
|
|
});
|
|
|
|
it('findChainsByNode returns chain membership', () => {
|
|
const r = loadRegistry();
|
|
// L8 = runtime debug chain: systematic-debugging + Sentry (#34) + Redis (#35)
|
|
const sentryChains = findChainsByNode(r, '#34');
|
|
expect(sentryChains.map(c => c.chainId)).toContain('L8');
|
|
});
|
|
|
|
it('caches across calls in same process', () => {
|
|
const r1 = loadRegistry();
|
|
const r2 = loadRegistry();
|
|
expect(r1).toBe(r2); // same reference
|
|
});
|
|
|
|
it('clearCache forces reload', () => {
|
|
const r1 = loadRegistry();
|
|
clearCache();
|
|
const r2 = loadRegistry();
|
|
expect(r1).not.toBe(r2);
|
|
});
|
|
|
|
it('throws on schema violation', () => {
|
|
expect(() => loadRegistry({ registryPath: '/nonexistent/path.yaml', useCache: false })).toThrow();
|
|
});
|
|
});
|