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
@@ -36,7 +36,7 @@ const PROJECT_ROOT = path.resolve(__dirname, '../../..');
36
36
  const SKILLS_DIR = path.resolve(PROJECT_ROOT, 'skills');
37
37
  const SOUL_PATH = path.resolve(PROJECT_ROOT, 'SOUL.md');
38
38
  const MAX_ITERATIONS = 6;
39
- /** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_candidate */
39
+ /** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_knowledge */
40
40
  const MAX_ITERATIONS_SYSTEM = 30;
41
41
  /** 原生函数调用模式下,已提交 ≥ MIN_SUBMITS_FOR_EARLY_EXIT 个候选后,连续 N 轮无新提交则提前退出 */
42
42
  const MIN_SUBMITS_FOR_EARLY_EXIT = 1;
@@ -69,23 +69,23 @@ const SYSTEM_CONTINUATION_PROMPT = `你的分析计划很好。但你需要 **
69
69
  请现在开始执行:
70
70
  1. 用 \`search_project_code\` 搜索项目代码获取真实示例
71
71
  2. 用 \`read_project_file\` 查看完整文件内容
72
- 3. 对每个值得保留的信号,用 \`submit_candidate\` 提交候选
72
+ 3. 对每个值得保留的信号,用 \`submit_knowledge\` 提交候选
73
73
 
74
74
  ⚡ 推荐使用 batch_actions 一次提交多条候选:
75
75
  \`\`\`batch_actions
76
76
  [
77
- {"tool": "submit_candidate", "params": {"title": "[Bootstrap] xxx/子主题", "code": "# 标题 — 项目特写\\n\\n> 摘要...\\n\\n描述和代码交织...", "language": "objectivec", "category": "Service", "summary": "...", "tags": ["bootstrap"], "source": "bootstrap", "reasoning": {"whyStandard": "...", "sources": ["file1"], "confidence": 0.7}}},
78
- {"tool": "submit_candidate", "params": {"title": "...", "code": "...", ...}}
77
+ {"tool": "submit_knowledge", "params": {"title": "[Bootstrap] xxx/子主题", "code": "# 标题 — 项目特写\\n\\n> 摘要...\\n\\n描述和代码交织...", "language": "objectivec", "category": "Service", "summary": "...", "tags": ["bootstrap"], "source": "bootstrap", "reasoning": {"whyStandard": "...", "sources": ["file1"], "confidence": 0.7}}},
78
+ {"tool": "submit_knowledge", "params": {"title": "...", "code": "...", ...}}
79
79
  ]
80
80
  \`\`\`
81
81
 
82
82
  请立即开始执行,不要再输出分析文字。`;
83
83
 
84
84
  /**
85
- * 系统调用提交提示 — 当 AI 做了工具调用(search/read)、写了分析文本,但没调 submit_candidate 时注入
86
- * 引导 AI 将已有分析转化为实际的 submit_candidate 调用
85
+ * 系统调用提交提示 — 当 AI 做了工具调用(search/read)、写了分析文本,但没调 submit_knowledge 时注入
86
+ * 引导 AI 将已有分析转化为实际的 submit_knowledge 调用
87
87
  */
88
- const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项目信息。但你还没有调用 \`submit_candidate\` 提交任何候选。
88
+ const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项目信息。但你还没有调用 \`submit_knowledge\` 提交任何候选。
89
89
 
90
90
  **你的分析不能只停留在文字描述层面** — 必须通过工具调用将分析结果持久化。
91
91
 
@@ -93,7 +93,7 @@ const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项
93
93
 
94
94
  \`\`\`batch_actions
95
95
  [
96
- {"tool": "submit_candidate", "params": {
96
+ {"tool": "submit_knowledge", "params": {
97
97
  "title": "[Bootstrap] 维度/子主题",
98
98
  "code": "# 标题 — 项目特写\\n\\n> 本项目使用 XX 模式, N 个文件采用此写法\\n\\n描述...\\n\\n\`\`\`objc\\n// 真实代码示例\\n\`\`\`\\n\\n要点说明...",
99
99
  "language": "objectivec",
@@ -103,11 +103,11 @@ const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项
103
103
  "source": "bootstrap",
104
104
  "reasoning": {"whyStandard": "为什么值得保留", "sources": ["真实文件名"], "confidence": 0.7}
105
105
  }},
106
- {"tool": "submit_candidate", "params": {...}}
106
+ {"tool": "submit_knowledge", "params": {...}}
107
107
  ]
108
108
  \`\`\`
109
109
 
110
- 将你上面分析出的每个有价值的发现都转化为一条 submit_candidate 调用。code 字段写「项目特写」风格: 描述和代码交织,用项目真实类名和代码。`;
110
+ 将你上面分析出的每个有价值的发现都转化为一条 submit_knowledge 调用。code 字段写「项目特写」风格: 描述和代码交织,用项目真实类名和代码。`;
111
111
 
112
112
  export class ChatAgent {
113
113
  #toolRegistry;
@@ -342,7 +342,7 @@ export class ChatAgent {
342
342
  // Bootstrap 场景限制可用工具集 (支持外部覆盖)
343
343
  const effectiveAllowedTools = allowedTools || (isSystem ? [
344
344
  'search_project_code', 'read_project_file',
345
- 'submit_candidate', 'submit_with_check',
345
+ 'submit_knowledge', 'submit_with_check',
346
346
  'list_project_structure', 'get_file_summary', 'semantic_search_code',
347
347
  // AST 结构化分析工具
348
348
  'get_project_overview', 'get_class_hierarchy', 'get_class_info',
@@ -384,7 +384,7 @@ export class ChatAgent {
384
384
  }
385
385
  // PhaseRouter 因 maxIterations 强制转入 SUMMARIZE → 注入收尾 nudge
386
386
  if (phaseRouter.consumeForcedSummarize()) {
387
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check').length;
387
+ const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
388
388
  ctx.appendUserNudge(
389
389
  `⚠️ 轮次即将耗尽 (${phaseRouter.totalIterations}/${budget.maxIterations}),**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
390
390
  `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
@@ -418,7 +418,7 @@ export class ChatAgent {
418
418
  } else if (isSystem && iterationCount >= maxIter && !ctx.__gracefulExitInjected) {
419
419
  // 达到上限 → 注入收尾消息让 AI 快速总结(而非硬中断)
420
420
  this.#logger.info(`[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) — injecting graceful exit nudge`);
421
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check').length;
421
+ const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
422
422
  ctx.appendUserNudge(
423
423
  `⚠️ 你已使用 ${iterationCount}/${maxIter} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
424
424
  `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
@@ -671,7 +671,7 @@ export class ChatAgent {
671
671
  explorationMetrics.uniqueQueries.add(qKey);
672
672
  foundNewInfo = true;
673
673
  }
674
- } else if (fc.name !== 'submit_candidate' && fc.name !== 'submit_with_check') {
674
+ } else if (fc.name !== 'submit_knowledge' && fc.name !== 'submit_with_check') {
675
675
  // 其他未分类工具 — 首次算新信息,之后同工具名+同参数去重
676
676
  const qKey = `${fc.name}:${JSON.stringify(fc.args || {}).substring(0, 80)}`;
677
677
  if (!explorationMetrics.uniqueQueries.has(qKey)) {
@@ -692,7 +692,7 @@ export class ChatAgent {
692
692
  let resultStr = limitToolResult(fc.name, toolResult, quota);
693
693
 
694
694
  // ── 重复提交 / 维度范围校验 ──
695
- if (fc.name === 'submit_candidate' || fc.name === 'submit_with_check') {
695
+ if (fc.name === 'submit_knowledge' || fc.name === 'submit_with_check') {
696
696
  const title = fc.args?.title || fc.args?.category || '';
697
697
  const isRejected = typeof toolResult === 'object' && toolResult?.status === 'rejected';
698
698
  const isError = typeof toolResult === 'object' && (toolResult?.error || toolResult?.status === 'error');
@@ -734,7 +734,7 @@ export class ChatAgent {
734
734
  // 需要一条 user 消息明确告知 AI 切换到提交模式
735
735
  if (transition.transitioned && transition.newPhase === 'PRODUCE') {
736
736
  ctx.appendUserNudge(
737
- '你已充分探索了项目代码,现在请开始调用 submit_candidate 工具来提交你发现的知识候选。不要再搜索,直接提交。'
737
+ '你已充分探索了项目代码,现在请开始调用 submit_knowledge 工具来提交你发现的知识候选。不要再搜索,直接提交。'
738
738
  );
739
739
  this.#logger.info('[ChatAgent] 📝 injected PRODUCE transition nudge');
740
740
  }
@@ -743,7 +743,7 @@ export class ChatAgent {
743
743
  // skill-only 维度从 EXPLORE 直接进入 SUMMARIZE (跳过 PRODUCE),
744
744
  // 需要明确告知 AI 输出 dimensionDigest JSON
745
745
  if (transition.transitioned && transition.newPhase === 'SUMMARIZE') {
746
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check').length;
746
+ const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
747
747
  ctx.appendUserNudge(
748
748
  `你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。`
749
749
  );
@@ -781,7 +781,7 @@ export class ChatAgent {
781
781
  // 注入 nudge 让 AI 再输出一次 digest JSON
782
782
  if (transition.transitioned) {
783
783
  ctx.appendAssistantText(aiResult.text || '');
784
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check').length;
784
+ const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
785
785
  ctx.appendUserNudge(
786
786
  `请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理的信号","reason":"原因","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 记录未来得及处理的信号。已全部覆盖则留空 \`[]\`。`
787
787
  );
@@ -802,7 +802,7 @@ export class ChatAgent {
802
802
  ctx.appendAssistantText(aiResult.text || '');
803
803
  if (phaseRouter.phase === 'PRODUCE') {
804
804
  ctx.appendUserNudge(
805
- '你的分析很好。请继续调用 submit_candidate 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
805
+ '你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
806
806
  );
807
807
  this.#logger.info('[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)');
808
808
  }
@@ -850,7 +850,7 @@ export class ChatAgent {
850
850
  this.#logger.info(`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls)`);
851
851
 
852
852
  const candidateCount = toolCalls.filter(tc =>
853
- tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check'
853
+ tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
854
854
  ).length;
855
855
 
856
856
  let finalReply;
@@ -865,7 +865,7 @@ export class ChatAgent {
865
865
  if (isCircuitOpen) throw new Error('circuit open — skip to synthetic digest');
866
866
 
867
867
  const submitSummary = toolCalls
868
- .filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check')
868
+ .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
869
869
  .map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
870
870
  .join('\n');
871
871
 
@@ -912,7 +912,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
912
912
  this.#logger.warn(`[ChatAgent] forced summary AI call failed: ${err.message}`);
913
913
  // 合成 digest 兜底
914
914
  const titles = toolCalls
915
- .filter(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check')
915
+ .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
916
916
  .map(tc => tc.params?.title || 'untitled');
917
917
  finalReply = `\`\`\`json
918
918
  {
@@ -997,7 +997,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
997
997
 
998
998
  if (!actions) {
999
999
  // ── 系统调用自动续跑 ──
1000
- const hasSubmits = toolCalls.some(tc => tc.tool === 'submit_candidate' || tc.tool === 'submit_with_check');
1000
+ const hasSubmits = toolCalls.some(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check');
1001
1001
  if (source === 'system' && iterations < maxIter && !hasSubmits) {
1002
1002
  if (this.#looksLikeIncompleteStep(response)) {
1003
1003
  this.#logger.info(`[ChatAgent] 🔄 detected planning-only response at iteration ${iterations}, injecting continuation prompt`);
@@ -1272,13 +1272,13 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1272
1272
  */
1273
1273
  async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
1274
1274
  const ctx = this.#getToolContext();
1275
- const recipeService = ctx.container.get('recipeService');
1276
- if (!recipeService) throw new Error('RecipeService 不可用');
1275
+ const knowledgeService = ctx.container.get('knowledgeService');
1276
+ if (!knowledgeService) throw new Error('KnowledgeService 不可用');
1277
1277
 
1278
1278
  if (!ctx.aiProvider) throw new Error('AI Provider 未配置,请先设置 API Key');
1279
1279
 
1280
- // 获取所有 recipe
1281
- const { items = [], data = [] } = await recipeService.listRecipes({}, { page: 1, pageSize: 500 });
1280
+ // 获取所有活跃知识条目
1281
+ const { items = [], data = [] } = await knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: 500 });
1282
1282
  const recipes = items.length > 0 ? items : data;
1283
1283
  if (recipes.length < 2) return { discovered: 0, totalPairs: 0, message: `只有 ${recipes.length} 条 Recipe,至少需要 2 条` };
1284
1284
 
@@ -1334,10 +1334,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1334
1334
  */
1335
1335
  async #taskFullEnrich({ status = 'pending', maxCount = 50 } = {}) {
1336
1336
  const ctx = this.#getToolContext();
1337
- const candidateService = ctx.container.get('candidateService');
1337
+ const knowledgeService = ctx.container.get('knowledgeService');
1338
1338
 
1339
- const { items = [], data = [] } = await candidateService.listCandidates(
1340
- { status }, { page: 1, pageSize: maxCount }
1339
+ const { items = [], data = [] } = await knowledgeService.list(
1340
+ { lifecycle: status }, { page: 1, pageSize: maxCount }
1341
1341
  );
1342
1342
  const candidates = items.length > 0 ? items : data;
1343
1343
  if (candidates.length === 0) return { enriched: 0, message: 'No candidates to enrich' };
@@ -1363,10 +1363,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1363
1363
  */
1364
1364
  async #taskQualityAudit({ threshold = 0.6, maxCount = 100 } = {}) {
1365
1365
  const ctx = this.#getToolContext();
1366
- const recipeService = ctx.container.get('recipeService');
1366
+ const knowledgeService = ctx.container.get('knowledgeService');
1367
1367
 
1368
- const { items = [], data = [] } = await recipeService.listRecipes(
1369
- { status: 'active' }, { page: 1, pageSize: maxCount }
1368
+ const { items = [], data = [] } = await knowledgeService.list(
1369
+ { lifecycle: 'active' }, { page: 1, pageSize: maxCount }
1370
1370
  );
1371
1371
  const recipes = items.length > 0 ? items : data;
1372
1372
  if (recipes.length === 0) return { total: 0, lowQuality: [], message: 'No active recipes' };
@@ -1493,7 +1493,7 @@ ${code.substring(0, 3000)}
1493
1493
  // 核心工具 — 使用最频繁,直接展示完整 schema
1494
1494
  const coreTools = new Set([
1495
1495
  'search_project_code', 'read_project_file',
1496
- 'search_knowledge', 'submit_candidate', 'submit_with_check', 'analyze_code',
1496
+ 'search_knowledge', 'submit_knowledge', 'submit_with_check', 'analyze_code',
1497
1497
  'bootstrap_knowledge', 'load_skill', 'suggest_skills',
1498
1498
  'create_skill', 'knowledge_overview', 'get_tool_details',
1499
1499
  'plan_task', 'review_my_output',
@@ -1549,8 +1549,8 @@ ${skillSection}
1549
1549
 
1550
1550
  \`\`\`batch_actions
1551
1551
  [
1552
- {"tool": "submit_candidate", "params": {"title": "...", "code": "..."}},
1553
- {"tool": "submit_candidate", "params": {"title": "...", "code": "..."}}
1552
+ {"tool": "submit_knowledge", "params": {"title": "...", "code": "..."}},
1553
+ {"tool": "submit_knowledge", "params": {"title": "...", "code": "..."}}
1554
1554
  ]
1555
1555
  \`\`\`
1556
1556
 
@@ -1616,7 +1616,7 @@ ${this.#projectBriefingCache}
1616
1616
  2. **定向探索** → get_file_summary 快速了解文件角色
1617
1617
  3. **深入研读** → search_project_code / read_project_file 获取真实代码
1618
1618
  4. **语义发现** → semantic_search_code 在知识库查找相关知识
1619
- 5. **知识产出** → submit_candidate 提交有价值的发现
1619
+ 5. **知识产出** → submit_knowledge 提交有价值的发现
1620
1620
 
1621
1621
  ## 高效使用工具(节省轮次)
1622
1622
  - **批量搜索**: search_project_code({ patterns: ["keywordA", "keywordB", "keywordC"] })
@@ -1624,7 +1624,7 @@ ${this.#projectBriefingCache}
1624
1624
  - 合并同类请求为一次调用,避免逐个搜索/读取浪费轮次。
1625
1625
 
1626
1626
  ## 「项目特写」= 基本用法 + 项目特征融合
1627
- submit_candidate 的 code 字段必须是「项目特写」— 将技术的基本用法与本项目的特征融合为一体:
1627
+ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基本用法与本项目的特征融合为一体:
1628
1628
  1. **项目选择了什么**: 采用了哪种写法/模式/约定
1629
1629
  2. **为什么这样选**: 统计数据(N 个文件、占比 M%)
1630
1630
  3. **项目禁止什么**: 被放弃的写法、反模式、显式禁用标记
@@ -2002,15 +2002,16 @@ submit_candidate 的 code 字段必须是「项目特写」— 将技术的基
2002
2002
  // rule: code-standard, code-style, best-practice, boundary-constraint
2003
2003
  // pattern: code-pattern, architecture, solution
2004
2004
  // fact: code-relation, inheritance, call-chain, data-flow, module-dependency
2005
+ // V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
2005
2006
  const stats = db.prepare(`
2006
2007
  SELECT
2007
- (SELECT COUNT(*) FROM recipes) as recipeCount,
2008
- (SELECT COUNT(*) FROM recipes WHERE knowledge_type IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
2009
- (SELECT COUNT(*) FROM recipes WHERE knowledge_type IN ('code-pattern','architecture','solution')) as patternCount,
2010
- (SELECT COUNT(*) FROM recipes WHERE knowledge_type IN ('code-relation','inheritance','call-chain','data-flow','module-dependency')) as factCount,
2011
- (SELECT COUNT(*) FROM recipes WHERE knowledge_type = 'boundary-constraint') as guardRuleCount,
2012
- (SELECT COUNT(*) FROM candidates WHERE status='pending') as pendingCandidates,
2013
- (SELECT COUNT(*) FROM candidates) as totalCandidates
2008
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
2009
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
2010
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-pattern','architecture','solution')) as patternCount,
2011
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-relation','inheritance','call-chain','data-flow','module-dependency')) as factCount,
2012
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type = 'boundary-constraint') as guardRuleCount,
2013
+ (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
2014
+ (SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
2014
2015
  `).get();
2015
2016
  if (!stats || stats.recipeCount === 0) {
2016
2017
  return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
@@ -218,7 +218,7 @@ export class ContextWindow {
218
218
  for (const m of removed) {
219
219
  if (m.role === 'assistant' && m.toolCalls) {
220
220
  for (const tc of m.toolCalls) {
221
- if (tc.name === 'submit_candidate' || tc.name === 'submit_with_check') {
221
+ if (tc.name === 'submit_knowledge' || tc.name === 'submit_with_check') {
222
222
  this.#compactedSubmits.add(tc.args?.title || tc.args?.category || 'untitled');
223
223
  }
224
224
  }
@@ -339,7 +339,7 @@ export class ContextWindow {
339
339
  const m = this.#messages[i];
340
340
  if (m.role === 'assistant' && m.toolCalls) {
341
341
  for (const tc of m.toolCalls) {
342
- if (tc.name === 'submit_candidate' || tc.name === 'submit_with_check') {
342
+ if (tc.name === 'submit_knowledge' || tc.name === 'submit_with_check') {
343
343
  this.#compactedSubmits.add(tc.args?.title || tc.args?.category || 'untitled');
344
344
  }
345
345
  }
@@ -393,8 +393,8 @@ export class ContextWindow {
393
393
  export function limitToolResult(toolName, result, quota) {
394
394
  const { maxChars = 4000, maxMatches = 10 } = quota;
395
395
 
396
- // submit_candidate / submit_with_check 结果很短,不截断
397
- if (toolName === 'submit_candidate' || toolName === 'submit_with_check') {
396
+ // submit_knowledge / submit_with_check 结果很短,不截断
397
+ if (toolName === 'submit_knowledge' || toolName === 'submit_with_check') {
398
398
  const raw = typeof result === 'string' ? result : JSON.stringify(result);
399
399
  return raw.length > 500 ? raw.substring(0, 500) : raw;
400
400
  }
@@ -783,7 +783,7 @@ export class PhaseRouter {
783
783
  if (this.#totalSubmits === 0 && this.#phaseRounds >= 1) {
784
784
  return this.#isSkillOnly
785
785
  ? '你已收集足够信息,请在回复中直接输出 dimensionDigest JSON。'
786
- : '⚠️ 探索阶段已结束。你已收集了足够的项目信息,请 **立即** 调用 submit_candidate 提交候选。不要继续搜索,直接提交。';
786
+ : '⚠️ 探索阶段已结束。你已收集了足够的项目信息,请 **立即** 调用 submit_knowledge 提交候选。不要继续搜索,直接提交。';
787
787
  }
788
788
  if (this.#totalSubmits >= this.#budget.softSubmitLimit && this.#budget.softSubmitLimit > 0) {
789
789
  const remaining = this.#budget.maxSubmits - this.#totalSubmits;
@@ -2,7 +2,7 @@
2
2
  * ProducerAgent.js — v3.0 生产者 Agent
3
3
  *
4
4
  * 职责:
5
- * - 将 Analyst 的分析文本转换为结构化的 submit_candidate 调用
5
+ * - 将 Analyst 的分析文本转换为结构化的 submit_knowledge 调用
6
6
  * - 遵循 PROJECT_SNAPSHOT_STYLE_GUIDE 格式
7
7
  * - 使用 read_project_file 获取代码片段
8
8
  * - CandidateGuardrail 验证每次提交
@@ -21,7 +21,7 @@ import Logger from '../../infrastructure/logging/Logger.js';
21
21
 
22
22
  const PRODUCER_SYSTEM_PROMPT = `你是知识管理专家。你会收到一段代码分析文本,需要将其中的知识点转化为结构化的知识候选。
23
23
 
24
- 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 submit_candidate 调用。
24
+ 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 submit_knowledge 调用。
25
25
 
26
26
  每个候选必须:
27
27
  1. 有清晰的标题 (描述知识点的核心,使用项目真实类名)
@@ -33,7 +33,7 @@ const PRODUCER_SYSTEM_PROMPT = `你是知识管理专家。你会收到一段代
33
33
  1. 阅读分析文本,识别每个独立的知识点/发现
34
34
  2. 用 read_project_file 批量获取关键代码片段:
35
35
  read_project_file({ filePaths: ["FileA.m", "FileB.m"], maxLines: 80 })
36
- 3. 立刻调用 submit_candidate 提交
36
+ 3. 立刻调用 submit_knowledge 提交
37
37
  4. 重复直到分析中的所有知识点都已提交
38
38
 
39
39
  关键规则:
@@ -55,7 +55,7 @@ const PRODUCER_SYSTEM_PROMPT = `你是知识管理专家。你会收到一段代
55
55
  // ──────────────────────────────────────────────────────────────────
56
56
 
57
57
  const PRODUCER_TOOLS = [
58
- 'submit_candidate',
58
+ 'submit_knowledge',
59
59
  'submit_with_check',
60
60
  'read_project_file',
61
61
  ];
@@ -79,7 +79,7 @@ const PRODUCER_BUDGET = {
79
79
 
80
80
  const STYLE_GUIDE = `# 「项目特写」写作要求
81
81
 
82
- submit_candidate 的 code 字段必须是「项目特写」。
82
+ submit_knowledge 的 code 字段必须是「项目特写」。
83
83
 
84
84
  ## 什么是「项目特写」
85
85
  将一种技术的**基本用法**与**本项目的具体特征**融合为一体。
@@ -207,7 +207,7 @@ export class ProducerAgent {
207
207
 
208
208
  // 统计提交 (区分成功/失败)
209
209
  const submitCalls = (result.toolCalls || []).filter(
210
- tc => (tc.tool || tc.name) === 'submit_candidate' || (tc.tool || tc.name) === 'submit_with_check'
210
+ tc => (tc.tool || tc.name) === 'submit_knowledge' || (tc.tool || tc.name) === 'submit_with_check'
211
211
  );
212
212
  const successCount = submitCalls.filter(tc => {
213
213
  const res = tc.result;
@@ -238,7 +238,7 @@ export class ProducerAgent {
238
238
  });
239
239
 
240
240
  const retrySubmits = (retryResult.toolCalls || []).filter(
241
- tc => (tc.tool || tc.name) === 'submit_candidate' || (tc.tool || tc.name) === 'submit_with_check'
241
+ tc => (tc.tool || tc.name) === 'submit_knowledge' || (tc.tool || tc.name) === 'submit_with_check'
242
242
  );
243
243
  const retrySuccess = retrySubmits.filter(tc => {
244
244
  const res = tc.result;