autosnippet 2.9.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-BkT3XrKf.js} +105 -100
  5. package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
  6. package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +13 -11
  9. package/lib/cli/KnowledgeSyncService.js +343 -0
  10. package/lib/cli/SetupService.js +8 -26
  11. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  12. package/lib/domain/index.js +16 -11
  13. package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
  14. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  15. package/lib/domain/knowledge/Lifecycle.js +109 -0
  16. package/lib/domain/knowledge/index.js +27 -0
  17. package/lib/domain/knowledge/values/Constraints.js +125 -0
  18. package/lib/domain/knowledge/values/Content.js +86 -0
  19. package/lib/domain/knowledge/values/Quality.js +93 -0
  20. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  21. package/lib/domain/knowledge/values/Relations.js +168 -0
  22. package/lib/domain/knowledge/values/Stats.js +87 -0
  23. package/lib/domain/knowledge/values/index.js +9 -0
  24. package/lib/external/ai/AiProvider.js +48 -0
  25. package/lib/external/mcp/McpServer.js +7 -5
  26. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +2 -2
  27. package/lib/external/mcp/handlers/bootstrap.js +116 -11
  28. package/lib/external/mcp/handlers/browse.js +77 -73
  29. package/lib/external/mcp/handlers/candidate.js +29 -276
  30. package/lib/external/mcp/handlers/guard.js +2 -0
  31. package/lib/external/mcp/handlers/knowledge.js +205 -0
  32. package/lib/external/mcp/handlers/structure.js +25 -23
  33. package/lib/external/mcp/handlers/system.js +10 -12
  34. package/lib/external/mcp/tools.js +125 -138
  35. package/lib/http/HttpServer.js +4 -8
  36. package/lib/http/routes/extract.js +48 -4
  37. package/lib/http/routes/knowledge.js +246 -0
  38. package/lib/http/routes/search.js +12 -17
  39. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  40. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  41. package/lib/injection/ServiceContainer.js +49 -60
  42. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  43. package/lib/service/automation/DirectiveDetector.js +2 -3
  44. package/lib/service/automation/FileWatcher.js +67 -28
  45. package/lib/service/automation/XcodeIntegration.js +931 -156
  46. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  47. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  48. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  49. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  50. package/lib/service/chat/CandidateGuardrail.js +1 -1
  51. package/lib/service/chat/ChatAgent.js +46 -45
  52. package/lib/service/chat/ContextWindow.js +5 -5
  53. package/lib/service/chat/ProducerAgent.js +7 -7
  54. package/lib/service/chat/tools.js +130 -123
  55. package/lib/service/guard/GuardCheckEngine.js +114 -10
  56. package/lib/service/guard/GuardService.js +59 -48
  57. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  58. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  59. package/lib/service/knowledge/KnowledgeService.js +725 -0
  60. package/lib/service/search/SearchEngine.js +92 -19
  61. package/lib/service/skills/SignalCollector.js +12 -7
  62. package/lib/service/skills/SkillAdvisor.js +13 -11
  63. package/lib/service/snippet/SnippetFactory.js +5 -5
  64. package/package.json +1 -1
  65. package/scripts/install-cursor-skill.js +0 -6
  66. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  67. package/skills/autosnippet-analysis/SKILL.md +15 -7
  68. package/skills/autosnippet-candidates/SKILL.md +6 -6
  69. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  70. package/skills/autosnippet-concepts/SKILL.md +7 -6
  71. package/skills/autosnippet-create/SKILL.md +13 -13
  72. package/skills/autosnippet-intent/SKILL.md +3 -2
  73. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  74. package/skills/autosnippet-recipes/SKILL.md +16 -4
  75. package/templates/constitution.yaml +1 -1
  76. package/templates/copilot-instructions.md +6 -6
  77. package/templates/recipes-setup/README.md +3 -3
  78. package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
  79. package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
  80. package/lib/cli/CandidateSyncService.js +0 -261
  81. package/lib/cli/SyncService.js +0 -356
  82. package/lib/domain/candidate/Candidate.js +0 -196
  83. package/lib/domain/candidate/CandidateRepository.js +0 -107
  84. package/lib/domain/candidate/Reasoning.js +0 -52
  85. package/lib/domain/recipe/Recipe.js +0 -421
  86. package/lib/domain/recipe/RecipeRepository.js +0 -54
  87. package/lib/domain/types/CandidateStatus.js +0 -52
  88. package/lib/http/routes/candidates.js +0 -559
  89. package/lib/http/routes/recipes.js +0 -397
  90. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  91. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  92. package/lib/service/candidate/CandidateAggregator.js +0 -52
  93. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  94. package/lib/service/candidate/CandidateService.js +0 -1001
  95. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  96. package/lib/service/recipe/RecipeService.js +0 -786
  97. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Model Context Protocol (stdio transport)
5
5
  * 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
6
- * 34 工具,全部基于 V2 服务层,不依赖 V1
6
+ * 38 工具,全部基于 V2 服务层,不依赖 V1
7
7
  * Gateway 权限 gating: 写操作经过 Gateway 权限/宪法/审计检查
8
8
  *
9
9
  * 本文件仅包含服务编排层(初始化、路由、Gateway gating、生命周期)。
@@ -31,6 +31,7 @@ import * as candidateHandlers from './handlers/candidate.js';
31
31
  import * as guardHandlers from './handlers/guard.js';
32
32
  import * as bootstrapHandlers from './handlers/bootstrap.js';
33
33
  import * as skillHandlers from './handlers/skill.js';
34
+ import * as knowledgeHandlers from './handlers/knowledge.js';
34
35
 
35
36
  // ─── McpServer 类 ─────────────────────────────────────────────
36
37
 
@@ -140,12 +141,9 @@ export class McpServer {
140
141
  case 'autosnippet_graph_impact': return structureHandlers.graphImpact(ctx, args);
141
142
  case 'autosnippet_graph_path': return structureHandlers.graphPath(ctx, args);
142
143
  case 'autosnippet_graph_stats': return structureHandlers.graphStats(ctx);
143
- // 候选校验 & 提交 & AI 补全
144
+ // 候选校验 & AI 补全(提交已移至 V3 knowledge handlers)
144
145
  case 'autosnippet_validate_candidate': return candidateHandlers.validateCandidate(ctx, args);
145
146
  case 'autosnippet_check_duplicate': return candidateHandlers.checkDuplicate(ctx, args);
146
- case 'autosnippet_submit_candidate': return candidateHandlers.submitSingle(ctx, args);
147
- case 'autosnippet_submit_candidates': return candidateHandlers.submitBatch(ctx, args);
148
- case 'autosnippet_submit_draft_recipes': return candidateHandlers.submitDrafts(ctx, args);
149
147
  case 'autosnippet_enrich_candidates': return candidateHandlers.enrichCandidates(ctx, args);
150
148
  // Guard & 扫描
151
149
  case 'autosnippet_guard_check': return guardHandlers.guardCheck(ctx, args);
@@ -161,6 +159,10 @@ export class McpServer {
161
159
  case 'autosnippet_delete_skill': return skillHandlers.deleteSkill(ctx, args);
162
160
  case 'autosnippet_update_skill': return skillHandlers.updateSkill(ctx, args);
163
161
  case 'autosnippet_suggest_skills': return skillHandlers.suggestSkills(ctx);
162
+ // V3 知识条目
163
+ case 'autosnippet_submit_knowledge': return knowledgeHandlers.submitKnowledge(ctx, args);
164
+ case 'autosnippet_submit_knowledge_batch': return knowledgeHandlers.submitKnowledgeBatch(ctx, args);
165
+ case 'autosnippet_knowledge_lifecycle': return knowledgeHandlers.knowledgeLifecycle(ctx, args);
164
166
  default: throw new Error(`Unknown tool: ${name}`);
165
167
  }
166
168
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 1. Analyst Agent 自由探索代码 (AST 工具 + 文件搜索)
7
7
  * 2. HandoffProtocol 质量门控
8
- * 3. Producer Agent 格式化输出 (submit_candidate)
8
+ * 3. Producer Agent 格式化输出 (submit_knowledge)
9
9
  * 4. TierScheduler 分层并行执行
10
10
  *
11
11
  * @module pipeline/orchestrator
@@ -412,7 +412,7 @@ export async function fillDimensionsV3(fillContext) {
412
412
  // 记录到 DimensionContext
413
413
  for (const tc of (producerResult.toolCalls || [])) {
414
414
  const tool = tc.tool || tc.name;
415
- if (tool === 'submit_candidate' || tool === 'submit_with_check') {
415
+ if (tool === 'submit_knowledge' || tool === 'submit_with_check') {
416
416
  dimContext.addSubmittedCandidate(dimId, {
417
417
  title: tc.params?.title || '',
418
418
  subTopic: tc.params?.category || '',
@@ -392,7 +392,7 @@ export async function bootstrapKnowledge(ctx, args) {
392
392
  skillWorthyDimensions: dimensions.filter(d => d.skillWorthy).map(d => d.id),
393
393
  candidateOnlyDimensions: dimensions.filter(d => !d.skillWorthy).map(d => d.id),
394
394
  candidateRequiredFields: ['title', 'code', 'language', 'category', 'knowledgeType', 'reasoning'],
395
- submissionTool: 'autosnippet_submit_candidates',
395
+ submissionTool: 'autosnippet_submit_knowledge_batch',
396
396
  expectedOutput: '候选知识(微观代码维度:code-pattern/best-practice/event-and-data-flow + 深度扫描:objc-deep-scan/category-scan)+ 6 个 Project Skills(宏观叙事维度:code-standard/architecture/project-profile/agent-guidelines + 深度扫描:objc-deep-scan/category-scan)',
397
397
  },
398
398
 
@@ -423,7 +423,7 @@ export async function bootstrapKnowledge(ctx, args) {
423
423
  '== 完成后可执行的后续操作 ==',
424
424
  '1. 调用 autosnippet_enrich_candidates(candidateIds) 补全候选缺失字段',
425
425
  '2. 调用 autosnippet_bootstrap_refine() 对候选进行 AI 精炼',
426
- '3. 使用 autosnippet_submit_candidates 手动提交更多候选',
426
+ '3. 使用 autosnippet_submit_knowledge_batch 手动提交更多知识条目',
427
427
  '4. 使用 autosnippet_load_skill 加载自动生成的 Project Skills',
428
428
  '',
429
429
  '== 宏观维度 → Project Skills ==',
@@ -520,7 +520,7 @@ export async function bootstrapKnowledge(ctx, args) {
520
520
  /**
521
521
  * bootstrapRefine — Phase 6 AI 润色
522
522
  *
523
- * 对 Bootstrap Phase 5 产出的候选进行 AI 二次精炼:
523
+ * 对 Bootstrap Phase 5 产出的知识条目进行 AI 二次精炼:
524
524
  * - 改善模板化描述 → 更自然精准
525
525
  * - 补充高阶架构洞察
526
526
  * - 推断并填充 relations 关联
@@ -531,7 +531,7 @@ export async function bootstrapKnowledge(ctx, args) {
531
531
  */
532
532
  export async function bootstrapRefine(ctx, args) {
533
533
  const t0 = Date.now();
534
- const candidateService = ctx.container.get('candidateService');
534
+ const knowledgeService = ctx.container.get('knowledgeService');
535
535
  const aiProvider = ctx.container.get('aiProvider');
536
536
 
537
537
  if (!aiProvider) {
@@ -545,17 +545,122 @@ export async function bootstrapRefine(ctx, args) {
545
545
  onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
546
546
  } catch { /* optional */ }
547
547
 
548
- const result = await candidateService.refineBootstrapCandidates(
549
- aiProvider,
550
- { candidateIds: args.candidateIds, userPrompt: args.userPrompt, dryRun: args.dryRun, onProgress },
551
- { userId: 'external_agent' },
552
- );
548
+ // 1. 收集待润色条目
549
+ let entries;
550
+ if (args.candidateIds?.length) {
551
+ entries = [];
552
+ for (const id of args.candidateIds) {
553
+ const e = await knowledgeService.get(id);
554
+ if (e) entries.push(typeof e.toJSON === 'function' ? e.toJSON() : e);
555
+ }
556
+ } else {
557
+ const result = await knowledgeService.list({ lifecycle: 'pending', source: 'bootstrap' }, { page: 1, pageSize: 200 });
558
+ entries = (result.items || []).map(e => typeof e.toJSON === 'function' ? e.toJSON() : e);
559
+ }
560
+
561
+ if (entries.length === 0) {
562
+ return envelope({ success: true, data: { refined: 0, total: 0, errors: [], results: [] }, meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 } });
563
+ }
564
+
565
+ onProgress?.('refine:started', { total: entries.length, candidateIds: entries.map(e => e.id) });
566
+
567
+ // 2. 逐条 AI 润色
568
+ const allTitles = entries.map(e => e.title).filter(Boolean);
569
+ const results = [];
570
+ const errors = [];
571
+ let refined = 0;
572
+ let processed = 0;
573
+
574
+ for (const entry of entries) {
575
+ processed++;
576
+ onProgress?.('refine:item-started', { candidateId: entry.id, title: entry.title, current: processed, total: entries.length, progress: Math.round(((processed - 1) / entries.length) * 100) });
577
+
578
+ try {
579
+ const prompt = `你是一位高级代码知识管理专家。请改进以下知识条目:
580
+
581
+ 标题: ${entry.title}
582
+ 类型: ${entry.knowledge_type}
583
+ 语言: ${entry.language}
584
+ 描述: ${entry.description || ''}
585
+ 代码:
586
+ \`\`\`
587
+ ${(entry.content?.pattern || '').substring(0, 2000)}
588
+ \`\`\`
589
+
590
+ 同批次知识: ${allTitles.slice(0, 20).join(', ')}
591
+ ${args.userPrompt ? `用户补充: ${args.userPrompt}` : ''}
592
+
593
+ 请返回 JSON:
594
+ {
595
+ "summary": "改进后的中文摘要",
596
+ "tags": ["建议标签"],
597
+ "relations": [{"target":"相关条目标题","relation":"depends_on|extends|related_to"}],
598
+ "confidence": 0.0-1.0,
599
+ "insight": "架构洞察(一句话)"
600
+ }`;
601
+
602
+ const response = await aiProvider.chat(prompt, { temperature: 0.3 });
603
+ const parsed = aiProvider.extractJSON(response, '{', '}');
604
+
605
+ if (!parsed) {
606
+ errors.push({ id: entry.id, title: entry.title, error: 'AI returned no valid JSON' });
607
+ onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: 'No valid JSON', current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
608
+ continue;
609
+ }
610
+
611
+ if (args.dryRun) {
612
+ results.push({ id: entry.id, title: entry.title, preview: parsed });
613
+ onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: false, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
614
+ continue;
615
+ }
616
+
617
+ // 构建更新数据
618
+ const updateData = {};
619
+ let changed = false;
620
+
621
+ if (parsed.summary && parsed.summary !== entry.summary_cn) {
622
+ updateData.summary_cn = parsed.summary;
623
+ changed = true;
624
+ }
625
+ if (parsed.tags && Array.isArray(parsed.tags)) {
626
+ const merged = [...new Set([...(entry.tags || []), ...parsed.tags])];
627
+ if (merged.length > (entry.tags || []).length) {
628
+ updateData.tags = merged;
629
+ changed = true;
630
+ }
631
+ }
632
+ if (typeof parsed.confidence === 'number' && parsed.confidence !== 0.6) {
633
+ updateData.reasoning = { ...(entry.reasoning || {}), confidence: parsed.confidence };
634
+ changed = true;
635
+ }
636
+ if (parsed.insight) {
637
+ updateData.description = `${entry.description || ''}\n\n**AI Insight**: ${parsed.insight}`.trim();
638
+ changed = true;
639
+ }
640
+
641
+ if (changed) {
642
+ await knowledgeService.update(entry.id, updateData);
643
+ refined++;
644
+ }
645
+
646
+ results.push({ id: entry.id, title: entry.title, refined: changed, fields: Object.keys(parsed) });
647
+ onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: changed, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100), refinedSoFar: refined });
648
+ } catch (err) {
649
+ errors.push({ id: entry.id, title: entry.title, error: err.message });
650
+ onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: err.message, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
651
+ }
652
+ }
653
+
654
+ onProgress?.('refine:completed', { total: entries.length, refined, failed: errors.length });
553
655
 
554
656
  return envelope({
555
657
  success: true,
556
658
  data: {
557
- ...result,
558
- message: `Phase 6 AI 润色完成: ${result.refined}/${result.total} 条候选已更新${args.dryRun ? '(预览模式)' : ''}`,
659
+ refined,
660
+ total: entries.length,
661
+ errors,
662
+ results,
663
+ message: `Phase 6 AI 润色完成: ${refined}/${entries.length} 条知识条目已更新${args.dryRun ? '(预览模式)' : ''}`,
559
664
  },
560
665
  meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 },
561
666
  });
@@ -1,65 +1,70 @@
1
1
  /**
2
- * MCP Handlers — 知识浏览类
3
- * listByKind, listRecipes, getRecipe, recipeInsights, complianceReport, confirmUsage
2
+ * MCP Handlers — 知识浏览类 (V3: 使用 knowledgeService)
3
+ * listByKind, listRecipes, getRecipe, recipeInsights, confirmUsage
4
4
  */
5
5
 
6
6
  import { envelope } from '../envelope.js';
7
7
 
8
+ /** 将 KnowledgeEntry 的 V3 字段投影为列表摘要 */
9
+ function _projectItem(r) {
10
+ const json = typeof r.toJSON === 'function' ? r.toJSON() : r;
11
+ return {
12
+ id: json.id, title: json.title, description: json.description,
13
+ trigger: json.trigger || '', lifecycle: json.lifecycle, kind: json.kind,
14
+ language: json.language, category: json.category,
15
+ knowledge_type: json.knowledge_type, complexity: json.complexity,
16
+ scope: json.scope, tags: json.tags || [],
17
+ quality: json.quality || null, stats: json.stats || null,
18
+ // 兼容旧字段名
19
+ status: json.lifecycle, knowledgeType: json.knowledge_type,
20
+ statistics: json.stats,
21
+ };
22
+ }
23
+
8
24
  export async function listByKind(ctx, kind, args) {
9
- const recipeService = ctx.container.get('recipeService');
25
+ const ks = ctx.container.get('knowledgeService');
10
26
  const filters = { kind };
11
- if (args.status) filters.status = args.status;
12
- if (args.language) filters.language = args.language;
13
- if (args.category) filters.category = args.category;
14
- const result = await recipeService.listRecipes(filters, { page: 1, pageSize: args.limit || 20 });
15
- const items = (result?.data || result?.items || []).map(r => ({
16
- id: r.id, title: r.title || r.name, description: r.description,
17
- trigger: r.trigger || '', status: r.status, language: r.language, category: r.category,
18
- knowledgeType: r.knowledgeType, kind: r.kind,
19
- complexity: r.complexity, scope: r.scope, tags: r.tags || [],
20
- quality: r.quality || null, statistics: r.statistics || null,
21
- }));
27
+ if (args.language) filters.language = args.language;
28
+ if (args.category) filters.category = args.category;
29
+ const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
30
+ const items = (result?.data || []).map(_projectItem);
22
31
  return envelope({ success: true, data: { kind, count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: `autosnippet_list_${kind}s` } });
23
32
  }
24
33
 
25
34
  export async function listRecipes(ctx, args) {
26
- const recipeService = ctx.container.get('recipeService');
35
+ const ks = ctx.container.get('knowledgeService');
27
36
  const filters = {};
28
- if (args.kind) filters.kind = args.kind;
29
- if (args.language) filters.language = args.language;
30
- if (args.category) filters.category = args.category;
31
- if (args.knowledgeType) filters.knowledgeType = args.knowledgeType;
32
- if (args.status) filters.status = args.status;
33
- if (args.complexity) filters.complexity = args.complexity;
34
- const result = await recipeService.listRecipes(filters, { page: 1, pageSize: args.limit || 20 });
35
- const items = (result?.data || result?.items || []).map(r => ({
36
- id: r.id, title: r.title, description: r.description,
37
- trigger: r.trigger || '', status: r.status, language: r.language, category: r.category,
38
- kind: r.kind, knowledgeType: r.knowledgeType, complexity: r.complexity,
39
- scope: r.scope, tags: r.tags,
40
- quality: r.quality, statistics: r.statistics,
41
- }));
37
+ if (args.kind) filters.kind = args.kind;
38
+ if (args.language) filters.language = args.language;
39
+ if (args.category) filters.category = args.category;
40
+ if (args.knowledgeType) filters.knowledgeType = args.knowledgeType;
41
+ if (args.complexity) filters.complexity = args.complexity;
42
+ if (args.status) filters.lifecycle = args.status;
43
+ const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
44
+ const items = (result?.data || []).map(_projectItem);
42
45
  return envelope({ success: true, data: { count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: 'autosnippet_list_recipes' } });
43
46
  }
44
47
 
45
48
  export async function getRecipe(ctx, args) {
46
49
  if (!args.id) throw new Error('id is required');
47
- const recipeService = ctx.container.get('recipeService');
48
- const recipe = await recipeService.getRecipe(args.id);
49
- if (!recipe) throw new Error(`Recipe not found: ${args.id}`);
50
- return envelope({ success: true, data: recipe, meta: { tool: 'autosnippet_get_recipe' } });
50
+ const ks = ctx.container.get('knowledgeService');
51
+ const entry = await ks.get(args.id);
52
+ if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
53
+ const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
54
+ return envelope({ success: true, data: json, meta: { tool: 'autosnippet_get_recipe' } });
51
55
  }
52
56
 
53
57
  export async function recipeInsights(ctx, args) {
54
58
  if (!args.id) throw new Error('id is required');
55
- const recipeService = ctx.container.get('recipeService');
56
- const recipe = await recipeService.getRecipe(args.id);
57
- if (!recipe) throw new Error(`Recipe not found: ${args.id}`);
59
+ const ks = ctx.container.get('knowledgeService');
60
+ const entry = await ks.get(args.id);
61
+ if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
62
+ const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
58
63
 
59
64
  // 聚合关系摘要
60
65
  const relationsSummary = {};
61
- if (recipe.relations) {
62
- for (const [type, targets] of Object.entries(recipe.relations)) {
66
+ if (json.relations) {
67
+ for (const [type, targets] of Object.entries(json.relations)) {
63
68
  if (Array.isArray(targets) && targets.length > 0) {
64
69
  relationsSummary[type] = targets.length;
65
70
  }
@@ -68,8 +73,8 @@ export async function recipeInsights(ctx, args) {
68
73
 
69
74
  // 约束条件概览
70
75
  const constraintsSummary = {};
71
- if (recipe.constraints) {
72
- for (const [type, items] of Object.entries(recipe.constraints)) {
76
+ if (json.constraints) {
77
+ for (const [type, items] of Object.entries(json.constraints)) {
73
78
  if (Array.isArray(items) && items.length > 0) {
74
79
  constraintsSummary[type] = items;
75
80
  }
@@ -77,43 +82,42 @@ export async function recipeInsights(ctx, args) {
77
82
  }
78
83
 
79
84
  const insights = {
80
- id: recipe.id,
81
- title: recipe.title,
82
- trigger: recipe.trigger || '',
83
- kind: recipe.kind,
84
- status: recipe.status,
85
- language: recipe.language,
86
- category: recipe.category,
87
- knowledgeType: recipe.knowledgeType,
85
+ id: json.id,
86
+ title: json.title,
87
+ trigger: json.trigger || '',
88
+ kind: json.kind,
89
+ lifecycle: json.lifecycle,
90
+ language: json.language,
91
+ category: json.category,
92
+ knowledge_type: json.knowledge_type,
88
93
  quality: {
89
- overall: recipe.quality?.overall ?? null,
90
- codeCompleteness: recipe.quality?.codeCompleteness ?? null,
91
- projectAdaptation: recipe.quality?.projectAdaptation ?? null,
92
- documentationClarity: recipe.quality?.documentationClarity ?? null,
94
+ overall: json.quality?.overall ?? null,
95
+ completeness: json.quality?.completeness ?? null,
96
+ adaptation: json.quality?.adaptation ?? null,
97
+ documentation: json.quality?.documentation ?? null,
93
98
  },
94
- statistics: {
95
- adoptionCount: recipe.statistics?.adoptionCount ?? 0,
96
- applicationCount: recipe.statistics?.applicationCount ?? 0,
97
- guardHitCount: recipe.statistics?.guardHitCount ?? 0,
98
- viewCount: recipe.statistics?.viewCount ?? 0,
99
- successCount: recipe.statistics?.successCount ?? 0,
100
- feedbackScore: recipe.statistics?.feedbackScore ?? 0,
99
+ stats: {
100
+ adoptions: json.stats?.adoptions ?? 0,
101
+ applications: json.stats?.applications ?? 0,
102
+ guard_hits: json.stats?.guard_hits ?? 0,
103
+ views: json.stats?.views ?? 0,
104
+ search_hits: json.stats?.search_hits ?? 0,
101
105
  },
102
106
  content: {
103
- hasPattern: !!recipe.content?.pattern,
104
- hasRationale: !!recipe.content?.rationale,
105
- hasMarkdown: !!recipe.content?.markdown,
106
- stepsCount: recipe.content?.steps?.length ?? 0,
107
- codeChangesCount: recipe.content?.codeChanges?.length ?? 0,
107
+ hasPattern: !!json.content?.pattern,
108
+ hasRationale: !!json.content?.rationale,
109
+ hasMarkdown: !!json.content?.markdown,
110
+ stepsCount: json.content?.steps?.length ?? 0,
111
+ codeChangesCount: json.content?.code_changes?.length ?? 0,
108
112
  },
109
113
  relations: relationsSummary,
110
114
  constraints: constraintsSummary,
111
- tags: recipe.tags || [],
112
- complexity: recipe.complexity,
113
- scope: recipe.scope,
114
- createdBy: recipe.createdBy,
115
- createdAt: recipe.createdAt,
116
- updatedAt: recipe.updatedAt,
115
+ tags: json.tags || [],
116
+ complexity: json.complexity,
117
+ scope: json.scope,
118
+ created_by: json.created_by,
119
+ created_at: json.created_at,
120
+ updated_at: json.updated_at,
117
121
  };
118
122
 
119
123
  return envelope({ success: true, data: insights, meta: { tool: 'autosnippet_recipe_insights' } });
@@ -121,11 +125,11 @@ export async function recipeInsights(ctx, args) {
121
125
 
122
126
  export async function confirmUsage(ctx, args) {
123
127
  if (!args.recipeId) throw new Error('recipeId is required');
124
- const recipeService = ctx.container.get('recipeService');
128
+ const ks = ctx.container.get('knowledgeService');
125
129
  const usageType = args.usageType || 'adoption';
126
130
  const feedback = args.feedback || null;
127
131
 
128
- await recipeService.incrementUsage(args.recipeId, usageType, {
132
+ await ks.incrementUsage(args.recipeId, usageType, {
129
133
  feedback,
130
134
  actor: 'mcp_user',
131
135
  });
@@ -146,7 +150,7 @@ export async function confirmUsage(ctx, args) {
146
150
  return envelope({
147
151
  success: true,
148
152
  data: { recipeId: args.recipeId, usageType, feedback },
149
- message: `已记录 Recipe ${args.recipeId} 的${usageType === 'adoption' ? '采纳' : '应用'}`,
153
+ message: `已记录使用 ${args.recipeId} 的${usageType === 'adoption' ? '采纳' : '应用'}`,
150
154
  meta: { tool: 'autosnippet_confirm_usage' },
151
155
  });
152
156
  }