Merge pull request #3013 from code-yeongyu/fix/issue-2971-skill-shortname
fix(skill): resolve namespaced skills by short name
This commit is contained in:
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user