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,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
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Migration 017: Recreate knowledge_entries with camelCase columns
3
+ *
4
+ * 全链路统一 camelCase — DB 列名 = 实体属性名。
5
+ * 新增 Cursor 交付字段列(topicHint, whenClause, doClause, dontClause, coreCode)。
6
+ * 清除旧 snake_case 列名和冗余字段。
7
+ *
8
+ * ⚠️ 破坏性迁移 — 需配合清库重跑 bootstrap 使用。
9
+ */
10
+ export default function migrate(db) {
11
+ // 检查旧表是否存在
12
+ const oldTable = db.prepare(
13
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='knowledge_entries'"
14
+ ).get();
15
+
16
+ if (oldTable) {
17
+ // 检查是否已是新 schema(有 topicHint 列)
18
+ const cols = db.prepare('PRAGMA table_info(knowledge_entries)').all();
19
+ const hasTopicHint = cols.some(c => c.name === 'topicHint');
20
+ if (hasTopicHint) {
21
+ process.stderr.write(' ℹ️ 017: knowledge_entries already has camelCase schema, skipping\n');
22
+ return;
23
+ }
24
+
25
+ // 备份旧表
26
+ db.exec(`ALTER TABLE knowledge_entries RENAME TO _legacy_knowledge_entries_v2;`);
27
+ process.stderr.write(' ℹ️ 017: renamed old knowledge_entries → _legacy_knowledge_entries_v2\n');
28
+ }
29
+
30
+ // 创建新 camelCase schema
31
+ db.exec(`
32
+ CREATE TABLE knowledge_entries (
33
+ id TEXT PRIMARY KEY,
34
+ title TEXT NOT NULL DEFAULT '',
35
+ description TEXT DEFAULT '',
36
+
37
+ lifecycle TEXT NOT NULL DEFAULT 'pending',
38
+ lifecycleHistory TEXT DEFAULT '[]',
39
+ autoApprovable INTEGER DEFAULT 0,
40
+
41
+ language TEXT NOT NULL DEFAULT '',
42
+ category TEXT NOT NULL DEFAULT 'general',
43
+ kind TEXT DEFAULT 'pattern',
44
+ knowledgeType TEXT DEFAULT 'code-pattern',
45
+ complexity TEXT DEFAULT 'intermediate',
46
+ scope TEXT DEFAULT 'universal',
47
+ difficulty TEXT,
48
+ tags TEXT DEFAULT '[]',
49
+
50
+ -- Cursor 交付字段
51
+ trigger TEXT DEFAULT '',
52
+ topicHint TEXT DEFAULT '',
53
+ whenClause TEXT DEFAULT '',
54
+ doClause TEXT DEFAULT '',
55
+ dontClause TEXT DEFAULT '',
56
+ coreCode TEXT DEFAULT '',
57
+
58
+ -- 值对象 (JSON)
59
+ content TEXT DEFAULT '{}',
60
+ relations TEXT DEFAULT '{}',
61
+ constraints TEXT DEFAULT '{}',
62
+ reasoning TEXT DEFAULT '{}',
63
+ quality TEXT DEFAULT '{}',
64
+ stats TEXT DEFAULT '{}',
65
+
66
+ -- ObjC/Swift headers
67
+ headers TEXT DEFAULT '[]',
68
+ headerPaths TEXT DEFAULT '[]',
69
+ moduleName TEXT DEFAULT '',
70
+ includeHeaders INTEGER DEFAULT 0,
71
+
72
+ -- AI notes
73
+ agentNotes TEXT,
74
+ aiInsight TEXT,
75
+
76
+ -- Review
77
+ reviewedBy TEXT,
78
+ reviewedAt INTEGER,
79
+ rejectionReason TEXT,
80
+
81
+ -- Source
82
+ source TEXT DEFAULT 'agent',
83
+ sourceFile TEXT,
84
+ sourceCandidateId TEXT,
85
+
86
+ -- Timestamps
87
+ createdBy TEXT DEFAULT 'agent',
88
+ createdAt INTEGER NOT NULL,
89
+ updatedAt INTEGER NOT NULL,
90
+ publishedAt INTEGER,
91
+ publishedBy TEXT
92
+ );
93
+
94
+ CREATE INDEX idx_ke3_lifecycle ON knowledge_entries(lifecycle);
95
+ CREATE INDEX idx_ke3_language ON knowledge_entries(language);
96
+ CREATE INDEX idx_ke3_category ON knowledge_entries(category);
97
+ CREATE INDEX idx_ke3_kind ON knowledge_entries(kind);
98
+ CREATE INDEX idx_ke3_createdAt ON knowledge_entries(createdAt);
99
+ CREATE INDEX idx_ke3_trigger ON knowledge_entries(trigger);
100
+ CREATE INDEX idx_ke3_title ON knowledge_entries(title);
101
+ CREATE INDEX idx_ke3_source ON knowledge_entries(source);
102
+ CREATE INDEX idx_ke3_guard_active ON knowledge_entries(kind, lifecycle);
103
+ CREATE INDEX idx_ke3_topicHint ON knowledge_entries(topicHint);
104
+ `);
105
+
106
+ process.stderr.write(' ✅ 017: created knowledge_entries with camelCase schema\n');
107
+ }