autosnippet 2.8.3 → 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 (110) hide show
  1. package/README.md +5 -5
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-B_Xg4B-s.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 +9 -27
  11. package/lib/core/ast/ProjectGraph.js +160 -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 +351 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +109 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +125 -0
  19. package/lib/domain/knowledge/values/Content.js +86 -0
  20. package/lib/domain/knowledge/values/Quality.js +93 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  22. package/lib/domain/knowledge/values/Relations.js +168 -0
  23. package/lib/domain/knowledge/values/Stats.js +87 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +48 -0
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
  27. package/lib/external/mcp/McpServer.js +7 -5
  28. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
  29. package/lib/external/mcp/handlers/bootstrap.js +121 -12
  30. package/lib/external/mcp/handlers/browse.js +77 -73
  31. package/lib/external/mcp/handlers/candidate.js +29 -276
  32. package/lib/external/mcp/handlers/guard.js +2 -0
  33. package/lib/external/mcp/handlers/knowledge.js +205 -0
  34. package/lib/external/mcp/handlers/skill.js +4 -2
  35. package/lib/external/mcp/handlers/structure.js +25 -23
  36. package/lib/external/mcp/handlers/system.js +10 -12
  37. package/lib/external/mcp/tools.js +125 -138
  38. package/lib/http/HttpServer.js +4 -8
  39. package/lib/http/middleware/requestLogger.js +3 -3
  40. package/lib/http/routes/ai.js +17 -1
  41. package/lib/http/routes/extract.js +48 -4
  42. package/lib/http/routes/knowledge.js +246 -0
  43. package/lib/http/routes/search.js +12 -17
  44. package/lib/http/routes/skills.js +44 -1
  45. package/lib/infrastructure/cache/GraphCache.js +143 -0
  46. package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
  47. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  48. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  49. package/lib/infrastructure/realtime/RealtimeService.js +14 -2
  50. package/lib/injection/ServiceContainer.js +164 -63
  51. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  52. package/lib/repository/token/TokenUsageStore.js +162 -0
  53. package/lib/service/automation/DirectiveDetector.js +2 -3
  54. package/lib/service/automation/FileWatcher.js +67 -28
  55. package/lib/service/automation/XcodeIntegration.js +931 -156
  56. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  57. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  58. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  59. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  60. package/lib/service/chat/AnalystAgent.js +25 -14
  61. package/lib/service/chat/CandidateGuardrail.js +1 -1
  62. package/lib/service/chat/ChatAgent.js +280 -48
  63. package/lib/service/chat/ContextWindow.js +92 -8
  64. package/lib/service/chat/HandoffProtocol.js +26 -1
  65. package/lib/service/chat/ProducerAgent.js +11 -9
  66. package/lib/service/chat/tools.js +298 -194
  67. package/lib/service/guard/GuardCheckEngine.js +114 -10
  68. package/lib/service/guard/GuardService.js +59 -48
  69. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  70. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  71. package/lib/service/knowledge/KnowledgeService.js +725 -0
  72. package/lib/service/search/SearchEngine.js +92 -19
  73. package/lib/service/skills/SignalCollector.js +15 -9
  74. package/lib/service/skills/SkillAdvisor.js +13 -11
  75. package/lib/service/snippet/SnippetFactory.js +5 -5
  76. package/lib/service/spm/SpmService.js +119 -18
  77. package/package.json +1 -1
  78. package/scripts/install-cursor-skill.js +0 -6
  79. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  80. package/skills/autosnippet-analysis/SKILL.md +15 -7
  81. package/skills/autosnippet-candidates/SKILL.md +6 -6
  82. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  83. package/skills/autosnippet-concepts/SKILL.md +7 -6
  84. package/skills/autosnippet-create/SKILL.md +13 -13
  85. package/skills/autosnippet-intent/SKILL.md +3 -2
  86. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  87. package/skills/autosnippet-recipes/SKILL.md +16 -4
  88. package/templates/constitution.yaml +1 -1
  89. package/templates/copilot-instructions.md +6 -6
  90. package/templates/recipes-setup/README.md +3 -3
  91. package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
  92. package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
  93. package/lib/cli/CandidateSyncService.js +0 -261
  94. package/lib/cli/SyncService.js +0 -356
  95. package/lib/domain/candidate/Candidate.js +0 -196
  96. package/lib/domain/candidate/CandidateRepository.js +0 -107
  97. package/lib/domain/candidate/Reasoning.js +0 -52
  98. package/lib/domain/recipe/Recipe.js +0 -421
  99. package/lib/domain/recipe/RecipeRepository.js +0 -54
  100. package/lib/domain/types/CandidateStatus.js +0 -52
  101. package/lib/http/routes/candidates.js +0 -559
  102. package/lib/http/routes/recipes.js +0 -397
  103. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  104. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  105. package/lib/service/candidate/CandidateAggregator.js +0 -52
  106. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  107. package/lib/service/candidate/CandidateService.js +0 -973
  108. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  109. package/lib/service/recipe/RecipeService.js +0 -786
  110. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -1,498 +0,0 @@
1
- import { BaseRepository } from '../base/BaseRepository.js';
2
- import { Recipe, RecipeStatus, KnowledgeType, Complexity, Kind, inferKind } from '../../domain/index.js';
3
- import Logger from '../../infrastructure/logging/Logger.js';
4
-
5
- /**
6
- * RecipeRepository 实现 — 统一知识实体
7
- * 面向 SQLite 数据库的 Recipe 持久化,支持 content_json / relations_json / constraints_json
8
- */
9
- export class RecipeRepositoryImpl extends BaseRepository {
10
- constructor(database) {
11
- super(database, 'recipes');
12
- this.logger = Logger.getInstance();
13
- }
14
-
15
- /**
16
- * 覆写分页查询 — 支持 _tagLike 特殊过滤
17
- */
18
- async findWithPagination(filters = {}, options = {}) {
19
- // 如果无特殊 filter,走 BaseRepository
20
- if (!filters._tagLike) {
21
- return super.findWithPagination(filters, options);
22
- }
23
-
24
- const { _tagLike, ...normalFilters } = filters;
25
- const { page = 1, pageSize = 20 } = options;
26
- const offset = (page - 1) * pageSize;
27
-
28
- const conditions = [];
29
- const params = [];
30
-
31
- for (const [key, value] of Object.entries(normalFilters)) {
32
- this._assertSafeColumn(key);
33
- conditions.push(`${key} = ?`);
34
- params.push(value);
35
- }
36
-
37
- // tag filter: JSON array 中包含标签名
38
- conditions.push(`tags_json LIKE ?`);
39
- const escaped = _tagLike.replace(/[%_\\]/g, ch => `\\${ch}`);
40
- params.push(`%"${escaped}"%`);
41
-
42
- const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
43
-
44
- const total = this.db.prepare(`SELECT COUNT(*) as count FROM recipes${where}`).get(...params).count;
45
- const data = this.db.prepare(`SELECT * FROM recipes${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`)
46
- .all(...params, pageSize, offset);
47
-
48
- return {
49
- data: data.map(row => this._mapRowToEntity(row)),
50
- pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) },
51
- };
52
- }
53
-
54
- /**
55
- * 创建 Recipe
56
- */
57
- async create(recipe) {
58
- if (!recipe || !recipe.isValid()) {
59
- throw new Error('Invalid recipe entity');
60
- }
61
-
62
- try {
63
- const row = this._mapEntityToRow(recipe);
64
- const keys = Object.keys(row);
65
- const placeholders = keys.map(() => '?').join(', ');
66
- const query = `
67
- INSERT INTO recipes (${keys.join(', ')})
68
- VALUES (${placeholders})
69
- `;
70
-
71
- const stmt = this.db.prepare(query);
72
- stmt.run(...Object.values(row));
73
-
74
- return this.findById(recipe.id);
75
- } catch (error) {
76
- this.logger.error('Error creating recipe', {
77
- recipeId: recipe.id,
78
- error: error.message,
79
- });
80
- throw error;
81
- }
82
- }
83
-
84
- /**
85
- * 根据状态查询
86
- */
87
- async findByStatus(status, { page = 1, pageSize = 20 } = {}) {
88
- if (!Object.values(RecipeStatus).includes(status)) {
89
- throw new Error(`Invalid status: ${status}`);
90
- }
91
-
92
- try {
93
- return this.findWithPagination({ status }, { page, pageSize });
94
- } catch (error) {
95
- this.logger.error('Error finding by status', {
96
- status,
97
- error: error.message,
98
- });
99
- throw error;
100
- }
101
- }
102
-
103
- /**
104
- * 根据编程语言查询
105
- */
106
- async findByLanguage(language, { page = 1, pageSize = 20 } = {}) {
107
- try {
108
- return this.findWithPagination({ language }, { page, pageSize });
109
- } catch (error) {
110
- this.logger.error('Error finding by language', {
111
- language,
112
- error: error.message,
113
- });
114
- throw error;
115
- }
116
- }
117
-
118
- /**
119
- * 根据分类查询
120
- */
121
- async findByCategory(category, { page = 1, pageSize = 20 } = {}) {
122
- try {
123
- return this.findWithPagination({ category }, { page, pageSize });
124
- } catch (error) {
125
- this.logger.error('Error finding by category', {
126
- category,
127
- error: error.message,
128
- });
129
- throw error;
130
- }
131
- }
132
-
133
- /**
134
- * 根据知识类型查询
135
- */
136
- async findByKnowledgeType(knowledgeType, { page = 1, pageSize = 20 } = {}) {
137
- if (!Object.values(KnowledgeType).includes(knowledgeType)) {
138
- throw new Error(`Invalid knowledge type: ${knowledgeType}`);
139
- }
140
-
141
- try {
142
- return this.findWithPagination({ knowledge_type: knowledgeType }, { page, pageSize });
143
- } catch (error) {
144
- this.logger.error('Error finding by knowledge type', {
145
- knowledgeType,
146
- error: error.message,
147
- });
148
- throw error;
149
- }
150
- }
151
-
152
- /**
153
- * 根据 Kind 查询(rule / pattern / fact)
154
- */
155
- async findByKind(kind, { page = 1, pageSize = 20, status } = {}) {
156
- if (!Object.values(Kind).includes(kind)) {
157
- throw new Error(`Invalid kind: ${kind}`);
158
- }
159
- try {
160
- const filters = { kind };
161
- if (status) filters.status = status;
162
- return this.findWithPagination(filters, { page, pageSize });
163
- } catch (error) {
164
- this.logger.error('Error finding by kind', { kind, error: error.message });
165
- throw error;
166
- }
167
- }
168
-
169
- /** 查询所有 Rules (kind='rule') */
170
- async findRules({ page = 1, pageSize = 50, status = 'active' } = {}) {
171
- return this.findByKind(Kind.RULE, { page, pageSize, status });
172
- }
173
-
174
- /** 查询所有 Patterns (kind='pattern') */
175
- async findPatterns({ page = 1, pageSize = 50, status = 'active' } = {}) {
176
- return this.findByKind(Kind.PATTERN, { page, pageSize, status });
177
- }
178
-
179
- /**
180
- * 搜索 Recipe(按标题、内容、约束)
181
- */
182
- async search(keyword, { page = 1, pageSize = 20 } = {}) {
183
- try {
184
- const offset = (page - 1) * pageSize;
185
- // 转义 LIKE 通配符 (% → \%, _ → \_) 防止特殊字符注入
186
- const escaped = keyword.replace(/[%_\\]/g, ch => `\\${ch}`);
187
- const like = `%${escaped}%`;
188
-
189
- const countStmt = this.db.prepare(`
190
- SELECT COUNT(*) as count FROM recipes
191
- WHERE title LIKE ? ESCAPE '\\' OR category LIKE ? ESCAPE '\\' OR content_json LIKE ? ESCAPE '\\' OR constraints_json LIKE ? ESCAPE '\\'
192
- OR tags_json LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\'
193
- `);
194
- const total = countStmt.get(like, like, like, like, like, like, like).count;
195
-
196
- const stmt = this.db.prepare(`
197
- SELECT * FROM recipes
198
- WHERE title LIKE ? ESCAPE '\\' OR category LIKE ? ESCAPE '\\' OR content_json LIKE ? ESCAPE '\\' OR constraints_json LIKE ? ESCAPE '\\'
199
- OR tags_json LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR trigger LIKE ? ESCAPE '\\'
200
- ORDER BY created_at DESC
201
- LIMIT ? OFFSET ?
202
- `);
203
- const data = stmt.all(like, like, like, like, like, like, like, pageSize, offset);
204
-
205
- return {
206
- data: data.map((row) => this._mapRowToEntity(row)),
207
- pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) },
208
- };
209
- } catch (error) {
210
- this.logger.error('Error searching recipes', { keyword, error: error.message });
211
- throw error;
212
- }
213
- }
214
-
215
- /**
216
- * 按 scope 查询
217
- */
218
- async findByScope(scope, { page = 1, pageSize = 20 } = {}) {
219
- try {
220
- return this.findWithPagination({ scope }, { page, pageSize });
221
- } catch (error) {
222
- this.logger.error('Error finding by scope', { scope, error: error.message });
223
- throw error;
224
- }
225
- }
226
-
227
- /**
228
- * 查询与指定 Recipe 有关系的所有 Recipes
229
- * 基于 relations_json 中的 target 字段做正向查找 + 被引用的反向查找
230
- */
231
- async findRelated(recipeId) {
232
- try {
233
- // 正向: 当前 Recipe 的 relations_json 引用了哪些
234
- const self = await this.findById(recipeId);
235
- if (!self) return [];
236
-
237
- const targetIds = new Set();
238
- const rels = self.relations || {};
239
- for (const group of Object.values(rels)) {
240
- if (Array.isArray(group)) {
241
- for (const r of group) {
242
- if (r.target) targetIds.add(r.target);
243
- }
244
- }
245
- }
246
-
247
- // 反向: 哪些 Recipe 的 relations_json 引用了当前 recipeId
248
- const reverseStmt = this.db.prepare(
249
- `SELECT * FROM recipes WHERE relations_json LIKE ? AND id != ?`
250
- );
251
- const reverseRows = reverseStmt.all(`%${recipeId}%`, recipeId);
252
- for (const row of reverseRows) targetIds.add(row.id);
253
-
254
- if (targetIds.size === 0) return [];
255
-
256
- const ids = [...targetIds];
257
- const placeholders = ids.map(() => '?').join(', ');
258
- const stmt = this.db.prepare(`SELECT * FROM recipes WHERE id IN (${placeholders})`);
259
- return stmt.all(...ids).map(row => this._mapRowToEntity(row));
260
- } catch (error) {
261
- this.logger.error('Error finding related recipes', { recipeId, error: error.message });
262
- throw error;
263
- }
264
- }
265
-
266
- /**
267
- * 查询包含 Guard 约束的 Recipes(用于 Guard 引擎)
268
- * 筛选 constraints_json 中有 guards 数组且语言匹配的条目
269
- */
270
- async findWithGuards(language = null) {
271
- try {
272
- let query = `SELECT * FROM recipes WHERE kind = 'rule' AND status = ?`;
273
- const params = [RecipeStatus.ACTIVE];
274
-
275
- if (language) {
276
- query += ` AND language = ?`;
277
- params.push(language);
278
- }
279
-
280
- const stmt = this.db.prepare(query);
281
- const rows = stmt.all(...params);
282
- return rows
283
- .map(row => this._mapRowToEntity(row))
284
- .filter(r => r.constraints?.guards?.length > 0);
285
- } catch (error) {
286
- this.logger.error('Error finding recipes with guards', { language, error: error.message });
287
- throw error;
288
- }
289
- }
290
-
291
- /**
292
- * 获取推荐 Recipe(最高质量最高采用率)
293
- */
294
- async getRecommendations(limit = 10) {
295
- try {
296
- const stmt = this.db.prepare(`
297
- SELECT * FROM recipes
298
- WHERE status = ?
299
- ORDER BY (
300
- COALESCE(quality_overall, 0) * 0.5 +
301
- MIN(COALESCE(adoption_count, 0) * 1.0 / 100.0, 1.0) * 0.3 +
302
- MIN(COALESCE(application_count, 0) * 1.0 / 100.0, 1.0) * 0.2
303
- ) DESC
304
- LIMIT ?
305
- `);
306
- const rows = stmt.all(RecipeStatus.ACTIVE, limit);
307
- return rows.map((row) => this._mapRowToEntity(row));
308
- } catch (error) {
309
- this.logger.error('Error getting recommendations', {
310
- error: error.message,
311
- });
312
- throw error;
313
- }
314
- }
315
-
316
- /**
317
- * 获取统计信息
318
- */
319
- async getStats() {
320
- try {
321
- const stmt = this.db.prepare(`
322
- SELECT
323
- COUNT(*) as total,
324
- SUM(CASE WHEN status = 'draft' THEN 1 ELSE 0 END) as draft,
325
- SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,
326
- SUM(CASE WHEN status = 'deprecated' THEN 1 ELSE 0 END) as deprecated,
327
- SUM(CASE WHEN kind = 'rule' THEN 1 ELSE 0 END) as rules,
328
- SUM(CASE WHEN kind = 'pattern' THEN 1 ELSE 0 END) as patterns,
329
- SUM(CASE WHEN kind = 'fact' THEN 1 ELSE 0 END) as facts,
330
- AVG(quality_overall) as avg_quality,
331
- AVG(adoption_count) as avg_adoption,
332
- AVG(application_count) as avg_application
333
- FROM recipes
334
- `); return stmt.get();
335
- } catch (error) {
336
- this.logger.error('Error getting recipe stats', {
337
- error: error.message,
338
- });
339
- throw error;
340
- }
341
- }
342
-
343
- /**
344
- * 映射 SQLite 行到 Recipe 实体(统一模型)
345
- */
346
- _mapRowToEntity(row) {
347
- if (!row) return null;
348
-
349
- // 解析新 JSON 列(migration 003+)
350
- const contentJson = this._parseJson(row.content_json, {});
351
- const relationsJson = this._parseJson(row.relations_json, {});
352
- const constraintsJson = this._parseJson(row.constraints_json, {});
353
-
354
- return new Recipe({
355
- id: row.id,
356
- title: row.title,
357
- description: row.description,
358
- language: row.language,
359
- category: row.category,
360
- summaryCn: row.summary_cn || '',
361
- summaryEn: row.summary_en || '',
362
- usageGuideCn: row.usage_guide_cn || '',
363
- usageGuideEn: row.usage_guide_en || '',
364
-
365
- kind: row.kind || inferKind(row.knowledge_type),
366
- knowledgeType: row.knowledge_type,
367
- complexity: row.complexity,
368
- scope: row.scope,
369
-
370
- // 内容
371
- content: {
372
- pattern: contentJson.pattern || '',
373
- rationale: contentJson.rationale || '',
374
- steps: contentJson.steps || [],
375
- codeChanges: contentJson.codeChanges || [],
376
- verification: contentJson.verification || null,
377
- markdown: contentJson.markdown || '',
378
- },
379
-
380
- // 关系图
381
- relations: {
382
- inherits: relationsJson.inherits || [],
383
- implements: relationsJson.implements || [],
384
- calls: relationsJson.calls || [],
385
- dependsOn: relationsJson.dependsOn || [],
386
- dataFlow: relationsJson.dataFlow || [],
387
- conflicts: relationsJson.conflicts || [],
388
- extends: relationsJson.extends || [],
389
- related: relationsJson.related || [],
390
- },
391
-
392
- // 约束
393
- constraints: {
394
- boundaries: constraintsJson.boundaries || [],
395
- preconditions: constraintsJson.preconditions || [],
396
- sideEffects: constraintsJson.sideEffects || [],
397
- guards: constraintsJson.guards || [],
398
- },
399
-
400
- quality: {
401
- codeCompleteness: row.quality_code_completeness,
402
- projectAdaptation: row.quality_project_adaptation,
403
- documentationClarity: row.quality_documentation_clarity,
404
- overall: row.quality_overall,
405
- },
406
-
407
- trigger: row.trigger || '',
408
- dimensions: this._parseJson(row.dimensions_json, {}),
409
- tags: this._parseJson(row.tags_json, []),
410
-
411
- statistics: {
412
- adoptionCount: row.adoption_count || 0,
413
- applicationCount: row.application_count || 0,
414
- guardHitCount: row.guard_hit_count || 0,
415
- viewCount: row.view_count || 0,
416
- successCount: row.success_count || 0,
417
- feedbackScore: row.feedback_score || 0,
418
- },
419
-
420
- status: row.status,
421
- createdBy: row.created_by,
422
- createdAt: row.created_at,
423
- updatedAt: row.updated_at,
424
- publishedBy: row.published_by,
425
- publishedAt: row.published_at,
426
- deprecation: row.deprecation_reason ? {
427
- reason: row.deprecation_reason,
428
- deprecatedAt: row.deprecated_at,
429
- } : null,
430
- sourceCandidate: row.source_candidate_id,
431
- sourceFile: row.source_file,
432
- });
433
- }
434
-
435
- /**
436
- * 映射 Recipe 实体到 SQLite 行(统一模型)
437
- */
438
- _mapEntityToRow(entity) {
439
- const now = Math.floor(Date.now() / 1000);
440
-
441
- return {
442
- id: entity.id,
443
- title: entity.title,
444
- description: entity.description || null,
445
- language: entity.language,
446
- category: entity.category,
447
- summary_cn: entity.summaryCn || null,
448
- summary_en: entity.summaryEn || null,
449
- usage_guide_cn: entity.usageGuideCn || null,
450
- usage_guide_en: entity.usageGuideEn || null,
451
-
452
- knowledge_type: entity.knowledgeType,
453
- kind: entity.kind || inferKind(entity.knowledgeType),
454
- complexity: entity.complexity,
455
- scope: entity.scope || null,
456
- source_file: entity.sourceFile || null,
457
-
458
- // 新 JSON 列
459
- content_json: JSON.stringify(entity.content || {}),
460
- relations_json: JSON.stringify(entity.relations || {}),
461
- constraints_json: JSON.stringify(entity.constraints || {}),
462
-
463
- quality_code_completeness: entity.quality.codeCompleteness,
464
- quality_project_adaptation: entity.quality.projectAdaptation,
465
- quality_documentation_clarity: entity.quality.documentationClarity,
466
- quality_overall: entity.quality.overall,
467
-
468
- trigger: entity.trigger || '',
469
- dimensions_json: JSON.stringify(entity.dimensions || {}),
470
- tags_json: JSON.stringify(entity.tags || []),
471
-
472
- adoption_count: entity.statistics.adoptionCount,
473
- application_count: entity.statistics.applicationCount,
474
- guard_hit_count: entity.statistics.guardHitCount,
475
- view_count: entity.statistics.viewCount || 0,
476
- success_count: entity.statistics.successCount || 0,
477
- feedback_score: entity.statistics.feedbackScore || 0,
478
-
479
- status: entity.status,
480
- created_by: entity.createdBy,
481
- created_at: entity.createdAt || now,
482
- updated_at: entity.updatedAt || now,
483
- published_by: entity.publishedBy,
484
- published_at: entity.publishedAt,
485
- deprecation_reason: entity.deprecation?.reason || null,
486
- deprecated_at: entity.deprecation?.deprecatedAt || null,
487
- source_candidate_id: entity.sourceCandidate,
488
- };
489
- }
490
-
491
- /** @private 安全解析 JSON */
492
- _parseJson(value, fallback) {
493
- if (!value || value === 'null') return fallback;
494
- try { return JSON.parse(value); } catch { return fallback; }
495
- }
496
- }
497
-
498
- export default RecipeRepositoryImpl;
@@ -1,52 +0,0 @@
1
- /**
2
- * CandidateAggregator — 候选去重与聚合
3
- * 对批量提交的候选进行标题相似度去重,合并重复项
4
- */
5
-
6
- /**
7
- * 简易字符串相似度(Jaccard on words)
8
- */
9
- function wordSimilarity(a, b) {
10
- if (!a || !b) return 0;
11
- const setA = new Set(a.toLowerCase().split(/\s+/));
12
- const setB = new Set(b.toLowerCase().split(/\s+/));
13
- let intersection = 0;
14
- for (const w of setA) {
15
- if (setB.has(w)) intersection++;
16
- }
17
- const union = setA.size + setB.size - intersection;
18
- return union === 0 ? 0 : intersection / union;
19
- }
20
-
21
- /**
22
- * 对候选数组去重聚合
23
- * @param {Array<object>} items - 候选数组
24
- * @param {object} [opts] - { titleThreshold: 0.8 }
25
- * @returns {{ items: Array<object>, removed: number }}
26
- */
27
- export function aggregateCandidates(items, opts = {}) {
28
- if (!Array.isArray(items) || items.length === 0) return { items: [], removed: 0 };
29
-
30
- const threshold = opts.titleThreshold ?? 0.8;
31
- const unique = [];
32
- const seen = [];
33
-
34
- for (const item of items) {
35
- const title = item.title || '';
36
- let isDup = false;
37
- for (const existing of seen) {
38
- if (wordSimilarity(title, existing) >= threshold) {
39
- isDup = true;
40
- break;
41
- }
42
- }
43
- if (!isDup) {
44
- unique.push(item);
45
- seen.push(title);
46
- }
47
- }
48
-
49
- return { items: unique, removed: items.length - unique.length };
50
- }
51
-
52
- export default { aggregateCandidates };