Fix skill discovery priority and deduplication tests
This commit is contained in:
parent
18e941b6be
commit
a459813888
@ -389,44 +389,132 @@ Skill body.
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("deduplication", () => {
|
describe("deduplication", () => {
|
||||||
it("deduplicates skills with same name, keeping higher priority (opencode-project > opencode)", async () => {
|
it("deduplicates skills by name across scopes, keeping higher priority (opencode-project > opencode > project)", async () => {
|
||||||
// given: same skill name in both opencode-project and opencode scopes
|
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 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(opencodeProjectSkillsDir, "duplicate-skill"), { recursive: true })
|
||||||
|
mkdirSync(join(opencodeGlobalSkillsDir, "duplicate-skill"), { recursive: true })
|
||||||
|
mkdirSync(join(projectClaudeSkillsDir, "duplicate-skill"), { recursive: true })
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(opencodeProjectSkillsDir, "duplicate-skill", "SKILL.md"),
|
join(opencodeProjectSkillsDir, "duplicate-skill", "SKILL.md"),
|
||||||
`---
|
`---
|
||||||
name: duplicate-skill
|
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 { discoverSkills } = await import("./loader")
|
||||||
const originalCwd = process.cwd()
|
|
||||||
process.chdir(TEST_DIR)
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// discoverSkills with includeClaudeCodePaths: false only loads opencode-project and opencode-global
|
const skills = await discoverSkills()
|
||||||
const skills = await discoverSkills({ includeClaudeCodePaths: false })
|
const duplicates = skills.filter(s => s.name === "duplicate-skill")
|
||||||
const duplicateSkills = skills.filter(s => s.name === "duplicate-skill")
|
|
||||||
|
|
||||||
// then: should have exactly one skill (deduplicated)
|
// then
|
||||||
expect(duplicateSkills).toHaveLength(1)
|
expect(duplicates).toHaveLength(1)
|
||||||
// and it should be from opencode-project (higher priority)
|
expect(duplicates[0]?.scope).toBe("opencode-project")
|
||||||
expect(duplicateSkills[0]?.definition.description).toContain("opencode-project")
|
expect(duplicates[0]?.definition.description).toContain("opencode-project")
|
||||||
expect(duplicateSkills[0]?.scope).toBe("opencode-project")
|
|
||||||
} finally {
|
} finally {
|
||||||
process.chdir(originalCwd)
|
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 () => {
|
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 = `---
|
const skillContent = `---
|
||||||
name: unique-test-skill
|
name: unique-test-skill
|
||||||
description: A unique skill for dedup test
|
description: A unique skill for dedup test
|
||||||
@ -437,18 +525,18 @@ Skill body.
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
const { discoverSkills } = await import("./loader")
|
const { discoverSkills } = await import("./loader")
|
||||||
const originalCwd = process.cwd()
|
|
||||||
process.chdir(TEST_DIR)
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const skills = await discoverSkills({ includeClaudeCodePaths: false })
|
const skills = await discoverSkills({ includeClaudeCodePaths: false })
|
||||||
|
|
||||||
// then: no duplicate names
|
// then
|
||||||
const names = skills.map(s => s.name)
|
const names = skills.map(s => s.name)
|
||||||
const uniqueNames = [...new Set(names)]
|
const uniqueNames = [...new Set(names)]
|
||||||
expect(names.length).toBe(uniqueNames.length)
|
expect(names.length).toBe(uniqueNames.length)
|
||||||
} finally {
|
} finally {
|
||||||
process.chdir(originalCwd)
|
process.chdir(originalCwd)
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = originalOpenCodeConfigDir
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -251,10 +251,10 @@ function deduplicateSkills(skills: LoadedSkill[]): LoadedSkill[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function discoverAllSkills(): Promise<LoadedSkill[]> {
|
export async function discoverAllSkills(): Promise<LoadedSkill[]> {
|
||||||
const [opencodeProjectSkills, projectSkills, opencodeGlobalSkills, userSkills] = await Promise.all([
|
const [opencodeProjectSkills, opencodeGlobalSkills, projectSkills, userSkills] = await Promise.all([
|
||||||
discoverOpencodeProjectSkills(),
|
discoverOpencodeProjectSkills(),
|
||||||
discoverProjectClaudeSkills(),
|
|
||||||
discoverOpencodeGlobalSkills(),
|
discoverOpencodeGlobalSkills(),
|
||||||
|
discoverProjectClaudeSkills(),
|
||||||
discoverUserClaudeSkills(),
|
discoverUserClaudeSkills(),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user