diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 41df88f8b6a..16fa1d08f6a 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -32,44 +32,59 @@ export namespace Skill { }), ) - const SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") + const OPENCODE_SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") + const CLAUDE_SKILL_GLOB = new Bun.Glob(".claude/skills/**/SKILL.md") export const state = Instance.state(async () => { const directories = await Config.directories() const skills: Record = {} + const addSkill = async (match: string) => { + const md = await ConfigMarkdown.parse(match) + if (!md) { + return + } + + const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) + if (!parsed.success) return + + // Warn on duplicate skill names + if (skills[parsed.data.name]) { + log.warn("duplicate skill name", { + name: parsed.data.name, + existing: skills[parsed.data.name].location, + duplicate: match, + }) + } + + skills[parsed.data.name] = { + name: parsed.data.name, + description: parsed.data.description, + location: match, + } + } + for (const dir of directories) { - for await (const match of SKILL_GLOB.scan({ + for await (const match of OPENCODE_SKILL_GLOB.scan({ cwd: dir, absolute: true, onlyFiles: true, followSymlinks: true, })) { - const md = await ConfigMarkdown.parse(match) - if (!md) { - continue - } - - const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) - if (!parsed.success) continue - - // Warn on duplicate skill names - if (skills[parsed.data.name]) { - log.warn("duplicate skill name", { - name: parsed.data.name, - existing: skills[parsed.data.name].location, - duplicate: match, - }) - } - - skills[parsed.data.name] = { - name: parsed.data.name, - description: parsed.data.description, - location: match, - } + await addSkill(match) } } + for await (const match of CLAUDE_SKILL_GLOB.scan({ + cwd: Instance.worktree, + absolute: true, + onlyFiles: true, + followSymlinks: true, + dot: true, + })) { + await addSkill(match) + } + return skills }) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 4a1d75f9fca..1da8105bd83 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -101,31 +101,31 @@ test("returns empty array when no skills exist", async () => { }) }) -// test("discovers skills from .claude/skills/ directory", async () => { -// await using tmp = await tmpdir({ -// git: true, -// init: async (dir) => { -// const skillDir = path.join(dir, ".claude", "skills", "claude-skill") -// await Bun.write( -// path.join(skillDir, "SKILL.md"), -// `--- -// name: claude-skill -// description: A skill in the .claude/skills directory. -// --- +test("discovers skills from .claude/skills/ directory", async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + const skillDir = path.join(dir, ".claude", "skills", "claude-skill") + await Bun.write( + path.join(skillDir, "SKILL.md"), + `--- +name: claude-skill +description: A skill in the .claude/skills directory. +--- -// # Claude Skill -// `, -// ) -// }, -// }) +# Claude Skill +`, + ) + }, + }) -// await Instance.provide({ -// directory: tmp.path, -// fn: async () => { -// const skills = await Skill.all() -// expect(skills.length).toBe(1) -// expect(skills[0].name).toBe("claude-skill") -// expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") -// }, -// }) -// }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const skills = await Skill.all() + expect(skills.length).toBe(1) + expect(skills[0].name).toBe("claude-skill") + expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") + }, + }) +})