Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions packages/opencode/src/skill/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
}
}

Expand Down