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
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Knowledge API 路由 (V3)
3
+ * 统一知识条目的 CRUD + 生命周期操作
4
+ * 替代 recipes.js + candidates.js (旧路由继续保留用于向后兼容)
5
+ */
6
+
7
+ import express from 'express';
8
+ import { asyncHandler } from '../middleware/errorHandler.js';
9
+ import { getServiceContainer } from '../../injection/ServiceContainer.js';
10
+ import { ValidationError } from '../../shared/errors/index.js';
11
+ import Logger from '../../infrastructure/logging/Logger.js';
12
+ import { getContext, safeInt } from '../utils/routeHelpers.js';
13
+
14
+ const logger = Logger.getInstance();
15
+ const router = express.Router();
16
+
17
+ const MAX_BATCH_SIZE = 100;
18
+
19
+ /* ═══ 查询 ═══════════════════════════════════════════════ */
20
+
21
+ /**
22
+ * GET /api/v1/knowledge
23
+ * 获取知识条目列表(支持筛选和分页)
24
+ */
25
+ router.get('/', asyncHandler(async (req, res) => {
26
+ const { lifecycle, kind, category, language, knowledgeType, scope, keyword, tag, source } = req.query;
27
+ const page = safeInt(req.query.page, 1);
28
+ const pageSize = safeInt(req.query.limit, 20, 1, 1000);
29
+
30
+ const container = getServiceContainer();
31
+ const knowledgeService = container.get('knowledgeService');
32
+
33
+ if (keyword) {
34
+ const result = await knowledgeService.search(keyword, { page, pageSize });
35
+ return res.json({ success: true, data: result });
36
+ }
37
+
38
+ const filters = {};
39
+ if (lifecycle) filters.lifecycle = lifecycle;
40
+ if (kind) filters.kind = kind;
41
+ if (category) filters.category = category;
42
+ if (language) filters.language = language;
43
+ if (knowledgeType) filters.knowledgeType = knowledgeType;
44
+ if (scope) filters.scope = scope;
45
+ if (tag) filters.tag = tag;
46
+ if (source) filters.source = source;
47
+
48
+ const result = await knowledgeService.list(filters, { page, pageSize });
49
+ res.json({ success: true, data: result });
50
+ }));
51
+
52
+ /**
53
+ * GET /api/v1/knowledge/stats
54
+ * 获取统计信息
55
+ */
56
+ router.get('/stats', asyncHandler(async (req, res) => {
57
+ const container = getServiceContainer();
58
+ const knowledgeService = container.get('knowledgeService');
59
+ const stats = await knowledgeService.getStats();
60
+ res.json({ success: true, data: stats });
61
+ }));
62
+
63
+ /**
64
+ * GET /api/v1/knowledge/:id
65
+ * 获取知识条目详情
66
+ */
67
+ router.get('/:id', asyncHandler(async (req, res) => {
68
+ const { id } = req.params;
69
+ const container = getServiceContainer();
70
+ const knowledgeService = container.get('knowledgeService');
71
+ const entry = await knowledgeService.get(id);
72
+ res.json({ success: true, data: entry.toJSON() });
73
+ }));
74
+
75
+ /* ═══ CRUD ═══════════════════════════════════════════════ */
76
+
77
+ /**
78
+ * POST /api/v1/knowledge
79
+ * 创建知识条目(wire format 直通)
80
+ */
81
+ router.post('/', asyncHandler(async (req, res) => {
82
+ const data = req.body;
83
+
84
+ if (!data.title || !data.content) {
85
+ throw new ValidationError('title and content are required');
86
+ }
87
+
88
+ const container = getServiceContainer();
89
+ const knowledgeService = container.get('knowledgeService');
90
+ const context = getContext(req);
91
+
92
+ const entry = await knowledgeService.create(data, context);
93
+ res.status(201).json({
94
+ success: true,
95
+ data: entry.toJSON(),
96
+ });
97
+ }));
98
+
99
+ /**
100
+ * PATCH /api/v1/knowledge/:id
101
+ * 更新知识条目(白名单字段)
102
+ */
103
+ router.patch('/:id', asyncHandler(async (req, res) => {
104
+ const { id } = req.params;
105
+ const container = getServiceContainer();
106
+ const knowledgeService = container.get('knowledgeService');
107
+ const context = getContext(req);
108
+
109
+ const entry = await knowledgeService.update(id, req.body, context);
110
+ res.json({ success: true, data: entry.toJSON() });
111
+ }));
112
+
113
+ /**
114
+ * DELETE /api/v1/knowledge/:id
115
+ * 删除知识条目
116
+ */
117
+ router.delete('/:id', asyncHandler(async (req, res) => {
118
+ const { id } = req.params;
119
+ const container = getServiceContainer();
120
+ const knowledgeService = container.get('knowledgeService');
121
+ const context = getContext(req);
122
+
123
+ const result = await knowledgeService.delete(id, context);
124
+ res.json({ success: true, data: result });
125
+ }));
126
+
127
+ /* ═══ 生命周期操作(3 状态: pending / active / deprecated)═══ */
128
+
129
+ /**
130
+ * PATCH /api/v1/knowledge/:id/publish
131
+ * 发布 (pending → active) — 仅开发者
132
+ */
133
+ router.patch('/:id/publish', asyncHandler(async (req, res) => {
134
+ const { id } = req.params;
135
+ const container = getServiceContainer();
136
+ const knowledgeService = container.get('knowledgeService');
137
+ const context = getContext(req);
138
+
139
+ const entry = await knowledgeService.publish(id, context);
140
+ res.json({ success: true, data: entry.toJSON() });
141
+ }));
142
+
143
+ /**
144
+ * PATCH /api/v1/knowledge/:id/deprecate
145
+ * 废弃 (pending|active → deprecated)
146
+ */
147
+ router.patch('/:id/deprecate', asyncHandler(async (req, res) => {
148
+ const { id } = req.params;
149
+ const { reason } = req.body;
150
+
151
+ if (!reason) {
152
+ throw new ValidationError('reason is required for deprecation');
153
+ }
154
+
155
+ const container = getServiceContainer();
156
+ const knowledgeService = container.get('knowledgeService');
157
+ const context = getContext(req);
158
+
159
+ const entry = await knowledgeService.deprecate(id, reason, context);
160
+ res.json({ success: true, data: entry.toJSON() });
161
+ }));
162
+
163
+ /**
164
+ * PATCH /api/v1/knowledge/:id/reactivate
165
+ * 重新激活 (deprecated → pending)
166
+ */
167
+ router.patch('/:id/reactivate', asyncHandler(async (req, res) => {
168
+ const { id } = req.params;
169
+ const container = getServiceContainer();
170
+ const knowledgeService = container.get('knowledgeService');
171
+ const context = getContext(req);
172
+
173
+ const entry = await knowledgeService.reactivate(id, context);
174
+ res.json({ success: true, data: entry.toJSON() });
175
+ }));
176
+
177
+ /* ═══ 批量操作 ═══════════════════════════════════════════ */
178
+
179
+ /**
180
+ * POST /api/v1/knowledge/batch-publish
181
+ * 批量发布 (pending → active)
182
+ * 支持 autoApprovableOnly=true 参数,只发布 auto_approvable 的条目
183
+ */
184
+ router.post('/batch-publish', asyncHandler(async (req, res) => {
185
+ const { ids } = req.body;
186
+
187
+ if (!Array.isArray(ids) || ids.length === 0) {
188
+ throw new ValidationError('ids array is required and must not be empty');
189
+ }
190
+ if (ids.length > MAX_BATCH_SIZE) {
191
+ throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
192
+ }
193
+
194
+ const container = getServiceContainer();
195
+ const knowledgeService = container.get('knowledgeService');
196
+ const context = getContext(req);
197
+
198
+ const results = await Promise.allSettled(
199
+ ids.map(id => knowledgeService.publish(id, context)),
200
+ );
201
+
202
+ const published = results.filter(r => r.status === 'fulfilled').map(r => r.value.toJSON());
203
+ const failed = results
204
+ .map((r, i) => r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null)
205
+ .filter(Boolean);
206
+
207
+ res.json({
208
+ success: true,
209
+ data: { published, failed, total: ids.length, successCount: published.length, failureCount: failed.length },
210
+ });
211
+ }));
212
+
213
+ /* ═══ 使用 / 质量 ═══════════════════════════════════════ */
214
+
215
+ /**
216
+ * POST /api/v1/knowledge/:id/usage
217
+ * 记录使用(adoption / application / guard_hit / view / success)
218
+ */
219
+ router.post('/:id/usage', asyncHandler(async (req, res) => {
220
+ const { id } = req.params;
221
+ const { type = 'adoption', feedback } = req.body;
222
+ const context = getContext(req);
223
+
224
+ const container = getServiceContainer();
225
+ const knowledgeService = container.get('knowledgeService');
226
+
227
+ await knowledgeService.incrementUsage(id, type, { actor: context.userId, feedback });
228
+ res.json({ success: true, message: `${type} recorded` });
229
+ }));
230
+
231
+ /**
232
+ * PATCH /api/v1/knowledge/:id/quality
233
+ * 重新计算质量评分
234
+ */
235
+ router.patch('/:id/quality', asyncHandler(async (req, res) => {
236
+ const { id } = req.params;
237
+ const context = getContext(req);
238
+
239
+ const container = getServiceContainer();
240
+ const knowledgeService = container.get('knowledgeService');
241
+
242
+ const result = await knowledgeService.updateQuality(id, context);
243
+ res.json({ success: true, data: result });
244
+ }));
245
+
246
+ export default router;
@@ -44,13 +44,13 @@ router.get('/', asyncHandler(async (req, res) => {
44
44
  const results = {};
45
45
  const pagination = { page, pageSize: limit };
46
46
 
47
- // 搜索 Recipe(统一模型,包含所有知识类型)
47
+ // 搜索知识条目(V3 统一模型)
48
48
  if (type === 'all' || type === 'recipe' || type === 'solution') {
49
49
  try {
50
- const recipeService = container.get('recipeService');
51
- results.recipes = await recipeService.searchRecipes(q, pagination);
50
+ const knowledgeService = container.get('knowledgeService');
51
+ results.recipes = await knowledgeService.search(q, pagination);
52
52
  } catch (err) {
53
- logger.warn('Recipe 搜索失败', { query: q, error: err.message });
53
+ logger.warn('Knowledge 搜索失败', { query: q, error: err.message });
54
54
  results.recipes = { items: [], total: 0 };
55
55
  }
56
56
  }
@@ -66,11 +66,11 @@ router.get('/', asyncHandler(async (req, res) => {
66
66
  }
67
67
  }
68
68
 
69
- // 搜索 Candidate
69
+ // 搜索候选知识条目 (V3: lifecycle=draft/pending)
70
70
  if (type === 'all' || type === 'candidate') {
71
71
  try {
72
- const candidateService = container.get('candidateService');
73
- results.candidates = await candidateService.searchCandidates(q, pagination);
72
+ const knowledgeService = container.get('knowledgeService');
73
+ results.candidates = await knowledgeService.search(q, pagination);
74
74
  } catch (err) {
75
75
  logger.warn('Candidate 搜索失败', { query: q, error: err.message });
76
76
  results.candidates = { items: [], total: 0 };
@@ -172,18 +172,14 @@ router.get('/graph/all', asyncHandler(async (req, res) => {
172
172
  const nodeTypes = {}; // id → 主要类型(供前端区分渲染)
173
173
  const nodeCategories = {}; // id → category/target 名(供前端分组布局)
174
174
  if (nodeMap.size > 0) {
175
- const recipeService = container.get('recipeService');
176
- // 使用 repository.findById 避免 RecipeService.getRecipe 在找不到时记录 error 日志
177
- const recipeRepo = recipeService?.recipeRepository ?? null;
175
+ const knowledgeRepo = container.get('knowledgeRepository');
178
176
  for (const [id, types] of nodeMap) {
179
- // 记录节点主要类型
180
177
  const primaryType = types.has('recipe') ? 'recipe' : [...types][0];
181
178
  nodeTypes[id] = primaryType;
182
179
 
183
- if (primaryType === 'recipe' && recipeRepo) {
184
- // 仅 recipe 类型才查 DB;直接用 repo 避免 NotFoundError 日志噪音
180
+ if ((primaryType === 'recipe' || primaryType === 'knowledge') && knowledgeRepo) {
185
181
  try {
186
- const r = await recipeRepo.findById(id);
182
+ const r = await knowledgeRepo.findById(id);
187
183
  if (r) {
188
184
  nodeLabels[id] = r.title || r.name || id;
189
185
  nodeCategories[id] = r.category || '';
@@ -191,7 +187,6 @@ router.get('/graph/all', asyncHandler(async (req, res) => {
191
187
  }
192
188
  } catch { /* not found – fall through */ }
193
189
  }
194
- // module / candidate 或查不到的 recipe → 直接用 ID
195
190
  nodeLabels[id] = id;
196
191
  }
197
192
  }
@@ -226,9 +221,9 @@ router.post('/context-aware', asyncHandler(async (req, res) => {
226
221
  throw new ValidationError('keyword is required');
227
222
  }
228
223
  const container = getServiceContainer();
229
- const recipeService = container.get('recipeService');
224
+ const knowledgeService = container.get('knowledgeService');
230
225
  const pageSize = Math.min(limit || 10, 100);
231
- const list = await recipeService.searchRecipes(keyword, { page: 1, pageSize });
226
+ const list = await knowledgeService.search(keyword, { page: 1, pageSize });
232
227
  const items = list.data || list.items || [];
233
228
  const results = items.map(r => ({
234
229
  name: (r.title || r.id) + '.md',