فهرست منبع

feat: add support for `.claude/skills` directory (#6252)

Connor Adams 1 ماه پیش
والد
کامیت
ae67f43ff0
2فایلهای تغییر یافته به همراه65 افزوده شده و 50 حذف شده
  1. 39 24
      packages/opencode/src/skill/skill.ts
  2. 26 26
      packages/opencode/test/skill/skill.test.ts

+ 39 - 24
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
   })
 

+ 26 - 26
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")
+    },
+  })
+})