autosnippet 2.9.0 → 2.11.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 (115) hide show
  1. package/README.md +12 -12
  2. package/bin/cli.js +53 -40
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
  5. package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
  6. package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +35 -36
  9. package/lib/cli/KnowledgeSyncService.js +345 -0
  10. package/lib/cli/SetupService.js +8 -26
  11. package/lib/cli/UpgradeService.js +28 -0
  12. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  13. package/lib/domain/index.js +16 -11
  14. package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +99 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +128 -0
  19. package/lib/domain/knowledge/values/Content.js +69 -0
  20. package/lib/domain/knowledge/values/Quality.js +81 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +70 -0
  22. package/lib/domain/knowledge/values/Relations.js +142 -0
  23. package/lib/domain/knowledge/values/Stats.js +72 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +85 -11
  26. package/lib/external/mcp/McpServer.js +7 -5
  27. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
  28. package/lib/external/mcp/handlers/bootstrap.js +116 -11
  29. package/lib/external/mcp/handlers/browse.js +76 -73
  30. package/lib/external/mcp/handlers/candidate.js +26 -275
  31. package/lib/external/mcp/handlers/guard.js +2 -0
  32. package/lib/external/mcp/handlers/knowledge.js +267 -0
  33. package/lib/external/mcp/handlers/structure.js +25 -23
  34. package/lib/external/mcp/handlers/system.js +10 -12
  35. package/lib/external/mcp/tools.js +134 -140
  36. package/lib/http/HttpServer.js +14 -8
  37. package/lib/http/routes/ai.js +4 -3
  38. package/lib/http/routes/extract.js +48 -4
  39. package/lib/http/routes/knowledge.js +246 -0
  40. package/lib/http/routes/search.js +12 -17
  41. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  42. package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
  43. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  44. package/lib/injection/ServiceContainer.js +69 -60
  45. package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
  46. package/lib/service/automation/DirectiveDetector.js +2 -3
  47. package/lib/service/automation/FileWatcher.js +59 -28
  48. package/lib/service/automation/XcodeIntegration.js +931 -156
  49. package/lib/service/automation/handlers/AlinkHandler.js +5 -4
  50. package/lib/service/automation/handlers/CreateHandler.js +53 -19
  51. package/lib/service/automation/handlers/DraftHandler.js +1 -1
  52. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  53. package/lib/service/automation/handlers/SearchHandler.js +25 -22
  54. package/lib/service/candidate/SimilarityService.js +2 -2
  55. package/lib/service/chat/AnalystAgent.js +9 -0
  56. package/lib/service/chat/CandidateGuardrail.js +22 -11
  57. package/lib/service/chat/ChatAgent.js +132 -54
  58. package/lib/service/chat/ContextWindow.js +5 -5
  59. package/lib/service/chat/HandoffProtocol.js +1 -0
  60. package/lib/service/chat/ProducerAgent.js +40 -13
  61. package/lib/service/chat/ReasoningLayer.js +854 -0
  62. package/lib/service/chat/ReasoningTrace.js +329 -0
  63. package/lib/service/chat/tools.js +308 -205
  64. package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
  65. package/lib/service/cursor/KnowledgeCompressor.js +87 -0
  66. package/lib/service/cursor/RulesGenerator.js +168 -0
  67. package/lib/service/cursor/SkillsSyncer.js +268 -0
  68. package/lib/service/cursor/TokenBudget.js +58 -0
  69. package/lib/service/cursor/TopicClassifier.js +141 -0
  70. package/lib/service/guard/GuardCheckEngine.js +99 -10
  71. package/lib/service/guard/GuardService.js +57 -46
  72. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  73. package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
  74. package/lib/service/knowledge/KnowledgeService.js +802 -0
  75. package/lib/service/recipe/RecipeParser.js +3 -12
  76. package/lib/service/search/SearchEngine.js +67 -22
  77. package/lib/service/skills/SignalCollector.js +14 -9
  78. package/lib/service/skills/SkillAdvisor.js +13 -11
  79. package/lib/service/snippet/SnippetFactory.js +5 -5
  80. package/lib/service/spm/SpmService.js +15 -48
  81. package/lib/shared/RecipeReadinessChecker.js +6 -11
  82. package/package.json +1 -1
  83. package/scripts/install-cursor-skill.js +0 -6
  84. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  85. package/skills/autosnippet-analysis/SKILL.md +15 -7
  86. package/skills/autosnippet-candidates/SKILL.md +8 -8
  87. package/skills/autosnippet-coldstart/SKILL.md +8 -4
  88. package/skills/autosnippet-concepts/SKILL.md +7 -6
  89. package/skills/autosnippet-create/SKILL.md +13 -13
  90. package/skills/autosnippet-intent/SKILL.md +3 -2
  91. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  92. package/skills/autosnippet-recipes/SKILL.md +18 -6
  93. package/templates/constitution.yaml +1 -1
  94. package/templates/copilot-instructions.md +6 -6
  95. package/templates/recipes-setup/README.md +3 -3
  96. package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
  97. package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
  98. package/lib/cli/CandidateSyncService.js +0 -261
  99. package/lib/cli/SyncService.js +0 -356
  100. package/lib/domain/candidate/Candidate.js +0 -196
  101. package/lib/domain/candidate/CandidateRepository.js +0 -107
  102. package/lib/domain/candidate/Reasoning.js +0 -52
  103. package/lib/domain/recipe/Recipe.js +0 -421
  104. package/lib/domain/recipe/RecipeRepository.js +0 -54
  105. package/lib/domain/types/CandidateStatus.js +0 -52
  106. package/lib/http/routes/candidates.js +0 -559
  107. package/lib/http/routes/recipes.js +0 -397
  108. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  109. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  110. package/lib/service/candidate/CandidateAggregator.js +0 -52
  111. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  112. package/lib/service/candidate/CandidateService.js +0 -1001
  113. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  114. package/lib/service/recipe/RecipeService.js +0 -786
  115. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -33,7 +33,7 @@
33
33
  * │ 17. generate_guard_rule AI 生成 Guard 规则 │
34
34
  * └─────────────────────────────────────────────────────┘
35
35
  * ┌─── 生命周期操作类 (7) ─────────────────────────────┐
36
- * │ 18. submit_candidate 提交候选 │
36
+ * │ 18. submit_knowledge 提交候选 │
37
37
  * │ 19. approve_candidate 批准候选 │
38
38
  * │ 20. reject_candidate 驳回候选 │
39
39
  * │ 21. publish_recipe 发布 Recipe │
@@ -853,19 +853,19 @@ const searchRecipes = {
853
853
  },
854
854
  },
855
855
  handler: async (params, ctx) => {
856
- const recipeService = ctx.container.get('recipeService');
856
+ const knowledgeService = ctx.container.get('knowledgeService');
857
857
  const { keyword, category, language, knowledgeType, limit = 10 } = params;
858
858
 
859
859
  if (keyword) {
860
- return recipeService.searchRecipes(keyword, { page: 1, pageSize: limit });
860
+ return knowledgeService.search(keyword, { page: 1, pageSize: limit });
861
861
  }
862
862
 
863
- const filters = {};
863
+ const filters = { lifecycle: 'active' };
864
864
  if (category) filters.category = category;
865
865
  if (language) filters.language = language;
866
866
  if (knowledgeType) filters.knowledgeType = knowledgeType;
867
867
 
868
- return recipeService.listRecipes(filters, { page: 1, pageSize: limit });
868
+ return knowledgeService.list(filters, { page: 1, pageSize: limit });
869
869
  },
870
870
  };
871
871
 
@@ -886,19 +886,20 @@ const searchCandidates = {
886
886
  },
887
887
  },
888
888
  handler: async (params, ctx) => {
889
- const candidateService = ctx.container.get('candidateService');
889
+ const knowledgeService = ctx.container.get('knowledgeService');
890
890
  const { keyword, status, language, category, limit = 10 } = params;
891
891
 
892
892
  if (keyword) {
893
- return candidateService.searchCandidates(keyword, { page: 1, pageSize: limit });
893
+ return knowledgeService.search(keyword, { page: 1, pageSize: limit });
894
894
  }
895
895
 
896
+ // V3: status 映射为 lifecycle
896
897
  const filters = {};
897
- if (status) filters.status = status;
898
+ if (status) filters.lifecycle = status;
898
899
  if (language) filters.language = language;
899
900
  if (category) filters.category = category;
900
901
 
901
- return candidateService.listCandidates(filters, { page: 1, pageSize: limit });
902
+ return knowledgeService.list(filters, { page: 1, pageSize: limit });
902
903
  },
903
904
  };
904
905
 
@@ -916,10 +917,13 @@ const getRecipeDetail = {
916
917
  required: ['recipeId'],
917
918
  },
918
919
  handler: async (params, ctx) => {
919
- const recipeRepo = ctx.container.get('recipeRepository');
920
- const recipe = await recipeRepo.findById(params.recipeId);
921
- if (!recipe) return { error: `Recipe '${params.recipeId}' not found` };
922
- return recipe;
920
+ const knowledgeService = ctx.container.get('knowledgeService');
921
+ try {
922
+ const entry = await knowledgeService.get(params.recipeId);
923
+ return typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
924
+ } catch {
925
+ return { error: `Knowledge entry '${params.recipeId}' not found` };
926
+ }
923
927
  },
924
928
  };
925
929
 
@@ -931,13 +935,8 @@ const getProjectStats = {
931
935
  description: '获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
932
936
  parameters: { type: 'object', properties: {} },
933
937
  handler: async (_params, ctx) => {
934
- const recipeService = ctx.container.get('recipeService');
935
- const candidateService = ctx.container.get('candidateService');
936
-
937
- const [recipeStats, candidateStats] = await Promise.all([
938
- recipeService.getRecipeStats(),
939
- candidateService.getCandidateStats(),
940
- ]);
938
+ const knowledgeService = ctx.container.get('knowledgeService');
939
+ const stats = await knowledgeService.getStats();
941
940
 
942
941
  // 尝试获取知识图谱统计
943
942
  let graphStats = null;
@@ -947,8 +946,7 @@ const getProjectStats = {
947
946
  } catch { /* KG not available */ }
948
947
 
949
948
  return {
950
- recipes: recipeStats,
951
- candidates: candidateStats,
949
+ knowledge: stats,
952
950
  knowledgeGraph: graphStats,
953
951
  };
954
952
  },
@@ -1000,15 +998,16 @@ const searchKnowledge = {
1000
998
  // 降级: RetrievalFunnel + 全量候选
1001
999
  try {
1002
1000
  const funnel = ctx.container.get('retrievalFunnel');
1003
- const recipeRepo = ctx.container.get('recipeRepository');
1004
- const allRecipes = await recipeRepo.findAll?.() || [];
1001
+ const knowledgeRepo = ctx.container.get('knowledgeRepository');
1002
+ const allResult = await knowledgeRepo.findWithPagination({}, { page: 1, pageSize: 500 });
1003
+ const allRecipes = allResult?.items || [];
1005
1004
 
1006
1005
  // 规范化为 funnel 输入格式
1007
1006
  const candidates = allRecipes.map(r => ({
1008
1007
  id: r.id,
1009
1008
  title: r.title,
1010
1009
  content: r.content || r.code || '',
1011
- description: r.description || r.summary_cn || '',
1010
+ description: r.description || '',
1012
1011
  language: r.language,
1013
1012
  category: r.category,
1014
1013
  trigger: r.trigger || '',
@@ -1088,7 +1087,7 @@ const extractRecipes = {
1088
1087
  },
1089
1088
  handler: async (params, ctx) => {
1090
1089
  if (!ctx.aiProvider) return { error: 'AI provider not available' };
1091
- const { targetName, files } = params;
1090
+ const { targetName, files, comprehensive } = params;
1092
1091
 
1093
1092
  // 加载语言参考 Skill(如有),注入到 AI 提取 prompt
1094
1093
  let skillReference = null;
@@ -1124,17 +1123,16 @@ const extractRecipes = {
1124
1123
  const extractOpts = {};
1125
1124
  if (skillReference) extractOpts.skillReference = skillReference;
1126
1125
  if (astContext) extractOpts.astContext = astContext;
1126
+ if (comprehensive) extractOpts.comprehensive = true;
1127
1127
 
1128
1128
  // 首选:使用当前 aiProvider
1129
+ let recipes;
1130
+ let fallbackUsed;
1129
1131
  try {
1130
- const recipes = await ctx.aiProvider.extractRecipes(targetName, files, extractOpts);
1131
- const count = Array.isArray(recipes) ? recipes.length : 0;
1132
- if (count === 0) {
1133
- ctx.logger?.warn?.(`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`);
1134
- }
1135
- return { targetName, extracted: count, recipes: Array.isArray(recipes) ? recipes : [] };
1132
+ recipes = await ctx.aiProvider.extractRecipes(targetName, files, extractOpts);
1136
1133
  } catch (primaryErr) {
1137
1134
  // 尝试 fallback(如果 AiFactory 可用)
1135
+ let recovered = false;
1138
1136
  try {
1139
1137
  const aiFactory = ctx.container?.singletons?._aiFactory;
1140
1138
  if (aiFactory?.isGeoOrProviderError?.(primaryErr)) {
@@ -1143,14 +1141,67 @@ const extractRecipes = {
1143
1141
  for (const fbName of fallbacks) {
1144
1142
  try {
1145
1143
  const fbProvider = aiFactory.createProvider({ provider: fbName });
1146
- const recipes = await fbProvider.extractRecipes(targetName, files, extractOpts);
1147
- return { targetName, extracted: Array.isArray(recipes) ? recipes.length : 0, recipes: Array.isArray(recipes) ? recipes : [], fallbackUsed: fbName };
1144
+ recipes = await fbProvider.extractRecipes(targetName, files, extractOpts);
1145
+ fallbackUsed = fbName;
1146
+ recovered = true;
1147
+ break;
1148
1148
  } catch { /* next fallback */ }
1149
1149
  }
1150
1150
  }
1151
- } catch { /* AiFactory not available, rethrow original */ }
1152
- throw primaryErr;
1151
+ } catch { /* AiFactory not available */ }
1152
+ if (!recovered) throw primaryErr;
1153
1153
  }
1154
+
1155
+ if (!Array.isArray(recipes)) recipes = [];
1156
+ if (recipes.length === 0) {
1157
+ ctx.logger?.warn?.(`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`);
1158
+ }
1159
+
1160
+ // ── V3 直透:AI 已输出完整 V3 结构,仅做来源标记 + 程序化评分/标签 ──
1161
+ let qualityScorer = null;
1162
+ let recipeExtractor = null;
1163
+ try { qualityScorer = ctx.container?.get?.('qualityScorer'); } catch { /* not available */ }
1164
+ try { recipeExtractor = ctx.container?.get?.('recipeExtractor'); } catch { /* not available */ }
1165
+
1166
+ for (const recipe of recipes) {
1167
+ // 来源 & 生命周期(非 AI 职责)
1168
+ recipe.source = recipe.source || 'ai-scan';
1169
+ recipe.lifecycle = recipe.lifecycle || 'pending';
1170
+
1171
+ // RecipeExtractor 语义标签增强(程序化补充,不替代 AI tags)
1172
+ const codeText = recipe.content?.pattern || '';
1173
+ if (recipeExtractor && codeText) {
1174
+ try {
1175
+ const extracted = recipeExtractor.extractFromContent(
1176
+ codeText, `${recipe.title || 'unknown'}.${recipe.language || 'swift'}`, ''
1177
+ );
1178
+ if (extracted.semanticTags?.length > 0) {
1179
+ recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
1180
+ }
1181
+ if ((!recipe.category || recipe.category === 'Utility') && extracted.category && extracted.category !== 'general') {
1182
+ recipe.category = extracted.category;
1183
+ }
1184
+ } catch { /* best effort */ }
1185
+ }
1186
+
1187
+ // QualityScorer 评分 → quality 结构化
1188
+ if (qualityScorer) {
1189
+ try {
1190
+ const scoreResult = qualityScorer.score(recipe);
1191
+ recipe.quality = {
1192
+ completeness: 0,
1193
+ adaptation: 0,
1194
+ documentation: 0,
1195
+ overall: scoreResult.score ?? 0,
1196
+ grade: scoreResult.grade || '',
1197
+ };
1198
+ } catch { /* best effort */ }
1199
+ }
1200
+ }
1201
+
1202
+ const result = { targetName, extracted: recipes.length, recipes };
1203
+ if (fallbackUsed) result.fallbackUsed = fallbackUsed;
1204
+ return result;
1154
1205
  },
1155
1206
  };
1156
1207
 
@@ -1169,12 +1220,10 @@ const enrichCandidate = {
1169
1220
  },
1170
1221
  handler: async (params, ctx) => {
1171
1222
  if (!ctx.aiProvider) return { error: 'AI provider not available' };
1172
- const candidateService = ctx.container.get('candidateService');
1173
- return candidateService.enrichCandidates(
1174
- params.candidateIds,
1175
- ctx.aiProvider,
1176
- { userId: 'agent' },
1177
- );
1223
+ // V3: 使用 MCP handler enrichCandidates 的逻辑
1224
+ const { enrichCandidates: enrichFn } = await import('../../external/mcp/handlers/candidate.js');
1225
+ const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
1226
+ return result?.data || result;
1178
1227
  },
1179
1228
  };
1180
1229
 
@@ -1194,20 +1243,14 @@ const refineBootstrapCandidates = {
1194
1243
  },
1195
1244
  handler: async (params, ctx) => {
1196
1245
  if (!ctx.aiProvider) return { error: 'AI provider not available' };
1197
- const candidateService = ctx.container.get('candidateService');
1198
-
1199
- // 接入 BootstrapTaskManager 双通道推送 refine:* 事件到前端
1200
- let onProgress = null;
1201
- try {
1202
- const taskManager = ctx.container.get('bootstrapTaskManager');
1203
- onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
1204
- } catch { /* optional: no realtime push */ }
1205
-
1206
- return candidateService.refineBootstrapCandidates(
1207
- ctx.aiProvider,
1208
- { candidateIds: params.candidateIds, userPrompt: params.userPrompt, dryRun: params.dryRun, onProgress },
1209
- { userId: 'agent' },
1210
- );
1246
+ // V3: 委托给 bootstrap handler 的 refine 逻辑
1247
+ const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
1248
+ const result = await bootstrapRefine(ctx, {
1249
+ candidateIds: params.candidateIds,
1250
+ userPrompt: params.userPrompt,
1251
+ dryRun: params.dryRun,
1252
+ });
1253
+ return result?.data || result;
1211
1254
  },
1212
1255
  };
1213
1256
 
@@ -1231,18 +1274,18 @@ const checkDuplicate = {
1231
1274
  const projectRoot = params.projectRoot || ctx.projectRoot;
1232
1275
  const threshold = params.threshold ?? 0.5;
1233
1276
 
1234
- // 如果提供 candidateId,从数据库读取候选信息
1277
+ // 如果提供 candidateId,从数据库读取条目信息
1235
1278
  if (!cand && params.candidateId) {
1236
1279
  try {
1237
- const candidateRepo = ctx.container.get('candidateRepository');
1238
- const found = await candidateRepo.findById(params.candidateId);
1280
+ const knowledgeService = ctx.container.get('knowledgeService');
1281
+ const found = await knowledgeService.get(params.candidateId);
1239
1282
  if (found) {
1240
- const meta = found.metadata || {};
1283
+ const json = typeof found.toJSON === 'function' ? found.toJSON() : found;
1241
1284
  cand = {
1242
- title: meta.title || '',
1243
- summary: meta.summary_cn || meta.summary || '',
1244
- code: found.code || '',
1245
- usageGuide: meta.usageGuide_cn || meta.usageGuide || '',
1285
+ title: json.title || '',
1286
+ summary: json.description || '',
1287
+ code: json.content?.pattern || '',
1288
+ usageGuide: '',
1246
1289
  };
1247
1290
  }
1248
1291
  } catch { /* ignore */ }
@@ -1442,8 +1485,9 @@ const getRecommendations = {
1442
1485
  },
1443
1486
  },
1444
1487
  handler: async (params, ctx) => {
1445
- const recipeService = ctx.container.get('recipeService');
1446
- return recipeService.getRecommendations(params.limit || 10);
1488
+ const knowledgeService = ctx.container.get('knowledgeService');
1489
+ // V3: 推荐 = 活跃条目按使用量排序
1490
+ return knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: params.limit || 10 });
1447
1491
  },
1448
1492
  };
1449
1493
 
@@ -1463,16 +1507,16 @@ const aiTranslate = {
1463
1507
  handler: async (params, ctx) => {
1464
1508
  if (!ctx.aiProvider) return { error: 'AI provider not available' };
1465
1509
  const { summary, usageGuide } = params;
1466
- if (!summary && !usageGuide) return { summary_en: '', usageGuide_en: '' };
1510
+ if (!summary && !usageGuide) return { summaryEn: '', usageGuideEn: '' };
1467
1511
 
1468
- const systemPrompt = 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summary_en": "...", "usageGuide_en": "..." }.';
1512
+ const systemPrompt = 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
1469
1513
  const parts = [];
1470
1514
  if (summary) parts.push(`summary: ${summary}`);
1471
1515
  if (usageGuide) parts.push(`usageGuide: ${usageGuide}`);
1472
1516
 
1473
1517
  const raw = await ctx.aiProvider.chat(parts.join('\n'), { systemPrompt, temperature: 0.2 });
1474
1518
  const parsed = ctx.aiProvider.extractJSON(raw, '{', '}');
1475
- return parsed || { summary_en: summary || '', usageGuide_en: usageGuide || '' };
1519
+ return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
1476
1520
  },
1477
1521
  };
1478
1522
 
@@ -1632,24 +1676,24 @@ const DIMENSION_DISPLAY_GROUP = {
1632
1676
  };
1633
1677
 
1634
1678
  // ────────────────────────────────────────────────────────────
1635
- // Bootstrap 维度类型校验 — submit_candidate / submit_with_check 共用
1679
+ // Bootstrap 维度类型校验 — submit_knowledge / submit_with_check 共用
1636
1680
  // 基于 dimensionMeta 类型标注系统,而非关键词模糊匹配
1637
1681
  // ────────────────────────────────────────────────────────────
1638
1682
 
1639
1683
  /**
1640
1684
  * 基于维度元数据 (dimensionMeta) 检查提交是否合法
1641
1685
  * @param {{ id: string, outputType: 'candidate'|'skill'|'dual', allowedKnowledgeTypes: string[] }} dimensionMeta
1642
- * @param {object} params - submit_candidate 的参数
1686
+ * @param {object} params - submit_knowledge 的参数
1643
1687
  * @param {object} [logger]
1644
1688
  * @returns {{ status: string, reason: string } | null} 不合法返回 rejected,合法返回 null
1645
1689
  */
1646
1690
  function _checkDimensionType(dimensionMeta, params, logger) {
1647
1691
  // 1. Skill-only 维度不允许提交 Candidate
1648
1692
  if (dimensionMeta.outputType === 'skill') {
1649
- logger?.info(`[submit_candidate] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`);
1693
+ logger?.info(`[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`);
1650
1694
  return {
1651
1695
  status: 'rejected',
1652
- reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_candidate。请只在最终回复中提供 dimensionDigest JSON。`,
1696
+ reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_knowledge。请只在最终回复中提供 dimensionDigest JSON。`,
1653
1697
  };
1654
1698
  }
1655
1699
 
@@ -1658,7 +1702,7 @@ function _checkDimensionType(dimensionMeta, params, logger) {
1658
1702
  if (allowed.length > 0 && params.knowledgeType) {
1659
1703
  if (!allowed.includes(params.knowledgeType)) {
1660
1704
  const corrected = allowed[0];
1661
- logger?.warn(`[submit_candidate] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`);
1705
+ logger?.warn(`[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`);
1662
1706
  params.knowledgeType = corrected;
1663
1707
  }
1664
1708
  }
@@ -1666,93 +1710,137 @@ function _checkDimensionType(dimensionMeta, params, logger) {
1666
1710
  return null;
1667
1711
  }
1668
1712
 
1669
- // 16. submit_candidate
1713
+ // 16. submit_knowledge
1670
1714
  // ────────────────────────────────────────────────────────────
1671
1715
  const submitCandidate = {
1672
- name: 'submit_candidate',
1716
+ name: 'submit_knowledge',
1673
1717
  description: '提交新的代码候选项到知识库审核队列。',
1674
1718
  parameters: {
1675
1719
  type: 'object',
1676
1720
  properties: {
1677
- code: { type: 'string', description: '代码内容(项目特写风格 Markdown: 描述和代码交织)' },
1678
- language: { type: 'string', description: '编程语言 (objectivec/swift/java/kotlin 等)' },
1679
- category: { type: 'string', description: '分类 (View/Service/Tool/Model/Network/Storage/UI/Utility/Core)' },
1680
- title: { type: 'string', description: '候选标题,如 "[Bootstrap] best-practice/单例模式"' },
1681
- summary: { type: 'string', description: '≤80字精准摘要,引用真实类名和数字' },
1682
- tags: { type: 'array', items: { type: 'string' }, description: '标签列表,如 ["bootstrap", "singleton"]' },
1683
- knowledgeType: { type: 'string', description: '知识类型: best-practice / code-pattern / architecture / convention' },
1684
- source: { type: 'string', description: '来源 (bootstrap/agent/mcp),默认 agent' },
1685
- reasoning: { type: 'object', description: '推理依据 { whyStandard: string, sources: string[], confidence: number }' },
1686
- metadata: { type: 'object', description: '其他元数据 (不常用,优先使用上面的顶层字段)' },
1721
+ // ── 内容(V3 content 子对象) ──
1722
+ content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
1723
+
1724
+ // ── 基本信息 ──
1725
+ title: { type: 'string', description: '候选标题' },
1726
+ description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
1727
+ tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
1728
+
1729
+ // ── Cursor 交付(AI 必填)──
1730
+ trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
1731
+ kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
1732
+ topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'], description: '主题分类' },
1733
+ whenClause: { type: 'string', description: '触发场景英文' },
1734
+ doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
1735
+ dontClause: { type: 'string', description: '反向约束英文(不以 Don\'t 开头)' },
1736
+
1737
+ // ── 推理 ──
1738
+ reasoning: { type: 'object', description: '{ whyStandard: string, sources: string[], confidence: number }' },
1739
+
1740
+ // ── V3 扩展字段 ──
1741
+ scope: { type: 'string', enum: ['universal', 'project-specific', 'team-convention'], description: '适用范围' },
1742
+ complexity: { type: 'string', enum: ['basic', 'intermediate', 'advanced'], description: '复杂度' },
1743
+ headers: { type: 'array', items: { type: 'string' }, description: '依赖的 import/require 行' },
1744
+ sourceFile: { type: 'string', description: '来源文件相对路径' },
1687
1745
  },
1688
- required: ['code', 'language', 'category', 'title'],
1746
+ required: ['content', 'title', 'trigger', 'kind', 'doClause'],
1689
1747
  },
1690
1748
  handler: async (params, ctx) => {
1691
- const candidateService = ctx.container.get('candidateService');
1749
+ const knowledgeService = ctx.container.get('knowledgeService');
1692
1750
 
1693
- // ── Bootstrap 维度类型校验 (基于 dimensionMeta 类型标注) ──
1751
+ // ── Bootstrap 维度类型校验 ──
1694
1752
  const dimMeta = ctx._dimensionMeta;
1695
1753
  if (dimMeta && ctx.source === 'system') {
1696
1754
  const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
1697
1755
  if (rejected) return rejected;
1698
1756
 
1699
- // 自动注入维度标签(确保可溯源)
1757
+ // 自动注入维度标签
1700
1758
  if (!params.tags) params.tags = [];
1701
1759
  if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
1702
1760
  if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
1703
1761
 
1704
- // 自动补充 knowledgeType(AI 未填时用维度默认值)
1705
- if (!params.knowledgeType && dimMeta.allowedKnowledgeTypes?.length > 0) {
1706
- params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
1707
- }
1708
-
1709
- // Bootstrap 模式: 将 category 覆盖为展示分组 ID,确保前端按合并后的分组展示
1710
- // AI 可能填 "UI"/"Core" 等功能分类,但前端通过 BOOTSTRAP_DIM_LABELS 按展示分组分组
1711
- params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
1762
+ // Bootstrap 模式: 将 category 覆盖为展示分组 ID
1763
+ params._category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
1712
1764
 
1713
- // ── CandidateGuardrail 质量验证 (Bootstrap 模式) ──
1765
+ // ── CandidateGuardrail 质量验证 ──
1714
1766
  const guardrail = new CandidateGuardrail(
1715
1767
  ctx._submittedTitles || new Set(),
1716
1768
  dimMeta,
1717
1769
  );
1718
1770
  const guardResult = guardrail.validate(params);
1719
1771
  if (!guardResult.valid) {
1720
- ctx.logger?.info(`[submit_candidate] ✗ guardrail rejected: ${guardResult.error}`);
1772
+ ctx.logger?.info(`[submit_knowledge] ✗ guardrail rejected: ${guardResult.error}`);
1721
1773
  return {
1722
1774
  status: 'rejected',
1723
1775
  error: guardResult.error,
1724
- hint: '请根据错误信息调整内容后重新提交。候选的 code 字段必须是「项目特写」风格,包含代码片段和项目上下文。',
1776
+ hint: '请根据错误信息调整内容后重新提交。',
1725
1777
  };
1726
1778
  }
1727
1779
  }
1728
1780
 
1729
- // 将所有顶层字段展开到 item — LLM 可能把 title/summary/tags 等
1730
- // 放在顶层而非 metadata 中(production prompt 指引)
1731
- const { code, language, category, source, reasoning, metadata, ...rest } = params;
1781
+ // ── 系统自动设置 ──
1782
+ const systemFields = {
1783
+ language: ctx._projectLanguage || '',
1784
+ category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
1785
+ knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
1786
+ source: ctx.source === 'system' ? 'bootstrap' : 'agent',
1787
+ };
1732
1788
 
1733
- // 防御性修复: AI 可能提交 reasoning.sources = [] 空数组,自动从 filePaths 补充
1734
- const finalReasoning = reasoning || { whyStandard: 'Submitted via ChatAgent', sources: ['agent'], confidence: 0.7 };
1735
- if (Array.isArray(finalReasoning.sources) && finalReasoning.sources.length === 0) {
1736
- const fallbackSources = params.filePaths || rest.filePaths;
1737
- if (Array.isArray(fallbackSources) && fallbackSources.length > 0) {
1738
- finalReasoning.sources = fallbackSources;
1739
- } else {
1740
- finalReasoning.sources = ['agent'];
1741
- }
1789
+ // ── 直传 KnowledgeEntry ──
1790
+ const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
1791
+ if (Array.isArray(reasoning.sources) && reasoning.sources.length === 0) {
1792
+ reasoning.sources = ['agent'];
1793
+ }
1794
+
1795
+ // V3 content 直透
1796
+ const contentObj = params.content && typeof params.content === 'object'
1797
+ ? params.content
1798
+ : { markdown: '', pattern: '' };
1799
+
1800
+ const data = {
1801
+ ...systemFields,
1802
+ title: params.title || '',
1803
+ description: params.description || '',
1804
+ tags: params.tags || [],
1805
+ trigger: params.trigger || '',
1806
+ kind: params.kind || 'pattern',
1807
+ topicHint: params.topicHint || '',
1808
+ whenClause: params.whenClause || '',
1809
+ doClause: params.doClause || '',
1810
+ dontClause: params.dontClause || '',
1811
+ coreCode: contentObj.pattern || '',
1812
+ content: contentObj,
1813
+ reasoning,
1814
+ // V3 扩展字段直透
1815
+ scope: params.scope || '',
1816
+ complexity: params.complexity || '',
1817
+ headers: params.headers || [],
1818
+ // sourceFile: 优先取 params,Bootstrap 回退从 reasoning.sources 推断
1819
+ sourceFile: params.sourceFile
1820
+ || (Array.isArray(reasoning.sources) && reasoning.sources.length > 0
1821
+ && reasoning.sources[0] !== 'agent'
1822
+ ? reasoning.sources[0]
1823
+ : ''),
1824
+ // 7.3.9 agentNotes/aiInsight 注入
1825
+ agentNotes: dimMeta
1826
+ ? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
1827
+ : null,
1828
+ aiInsight: reasoning.whyStandard || params.description || null,
1829
+ };
1830
+
1831
+ if (dimMeta && ctx.source === 'system') {
1832
+ const displayGroup = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
1833
+ data.tags = [...new Set([...(data.tags || []), displayGroup])];
1742
1834
  }
1743
1835
 
1744
- const item = {
1745
- code,
1746
- language,
1747
- category,
1748
- ...rest, // 顶层扩展字段 (title, summary, knowledgeType, tags )
1749
- ...metadata, // metadata 对象 (如有)
1750
- reasoning: finalReasoning,
1751
- };
1752
- // Bootstrap 模式额外注入 targetName — 前端优先按 meta.targetName 分组
1753
- const displayGroup = dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : null;
1754
- const extraMeta = (dimMeta && ctx.source === 'system') ? { targetName: displayGroup } : {};
1755
- return candidateService.createFromToolParams(item, source || 'agent', extraMeta, { userId: 'agent' });
1836
+ const saved = await knowledgeService.create(data, { userId: 'agent' });
1837
+
1838
+ // ── QualityScorer 自动评分 ──
1839
+ try {
1840
+ await knowledgeService.updateQuality(saved.id, { userId: 'agent' });
1841
+ } catch { /* best effort — 不阻塞创建流程 */ }
1842
+
1843
+ return saved;
1756
1844
  },
1757
1845
  };
1758
1846
 
@@ -1770,8 +1858,8 @@ const approveCandidate = {
1770
1858
  required: ['candidateId'],
1771
1859
  },
1772
1860
  handler: async (params, ctx) => {
1773
- const candidateService = ctx.container.get('candidateService');
1774
- return candidateService.approveCandidate(params.candidateId, { userId: 'agent' });
1861
+ const knowledgeService = ctx.container.get('knowledgeService');
1862
+ return knowledgeService.approve(params.candidateId, { userId: 'agent' });
1775
1863
  },
1776
1864
  };
1777
1865
 
@@ -1790,8 +1878,8 @@ const rejectCandidate = {
1790
1878
  required: ['candidateId', 'reason'],
1791
1879
  },
1792
1880
  handler: async (params, ctx) => {
1793
- const candidateService = ctx.container.get('candidateService');
1794
- return candidateService.rejectCandidate(params.candidateId, params.reason, { userId: 'agent' });
1881
+ const knowledgeService = ctx.container.get('knowledgeService');
1882
+ return knowledgeService.reject(params.candidateId, params.reason, { userId: 'agent' });
1795
1883
  },
1796
1884
  };
1797
1885
 
@@ -1809,8 +1897,8 @@ const publishRecipe = {
1809
1897
  required: ['recipeId'],
1810
1898
  },
1811
1899
  handler: async (params, ctx) => {
1812
- const recipeService = ctx.container.get('recipeService');
1813
- return recipeService.publishRecipe(params.recipeId, { userId: 'agent' });
1900
+ const knowledgeService = ctx.container.get('knowledgeService');
1901
+ return knowledgeService.publish(params.recipeId, { userId: 'agent' });
1814
1902
  },
1815
1903
  };
1816
1904
 
@@ -1829,8 +1917,8 @@ const deprecateRecipe = {
1829
1917
  required: ['recipeId', 'reason'],
1830
1918
  },
1831
1919
  handler: async (params, ctx) => {
1832
- const recipeService = ctx.container.get('recipeService');
1833
- return recipeService.deprecateRecipe(params.recipeId, params.reason, { userId: 'agent' });
1920
+ const knowledgeService = ctx.container.get('knowledgeService');
1921
+ return knowledgeService.deprecate(params.recipeId, params.reason, { userId: 'agent' });
1834
1922
  },
1835
1923
  };
1836
1924
 
@@ -1849,8 +1937,8 @@ const updateRecipe = {
1849
1937
  required: ['recipeId', 'updates'],
1850
1938
  },
1851
1939
  handler: async (params, ctx) => {
1852
- const recipeService = ctx.container.get('recipeService');
1853
- return recipeService.updateRecipe(params.recipeId, params.updates, { userId: 'agent' });
1940
+ const knowledgeService = ctx.container.get('knowledgeService');
1941
+ return knowledgeService.update(params.recipeId, params.updates, { userId: 'agent' });
1854
1942
  },
1855
1943
  };
1856
1944
 
@@ -1869,9 +1957,9 @@ const recordUsage = {
1869
1957
  required: ['recipeId'],
1870
1958
  },
1871
1959
  handler: async (params, ctx) => {
1872
- const recipeService = ctx.container.get('recipeService');
1960
+ const knowledgeService = ctx.container.get('knowledgeService');
1873
1961
  const type = params.type || 'adoption';
1874
- await recipeService.incrementUsage(params.recipeId, type);
1962
+ await knowledgeService.incrementUsage(params.recipeId, type);
1875
1963
  return { success: true, recipeId: params.recipeId, type };
1876
1964
  },
1877
1965
  };
@@ -1894,9 +1982,13 @@ const qualityScore = {
1894
1982
  let recipe = params.recipe;
1895
1983
 
1896
1984
  if (!recipe && params.recipeId) {
1897
- const recipeRepo = ctx.container.get('recipeRepository');
1898
- recipe = await recipeRepo.findById(params.recipeId);
1899
- if (!recipe) return { error: `Recipe '${params.recipeId}' not found` };
1985
+ const knowledgeService = ctx.container.get('knowledgeService');
1986
+ try {
1987
+ const entry = await knowledgeService.get(params.recipeId);
1988
+ recipe = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
1989
+ } catch {
1990
+ return { error: `Knowledge entry '${params.recipeId}' not found` };
1991
+ }
1900
1992
  }
1901
1993
  if (!recipe) return { error: 'Provide recipeId or recipe object' };
1902
1994
 
@@ -2210,13 +2302,8 @@ const knowledgeOverview = {
2210
2302
  const [statsResult, feedbackResult] = await Promise.all([
2211
2303
  (async () => {
2212
2304
  try {
2213
- const recipeService = ctx.container.get('recipeService');
2214
- const candidateService = ctx.container.get('candidateService');
2215
- const [rs, cs] = await Promise.all([
2216
- recipeService.getRecipeStats(),
2217
- candidateService.getCandidateStats(),
2218
- ]);
2219
- return { recipes: rs, candidates: cs };
2305
+ const knowledgeService = ctx.container.get('knowledgeService');
2306
+ return knowledgeService.getStats();
2220
2307
  } catch { return null; }
2221
2308
  })(),
2222
2309
  (async () => {
@@ -2229,8 +2316,7 @@ const knowledgeOverview = {
2229
2316
  ]);
2230
2317
 
2231
2318
  if (statsResult) {
2232
- result.recipes = statsResult.recipes;
2233
- result.candidates = statsResult.candidates;
2319
+ result.knowledge = statsResult;
2234
2320
  }
2235
2321
 
2236
2322
  // 知识图谱统计
@@ -2256,45 +2342,45 @@ const knowledgeOverview = {
2256
2342
  // ────────────────────────────────────────────────────────────
2257
2343
  const submitWithCheck = {
2258
2344
  name: 'submit_with_check',
2259
- description: '安全提交候选:先执行查重检测,无重复则自动提交。如果发现高度相似 Recipe 则阻止并返回相似列表。一次调用完成 check_duplicate + submit_candidate。',
2345
+ description: '安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
2260
2346
  parameters: {
2261
2347
  type: 'object',
2262
2348
  properties: {
2263
- code: { type: 'string', description: '代码内容' },
2264
- language: { type: 'string', description: '编程语言' },
2265
- category: { type: 'string', description: '分类 (View/Service/Tool/Model 等)' },
2266
- title: { type: 'string', description: '候选标题' },
2267
- summary: { type: 'string', description: '摘要' },
2268
- threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
2349
+ content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
2350
+ title: { type: 'string', description: '候选标题' },
2351
+ description:{ type: 'string', description: '中文简述 ≤80 ' },
2352
+ trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2353
+ kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
2354
+ topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'] },
2355
+ whenClause: { type: 'string', description: '触发场景英文' },
2356
+ doClause: { type: 'string', description: '正向指令英文' },
2357
+ dontClause: { type: 'string', description: '反向约束英文' },
2358
+ tags: { type: 'array', items: { type: 'string' } },
2359
+ reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
2360
+ threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
2269
2361
  },
2270
- required: ['code', 'language', 'category'],
2362
+ required: ['content', 'title', 'trigger', 'kind', 'doClause'],
2271
2363
  },
2272
2364
  handler: async (params, ctx) => {
2273
- const { code, language, category, title, summary, threshold = 0.7 } = params;
2274
2365
  const projectRoot = ctx.projectRoot;
2275
2366
 
2276
- // ── Bootstrap 维度类型校验 (与 submit_candidate 共用逻辑) ──
2367
+ // ── Bootstrap 维度类型校验 ──
2277
2368
  const dimMeta = ctx._dimensionMeta;
2278
2369
  if (dimMeta && ctx.source === 'system') {
2279
2370
  const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
2280
2371
  if (rejected) return rejected;
2281
2372
 
2282
- // 自动注入维度标签
2283
2373
  if (!params.tags) params.tags = [];
2284
2374
  if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
2285
2375
  if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
2286
-
2287
- // 自动补充 knowledgeType(AI 未填时用维度默认值)
2288
- if (!params.knowledgeType && dimMeta.allowedKnowledgeTypes?.length > 0) {
2289
- params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
2290
- }
2291
-
2292
- // 覆盖 category 为展示分组
2293
- params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
2294
2376
  }
2295
2377
 
2296
2378
  // Step 1: 查重
2297
- const cand = { title: title || '', summary: summary || '', code };
2379
+ const threshold = params.threshold || 0.7;
2380
+ const contentObj2 = params.content && typeof params.content === 'object'
2381
+ ? params.content
2382
+ : { markdown: '', pattern: '' };
2383
+ const cand = { title: params.title || '', summary: params.description || '', code: contentObj2.markdown || contentObj2.pattern || '' };
2298
2384
  const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
2299
2385
  const hasDuplicate = similar.some(s => s.similarity >= threshold);
2300
2386
 
@@ -2306,38 +2392,49 @@ const submitWithCheck = {
2306
2392
  highestSimilarity: similar[0]?.similarity || 0,
2307
2393
  _meta: {
2308
2394
  confidence: 'high',
2309
- hint: `发现高度相似 Recipe(相似度 ${(similar[0]?.similarity * 100).toFixed(0)}%),已阻止提交。请人工审核。`,
2395
+ hint: `发现高度相似 Recipe(相似度 ${(similar[0]?.similarity * 100).toFixed(0)}%),已阻止提交。`,
2310
2396
  },
2311
2397
  };
2312
2398
  }
2313
2399
 
2314
- // Step 2: 提交
2400
+ // Step 2: 提交 — 委托给 submit_knowledge handler
2315
2401
  try {
2316
- const candidateService = ctx.container.get('candidateService');
2317
- const { code: _c, language: _l, category: _cat, title: _t, summary: _s, threshold: _th, source: paramSource, reasoning: userReasoning, metadata, ...rest } = params;
2318
- const item = {
2319
- code,
2320
- language,
2321
- category,
2322
- ...rest, // 顶层扩展字段 (title, summary, knowledgeType, tags 等)
2323
- ...metadata, // metadata 对象 (如有)
2324
- title: title || '',
2325
- summary: summary || '',
2326
- reasoning: userReasoning || { whyStandard: 'Submitted via submit_with_check', sources: ['agent'], confidence: 0.7 },
2402
+ const knowledgeService = ctx.container.get('knowledgeService');
2403
+ const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
2404
+
2405
+ const systemFields = {
2406
+ language: ctx._projectLanguage || '',
2407
+ category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
2408
+ knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
2409
+ source: ctx.source === 'system' ? 'bootstrap' : 'agent',
2410
+ };
2411
+
2412
+ const data = {
2413
+ ...systemFields,
2414
+ title: params.title || '',
2415
+ description: params.description || '',
2416
+ tags: params.tags || [],
2417
+ trigger: params.trigger || '',
2418
+ kind: params.kind || 'pattern',
2419
+ topicHint: params.topicHint || '',
2420
+ whenClause: params.whenClause || '',
2421
+ doClause: params.doClause || '',
2422
+ dontClause: params.dontClause || '',
2423
+ coreCode: contentObj2.pattern || '',
2424
+ content: contentObj2,
2425
+ reasoning,
2327
2426
  };
2328
- // Bootstrap 模式注入展示分组 targetName
2329
- const swcDisplayGroup = dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : null;
2330
- const swcExtraMeta = (dimMeta && ctx.source === 'system') ? { targetName: swcDisplayGroup } : {};
2331
- const created = await candidateService.createFromToolParams(item, paramSource || 'agent', swcExtraMeta, { userId: 'agent' });
2427
+
2428
+ const created = await knowledgeService.create(data, { userId: 'agent' });
2332
2429
 
2333
2430
  return {
2334
2431
  submitted: true,
2335
- candidate: created,
2432
+ entry: typeof created.toJSON === 'function' ? created.toJSON() : created,
2336
2433
  similar: similar.length > 0 ? similar : [],
2337
2434
  _meta: {
2338
2435
  confidence: 'high',
2339
2436
  hint: similar.length > 0
2340
- ? `已提交,但有 ${similar.length} 个低相似度匹配,大概率不是重复。`
2437
+ ? `已提交,但有 ${similar.length} 个低相似度匹配。`
2341
2438
  : '已提交,无重复风险。',
2342
2439
  },
2343
2440
  };
@@ -2444,7 +2541,7 @@ const planTask = {
2444
2541
  // ─── 元工具: 自我质量审查 ───────────────────────────────
2445
2542
  const reviewMyOutput = {
2446
2543
  name: 'review_my_output',
2447
- description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、summary 泛化措辞、代码示例来源标注等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
2544
+ description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
2448
2545
  parameters: {
2449
2546
  type: 'object',
2450
2547
  properties: {
@@ -2457,7 +2554,7 @@ const reviewMyOutput = {
2457
2554
  },
2458
2555
  handler: async (params, context) => {
2459
2556
  const submitted = (context._sessionToolCalls || []).filter(
2460
- tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check',
2557
+ tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check',
2461
2558
  );
2462
2559
 
2463
2560
  if (submitted.length === 0) {
@@ -2469,48 +2566,54 @@ const reviewMyOutput = {
2469
2566
 
2470
2567
  for (const tc of submitted) {
2471
2568
  const p = tc.params || {};
2472
- const code = p.code || '';
2569
+ const contentObj3 = p.content && typeof p.content === 'object' ? p.content : {};
2570
+ const markdown = contentObj3.markdown || '';
2473
2571
  const title = p.title || '';
2474
- const summary = p.summary || '';
2572
+ const description = p.description || '';
2475
2573
  const candidateIssues = [];
2476
2574
 
2477
2575
  // 检查 1: 项目特写后缀
2478
- if (!title.includes('— 项目特写') && !code.includes('— 项目特写')) {
2576
+ if (!title.includes('— 项目特写') && !markdown.includes('— 项目特写')) {
2479
2577
  candidateIssues.push('缺少 "— 项目特写" 后缀');
2480
2578
  }
2481
2579
 
2482
2580
  // 检查 2: 项目特写融合叙事质量 — 必须同时包含代码和描述性文字
2483
- const hasCodeBlock = /```[\s\S]*?```/.test(code);
2581
+ const hasCodeBlock = /```[\s\S]*?```/.test(markdown);
2484
2582
  if (!hasCodeBlock) {
2485
2583
  candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
2486
2584
  }
2487
2585
  // 去掉代码块后,剩余描述性文字应足够
2488
- const proseLength = code.replace(/```[\s\S]*?```/g, '').replace(/[#>\-*`\n]/g, '').trim().length;
2586
+ const proseLength = markdown.replace(/```[\s\S]*?```/g, '').replace(/[#>\-*`\n]/g, '').trim().length;
2489
2587
  if (proseLength < 50) {
2490
2588
  candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
2491
2589
  }
2492
2590
 
2493
- // 检查 3: summary 泛化措辞
2494
- if (/本模块|该文件|这个类|该项目/.test(summary)) {
2495
- candidateIssues.push('summary 使用了泛化措辞,应引用具体类名和数字');
2591
+ // 检查 3: description 泛化措辞
2592
+ if (/本模块|该文件|这个类|该项目/.test(description)) {
2593
+ candidateIssues.push('description 使用了泛化措辞,应引用具体类名和数字');
2496
2594
  }
2497
2595
 
2498
- // 检查 4: summary 过短
2499
- if (summary.length < 15) {
2500
- candidateIssues.push(`summary 过短 (${summary.length} 字), 应≥15字并包含具体类名和数字`);
2596
+ // 检查 4: description 过短
2597
+ if (description.length < 15) {
2598
+ candidateIssues.push(`description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`);
2501
2599
  }
2502
2600
 
2503
- // 检查 5: code 过短(可能是空壳)
2504
- if (code.length < 200) {
2505
- candidateIssues.push(`code 文档过短 (${code.length} 字), 可能缺少实质内容`);
2601
+ // 检查 5: content.markdown 过短(可能是空壳)
2602
+ if (markdown.length < 200) {
2603
+ candidateIssues.push(`content.markdown 文档过短 (${markdown.length} 字), 可能缺少实质内容`);
2506
2604
  }
2507
2605
 
2508
2606
  // 检查 6: 代码示例来源
2509
- const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(code);
2607
+ const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(markdown);
2510
2608
  if (hasCodeBlock && !hasSourceAnnotation) {
2511
2609
  candidateIssues.push('代码示例可能缺少来源文件标注 (建议标注 "来源: FileName.m:行号")');
2512
2610
  }
2513
2611
 
2612
+ // 检查 7: Cursor 交付字段
2613
+ if (!p.trigger) candidateIssues.push('缺少 trigger 字段');
2614
+ if (!p.doClause) candidateIssues.push('缺少 doClause 字段');
2615
+ if (!p.kind) candidateIssues.push('缺少 kind 字段');
2616
+
2514
2617
  if (candidateIssues.length > 0) {
2515
2618
  issues.push({ title, issues: candidateIssues });
2516
2619
  }