diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ba9d1973025..45beadf2259 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -71,14 +71,14 @@ export namespace Config { Global.Path.config, ...(await Array.fromAsync( Filesystem.up({ - targets: [".opencode"], + targets: [".opencode", ".claude"], start: Instance.directory, stop: Instance.worktree, }), )), ...(await Array.fromAsync( Filesystem.up({ - targets: [".opencode"], + targets: [".opencode", ".claude"], start: Global.Path.home, stop: Global.Path.home, }), @@ -92,7 +92,9 @@ export namespace Config { const promises: Promise[] = [] for (const dir of unique(directories)) { - await assertValid(dir) + if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) { + await assertValid(dir) + } if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) { for (const file of ["opencode.jsonc", "opencode.json"]) { @@ -147,7 +149,7 @@ export namespace Config { } }) - const INVALID_DIRS = new Bun.Glob(`{${["agents", "commands", "plugins", "tools"].join(",")}}/`) + const INVALID_DIRS = new Bun.Glob(`{${["agents", "commands", "plugins", "tools", "skill"].join(",")}}/`) async function assertValid(dir: string) { const invalid = await Array.fromAsync( INVALID_DIRS.scan({ diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 41df88f8b6a..77ed30a0a74 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -32,7 +32,7 @@ export namespace Skill { }), ) - const SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") + const SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md") export const state = Instance.state(async () => { const directories = await Config.directories() diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 4a1d75f9fca..638a03ba633 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -5,11 +5,11 @@ import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import path from "path" -test("discovers skills from .opencode/skill/ directory", async () => { +test("discovers skills from .opencode/skills/ directory", async () => { await using tmp = await tmpdir({ git: true, init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "test-skill") + const skillDir = path.join(dir, ".opencode", "skills", "test-skill") await Bun.write( path.join(skillDir, "SKILL.md"), `--- @@ -32,16 +32,16 @@ Instructions here. expect(skills.length).toBe(1) expect(skills[0].name).toBe("test-skill") expect(skills[0].description).toBe("A test skill for verification.") - expect(skills[0].location).toContain("skill/test-skill/SKILL.md") + expect(skills[0].location).toContain("skills/test-skill/SKILL.md") }, }) }) -test("discovers multiple skills from .opencode/skill/ directory", async () => { +test("discovers multiple skills from .opencode/skills/ directory", async () => { await using tmp = await tmpdir({ git: true, init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "my-skill") + const skillDir = path.join(dir, ".opencode", "skills", "my-skill") await Bun.write( path.join(skillDir, "SKILL.md"), `--- @@ -69,7 +69,7 @@ test("skips skills with missing frontmatter", async () => { await using tmp = await tmpdir({ git: true, init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "no-frontmatter") + const skillDir = path.join(dir, ".opencode", "skills", "no-frontmatter") await Bun.write( path.join(skillDir, "SKILL.md"), `# No Frontmatter @@ -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") + }, + }) +}) diff --git a/packages/web/src/content/docs/skills.mdx b/packages/web/src/content/docs/skills.mdx index c1d433a839f..a80fa2a1115 100644 --- a/packages/web/src/content/docs/skills.mdx +++ b/packages/web/src/content/docs/skills.mdx @@ -13,8 +13,8 @@ Skills are loaded on-demand via the native `skill` tool—agents see available s Create one folder per skill name and put a `SKILL.md` inside it. OpenCode searches these locations: -- Project config: `.opencode/skill//SKILL.md` -- Global config: `~/.opencode/skill//SKILL.md` +- Project config: `.opencode/skills//SKILL.md` +- Global config: `~/.opencode/skills//SKILL.md` - Claude-compatible: `.claude/skills//SKILL.md` --- @@ -22,9 +22,9 @@ OpenCode searches these locations: ## Understand discovery For project-local paths, OpenCode walks up from your current working directory until it reaches the git worktree. -It loads any matching `skill/*/SKILL.md` in `.opencode/` and any matching `.claude/skills/*/SKILL.md` along the way. +It loads any matching `skills/*/SKILL.md` in `.opencode/` and any matching `.claude/skills/*/SKILL.md` along the way. -Global definitions are also loaded from `~/.opencode/skill/*/SKILL.md`. +Global definitions are also loaded from `~/.opencode/skills/*/SKILL.md`. --- @@ -70,7 +70,7 @@ Keep it specific enough for the agent to choose correctly. ## Use an example -Create `.opencode/skill/git-release/SKILL.md` like this: +Create `.opencode/skills/git-release/SKILL.md` like this: ```markdown ---