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
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Migration 016: Create knowledge_entries unified table
3
+ *
4
+ * 合并 candidates + recipes 为单表 knowledge_entries。
5
+ * 所有字段统一命名(snake_case),消除 metadata_json 袋子模式。
6
+ *
7
+ * 数据迁移策略:
8
+ * 1. 创建 knowledge_entries 表
9
+ * 2. 从 recipes 迁移数据 → lifecycle = active/deprecated/draft
10
+ * 3. 从 candidates 迁移数据 → lifecycle = 原 status 映射
11
+ * 4. 更新 knowledge_edges 的 from_type/to_type
12
+ * 5. 旧表重命名为 _legacy_* (保留回滚能力)
13
+ */
14
+ export default function migrate(db) {
15
+ // 1. 检查是否已存在
16
+ const existing = db.prepare(
17
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='knowledge_entries'"
18
+ ).get();
19
+
20
+ if (existing) {
21
+ process.stderr.write(' ℹ️ 016: knowledge_entries already exists, skipping\n');
22
+ return;
23
+ }
24
+
25
+ // 2. 建表
26
+ db.exec(`
27
+ CREATE TABLE knowledge_entries (
28
+ id TEXT PRIMARY KEY,
29
+ title TEXT NOT NULL DEFAULT '',
30
+ trigger_key TEXT DEFAULT '',
31
+ description TEXT DEFAULT '',
32
+
33
+ lifecycle TEXT NOT NULL DEFAULT 'draft',
34
+ lifecycle_history TEXT DEFAULT '[]',
35
+ probation INTEGER DEFAULT 0,
36
+
37
+ language TEXT NOT NULL DEFAULT '',
38
+ category TEXT NOT NULL DEFAULT '',
39
+ kind TEXT DEFAULT 'pattern',
40
+ knowledge_type TEXT DEFAULT 'code-pattern',
41
+ complexity TEXT DEFAULT 'intermediate',
42
+ scope TEXT DEFAULT 'universal',
43
+ difficulty TEXT,
44
+ tags TEXT DEFAULT '[]',
45
+
46
+ summary_cn TEXT DEFAULT '',
47
+ summary_en TEXT DEFAULT '',
48
+ usage_guide_cn TEXT DEFAULT '',
49
+ usage_guide_en TEXT DEFAULT '',
50
+
51
+ content TEXT DEFAULT '{}',
52
+ relations TEXT DEFAULT '{}',
53
+ constraints TEXT DEFAULT '{}',
54
+ reasoning TEXT DEFAULT '{}',
55
+ quality TEXT DEFAULT '{}',
56
+ stats TEXT DEFAULT '{}',
57
+
58
+ headers TEXT DEFAULT '[]',
59
+ header_paths TEXT DEFAULT '[]',
60
+ module_name TEXT DEFAULT '',
61
+ include_headers INTEGER DEFAULT 0,
62
+
63
+ agent_notes TEXT,
64
+ ai_insight TEXT,
65
+
66
+ reviewed_by TEXT,
67
+ reviewed_at INTEGER,
68
+ rejection_reason TEXT,
69
+
70
+ source TEXT DEFAULT 'manual',
71
+ source_file TEXT,
72
+ source_candidate_id TEXT,
73
+
74
+ created_by TEXT DEFAULT 'system',
75
+ created_at INTEGER NOT NULL,
76
+ updated_at INTEGER NOT NULL,
77
+ published_at INTEGER,
78
+ published_by TEXT,
79
+
80
+ content_hash TEXT
81
+ );
82
+
83
+ CREATE INDEX idx_ke2_lifecycle ON knowledge_entries(lifecycle);
84
+ CREATE INDEX idx_ke2_language ON knowledge_entries(language);
85
+ CREATE INDEX idx_ke2_category ON knowledge_entries(category);
86
+ CREATE INDEX idx_ke2_kind ON knowledge_entries(kind);
87
+ CREATE INDEX idx_ke2_knowledge_type ON knowledge_entries(knowledge_type);
88
+ CREATE INDEX idx_ke2_created_at ON knowledge_entries(created_at);
89
+ CREATE INDEX idx_ke2_trigger ON knowledge_entries(trigger_key);
90
+ CREATE INDEX idx_ke2_title ON knowledge_entries(title);
91
+ CREATE INDEX idx_ke2_source ON knowledge_entries(source);
92
+ CREATE INDEX idx_ke2_guard_active ON knowledge_entries(kind, lifecycle);
93
+ `);
94
+
95
+ const now = Math.floor(Date.now() / 1000);
96
+ let recipeCount = 0;
97
+ let candidateCount = 0;
98
+
99
+ // 3. 迁移 recipes → knowledge_entries
100
+ try {
101
+ const hasRecipes = db.prepare(
102
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='recipes'"
103
+ ).get();
104
+
105
+ if (hasRecipes) {
106
+ const recipes = db.prepare('SELECT * FROM recipes').all();
107
+
108
+ const insertStmt = db.prepare(`
109
+ INSERT OR IGNORE INTO knowledge_entries (
110
+ id, title, trigger_key, description,
111
+ lifecycle, lifecycle_history, probation,
112
+ language, category, kind, knowledge_type, complexity, scope, difficulty, tags,
113
+ summary_cn, summary_en, usage_guide_cn, usage_guide_en,
114
+ content, relations, constraints, reasoning, quality, stats,
115
+ headers, header_paths, module_name, include_headers,
116
+ source, source_file, source_candidate_id,
117
+ created_by, created_at, updated_at, published_at, published_by,
118
+ reviewed_by, reviewed_at
119
+ ) VALUES (
120
+ ?, ?, ?, ?,
121
+ ?, ?, ?,
122
+ ?, ?, ?, ?, ?, ?, ?, ?,
123
+ ?, ?, ?, ?,
124
+ ?, ?, ?, ?, ?, ?,
125
+ ?, ?, ?, ?,
126
+ ?, ?, ?,
127
+ ?, ?, ?, ?, ?,
128
+ ?, ?
129
+ )
130
+ `);
131
+
132
+ for (const r of recipes) {
133
+ const lifecycle = r.status === 'active' ? 'active'
134
+ : r.status === 'deprecated' ? 'deprecated'
135
+ : 'draft';
136
+
137
+ const dims = _json(r.dimensions_json, {});
138
+ const quality = JSON.stringify({
139
+ completeness: r.quality_code_completeness || 0,
140
+ adaptation: r.quality_project_adaptation || 0,
141
+ documentation: r.quality_documentation_clarity || 0,
142
+ overall: r.quality_overall || 0,
143
+ grade: _calcGrade(r.quality_overall || 0),
144
+ });
145
+ const stats = JSON.stringify({
146
+ views: r.view_count || 0,
147
+ adoptions: r.adoption_count || 0,
148
+ applications: r.application_count || 0,
149
+ guard_hits: r.guard_hit_count || 0,
150
+ search_hits: 0,
151
+ authority: Math.min((r.quality_overall || 0) * 5, 5),
152
+ });
153
+
154
+ insertStmt.run(
155
+ r.id,
156
+ r.title || '',
157
+ r.trigger || '',
158
+ r.description || '',
159
+ lifecycle,
160
+ '[]',
161
+ 0,
162
+ r.language || '',
163
+ r.category || '',
164
+ r.kind || _inferKind(r.knowledge_type),
165
+ r.knowledge_type || 'code-pattern',
166
+ r.complexity || 'intermediate',
167
+ r.scope || 'universal',
168
+ dims.difficulty || null,
169
+ r.tags_json || '[]',
170
+ r.summary_cn || '',
171
+ r.summary_en || '',
172
+ r.usage_guide_cn || '',
173
+ r.usage_guide_en || '',
174
+ r.content_json || '{}',
175
+ r.relations_json || '{}',
176
+ r.constraints_json || '{}',
177
+ '{}', // reasoning (recipes 没有)
178
+ quality,
179
+ stats,
180
+ JSON.stringify(dims.headers || []),
181
+ '[]',
182
+ '',
183
+ 0,
184
+ 'migration',
185
+ r.source_file || null,
186
+ r.source_candidate_id || null,
187
+ r.created_by || 'system',
188
+ r.created_at || now,
189
+ r.updated_at || now,
190
+ r.published_at || null,
191
+ r.published_by || null,
192
+ null, // reviewed_by
193
+ null, // reviewed_at
194
+ );
195
+ recipeCount++;
196
+ }
197
+ }
198
+ } catch (err) {
199
+ process.stderr.write(` ⚠️ 016: Recipe migration error: ${err.message}\n`);
200
+ }
201
+
202
+ // 4. 迁移 candidates → knowledge_entries
203
+ try {
204
+ const hasCandidates = db.prepare(
205
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='candidates'"
206
+ ).get();
207
+
208
+ if (hasCandidates) {
209
+ const candidates = db.prepare('SELECT * FROM candidates').all();
210
+
211
+ const insertStmt = db.prepare(`
212
+ INSERT OR IGNORE INTO knowledge_entries (
213
+ id, title, trigger_key, description,
214
+ lifecycle, lifecycle_history, probation,
215
+ language, category, kind, knowledge_type, complexity, scope, difficulty, tags,
216
+ summary_cn, summary_en, usage_guide_cn, usage_guide_en,
217
+ content, relations, constraints, reasoning, quality, stats,
218
+ headers, header_paths, module_name, include_headers,
219
+ source, source_file, source_candidate_id,
220
+ created_by, created_at, updated_at,
221
+ reviewed_by, reviewed_at, rejection_reason
222
+ ) VALUES (
223
+ ?, ?, ?, ?,
224
+ ?, ?, ?,
225
+ ?, ?, ?, ?, ?, ?, ?, ?,
226
+ ?, ?, ?, ?,
227
+ ?, ?, ?, ?, ?, ?,
228
+ ?, ?, ?, ?,
229
+ ?, ?, ?,
230
+ ?, ?, ?,
231
+ ?, ?, ?
232
+ )
233
+ `);
234
+
235
+ for (const c of candidates) {
236
+ const meta = _json(c.metadata_json, {});
237
+ const reasoning = _json(c.reasoning_json, {});
238
+
239
+ // 状态映射: applied → active, 其余 1:1
240
+ const lifecycle = c.status === 'applied' ? 'active' : (c.status || 'pending');
241
+
242
+ // 判断内容类型
243
+ const code = c.code || '';
244
+ const isMarkdown = code && (
245
+ code.includes('— 项目特写') || /^#{1,3}\s/.test(code.trimStart())
246
+ );
247
+
248
+ const content = JSON.stringify({
249
+ pattern: isMarkdown ? '' : code,
250
+ markdown: isMarkdown ? code : '',
251
+ rationale: meta.rationale || reasoning.whyStandard || '',
252
+ steps: meta.steps || [],
253
+ code_changes: meta.codeChanges || [],
254
+ verification: meta.verification || null,
255
+ });
256
+
257
+ // relations: 旧 candidate 可能是扁平数组
258
+ let relations = '{}';
259
+ if (meta.relations) {
260
+ if (Array.isArray(meta.relations)) {
261
+ const buckets = {};
262
+ for (const rel of meta.relations) {
263
+ const bucket = rel.type || 'related';
264
+ if (!buckets[bucket]) buckets[bucket] = [];
265
+ buckets[bucket].push({
266
+ target: rel.target || '',
267
+ description: rel.description || '',
268
+ });
269
+ }
270
+ relations = JSON.stringify(buckets);
271
+ } else {
272
+ relations = JSON.stringify(meta.relations);
273
+ }
274
+ }
275
+
276
+ const reasoningJson = JSON.stringify({
277
+ why_standard: reasoning.whyStandard || '',
278
+ sources: reasoning.sources || [],
279
+ confidence: reasoning.confidence ?? 0.7,
280
+ quality_signals: reasoning.qualitySignals || {},
281
+ alternatives: reasoning.alternatives || [],
282
+ });
283
+
284
+ insertStmt.run(
285
+ c.id,
286
+ meta.title || code.substring(0, 60) || '',
287
+ meta.trigger || '',
288
+ meta.description || '',
289
+ lifecycle,
290
+ c.status_history_json || '[]',
291
+ 0,
292
+ c.language || '',
293
+ meta.category || c.category || 'general',
294
+ _inferKind(meta.knowledgeType),
295
+ meta.knowledgeType || 'code-pattern',
296
+ meta.complexity || 'intermediate',
297
+ meta.scope || 'universal',
298
+ null, // difficulty
299
+ JSON.stringify(meta.tags || []),
300
+ meta.summary || meta.summary_cn || '',
301
+ meta.summary_en || '',
302
+ meta.usageGuide || meta.usageGuide_cn || '',
303
+ meta.usageGuide_en || '',
304
+ content,
305
+ relations,
306
+ JSON.stringify(meta.constraints || {}),
307
+ reasoningJson,
308
+ JSON.stringify(meta.quality || {}),
309
+ '{}', // stats
310
+ JSON.stringify(meta.headers || []),
311
+ '[]',
312
+ '',
313
+ 0,
314
+ c.source || 'manual',
315
+ meta.sourceFile || null,
316
+ null,
317
+ c.created_by || 'system',
318
+ c.created_at || now,
319
+ c.updated_at || now,
320
+ c.approved_by || c.rejected_by || null,
321
+ c.approved_at || null,
322
+ c.rejection_reason || null,
323
+ );
324
+ candidateCount++;
325
+ }
326
+ }
327
+ } catch (err) {
328
+ process.stderr.write(` ⚠️ 016: Candidate migration error: ${err.message}\n`);
329
+ }
330
+
331
+ // 5. 更新 knowledge_edges 的 type 字段
332
+ try {
333
+ const hasEdges = db.prepare(
334
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='knowledge_edges'"
335
+ ).get();
336
+ if (hasEdges) {
337
+ db.exec(`
338
+ UPDATE knowledge_edges SET from_type = 'knowledge_entry' WHERE from_type = 'recipe';
339
+ UPDATE knowledge_edges SET to_type = 'knowledge_entry' WHERE to_type = 'recipe';
340
+ `);
341
+ }
342
+ } catch (err) {
343
+ process.stderr.write(` ⚠️ 016: knowledge_edges update error: ${err.message}\n`);
344
+ }
345
+
346
+ // 6. 重命名旧表(不删除以保留回滚能力)
347
+ try {
348
+ const hasRecipes = db.prepare(
349
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='recipes'"
350
+ ).get();
351
+ if (hasRecipes) {
352
+ db.exec(`ALTER TABLE recipes RENAME TO _legacy_recipes`);
353
+ }
354
+ } catch { /* already renamed or doesn't exist */ }
355
+
356
+ try {
357
+ const hasCandidates = db.prepare(
358
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='candidates'"
359
+ ).get();
360
+ if (hasCandidates) {
361
+ db.exec(`ALTER TABLE candidates RENAME TO _legacy_candidates`);
362
+ }
363
+ } catch { /* already renamed or doesn't exist */ }
364
+
365
+ process.stderr.write(
366
+ ` ✅ 016_unified_knowledge_entries: Created table, migrated ${recipeCount} recipes + ${candidateCount} candidates\n`
367
+ );
368
+ }
369
+
370
+ /* ── 迁移辅助函数 ── */
371
+
372
+ function _json(str, fallback) {
373
+ if (!str) return fallback;
374
+ try { return JSON.parse(str); } catch { return fallback; }
375
+ }
376
+
377
+ function _inferKind(knowledgeType) {
378
+ const map = {
379
+ 'code-standard': 'rule', 'code-style': 'rule', 'best-practice': 'rule',
380
+ 'boundary-constraint': 'rule',
381
+ 'code-pattern': 'pattern', 'architecture': 'pattern', 'solution': 'pattern',
382
+ 'anti-pattern': 'pattern',
383
+ 'code-relation': 'fact', 'inheritance': 'fact', 'call-chain': 'fact',
384
+ 'data-flow': 'fact', 'module-dependency': 'fact',
385
+ };
386
+ return map[knowledgeType] || 'pattern';
387
+ }
388
+
389
+ function _calcGrade(score) {
390
+ if (score >= 0.9) return 'A';
391
+ if (score >= 0.75) return 'B';
392
+ if (score >= 0.6) return 'C';
393
+ if (score >= 0.4) return 'D';
394
+ return 'F';
395
+ }