evo-skills 0.1.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 (188) hide show
  1. package/.eslintrc.json +36 -0
  2. package/.prettierrc +11 -0
  3. package/README.md +227 -0
  4. package/USER-GUIDE.md +382 -0
  5. package/dist/cli/commands/diff.d.ts +7 -0
  6. package/dist/cli/commands/diff.d.ts.map +1 -0
  7. package/dist/cli/commands/diff.js +126 -0
  8. package/dist/cli/commands/diff.js.map +1 -0
  9. package/dist/cli/commands/freeze.d.ts +12 -0
  10. package/dist/cli/commands/freeze.d.ts.map +1 -0
  11. package/dist/cli/commands/freeze.js +93 -0
  12. package/dist/cli/commands/freeze.js.map +1 -0
  13. package/dist/cli/commands/log.d.ts +7 -0
  14. package/dist/cli/commands/log.d.ts.map +1 -0
  15. package/dist/cli/commands/log.js +76 -0
  16. package/dist/cli/commands/log.js.map +1 -0
  17. package/dist/cli/commands/rollback.d.ts +7 -0
  18. package/dist/cli/commands/rollback.d.ts.map +1 -0
  19. package/dist/cli/commands/rollback.js +92 -0
  20. package/dist/cli/commands/rollback.js.map +1 -0
  21. package/dist/cli/commands/status.d.ts +7 -0
  22. package/dist/cli/commands/status.d.ts.map +1 -0
  23. package/dist/cli/commands/status.js +88 -0
  24. package/dist/cli/commands/status.js.map +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +25 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/config/defaults.d.ts +6 -0
  30. package/dist/config/defaults.d.ts.map +1 -0
  31. package/dist/config/defaults.js +31 -0
  32. package/dist/config/defaults.js.map +1 -0
  33. package/dist/config/index.d.ts +68 -0
  34. package/dist/config/index.d.ts.map +1 -0
  35. package/dist/config/index.js +195 -0
  36. package/dist/config/index.js.map +1 -0
  37. package/dist/core/evaluator/base-rule.d.ts +50 -0
  38. package/dist/core/evaluator/base-rule.d.ts.map +1 -0
  39. package/dist/core/evaluator/base-rule.js +69 -0
  40. package/dist/core/evaluator/base-rule.js.map +1 -0
  41. package/dist/core/evaluator/index.d.ts +33 -0
  42. package/dist/core/evaluator/index.d.ts.map +1 -0
  43. package/dist/core/evaluator/index.js +74 -0
  44. package/dist/core/evaluator/index.js.map +1 -0
  45. package/dist/core/evaluator/rules/repeated-drift.d.ts +19 -0
  46. package/dist/core/evaluator/rules/repeated-drift.d.ts.map +1 -0
  47. package/dist/core/evaluator/rules/repeated-drift.js +82 -0
  48. package/dist/core/evaluator/rules/repeated-drift.js.map +1 -0
  49. package/dist/core/evaluator/rules/repeated-manual-fix.d.ts +23 -0
  50. package/dist/core/evaluator/rules/repeated-manual-fix.d.ts.map +1 -0
  51. package/dist/core/evaluator/rules/repeated-manual-fix.js +97 -0
  52. package/dist/core/evaluator/rules/repeated-manual-fix.js.map +1 -0
  53. package/dist/core/journal/index.d.ts +76 -0
  54. package/dist/core/journal/index.d.ts.map +1 -0
  55. package/dist/core/journal/index.js +207 -0
  56. package/dist/core/journal/index.js.map +1 -0
  57. package/dist/core/observer/base-observer.d.ts +64 -0
  58. package/dist/core/observer/base-observer.d.ts.map +1 -0
  59. package/dist/core/observer/base-observer.js +142 -0
  60. package/dist/core/observer/base-observer.js.map +1 -0
  61. package/dist/core/observer/codex-observer.d.ts +71 -0
  62. package/dist/core/observer/codex-observer.d.ts.map +1 -0
  63. package/dist/core/observer/codex-observer.js +248 -0
  64. package/dist/core/observer/codex-observer.js.map +1 -0
  65. package/dist/core/observer/index.d.ts +7 -0
  66. package/dist/core/observer/index.d.ts.map +1 -0
  67. package/dist/core/observer/index.js +7 -0
  68. package/dist/core/observer/index.js.map +1 -0
  69. package/dist/core/observer/trace-manager.d.ts +82 -0
  70. package/dist/core/observer/trace-manager.d.ts.map +1 -0
  71. package/dist/core/observer/trace-manager.js +196 -0
  72. package/dist/core/observer/trace-manager.js.map +1 -0
  73. package/dist/core/origin-registry/index.d.ts +59 -0
  74. package/dist/core/origin-registry/index.d.ts.map +1 -0
  75. package/dist/core/origin-registry/index.js +235 -0
  76. package/dist/core/origin-registry/index.js.map +1 -0
  77. package/dist/core/patch-generator/base-strategy.d.ts +35 -0
  78. package/dist/core/patch-generator/base-strategy.d.ts.map +1 -0
  79. package/dist/core/patch-generator/base-strategy.js +55 -0
  80. package/dist/core/patch-generator/base-strategy.js.map +1 -0
  81. package/dist/core/patch-generator/index.d.ts +32 -0
  82. package/dist/core/patch-generator/index.d.ts.map +1 -0
  83. package/dist/core/patch-generator/index.js +101 -0
  84. package/dist/core/patch-generator/index.js.map +1 -0
  85. package/dist/core/patch-generator/strategies/add-fallback.d.ts +11 -0
  86. package/dist/core/patch-generator/strategies/add-fallback.d.ts.map +1 -0
  87. package/dist/core/patch-generator/strategies/add-fallback.js +57 -0
  88. package/dist/core/patch-generator/strategies/add-fallback.js.map +1 -0
  89. package/dist/core/patch-generator/strategies/prune-noise.d.ts +11 -0
  90. package/dist/core/patch-generator/strategies/prune-noise.d.ts.map +1 -0
  91. package/dist/core/patch-generator/strategies/prune-noise.js +48 -0
  92. package/dist/core/patch-generator/strategies/prune-noise.js.map +1 -0
  93. package/dist/core/shadow-manager/index.d.ts +65 -0
  94. package/dist/core/shadow-manager/index.d.ts.map +1 -0
  95. package/dist/core/shadow-manager/index.js +229 -0
  96. package/dist/core/shadow-manager/index.js.map +1 -0
  97. package/dist/core/shadow-registry/index.d.ts +68 -0
  98. package/dist/core/shadow-registry/index.d.ts.map +1 -0
  99. package/dist/core/shadow-registry/index.js +173 -0
  100. package/dist/core/shadow-registry/index.js.map +1 -0
  101. package/dist/daemon/index.d.ts +43 -0
  102. package/dist/daemon/index.d.ts.map +1 -0
  103. package/dist/daemon/index.js +144 -0
  104. package/dist/daemon/index.js.map +1 -0
  105. package/dist/storage/index.d.ts +7 -0
  106. package/dist/storage/index.d.ts.map +1 -0
  107. package/dist/storage/index.js +7 -0
  108. package/dist/storage/index.js.map +1 -0
  109. package/dist/storage/markdown.d.ts +66 -0
  110. package/dist/storage/markdown.d.ts.map +1 -0
  111. package/dist/storage/markdown.js +227 -0
  112. package/dist/storage/markdown.js.map +1 -0
  113. package/dist/storage/ndjson.d.ts +118 -0
  114. package/dist/storage/ndjson.d.ts.map +1 -0
  115. package/dist/storage/ndjson.js +238 -0
  116. package/dist/storage/ndjson.js.map +1 -0
  117. package/dist/storage/sqlite.d.ts +131 -0
  118. package/dist/storage/sqlite.d.ts.map +1 -0
  119. package/dist/storage/sqlite.js +415 -0
  120. package/dist/storage/sqlite.js.map +1 -0
  121. package/dist/types/index.d.ts +158 -0
  122. package/dist/types/index.d.ts.map +1 -0
  123. package/dist/types/index.js +5 -0
  124. package/dist/types/index.js.map +1 -0
  125. package/dist/utils/diff.d.ts +20 -0
  126. package/dist/utils/diff.d.ts.map +1 -0
  127. package/dist/utils/diff.js +47 -0
  128. package/dist/utils/diff.js.map +1 -0
  129. package/dist/utils/hash.d.ts +17 -0
  130. package/dist/utils/hash.d.ts.map +1 -0
  131. package/dist/utils/hash.js +28 -0
  132. package/dist/utils/hash.js.map +1 -0
  133. package/dist/utils/index.d.ts +8 -0
  134. package/dist/utils/index.d.ts.map +1 -0
  135. package/dist/utils/index.js +8 -0
  136. package/dist/utils/index.js.map +1 -0
  137. package/dist/utils/logger.d.ts +11 -0
  138. package/dist/utils/logger.d.ts.map +1 -0
  139. package/dist/utils/logger.js +64 -0
  140. package/dist/utils/logger.js.map +1 -0
  141. package/dist/utils/path.d.ts +67 -0
  142. package/dist/utils/path.d.ts.map +1 -0
  143. package/dist/utils/path.js +115 -0
  144. package/dist/utils/path.js.map +1 -0
  145. package/docs/ENGINEERING_PLAN.md +1065 -0
  146. package/docs/PRD.md +1004 -0
  147. package/docs/PROGRESS.md +523 -0
  148. package/package.json +60 -0
  149. package/src/cli/commands/diff.ts +158 -0
  150. package/src/cli/commands/freeze.ts +108 -0
  151. package/src/cli/commands/log.ts +87 -0
  152. package/src/cli/commands/rollback.ts +99 -0
  153. package/src/cli/commands/status.ts +101 -0
  154. package/src/cli/index.ts +31 -0
  155. package/src/config/defaults.ts +32 -0
  156. package/src/config/index.ts +220 -0
  157. package/src/core/evaluator/base-rule.ts +90 -0
  158. package/src/core/evaluator/index.ts +86 -0
  159. package/src/core/evaluator/rules/repeated-drift.ts +109 -0
  160. package/src/core/evaluator/rules/repeated-manual-fix.ts +129 -0
  161. package/src/core/journal/index.ts +286 -0
  162. package/src/core/observer/base-observer.ts +200 -0
  163. package/src/core/observer/codex-observer.ts +309 -0
  164. package/src/core/observer/index.ts +7 -0
  165. package/src/core/observer/trace-manager.ts +250 -0
  166. package/src/core/origin-registry/index.ts +264 -0
  167. package/src/core/patch-generator/base-strategy.ts +67 -0
  168. package/src/core/patch-generator/index.ts +118 -0
  169. package/src/core/patch-generator/strategies/add-fallback.ts +69 -0
  170. package/src/core/patch-generator/strategies/prune-noise.ts +65 -0
  171. package/src/core/shadow-manager/index.ts +278 -0
  172. package/src/core/shadow-registry/index.ts +200 -0
  173. package/src/daemon/index.ts +174 -0
  174. package/src/storage/index.ts +7 -0
  175. package/src/storage/markdown.ts +267 -0
  176. package/src/storage/ndjson.ts +273 -0
  177. package/src/storage/sqlite.ts +555 -0
  178. package/src/types/index.ts +212 -0
  179. package/src/utils/diff.ts +60 -0
  180. package/src/utils/hash.ts +31 -0
  181. package/src/utils/index.ts +8 -0
  182. package/src/utils/logger.ts +78 -0
  183. package/src/utils/path.ts +129 -0
  184. package/tests/unit/config.test.ts +65 -0
  185. package/tests/unit/path.test.ts +89 -0
  186. package/tests/unit/utils.test.ts +45 -0
  187. package/tsconfig.json +31 -0
  188. package/vitest.config.ts +19 -0
@@ -0,0 +1,555 @@
1
+ import initSqlJs, { type Database } from 'sql.js';
2
+ import { join } from 'node:path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { createChildLogger } from '../utils/logger.js';
5
+ import type { ProjectSkillShadow, ShadowStatus, Session, RuntimeType } from '../types/index.js';
6
+
7
+ const logger = createChildLogger('sqlite');
8
+
9
+ /**
10
+ * SQLite 存储管理器
11
+ */
12
+ export class SQLiteStorage {
13
+ private db: Database | null = null;
14
+ private dbPath: string;
15
+
16
+ constructor(dbPath: string) {
17
+ this.dbPath = dbPath;
18
+ }
19
+
20
+ /**
21
+ * 初始化数据库
22
+ */
23
+ async init(): Promise<void> {
24
+ // 确保目录存在
25
+ const dir = join(this.dbPath, '..');
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+
30
+ // 初始化 sql.js
31
+ const SQL = await initSqlJs();
32
+
33
+ // 尝试加载现有数据库
34
+ if (existsSync(this.dbPath)) {
35
+ const buffer = readFileSync(this.dbPath);
36
+ this.db = new SQL.Database(buffer);
37
+ } else {
38
+ this.db = new SQL.Database();
39
+ }
40
+
41
+ this.createTables();
42
+ this.save();
43
+ logger.info('Database initialized', { path: this.dbPath });
44
+ }
45
+
46
+ /**
47
+ * 保存数据库到文件
48
+ */
49
+ private save(): void {
50
+ if (!this.db) throw new Error('Database not initialized');
51
+ const data = this.db.export();
52
+ const buffer = Buffer.from(data);
53
+ writeFileSync(this.dbPath, buffer);
54
+ }
55
+
56
+ /**
57
+ * 创建表结构
58
+ */
59
+ private createTables(): void {
60
+ if (!this.db) throw new Error('Database not initialized');
61
+
62
+ this.db.run(`
63
+ -- Shadow Skills 表
64
+ CREATE TABLE IF NOT EXISTS shadow_skills (
65
+ shadow_id TEXT PRIMARY KEY,
66
+ project_id TEXT NOT NULL,
67
+ skill_id TEXT NOT NULL,
68
+ origin_skill_id TEXT NOT NULL,
69
+ origin_version_at_fork TEXT NOT NULL,
70
+ shadow_path TEXT NOT NULL,
71
+ current_revision INTEGER DEFAULT 0,
72
+ status TEXT DEFAULT 'active',
73
+ created_at TEXT NOT NULL,
74
+ last_optimized_at TEXT,
75
+ hit_count INTEGER DEFAULT 0,
76
+ success_count INTEGER DEFAULT 0,
77
+ manual_override_count INTEGER DEFAULT 0,
78
+ health_score REAL DEFAULT 100.0,
79
+ UNIQUE(project_id, skill_id)
80
+ );
81
+ `);
82
+
83
+ this.db.run(`
84
+ CREATE TABLE IF NOT EXISTS evolution_records_index (
85
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
86
+ shadow_id TEXT NOT NULL,
87
+ revision INTEGER NOT NULL,
88
+ timestamp TEXT NOT NULL,
89
+ change_type TEXT NOT NULL,
90
+ source_sessions TEXT,
91
+ confidence REAL,
92
+ FOREIGN KEY (shadow_id) REFERENCES shadow_skills(shadow_id)
93
+ );
94
+ `);
95
+
96
+ this.db.run(`
97
+ CREATE TABLE IF NOT EXISTS snapshots (
98
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
99
+ shadow_id TEXT NOT NULL,
100
+ revision INTEGER NOT NULL,
101
+ timestamp TEXT NOT NULL,
102
+ file_path TEXT NOT NULL,
103
+ content_hash TEXT NOT NULL,
104
+ FOREIGN KEY (shadow_id) REFERENCES shadow_skills(shadow_id)
105
+ );
106
+ `);
107
+
108
+ this.db.run(`
109
+ CREATE TABLE IF NOT EXISTS sessions (
110
+ session_id TEXT PRIMARY KEY,
111
+ runtime TEXT NOT NULL,
112
+ project_id TEXT,
113
+ started_at TEXT NOT NULL,
114
+ ended_at TEXT,
115
+ trace_count INTEGER DEFAULT 0
116
+ );
117
+ `);
118
+
119
+ this.db.run(`
120
+ CREATE TABLE IF NOT EXISTS traces_index (
121
+ trace_id TEXT PRIMARY KEY,
122
+ session_id TEXT NOT NULL,
123
+ runtime TEXT NOT NULL,
124
+ event_type TEXT NOT NULL,
125
+ timestamp TEXT NOT NULL,
126
+ status TEXT,
127
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id)
128
+ );
129
+ `);
130
+
131
+ // 创建索引
132
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_shadow_project ON shadow_skills(project_id);');
133
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_evolution_shadow ON evolution_records_index(shadow_id);');
134
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_traces_session ON traces_index(session_id);');
135
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_traces_timestamp ON traces_index(timestamp);');
136
+ }
137
+
138
+ /**
139
+ * 关闭数据库连接
140
+ */
141
+ close(): void {
142
+ if (this.db) {
143
+ this.save();
144
+ this.db.close();
145
+ this.db = null;
146
+ logger.info('Database closed');
147
+ }
148
+ }
149
+
150
+ // ==================== Shadow Skills ====================
151
+
152
+ /**
153
+ * 插入或更新 shadow skill
154
+ */
155
+ upsertShadowSkill(shadow: ProjectSkillShadow): void {
156
+ if (!this.db) throw new Error('Database not initialized');
157
+
158
+ this.db.run(
159
+ `INSERT OR REPLACE INTO shadow_skills (
160
+ shadow_id, project_id, skill_id, origin_skill_id, origin_version_at_fork,
161
+ shadow_path, current_revision, status, created_at, last_optimized_at,
162
+ hit_count, success_count, manual_override_count, health_score
163
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
164
+ [
165
+ shadow.shadow_id,
166
+ shadow.project_id,
167
+ shadow.skill_id,
168
+ shadow.origin_skill_id,
169
+ shadow.origin_version_at_fork,
170
+ shadow.shadow_path,
171
+ shadow.current_revision,
172
+ shadow.status,
173
+ shadow.created_at,
174
+ shadow.last_optimized_at,
175
+ 0,
176
+ 0,
177
+ 0,
178
+ 100.0,
179
+ ]
180
+ );
181
+ this.save();
182
+ }
183
+
184
+ /**
185
+ * 获取 shadow skill
186
+ */
187
+ getShadowSkill(projectId: string, skillId: string): ProjectSkillShadow | null {
188
+ if (!this.db) throw new Error('Database not initialized');
189
+
190
+ const stmt = this.db.prepare(
191
+ 'SELECT * FROM shadow_skills WHERE project_id = ? AND skill_id = ?'
192
+ );
193
+ stmt.bind([projectId, skillId]);
194
+
195
+ if (!stmt.step()) {
196
+ stmt.free();
197
+ return null;
198
+ }
199
+
200
+ const row = stmt.getAsObject();
201
+ stmt.free();
202
+
203
+ return {
204
+ project_id: row.project_id as string,
205
+ skill_id: row.skill_id as string,
206
+ shadow_id: row.shadow_id as string,
207
+ origin_skill_id: row.origin_skill_id as string,
208
+ origin_version_at_fork: row.origin_version_at_fork as string,
209
+ shadow_path: row.shadow_path as string,
210
+ current_revision: row.current_revision as number,
211
+ status: row.status as ShadowStatus,
212
+ created_at: row.created_at as string,
213
+ last_optimized_at: row.last_optimized_at as string,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * 列出项目的所有 shadow skills
219
+ */
220
+ listShadowSkills(projectId: string): ProjectSkillShadow[] {
221
+ if (!this.db) throw new Error('Database not initialized');
222
+
223
+ const stmt = this.db.prepare(
224
+ 'SELECT * FROM shadow_skills WHERE project_id = ? ORDER BY created_at DESC'
225
+ );
226
+ stmt.bind([projectId]);
227
+
228
+ const results: ProjectSkillShadow[] = [];
229
+ while (stmt.step()) {
230
+ const row = stmt.getAsObject();
231
+ results.push({
232
+ project_id: row.project_id as string,
233
+ skill_id: row.skill_id as string,
234
+ shadow_id: row.shadow_id as string,
235
+ origin_skill_id: row.origin_skill_id as string,
236
+ origin_version_at_fork: row.origin_version_at_fork as string,
237
+ shadow_path: row.shadow_path as string,
238
+ current_revision: row.current_revision as number,
239
+ status: row.status as ShadowStatus,
240
+ created_at: row.created_at as string,
241
+ last_optimized_at: row.last_optimized_at as string,
242
+ });
243
+ }
244
+ stmt.free();
245
+
246
+ return results;
247
+ }
248
+
249
+ /**
250
+ * 更新 shadow skill 状态
251
+ */
252
+ updateShadowStatus(shadowId: string, status: ShadowStatus): void {
253
+ if (!this.db) throw new Error('Database not initialized');
254
+ this.db.run('UPDATE shadow_skills SET status = ? WHERE shadow_id = ?', [status, shadowId]);
255
+ this.save();
256
+ }
257
+
258
+ /**
259
+ * 更新 shadow skill revision
260
+ */
261
+ updateShadowRevision(shadowId: string, revision: number): void {
262
+ if (!this.db) throw new Error('Database not initialized');
263
+ this.db.run('UPDATE shadow_skills SET current_revision = ? WHERE shadow_id = ?', [revision, shadowId]);
264
+ this.save();
265
+ }
266
+
267
+ // ==================== Sessions ====================
268
+
269
+ /**
270
+ * 创建 session
271
+ */
272
+ createSession(session: Session): void {
273
+ if (!this.db) throw new Error('Database not initialized');
274
+
275
+ this.db.run(
276
+ 'INSERT INTO sessions (session_id, runtime, project_id, started_at, ended_at, trace_count) VALUES (?, ?, ?, ?, ?, ?)',
277
+ [
278
+ session.session_id,
279
+ session.runtime,
280
+ session.project_id,
281
+ session.started_at,
282
+ session.ended_at,
283
+ session.trace_count,
284
+ ]
285
+ );
286
+ this.save();
287
+ }
288
+
289
+ /**
290
+ * 获取 session
291
+ */
292
+ getSession(sessionId: string): Session | null {
293
+ if (!this.db) throw new Error('Database not initialized');
294
+
295
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE session_id = ?');
296
+ stmt.bind([sessionId]);
297
+
298
+ if (!stmt.step()) {
299
+ stmt.free();
300
+ return null;
301
+ }
302
+
303
+ const row = stmt.getAsObject();
304
+ stmt.free();
305
+
306
+ return {
307
+ session_id: row.session_id as string,
308
+ runtime: row.runtime as RuntimeType,
309
+ project_id: row.project_id as string | null,
310
+ started_at: row.started_at as string,
311
+ ended_at: row.ended_at as string | null,
312
+ trace_count: row.trace_count as number,
313
+ };
314
+ }
315
+
316
+ /**
317
+ * 更新 session trace 计数
318
+ */
319
+ incrementSessionTraceCount(sessionId: string): void {
320
+ if (!this.db) throw new Error('Database not initialized');
321
+ this.db.run('UPDATE sessions SET trace_count = trace_count + 1 WHERE session_id = ?', [sessionId]);
322
+ this.save();
323
+ }
324
+
325
+ // ==================== Traces Index ====================
326
+
327
+ /**
328
+ * 添加 trace 索引记录
329
+ */
330
+ addTraceIndex(trace: {
331
+ trace_id: string;
332
+ session_id: string;
333
+ runtime: RuntimeType;
334
+ event_type: string;
335
+ timestamp: string;
336
+ status: string;
337
+ }): void {
338
+ if (!this.db) throw new Error('Database not initialized');
339
+
340
+ this.db.run(
341
+ 'INSERT INTO traces_index (trace_id, session_id, runtime, event_type, timestamp, status) VALUES (?, ?, ?, ?, ?, ?)',
342
+ [trace.trace_id, trace.session_id, trace.runtime, trace.event_type, trace.timestamp, trace.status]
343
+ );
344
+ this.save();
345
+ }
346
+
347
+ /**
348
+ * 获取 session 的 trace 索引
349
+ */
350
+ getTraceIndexBySession(sessionId: string, limit = 100): Array<{
351
+ trace_id: string;
352
+ event_type: string;
353
+ timestamp: string;
354
+ status: string;
355
+ }> {
356
+ if (!this.db) throw new Error('Database not initialized');
357
+
358
+ const stmt = this.db.prepare(
359
+ 'SELECT trace_id, event_type, timestamp, status FROM traces_index WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?'
360
+ );
361
+ stmt.bind([sessionId, limit]);
362
+
363
+ const results: Array<{
364
+ trace_id: string;
365
+ event_type: string;
366
+ timestamp: string;
367
+ status: string;
368
+ }> = [];
369
+
370
+ while (stmt.step()) {
371
+ const row = stmt.getAsObject();
372
+ results.push({
373
+ trace_id: row.trace_id as string,
374
+ event_type: row.event_type as string,
375
+ timestamp: row.timestamp as string,
376
+ status: row.status as string,
377
+ });
378
+ }
379
+ stmt.free();
380
+
381
+ return results;
382
+ }
383
+
384
+ // ==================== Evolution Records Index ====================
385
+
386
+ /**
387
+ * 添加演化记录索引
388
+ */
389
+ addEvolutionRecordIndex(record: {
390
+ shadow_id: string;
391
+ revision: number;
392
+ timestamp: string;
393
+ change_type: string;
394
+ source_sessions: string[];
395
+ confidence: number;
396
+ }): void {
397
+ if (!this.db) throw new Error('Database not initialized');
398
+
399
+ this.db.run(
400
+ 'INSERT INTO evolution_records_index (shadow_id, revision, timestamp, change_type, source_sessions, confidence) VALUES (?, ?, ?, ?, ?, ?)',
401
+ [
402
+ record.shadow_id,
403
+ record.revision,
404
+ record.timestamp,
405
+ record.change_type,
406
+ JSON.stringify(record.source_sessions),
407
+ record.confidence,
408
+ ]
409
+ );
410
+ this.save();
411
+ }
412
+
413
+ /**
414
+ * 获取 shadow 的演化记录索引
415
+ */
416
+ getEvolutionRecordIndex(shadowId: string, limit = 50): Array<{
417
+ id: number;
418
+ revision: number;
419
+ timestamp: string;
420
+ change_type: string;
421
+ source_sessions: string[];
422
+ confidence: number;
423
+ }> {
424
+ if (!this.db) throw new Error('Database not initialized');
425
+
426
+ const stmt = this.db.prepare(
427
+ 'SELECT * FROM evolution_records_index WHERE shadow_id = ? ORDER BY revision DESC LIMIT ?'
428
+ );
429
+ stmt.bind([shadowId, limit]);
430
+
431
+ const results: Array<{
432
+ id: number;
433
+ revision: number;
434
+ timestamp: string;
435
+ change_type: string;
436
+ source_sessions: string[];
437
+ confidence: number;
438
+ }> = [];
439
+
440
+ while (stmt.step()) {
441
+ const row = stmt.getAsObject();
442
+ results.push({
443
+ id: row.id as number,
444
+ revision: row.revision as number,
445
+ timestamp: row.timestamp as string,
446
+ change_type: row.change_type as string,
447
+ source_sessions: JSON.parse(row.source_sessions as string) as string[],
448
+ confidence: row.confidence as number,
449
+ });
450
+ }
451
+ stmt.free();
452
+
453
+ return results;
454
+ }
455
+
456
+ // ==================== Snapshots ====================
457
+
458
+ /**
459
+ * 添加 snapshot 记录
460
+ */
461
+ addSnapshot(snapshot: {
462
+ shadow_id: string;
463
+ revision: number;
464
+ timestamp: string;
465
+ file_path: string;
466
+ content_hash: string;
467
+ }): void {
468
+ if (!this.db) throw new Error('Database not initialized');
469
+
470
+ this.db.run(
471
+ 'INSERT INTO snapshots (shadow_id, revision, timestamp, file_path, content_hash) VALUES (?, ?, ?, ?, ?)',
472
+ [snapshot.shadow_id, snapshot.revision, snapshot.timestamp, snapshot.file_path, snapshot.content_hash]
473
+ );
474
+ this.save();
475
+ }
476
+
477
+ /**
478
+ * 获取 shadow 的 snapshots
479
+ */
480
+ getSnapshots(shadowId: string): Array<{
481
+ id: number;
482
+ revision: number;
483
+ timestamp: string;
484
+ file_path: string;
485
+ content_hash: string;
486
+ }> {
487
+ if (!this.db) throw new Error('Database not initialized');
488
+
489
+ const stmt = this.db.prepare(
490
+ 'SELECT * FROM snapshots WHERE shadow_id = ? ORDER BY revision DESC'
491
+ );
492
+ stmt.bind([shadowId]);
493
+
494
+ const results: Array<{
495
+ id: number;
496
+ revision: number;
497
+ timestamp: string;
498
+ file_path: string;
499
+ content_hash: string;
500
+ }> = [];
501
+
502
+ while (stmt.step()) {
503
+ const row = stmt.getAsObject();
504
+ results.push({
505
+ id: row.id as number,
506
+ revision: row.revision as number,
507
+ timestamp: row.timestamp as string,
508
+ file_path: row.file_path as string,
509
+ content_hash: row.content_hash as string,
510
+ });
511
+ }
512
+ stmt.free();
513
+
514
+ return results;
515
+ }
516
+
517
+ /**
518
+ * 获取最新的 snapshot
519
+ */
520
+ getLatestSnapshot(shadowId: string): {
521
+ id: number;
522
+ revision: number;
523
+ timestamp: string;
524
+ file_path: string;
525
+ content_hash: string;
526
+ } | null {
527
+ if (!this.db) throw new Error('Database not initialized');
528
+
529
+ const stmt = this.db.prepare(
530
+ 'SELECT * FROM snapshots WHERE shadow_id = ? ORDER BY revision DESC LIMIT 1'
531
+ );
532
+ stmt.bind([shadowId]);
533
+
534
+ if (!stmt.step()) {
535
+ stmt.free();
536
+ return null;
537
+ }
538
+
539
+ const row = stmt.getAsObject();
540
+ stmt.free();
541
+
542
+ return {
543
+ id: row.id as number,
544
+ revision: row.revision as number,
545
+ timestamp: row.timestamp as string,
546
+ file_path: row.file_path as string,
547
+ content_hash: row.content_hash as string,
548
+ };
549
+ }
550
+ }
551
+
552
+ // 导出工厂函数
553
+ export function createSQLiteStorage(dbPath: string): SQLiteStorage {
554
+ return new SQLiteStorage(dbPath);
555
+ }