Files
portal/tools/registry-load.test.mjs
T
Дмитрий c7f603aa75 feat(brain): register project-agents delegation rule (Pravila §2.4 + CLAUDE.md §3.9 + registry #84/#85)
Level 1 + Level 2 of agent auto-invocation:

Level 1 — нормативный контракт:
- Pravila §2.4 (new) — controller MUST delegate to project agents:
  * normative-sync (#84) after big task closure (4-file sync trigger)
  * prod-deploy-validator (#85) before any liderra.ru deploy
  * pest-parallel-debugger / rls-reviewer — prior project agents formalized in same table
- CLAUDE.md §3.9 (new) — operational map index of all 4 project agents

Level 2 — наблюдатель (missed-activation detector):
- docs/registry/nodes.yaml +#84 normative-sync, +#85 prod-deploy-validator
  с subcategory: "project-agent" + agent_file: attribute
- triggers.classification: "normative_sync_needed" / "prod_deploy_imminent"
  автоматически подхватываются registry-to-classification-map.mjs runtime;
  deprecated observer-classification-map.json не правится.
- tools/registry-load.test.mjs fixtures: 83→85 / 75→77 active

Tooling канон счётчиков НЕ изменился (#1-#83 остаётся; project-агенты вне Tooling).

Spec: docs/superpowers/specs/2026-05-24-controller-offload-agents-design.md.
Headers: Pravila v1.39→v1.40, CLAUDE.md v2.27→v2.28.

Level 3 (hooks) — defer; level 1+2 покрывают первый раунд автоматизации.

Also: +6 cspell words for new vocabulary in normative paragraphs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:10:28 +03:00

82 lines
2.8 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 (85 nodes after #84/#85 project-agents added 24.05.2026)', () => {
const r = loadRegistry();
expect(r.nodes).toHaveLength(85);
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('#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', () => {
const r = loadRegistry();
const active = findActiveNodes(r);
// 85 nodes total; #1 historic, #17 dormant, #44/#50/#54/#67/#82/#83 deferred,
// #84/#85 (project-agents added 24.05.2026) are active → 75 + 2 = 77 active
expect(active).toHaveLength(77);
expect(active.map(n => n.id)).toContain('#18');
expect(active.map(n => n.id)).toContain('#19');
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();
});
});