diff --git a/src/tools/skill/tools.test.ts b/src/tools/skill/tools.test.ts index b5551d151..5c7282766 100644 --- a/src/tools/skill/tools.test.ts +++ b/src/tools/skill/tools.test.ts @@ -670,3 +670,58 @@ describe("skill tool - nativeSkills integration", () => { expect(result).toContain("External plugin skill body") }) }) + +describe("skill tool - short name resolution", () => { + it("resolves namespaced skill by short name when unambiguous", async () => { + // given + const loadedSkills = [createMockSkill("superpowers/systematic-debugging")] + const tool = createSkillTool({ skills: loadedSkills }) + + // when + const result = await tool.execute({ name: "systematic-debugging" }, mockContext) + + // then + expect(result).toContain("superpowers/systematic-debugging") + }) + + it("still resolves by exact full name", async () => { + // given + const loadedSkills = [createMockSkill("superpowers/systematic-debugging")] + const tool = createSkillTool({ skills: loadedSkills }) + + // when + const result = await tool.execute({ name: "superpowers/systematic-debugging" }, mockContext) + + // then + expect(result).toContain("superpowers/systematic-debugging") + }) + + it("does not resolve short name when ambiguous (multiple matches)", async () => { + // given + const loadedSkills = [ + createMockSkill("superpowers/debugging"), + createMockSkill("utils/debugging"), + ] + const tool = createSkillTool({ skills: loadedSkills }) + + // when / then — should not resolve (ambiguous), should suggest both + await expect(tool.execute({ name: "debugging" }, mockContext)).rejects.toThrow( + "not found" + ) + }) + + it("prefers exact match over short name match", async () => { + // given — "debugging" exists as both exact and as part of a namespace + const loadedSkills = [ + createMockSkill("debugging"), + createMockSkill("superpowers/debugging"), + ] + const tool = createSkillTool({ skills: loadedSkills }) + + // when + const result = await tool.execute({ name: "debugging" }, mockContext) + + // then — should match "debugging" exactly, not "superpowers/debugging" + expect(result).toContain("## Skill: debugging") + }) +}) diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index 70d2016e3..68ac1a827 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -324,7 +324,19 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition const requestedName = args.name.replace(/^\//, "") // Check skills first (exact match, case-insensitive) - const matchedSkill = skills.find(s => s.name.toLowerCase() === requestedName.toLowerCase()) + let matchedSkill = skills.find(s => s.name.toLowerCase() === requestedName.toLowerCase()) + + // Fallback: try matching by short name (basename) for namespaced skills + // e.g. "systematic-debugging" matches "superpowers/systematic-debugging" + if (!matchedSkill) { + const shortNameMatches = skills.filter(s => { + const parts = s.name.split("/") + return parts.length > 1 && parts[parts.length - 1].toLowerCase() === requestedName.toLowerCase() + }) + if (shortNameMatches.length === 1) { + matchedSkill = shortNameMatches[0] + } + } if (matchedSkill) { if (matchedSkill.definition.agent && (!ctx?.agent || matchedSkill.definition.agent !== ctx.agent)) {