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,267 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { createChildLogger } from '../utils/logger.js';
4
+ import { hashString } from '../utils/hash.js';
5
+ import { createUnifiedDiff } from '../utils/diff.js';
6
+
7
+ const logger = createChildLogger('markdown');
8
+
9
+ /**
10
+ * Markdown Skill 文件操作器
11
+ */
12
+ export class MarkdownSkill {
13
+ private filePath: string;
14
+ private content: string = '';
15
+
16
+ constructor(filePath: string) {
17
+ this.filePath = filePath;
18
+ }
19
+
20
+ /**
21
+ * 读取文件内容
22
+ */
23
+ read(): string {
24
+ if (!existsSync(this.filePath)) {
25
+ throw new Error(`Skill file not found: ${this.filePath}`);
26
+ }
27
+ this.content = readFileSync(this.filePath, 'utf-8');
28
+ return this.content;
29
+ }
30
+
31
+ /**
32
+ * 写入文件内容
33
+ */
34
+ write(content: string): void {
35
+ const dir = dirname(this.filePath);
36
+ if (!existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true });
38
+ }
39
+ writeFileSync(this.filePath, content, 'utf-8');
40
+ this.content = content;
41
+ logger.debug('Skill file written', { path: this.filePath });
42
+ }
43
+
44
+ /**
45
+ * 获取当前内容
46
+ */
47
+ getContent(): string {
48
+ return this.content;
49
+ }
50
+
51
+ /**
52
+ * 获取内容哈希
53
+ */
54
+ getContentHash(): string {
55
+ return hashString(this.content);
56
+ }
57
+
58
+ /**
59
+ * 检查文件是否存在
60
+ */
61
+ exists(): boolean {
62
+ return existsSync(this.filePath);
63
+ }
64
+
65
+ /**
66
+ * 获取文件路径
67
+ */
68
+ getPath(): string {
69
+ return this.filePath;
70
+ }
71
+
72
+ /**
73
+ * 解析 frontmatter
74
+ */
75
+ parseFrontmatter(): Record<string, unknown> {
76
+ const frontmatter: Record<string, unknown> = {};
77
+ const lines = this.content.split('\n');
78
+
79
+ if (lines[0]?.trim() !== '---') {
80
+ return frontmatter;
81
+ }
82
+
83
+ let endIndex = -1;
84
+ for (let i = 1; i < lines.length; i++) {
85
+ if (lines[i]?.trim() === '---') {
86
+ endIndex = i;
87
+ break;
88
+ }
89
+ }
90
+
91
+ if (endIndex === -1) {
92
+ return frontmatter;
93
+ }
94
+
95
+ for (let i = 1; i < endIndex; i++) {
96
+ const line = lines[i];
97
+ const colonIndex = line?.indexOf(':') ?? -1;
98
+ if (colonIndex > 0) {
99
+ const key = line?.substring(0, colonIndex).trim();
100
+ const value = line?.substring(colonIndex + 1).trim();
101
+ if (key) {
102
+ frontmatter[key] = value;
103
+ }
104
+ }
105
+ }
106
+
107
+ return frontmatter;
108
+ }
109
+
110
+ /**
111
+ * 更新 frontmatter
112
+ */
113
+ updateFrontmatter(updates: Record<string, unknown>): string {
114
+ const lines = this.content.split('\n');
115
+
116
+ if (lines[0]?.trim() !== '---') {
117
+ // 没有 frontmatter,添加一个
118
+ const newFrontmatter = Object.entries(updates)
119
+ .map(([k, v]) => `${k}: ${v}`)
120
+ .join('\n');
121
+ const newContent = `---\n${newFrontmatter}\n---\n\n${this.content}`;
122
+ this.write(newContent);
123
+ return newContent;
124
+ }
125
+
126
+ let endIndex = -1;
127
+ for (let i = 1; i < lines.length; i++) {
128
+ if (lines[i]?.trim() === '---') {
129
+ endIndex = i;
130
+ break;
131
+ }
132
+ }
133
+
134
+ if (endIndex === -1) {
135
+ return this.content;
136
+ }
137
+
138
+ // 解析现有 frontmatter
139
+ const existingFrontmatter = this.parseFrontmatter();
140
+ const mergedFrontmatter = { ...existingFrontmatter, ...updates };
141
+
142
+ // 重建 frontmatter
143
+ const newFrontmatterLines = Object.entries(mergedFrontmatter).map(
144
+ ([k, v]) => `${k}: ${v}`
145
+ );
146
+
147
+ const newContent = [
148
+ '---',
149
+ ...newFrontmatterLines,
150
+ '---',
151
+ ...lines.slice(endIndex + 1),
152
+ ].join('\n');
153
+
154
+ this.write(newContent);
155
+ return newContent;
156
+ }
157
+
158
+ /**
159
+ * 查找 section
160
+ */
161
+ findSection(heading: string): { start: number; end: number; content: string } | null {
162
+ const lines = this.content.split('\n');
163
+ const headingPattern = new RegExp(`^#{1,6}\\s+${heading}`, 'i');
164
+
165
+ let start = -1;
166
+ let headingLevel = 0;
167
+
168
+ for (let i = 0; i < lines.length; i++) {
169
+ if (headingPattern.test(lines[i] ?? '')) {
170
+ start = i;
171
+ headingLevel = (lines[i]?.match(/^(#{1,6})/) ?? ['', ''])[1].length;
172
+ break;
173
+ }
174
+ }
175
+
176
+ if (start === -1) {
177
+ return null;
178
+ }
179
+
180
+ // 找到下一个同级或更高级的 heading
181
+ let end = lines.length;
182
+ for (let i = start + 1; i < lines.length; i++) {
183
+ const match = lines[i]?.match(/^(#{1,6})\s/);
184
+ if (match && match[1].length <= headingLevel) {
185
+ end = i;
186
+ break;
187
+ }
188
+ }
189
+
190
+ return {
191
+ start,
192
+ end,
193
+ content: lines.slice(start, end).join('\n'),
194
+ };
195
+ }
196
+
197
+ /**
198
+ * 替换 section 内容
199
+ */
200
+ replaceSection(heading: string, newContent: string): string {
201
+ const section = this.findSection(heading);
202
+ if (!section) {
203
+ logger.warn(`Section not found: ${heading}`);
204
+ return this.content;
205
+ }
206
+
207
+ const lines = this.content.split('\n');
208
+ const newLines = [
209
+ ...lines.slice(0, section.start + 1),
210
+ ...newContent.split('\n'),
211
+ ...lines.slice(section.end),
212
+ ];
213
+
214
+ const result = newLines.join('\n');
215
+ this.write(result);
216
+ return result;
217
+ }
218
+
219
+ /**
220
+ * 在指定 section 后追加内容
221
+ */
222
+ appendAfterSection(heading: string, content: string): string {
223
+ const section = this.findSection(heading);
224
+ if (!section) {
225
+ logger.warn(`Section not found: ${heading}`);
226
+ return this.content;
227
+ }
228
+
229
+ const lines = this.content.split('\n');
230
+ const newLines = [
231
+ ...lines.slice(0, section.end),
232
+ '',
233
+ content,
234
+ '',
235
+ ...lines.slice(section.end),
236
+ ];
237
+
238
+ const result = newLines.join('\n');
239
+ this.write(result);
240
+ return result;
241
+ }
242
+
243
+ /**
244
+ * 生成 diff
245
+ */
246
+ generateDiff(oldContent: string, newContent: string): string {
247
+ return createUnifiedDiff(this.filePath, oldContent, newContent);
248
+ }
249
+
250
+ /**
251
+ * 从 origin 复制
252
+ */
253
+ copyFromOrigin(originPath: string): void {
254
+ if (!existsSync(originPath)) {
255
+ throw new Error(`Origin skill not found: ${originPath}`);
256
+ }
257
+
258
+ const originContent = readFileSync(originPath, 'utf-8');
259
+ this.write(originContent);
260
+ logger.info('Skill copied from origin', { from: originPath, to: this.filePath });
261
+ }
262
+ }
263
+
264
+ // 导出工厂函数
265
+ export function createMarkdownSkill(filePath: string): MarkdownSkill {
266
+ return new MarkdownSkill(filePath);
267
+ }
@@ -0,0 +1,273 @@
1
+ import { createReadStream, createWriteStream, existsSync, mkdirSync, appendFileSync } from 'node:fs';
2
+ import { createInterface } from 'node:readline';
3
+ import { join, dirname } from 'node:path';
4
+ import { createChildLogger } from '../utils/logger.js';
5
+ import type { Trace, EvolutionRecord } from '../types/index.js';
6
+
7
+ const logger = createChildLogger('ndjson');
8
+
9
+ /**
10
+ * NDJSON 读写器
11
+ */
12
+ export class NDJSONReader<T> {
13
+ private filePath: string;
14
+
15
+ constructor(filePath: string) {
16
+ this.filePath = filePath;
17
+ }
18
+
19
+ /**
20
+ * 检查文件是否存在
21
+ */
22
+ exists(): boolean {
23
+ return existsSync(this.filePath);
24
+ }
25
+
26
+ /**
27
+ * 逐行读取并解析
28
+ */
29
+ async *readLines(): AsyncGenerator<T> {
30
+ if (!this.exists()) {
31
+ return;
32
+ }
33
+
34
+ const stream = createReadStream(this.filePath, { encoding: 'utf-8' });
35
+ const rl = createInterface({
36
+ input: stream,
37
+ crlfDelay: Infinity,
38
+ });
39
+
40
+ let lineNumber = 0;
41
+ for await (const line of rl) {
42
+ lineNumber++;
43
+ if (line.trim() === '') continue;
44
+
45
+ try {
46
+ const item = JSON.parse(line) as T;
47
+ yield item;
48
+ } catch (error) {
49
+ logger.warn(`Failed to parse line ${lineNumber}`, { error, line: line.substring(0, 100) });
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 读取所有记录
56
+ */
57
+ async readAll(): Promise<T[]> {
58
+ const records: T[] = [];
59
+ for await (const record of this.readLines()) {
60
+ records.push(record);
61
+ }
62
+ return records;
63
+ }
64
+
65
+ /**
66
+ * 读取最后 N 条记录
67
+ */
68
+ async readLast(n: number): Promise<T[]> {
69
+ const allRecords = await this.readAll();
70
+ return allRecords.slice(-n);
71
+ }
72
+
73
+ /**
74
+ * 按条件过滤
75
+ */
76
+ async *filter(predicate: (item: T) => boolean): AsyncGenerator<T> {
77
+ for await (const item of this.readLines()) {
78
+ if (predicate(item)) {
79
+ yield item;
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 统计记录数
86
+ */
87
+ async count(): Promise<number> {
88
+ let count = 0;
89
+ for await (const _ of this.readLines()) {
90
+ count++;
91
+ }
92
+ return count;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * NDJSON 写入器
98
+ */
99
+ export class NDJSONWriter<T> {
100
+ private filePath: string;
101
+
102
+ constructor(filePath: string) {
103
+ this.filePath = filePath;
104
+ this.ensureDirectory();
105
+ }
106
+
107
+ /**
108
+ * 确保目录存在
109
+ */
110
+ private ensureDirectory(): void {
111
+ const dir = dirname(this.filePath);
112
+ if (!existsSync(dir)) {
113
+ mkdirSync(dir, { recursive: true });
114
+ }
115
+ }
116
+
117
+ /**
118
+ * 追加一条记录
119
+ */
120
+ append(record: T): void {
121
+ const line = JSON.stringify(record) + '\n';
122
+ appendFileSync(this.filePath, line, 'utf-8');
123
+ }
124
+
125
+ /**
126
+ * 批量追加记录
127
+ */
128
+ appendBatch(records: T[]): void {
129
+ const lines = records.map((r) => JSON.stringify(r)).join('\n') + '\n';
130
+ appendFileSync(this.filePath, lines, 'utf-8');
131
+ }
132
+
133
+ /**
134
+ * 清空文件
135
+ */
136
+ clear(): void {
137
+ const stream = createWriteStream(this.filePath, { flags: 'w' });
138
+ stream.end();
139
+ }
140
+
141
+ /**
142
+ * 获取文件路径
143
+ */
144
+ getPath(): string {
145
+ return this.filePath;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Trace NDJSON 存储
151
+ */
152
+ export class TraceStore {
153
+ private writer: NDJSONWriter<Trace>;
154
+ private reader: NDJSONReader<Trace>;
155
+
156
+ constructor(tracesDir: string, sessionId: string) {
157
+ const filePath = join(tracesDir, `${sessionId}.ndjson`);
158
+ this.writer = new NDJSONWriter<Trace>(filePath);
159
+ this.reader = new NDJSONReader<Trace>(filePath);
160
+ }
161
+
162
+ /**
163
+ * 记录 trace
164
+ */
165
+ append(trace: Trace): void {
166
+ this.writer.append(trace);
167
+ logger.debug('Trace appended', { trace_id: trace.trace_id, event_type: trace.event_type });
168
+ }
169
+
170
+ /**
171
+ * 读取所有 traces
172
+ */
173
+ async readAll(): Promise<Trace[]> {
174
+ return this.reader.readAll();
175
+ }
176
+
177
+ /**
178
+ * 读取 session 的 traces
179
+ */
180
+ async readBySession(sessionId: string): Promise<Trace[]> {
181
+ const traces: Trace[] = [];
182
+ for await (const trace of this.reader.filter((t) => t.session_id === sessionId)) {
183
+ traces.push(trace);
184
+ }
185
+ return traces;
186
+ }
187
+
188
+ /**
189
+ * 读取最近的 traces
190
+ */
191
+ async readRecent(count: number): Promise<Trace[]> {
192
+ return this.reader.readLast(count);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Journal NDJSON 存储
198
+ */
199
+ export class JournalStore {
200
+ private writer: NDJSONWriter<EvolutionRecord>;
201
+ private reader: NDJSONReader<EvolutionRecord>;
202
+
203
+ constructor(journalPath: string) {
204
+ this.writer = new NDJSONWriter<EvolutionRecord>(journalPath);
205
+ this.reader = new NDJSONReader<EvolutionRecord>(journalPath);
206
+ }
207
+
208
+ /**
209
+ * 记录演化
210
+ */
211
+ append(record: EvolutionRecord): void {
212
+ this.writer.append(record);
213
+ logger.info('Evolution record appended', {
214
+ shadow_id: record.shadow_id,
215
+ revision: record.revision,
216
+ change_type: record.change_type,
217
+ });
218
+ }
219
+
220
+ /**
221
+ * 读取所有记录
222
+ */
223
+ async readAll(): Promise<EvolutionRecord[]> {
224
+ return this.reader.readAll();
225
+ }
226
+
227
+ /**
228
+ * 读取指定 revision 范围的记录
229
+ */
230
+ async readRange(fromRevision: number, toRevision: number): Promise<EvolutionRecord[]> {
231
+ const records: EvolutionRecord[] = [];
232
+ for await (const record of this.reader.filter(
233
+ (r) => r.revision >= fromRevision && r.revision <= toRevision
234
+ )) {
235
+ records.push(record);
236
+ }
237
+ return records;
238
+ }
239
+
240
+ /**
241
+ * 读取最后 N 条记录
242
+ */
243
+ async readLast(n: number): Promise<EvolutionRecord[]> {
244
+ return this.reader.readLast(n);
245
+ }
246
+
247
+ /**
248
+ * 获取最新 revision
249
+ */
250
+ async getLatestRevision(): Promise<number> {
251
+ const records = await this.reader.readLast(1);
252
+ return records.length > 0 ? records[0].revision : 0;
253
+ }
254
+
255
+ /**
256
+ * 获取指定 revision 的记录
257
+ */
258
+ async getByRevision(revision: number): Promise<EvolutionRecord | null> {
259
+ for await (const record of this.reader.filter((r) => r.revision === revision)) {
260
+ return record;
261
+ }
262
+ return null;
263
+ }
264
+ }
265
+
266
+ // 导出工厂函数
267
+ export function createTraceStore(tracesDir: string, sessionId: string): TraceStore {
268
+ return new TraceStore(tracesDir, sessionId);
269
+ }
270
+
271
+ export function createJournalStore(journalPath: string): JournalStore {
272
+ return new JournalStore(journalPath);
273
+ }