97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
import type { LoadedSkill } from "./types"
|
|
import type { SkillsConfig } from "../../config/schema"
|
|
import type { BuiltinSkill } from "../builtin-skills/types"
|
|
import { builtinToLoadedSkill } from "./merger/builtin-skill-converter"
|
|
import { configEntryToLoadedSkill } from "./merger/config-skill-entry-loader"
|
|
import { mergeSkillDefinitions } from "./merger/skill-definition-merger"
|
|
import { normalizeSkillsConfig } from "./merger/skills-config-normalizer"
|
|
import { SCOPE_PRIORITY } from "./merger/scope-priority"
|
|
|
|
export interface MergeSkillsOptions {
|
|
configDir?: string
|
|
}
|
|
|
|
export function mergeSkills(
|
|
builtinSkills: BuiltinSkill[],
|
|
config: SkillsConfig | undefined,
|
|
configSourceSkills: LoadedSkill[],
|
|
userClaudeSkills: LoadedSkill[],
|
|
userOpencodeSkills: LoadedSkill[],
|
|
projectClaudeSkills: LoadedSkill[],
|
|
projectOpencodeSkills: LoadedSkill[],
|
|
options: MergeSkillsOptions = {}
|
|
): LoadedSkill[] {
|
|
const skillMap = new Map<string, LoadedSkill>()
|
|
|
|
for (const builtin of builtinSkills) {
|
|
const loaded = builtinToLoadedSkill(builtin)
|
|
skillMap.set(loaded.name, loaded)
|
|
}
|
|
|
|
const normalizedConfig = normalizeSkillsConfig(config)
|
|
|
|
for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
|
|
if (entry === false) continue
|
|
if (entry === true) continue
|
|
|
|
if (entry.disable) continue
|
|
|
|
const loaded = configEntryToLoadedSkill(name, entry, options.configDir)
|
|
if (loaded) {
|
|
const existing = skillMap.get(name)
|
|
if (existing && !entry.template && !entry.from) {
|
|
skillMap.set(name, mergeSkillDefinitions(existing, entry))
|
|
} else {
|
|
skillMap.set(name, loaded)
|
|
}
|
|
}
|
|
}
|
|
|
|
const fileSystemSkills = [
|
|
...configSourceSkills,
|
|
...userClaudeSkills,
|
|
...userOpencodeSkills,
|
|
...projectClaudeSkills,
|
|
...projectOpencodeSkills,
|
|
]
|
|
|
|
for (const skill of fileSystemSkills) {
|
|
const existing = skillMap.get(skill.name)
|
|
if (!existing || SCOPE_PRIORITY[skill.scope] > SCOPE_PRIORITY[existing.scope]) {
|
|
skillMap.set(skill.name, skill)
|
|
}
|
|
}
|
|
|
|
for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
|
|
if (entry === true) continue
|
|
if (entry === false) {
|
|
skillMap.delete(name)
|
|
continue
|
|
}
|
|
if (entry.disable) {
|
|
skillMap.delete(name)
|
|
continue
|
|
}
|
|
|
|
const existing = skillMap.get(name)
|
|
if (existing && !entry.template && !entry.from) {
|
|
skillMap.set(name, mergeSkillDefinitions(existing, entry))
|
|
}
|
|
}
|
|
|
|
for (const name of normalizedConfig.disable) {
|
|
skillMap.delete(name)
|
|
}
|
|
|
|
if (normalizedConfig.enable.length > 0) {
|
|
const enableSet = new Set(normalizedConfig.enable)
|
|
for (const name of skillMap.keys()) {
|
|
if (!enableSet.has(name)) {
|
|
skillMap.delete(name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return Array.from(skillMap.values())
|
|
}
|