Fix skill discovery priority and deduplication tests
This commit is contained in:
@@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user