diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index b8bd6bef6e11..462b0ec441f3 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -164,7 +164,7 @@ const scan = Effect.fnUntraced(function* ( }), ) - for (const match of matches) { + for (const match of matches.toSorted()) { state.matches.add(match) state.dirs.add(path.dirname(match)) } @@ -228,7 +228,7 @@ const discoverSkills = Effect.fnUntraced(function* ( return { matches: Array.from(state.matches), - dirs: Array.from(state.dirs), + dirs: Array.from(state.dirs).toSorted(), } }) @@ -237,10 +237,9 @@ const loadSkills = Effect.fnUntraced(function* ( discovered: DiscoveryState, events: EventV2Bridge.Service["Service"], ) { - yield* Effect.forEach(discovered.matches, (match) => add(state, match, events), { - concurrency: "unbounded", - discard: true, - }) + for (const match of discovered.matches) { + yield* add(state, match, events) + } yield* Effect.logInfo("init", { count: Object.keys(state.skills).length }) }) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index fd79a68cee21..dba06f93ecf6 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -429,6 +429,44 @@ description: A skill in the .agents/skills directory. ), ) + it.live("resolves duplicate skill names by source precedence", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + yield* Effect.promise(() => + Promise.all([ + Bun.write( + path.join(dir, ".claude", "skills", "duplicate-skill", "SKILL.md"), + `--- +name: duplicate-skill +description: Claude-compatible duplicate. +--- + +# Duplicate Skill +`, + ), + Bun.write( + path.join(dir, ".opencode", "skills", "duplicate-skill", "SKILL.md"), + `--- +name: duplicate-skill +description: OpenCode duplicate. +--- + +# Duplicate Skill +`, + ), + ]), + ) + + const skill = yield* Skill.Service + const duplicate = yield* skill.require("duplicate-skill") + expect(duplicate.description).toBe("OpenCode duplicate.") + expect(duplicate.location).toContain(path.join(".opencode", "skills", "duplicate-skill", "SKILL.md")) + }), + { git: true }, + ), + ) + itWithoutClaudeCodeSkills.live("skips Claude Code skills when disabled", () => provideTmpdirInstance( (dir) =>