import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createSkillManagerTool } from "../../.opencode/tools/skill_manager.js"; import { type RuntimeSessionContext } from "../../src/runtime/sessionContext.js"; import { SkillStore } from "../../src/skills/store.js"; describe("skill_manager tool", () => { let tempDir: string; let skillStore: SkillStore; let context: RuntimeSessionContext; const toolContext = { abort: new AbortController().signal, agent: "test", ask: (() => undefined) as never, directory: "", messageID: "message-1", metadata: () => undefined, sessionID: "session-1", worktree: "", }; const skillDocument = (body: string) => [ "---", "name: pressure-review", "description: Pressure review workflow.", "---", "", body, ].join("\n"); beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "tjwater-skill-tool-")); skillStore = new SkillStore( join(tempDir, "skills"), join(tempDir, "backup", "skills"), ); context = { actorKey: "actor-1", allowLearningWrite: true, clientSessionId: "client-session-1", projectKey: "project-1", sessionId: "session-1", traceId: "trace-1", }; }); afterEach(async () => { await rm(tempDir, { force: true, recursive: true }); }); it("dispatches skill-level write, overwrite, and remove actions", async () => { const tool = createSkillManagerTool( skillStore, { read: () => context }, Promise.resolve(), ); const writeResult = JSON.parse( await tool.execute( { action: "write_skill", content: skillDocument("# Pressure Review"), reason: "verified reusable workflow", skill_path: "workflow/pressure-review", }, toolContext, ) as string, ); expect(writeResult.decision).toBe("accepted"); await expect(readFile(writeResult.target, "utf8")).resolves.toContain( "# Pressure Review\n", ); const updateResult = JSON.parse( await tool.execute( { action: "write_skill", content: skillDocument("# Updated Pressure Review"), reason: "verified reusable workflow overwrite", skill_path: "workflow/pressure-review", }, toolContext, ) as string, ); expect(updateResult.decision).toBe("accepted"); await expect(readFile(updateResult.target, "utf8")).resolves.toContain( "# Updated Pressure Review\n", ); const removeResult = JSON.parse( await tool.execute( { action: "remove_skill", reason: "workflow is obsolete", skill_path: "workflow/pressure-review", }, toolContext, ) as string, ); expect(removeResult.decision).toBe("accepted"); await expect(readFile(removeResult.target, "utf8")).rejects.toThrow(); }); it("writes the root skills index through the reserved alias", async () => { const tool = createSkillManagerTool( skillStore, { read: () => context }, Promise.resolve(), ); const writeResult = JSON.parse( await tool.execute( { action: "write_skill", content: [ "---", "name: skills", "description: TJWater Skills root index.", "---", "", "# TJWater Skills", ].join("\n"), reason: "refresh root skills index", skill_path: "__root__", }, toolContext, ) as string, ); expect(writeResult.decision).toBe("accepted"); await expect(readFile(writeResult.target, "utf8")).resolves.toContain( "# TJWater Skills\n", ); }); });