forked from bai/curriculum-project-hub
feat: add curated curriculum agent skills
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
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<string> {
|
||||
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;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user