import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { CURATED_SKILL_IDS, CURATED_SKILL_NAMES, CURATED_SKILL_PLUGIN_NAME, validateCuratedSkillPlugin, } from "../../src/agent/curatedSkills.js"; describe("validateCuratedSkillPlugin", () => { const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); it("accepts exactly the release-owned plugin and returns qualified skill ids", async () => { const root = await skillPluginFixture(); await expect(validateCuratedSkillPlugin(root)).resolves.toEqual({ root, skillIds: CURATED_SKILL_IDS, }); }); it("fails closed when a curated skill is absent from the release", async () => { const root = await skillPluginFixture(); await rm(join(root, "skills", "outline"), { recursive: true }); await expect(validateCuratedSkillPlugin(root)).rejects.toThrow(/curated plugin entry missing: skills\/outline/); }); it("fails closed when a skill manifest name does not match the allowlist", async () => { const root = await skillPluginFixture(); await writeFile(join(root, "skills", "outline", "SKILL.md"), "---\nname: other\n---\n"); await expect(validateCuratedSkillPlugin(root)).rejects.toThrow(/curated skill manifest name mismatch/); }); it("rejects an extra skill directory", async () => { const root = await skillPluginFixture(); await mkdir(join(root, "skills", "extra")); await expect(validateCuratedSkillPlugin(root)).rejects.toThrow(/unexpected curated plugin entry: skills\/extra/); }); it("rejects plugin capabilities outside the reviewed skill catalog", async () => { const root = await skillPluginFixture(); await mkdir(join(root, "hooks")); await expect(validateCuratedSkillPlugin(root)).rejects.toThrow(/unexpected curated plugin entry: hooks/); }); async function skillPluginFixture(): Promise { const root = await mkdtemp(join(tmpdir(), "cph-skills-")); roots.push(root); await mkdir(join(root, ".claude-plugin"), { recursive: true }); await writeFile( join(root, ".claude-plugin", "plugin.json"), JSON.stringify({ name: CURATED_SKILL_PLUGIN_NAME }), ); for (const name of CURATED_SKILL_NAMES) { const source = join(root, "skills", name); await mkdir(source, { recursive: true }); await writeFile(join(source, "SKILL.md"), `---\nname: ${name}\n---\n# ${name}\n`); } return root; } });