feat(router-gate): nodeMatches() pure function for recommendation/node match

Migrated from tools/enforce-classifier-match.mjs:42-66 as part of
router-gate Phase 1 Task 1.

Plan: docs/superpowers/plans/2026-05-29-router-gate-hard-wall.md
Spec: docs/superpowers/specs/2026-05-29-router-gate-hard-wall-design-condensed.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-29 10:20:11 +03:00
parent 8b60a18298
commit f7b4b98e0d
2 changed files with 40 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
/**
* Compare router recommendation (e.g. "#19", "superpowers:writing-plans", "writing-plans")
* with a registry node (id/slug/name). Returns true if any match.
*/
export function nodeMatches(recommendation, node) {
if (!recommendation || !node) return false;
return (
recommendation === node.id ||
recommendation === node.slug ||
recommendation === node.name
);
}
+28
View File
@@ -0,0 +1,28 @@
import { describe, it, expect } from 'vitest';
import { nodeMatches } from './router-gate-decide.mjs';
describe('nodeMatches', () => {
it('matches #NN to node.id', () => {
expect(nodeMatches('#19', { name: 'writing-plans', id: '#19', slug: 'superpowers:writing-plans' })).toBe(true);
});
it('matches superpowers:X to canonical slug', () => {
expect(nodeMatches('superpowers:writing-plans', { name: 'writing-plans', id: '#19', slug: 'superpowers:writing-plans' })).toBe(true);
});
it('matches by name', () => {
expect(nodeMatches('writing-plans', { name: 'writing-plans', id: '#19', slug: 'superpowers:writing-plans' })).toBe(true);
});
it('rejects mismatch', () => {
expect(nodeMatches('#20', { name: 'writing-plans', id: '#19', slug: 'superpowers:writing-plans' })).toBe(false);
});
it('handles null recommendation', () => {
expect(nodeMatches(null, { name: 'writing-plans', id: '#19', slug: 'superpowers:writing-plans' })).toBe(false);
});
it('handles null node', () => {
expect(nodeMatches('#19', null)).toBe(false);
});
});