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
@@ -0,0 +1,338 @@
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
+ * 全链路 camelCase — DB 列名 = 实体属性名。
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(接受完整实体或部分数据)
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
+ if (updates instanceof KnowledgeEntry) {
57
+ const row = this._entityToRow(updates);
58
+ delete row.id;
59
+ delete row.createdAt;
60
+ row.updatedAt = Math.floor(Date.now() / 1000);
61
+
62
+ const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
63
+ this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
64
+ .run(...Object.values(row), id);
65
+ return this.findById(id);
66
+ }
67
+
68
+ // 部分更新 — 合并到现有实体
69
+ const merged = KnowledgeEntry.fromJSON({
70
+ ...existing.toJSON(),
71
+ ...updates,
72
+ updatedAt: Math.floor(Date.now() / 1000),
73
+ });
74
+ const row = this._entityToRow(merged);
75
+ delete row.id;
76
+ delete row.createdAt;
77
+
78
+ const setClauses = Object.keys(row).map(k => `${k} = ?`).join(', ');
79
+ this.db.prepare(`UPDATE knowledge_entries SET ${setClauses} WHERE id = ?`)
80
+ .run(...Object.values(row), id);
81
+ return this.findById(id);
82
+ } catch (error) {
83
+ this.logger.error('Error updating knowledge entry', {
84
+ id,
85
+ error: error.message,
86
+ });
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 删除
93
+ * @param {string} id
94
+ * @returns {Promise<boolean>}
95
+ */
96
+ async delete(id) {
97
+ try {
98
+ const result = this.db.prepare('DELETE FROM knowledge_entries WHERE id = ?').run(id);
99
+ return result.changes > 0;
100
+ } catch (error) {
101
+ this.logger.error('Error deleting knowledge entry', { id, error: error.message });
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /* ═══ 查询 ═══════════════════════════════════════════ */
107
+
108
+ /**
109
+ * 分页查询
110
+ * @override
111
+ */
112
+ async findWithPagination(filters = {}, options = {}) {
113
+ const { page = 1, pageSize = 20, orderBy = 'createdAt', order = 'DESC' } = options;
114
+ const offset = (page - 1) * pageSize;
115
+
116
+ const conditions = [];
117
+ const params = [];
118
+
119
+ const { _tagLike, _search, lifecycle: lcFilter, ...normalFilters } = filters;
120
+
121
+ if (lcFilter) {
122
+ conditions.push(`lifecycle = ?`);
123
+ params.push(lcFilter);
124
+ }
125
+
126
+ for (const [key, value] of Object.entries(normalFilters)) {
127
+ if (value == null) continue;
128
+ this._assertSafeColumn(key);
129
+ conditions.push(`${key} = ?`);
130
+ params.push(value);
131
+ }
132
+
133
+ if (_tagLike) {
134
+ conditions.push(`tags LIKE ?`);
135
+ const escaped = _tagLike.replace(/[%_\\]/g, ch => `\\${ch}`);
136
+ params.push(`%"${escaped}"%`);
137
+ }
138
+
139
+ if (_search) {
140
+ const escaped = _search.replace(/[%_\\]/g, ch => `\\${ch}`);
141
+ const like = `%${escaped}%`;
142
+ conditions.push(`(title LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\')`);
143
+ params.push(like, like, like, like, like);
144
+ }
145
+
146
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
147
+
148
+ this._assertSafeColumn(orderBy);
149
+ const orderClause = ` ORDER BY ${orderBy} ${order === 'ASC' ? 'ASC' : 'DESC'}`;
150
+
151
+ const total = this.db.prepare(`SELECT COUNT(*) as count FROM knowledge_entries${where}`).get(...params).count;
152
+ const data = this.db.prepare(`SELECT * FROM knowledge_entries${where}${orderClause} LIMIT ? OFFSET ?`)
153
+ .all(...params, pageSize, offset);
154
+
155
+ return {
156
+ data: data.map(row => this._rowToEntity(row)),
157
+ pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) },
158
+ };
159
+ }
160
+
161
+ /**
162
+ * 根据生命周期状态查询
163
+ */
164
+ async findByLifecycle(lifecycle, pagination = {}) {
165
+ return this.findWithPagination({ lifecycle }, pagination);
166
+ }
167
+
168
+ /**
169
+ * 根据 kind 查询
170
+ */
171
+ async findByKind(kind, options = {}) {
172
+ const { lifecycle, ...pagination } = options;
173
+ const filters = { kind };
174
+ if (lifecycle) filters.lifecycle = lifecycle;
175
+ return this.findWithPagination(filters, pagination);
176
+ }
177
+
178
+ /**
179
+ * 查询所有 active 的 rule 类型(Guard 消费热路径)
180
+ * @returns {Promise<KnowledgeEntry[]>}
181
+ */
182
+ async findActiveRules() {
183
+ try {
184
+ const rows = this.db.prepare(`
185
+ SELECT * FROM knowledge_entries
186
+ WHERE kind = 'rule' AND lifecycle = 'active'
187
+ `).all();
188
+ return rows.map(row => this._rowToEntity(row));
189
+ } catch (error) {
190
+ this.logger.error('Error finding active rules', { error: error.message });
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * 根据语言查询
197
+ */
198
+ async findByLanguage(language, pagination = {}) {
199
+ return this.findWithPagination({ language }, pagination);
200
+ }
201
+
202
+ /**
203
+ * 根据分类查询
204
+ */
205
+ async findByCategory(category, pagination = {}) {
206
+ return this.findWithPagination({ category }, pagination);
207
+ }
208
+
209
+ /**
210
+ * 搜索
211
+ */
212
+ async search(keyword, pagination = {}) {
213
+ return this.findWithPagination({ _search: keyword }, pagination);
214
+ }
215
+
216
+ /**
217
+ * 获取统计信息
218
+ */
219
+ async getStats() {
220
+ try {
221
+ return this.db.prepare(`
222
+ SELECT
223
+ COUNT(*) as total,
224
+ SUM(CASE WHEN lifecycle = 'pending' THEN 1 ELSE 0 END) as pending,
225
+ SUM(CASE WHEN lifecycle = 'active' THEN 1 ELSE 0 END) as active,
226
+ SUM(CASE WHEN lifecycle = 'deprecated' THEN 1 ELSE 0 END) as deprecated,
227
+ SUM(CASE WHEN kind = 'rule' THEN 1 ELSE 0 END) as rules,
228
+ SUM(CASE WHEN kind = 'pattern' THEN 1 ELSE 0 END) as patterns,
229
+ SUM(CASE WHEN kind = 'fact' THEN 1 ELSE 0 END) as facts
230
+ FROM knowledge_entries
231
+ `).get();
232
+ } catch (error) {
233
+ this.logger.error('Error getting knowledge stats', { error: error.message });
234
+ throw error;
235
+ }
236
+ }
237
+
238
+ /* ═══ 行 ↔ 实体 映射 ═══════════════════════════════ */
239
+
240
+ /**
241
+ * DB Row → KnowledgeEntry (camelCase 列名 = 属性名,直传)
242
+ * @param {Object} row
243
+ * @returns {KnowledgeEntry}
244
+ */
245
+ _rowToEntity(row) {
246
+ if (!row) return null;
247
+
248
+ return new KnowledgeEntry({
249
+ ...row,
250
+ // JSON 列需要 parse
251
+ lifecycleHistory: this._parseJson(row.lifecycleHistory, []),
252
+ tags: this._parseJson(row.tags, []),
253
+ content: this._parseJson(row.content, {}),
254
+ relations: this._parseJson(row.relations, {}),
255
+ constraints: this._parseJson(row.constraints, {}),
256
+ reasoning: this._parseJson(row.reasoning, {}),
257
+ quality: this._parseJson(row.quality, {}),
258
+ stats: this._parseJson(row.stats, {}),
259
+ headers: this._parseJson(row.headers, []),
260
+ headerPaths: this._parseJson(row.headerPaths, []),
261
+ agentNotes: this._parseJson(row.agentNotes, null),
262
+ // SQLite INTEGER → boolean
263
+ autoApprovable: !!row.autoApprovable,
264
+ includeHeaders: !!row.includeHeaders,
265
+ });
266
+ }
267
+
268
+ /**
269
+ * KnowledgeEntry → DB Row (camelCase 列名 = 属性名,直传)
270
+ * @param {KnowledgeEntry} e
271
+ * @returns {Object}
272
+ */
273
+ _entityToRow(e) {
274
+ const now = Math.floor(Date.now() / 1000);
275
+ return {
276
+ id: e.id,
277
+ title: e.title,
278
+ description: e.description || '',
279
+ lifecycle: e.lifecycle,
280
+ lifecycleHistory: JSON.stringify(e.lifecycleHistory || []),
281
+ autoApprovable: e.autoApprovable ? 1 : 0,
282
+ language: e.language,
283
+ category: e.category,
284
+ kind: e.kind || inferKind(e.knowledgeType),
285
+ knowledgeType: e.knowledgeType || 'code-pattern',
286
+ complexity: e.complexity || 'intermediate',
287
+ scope: e.scope || null,
288
+ difficulty: e.difficulty || null,
289
+ tags: JSON.stringify(e.tags || []),
290
+ trigger: e.trigger || '',
291
+ topicHint: e.topicHint || '',
292
+ whenClause: e.whenClause || '',
293
+ doClause: e.doClause || '',
294
+ dontClause: e.dontClause || '',
295
+ coreCode: e.coreCode || '',
296
+ content: JSON.stringify(typeof e.content?.toJSON === 'function' ? e.content.toJSON() : (e.content || {})),
297
+ relations: JSON.stringify(typeof e.relations?.toJSON === 'function' ? e.relations.toJSON() : (e.relations || {})),
298
+ constraints: JSON.stringify(typeof e.constraints?.toJSON === 'function' ? e.constraints.toJSON() : (e.constraints || {})),
299
+ reasoning: JSON.stringify(typeof e.reasoning?.toJSON === 'function' ? e.reasoning.toJSON() : (e.reasoning || {})),
300
+ quality: JSON.stringify(typeof e.quality?.toJSON === 'function' ? e.quality.toJSON() : (e.quality || {})),
301
+ stats: JSON.stringify(typeof e.stats?.toJSON === 'function' ? e.stats.toJSON() : (e.stats || {})),
302
+ headers: JSON.stringify(e.headers || []),
303
+ headerPaths: JSON.stringify(e.headerPaths || []),
304
+ moduleName: e.moduleName || null,
305
+ includeHeaders: e.includeHeaders ? 1 : 0,
306
+ agentNotes: e.agentNotes ? JSON.stringify(e.agentNotes) : null,
307
+ aiInsight: e.aiInsight || null,
308
+ reviewedBy: e.reviewedBy || null,
309
+ reviewedAt: e.reviewedAt || null,
310
+ rejectionReason: e.rejectionReason || null,
311
+ source: e.source || 'manual',
312
+ sourceFile: e.sourceFile || null,
313
+ sourceCandidateId: e.sourceCandidateId || null,
314
+ createdBy: e.createdBy || 'system',
315
+ createdAt: e.createdAt || now,
316
+ updatedAt: e.updatedAt || now,
317
+ publishedAt: e.publishedAt || null,
318
+ publishedBy: e.publishedBy || null,
319
+ };
320
+ }
321
+
322
+ /**
323
+ * 覆写 BaseRepository 的 _mapRowToEntity
324
+ * @override
325
+ */
326
+ _mapRowToEntity(row) {
327
+ return this._rowToEntity(row);
328
+ }
329
+
330
+ /** @private 安全解析 JSON */
331
+ _parseJson(value, fallback) {
332
+ if (!value || value === 'null') return fallback;
333
+ if (typeof value === 'object') return value;
334
+ try { return JSON.parse(value); } catch { return fallback; }
335
+ }
336
+ }
337
+
338
+ 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,77 @@ 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
+ description: item.summary || item.description || '',
264
+ moduleName: item.moduleName || 'watch-create',
265
+ trigger: item.trigger || '',
266
+ headers: item.headers || [],
267
+ tags: item.tags || [],
268
+ }, context);
255
269
  }
256
270
  return;
257
- } catch {
258
- // ServiceContainer 未初始化
271
+ } catch (err) {
272
+ serviceError = err;
273
+ console.warn('[Watcher] KnowledgeService 创建失败,尝试 HTTP 回退:', err.message);
259
274
  }
260
275
 
261
- // 回退:HTTP API
276
+ // 回退:HTTP API(使用 knowledge 端点而非 candidates)
262
277
  const dashboardUrl = process.env.ASD_DASHBOARD_URL || 'http://localhost:3000';
263
278
  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}`);
279
+ for (const item of validItems) {
280
+ const resp = await fetch(`${dashboardUrl}/api/v1/knowledge`, {
281
+ method: 'POST',
282
+ headers: { 'Content-Type': 'application/json' },
283
+ body: JSON.stringify({
284
+ title: item.title,
285
+ content: { pattern: item.code || '' },
286
+ language: item.language || 'objc',
287
+ category: item.category || 'Utility',
288
+ source: source || 'watch',
289
+ description: item.summary || item.description || '',
290
+ moduleName: item.moduleName || 'watch-create',
291
+ trigger: item.trigger || '',
292
+ headers: item.headers || [],
293
+ }),
294
+ });
295
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
296
+ }
297
+ return;
270
298
  } catch (err) {
271
- console.warn(`[Watcher] 候选提交失败: ${err.message}`);
299
+ console.warn(`[Watcher] HTTP 候选提交也失败: ${err.message}`);
272
300
  }
301
+
302
+ // 两条路径都失败 → 抛出原始错误
303
+ throw serviceError || new Error('候选提交失败:ServiceContainer 和 HTTP 均不可用');
273
304
  }
274
305
 
275
306
  /**