// reference-integrity.test.mjs // B1 (1.7.1): referanse-integritet for alle ${CLAUDE_PLUGIN_ROOT}-stier i // commands/ og agents/. Fanger doed-referanse-klassen permanent (review 4: // freshen-references pekte paa gitignored .claude/-sti som aldri shippes). // To invarianter per referert sti: // 1. Stien maa finnes paa disk (relativt til plugin-rot). // 2. Stien maa vaere shippbar: aldri under .claude/ (gitignored, finnes // lokalt men ikke i installert plugin — ren existsSync er falsk groenn). // Glob-stier (* i siste segment) sjekkes som: katalog finnes + minst ett treff. // Zero npm deps. Moenster: tests/package-shape.test.mjs (les fil, assert). import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync, readdirSync, existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const SCAN_DIRS = ['commands', 'agents']; // Stopper paa whitespace, backtick, anfoerselstegn, ), ] — tegnene som // avslutter en sti i markdown-prosa/lenker. Ingen legitime stier her // inneholder dem. const PATH_RE = /\$\{CLAUDE_PLUGIN_ROOT\}\/([^\s`"')\]]+)/g; function collectReferences() { const refs = []; for (const dir of SCAN_DIRS) { for (const name of readdirSync(join(ROOT, dir))) { if (!name.endsWith('.md')) continue; const file = join(dir, name); const body = readFileSync(join(ROOT, file), 'utf8'); for (const m of body.matchAll(PATH_RE)) { refs.push({ file, rel: m[1] }); } } } return refs; } function assertResolvable(rel) { const starName = rel.lastIndexOf('*'); if (starName === -1) { assert.ok(existsSync(join(ROOT, rel)), `finnes ikke: ${rel}`); return; } // Glob: kun *-i-siste-segment brukes i repoet (f.eks. references/*.md). const dir = dirname(rel); assert.ok(!dir.includes('*'), `glob i katalogsegment stoettes ikke: ${rel}`); assert.ok(existsSync(join(ROOT, dir)), `glob-katalog finnes ikke: ${dir}`); const suffix = rel.slice(starName + 1); const hits = readdirSync(join(ROOT, dir)).filter((n) => n.endsWith(suffix)); assert.ok(hits.length > 0, `glob uten treff: ${rel}`); } test('commands/agents refererer minst en plugin-rot-sti (regex-sanity)', () => { const refs = collectReferences(); assert.ok(refs.length >= 10, `fant bare ${refs.length} referanser — regex broken?`); }); test('alle ${CLAUDE_PLUGIN_ROOT}-stier er shippbare (aldri under .claude/)', () => { const offenders = collectReferences().filter(({ rel }) => rel === '.claude' || rel.startsWith('.claude/')); assert.deepEqual( offenders.map((o) => `${o.file} -> ${o.rel}`), [], 'gitignored .claude/-stier shippes aldri med pluginen' ); }); test('alle ${CLAUDE_PLUGIN_ROOT}-stier finnes paa disk', () => { const missing = []; for (const { file, rel } of collectReferences()) { try { assertResolvable(rel); } catch (e) { missing.push(`${file} -> ${rel} (${e.message})`); } } assert.deepEqual(missing, [], 'doede referanser funnet'); });