summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorConnor Adams <[email protected]>2025-12-29 04:39:10 +0000
committerGitHub <[email protected]>2025-12-28 22:39:10 -0600
commitae67f43ff0f241dfb6a48ad949b8d6a56d62a675 (patch)
treead90eb9d8b25e17b6e934fc6e712c7ec30e1df76
parent76880dce0d67998c03f9dbb162f4f91f1a762a47 (diff)
downloadopencode-ae67f43ff0f241dfb6a48ad949b8d6a56d62a675.tar.gz
opencode-ae67f43ff0f241dfb6a48ad949b8d6a56d62a675.zip
feat: add support for `.claude/skills` directory (#6252)
-rw-r--r--packages/opencode/src/skill/skill.ts63
-rw-r--r--packages/opencode/test/skill/skill.test.ts52
2 files changed, 65 insertions, 50 deletions
diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts
index 41df88f8b..16fa1d08f 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<string, Info> = {}
+ 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 4a1d75f9f..1da8105bd 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")
+ },
+ })
+})