From c637d77965f90883c672413283963862e5d68c3d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 26 Mar 2026 12:59:39 +0900 Subject: [PATCH] fix(commands): discover ancestor opencode project commands Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../claude-code-command-loader/loader.test.ts | 24 +++++++++++++++++++ .../claude-code-command-loader/loader.ts | 5 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/features/claude-code-command-loader/loader.test.ts b/src/features/claude-code-command-loader/loader.test.ts index dde2096c0..be7928d3f 100644 --- a/src/features/claude-code-command-loader/loader.test.ts +++ b/src/features/claude-code-command-loader/loader.test.ts @@ -1,3 +1,4 @@ +import { execFileSync } from "node:child_process" import { afterEach, beforeEach, describe, expect, it } from "bun:test" import { mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" @@ -98,4 +99,27 @@ describe("claude-code command loader", () => { // then expect(commands["duplicate-global"]?.description).toBe("(opencode) Profile global command") }) + + it("#given nested project opencode commands in a worktree #when loadOpencodeProjectCommands is called #then it preserves slash names and stops at the worktree root", async () => { + // given + const repositoryDir = join(TEST_DIR, "repo") + const nestedDirectory = join(repositoryDir, "packages", "app", "src") + mkdirSync(nestedDirectory, { recursive: true }) + execFileSync("git", ["init"], { + cwd: repositoryDir, + stdio: ["ignore", "ignore", "ignore"], + }) + writeCommand(join(repositoryDir, ".opencode", "commands", "deploy"), "staging", "Deploy staging") + writeCommand(join(repositoryDir, ".opencode", "command"), "release", "Release command") + writeCommand(join(TEST_DIR, ".opencode", "commands"), "outside", "Outside command") + + // when + const commands = await loadOpencodeProjectCommands(nestedDirectory) + + // then + expect(commands["deploy/staging"]?.description).toBe("(opencode-project) Deploy staging") + expect(commands.release?.description).toBe("(opencode-project) Release command") + expect(commands.outside).toBeUndefined() + expect(commands["deploy:staging"]).toBeUndefined() + }) }) diff --git a/src/features/claude-code-command-loader/loader.ts b/src/features/claude-code-command-loader/loader.ts index aee1f6e59..b052f56bd 100644 --- a/src/features/claude-code-command-loader/loader.ts +++ b/src/features/claude-code-command-loader/loader.ts @@ -7,7 +7,6 @@ import { findProjectOpencodeCommandDirs, getClaudeConfigDir, getOpenCodeCommandDirs, - getOpenCodeConfigDir, } from "../../shared" import { log } from "../../shared/logger" import type { CommandScope, CommandDefinition, CommandFrontmatter, LoadedCommand } from "./types" @@ -51,7 +50,7 @@ async function loadCommandsFromDir( if (entry.isDirectory()) { if (entry.name.startsWith(".")) continue const subDirPath = join(commandsDir, entry.name) - const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name + const subPrefix = prefix ? `${prefix}/${entry.name}` : entry.name const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix) commands.push(...subCommands) continue @@ -61,7 +60,7 @@ async function loadCommandsFromDir( const commandPath = join(commandsDir, entry.name) const baseCommandName = basename(entry.name, ".md") - const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName + const commandName = prefix ? `${prefix}/${baseCommandName}` : baseCommandName try { const content = await fs.readFile(commandPath, "utf-8")