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.
- package/.eslintrc.json +36 -0
- package/.prettierrc +11 -0
- package/README.md +227 -0
- package/USER-GUIDE.md +382 -0
- package/dist/cli/commands/diff.d.ts +7 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/diff.js +126 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/freeze.d.ts +12 -0
- package/dist/cli/commands/freeze.d.ts.map +1 -0
- package/dist/cli/commands/freeze.js +93 -0
- package/dist/cli/commands/freeze.js.map +1 -0
- package/dist/cli/commands/log.d.ts +7 -0
- package/dist/cli/commands/log.d.ts.map +1 -0
- package/dist/cli/commands/log.js +76 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/rollback.d.ts +7 -0
- package/dist/cli/commands/rollback.d.ts.map +1 -0
- package/dist/cli/commands/rollback.js +92 -0
- package/dist/cli/commands/rollback.js.map +1 -0
- package/dist/cli/commands/status.d.ts +7 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +88 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +31 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +68 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +195 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/evaluator/base-rule.d.ts +50 -0
- package/dist/core/evaluator/base-rule.d.ts.map +1 -0
- package/dist/core/evaluator/base-rule.js +69 -0
- package/dist/core/evaluator/base-rule.js.map +1 -0
- package/dist/core/evaluator/index.d.ts +33 -0
- package/dist/core/evaluator/index.d.ts.map +1 -0
- package/dist/core/evaluator/index.js +74 -0
- package/dist/core/evaluator/index.js.map +1 -0
- package/dist/core/evaluator/rules/repeated-drift.d.ts +19 -0
- package/dist/core/evaluator/rules/repeated-drift.d.ts.map +1 -0
- package/dist/core/evaluator/rules/repeated-drift.js +82 -0
- package/dist/core/evaluator/rules/repeated-drift.js.map +1 -0
- package/dist/core/evaluator/rules/repeated-manual-fix.d.ts +23 -0
- package/dist/core/evaluator/rules/repeated-manual-fix.d.ts.map +1 -0
- package/dist/core/evaluator/rules/repeated-manual-fix.js +97 -0
- package/dist/core/evaluator/rules/repeated-manual-fix.js.map +1 -0
- package/dist/core/journal/index.d.ts +76 -0
- package/dist/core/journal/index.d.ts.map +1 -0
- package/dist/core/journal/index.js +207 -0
- package/dist/core/journal/index.js.map +1 -0
- package/dist/core/observer/base-observer.d.ts +64 -0
- package/dist/core/observer/base-observer.d.ts.map +1 -0
- package/dist/core/observer/base-observer.js +142 -0
- package/dist/core/observer/base-observer.js.map +1 -0
- package/dist/core/observer/codex-observer.d.ts +71 -0
- package/dist/core/observer/codex-observer.d.ts.map +1 -0
- package/dist/core/observer/codex-observer.js +248 -0
- package/dist/core/observer/codex-observer.js.map +1 -0
- package/dist/core/observer/index.d.ts +7 -0
- package/dist/core/observer/index.d.ts.map +1 -0
- package/dist/core/observer/index.js +7 -0
- package/dist/core/observer/index.js.map +1 -0
- package/dist/core/observer/trace-manager.d.ts +82 -0
- package/dist/core/observer/trace-manager.d.ts.map +1 -0
- package/dist/core/observer/trace-manager.js +196 -0
- package/dist/core/observer/trace-manager.js.map +1 -0
- package/dist/core/origin-registry/index.d.ts +59 -0
- package/dist/core/origin-registry/index.d.ts.map +1 -0
- package/dist/core/origin-registry/index.js +235 -0
- package/dist/core/origin-registry/index.js.map +1 -0
- package/dist/core/patch-generator/base-strategy.d.ts +35 -0
- package/dist/core/patch-generator/base-strategy.d.ts.map +1 -0
- package/dist/core/patch-generator/base-strategy.js +55 -0
- package/dist/core/patch-generator/base-strategy.js.map +1 -0
- package/dist/core/patch-generator/index.d.ts +32 -0
- package/dist/core/patch-generator/index.d.ts.map +1 -0
- package/dist/core/patch-generator/index.js +101 -0
- package/dist/core/patch-generator/index.js.map +1 -0
- package/dist/core/patch-generator/strategies/add-fallback.d.ts +11 -0
- package/dist/core/patch-generator/strategies/add-fallback.d.ts.map +1 -0
- package/dist/core/patch-generator/strategies/add-fallback.js +57 -0
- package/dist/core/patch-generator/strategies/add-fallback.js.map +1 -0
- package/dist/core/patch-generator/strategies/prune-noise.d.ts +11 -0
- package/dist/core/patch-generator/strategies/prune-noise.d.ts.map +1 -0
- package/dist/core/patch-generator/strategies/prune-noise.js +48 -0
- package/dist/core/patch-generator/strategies/prune-noise.js.map +1 -0
- package/dist/core/shadow-manager/index.d.ts +65 -0
- package/dist/core/shadow-manager/index.d.ts.map +1 -0
- package/dist/core/shadow-manager/index.js +229 -0
- package/dist/core/shadow-manager/index.js.map +1 -0
- package/dist/core/shadow-registry/index.d.ts +68 -0
- package/dist/core/shadow-registry/index.d.ts.map +1 -0
- package/dist/core/shadow-registry/index.js +173 -0
- package/dist/core/shadow-registry/index.js.map +1 -0
- package/dist/daemon/index.d.ts +43 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +144 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/markdown.d.ts +66 -0
- package/dist/storage/markdown.d.ts.map +1 -0
- package/dist/storage/markdown.js +227 -0
- package/dist/storage/markdown.js.map +1 -0
- package/dist/storage/ndjson.d.ts +118 -0
- package/dist/storage/ndjson.d.ts.map +1 -0
- package/dist/storage/ndjson.js +238 -0
- package/dist/storage/ndjson.js.map +1 -0
- package/dist/storage/sqlite.d.ts +131 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +415 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/types/index.d.ts +158 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/diff.d.ts +20 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +47 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/hash.d.ts +17 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +28 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +64 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path.d.ts +67 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +115 -0
- package/dist/utils/path.js.map +1 -0
- package/docs/ENGINEERING_PLAN.md +1065 -0
- package/docs/PRD.md +1004 -0
- package/docs/PROGRESS.md +523 -0
- package/package.json +60 -0
- package/src/cli/commands/diff.ts +158 -0
- package/src/cli/commands/freeze.ts +108 -0
- package/src/cli/commands/log.ts +87 -0
- package/src/cli/commands/rollback.ts +99 -0
- package/src/cli/commands/status.ts +101 -0
- package/src/cli/index.ts +31 -0
- package/src/config/defaults.ts +32 -0
- package/src/config/index.ts +220 -0
- package/src/core/evaluator/base-rule.ts +90 -0
- package/src/core/evaluator/index.ts +86 -0
- package/src/core/evaluator/rules/repeated-drift.ts +109 -0
- package/src/core/evaluator/rules/repeated-manual-fix.ts +129 -0
- package/src/core/journal/index.ts +286 -0
- package/src/core/observer/base-observer.ts +200 -0
- package/src/core/observer/codex-observer.ts +309 -0
- package/src/core/observer/index.ts +7 -0
- package/src/core/observer/trace-manager.ts +250 -0
- package/src/core/origin-registry/index.ts +264 -0
- package/src/core/patch-generator/base-strategy.ts +67 -0
- package/src/core/patch-generator/index.ts +118 -0
- package/src/core/patch-generator/strategies/add-fallback.ts +69 -0
- package/src/core/patch-generator/strategies/prune-noise.ts +65 -0
- package/src/core/shadow-manager/index.ts +278 -0
- package/src/core/shadow-registry/index.ts +200 -0
- package/src/daemon/index.ts +174 -0
- package/src/storage/index.ts +7 -0
- package/src/storage/markdown.ts +267 -0
- package/src/storage/ndjson.ts +273 -0
- package/src/storage/sqlite.ts +555 -0
- package/src/types/index.ts +212 -0
- package/src/utils/diff.ts +60 -0
- package/src/utils/hash.ts +31 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +78 -0
- package/src/utils/path.ts +129 -0
- package/tests/unit/config.test.ts +65 -0
- package/tests/unit/path.test.ts +89 -0
- package/tests/unit/utils.test.ts +45 -0
- package/tsconfig.json +31 -0
- 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
|
+
}
|