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,373 @@
1
+ import { BaseRepository } from '../base/BaseRepository.js';
2
+ import { KnowledgeEntry, Lifecycle, inferKind } from '../../domain/knowledge/index.js';
3
+ import Logger from '../../infrastructure/logging/Logger.js';
4
+
5
+ /**
6
+ * KnowledgeRepositoryImpl — 统一知识实体仓储实现
7
+ *
8
+ * 面向 knowledge_entries 表的 SQLite 持久化。
9
+ * 替代 CandidateRepositoryImpl + RecipeRepositoryImpl。
10
+ */
11
+ export class KnowledgeRepositoryImpl extends BaseRepository {
12
+ constructor(database) {
13
+ super(database, 'knowledge_entries');
14
+ this.logger = Logger.getInstance();
15
+ }
16
+
17
+ /* ═══ CRUD ═══════════════════════════════════════════ */
18
+
19
+ /**
20
+ * 创建 KnowledgeEntry
21
+ * @param {KnowledgeEntry} entry
22
+ * @returns {Promise<KnowledgeEntry>}
23
+ */
24
+ async create(entry) {
25
+ if (!entry || !entry.isValid()) {
26
+ throw new Error('Invalid knowledge entry: title + content required');
27
+ }
28
+
29
+ try {
30
+ const row = this._entityToRow(entry);
31
+ const keys = Object.keys(row);
32
+ const placeholders = keys.map(() => '?').join(', ');
33
+ const query = `INSERT INTO knowledge_entries (${keys.join(', ')}) VALUES (${placeholders})`;
34
+ this.db.prepare(query).run(...Object.values(row));
35
+ return this.findById(entry.id);
36
+ } catch (error) {
37
+ this.logger.error('Error creating knowledge entry', {
38
+ entryId: entry.id,
39
+ error: error.message,
40
+ });
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 更新 KnowledgeEntry(接受完整实体或部分 wire format)
47
+ * @param {string} id
48
+ * @param {Object|KnowledgeEntry} updates
49
+ * @returns {Promise<KnowledgeEntry>}
50
+ */
51
+ async update(id, updates) {
52
+ try {
53
+ const existing = await this.findById(id);
54
+ if (!existing) throw new Error(`Knowledge entry not found: ${id}`);
55
+
56
+ // 如果传入的是完整实体
57
+ if (updates instanceof KnowledgeEntry) {
58
+ const row = this._entityToRow(updates);
59
+ delete row.id;
60
+ delete row.created_at;
61
+ row.updated_at = Math.floor(Date.now() / 1000);
62
+
63
+ const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
64
+ this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
65
+ .run(...Object.values(row), id);
66
+ return this.findById(id);
67
+ }
68
+
69
+ // 部分更新 — 合并 wire format 到现有实体
70
+ const merged = KnowledgeEntry.fromJSON({
71
+ ...existing.toJSON(),
72
+ ...updates,
73
+ updated_at: Math.floor(Date.now() / 1000),
74
+ });
75
+ const row = this._entityToRow(merged);
76
+ delete row.id;
77
+ delete row.created_at;
78
+
79
+ const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
80
+ this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
81
+ .run(...Object.values(row), id);
82
+ return this.findById(id);
83
+ } catch (error) {
84
+ this.logger.error('Error updating knowledge entry', {
85
+ id,
86
+ error: error.message,
87
+ });
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 删除
94
+ * @param {string} id
95
+ * @returns {Promise<boolean>}
96
+ */
97
+ async delete(id) {
98
+ try {
99
+ const result = this.db.prepare('DELETE FROM knowledge_entries WHERE id = ?').run(id);
100
+ return result.changes > 0;
101
+ } catch (error) {
102
+ this.logger.error('Error deleting knowledge entry', { id, error: error.message });
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ /* ═══ 查询 ═══════════════════════════════════════════ */
108
+
109
+ /**
110
+ * 分页查询
111
+ * @override
112
+ */
113
+ async findWithPagination(filters = {}, options = {}) {
114
+ const { page = 1, pageSize = 20, orderBy = 'created_at', order = 'DESC' } = options;
115
+ const offset = (page - 1) * pageSize;
116
+
117
+ const conditions = [];
118
+ const params = [];
119
+
120
+ // 处理特殊过滤字段
121
+ const { _tagLike, _search, lifecycle: lcFilter, ...normalFilters } = filters;
122
+
123
+ // lifecycle 筛选:将新 3 状态映射到可能存在的旧值
124
+ if (lcFilter) {
125
+ if (lcFilter === 'pending') {
126
+ conditions.push(`lifecycle IN ('pending', 'draft', 'approved', 'auto_approved')`);
127
+ } else if (lcFilter === 'deprecated') {
128
+ conditions.push(`lifecycle IN ('deprecated', 'rejected')`);
129
+ } else {
130
+ conditions.push(`lifecycle = ?`);
131
+ params.push(lcFilter);
132
+ }
133
+ }
134
+
135
+ for (const [key, value] of Object.entries(normalFilters)) {
136
+ if (value == null) continue;
137
+ this._assertSafeColumn(key);
138
+ conditions.push(`${key} = ?`);
139
+ params.push(value);
140
+ }
141
+
142
+ if (_tagLike) {
143
+ conditions.push(`tags LIKE ?`);
144
+ const escaped = _tagLike.replace(/[%_\\]/g, ch => `\\${ch}`);
145
+ params.push(`%"${escaped}"%`);
146
+ }
147
+
148
+ if (_search) {
149
+ const escaped = _search.replace(/[%_\\]/g, ch => `\\${ch}`);
150
+ const like = `%${escaped}%`;
151
+ conditions.push(`(title LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger_key LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\')`);
152
+ params.push(like, like, like, like, like);
153
+ }
154
+
155
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
156
+
157
+ this._assertSafeColumn(orderBy);
158
+ const orderClause = ` ORDER BY ${orderBy} ${order === 'ASC' ? 'ASC' : 'DESC'}`;
159
+
160
+ const total = this.db.prepare(`SELECT COUNT(*) as count FROM knowledge_entries${where}`).get(...params).count;
161
+ const data = this.db.prepare(`SELECT * FROM knowledge_entries${where}${orderClause} LIMIT ? OFFSET ?`)
162
+ .all(...params, pageSize, offset);
163
+
164
+ return {
165
+ data: data.map(row => this._rowToEntity(row)),
166
+ pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) },
167
+ };
168
+ }
169
+
170
+ /**
171
+ * 根据生命周期状态查询
172
+ */
173
+ async findByLifecycle(lifecycle, pagination = {}) {
174
+ return this.findWithPagination({ lifecycle }, pagination);
175
+ }
176
+
177
+ /**
178
+ * 根据 kind 查询
179
+ */
180
+ async findByKind(kind, options = {}) {
181
+ const { lifecycle, ...pagination } = options;
182
+ const filters = { kind };
183
+ if (lifecycle) filters.lifecycle = lifecycle;
184
+ return this.findWithPagination(filters, pagination);
185
+ }
186
+
187
+ /**
188
+ * 查询所有 active 的 rule 类型(Guard 消费热路径)
189
+ * @returns {Promise<KnowledgeEntry[]>}
190
+ */
191
+ async findActiveRules() {
192
+ try {
193
+ const rows = this.db.prepare(`
194
+ SELECT * FROM knowledge_entries
195
+ WHERE kind = 'rule' AND lifecycle = 'active'
196
+ `).all();
197
+ return rows.map(row => this._rowToEntity(row));
198
+ } catch (error) {
199
+ this.logger.error('Error finding active rules', { error: error.message });
200
+ throw error;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 根据语言查询
206
+ */
207
+ async findByLanguage(language, pagination = {}) {
208
+ return this.findWithPagination({ language }, pagination);
209
+ }
210
+
211
+ /**
212
+ * 根据分类查询
213
+ */
214
+ async findByCategory(category, pagination = {}) {
215
+ return this.findWithPagination({ category }, pagination);
216
+ }
217
+
218
+ /**
219
+ * 搜索
220
+ */
221
+ async search(keyword, pagination = {}) {
222
+ return this.findWithPagination({ _search: keyword }, pagination);
223
+ }
224
+
225
+ /**
226
+ * 获取统计信息
227
+ */
228
+ async getStats() {
229
+ try {
230
+ return this.db.prepare(`
231
+ SELECT
232
+ COUNT(*) as total,
233
+ SUM(CASE WHEN lifecycle IN ('pending', 'draft', 'approved', 'auto_approved') THEN 1 ELSE 0 END) as pending,
234
+ SUM(CASE WHEN lifecycle = 'active' THEN 1 ELSE 0 END) as active,
235
+ SUM(CASE WHEN lifecycle IN ('deprecated', 'rejected') THEN 1 ELSE 0 END) as deprecated,
236
+ SUM(CASE WHEN kind = 'rule' THEN 1 ELSE 0 END) as rules,
237
+ SUM(CASE WHEN kind = 'pattern' THEN 1 ELSE 0 END) as patterns,
238
+ SUM(CASE WHEN kind = 'fact' THEN 1 ELSE 0 END) as facts
239
+ FROM knowledge_entries
240
+ `).get();
241
+ } catch (error) {
242
+ this.logger.error('Error getting knowledge stats', { error: error.message });
243
+ throw error;
244
+ }
245
+ }
246
+
247
+ /* ═══ 行 ↔ 实体 映射 ═══════════════════════════════ */
248
+
249
+ /**
250
+ * DB Row → KnowledgeEntry
251
+ * @param {Object} row
252
+ * @returns {KnowledgeEntry}
253
+ */
254
+ _rowToEntity(row) {
255
+ if (!row) return null;
256
+
257
+ return new KnowledgeEntry({
258
+ id: row.id,
259
+ title: row.title,
260
+ trigger: row.trigger_key,
261
+ description: row.description,
262
+ lifecycle: row.lifecycle,
263
+ lifecycleHistory: this._parseJson(row.lifecycle_history, []),
264
+ autoApprovable: !!row.probation,
265
+ language: row.language,
266
+ category: row.category,
267
+ kind: row.kind || inferKind(row.knowledge_type),
268
+ knowledgeType: row.knowledge_type,
269
+ complexity: row.complexity,
270
+ scope: row.scope,
271
+ difficulty: row.difficulty,
272
+ tags: this._parseJson(row.tags, []),
273
+ summaryCn: row.summary_cn || '',
274
+ summaryEn: row.summary_en || '',
275
+ usageGuideCn: row.usage_guide_cn || '',
276
+ usageGuideEn: row.usage_guide_en || '',
277
+ content: this._parseJson(row.content, {}),
278
+ relations: this._parseJson(row.relations, {}),
279
+ constraints: this._parseJson(row.constraints, {}),
280
+ reasoning: this._parseJson(row.reasoning, {}),
281
+ quality: this._parseJson(row.quality, {}),
282
+ stats: this._parseJson(row.stats, {}),
283
+ headers: this._parseJson(row.headers, []),
284
+ headerPaths: this._parseJson(row.header_paths, []),
285
+ moduleName: row.module_name || '',
286
+ includeHeaders: !!row.include_headers,
287
+ agentNotes: this._parseJson(row.agent_notes, null),
288
+ aiInsight: row.ai_insight || null,
289
+ reviewedBy: row.reviewed_by || null,
290
+ reviewedAt: row.reviewed_at || null,
291
+ rejectionReason: row.rejection_reason || null,
292
+ source: row.source || 'manual',
293
+ sourceFile: row.source_file || null,
294
+ sourceCandidateId: row.source_candidate_id || null,
295
+ createdBy: row.created_by || 'system',
296
+ createdAt: row.created_at,
297
+ updatedAt: row.updated_at,
298
+ publishedAt: row.published_at || null,
299
+ publishedBy: row.published_by || null,
300
+ });
301
+ }
302
+
303
+ /**
304
+ * KnowledgeEntry → DB Row
305
+ * @param {KnowledgeEntry} e
306
+ * @returns {Object}
307
+ */
308
+ _entityToRow(e) {
309
+ const now = Math.floor(Date.now() / 1000);
310
+ return {
311
+ id: e.id,
312
+ title: e.title,
313
+ trigger_key: e.trigger || '',
314
+ description: e.description || '',
315
+ lifecycle: e.lifecycle,
316
+ lifecycle_history: JSON.stringify(e.lifecycleHistory || []),
317
+ probation: e.autoApprovable ? 1 : 0,
318
+ language: e.language,
319
+ category: e.category,
320
+ kind: e.kind || inferKind(e.knowledgeType),
321
+ knowledge_type: e.knowledgeType || 'code-pattern',
322
+ complexity: e.complexity || 'intermediate',
323
+ scope: e.scope || null,
324
+ difficulty: e.difficulty || null,
325
+ tags: JSON.stringify(e.tags || []),
326
+ summary_cn: e.summaryCn || null,
327
+ summary_en: e.summaryEn || null,
328
+ usage_guide_cn: e.usageGuideCn || null,
329
+ usage_guide_en: e.usageGuideEn || null,
330
+ content: JSON.stringify(e.content.toJSON()),
331
+ relations: JSON.stringify(e.relations.toJSON()),
332
+ constraints: JSON.stringify(e.constraints.toJSON()),
333
+ reasoning: JSON.stringify(e.reasoning.toJSON()),
334
+ quality: JSON.stringify(e.quality.toJSON()),
335
+ stats: JSON.stringify(e.stats.toJSON()),
336
+ headers: JSON.stringify(e.headers || []),
337
+ header_paths: JSON.stringify(e.headerPaths || []),
338
+ module_name: e.moduleName || null,
339
+ include_headers: e.includeHeaders ? 1 : 0,
340
+ agent_notes: e.agentNotes ? JSON.stringify(e.agentNotes) : null,
341
+ ai_insight: e.aiInsight || null,
342
+ reviewed_by: e.reviewedBy || null,
343
+ reviewed_at: e.reviewedAt || null,
344
+ rejection_reason: e.rejectionReason || null,
345
+ source: e.source || 'manual',
346
+ source_file: e.sourceFile || null,
347
+ source_candidate_id: e.sourceCandidateId || null,
348
+ created_by: e.createdBy || 'system',
349
+ created_at: e.createdAt || now,
350
+ updated_at: e.updatedAt || now,
351
+ published_at: e.publishedAt || null,
352
+ published_by: e.publishedBy || null,
353
+ content_hash: null,
354
+ };
355
+ }
356
+
357
+ /**
358
+ * 覆写 BaseRepository 的 _mapRowToEntity
359
+ * @override
360
+ */
361
+ _mapRowToEntity(row) {
362
+ return this._rowToEntity(row);
363
+ }
364
+
365
+ /** @private 安全解析 JSON */
366
+ _parseJson(value, fallback) {
367
+ if (!value || value === 'null') return fallback;
368
+ if (typeof value === 'object') return value;
369
+ try { return JSON.parse(value); } catch { return fallback; }
370
+ }
371
+ }
372
+
373
+ export default KnowledgeRepositoryImpl;
@@ -123,9 +123,8 @@ function _isHeaderDirective(line) {
123
123
  }
124
124
 
125
125
  function _isGuardDirective(line) {
126
- return (
127
- line.startsWith(MARKS.AUDIT_SHORT) || line.startsWith(MARKS.AUDIT_ALIAS)
128
- );
126
+ // 精确匹配: // as:audit 或 // as:a,后面只能是空格或行尾
127
+ return /^\/\/\s*as:(?:audit|a)(?:\s|$)/.test(line);
129
128
  }
130
129
 
131
130
  function _isSearchDirective(line) {
@@ -187,7 +187,7 @@ export class FileWatcher {
187
187
 
188
188
  // // as:a — Guard 检查
189
189
  if (triggers.guardLine) {
190
- await handleGuard(fullPath, data, triggers.guardLine);
190
+ await handleGuard(this, fullPath, data, triggers.guardLine);
191
191
  }
192
192
 
193
193
  // // as:s — 搜索
@@ -230,46 +230,85 @@ export class FileWatcher {
230
230
  * 追加候选项(通过 ServiceContainer 或 HTTP API)
231
231
  */
232
232
  async _appendCandidates(items, source) {
233
+ // 过滤空 title / 空 code 的无效条目
234
+ const validItems = items.filter(item => {
235
+ const title = (item.title || '').trim();
236
+ const code = (item.code || '').trim();
237
+ if (!title || !code) {
238
+ console.warn(`[Watcher] 跳过无效候选: title=${JSON.stringify(title)}, code length=${code.length}`);
239
+ return false;
240
+ }
241
+ return true;
242
+ });
243
+ if (validItems.length === 0) {
244
+ throw new Error('所有候选条目缺少 title 或 code,无法提交');
245
+ }
246
+
233
247
  // 优先 ServiceContainer
248
+ let serviceError = null;
234
249
  try {
235
250
  const { ServiceContainer } = await import('../../injection/ServiceContainer.js');
236
251
  const container = ServiceContainer.getInstance();
237
- const candidateService = container.get('candidateService');
238
- for (const item of items) {
239
- await candidateService.createCandidate(
240
- {
241
- code: item.code || '',
242
- language: item.language || 'objc',
243
- category: item.category || 'Utility',
244
- source: source || 'watch',
245
- metadata: {
246
- title: item.title,
247
- summary: item.summary,
248
- trigger: item.trigger,
249
- usageGuide: item.usageGuide,
250
- headers: item.headers,
251
- },
252
+ const knowledgeService = container.get('knowledgeService');
253
+ const context = { userId: 'filewatcher' };
254
+ for (const item of validItems) {
255
+ await knowledgeService.create({
256
+ content: {
257
+ pattern: item.code || '',
252
258
  },
253
- { userId: 'file-watcher' }
254
- );
259
+ language: item.language || 'objc',
260
+ category: item.category || 'Utility',
261
+ source: source || 'watch',
262
+ title: item.title,
263
+ summary_cn: item.summary || item.summary_cn || '',
264
+ summary_en: item.summary_en || '',
265
+ usage_guide_cn: item.usageGuide || item.usageGuide_cn || '',
266
+ usage_guide_en: item.usageGuide_en || '',
267
+ module_name: item.moduleName || 'watch-create',
268
+ trigger: item.trigger || '',
269
+ headers: item.headers || [],
270
+ tags: item.tags || [],
271
+ metadata: {
272
+ trigger: item.trigger,
273
+ usageGuide: item.usageGuide,
274
+ headers: item.headers,
275
+ },
276
+ }, context);
255
277
  }
256
278
  return;
257
- } catch {
258
- // ServiceContainer 未初始化
279
+ } catch (err) {
280
+ serviceError = err;
281
+ console.warn('[Watcher] KnowledgeService 创建失败,尝试 HTTP 回退:', err.message);
259
282
  }
260
283
 
261
- // 回退:HTTP API
284
+ // 回退:HTTP API(使用 knowledge 端点而非 candidates)
262
285
  const dashboardUrl = process.env.ASD_DASHBOARD_URL || 'http://localhost:3000';
263
286
  try {
264
- const resp = await fetch(`${dashboardUrl}/api/v1/candidates`, {
265
- method: 'POST',
266
- headers: { 'Content-Type': 'application/json' },
267
- body: JSON.stringify({ items, source }),
268
- });
269
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
287
+ for (const item of validItems) {
288
+ const resp = await fetch(`${dashboardUrl}/api/v1/knowledge`, {
289
+ method: 'POST',
290
+ headers: { 'Content-Type': 'application/json' },
291
+ body: JSON.stringify({
292
+ title: item.title,
293
+ content: { pattern: item.code || '' },
294
+ language: item.language || 'objc',
295
+ category: item.category || 'Utility',
296
+ source: source || 'watch',
297
+ summary_cn: item.summary || item.summary_cn || '',
298
+ module_name: item.moduleName || 'watch-create',
299
+ trigger: item.trigger || '',
300
+ headers: item.headers || [],
301
+ }),
302
+ });
303
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
304
+ }
305
+ return;
270
306
  } catch (err) {
271
- console.warn(`[Watcher] 候选提交失败: ${err.message}`);
307
+ console.warn(`[Watcher] HTTP 候选提交也失败: ${err.message}`);
272
308
  }
309
+
310
+ // 两条路径都失败 → 抛出原始错误
311
+ throw serviceError || new Error('候选提交失败:ServiceContainer 和 HTTP 均不可用');
273
312
  }
274
313
 
275
314
  /**