Fix skill discovery priority and deduplication tests

This commit is contained in:
YeonGyu-Kim
2026-02-05 02:36:43 +09:00
parent 18e941b6be
commit a459813888
2 changed files with 107 additions and 19 deletions

View File

@@ -389,44 +389,132 @@ Skill body.
})
describe("deduplication", () => {
it("deduplicates skills with same name, keeping higher priority (opencode-project > opencode)", async () => {
// given: same skill name in both opencode-project and opencode scopes
it("deduplicates skills by name across scopes, keeping higher priority (opencode-project > opencode > project)", async () => {
const originalCwd = process.cwd()
const originalOpenCodeConfigDir = process.env.OPENCODE_CONFIG_DIR
const originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR
// given: same skill name in multiple scopes
const opencodeProjectSkillsDir = join(TEST_DIR, ".opencode", "skills")
const opencodeConfigDir = join(TEST_DIR, "opencode-global")
const opencodeGlobalSkillsDir = join(opencodeConfigDir, "skills")
const projectClaudeSkillsDir = join(TEST_DIR, ".claude", "skills")
process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir
process.env.CLAUDE_CONFIG_DIR = join(TEST_DIR, "claude-user")
mkdirSync(join(opencodeProjectSkillsDir, "duplicate-skill"), { recursive: true })
mkdirSync(join(opencodeGlobalSkillsDir, "duplicate-skill"), { recursive: true })
mkdirSync(join(projectClaudeSkillsDir, "duplicate-skill"), { recursive: true })
writeFileSync(
join(opencodeProjectSkillsDir, "duplicate-skill", "SKILL.md"),
`---
name: duplicate-skill
description: From opencode-project (higher priority)
description: From opencode-project (highest priority)
---
Project skill body.
opencode-project body.
`
)
// when: use discoverSkills which performs actual deduplication
writeFileSync(
join(opencodeGlobalSkillsDir, "duplicate-skill", "SKILL.md"),
`---
name: duplicate-skill
description: From opencode-global (middle priority)
---
opencode-global body.
`
)
writeFileSync(
join(projectClaudeSkillsDir, "duplicate-skill", "SKILL.md"),
`---
name: duplicate-skill
description: From claude project (lowest priority among these)
---
claude project body.
`
)
// when
const { discoverSkills } = await import("./loader")
const originalCwd = process.cwd()
process.chdir(TEST_DIR)
try {
// discoverSkills with includeClaudeCodePaths: false only loads opencode-project and opencode-global
const skills = await discoverSkills({ includeClaudeCodePaths: false })
const duplicateSkills = skills.filter(s => s.name === "duplicate-skill")
const skills = await discoverSkills()
const duplicates = skills.filter(s => s.name === "duplicate-skill")
// then: should have exactly one skill (deduplicated)
expect(duplicateSkills).toHaveLength(1)
// and it should be from opencode-project (higher priority)
expect(duplicateSkills[0]?.definition.description).toContain("opencode-project")
expect(duplicateSkills[0]?.scope).toBe("opencode-project")
// then
expect(duplicates).toHaveLength(1)
expect(duplicates[0]?.scope).toBe("opencode-project")
expect(duplicates[0]?.definition.description).toContain("opencode-project")
} finally {
process.chdir(originalCwd)
process.env.OPENCODE_CONFIG_DIR = originalOpenCodeConfigDir
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir
}
})
it("prioritizes OpenCode global skills over legacy Claude project skills", async () => {
const originalCwd = process.cwd()
const originalOpenCodeConfigDir = process.env.OPENCODE_CONFIG_DIR
const originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR
const opencodeConfigDir = join(TEST_DIR, "opencode-global")
const opencodeGlobalSkillsDir = join(opencodeConfigDir, "skills")
const projectClaudeSkillsDir = join(TEST_DIR, ".claude", "skills")
process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir
process.env.CLAUDE_CONFIG_DIR = join(TEST_DIR, "claude-user")
mkdirSync(join(opencodeGlobalSkillsDir, "global-over-project"), { recursive: true })
mkdirSync(join(projectClaudeSkillsDir, "global-over-project"), { recursive: true })
writeFileSync(
join(opencodeGlobalSkillsDir, "global-over-project", "SKILL.md"),
`---
name: global-over-project
description: From opencode-global (should win)
---
opencode-global body.
`
)
writeFileSync(
join(projectClaudeSkillsDir, "global-over-project", "SKILL.md"),
`---
name: global-over-project
description: From claude project (should lose)
---
claude project body.
`
)
const { discoverSkills } = await import("./loader")
process.chdir(TEST_DIR)
try {
const skills = await discoverSkills()
const matches = skills.filter(s => s.name === "global-over-project")
expect(matches).toHaveLength(1)
expect(matches[0]?.scope).toBe("opencode")
expect(matches[0]?.definition.description).toContain("opencode-global")
} finally {
process.chdir(originalCwd)
process.env.OPENCODE_CONFIG_DIR = originalOpenCodeConfigDir
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir
}
})
it("returns no duplicates from discoverSkills", async () => {
// given: create skill in opencode-project
const originalCwd = process.cwd()
const originalOpenCodeConfigDir = process.env.OPENCODE_CONFIG_DIR
process.env.OPENCODE_CONFIG_DIR = join(TEST_DIR, "opencode-global")
// given
const skillContent = `---
name: unique-test-skill
description: A unique skill for dedup test
@@ -437,18 +525,18 @@ Skill body.
// when
const { discoverSkills } = await import("./loader")
const originalCwd = process.cwd()
process.chdir(TEST_DIR)
try {
const skills = await discoverSkills({ includeClaudeCodePaths: false })
// then: no duplicate names
// then
const names = skills.map(s => s.name)
const uniqueNames = [...new Set(names)]
expect(names.length).toBe(uniqueNames.length)
} finally {
process.chdir(originalCwd)
process.env.OPENCODE_CONFIG_DIR = originalOpenCodeConfigDir
}
})
})