diff --git a/src/features/claude-code-command-loader/loader.test.ts b/src/features/claude-code-command-loader/loader.test.ts new file mode 100644 index 00000000..1d5c9586 --- /dev/null +++ b/src/features/claude-code-command-loader/loader.test.ts @@ -0,0 +1,59 @@ +/// + +import { afterEach, describe, expect, it } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { loadOpencodeGlobalCommands, loadOpencodeProjectCommands } from "./loader" + +const testRoots: string[] = [] + +function createTempRoot(): string { + const root = join(tmpdir(), `command-loader-${Date.now()}-${Math.random().toString(16).slice(2)}`) + mkdirSync(root, { recursive: true }) + testRoots.push(root) + return root +} + +function writeCommand(dir: string, name: string): void { + mkdirSync(dir, { recursive: true }) + writeFileSync( + join(dir, `${name}.md`), + "---\ndescription: command from test\n---\nUse this command" + ) +} + +afterEach(() => { + for (const root of testRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }) + } + delete process.env.OPENCODE_CONFIG_DIR +}) + +describe("claude-code-command-loader OpenCode paths", () => { + it("loads commands from global OpenCode commands directory", async () => { + // given + const root = createTempRoot() + const opencodeConfigDir = join(root, "config") + writeCommand(join(opencodeConfigDir, "commands"), "global-opencode") + process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir + + // when + const commands = await loadOpencodeGlobalCommands() + + // then + expect(commands["global-opencode"]).toBeDefined() + }) + + it("loads commands from project OpenCode commands directory", async () => { + // given + const root = createTempRoot() + writeCommand(join(root, ".opencode", "commands"), "project-opencode") + + // when + const commands = await loadOpencodeProjectCommands(root) + + // then + expect(commands["project-opencode"]).toBeDefined() + }) +}) diff --git a/src/features/claude-code-command-loader/loader.ts b/src/features/claude-code-command-loader/loader.ts index adf2cc4c..ae1a07e6 100644 --- a/src/features/claude-code-command-loader/loader.ts +++ b/src/features/claude-code-command-loader/loader.ts @@ -122,13 +122,13 @@ export async function loadProjectCommands(directory?: string): Promise> { const configDir = getOpenCodeConfigDir({ binary: "opencode" }) - const opencodeCommandsDir = join(configDir, "command") + const opencodeCommandsDir = join(configDir, "commands") const commands = await loadCommandsFromDir(opencodeCommandsDir, "opencode") return commandsToRecord(commands) } export async function loadOpencodeProjectCommands(directory?: string): Promise> { - const opencodeProjectDir = join(directory ?? process.cwd(), ".opencode", "command") + const opencodeProjectDir = join(directory ?? process.cwd(), ".opencode", "commands") const commands = await loadCommandsFromDir(opencodeProjectDir, "opencode-project") return commandsToRecord(commands) } diff --git a/src/hooks/auto-slash-command/executor.test.ts b/src/hooks/auto-slash-command/executor.test.ts new file mode 100644 index 00000000..54c1c559 --- /dev/null +++ b/src/hooks/auto-slash-command/executor.test.ts @@ -0,0 +1,63 @@ +/// + +import { afterEach, describe, expect, it } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { executeSlashCommand } from "./executor" + +const testRoots: string[] = [] + +function createTempRoot(): string { + const root = join(tmpdir(), `auto-slash-executor-${Date.now()}-${Math.random().toString(16).slice(2)}`) + mkdirSync(root, { recursive: true }) + testRoots.push(root) + return root +} + +function writeCommand(dir: string, name: string): void { + mkdirSync(dir, { recursive: true }) + writeFileSync( + join(dir, `${name}.md`), + "---\ndescription: command from test\n---\nRun from OpenCode command directory" + ) +} + +afterEach(() => { + for (const root of testRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }) + } + delete process.env.OPENCODE_CONFIG_DIR +}) + +describe("auto-slash-command executor OpenCode paths", () => { + it("resolves commands from OpenCode global and project plural directories", async () => { + // given + const root = createTempRoot() + const opencodeConfigDir = join(root, "config") + writeCommand(join(opencodeConfigDir, "commands"), "global-cmd") + writeCommand(join(root, ".opencode", "commands"), "project-cmd") + process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir + + const originalCwd = process.cwd() + process.chdir(root) + + try { + // when + const globalResult = await executeSlashCommand( + { command: "global-cmd", args: "", raw: "/global-cmd" }, + { skills: [] } + ) + const projectResult = await executeSlashCommand( + { command: "project-cmd", args: "", raw: "/project-cmd" }, + { skills: [] } + ) + + // then + expect(globalResult.success).toBe(true) + expect(projectResult.success).toBe(true) + } finally { + process.chdir(originalCwd) + } + }) +}) diff --git a/src/hooks/auto-slash-command/executor.ts b/src/hooks/auto-slash-command/executor.ts index ffa96be8..4bdfd9ee 100644 --- a/src/hooks/auto-slash-command/executor.ts +++ b/src/hooks/auto-slash-command/executor.ts @@ -105,8 +105,8 @@ async function discoverAllCommands(options?: ExecutorOptions): Promise + +import { afterEach, describe, expect, it } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" import * as slashcommand from "./index" +const testRoots: string[] = [] + +function createTempRoot(): string { + const root = join(tmpdir(), `slashcommand-discovery-${Date.now()}-${Math.random().toString(16).slice(2)}`) + mkdirSync(root, { recursive: true }) + testRoots.push(root) + return root +} + +afterEach(() => { + for (const root of testRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }) + } + delete process.env.OPENCODE_CONFIG_DIR +}) + describe("slashcommand module exports", () => { it("exports discovery API only", () => { // given @@ -14,4 +35,32 @@ describe("slashcommand module exports", () => { expect(exportNames).not.toContain("createSlashcommandTool") expect(exportNames).not.toContain("slashcommand") }) + + it("discovers commands from OpenCode plural command directories", () => { + // given + const root = createTempRoot() + const opencodeConfigDir = join(root, "config") + const globalCommandsDir = join(opencodeConfigDir, "commands") + const projectCommandsDir = join(root, ".opencode", "commands") + + mkdirSync(globalCommandsDir, { recursive: true }) + mkdirSync(projectCommandsDir, { recursive: true }) + + writeFileSync( + join(globalCommandsDir, "global-cmd.md"), + "---\ndescription: global command\n---\nGlobal command body" + ) + writeFileSync( + join(projectCommandsDir, "project-cmd.md"), + "---\ndescription: project command\n---\nProject command body" + ) + process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir + + // when + const commands = slashcommand.discoverCommandsSync(root) + + // then + expect(commands.some((cmd) => cmd.name === "global-cmd" && cmd.scope === "opencode")).toBe(true) + expect(commands.some((cmd) => cmd.name === "project-cmd" && cmd.scope === "opencode-project")).toBe(true) + }) })