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)
+ })
})