Skip to content

Commit ae67f43

Browse files
authored
feat: add support for .claude/skills directory (anomalyco#6252)
1 parent 76880dc commit ae67f43

File tree

2 files changed

+65
-50
lines changed

2 files changed

+65
-50
lines changed

packages/opencode/src/skill/skill.ts

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,44 +32,59 @@ export namespace Skill {
3232
}),
3333
)
3434

35-
const SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md")
35+
const OPENCODE_SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md")
36+
const CLAUDE_SKILL_GLOB = new Bun.Glob(".claude/skills/**/SKILL.md")
3637

3738
export const state = Instance.state(async () => {
3839
const directories = await Config.directories()
3940
const skills: Record<string, Info> = {}
4041

42+
const addSkill = async (match: string) => {
43+
const md = await ConfigMarkdown.parse(match)
44+
if (!md) {
45+
return
46+
}
47+
48+
const parsed = Info.pick({ name: true, description: true }).safeParse(md.data)
49+
if (!parsed.success) return
50+
51+
// Warn on duplicate skill names
52+
if (skills[parsed.data.name]) {
53+
log.warn("duplicate skill name", {
54+
name: parsed.data.name,
55+
existing: skills[parsed.data.name].location,
56+
duplicate: match,
57+
})
58+
}
59+
60+
skills[parsed.data.name] = {
61+
name: parsed.data.name,
62+
description: parsed.data.description,
63+
location: match,
64+
}
65+
}
66+
4167
for (const dir of directories) {
42-
for await (const match of SKILL_GLOB.scan({
68+
for await (const match of OPENCODE_SKILL_GLOB.scan({
4369
cwd: dir,
4470
absolute: true,
4571
onlyFiles: true,
4672
followSymlinks: true,
4773
})) {
48-
const md = await ConfigMarkdown.parse(match)
49-
if (!md) {
50-
continue
51-
}
52-
53-
const parsed = Info.pick({ name: true, description: true }).safeParse(md.data)
54-
if (!parsed.success) continue
55-
56-
// Warn on duplicate skill names
57-
if (skills[parsed.data.name]) {
58-
log.warn("duplicate skill name", {
59-
name: parsed.data.name,
60-
existing: skills[parsed.data.name].location,
61-
duplicate: match,
62-
})
63-
}
64-
65-
skills[parsed.data.name] = {
66-
name: parsed.data.name,
67-
description: parsed.data.description,
68-
location: match,
69-
}
74+
await addSkill(match)
7075
}
7176
}
7277

78+
for await (const match of CLAUDE_SKILL_GLOB.scan({
79+
cwd: Instance.worktree,
80+
absolute: true,
81+
onlyFiles: true,
82+
followSymlinks: true,
83+
dot: true,
84+
})) {
85+
await addSkill(match)
86+
}
87+
7388
return skills
7489
})
7590

packages/opencode/test/skill/skill.test.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -101,31 +101,31 @@ test("returns empty array when no skills exist", async () => {
101101
})
102102
})
103103

104-
// test("discovers skills from .claude/skills/ directory", async () => {
105-
// await using tmp = await tmpdir({
106-
// git: true,
107-
// init: async (dir) => {
108-
// const skillDir = path.join(dir, ".claude", "skills", "claude-skill")
109-
// await Bun.write(
110-
// path.join(skillDir, "SKILL.md"),
111-
// `---
112-
// name: claude-skill
113-
// description: A skill in the .claude/skills directory.
114-
// ---
104+
test("discovers skills from .claude/skills/ directory", async () => {
105+
await using tmp = await tmpdir({
106+
git: true,
107+
init: async (dir) => {
108+
const skillDir = path.join(dir, ".claude", "skills", "claude-skill")
109+
await Bun.write(
110+
path.join(skillDir, "SKILL.md"),
111+
`---
112+
name: claude-skill
113+
description: A skill in the .claude/skills directory.
114+
---
115115
116-
// # Claude Skill
117-
// `,
118-
// )
119-
// },
120-
// })
116+
# Claude Skill
117+
`,
118+
)
119+
},
120+
})
121121

122-
// await Instance.provide({
123-
// directory: tmp.path,
124-
// fn: async () => {
125-
// const skills = await Skill.all()
126-
// expect(skills.length).toBe(1)
127-
// expect(skills[0].name).toBe("claude-skill")
128-
// expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md")
129-
// },
130-
// })
131-
// })
122+
await Instance.provide({
123+
directory: tmp.path,
124+
fn: async () => {
125+
const skills = await Skill.all()
126+
expect(skills.length).toBe(1)
127+
expect(skills[0].name).toBe("claude-skill")
128+
expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md")
129+
},
130+
})
131+
})

0 commit comments

Comments
 (0)