import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { importSkillDirectory, prepareRunSkillPlugin } from "../../src/agent/skillStore.js"; describe("content-addressed Agent skill store", () => { const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); it("imports a skill into an immutable digest directory and materializes a selected run plugin", async () => { const root = await makeRoot(); const source = await makeSkill(root, "typst", "Typst help"); const storeRoot = join(root, "store"); const installed = await importSkillDirectory({ sourceDir: source, storeRoot }); expect(installed).toMatchObject({ name: "typst", description: "Typst help" }); expect(installed.contentDigest).toMatch(/^[a-f0-9]{64}$/); await expect(readFile(join(storeRoot, "versions", installed.contentDigest, "SKILL.md"), "utf8")) .resolves.toContain("name: typst"); const plugin = await prepareRunSkillPlugin({ storeRoot, runId: "run-1", skills: [{ name: "typst", version: "0.15.0", contentDigest: installed.contentDigest }], }); expect(plugin).not.toBeNull(); expect(plugin?.skillIds).toEqual(["cph-runtime:typst"]); await expect(readFile(join(plugin!.root, "skills", "typst", "reference.md"), "utf8")) .resolves.toBe("reference\n"); await plugin?.cleanup(); await expect(readFile(join(plugin!.root, ".claude-plugin", "plugin.json"), "utf8")) .rejects.toMatchObject({ code: "ENOENT" }); }); it("rejects symlinks and detects content tampering before a run", async () => { const root = await makeRoot(); const source = await makeSkill(root, "outline", "Outline"); await symlink(join(source, "reference.md"), join(source, "link.md")); await expect(importSkillDirectory({ sourceDir: source, storeRoot: join(root, "store") })) .rejects.toThrow(/symlink/); await rm(join(source, "link.md")); const storeRoot = join(root, "store"); const installed = await importSkillDirectory({ sourceDir: source, storeRoot }); await writeFile(join(storeRoot, "versions", installed.contentDigest, "reference.md"), "tampered\n"); await expect(prepareRunSkillPlugin({ storeRoot, runId: "run-2", skills: [{ name: "outline", version: "1", contentDigest: installed.contentDigest }], })).rejects.toThrow(/content digest mismatch/); }); async function makeRoot(): Promise { const root = await mkdtemp(join(tmpdir(), "cph-skill-store-")); roots.push(root); return root; } }); async function makeSkill(root: string, name: string, description: string): Promise { const source = join(root, "source", name); await mkdir(source, { recursive: true }); await writeFile(join(source, "SKILL.md"), `---\nname: ${name}\ndescription: ${description}\n---\n# ${name}\n`); await writeFile(join(source, "reference.md"), "reference\n"); return source; }