/** * Drift lock: the inline platform-catalogue tables embedded in the BA SKILL.md * files (deployed standalone, markdown-only) must stay byte-consistent with the * canonical `lib/platform-catalog.ts`. Edit BOTH or this suite fails. * * - `platform-apps:v1` → carried by create-menu + audit-menu (the menu skills) * - `platform-hr-entities:v1` → carried by audit-cross-ref-code (CODE-005) */ import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import { PLATFORM_APPS, PLATFORM_HR_ENTITIES } from '../platform-catalog.js' const skillsRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..') const APPS_CARRIERS = [ 'business-analyse/create-menu/SKILL.md', 'business-analyse/audit-menu/SKILL.md', ] const HR_ENTITIES_CARRIERS = [ 'business-analyse/audit-cross-ref-code/SKILL.md', ] function readSkill(rel: string): string { return readFileSync(join(skillsRoot, rel), 'utf8') } function extractBlock(md: string, marker: string, file: string): string { const re = new RegExp(`([\\s\\S]*?)`) const m = md.match(re) if (!m) throw new Error(`marker "${marker}" not found in ${file}`) return m[1] } function tableRows(block: string): string[][] { return block .split('\n') .map(l => l.trim()) .filter(l => l.startsWith('|')) .filter(l => !/^\|(\s*:?-+:?\s*\|)+$/.test(l)) .slice(1) // drop the header row .map(l => l.split('|').slice(1, -1).map(c => c.trim())) } function parseAliasCell(cell: string): string[] { return cell === '—' || cell === '' ? [] : cell.split(',').map(a => a.trim()) } describe.each(APPS_CARRIERS)('platform-catalog drift — %s', (file) => { it('carries the platform-apps:v1 table equal to PLATFORM_APPS', () => { const rows = tableRows(extractBlock(readSkill(file), 'platform-apps:v1', file)).map(cells => ({ code: cells[0], label: cells[1], isPersonal: cells[2] === 'yes', extendable: cells[3] === 'yes', aliases: parseAliasCell(cells[4]).sort(), })) const expected = PLATFORM_APPS.map(a => ({ code: a.code, label: a.label, isPersonal: a.isPersonal, extendable: a.extendable, aliases: [...a.aliases].sort(), })) expect(rows).toEqual(expected) }) }) describe.each(HR_ENTITIES_CARRIERS)('platform-catalog drift — %s', (file) => { it('carries the platform-hr-entities:v1 table equal to PLATFORM_HR_ENTITIES', () => { const rows = tableRows(extractBlock(readSkill(file), 'platform-hr-entities:v1', file)).map(cells => ({ name: cells[0], table: cells[1].replace(/`/g, ''), aliases: parseAliasCell(cells[2]).sort(), })) const expected = PLATFORM_HR_ENTITIES.map(e => ({ name: e.name, table: e.table, aliases: [...e.aliases].sort(), })) expect(rows).toEqual(expected) }) })