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
@@ -52,6 +52,7 @@ router.post('/path', asyncHandler(async (req, res) => {
52
52
  const aiResult = await chatAgent.executeTool('extract_recipes', {
53
53
  targetName: fileName,
54
54
  files: [{ name: fileName, content: file.code || '' }],
55
+ comprehensive: true,
55
56
  });
56
57
 
57
58
  if (aiResult && !aiResult.error && Array.isArray(aiResult.recipes) && aiResult.recipes.length > 0) {
@@ -82,6 +83,7 @@ router.post('/path', asyncHandler(async (req, res) => {
82
83
  /**
83
84
  * POST /api/v1/extract/text
84
85
  * 从文本内容提取代码片段(剪贴板等)
86
+ * 管线: RecipeParser(MD解析) → AI 提取(ChatAgent) → 基础兜底
85
87
  */
86
88
  router.post('/text', asyncHandler(async (req, res) => {
87
89
  const { text, language, relativePath, projectRoot: bodyRoot } = req.body;
@@ -94,21 +96,63 @@ router.post('/text', asyncHandler(async (req, res) => {
94
96
  const recipeParser = container.get('recipeParser');
95
97
  const projectRoot = bodyRoot || container.singletons?._projectRoot || process.cwd();
96
98
 
97
- // 先尝试解析为 Recipe Markdown 格式
99
+ // 1. 先尝试解析为 Recipe Markdown 格式
98
100
  let result;
99
101
  try {
100
102
  result = await recipeParser.parseFromText(text, {
101
103
  language,
102
104
  relativePath,
103
105
  });
106
+ // 解析成功,直接返回
107
+ return res.json({
108
+ success: true,
109
+ data: {
110
+ result: Array.isArray(result) ? result : [result],
111
+ source: 'text',
112
+ },
113
+ });
104
114
  } catch (error) {
105
- logger.debug('Recipe MD parse failed, falling back to AI extraction', {
115
+ logger.debug('Recipe MD parse failed, trying AI extraction', {
106
116
  error: error.message,
107
117
  });
108
- // 回退到 AI 提取
109
- result = await recipeParser.extractFromText(text, { language });
110
118
  }
111
119
 
120
+ // 2. Recipe MD 解析失败 → 尝试 ChatAgent AI 提取
121
+ try {
122
+ const chatAgent = container.get('chatAgent');
123
+ if (chatAgent) {
124
+ const lang = language || (relativePath && /\.swift$/i.test(relativePath) ? 'swift' : 'objc');
125
+ const fileName = relativePath ? basename(relativePath) : `clipboard.${lang === 'swift' ? 'swift' : 'm'}`;
126
+ const aiResult = await chatAgent.executeTool('extract_recipes', {
127
+ targetName: fileName,
128
+ files: [{ name: fileName, content: text }],
129
+ comprehensive: true,
130
+ });
131
+
132
+ if (aiResult && !aiResult.error && Array.isArray(aiResult.recipes) && aiResult.recipes.length > 0) {
133
+ logger.info('extract/text: AI extraction succeeded', { count: aiResult.recipes.length });
134
+
135
+ // 多条 Recipe 时在第一条上标记总数(供前端提示)
136
+ if (aiResult.recipes.length > 1) {
137
+ aiResult.recipes[0]._multipleCount = aiResult.recipes.length;
138
+ }
139
+
140
+ return res.json({
141
+ success: true,
142
+ data: {
143
+ result: aiResult.recipes,
144
+ source: 'text',
145
+ },
146
+ });
147
+ }
148
+ }
149
+ } catch (err) {
150
+ logger.debug('extract/text: AI extraction failed, using basic fallback', { error: err.message });
151
+ }
152
+
153
+ // 3. AI 也失败 → 基础代码块提取兜底
154
+ result = await recipeParser.extractFromText(text, { language });
155
+
112
156
  res.json({
113
157
  success: true,
114
158
  data: {
@@ -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 参数,只发布 autoApprovable 的条目
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',