- Ny tests/reference-integrity.test.mjs: alle ${CLAUDE_PLUGIN_ROOT}-stier i
commands/agents må finnes på disk OG være shippbare (aldri under gitignored
.claude/). Fanger død-referanse-klassen permanent (TDD: rød på
freshen-references før fiks). Suite 135 -> 138.
- Døde referanser lukket: freshen-references peker nå på metrics-library
«Review-kadens»; metrics-library-provenanslinje uten død relativ sti.
- Kryssref-isolasjon: «Ressurser/Interne referanser»-seksjon i de 5 isolerte
KB-filene (examples, oboard-guide, meeting-guides, calculator, cfr).
- «Sist oppdatert»-markør på alle 17 referansefiler (var 4).
- 19 -> 20 antipatterns (SKILL, kvalitet x2, help — help-forekomst funnet i
sweep utover review-lista); «15. oktober» -> tidlig oktober (arshjul x2,
framework); garblet prognoseformel rettet mot calculator-kanon (framework).
- Typonits: schuld/Næste (meeting-guides), resultatmål (dfo-mapping),
sandbagging (trendanalytiker), Q1-2026 -> T1-2026 (fremdriftssporer).
- README: hooks-badge 4 -> 3, død Stop-rad fjernet.
- BACKLOG.md foldet inn i docs/roadmap.md (tema-nivå, ingen interne
plandetaljer) og slettet; .gitignore ROADMAP.md ankret til rot (/ROADMAP.md)
så docs/roadmap.md kan trackes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
3.1 KiB
JavaScript
81 lines
3.1 KiB
JavaScript
// 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');
|
|
});
|