gstack/test/skill-coverage-matrix.test.ts

73 lines
2.5 KiB
TypeScript

/**
* Skill coverage matrix CI gate (v1.45.0.0 T1).
*
* Asserts every skill on disk has an entry in SKILL_COVERAGE with at
* least one gate-tier test. The detailed per-skill structural checks
* live in test/skill-coverage-floor.test.ts; this file is the matrix-
* level gate that surfaces "skill added but eval not registered" cleanly.
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import { SKILL_COVERAGE, type SkillCoverage } from './skill-coverage-matrix';
const REPO_ROOT = path.resolve(import.meta.dir, '..');
function discoverSkills(): string[] {
return fs.readdirSync(REPO_ROOT, { withFileTypes: true })
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
.filter(e => fs.existsSync(path.join(REPO_ROOT, e.name, 'SKILL.md')))
.map(e => e.name)
.sort();
}
describe('skill coverage matrix', () => {
test('SKILL_COVERAGE is exported and non-empty', () => {
expect(typeof SKILL_COVERAGE).toBe('object');
expect(Object.keys(SKILL_COVERAGE).length).toBeGreaterThan(0);
});
test('every entry has the right shape', () => {
for (const [skill, coverage] of Object.entries(SKILL_COVERAGE)) {
expect(Array.isArray(coverage.gate)).toBe(true);
expect(Array.isArray(coverage.periodic)).toBe(true);
expect(coverage.gate.length).toBeGreaterThan(0);
for (const p of [...coverage.gate, ...coverage.periodic]) {
expect(typeof p).toBe('string');
expect(p.startsWith('test/')).toBe(true);
expect(p.endsWith('.test.ts')).toBe(true);
}
}
});
test('every skill on disk has a registry entry', () => {
const skills = discoverSkills();
const missing: string[] = [];
for (const s of skills) {
if (!SKILL_COVERAGE[s]) missing.push(s);
}
if (missing.length > 0) {
throw new Error(
`Skills on disk missing from SKILL_COVERAGE: ${missing.join(', ')}. ` +
`Add an entry to test/skill-coverage-matrix.ts with at least ` +
`'test/skill-coverage-floor.test.ts' in gate[].`,
);
}
});
test('no registry entry references a skill that does not exist on disk', () => {
const skills = new Set(discoverSkills());
const orphans: string[] = [];
for (const skill of Object.keys(SKILL_COVERAGE)) {
if (!skills.has(skill)) orphans.push(skill);
}
if (orphans.length > 0) {
throw new Error(
`Registry references skills not on disk: ${orphans.join(', ')}. ` +
`Remove from SKILL_COVERAGE or restore the skill directory.`,
);
}
});
});