From f7b4b98e0d8ef4541a8e4d780047daaad52c4050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 29 May 2026 10:20:11 +0300 Subject: [PATCH] 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) --- tools/router-gate-decide.mjs | 12 ++++++++++++ tools/router-gate-decide.test.mjs | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tools/router-gate-decide.mjs create mode 100644 tools/router-gate-decide.test.mjs diff --git a/tools/router-gate-decide.mjs b/tools/router-gate-decide.mjs new file mode 100644 index 00000000..c7dce650 --- /dev/null +++ b/tools/router-gate-decide.mjs @@ -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 + ); +} diff --git a/tools/router-gate-decide.test.mjs b/tools/router-gate-decide.test.mjs new file mode 100644 index 00000000..3f725b78 --- /dev/null +++ b/tools/router-gate-decide.test.mjs @@ -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); + }); +});