diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 26b9b1065955..17e8ff707058 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -40,8 +40,10 @@ export namespace Skill { }), ) - // External skill patterns to search for (project-level and global) - const EXTERNAL_PATTERNS = [".claude/skills/**/SKILL.md", ".agents/skills/**/SKILL.md"] + // External skill directories to search for (project-level and global) + // These follow the directory layout used by Claude Code and other agents. + const EXTERNAL_DIRS = [".claude", ".agents"] + const EXTERNAL_SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md") const OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/SKILL.md") const SKILL_GLOB = new Bun.Glob("**/SKILL.md") @@ -81,29 +83,37 @@ export namespace Skill { } } - // Scan external skill directories (.claude/skills/, .agents/skills/, etc.) - // Load global (home) first, then project-level (so project-level overwrites) - if (!Flag.OPENCODE_DISABLE_EXTERNAL_SKILLS) { - for (const pattern of EXTERNAL_PATTERNS) { - // Scan global home directory for external skills first - const glob = new Bun.Glob(pattern) - for await (const match of glob.scan({ - cwd: Global.Path.home, + const scanExternal = async (root: string, scope: "global" | "project") => { + return Array.fromAsync( + EXTERNAL_SKILL_GLOB.scan({ + cwd: root, absolute: true, onlyFiles: true, followSymlinks: true, dot: true, - })) { - await addSkill(match) - } - - // Then walk up from current directory to find project-level skills (overwrites globals) - for (const match of await Filesystem.globUp(pattern, Instance.directory, Instance.worktree).catch((error) => { - log.error("failed to scan project directories for skills", { pattern, error }) - return [] - })) { - await addSkill(match) - } + }), + ) + .then((matches) => Promise.all(matches.map(addSkill))) + .catch((error) => { + log.error(`failed to scan ${scope} skills`, { dir: root, error }) + }) + } + + // Scan external skill directories (.claude/skills/, .agents/skills/, etc.) + // Load global (home) first, then project-level (so project-level overwrites) + if (!Flag.OPENCODE_DISABLE_EXTERNAL_SKILLS) { + for (const dir of EXTERNAL_DIRS) { + const root = path.join(Global.Path.home, dir) + if (!(await Filesystem.isDir(root))) continue + await scanExternal(root, "global") + } + + for await (const root of Filesystem.up({ + targets: EXTERNAL_DIRS, + start: Instance.directory, + stop: Instance.worktree, + })) { + await scanExternal(root, "project") } }