Merge pull request #4248 from MoerAI/fix/getskillbyname-short-name
fix(opencode-skill-loader): align getSkillByName with short-name matching (fixes #4183)
This commit is contained in:
@@ -703,4 +703,90 @@ Skill body.
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("getSkillByName", () => {
|
||||||
|
it("#given a discoverable skill #when getSkillByName is called with the exact full name #then it returns the skill", async () => {
|
||||||
|
// given - a skill with a plain (non-namespaced) name
|
||||||
|
const skillContent = `---
|
||||||
|
name: my-exact-skill
|
||||||
|
description: A skill resolvable by exact name
|
||||||
|
---
|
||||||
|
Body.
|
||||||
|
`
|
||||||
|
createTestSkill("my-exact-skill", skillContent)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const { getSkillByName } = await import("./loader")
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skill = await getSkillByName("my-exact-skill", { includeClaudeCodePaths: false })
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(skill).toBeDefined()
|
||||||
|
expect(skill?.name).toBe("my-exact-skill")
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given a namespaced skill #when getSkillByName is called with its unique short name #then it returns the skill", async () => {
|
||||||
|
// given - a namespaced skill that is the unique short-name match
|
||||||
|
const skillContent = `---
|
||||||
|
name: superpowers/systematic-debugging
|
||||||
|
description: Namespaced skill the agent should be able to load by short name
|
||||||
|
---
|
||||||
|
Body.
|
||||||
|
`
|
||||||
|
createTestSkill("systematic-debugging", skillContent)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const { getSkillByName } = await import("./loader")
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skill = await getSkillByName("systematic-debugging", { includeClaudeCodePaths: false })
|
||||||
|
|
||||||
|
// then - the short-name lookup must succeed, mirroring matchSkillByName semantics
|
||||||
|
expect(skill).toBeDefined()
|
||||||
|
expect(skill?.name).toBe("superpowers/systematic-debugging")
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given two namespaced skills sharing a short name #when getSkillByName is called with that short name #then it returns undefined (ambiguous)", async () => {
|
||||||
|
// given - two skills under different namespaces with the same short name
|
||||||
|
const skillA = `---
|
||||||
|
name: alpha/duplicated
|
||||||
|
description: Skill A
|
||||||
|
---
|
||||||
|
Body A.
|
||||||
|
`
|
||||||
|
const skillB = `---
|
||||||
|
name: beta/duplicated
|
||||||
|
description: Skill B
|
||||||
|
---
|
||||||
|
Body B.
|
||||||
|
`
|
||||||
|
createTestSkill("alpha-duplicated", skillA)
|
||||||
|
createTestSkill("beta-duplicated", skillB)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const { getSkillByName } = await import("./loader")
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skill = await getSkillByName("duplicated", { includeClaudeCodePaths: false })
|
||||||
|
|
||||||
|
// then - ambiguous short-name match must NOT resolve, matching matchSkillByName behavior
|
||||||
|
expect(skill).toBeUndefined()
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
findProjectClaudeSkillDirs,
|
findProjectClaudeSkillDirs,
|
||||||
findProjectOpencodeSkillDirs,
|
findProjectOpencodeSkillDirs,
|
||||||
} from "../../shared/project-discovery-dirs"
|
} from "../../shared/project-discovery-dirs"
|
||||||
|
import { matchSkillByName } from "../../tools/skill/skill-matcher"
|
||||||
import type { CommandDefinition } from "../claude-code-command-loader/types"
|
import type { CommandDefinition } from "../claude-code-command-loader/types"
|
||||||
import type { LoadedSkill } from "./types"
|
import type { LoadedSkill } from "./types"
|
||||||
import { skillsToCommandDefinitionRecord } from "./skill-definition-record"
|
import { skillsToCommandDefinitionRecord } from "./skill-definition-record"
|
||||||
@@ -122,7 +123,7 @@ export async function discoverSkills(options: DiscoverSkillsOptions = {}): Promi
|
|||||||
|
|
||||||
export async function getSkillByName(name: string, options: DiscoverSkillsOptions = {}): Promise<LoadedSkill | undefined> {
|
export async function getSkillByName(name: string, options: DiscoverSkillsOptions = {}): Promise<LoadedSkill | undefined> {
|
||||||
const skills = await discoverSkills(options)
|
const skills = await discoverSkills(options)
|
||||||
return skills.find(s => s.name === name)
|
return matchSkillByName(skills, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discoverUserClaudeSkills(): Promise<LoadedSkill[]> {
|
export async function discoverUserClaudeSkills(): Promise<LoadedSkill[]> {
|
||||||
|
|||||||
Reference in New Issue
Block a user