/** * Skill 注册表 * * 管理所有可用的 Skills,支持: * - 注册/注销 Skills * - 按名称/分类/关键词查询 * - 渲染 Skill 提示模板 */ import type { Skill, SkillContext, SkillExecutionResult, SkillSearchResult, SkillRegistryConfig, } from './types.js'; import { skillLoader } from './loader.js'; import { builtinSkills } from './builtin/index.js'; /** * Skill 注册表 */ export class SkillRegistry { private skills = new Map(); private config: SkillRegistryConfig; private initialized = false; constructor(config: SkillRegistryConfig = {}) { this.config = { autoLoad: true, ...config, }; } /** * 初始化注册表 */ async initialize(workdir: string = process.cwd()): Promise { if (this.initialized) { return; } // 1. 注册内置 Skills for (const skill of builtinSkills) { this.register(skill); } // 2. 加载用户 Skills if (this.config.autoLoad) { const userDir = this.config.userSkillsDir || skillLoader.getUserSkillsDir(); const userSkills = await skillLoader.loadFromDirectory(userDir, 'user'); for (const skill of userSkills) { this.register(skill); } } // 3. 加载项目 Skills if (this.config.autoLoad) { const projectDir = this.config.projectSkillsDir || skillLoader.getProjectSkillsDir(workdir); const projectSkills = await skillLoader.loadFromDirectory( projectDir, 'project' ); for (const skill of projectSkills) { this.register(skill); } } this.initialized = true; } /** * 注册 Skill */ register(skill: Skill): void { // 项目 Skills 优先级最高,可以覆盖同名的内置/用户 Skills const existing = this.skills.get(skill.name); if (existing) { // 优先级: project > user > builtin const priority = { project: 3, user: 2, builtin: 1 }; if (priority[skill.source] < priority[existing.source]) { return; // 不覆盖更高优先级的 Skill } } this.skills.set(skill.name, skill); } /** * 注销 Skill */ unregister(name: string): boolean { return this.skills.delete(name); } /** * 获取 Skill */ get(name: string): Skill | undefined { return this.skills.get(name); } /** * 检查 Skill 是否存在 */ has(name: string): boolean { return this.skills.has(name); } /** * 获取所有 Skills */ getAll(): Skill[] { return Array.from(this.skills.values()); } /** * 获取启用的 Skills */ getEnabled(): Skill[] { return this.getAll().filter((s) => s.enabled !== false); } /** * 按分类获取 Skills */ getByCategory(category: string): Skill[] { return this.getEnabled().filter((s) => s.category === category); } /** * 获取所有分类 */ getCategories(): string[] { const categories = new Set(); for (const skill of this.getEnabled()) { if (skill.category) { categories.add(skill.category); } } return Array.from(categories).sort(); } /** * 搜索 Skills */ search(query: string, limit: number = 10): SkillSearchResult[] { const queryLower = query.toLowerCase(); const results: SkillSearchResult[] = []; for (const skill of this.getEnabled()) { let score = 0; let matchReason = ''; // 精确名称匹配 if (skill.name.toLowerCase() === queryLower) { score = 100; matchReason = '名称精确匹配'; } // 名称前缀匹配 else if (skill.name.toLowerCase().startsWith(queryLower)) { score = 80; matchReason = '名称前缀匹配'; } // 名称包含匹配 else if (skill.name.toLowerCase().includes(queryLower)) { score = 60; matchReason = '名称包含匹配'; } // 描述匹配 else if (skill.description.toLowerCase().includes(queryLower)) { score = 40; matchReason = '描述匹配'; } // 关键词匹配 else if ( skill.keywords?.some((k) => k.toLowerCase().includes(queryLower)) ) { score = 30; matchReason = '关键词匹配'; } // 分类匹配 else if (skill.category?.toLowerCase().includes(queryLower)) { score = 20; matchReason = '分类匹配'; } if (score > 0) { results.push({ skill, score, matchReason }); } } // 按分数降序排序 results.sort((a, b) => b.score - a.score); return results.slice(0, limit); } /** * 渲染 Skill 提示模板 */ renderPrompt( skill: Skill, params: Record, context?: SkillContext ): SkillExecutionResult { try { // 验证必需参数 if (skill.parameters) { for (const [name, param] of Object.entries(skill.parameters)) { if (param.required && !(name in params)) { return { success: false, error: `缺少必需参数: ${name}`, }; } } } // 构建变量映射 const variables: Record = {}; // 添加参数值 for (const [key, value] of Object.entries(params)) { variables[key] = String(value); } // 添加上下文变量 if (context?.variables) { for (const [key, value] of Object.entries(context.variables)) { if (!(key in variables)) { variables[key] = value; } } } // 添加默认值 if (skill.parameters) { for (const [name, param] of Object.entries(skill.parameters)) { if (!(name in variables) && param.default !== undefined) { variables[name] = String(param.default); } } } // 渲染模板 let prompt = skill.promptTemplate; // 替换 {{variable}} 格式的变量 prompt = prompt.replace(/\{\{(\w+)\}\}/g, (match, varName) => { if (varName in variables) { return variables[varName]; } // 保留未匹配的变量(可能是用户意图保留) return match; }); return { success: true, prompt, }; } catch (error) { return { success: false, error: `渲染 Skill 提示失败: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * 执行 Skill(渲染模板并返回提示) */ execute( name: string, params: Record, context?: SkillContext ): SkillExecutionResult { const skill = this.get(name); if (!skill) { return { success: false, error: `Skill 不存在: ${name}`, }; } if (skill.enabled === false) { return { success: false, error: `Skill 已禁用: ${name}`, }; } return this.renderPrompt(skill, params, context); } /** * 重新加载 Skills */ async reload(workdir: string = process.cwd()): Promise { this.skills.clear(); this.initialized = false; await this.initialize(workdir); } /** * 获取 Skill 统计信息 */ getStats(): { total: number; enabled: number; bySource: Record; byCategory: Record; } { const skills = this.getAll(); const enabled = this.getEnabled(); const bySource: Record = {}; const byCategory: Record = {}; for (const skill of skills) { bySource[skill.source] = (bySource[skill.source] || 0) + 1; if (skill.category) { byCategory[skill.category] = (byCategory[skill.category] || 0) + 1; } } return { total: skills.length, enabled: enabled.length, bySource, byCategory, }; } } /** * 全局 Skill 注册表实例 */ let skillRegistryInstance: SkillRegistry | null = null; /** * 获取全局 Skill 注册表 */ export function getSkillRegistry(): SkillRegistry { if (!skillRegistryInstance) { skillRegistryInstance = new SkillRegistry(); } return skillRegistryInstance; } /** * 重置全局 Skill 注册表(用于测试) */ export function resetSkillRegistry(): void { skillRegistryInstance = null; }