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
@@ -174,6 +174,67 @@ export class AiProvider {
174
174
  ? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
175
175
  : '';
176
176
 
177
+ // comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
178
+ if (options.comprehensive) {
179
+ return `# Role
180
+ You are a ${langProfile.role} performing a **comprehensive full-file analysis**.
181
+
182
+ # Goal
183
+ Thoroughly analyze ALL code in "${targetName}" and create Recipe entries for **every significant method, function, or code block**.
184
+ This is a full-file analysis — do NOT skip methods just because they seem "simple" or "standard".
185
+ ${skillSection}${astSection}
186
+
187
+ # What to extract
188
+ - **Every** complete method/function with 5+ lines of implementation
189
+ - Initialization and configuration methods (init, viewDidLoad, setup, configure)
190
+ - Event handlers and action methods
191
+ - Data processing and business logic
192
+ - Protocol/delegate implementations
193
+ - ANY code block that a developer might reference, learn from, or reuse
194
+
195
+ # Extraction Rules
196
+ - Extract **BROADLY** — include all meaningful code units, not just "clever" or "novel" patterns
197
+ - Each recipe must be a **complete, standalone** code unit with full signature and body
198
+ - Put the complete code in \`content.pattern\`, and a meaningful project writeup in \`content.markdown\`
199
+ - Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
200
+ - Every recipe must be traceable to real code in the file. Do NOT invent code
201
+ - Include relevant \`headers\` (import/require lines) that the code depends on
202
+ - You **MUST** extract at least ONE recipe — every source file has something worth capturing
203
+ - For each recipe, provide a concise \`doClause\` (imperative sentence) and a \`topicHint\` group label
204
+
205
+ ${langProfile.extractionExamples}
206
+
207
+ # Output (JSON Array)
208
+ Each item MUST use the following V3 KnowledgeEntry structure:
209
+ - title (string): Descriptive English name
210
+ - description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
211
+ - trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
212
+ - kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
213
+ - knowledgeType (string): "code-pattern" | "architecture" | "api-usage" | "naming-convention" | "error-handling" | "performance" | "best-practice"
214
+ - complexity (string): "basic" | "intermediate" | "advanced"
215
+ - scope (string): "universal" | "project-specific" | "team-convention"
216
+ - category: ${langProfile.categories}
217
+ - language: "${langProfile.primaryLanguage}"
218
+ - content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
219
+ - reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
220
+ - headers (string[]): Required import/require lines
221
+ - tags (string[]): Search keywords
222
+ - doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
223
+ - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
224
+ - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
225
+ - topicHint (string): Group label for related patterns (e.g. "Networking", "UI-Layout", "Error-Handling")
226
+ - coreCode (string): Minimal 3-10 line code skeleton that captures the essence
227
+ - constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
228
+ - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
229
+
230
+ IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
231
+
232
+ Return ONLY a JSON array. Do NOT return an empty array.
233
+
234
+ Files Content:
235
+ ${files}`;
236
+ }
237
+
177
238
  return `# Role
178
239
  You are a ${langProfile.role} extracting production-quality reusable code patterns.
179
240
 
@@ -197,24 +258,37 @@ For each function/method/class in the file, ask: "Would a developer benefit from
197
258
  ${langProfile.extractionExamples}
198
259
 
199
260
  # Rules
200
- 1. Each \`code\` field must contain a **complete function/method or logical unit** — include the signature and full body
201
- 2. Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
202
- 3. Every recipe must be traceable to real code in the file. Do NOT invent code
203
- 4. Include relevant \`headers\` (import/require lines) that the code depends on
261
+ 1. \`content.pattern\` must contain a **complete function/method or logical unit** — include the signature and full body
262
+ 2. \`content.markdown\` must be a meaningful project-specific writeup explaining what this code does and when to use it
263
+ 3. Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
264
+ 4. Every recipe must be traceable to real code in the file. Do NOT invent code
265
+ 5. Include relevant \`headers\` (import/require lines) that the code depends on
266
+ 6. Every recipe must have a concise \`doClause\` and a \`topicHint\` group label
204
267
 
205
268
  # Output (JSON Array)
206
- Each item:
269
+ Each item MUST use the following V3 KnowledgeEntry structure:
207
270
  - title (string): Descriptive English name
208
- - summary_cn (string): Chinese description
209
- - summary_en (string): English description
210
- - trigger (string): @shortcut
271
+ - description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
272
+ - trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
273
+ - kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
274
+ - knowledgeType (string): "code-pattern" | "architecture" | "api-usage" | "naming-convention" | "error-handling" | "performance" | "best-practice"
275
+ - complexity (string): "basic" | "intermediate" | "advanced"
276
+ - scope (string): "universal" | "project-specific" | "team-convention"
211
277
  - category: ${langProfile.categories}
212
278
  - language: "${langProfile.primaryLanguage}"
213
- - code (string): Complete function/method/class from the file
279
+ - content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
280
+ - reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
214
281
  - headers (string[]): Required import/require lines
215
282
  - tags (string[]): Search keywords
216
- - usageGuide_cn (string): "何时使用" + "关键要点" (2-3 lines)
217
- - usageGuide_en (string): "When to use" + "Key points" (2-3 lines)
283
+ - doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
284
+ - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
285
+ - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
286
+ - topicHint (string): Group label (e.g. "Networking", "UI-Layout")
287
+ - coreCode (string): Minimal 3-10 line code skeleton that captures the essence
288
+ - constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
289
+ - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
290
+
291
+ IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
218
292
 
219
293
  Return ONLY a JSON array. If no meaningful patterns found, return [].
220
294
 
@@ -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 || '',
@@ -652,6 +652,22 @@ export async function fillDimensionsV3(fillContext) {
652
652
  // 释放文件缓存
653
653
  allFiles = null;
654
654
  chatAgent.setFileCache(null);
655
+
656
+ // ── Cursor Delivery: 生成 4 通道交付物料 ──
657
+ try {
658
+ const { getServiceContainer } = await import('../../../../../injection/ServiceContainer.js');
659
+ const container = getServiceContainer();
660
+ if (container.services.cursorDeliveryPipeline) {
661
+ const pipeline = container.get('cursorDeliveryPipeline');
662
+ const deliveryResult = await pipeline.deliver();
663
+ logger.info(`[Bootstrap-v3] 🚀 Cursor Delivery complete — ` +
664
+ `A: ${deliveryResult.channelA.rulesCount} rules, ` +
665
+ `B: ${deliveryResult.channelB.topicCount} topics, ` +
666
+ `C: ${deliveryResult.channelC.synced} skills`);
667
+ }
668
+ } catch (deliveryErr) {
669
+ logger.warn(`[Bootstrap-v3] Cursor Delivery failed (non-blocking): ${deliveryErr.message}`);
670
+ }
655
671
  }
656
672
 
657
673
  export { clearCheckpoints };
@@ -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.knowledgeType}
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.description) {
622
+ updateData.description = 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,69 @@
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
+ knowledgeType: json.knowledgeType, complexity: json.complexity,
16
+ scope: json.scope, tags: json.tags || [],
17
+ quality: json.quality || null, stats: json.stats || null,
18
+ status: json.lifecycle,
19
+ statistics: json.stats,
20
+ };
21
+ }
22
+
8
23
  export async function listByKind(ctx, kind, args) {
9
- const recipeService = ctx.container.get('recipeService');
24
+ const ks = ctx.container.get('knowledgeService');
10
25
  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
- }));
26
+ if (args.language) filters.language = args.language;
27
+ if (args.category) filters.category = args.category;
28
+ const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
29
+ const items = (result?.data || []).map(_projectItem);
22
30
  return envelope({ success: true, data: { kind, count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: `autosnippet_list_${kind}s` } });
23
31
  }
24
32
 
25
33
  export async function listRecipes(ctx, args) {
26
- const recipeService = ctx.container.get('recipeService');
34
+ const ks = ctx.container.get('knowledgeService');
27
35
  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
- }));
36
+ if (args.kind) filters.kind = args.kind;
37
+ if (args.language) filters.language = args.language;
38
+ if (args.category) filters.category = args.category;
39
+ if (args.knowledgeType) filters.knowledgeType = args.knowledgeType;
40
+ if (args.complexity) filters.complexity = args.complexity;
41
+ if (args.status) filters.lifecycle = args.status;
42
+ const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
43
+ const items = (result?.data || []).map(_projectItem);
42
44
  return envelope({ success: true, data: { count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: 'autosnippet_list_recipes' } });
43
45
  }
44
46
 
45
47
  export async function getRecipe(ctx, args) {
46
48
  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' } });
49
+ const ks = ctx.container.get('knowledgeService');
50
+ const entry = await ks.get(args.id);
51
+ if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
52
+ const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
53
+ return envelope({ success: true, data: json, meta: { tool: 'autosnippet_get_recipe' } });
51
54
  }
52
55
 
53
56
  export async function recipeInsights(ctx, args) {
54
57
  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}`);
58
+ const ks = ctx.container.get('knowledgeService');
59
+ const entry = await ks.get(args.id);
60
+ if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
61
+ const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
58
62
 
59
63
  // 聚合关系摘要
60
64
  const relationsSummary = {};
61
- if (recipe.relations) {
62
- for (const [type, targets] of Object.entries(recipe.relations)) {
65
+ if (json.relations) {
66
+ for (const [type, targets] of Object.entries(json.relations)) {
63
67
  if (Array.isArray(targets) && targets.length > 0) {
64
68
  relationsSummary[type] = targets.length;
65
69
  }
@@ -68,8 +72,8 @@ export async function recipeInsights(ctx, args) {
68
72
 
69
73
  // 约束条件概览
70
74
  const constraintsSummary = {};
71
- if (recipe.constraints) {
72
- for (const [type, items] of Object.entries(recipe.constraints)) {
75
+ if (json.constraints) {
76
+ for (const [type, items] of Object.entries(json.constraints)) {
73
77
  if (Array.isArray(items) && items.length > 0) {
74
78
  constraintsSummary[type] = items;
75
79
  }
@@ -77,43 +81,42 @@ export async function recipeInsights(ctx, args) {
77
81
  }
78
82
 
79
83
  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,
84
+ id: json.id,
85
+ title: json.title,
86
+ trigger: json.trigger || '',
87
+ kind: json.kind,
88
+ lifecycle: json.lifecycle,
89
+ language: json.language,
90
+ category: json.category,
91
+ knowledgeType: json.knowledgeType,
88
92
  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,
93
+ overall: json.quality?.overall ?? null,
94
+ completeness: json.quality?.completeness ?? null,
95
+ adaptation: json.quality?.adaptation ?? null,
96
+ documentation: json.quality?.documentation ?? null,
93
97
  },
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,
98
+ stats: {
99
+ adoptions: json.stats?.adoptions ?? 0,
100
+ applications: json.stats?.applications ?? 0,
101
+ guardHits: json.stats?.guardHits ?? 0,
102
+ views: json.stats?.views ?? 0,
103
+ searchHits: json.stats?.searchHits ?? 0,
101
104
  },
102
105
  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,
106
+ hasPattern: !!json.content?.pattern,
107
+ hasRationale: !!json.content?.rationale,
108
+ hasMarkdown: !!json.content?.markdown,
109
+ stepsCount: json.content?.steps?.length ?? 0,
110
+ codeChangesCount: json.content?.codeChanges?.length ?? 0,
108
111
  },
109
112
  relations: relationsSummary,
110
113
  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,
114
+ tags: json.tags || [],
115
+ complexity: json.complexity,
116
+ scope: json.scope,
117
+ createdBy: json.createdBy,
118
+ createdAt: json.createdAt,
119
+ updatedAt: json.updatedAt,
117
120
  };
118
121
 
119
122
  return envelope({ success: true, data: insights, meta: { tool: 'autosnippet_recipe_insights' } });
@@ -121,11 +124,11 @@ export async function recipeInsights(ctx, args) {
121
124
 
122
125
  export async function confirmUsage(ctx, args) {
123
126
  if (!args.recipeId) throw new Error('recipeId is required');
124
- const recipeService = ctx.container.get('recipeService');
127
+ const ks = ctx.container.get('knowledgeService');
125
128
  const usageType = args.usageType || 'adoption';
126
129
  const feedback = args.feedback || null;
127
130
 
128
- await recipeService.incrementUsage(args.recipeId, usageType, {
131
+ await ks.incrementUsage(args.recipeId, usageType, {
129
132
  feedback,
130
133
  actor: 'mcp_user',
131
134
  });
@@ -146,7 +149,7 @@ export async function confirmUsage(ctx, args) {
146
149
  return envelope({
147
150
  success: true,
148
151
  data: { recipeId: args.recipeId, usageType, feedback },
149
- message: `已记录 Recipe ${args.recipeId} 的${usageType === 'adoption' ? '采纳' : '应用'}`,
152
+ message: `已记录使用 ${args.recipeId} 的${usageType === 'adoption' ? '采纳' : '应用'}`,
150
153
  meta: { tool: 'autosnippet_confirm_usage' },
151
154
  });
152
155
  }