UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

364 lines (363 loc) 18.5 kB
/** * lifecycle.js — 生命周期操作类工具 (10) * * 16. submit_knowledge 提交候选项 * 17. approve_candidate 批准候选 * 18. reject_candidate 驳回候选 * 19. publish_recipe 发布 Recipe * 20. deprecate_recipe 弃用 Recipe * 21. update_recipe 更新 Recipe * 22. record_usage 记录使用 * 23. quality_score 质量评分 * 24. validate_candidate 候选校验 * 25. get_feedback_stats 反馈统计 */ import { getInternalAgentRequiredFields, getSystemInjectedFields, } from '#domain/knowledge/FieldSpec.js'; import { checkDimensionType, DIMENSION_DISPLAY_GROUP, stripProjectNamePrefix, } from './_shared.js'; // ──────────────────────────────────────────────────────────── // 16. submit_knowledge // ──────────────────────────────────────────────────────────── export const submitCandidate = { name: 'submit_knowledge', description: '提交新的代码候选项到知识库审核队列。', parameters: { type: 'object', properties: { // ── 内容(V3 content 子对象) ── content: { type: 'object', description: '{ markdown: "项目特写 Markdown(≥200字)", pattern: "核心代码 3-8 行", rationale: "设计原理" }', }, // ── 基本信息 ── title: { type: 'string', description: '候选标题(中文 ≤20 字)' }, description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' }, tags: { type: 'array', items: { type: 'string' }, description: '标签列表' }, // ── Cursor 交付(AI 必填)── trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' }, kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' }, topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'], description: '主题分类', }, whenClause: { type: 'string', description: '触发场景英文' }, doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' }, dontClause: { type: 'string', description: "反向约束英文(不以 Don't 开头)" }, coreCode: { type: 'string', description: '3-8 行纯代码骨架,语法完整可复制' }, // ── 推理(必填) ── reasoning: { type: 'object', description: '{ whyStandard: string, sources: string[], confidence: number } — 全部必填', }, // ── V3 扩展字段 ── scope: { type: 'string', enum: ['universal', 'project-specific', 'team-convention'], description: '适用范围', }, complexity: { type: 'string', enum: ['basic', 'intermediate', 'advanced'], description: '复杂度', }, headers: { type: 'array', items: { type: 'string' }, description: '依赖的 import/require 行(无 import 时传 [])', }, knowledgeType: { type: 'string', description: '知识维度:code-pattern / architecture / best-practice 等', }, usageGuide: { type: 'string', description: '使用指南 Markdown(### 章节格式)' }, sourceFile: { type: 'string', description: 'Recipe md 文件相对路径(由系统自动设置,无需手动填写)', }, supersedes: { type: 'string', description: '被替代的旧 Recipe ID。传入后将创建 supersede 提案,72h 观察窗口后自动替代。', }, }, // FieldSpec 驱动: 内部 Agent 路径排除系统注入字段 required: getInternalAgentRequiredFields(), }, handler: async (params, ctx) => { // ── 标题正规化:剥离冗余的项目名前缀 ── if (params.title) { params.title = stripProjectNamePrefix(params.title, ctx.projectRoot); } // ── Bootstrap 维度类型校验(source-specific: 仅 bootstrap 流程) ── const dimMeta = ctx._dimensionMeta; if (dimMeta && ctx.source === 'system') { const rejected = checkDimensionType(dimMeta, params, ctx.logger); if (rejected) { return rejected; } // 自动注入维度标签 if (!params.tags) { params.tags = []; } if (!params.tags.includes(dimMeta.id)) { params.tags.push(dimMeta.id); } if (!params.tags.includes('bootstrap')) { params.tags.push('bootstrap'); } // Bootstrap 模式: 将 category 设为维度 ID(前端按此分组显示) params._category = dimMeta.id; } // ── 系统自动设置 ── const item = { ...params, language: ctx._projectLanguage || '', category: dimMeta ? dimMeta.id : params._category || 'general', knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || params.knowledgeType || 'code-pattern', source: ctx.source === 'system' ? 'bootstrap' : 'agent', agentNotes: dimMeta ? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' } : null, }; if (dimMeta && ctx.source === 'system') { const displayGroup = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id; item.tags = [...new Set([...(item.tags || []), displayGroup])]; } // ── 委托 RecipeProductionGateway 统一管道 ── const { RecipeProductionGateway } = await import('#service/knowledge/RecipeProductionGateway.js'); const gateway = new RecipeProductionGateway({ knowledgeService: ctx.container.get('knowledgeService'), projectRoot: ctx.projectRoot, logger: ctx.logger, proposalRepository: ctx.container.get('proposalRepository') ?? null, }); const gatewayResult = await gateway.create({ source: 'agent-tool', items: [item], options: { skipSimilarityCheck: true, skipConsolidation: true, supersedes: params.supersedes, existingTitles: ctx._submittedTitles, existingFingerprints: ctx._submittedPatterns, systemInjectedFields: dimMeta && ctx.source === 'system' ? getSystemInjectedFields() : undefined, userId: 'agent', }, }); // ── 映射 Gateway 结果 → 原有返回格式 ── if (gatewayResult.rejected.length > 0) { const rej = gatewayResult.rejected[0]; ctx.logger?.info(`[submit_knowledge] ✗ validator rejected: ${rej.errors.join('; ')}`); return { status: 'rejected', error: rej.errors.join('\n'), warnings: rej.warnings, hint: '请根据错误信息调整内容后重新提交。', }; } if (gatewayResult.created.length > 0) { const created = gatewayResult.created[0]; if (gatewayResult.supersedeProposal) { return { ...created.raw, _supersedeProposal: gatewayResult.supersedeProposal }; } return created.raw; } return { status: 'error', error: 'No items created' }; }, }; // ──────────────────────────────────────────────────────────── // 16b. save_document — 保存开发文档到知识库 // ── (已删除: save_document — 已合并到 submit_knowledge 统一管线) ── // ──────────────────────────────────────────────────────────── // 17. approve_candidate // ──────────────────────────────────────────────────────────── export const approveCandidate = { name: 'approve_candidate', description: '批准候选项(PENDING → APPROVED)。', parameters: { type: 'object', properties: { candidateId: { type: 'string', description: '候选 ID' }, }, required: ['candidateId'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); return knowledgeService.approve(params.candidateId, { userId: 'agent' }); }, }; // ──────────────────────────────────────────────────────────── // 18. reject_candidate // ──────────────────────────────────────────────────────────── export const rejectCandidate = { name: 'reject_candidate', description: '驳回候选项并填写驳回理由。', parameters: { type: 'object', properties: { candidateId: { type: 'string', description: '候选 ID' }, reason: { type: 'string', description: '驳回理由' }, }, required: ['candidateId', 'reason'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); return knowledgeService.reject(params.candidateId, params.reason, { userId: 'agent' }); }, }; // ──────────────────────────────────────────────────────────── // 19. publish_recipe // ──────────────────────────────────────────────────────────── export const publishRecipe = { name: 'publish_recipe', description: '发布 Recipe(DRAFT → ACTIVE)。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: 'Recipe ID' }, }, required: ['recipeId'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); return knowledgeService.publish(params.recipeId, { userId: 'agent' }); }, }; // ──────────────────────────────────────────────────────────── // 20. deprecate_recipe // ──────────────────────────────────────────────────────────── export const deprecateRecipe = { name: 'deprecate_recipe', description: '弃用 Recipe 并填写弃用原因。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: 'Recipe ID' }, reason: { type: 'string', description: '弃用原因' }, }, required: ['recipeId', 'reason'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); return knowledgeService.deprecate(params.recipeId, params.reason, { userId: 'agent' }); }, }; // ──────────────────────────────────────────────────────────── // 21. update_recipe // ──────────────────────────────────────────────────────────── export const updateRecipe = { name: 'update_recipe', description: '更新 Recipe 的指定字段(title/description/content/category/tags 等)。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: 'Recipe ID' }, updates: { type: 'object', description: '要更新的字段和值' }, }, required: ['recipeId', 'updates'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); return knowledgeService.update(params.recipeId, params.updates, { userId: 'agent' }); }, }; // ──────────────────────────────────────────────────────────── // 22. record_usage // ──────────────────────────────────────────────────────────── export const recordUsage = { name: 'record_usage', description: '记录 Recipe 的使用(adoption 被采纳 / application 被应用)。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: 'Recipe ID' }, type: { type: 'string', description: 'adoption 或 application,默认 adoption' }, }, required: ['recipeId'], }, handler: async (params, ctx) => { const knowledgeService = ctx.container.get('knowledgeService'); const type = params.type || 'adoption'; await knowledgeService.incrementUsage(params.recipeId, type); return { success: true, recipeId: params.recipeId, type }; }, }; // ──────────────────────────────────────────────────────────── // 23. quality_score // ──────────────────────────────────────────────────────────── export const qualityScore = { name: 'quality_score', description: 'Recipe 质量评分 — 5 维度综合评估(完整性/格式/代码质量/元数据/互动),返回分数和等级(A-F)。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: 'Recipe ID(从数据库读取后评分)' }, recipe: { type: 'object', description: '或直接提供 Recipe 对象 { title, trigger, code, language, ... }', }, }, }, handler: async (params, ctx) => { const qualityScorer = ctx.container.get('qualityScorer'); let recipe = params.recipe; if (!recipe && params.recipeId) { const knowledgeService = ctx.container.get('knowledgeService'); try { const entry = await knowledgeService.get(params.recipeId); recipe = typeof entry.toJSON === 'function' ? entry.toJSON() : entry; } catch { return { error: `Knowledge entry '${params.recipeId}' not found` }; } } if (!recipe) { return { error: 'Provide recipeId or recipe object' }; } return qualityScorer.score(recipe); }, }; // ──────────────────────────────────────────────────────────── // 24. validate_candidate // ──────────────────────────────────────────────────────────── export const validateCandidate = { name: 'validate_candidate', description: '候选校验 — 检查候选是否满足提交要求(必填字段/格式/质量),返回 errors 和 warnings。', parameters: { type: 'object', properties: { candidate: { type: 'object', description: '候选对象 { title, trigger, category, language, code, reasoning, ... }', }, }, required: ['candidate'], }, handler: async (params, ctx) => { const validator = ctx.container.get('recipeCandidateValidator'); return validator.validate(params.candidate); }, }; // ──────────────────────────────────────────────────────────── // 25. get_feedback_stats // ──────────────────────────────────────────────────────────── export const getFeedbackStats = { name: 'get_feedback_stats', description: '获取用户反馈统计 — 全局交互事件统计 + 热门 Recipe + 指定 Recipe 的详细反馈。', parameters: { type: 'object', properties: { recipeId: { type: 'string', description: '查询指定 Recipe 的反馈(可选)' }, topN: { type: 'number', description: '热门 Recipe 数量,默认 10' }, }, }, handler: async (params, ctx) => { const feedbackCollector = ctx.container.get('feedbackCollector'); const result = {}; result.global = feedbackCollector.getGlobalStats(); result.topRecipes = feedbackCollector.getTopRecipes(params.topN || 10); if (params.recipeId) { result.recipeStats = feedbackCollector.getRecipeStats(params.recipeId); } return result; }, };