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
@@ -262,40 +262,41 @@ export async function graphImpact(ctx, args) {
262
262
  }
263
263
 
264
264
  /**
265
- * 降级:从 Recipe.relations_json 提取关系(不依赖 knowledge_edges 表)
265
+ * 降级:从 knowledge_entries.relations 提取关系(不依赖 knowledge_edges 表)
266
266
  */
267
267
  async function _fallbackRelationsFromRecipe(ctx, nodeId, relation, direction) {
268
- const recipeService = ctx.container.get('recipeService');
269
268
  try {
270
- const recipe = await recipeService.getRecipe(nodeId);
271
- if (!recipe) return { outgoing: [], incoming: [] };
269
+ const knowledgeService = ctx.container.get('knowledgeService');
270
+ const entry = await knowledgeService.get(nodeId);
271
+ if (!entry) return { outgoing: [], incoming: [] };
272
272
 
273
+ const relJson = typeof entry.relations?.toJSON === 'function' ? entry.relations.toJSON() : (entry.relations || {});
273
274
  const outgoing = [];
274
275
  if (direction === 'both' || direction === 'out') {
275
- for (const [relType, targets] of Object.entries(recipe.relations || {})) {
276
+ for (const [relType, targets] of Object.entries(relJson)) {
276
277
  if (relation && relType !== relation) continue;
277
278
  for (const t of (Array.isArray(targets) ? targets : [])) {
278
- outgoing.push({ fromId: nodeId, fromType: 'recipe', toId: t.target || t.id || t, toType: 'recipe', relation: relType });
279
+ outgoing.push({ fromId: nodeId, fromType: 'knowledge', toId: t.target || t.id || t, toType: 'knowledge', relation: relType });
279
280
  }
280
281
  }
281
282
  }
282
283
 
283
- // 反向查找:其他 Recipe 中 relations_json 包含当前 nodeId
284
+ // 反向查找:其他条目中 relations 包含当前 nodeId
284
285
  const incoming = [];
285
286
  if (direction === 'both' || direction === 'in') {
286
- const recipeRepo = ctx.container.get('recipeRepository');
287
- const reverseRows = recipeRepo.db.prepare(
288
- `SELECT id, relations_json FROM recipes WHERE relations_json LIKE ? AND id != ?`
287
+ const knowledgeRepo = ctx.container.get('knowledgeRepository');
288
+ const reverseRows = knowledgeRepo.db.prepare(
289
+ `SELECT id, relations FROM knowledge_entries WHERE relations LIKE ? AND id != ?`
289
290
  ).all(`%${nodeId}%`, nodeId);
290
291
  for (const row of reverseRows) {
291
292
  try {
292
- const rels = JSON.parse(row.relations_json || '{}');
293
+ const rels = JSON.parse(row.relations || '{}');
293
294
  for (const [relType, targets] of Object.entries(rels)) {
294
295
  if (relation && relType !== relation) continue;
295
296
  for (const t of (Array.isArray(targets) ? targets : [])) {
296
297
  const targetId = t.target || t.id || t;
297
298
  if (targetId === nodeId) {
298
- incoming.push({ fromId: row.id, fromType: 'recipe', toId: nodeId, toType: 'recipe', relation: relType });
299
+ incoming.push({ fromId: row.id, fromType: 'knowledge', toId: nodeId, toType: 'knowledge', relation: relType });
299
300
  }
300
301
  }
301
302
  }
@@ -308,23 +309,23 @@ async function _fallbackRelationsFromRecipe(ctx, nodeId, relation, direction) {
308
309
  }
309
310
 
310
311
  /**
311
- * 降级:从 relations_json 反查受影响的 Recipe
312
+ * 降级:从 knowledge_entries.relations 反查受影响的条目
312
313
  */
313
314
  async function _fallbackImpactFromRecipe(ctx, nodeId) {
314
315
  try {
315
- const recipeRepo = ctx.container.get('recipeRepository');
316
- const rows = recipeRepo.db.prepare(
317
- `SELECT id, title, relations_json FROM recipes WHERE relations_json LIKE ? AND id != ?`
316
+ const knowledgeRepo = ctx.container.get('knowledgeRepository');
317
+ const rows = knowledgeRepo.db.prepare(
318
+ `SELECT id, title, relations FROM knowledge_entries WHERE relations LIKE ? AND id != ?`
318
319
  ).all(`%${nodeId}%`, nodeId);
319
320
 
320
321
  const impacted = [];
321
322
  for (const row of rows) {
322
323
  try {
323
- const rels = JSON.parse(row.relations_json || '{}');
324
+ const rels = JSON.parse(row.relations || '{}');
324
325
  for (const [relType, targets] of Object.entries(rels)) {
325
326
  for (const t of (Array.isArray(targets) ? targets : [])) {
326
327
  if ((t.target || t.id || t) === nodeId) {
327
- impacted.push({ id: row.id, title: row.title, type: 'recipe', relation: relType, depth: 1 });
328
+ impacted.push({ id: row.id, title: row.title, type: 'knowledge', relation: relType, depth: 1 });
328
329
  }
329
330
  }
330
331
  }
@@ -364,17 +365,18 @@ export async function graphPath(ctx, args) {
364
365
  */
365
366
  async function _fallbackPathFromRecipe(ctx, fromId, toId) {
366
367
  try {
367
- const recipeService = ctx.container.get('recipeService');
368
- const recipe = await recipeService.getRecipe(fromId);
369
- if (!recipe) return { found: false, path: [], depth: -1 };
368
+ const knowledgeService = ctx.container.get('knowledgeService');
369
+ const entry = await knowledgeService.get(fromId);
370
+ if (!entry) return { found: false, path: [], depth: -1 };
370
371
 
371
- for (const [relType, targets] of Object.entries(recipe.relations || {})) {
372
+ const relJson = typeof entry.relations?.toJSON === 'function' ? entry.relations.toJSON() : (entry.relations || {});
373
+ for (const [relType, targets] of Object.entries(relJson)) {
372
374
  for (const t of (Array.isArray(targets) ? targets : [])) {
373
375
  const targetId = t.target || t.id || t;
374
376
  if (targetId === toId) {
375
377
  return {
376
378
  found: true,
377
- path: [{ from: { id: fromId, type: 'recipe' }, to: { id: toId, type: 'recipe' }, relation: relType }],
379
+ path: [{ from: { id: fromId, type: 'knowledge' }, to: { id: toId, type: 'knowledge' }, relation: relType }],
378
380
  depth: 1,
379
381
  };
380
382
  }
@@ -30,22 +30,23 @@ export async function health(ctx) {
30
30
  checks.database = true;
31
31
  // 知识库统计(轻量聚合查询)
32
32
  try {
33
+ // V3: knowledge_entries 统一表(lifecycle 替代 status)
33
34
  const rStats = db.prepare(`
34
35
  SELECT COUNT(*) as total,
35
- SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active,
36
+ SUM(CASE WHEN lifecycle='active' THEN 1 ELSE 0 END) as active,
36
37
  SUM(CASE WHEN kind='rule' THEN 1 ELSE 0 END) as rules,
37
38
  SUM(CASE WHEN kind='pattern' THEN 1 ELSE 0 END) as patterns,
38
39
  SUM(CASE WHEN kind='fact' THEN 1 ELSE 0 END) as facts
39
- FROM recipes
40
+ FROM knowledge_entries
40
41
  `).get();
41
- const cStats = db.prepare(`
42
+ const cPending = db.prepare(`
42
43
  SELECT COUNT(*) as total,
43
- SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) as pending
44
- FROM candidates
44
+ SUM(CASE WHEN lifecycle='pending' THEN 1 ELSE 0 END) as pending
45
+ FROM knowledge_entries
45
46
  `).get();
46
47
  knowledgeBase = {
47
48
  recipes: { total: rStats.total, active: rStats.active, rules: rStats.rules, patterns: rStats.patterns, facts: rStats.facts },
48
- candidates: { total: cStats.total, pending: cStats.pending },
49
+ candidates: { total: cPending.total, pending: cPending.pending },
49
50
  };
50
51
  } catch { /* 统计查询失败不影响 health */ }
51
52
  }
@@ -132,9 +133,6 @@ export function capabilities() {
132
133
  autosnippet_get_target_metadata: 'structure',
133
134
  autosnippet_validate_candidate: 'validate',
134
135
  autosnippet_check_duplicate: 'validate',
135
- autosnippet_submit_candidate: 'submit',
136
- autosnippet_submit_candidates: 'submit',
137
- autosnippet_submit_draft_recipes: 'submit',
138
136
  autosnippet_guard_check: 'guard',
139
137
  autosnippet_guard_audit_files: 'guard',
140
138
  autosnippet_scan_project: 'scan',
@@ -191,9 +189,9 @@ export function capabilities() {
191
189
  tools,
192
190
  workflows: [
193
191
  { name: '知识查询', steps: ['search(推荐首选,auto mode 融合)', 'get_recipe', 'confirm_usage'], tips: '精确匹配用 keyword_search,需意图+会话上下文用 context_search' },
194
- { name: '单文件候选提交', steps: ['check_duplicate', 'validate_candidate', 'submit_candidate'] },
195
- { name: '批量 Target 扫描', steps: ['get_targets', 'get_target_files', '(Agent 分析)', 'submit_candidates'] },
196
- { name: '全项目深度扫描', steps: ['scan_project', '(Agent 语义分析)', 'submit_candidates', 'enrich_candidates'] },
192
+ { name: '单条知识提交', steps: ['check_duplicate', 'validate_candidate', 'submit_knowledge'] },
193
+ { name: '批量 Target 扫描', steps: ['get_targets', 'get_target_files', '(Agent 分析)', 'submit_knowledge_batch'] },
194
+ { name: '全项目深度扫描', steps: ['scan_project', '(Agent 语义分析)', 'submit_knowledge_batch', 'enrich_candidates'] },
197
195
  { name: '候选就绪检查', steps: ['enrich_candidates', 'validate_candidate', 'check_duplicate'] },
198
196
  ],
199
197
  },
@@ -1,7 +1,9 @@
1
1
  /**
2
- * MCP 工具定义(38 个)+ Gateway 映射
2
+ * MCP 工具定义(35 个)+ Gateway 映射
3
3
  *
4
4
  * 只包含 JSON Schema 级别的声明,不含任何业务逻辑。
5
+ * V3: 旧 submit_candidate / submit_candidates / submit_draft_recipes 已移除,
6
+ * 统一使用 submit_knowledge / submit_knowledge_batch / knowledge_lifecycle。
5
7
  */
6
8
 
7
9
  /**
@@ -9,9 +11,6 @@
9
11
  * 只读工具不在此映射中,跳过 Gateway 以保持性能
10
12
  */
11
13
  export const TOOL_GATEWAY_MAP = {
12
- autosnippet_submit_candidate: { action: 'candidate:create', resource: 'candidates' },
13
- autosnippet_submit_candidates: { action: 'candidate:create', resource: 'candidates' },
14
- autosnippet_submit_draft_recipes: { action: 'candidate:create', resource: 'candidates' },
15
14
  autosnippet_guard_audit_files: { action: 'guard_rule:check_code', resource: 'guard_rules' },
16
15
  autosnippet_scan_project: { action: 'guard_rule:check_code', resource: 'guard_rules' },
17
16
  autosnippet_enrich_candidates: { action: 'candidate:update', resource: 'candidates' },
@@ -20,6 +19,10 @@ export const TOOL_GATEWAY_MAP = {
20
19
  autosnippet_create_skill: { action: 'create:skills', resource: 'skills' },
21
20
  autosnippet_delete_skill: { action: 'delete:skills', resource: 'skills' },
22
21
  autosnippet_update_skill: { action: 'update:skills', resource: 'skills' },
22
+ // V3 知识条目
23
+ autosnippet_submit_knowledge: { action: 'knowledge:create', resource: 'knowledge' },
24
+ autosnippet_submit_knowledge_batch: { action: 'knowledge:create', resource: 'knowledge' },
25
+ autosnippet_knowledge_lifecycle: { action: 'knowledge:update', resource: 'knowledge' },
23
26
  };
24
27
 
25
28
  export const TOOLS = [
@@ -283,132 +286,8 @@ export const TOOLS = [
283
286
  required: ['candidate'],
284
287
  },
285
288
  },
286
- // 18. 单条候选提交(支持结构化 content)
287
- {
288
- name: 'autosnippet_submit_candidate',
289
- description:
290
- '提交单条代码片段候选供审核。支持 V2 结构化全字段。含限流保护。Agent 必须提供 reasoning(推理依据)。\n' +
291
- '⚠️ Recipe-Ready 要求:为使候选直接审核通过为 Recipe,请尽量填写以下字段:\n' +
292
- ' 必填: title, code, language, category, trigger(@开头), summary_cn, headers(完整import语句)\n' +
293
- ' 强烈建议: summary_en, usageGuide(Markdown ### 章节), reasoning, knowledgeType, complexity\n' +
294
- ' 推荐: usageGuide_en, rationale, steps, constraints, relations\n' +
295
- '如字段不全,返回值中 recipeReadyHints 会提示缺失字段,Agent 应据此补全后重新提交或调用 enrich_candidates 查漏。',
296
- inputSchema: {
297
- type: 'object',
298
- properties: {
299
- // ── 核心(必填)──
300
- title: { type: 'string', description: '中文简短标题(≤20字)' },
301
- code: { type: 'string', description: '代码片段(映射到 content.pattern),使用 Xcode 占位符 <#name#>' },
302
- language: { type: 'string', description: '编程语言:swift / objectivec(必须小写)' },
303
- // ── 分类 ──
304
- category: { type: 'string', description: '分类(必填):View/Service/Tool/Model/Network/Storage/UI/Utility' },
305
- knowledgeType: { type: 'string', description: '知识维度:code-pattern|architecture|best-practice|code-standard|code-relation|inheritance|call-chain|data-flow|module-dependency|boundary-constraint|code-style|solution' },
306
- complexity: { type: 'string', enum: ['beginner', 'intermediate', 'advanced'] },
307
- scope: { type: 'string', enum: ['universal', 'project-specific', 'target-specific'], description: '适用范围' },
308
- tags: { type: 'array', items: { type: 'string' }, description: '可搜索标签' },
309
- // ── 描述与文档(双语) ──
310
- description: { type: 'string', description: '一句话功能描述' },
311
- summary: { type: 'string', description: '中文详细摘要(等同 summary_cn,Markdown)' },
312
- summary_cn: { type: 'string', description: '中文摘要(≤100字)。与 summary 二选一' },
313
- summary_en: { type: 'string', description: '英文摘要(≤100 words)。强烈建议提供,提升检索与 AI 理解' },
314
- trigger: { type: 'string', description: '触发关键词(必填,@开头,小写,如 @video-cover-cell)' },
315
- usageGuide: { type: 'string', description: '中文使用指南(等同 usageGuide_cn,Markdown ### 章节格式)' },
316
- usageGuide_cn: { type: 'string', description: '中文使用指南。与 usageGuide 二选一' },
317
- usageGuide_en: { type: 'string', description: '英文使用指南(Markdown ### 章节格式)。推荐提供' },
318
- // ── 结构化内容 ──
319
- rationale: { type: 'string', description: '设计原理/为什么这样做' },
320
- steps: { type: 'array', items: { type: 'object' }, description: '实施步骤 [{title, description, code}]' },
321
- codeChanges: { type: 'array', items: { type: 'object' }, description: '代码变更 [{file, before, after, explanation}]' },
322
- verification: { type: 'object', description: '验证方式 {method, expectedResult, testCode}' },
323
- headers: { type: 'array', items: { type: 'string' }, description: '完整 import/include 语句(必填)如 ["#import <Module/Header.h>"] 或 ["import Foundation"]' },
324
- // ── 约束与关系 ──
325
- constraints: { type: 'object', description: '约束 {boundaries[], preconditions[], sideEffects[], guards[{pattern,severity,message}]}' },
326
- relations: { type: 'object', description: '关系 {dependsOn[], extends[], conflicts[], related[], inherits[], implements[], calls[], dataFlow[]},每项 {target, description}' },
327
- // ── 质量 & 来源 ──
328
- quality: { type: 'object', description: '质量评分 {codeCompleteness, projectAdaptation, documentationClarity} (0-1)' },
329
- sourceFile: { type: 'string', description: '来源文件路径' },
330
- // ── 推理依据(Reasoning)— 必填 ──
331
- reasoning: {
332
- type: 'object',
333
- description: '推理依据:为什么提取这段代码。Agent 必须填写。',
334
- properties: {
335
- whyStandard: { type: 'string', description: '为什么这段代码值得沉淀为知识——如"该模式在项目中被反复使用且新人容易写错"' },
336
- sources: { type: 'array', items: { type: 'string' }, description: '来源列表:文件路径、文档链接、上下文引用等' },
337
- confidence: { type: 'number', description: '置信度 0-1,表示 Agent 对这条候选质量的确信程度' },
338
- qualitySignals: { type: 'object', description: '质量信号 {clarity, reusability, importance} 等自由 KV' },
339
- alternatives: { type: 'array', items: { type: 'string' }, description: '备选方案描述(如果有替代实现)' },
340
- },
341
- required: ['whyStandard', 'sources', 'confidence'],
342
- },
343
- source: { type: 'string', description: '来源标识(默认 mcp)' },
344
- clientId: { type: 'string', description: '客户端标识(用于限流)' },
345
- },
346
- required: ['title', 'code', 'language'],
347
- },
348
- },
349
- // 19. 批量候选提交
350
- {
351
- name: 'autosnippet_submit_candidates',
352
- description:
353
- '批量提交候选到 Candidates,支持去重与限流。每个 item 支持 V2 全字段。返回逐条结果与 recipeReadyHints。\n' +
354
- '⚠️ Recipe-Ready:请尽量每条填写 category, trigger(@开头), summary_cn, summary_en, headers, reasoning。缺失字段会在 recipeReadyHints 中提示。',
355
- inputSchema: {
356
- type: 'object',
357
- properties: {
358
- targetName: { type: 'string', description: 'Target 名称' },
359
- items: {
360
- type: 'array',
361
- description: '候选数组,每项字段详见 submit_candidate。尽量填充全部 Recipe-Ready 字段。',
362
- items: {
363
- type: 'object',
364
- properties: {
365
- title: { type: 'string' }, code: { type: 'string' }, language: { type: 'string' },
366
- category: { type: 'string' }, knowledgeType: { type: 'string' },
367
- complexity: { type: 'string' }, scope: { type: 'string' },
368
- tags: { type: 'array', items: { type: 'string' } },
369
- description: { type: 'string' },
370
- summary: { type: 'string', description: '中文摘要(等同 summary_cn)' },
371
- summary_cn: { type: 'string', description: '中文摘要' },
372
- summary_en: { type: 'string', description: '英文摘要(强烈建议)' },
373
- trigger: { type: 'string', description: '触发词(@开头)' },
374
- usageGuide: { type: 'string', description: '中文使用指南(等同 usageGuide_cn)' },
375
- usageGuide_cn: { type: 'string', description: '中文使用指南' },
376
- usageGuide_en: { type: 'string', description: '英文使用指南' },
377
- rationale: { type: 'string' },
378
- steps: { type: 'array', items: { type: 'object' } },
379
- codeChanges: { type: 'array', items: { type: 'object' } },
380
- verification: { type: 'object' },
381
- headers: { type: 'array', items: { type: 'string' }, description: '完整 import 语句' },
382
- constraints: { type: 'object' }, relations: { type: 'object' },
383
- quality: { type: 'object' }, sourceFile: { type: 'string' },
384
- reasoning: { type: 'object', description: '{whyStandard, sources[], confidence}(必填)' },
385
- },
386
- required: ['title', 'code', 'language'],
387
- },
388
- },
389
- source: { type: 'string', default: 'cursor-scan' },
390
- deduplicate: { type: 'boolean', default: true },
391
- clientId: { type: 'string' },
392
- },
393
- required: ['targetName', 'items'],
394
- },
395
- },
396
- // 20. 草稿 Recipe 提交
397
- {
398
- name: 'autosnippet_submit_draft_recipes',
399
- description: '解析草稿 Markdown 文件为 Recipe 候选并提交。支持完整 Recipe 和纯介绍。保留解析出的全部结构化字段(trigger/usageGuide/headers 等)。自动生成默认 reasoning。',
400
- inputSchema: {
401
- type: 'object',
402
- properties: {
403
- filePaths: { description: '草稿文件路径(字符串或数组)' },
404
- targetName: { type: 'string', default: '_draft' },
405
- source: { type: 'string', default: 'copilot-draft' },
406
- deleteAfterSubmit: { type: 'boolean', default: false },
407
- clientId: { type: 'string' },
408
- },
409
- required: ['filePaths'],
410
- },
411
- },
289
+ // 18-20: [已移除] 旧 submit_candidate / submit_candidates / submit_draft_recipes
290
+ // 统一使用 V3 submit_knowledge / submit_knowledge_batch / knowledge_lifecycle
412
291
  // 21. 能力声明
413
292
  {
414
293
  name: 'autosnippet_capabilities',
@@ -542,7 +421,7 @@ export const TOOLS = [
542
421
  description:
543
422
  '① 结构补齐(诊断模式)— 检查候选的字段完整性,返回缺失清单。\n' +
544
423
  '检查两层:\n' +
545
- ' • Recipe 必填:category、trigger(@开头)、summary_cnsummary_en、headers\n' +
424
+ ' • Recipe 必填:category、trigger(@开头)、description、headers\n' +
546
425
  ' • 语义字段:rationale、knowledgeType、complexity、scope、steps、constraints\n' +
547
426
  '不调用内置 AI,仅做诊断。返回每条候选的 missingFields 列表。\n' +
548
427
  '\n' +
@@ -574,10 +453,10 @@ export const TOOLS = [
574
453
  '⚠️ 产出为启发式初稿,必须执行后续步骤提升质量:\n' +
575
454
  ' Step 1: autosnippet_enrich_candidates — 诊断字段缺失,逐条补全必填字段\n' +
576
455
  ' Step 2: autosnippet_bootstrap_refine — AI 润色 summary/insight/relations/confidence\n' +
577
- ' Step 3: 逐 Target 深入分析,补充更细粒度候选(autosnippet_submit_candidates)\n' +
456
+ ' Step 3: 逐 Target 深入分析,补充更细粒度知识条目(autosnippet_submit_knowledge_batch)\n' +
578
457
  ' Step 4: 对新候选重复 Step 1-2\n' +
579
458
  '\n' +
580
- '质量标准:每条必须包含 title/code/language/category/trigger/summary_cn/headers/reasoning。',
459
+ '质量标准:每条必须包含 title/code/language/category/trigger/description/headers/reasoning。',
581
460
  inputSchema: {
582
461
  type: 'object',
583
462
  properties: {
@@ -589,7 +468,7 @@ export const TOOLS = [
589
468
  required: [],
590
469
  },
591
470
  },
592
- // 33. Skills 发现:列出所有可用 Agent Skill 及其适用场景
471
+ // 32. Skills 发现:列出所有可用 Agent Skill 及其适用场景
593
472
  {
594
473
  name: 'autosnippet_list_skills',
595
474
  description:
@@ -603,7 +482,7 @@ export const TOOLS = [
603
482
  ' • 执行具体任务前,加载对应的 Skill 获取操作指南和最佳实践',
604
483
  inputSchema: { type: 'object', properties: {}, required: [] },
605
484
  },
606
- // 34. Skills 加载:按需获取指定 Skill 的完整操作指南
485
+ // 33. Skills 加载:按需获取指定 Skill 的完整操作指南
607
486
  {
608
487
  name: 'autosnippet_load_skill',
609
488
  description:
@@ -627,7 +506,7 @@ export const TOOLS = [
627
506
  required: ['skillName'],
628
507
  },
629
508
  },
630
- // 35. 创建项目级 Skill
509
+ // 34. 创建项目级 Skill
631
510
  {
632
511
  name: 'autosnippet_create_skill',
633
512
  description:
@@ -671,7 +550,7 @@ export const TOOLS = [
671
550
  required: ['name', 'description', 'content'],
672
551
  },
673
552
  },
674
- // 36. Skill 推荐:基于使用模式分析,推荐创建 Skill
553
+ // 35. Skill 推荐:基于使用模式分析,推荐创建 Skill
675
554
  {
676
555
  name: 'autosnippet_suggest_skills',
677
556
  description:
@@ -686,7 +565,7 @@ export const TOOLS = [
686
565
  ' • 候选被大量驳回时',
687
566
  inputSchema: { type: 'object', properties: {}, required: [] },
688
567
  },
689
- // 37. 删除项目级 Skill
568
+ // 36. 删除项目级 Skill
690
569
  {
691
570
  name: 'autosnippet_delete_skill',
692
571
  description:
@@ -707,7 +586,7 @@ export const TOOLS = [
707
586
  required: ['name'],
708
587
  },
709
588
  },
710
- // 38. 更新项目级 Skill
589
+ // 37. 更新项目级 Skill
711
590
  {
712
591
  name: 'autosnippet_update_skill',
713
592
  description:
@@ -737,7 +616,7 @@ export const TOOLS = [
737
616
  required: ['name'],
738
617
  },
739
618
  },
740
- // 36. ② 内容润色:Bootstrap 候选 AI 精炼(Phase 6)
619
+ // 38. ② 内容润色:Bootstrap 候选 AI 精炼(Phase 6)
741
620
  {
742
621
  name: 'autosnippet_bootstrap_refine',
743
622
  description:
@@ -757,4 +636,119 @@ export const TOOLS = [
757
636
  required: [],
758
637
  },
759
638
  },
639
+ // ═══ V3 知识条目(统一实体) ═══════════════════════════════
640
+ // 36. 单条知识提交(V3 wire format 直通)
641
+ {
642
+ name: 'autosnippet_submit_knowledge',
643
+ description:
644
+ '提交单条知识条目到 AutoSnippet 知识库(V3 统一实体)。\n' +
645
+ '参数即 wire format — 直接构造 KnowledgeEntry。\n' +
646
+ '⚠️ Cursor Delivery 字段(kind/doClause/topicHint)直接影响 .mdc 规则文件生成质量,请务必填写。\n' +
647
+ '\n' +
648
+ '核心必填: title + language + content(pattern 或 markdown) + kind + doClause\n' +
649
+ '推荐填写: trigger, topicHint, whenClause, dontClause, coreCode, reasoning, tags\n' +
650
+ '\n' +
651
+ '根据 ConfidenceRouter 自动路由:高置信度自动入库(active),低置信度待审核(pending)。',
652
+ inputSchema: {
653
+ type: 'object',
654
+ properties: {
655
+ // ── 必填 ──
656
+ title: { type: 'string', description: '中文标题(≤20字)' },
657
+ language: { type: 'string', description: '编程语言(swift/objectivec/javascript/typescript/python,小写)' },
658
+ content: {
659
+ type: 'object',
660
+ description: '内容值对象(必须有 pattern 或 markdown)',
661
+ properties: {
662
+ pattern: { type: 'string', description: '完整代码片段(函数/方法/类实现)' },
663
+ markdown: { type: 'string', description: '项目特写 Markdown(技术说明文档)' },
664
+ rationale: { type: 'string', description: '设计原理(英文)' },
665
+ steps: { type: 'array', items: { type: 'object', properties: {
666
+ title: { type: 'string' }, description: { type: 'string' }, code: { type: 'string' },
667
+ }}},
668
+ codeChanges: { type: 'array', items: { type: 'object' } },
669
+ verification: { type: 'object' },
670
+ },
671
+ },
672
+ // ── Cursor Delivery(关键字段,影响 .mdc 规则生成) ──
673
+ kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型: rule=必须遵守的规则, pattern=可复用代码模板, fact=参考信息' },
674
+ doClause: { type: 'string', description: '正向指令: 英文祈使句 ≤60 tokens(e.g. "Use dependency injection via constructor")' },
675
+ dontClause: { type: 'string', description: '反向约束: 英文(不以 Don\'t 开头),e.g. "Instantiate services with new directly"' },
676
+ whenClause: { type: 'string', description: '触发场景: 英文描述何时应用此知识(e.g. "When creating a new ViewController subclass")' },
677
+ topicHint: { type: 'string', description: '主题分组标签,用于 .mdc 文件归类(e.g. "networking", "ui-layout", "error-handling", "data-model")' },
678
+ coreCode: { type: 'string', description: '3-8 行精华代码骨架,可直接复制使用(无 Markdown 格式)' },
679
+ trigger: { type: 'string', description: '触发关键词(@前缀 kebab-case,e.g. "@json-parse")' },
680
+ // ── 分类 ──
681
+ category: { type: 'string', description: '分类: View/Service/Tool/Model/Network/Storage/UI/Utility' },
682
+ knowledgeType: { type: 'string', description: '知识维度: code-pattern|architecture|best-practice|boundary-constraint|...' },
683
+ complexity: { type: 'string', enum: ['beginner', 'intermediate', 'advanced'] },
684
+ scope: { type: 'string', enum: ['universal', 'project-specific', 'target-specific'] },
685
+ difficulty: { type: 'string' },
686
+ tags: { type: 'array', items: { type: 'string' } },
687
+ // ── 描述 ──
688
+ description: { type: 'string', description: '中文简述 ≤80 字(引用真实类名/方法名)' },
689
+ // ── 约束与关系 ──
690
+ constraints: { type: 'object', description: '约束 {guards[], boundaries[], preconditions[], sideEffects[]}' },
691
+ relations: { type: 'object', description: '关系分桶 {inherits[], extends[], depends_on[], conflicts[], related[], ...}' },
692
+ // ── 推理 ──
693
+ reasoning: {
694
+ type: 'object',
695
+ description: '推理依据',
696
+ properties: {
697
+ whyStandard: { type: 'string', description: '为什么值得沉淀为知识' },
698
+ sources: { type: 'array', items: { type: 'string' }, description: '来源文件列表' },
699
+ confidence: { type: 'number', description: '置信度 0-1' },
700
+ qualitySignals: { type: 'object' },
701
+ alternatives: { type: 'array', items: { type: 'string' } },
702
+ },
703
+ },
704
+ // ── 头文件 ──
705
+ headers: { type: 'array', items: { type: 'string' }, description: '完整 import/include 语句' },
706
+ headerPaths: { type: 'array', items: { type: 'string' } },
707
+ moduleName: { type: 'string' },
708
+ includeHeaders: { type: 'boolean' },
709
+ // ── 控制 ──
710
+ source: { type: 'string', description: '来源标识(默认 mcp)' },
711
+ client_id: { type: 'string', description: '客户端标识(用于限流)' },
712
+ },
713
+ required: ['title', 'language', 'content', 'kind', 'doClause'],
714
+ },
715
+ },
716
+ // 37. 批量知识提交
717
+ {
718
+ name: 'autosnippet_submit_knowledge_batch',
719
+ description:
720
+ '批量提交知识条目到知识库(V3 统一实体)。\n' +
721
+ '每条 item 使用与 autosnippet_submit_knowledge 相同的 wire format。\n' +
722
+ '每条 item 必须包含 Cursor Delivery 字段(kind/doClause),否则生成的 .mdc 规则质量低。\n' +
723
+ '支持去重。返回逐条结果与 recipeReadyHints。',
724
+ inputSchema: {
725
+ type: 'object',
726
+ properties: {
727
+ target_name: { type: 'string', description: 'Target 名称' },
728
+ items: { type: 'array', description: '知识条目数组,每项字段同 submit_knowledge(必须含 kind/doClause)', items: { type: 'object' } },
729
+ source: { type: 'string', default: 'cursor-scan', description: '来源标识' },
730
+ deduplicate: { type: 'boolean', default: true, description: '是否去重' },
731
+ client_id: { type: 'string', description: '客户端标识(用于限流)' },
732
+ },
733
+ required: ['target_name', 'items'],
734
+ },
735
+ },
736
+ // 38. 知识条目生命周期操作
737
+ {
738
+ name: 'autosnippet_knowledge_lifecycle',
739
+ description:
740
+ '知识条目生命周期操作。\n' +
741
+ '可用操作: submit(draft→pending), approve(pending→approved), reject(pending→rejected),\n' +
742
+ 'publish(approved→active), deprecate(active→deprecated), reactivate(deprecated→active),\n' +
743
+ 'to_draft(rejected→draft), fast_track(draft→active 一键发布)。',
744
+ inputSchema: {
745
+ type: 'object',
746
+ properties: {
747
+ id: { type: 'string', description: '知识条目 ID' },
748
+ action: { type: 'string', enum: ['submit', 'approve', 'reject', 'publish', 'deprecate', 'reactivate', 'to_draft', 'fast_track'], description: '生命周期操作' },
749
+ reason: { type: 'string', description: 'reject/deprecate 时必须提供原因' },
750
+ },
751
+ required: ['id', 'action'],
752
+ },
753
+ },
760
754
  ];
@@ -13,8 +13,6 @@ import { requestLogger } from './middleware/requestLogger.js';
13
13
  import { gatewayMiddleware } from './middleware/gatewayMiddleware.js';
14
14
  import { roleResolverMiddleware } from './middleware/roleResolver.js';
15
15
  import { CapabilityProbe } from '../core/capability/CapabilityProbe.js';
16
- import candidateRouter from './routes/candidates.js';
17
- import recipeRouter from './routes/recipes.js';
18
16
  import guardRuleRouter from './routes/guardRules.js';
19
17
  import searchRouter from './routes/search.js';
20
18
  import healthRouter from './routes/health.js';
@@ -27,6 +25,7 @@ import spmRouter from './routes/spm.js';
27
25
  import violationsRouter from './routes/violations.js';
28
26
  import authRouter from './routes/auth.js';
29
27
  import skillsRouter from './routes/skills.js';
28
+ import knowledgeRouter from './routes/knowledge.js';
30
29
  import apiSpec from './api-spec.js';
31
30
  import { initRedisService } from '../infrastructure/cache/RedisService.js';
32
31
  import { initCacheAdapter } from '../infrastructure/cache/UnifiedCacheAdapter.js';
@@ -235,12 +234,6 @@ export class HttpServer {
235
234
  this.app.use(`${apiPrefix}/monitoring`, monitoringRouter);
236
235
  }
237
236
 
238
- // 候选项路由
239
- this.app.use(`${apiPrefix}/candidates`, candidateRouter);
240
-
241
- // 配方路由
242
- this.app.use(`${apiPrefix}/recipes`, recipeRouter);
243
-
244
237
  // 守护规则路由
245
238
  this.app.use(`${apiPrefix}/rules`, guardRuleRouter);
246
239
 
@@ -268,6 +261,19 @@ export class HttpServer {
268
261
  // 违规记录路由
269
262
  this.app.use(`${apiPrefix}/violations`, violationsRouter);
270
263
 
264
+ // 知识条目路由 (V3)
265
+ this.app.use(`${apiPrefix}/knowledge`, knowledgeRouter);
266
+
267
+ // 根路径 — 返回 API 元信息(避免外部探测产生无意义 404)
268
+ this.app.all('/', (req, res) => {
269
+ res.json({
270
+ name: 'AutoSnippet API',
271
+ version: '2.0',
272
+ docs: '/api-spec',
273
+ health: `${apiPrefix}/health`,
274
+ });
275
+ });
276
+
271
277
  // 404 处理
272
278
  this.app.use('*', (req, res) => {
273
279
  res.status(404).json({
@@ -126,7 +126,7 @@ router.post('/translate', asyncHandler(async (req, res) => {
126
126
  if (!summary && !usageGuide) {
127
127
  return res.json({
128
128
  success: true,
129
- data: { summary_en: '', usageGuide_en: '' },
129
+ data: { summaryEn: '', usageGuideEn: '' },
130
130
  });
131
131
  }
132
132
 
@@ -139,7 +139,7 @@ router.post('/translate', asyncHandler(async (req, res) => {
139
139
  logger.warn('AI translate tool returned error', { error: result.error });
140
140
  return res.json({
141
141
  success: true,
142
- data: { summary_en: summary || '', usageGuide_en: usageGuide || '' },
142
+ data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
143
143
  warning: result.error,
144
144
  });
145
145
  }
@@ -149,7 +149,7 @@ router.post('/translate', asyncHandler(async (req, res) => {
149
149
  logger.warn('AI translate failed, returning original text', { error: err.message });
150
150
  res.json({
151
151
  success: true,
152
- data: { summary_en: summary || '', usageGuide_en: usageGuide || '' },
152
+ data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
153
153
  warning: `Translation failed: ${err.message}`,
154
154
  });
155
155
  }
@@ -175,6 +175,7 @@ router.post('/chat', asyncHandler(async (req, res) => {
175
175
  reply: result.reply,
176
176
  hasContext: result.hasContext,
177
177
  toolCalls: result.toolCalls,
178
+ reasoningQuality: result.reasoningQuality || null,
178
179
  },
179
180
  });
180
181
  }));