UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

227 lines (226 loc) 10.2 kB
/** * MCP Handler — autosnippet_evolve (批量 Recipe 进化决策) * * 双入口工具: * - Rescan 模式: 每个维度内先 evolve 再 gap-fill,与内部 Agent Pipeline 一致 * - 独立模式: 用户通过提示词触发,验证已有 Recipe 的有效性 * * 三种决策委托给 evolution-tools.ts 中已有的 handler 实现: * - propose_evolution → ProposalRepository.create (观察窗口) * - confirm_deprecation → RecipeLifecycleSupervisor.transition → deprecated (优先) / KnowledgeService.deprecate (回退) * - skip → 不变更状态,skip(still_valid) 刷新 stats.lastVerifiedAt * * @module handlers/evolve-external */ import { getDeveloperIdentity } from '#shared/developer-identity.js'; import { envelope } from '../envelope.js'; // ── 主入口 ───────────────────────────────────────────────── export async function evolveExternal(ctx, args) { const t0 = Date.now(); const { decisions } = args; if (!decisions || decisions.length === 0) { return envelope({ success: true, data: { processed: 0, proposed: 0, deprecated: 0, skipped: 0, refreshed: 0, quotaChange: { freed: 0, occupied: 0 }, errors: [], }, message: '⚠️ 没有提交任何 evolve 决策', meta: { tool: 'autosnippet_evolve', responseTimeMs: Date.now() - t0 }, }); } const result = { processed: 0, proposed: 0, deprecated: 0, skipped: 0, refreshed: 0, quotaChange: { freed: 0, occupied: 0 }, errors: [], }; const proposalRepo = ctx.container.get('proposalRepository'); const knowledgeService = ctx.container.get('knowledgeService'); const supervisor = ctx.container.get('lifecycleSupervisor'); const knowledgeRepo = ctx.container.get('knowledgeRepository'); for (const decision of decisions) { try { // O4: Recipe 存在性前置检查 if (knowledgeRepo) { const exists = await knowledgeRepo.findById(decision.recipeId); if (!exists) { result.errors.push({ recipeId: decision.recipeId, error: 'Recipe not found' }); result.processed++; continue; } } switch (decision.action) { case 'propose_evolution': { if (!proposalRepo) { result.errors.push({ recipeId: decision.recipeId, error: 'ProposalRepository not available', }); break; } if (!decision.evidence) { result.errors.push({ recipeId: decision.recipeId, error: 'evidence is required for propose_evolution', }); break; } const proposal = proposalRepo.create({ type: decision.evidence.type, targetRecipeId: decision.recipeId, relatedRecipeIds: [], confidence: 0.8, source: 'ide-agent', description: decision.evidence.suggestedChanges, evidence: [ { sourceStatus: 'modified', currentCode: decision.evidence.codeSnippet, filePath: decision.evidence.filePath, suggestedChanges: decision.evidence.suggestedChanges, verifiedBy: 'ide-agent', verifiedAt: Date.now(), }, ], }); if (proposal) { result.proposed++; ctx.logger.info(`[Evolve] propose_evolution: ${decision.recipeId} → proposal ${proposal.id}`); } else { result.errors.push({ recipeId: decision.recipeId, error: 'Failed to create proposal (target recipe may not exist or duplicate proposal)', }); } break; } case 'confirm_deprecation': { // O1: 优先通过 RecipeLifecycleSupervisor 执行,回退到 KnowledgeService const reason = decision.reason || 'IDE Agent confirmed deprecation'; if (supervisor) { const transResult = await supervisor.transition({ recipeId: decision.recipeId, targetState: 'deprecated', trigger: 'manual-deprecation', evidence: { reason }, operatorId: 'ide-agent', }); if (!transResult.success) { // Supervisor 拒绝(可能状态不允许直接转 deprecated),回退到 KnowledgeService if (knowledgeService) { await knowledgeService.deprecate(decision.recipeId, reason, { userId: getDeveloperIdentity(), }); } else { result.errors.push({ recipeId: decision.recipeId, error: `Supervisor rejected: ${transResult.error}`, }); break; } } } else if (knowledgeService) { // P3: 添加 await await knowledgeService.deprecate(decision.recipeId, reason, { userId: getDeveloperIdentity(), }); } else { result.errors.push({ recipeId: decision.recipeId, error: 'Neither Supervisor nor KnowledgeService available', }); break; } // 解决关联的 deprecate proposal if (proposalRepo) { try { const existing = proposalRepo.findByTarget(decision.recipeId); for (const p of existing) { if (p.type === 'deprecate') { proposalRepo.markExecuted(p.id, `IDE Agent confirmed deprecation: ${reason}`, 'ide-agent'); } } } catch { // ProposalRepository 操作失败时静默 } } result.deprecated++; result.quotaChange.freed++; ctx.logger.info(`[Evolve] confirm_deprecation: ${decision.recipeId}`); break; } case 'skip': { if (decision.skipReason === 'still_valid' && knowledgeRepo) { // P4: 更新 stats.lastVerifiedAt 而非 updated_at try { const entry = await knowledgeRepo.findById(decision.recipeId); if (entry) { const stats = (typeof entry.stats === 'object' ? entry.stats : {}); stats.lastVerifiedAt = Date.now(); await knowledgeRepo.updateStats(decision.recipeId, stats); } result.refreshed++; } catch { // DB 更新失败时静默 } } result.skipped++; ctx.logger.info(`[Evolve] skip: ${decision.recipeId} (${decision.skipReason || 'no reason'})`); break; } default: { result.errors.push({ recipeId: decision.recipeId, error: `Unknown action: ${decision.action}`, }); } } result.processed++; } catch (err) { result.errors.push({ recipeId: decision.recipeId, error: err instanceof Error ? err.message : String(err), }); result.processed++; } } const parts = []; if (result.proposed > 0) { parts.push(`${result.proposed} 个进化提案`); } if (result.deprecated > 0) { parts.push(`${result.deprecated} 个废弃`); } if (result.refreshed > 0) { parts.push(`${result.refreshed} 个仍然有效`); } if (result.skipped - result.refreshed > 0) { parts.push(`${result.skipped - result.refreshed} 个跳过`); } const summary = parts.length > 0 ? parts.join(', ') : '无变更'; return envelope({ success: true, data: result, message: `✅ 处理了 ${result.processed} 个 Recipe: ${summary}` + (result.errors.length > 0 ? ` (${result.errors.length} 个错误)` : ''), meta: { tool: 'autosnippet_evolve', responseTimeMs: Date.now() - t0 }, }); }