import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; import os from "node:os"; import { loadPlugin } from "./loader.js"; describe("loadPlugin", () => { let tempDir: string; beforeEach(() => { tempDir = path.join(os.tmpdir(), `locode-plugin-${Math.random().toString(36).slice(2)}`); mkdirSync(tempDir, { recursive: true }); }); afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); }); it("loads a skill with references/*.md files", () => { mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true }); mkdirSync(path.join(tempDir, "skills", "dataviz", "references"), { recursive: true }); writeFileSync( path.join(tempDir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: "test-plugin" }), ); writeFileSync(path.join(tempDir, "skills", "dataviz", "SKILL.md"), "---\ndescription: dataviz skill\n---\nmain skill"); writeFileSync(path.join(tempDir, "skills", "dataviz", "references", "colors.md"), "use blue"); const plugin = loadPlugin(tempDir); expect(plugin.skills).toHaveLength(1); expect(plugin.skills[0]).toMatchObject({ name: "dataviz", content: "main skill" }); expect(plugin.skills[0]?.references).toEqual([{ name: "colors", content: "use blue" }]); }); it("loads commands and agents", () => { mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true }); writeFileSync(path.join(tempDir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: "test-plugin" })); mkdirSync(path.join(tempDir, "commands"), { recursive: true }); writeFileSync(path.join(tempDir, "commands", "hello.md"), "---\ndescription: Say hello\n---\nHello $ARGUMENTS"); mkdirSync(path.join(tempDir, "agents"), { recursive: true }); writeFileSync(path.join(tempDir, "agents", "review.md"), "---\ndescription: Code reviewer\n---\nYou review code."); const plugin = loadPlugin(tempDir); expect(plugin.commands).toHaveLength(1); expect(plugin.commands[0]).toMatchObject({ name: "hello", description: "Say hello" }); expect(plugin.agents).toHaveLength(1); expect(plugin.agents[0]).toMatchObject({ name: "review", description: "Code reviewer" }); }); it("parses a command's allowed-tools frontmatter, translating Claude Code tool names", () => { mkdirSync(path.join(tempDir, ".claude-plugin"), { recursive: true }); writeFileSync(path.join(tempDir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: "test-plugin" })); mkdirSync(path.join(tempDir, "commands"), { recursive: true }); writeFileSync( path.join(tempDir, "commands", "readonly.md"), "---\ndescription: Look but don't touch\nallowed-tools: Read, Grep\n---\nInvestigate $ARGUMENTS", ); writeFileSync(path.join(tempDir, "commands", "unrestricted.md"), "---\ndescription: No restriction\n---\nDo $ARGUMENTS"); const plugin = loadPlugin(tempDir); const readonly = plugin.commands.find((c) => c.name === "readonly"); const unrestricted = plugin.commands.find((c) => c.name === "unrestricted"); expect(readonly?.allowedTools).toEqual(["read_file", "grep"]); expect(unrestricted?.allowedTools).toBeUndefined(); }); });