From b21385d13c5902d59acb8712908e09be3404e245 Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 15 Jun 2026 12:52:12 +0000 Subject: [PATCH 1/2] fix(coding-agent): hid plugin skills from slash commands Removed Claude marketplace plugin skill mirroring from slash-command discovery while keeping real plugin commands registered.\n\nFixes #2645 --- packages/coding-agent/CHANGELOG.md | 4 ++ .../src/discovery/claude-plugins.ts | 45 ++--------------- .../test/discovery/claude-plugins.test.ts | 49 +++---------------- 3 files changed, 14 insertions(+), 84 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 93cfaebf9..0376b7fc5 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Claude marketplace plugin skills appearing as bare slash commands while keeping real plugin `commands/` entries available ([#2645](https://github.com/can1357/oh-my-pi/issues/2645)). + ## [15.13.3] - 2026-06-15 ### Added diff --git a/packages/coding-agent/src/discovery/claude-plugins.ts b/packages/coding-agent/src/discovery/claude-plugins.ts index a1497fd96..df1021f20 100644 --- a/packages/coding-agent/src/discovery/claude-plugins.ts +++ b/packages/coding-agent/src/discovery/claude-plugins.ts @@ -124,43 +124,6 @@ async function loadSkills(ctx: LoadContext): Promise> { } return { items, warnings }; } -async function loadSkillSlashCommands(ctx: LoadContext, root: ClaudePluginRoot): Promise> { - const { dir: skillsDir, warning } = await resolvePluginDir(root, ["skills"], "skills"); - const warnings: string[] = warning ? [warning] : []; - const skillsResult = await scanSkillsFromDir(ctx, { - dir: skillsDir, - providerId: PROVIDER_ID, - level: root.scope, - }); - warnings.push(...(skillsResult.warnings ?? [])); - - const commands = await Promise.all( - skillsResult.items.map(async skill => { - const content = await readFile(skill.path); - if (content === null) { - warnings.push(`Failed to read skill slash command: ${skill.path}`); - return null; - } - // Slash command name MUST come from the skill directory basename, not - // frontmatter `name`: `expandSlashCommand` splits the command at the first - // whitespace, so a display name like "Understand Anything" would never match - // `/understand`. The documented layout is `skills//SKILL.md` → `/`. - const command: SlashCommand = { - name: path.basename(path.dirname(skill.path)), - path: skill.path, - content, - level: skill.level, - _source: skill._source, - }; - return command; - }), - ); - - return { - items: commands.filter((command): command is SlashCommand => command !== null), - warnings, - }; -} // ============================================================================= // Slash Commands @@ -189,16 +152,14 @@ async function loadSlashCommands(ctx: LoadContext): Promise { expect(found).toBeDefined(); expect(found?.path).toContain(path.join(".claude", "skills", "manifest-skill", "SKILL.md")); }); - test("exposes plugin skills as bare slash commands", async () => { - const pluginsDir = path.join(tempDir, ".omp", "plugins"); - const pluginPath = path.join(tempDir, ".omp", "plugins", "cache", "plugins", "understand-anything"); + test("keeps plugin skills out of slash commands while loading them as skills", async () => { + const pluginsDir = path.join(tempDir, ".claude", "plugins"); + const pluginPath = path.join(tempDir, "plugins", "understand-anything"); await fs.mkdir(pluginsDir, { recursive: true }); await fs.mkdir(path.join(pluginPath, "skills", "understand"), { recursive: true }); @@ -384,45 +384,10 @@ describe("listClaudePluginRoots", () => { ); const commands = await loadSlashCommands({ cwd: tempDir }); - const found = commands.find(command => command.name === "understand"); + const skills = await loadCapability("skills", { cwd: tempDir }); - expect(found?.description).toBe("Build an understanding graph"); - expect(expandSlashCommand("/understand --language zh", commands)).toContain("Analyze the project."); - }); - test("uses skill directory basename when frontmatter name contains spaces", async () => { - const pluginsDir = path.join(tempDir, ".omp", "plugins"); - const pluginPath = path.join(tempDir, ".omp", "plugins", "cache", "plugins", "display-name-skill"); - await fs.mkdir(pluginsDir, { recursive: true }); - await fs.mkdir(path.join(pluginPath, "skills", "understand"), { recursive: true }); - - const registry = { - version: 2, - plugins: { - "display-name-skill@display-name-skill": [ - { - scope: "user", - installPath: pluginPath, - version: "1.0.0", - installedAt: "2026-06-12T00:00:00Z", - lastUpdated: "2026-06-12T00:00:00Z", - }, - ], - }, - }; - - await fs.writeFile(path.join(pluginsDir, "installed_plugins.json"), JSON.stringify(registry)); - await fs.writeFile( - path.join(pluginPath, "skills", "understand", "SKILL.md"), - "---\nname: Understand Anything\ndescription: Build an understanding graph\n---\nAnalyze the project.\n", - ); - - const commands = await loadSlashCommands({ cwd: tempDir }); - // Skill is registered by directory basename so `/understand` resolves, - // even though the frontmatter `name` is the multi-word display label. - const found = commands.find(command => command.name === "understand"); - expect(found?.description).toBe("Build an understanding graph"); - expect(commands.find(command => command.name === "Understand Anything")).toBeUndefined(); - expect(expandSlashCommand("/understand", commands)).toContain("Analyze the project."); + expect(commands.find(command => command.name === "understand")).toBeUndefined(); + expect(skills.all.find(skill => skill.name === "understand")?.frontmatter?.description).toBe("Build an understanding graph"); }); test("reads slash commands directory from plugin manifest slash-commands field", async () => { From 40ad1c6a7be7bac8ce06ed7e74ae81a6923f6d2c Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 15 Jun 2026 12:52:36 +0000 Subject: [PATCH 2/2] style: bun run fix --- packages/coding-agent/test/discovery/claude-plugins.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/test/discovery/claude-plugins.test.ts b/packages/coding-agent/test/discovery/claude-plugins.test.ts index df89faf5e..589e3272d 100644 --- a/packages/coding-agent/test/discovery/claude-plugins.test.ts +++ b/packages/coding-agent/test/discovery/claude-plugins.test.ts @@ -387,7 +387,9 @@ describe("listClaudePluginRoots", () => { const skills = await loadCapability("skills", { cwd: tempDir }); expect(commands.find(command => command.name === "understand")).toBeUndefined(); - expect(skills.all.find(skill => skill.name === "understand")?.frontmatter?.description).toBe("Build an understanding graph"); + expect(skills.all.find(skill => skill.name === "understand")?.frontmatter?.description).toBe( + "Build an understanding graph", + ); }); test("reads slash commands directory from plugin manifest slash-commands field", async () => {