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,278 @@
|
|
|
1
|
+
import { createChildLogger } from '../../utils/logger.js';
|
|
2
|
+
import { configManager } from '../../config/index.js';
|
|
3
|
+
import { createShadowRegistry } from '../shadow-registry/index.js';
|
|
4
|
+
import { createJournalManager } from '../journal/index.js';
|
|
5
|
+
import { createTraceManager } from '../observer/trace-manager.js';
|
|
6
|
+
import { evaluator } from '../evaluator/index.js';
|
|
7
|
+
import { patchGenerator } from '../patch-generator/index.js';
|
|
8
|
+
import { hashString } from '../../utils/hash.js';
|
|
9
|
+
import type { Trace, EvaluationResult, AutoOptimizePolicy } from '../../types/index.js';
|
|
10
|
+
|
|
11
|
+
const logger = createChildLogger('shadow-manager');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Shadow Manager
|
|
15
|
+
* 负责编排整个演化流程
|
|
16
|
+
*/
|
|
17
|
+
export class ShadowManager {
|
|
18
|
+
private shadowRegistry;
|
|
19
|
+
private journalManager;
|
|
20
|
+
private traceManager;
|
|
21
|
+
private policy: AutoOptimizePolicy;
|
|
22
|
+
private lastPatchTime: Map<string, number> = new Map();
|
|
23
|
+
private patchCountToday: Map<string, number> = new Map();
|
|
24
|
+
|
|
25
|
+
constructor(projectRoot: string) {
|
|
26
|
+
this.shadowRegistry = createShadowRegistry(projectRoot);
|
|
27
|
+
this.journalManager = createJournalManager(projectRoot);
|
|
28
|
+
this.traceManager = createTraceManager(projectRoot);
|
|
29
|
+
|
|
30
|
+
const patchConfig = configManager.getPatchConfig();
|
|
31
|
+
this.policy = {
|
|
32
|
+
min_signal_count: configManager.getEvaluatorConfig().min_signal_count,
|
|
33
|
+
min_source_sessions: configManager.getEvaluatorConfig().min_source_sessions,
|
|
34
|
+
min_confidence: configManager.getEvaluatorConfig().min_confidence,
|
|
35
|
+
cooldown_hours: patchConfig.cooldown_hours,
|
|
36
|
+
max_patches_per_day: patchConfig.max_patches_per_day,
|
|
37
|
+
pause_after_rollback_hours: 48,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 初始化
|
|
43
|
+
*/
|
|
44
|
+
async init(): Promise<void> {
|
|
45
|
+
await this.shadowRegistry.init();
|
|
46
|
+
await this.journalManager.init();
|
|
47
|
+
await this.traceManager.init();
|
|
48
|
+
logger.info('Shadow manager initialized');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 处理 trace
|
|
53
|
+
*/
|
|
54
|
+
async processTrace(trace: Trace): Promise<void> {
|
|
55
|
+
// 记录 trace
|
|
56
|
+
this.traceManager.recordTrace(trace);
|
|
57
|
+
|
|
58
|
+
// 检查是否需要触发评估
|
|
59
|
+
const shadowId = await this.findShadowForTrace(trace);
|
|
60
|
+
if (!shadowId) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 获取最近的 traces
|
|
65
|
+
const recentTraces = await this.traceManager.getSessionTraces(trace.session_id);
|
|
66
|
+
|
|
67
|
+
// 评估是否需要优化
|
|
68
|
+
const evaluation = evaluator.evaluate(recentTraces);
|
|
69
|
+
|
|
70
|
+
if (evaluation && evaluation.should_patch) {
|
|
71
|
+
await this.handleEvaluation(shadowId, evaluation, recentTraces);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 查找 trace 对应的 shadow
|
|
77
|
+
*/
|
|
78
|
+
private async findShadowForTrace(_trace: Trace): Promise<string | null> {
|
|
79
|
+
// 这里需要根据 trace 的内容推断对应的 skill
|
|
80
|
+
// 简化实现:返回 null,实际需要更复杂的逻辑
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 处理评估结果
|
|
86
|
+
*/
|
|
87
|
+
private async handleEvaluation(
|
|
88
|
+
shadowId: string,
|
|
89
|
+
evaluation: EvaluationResult,
|
|
90
|
+
_traces: Trace[]
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
// 检查是否在冷却期
|
|
93
|
+
if (this.isInCooldown(shadowId)) {
|
|
94
|
+
logger.debug(`Shadow ${shadowId} is in cooldown, skipping patch`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 检查是否超过每日限制
|
|
99
|
+
if (this.exceedsDailyLimit(shadowId)) {
|
|
100
|
+
logger.debug(`Shadow ${shadowId} exceeds daily patch limit, skipping`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 检查是否被冻结
|
|
105
|
+
const shadow = await this.shadowRegistry.get(shadowId.split('@')[0]);
|
|
106
|
+
if (shadow?.status === 'frozen') {
|
|
107
|
+
logger.debug(`Shadow ${shadowId} is frozen, skipping patch`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 检查置信度
|
|
112
|
+
if (evaluation.confidence < this.policy.min_confidence) {
|
|
113
|
+
logger.debug(`Confidence ${evaluation.confidence} below threshold, skipping patch`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 执行 patch
|
|
118
|
+
await this.executePatch(shadowId, evaluation);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 执行 patch
|
|
123
|
+
*/
|
|
124
|
+
private async executePatch(shadowId: string, evaluation: EvaluationResult): Promise<void> {
|
|
125
|
+
const skillId = shadowId.split('@')[0];
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// 读取当前内容
|
|
129
|
+
const currentContent = await this.shadowRegistry.readContent(skillId);
|
|
130
|
+
if (!currentContent) {
|
|
131
|
+
logger.warn(`Cannot read shadow content: ${skillId}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 生成 patch
|
|
136
|
+
const context = {
|
|
137
|
+
pattern: evaluation.reason,
|
|
138
|
+
reason: evaluation.reason,
|
|
139
|
+
section: evaluation.target_section,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const patchResult = patchGenerator.generate(
|
|
143
|
+
evaluation.change_type!,
|
|
144
|
+
currentContent,
|
|
145
|
+
context
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (!patchResult.success) {
|
|
149
|
+
logger.warn(`Patch generation failed: ${patchResult.error}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 获取当前 revision
|
|
154
|
+
const currentRevision = await this.journalManager.getLatestRevision(shadowId);
|
|
155
|
+
|
|
156
|
+
// 写入新内容
|
|
157
|
+
await this.shadowRegistry.writeContent(skillId, patchResult.newContent);
|
|
158
|
+
|
|
159
|
+
// 记录演化
|
|
160
|
+
await this.journalManager.record(shadowId, {
|
|
161
|
+
shadow_id: shadowId,
|
|
162
|
+
timestamp: new Date().toISOString(),
|
|
163
|
+
reason: evaluation.reason ?? 'Auto optimization',
|
|
164
|
+
source_sessions: evaluation.source_sessions,
|
|
165
|
+
change_type: evaluation.change_type!,
|
|
166
|
+
patch: patchResult.patch,
|
|
167
|
+
before_hash: hashString(currentContent),
|
|
168
|
+
after_hash: hashString(patchResult.newContent),
|
|
169
|
+
applied_by: 'auto',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// 更新时间戳
|
|
173
|
+
this.lastPatchTime.set(shadowId, Date.now());
|
|
174
|
+
|
|
175
|
+
// 更新计数
|
|
176
|
+
const today = new Date().toDateString();
|
|
177
|
+
const key = `${shadowId}:${today}`;
|
|
178
|
+
this.patchCountToday.set(key, (this.patchCountToday.get(key) ?? 0) + 1);
|
|
179
|
+
|
|
180
|
+
// 检查是否需要创建 snapshot
|
|
181
|
+
if (evaluation.change_type === 'rewrite_section' || (currentRevision + 1) % 5 === 0) {
|
|
182
|
+
await this.journalManager.createSnapshot(shadowId, currentRevision + 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
logger.info(`Patch executed successfully`, {
|
|
186
|
+
shadow_id: shadowId,
|
|
187
|
+
change_type: evaluation.change_type,
|
|
188
|
+
revision: currentRevision + 1,
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
logger.error(`Patch execution failed`, { shadow_id: shadowId, error });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 检查是否在冷却期
|
|
197
|
+
*/
|
|
198
|
+
private isInCooldown(shadowId: string): boolean {
|
|
199
|
+
const lastTime = this.lastPatchTime.get(shadowId);
|
|
200
|
+
if (!lastTime) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const cooldownMs = this.policy.cooldown_hours * 60 * 60 * 1000;
|
|
205
|
+
return Date.now() - lastTime < cooldownMs;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 检查是否超过每日限制
|
|
210
|
+
*/
|
|
211
|
+
private exceedsDailyLimit(shadowId: string): boolean {
|
|
212
|
+
const today = new Date().toDateString();
|
|
213
|
+
const key = `${shadowId}:${today}`;
|
|
214
|
+
const count = this.patchCountToday.get(key) ?? 0;
|
|
215
|
+
return count >= this.policy.max_patches_per_day;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 手动触发优化
|
|
220
|
+
*/
|
|
221
|
+
async triggerOptimize(shadowId: string): Promise<EvaluationResult | null> {
|
|
222
|
+
// 获取最近的 traces
|
|
223
|
+
const traces = await this.traceManager.getRecentTraces(100);
|
|
224
|
+
|
|
225
|
+
// 评估
|
|
226
|
+
const evaluation = evaluator.evaluate(traces);
|
|
227
|
+
|
|
228
|
+
if (evaluation && evaluation.should_patch) {
|
|
229
|
+
await this.executePatch(shadowId, evaluation);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return evaluation;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 获取 shadow 状态
|
|
237
|
+
*/
|
|
238
|
+
async getShadowState(shadowId: string) {
|
|
239
|
+
const skillId = shadowId.split('@')[0];
|
|
240
|
+
const shadow = await this.shadowRegistry.get(skillId);
|
|
241
|
+
|
|
242
|
+
if (!shadow) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const latestRevision = await this.journalManager.getLatestRevision(shadowId);
|
|
247
|
+
const snapshots = await this.journalManager.getSnapshots(shadowId);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
shadow,
|
|
251
|
+
latest_revision: latestRevision,
|
|
252
|
+
snapshot_count: snapshots.length,
|
|
253
|
+
last_patch_time: this.lastPatchTime.get(shadowId),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 清理旧 traces
|
|
259
|
+
*/
|
|
260
|
+
async cleanupOldTraces(retentionDays: number): Promise<number> {
|
|
261
|
+
return this.traceManager.cleanupOldTraces(retentionDays);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 关闭
|
|
266
|
+
*/
|
|
267
|
+
close(): void {
|
|
268
|
+
this.shadowRegistry.close();
|
|
269
|
+
this.journalManager.close();
|
|
270
|
+
this.traceManager.close();
|
|
271
|
+
logger.info('Shadow manager closed');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 导出工厂函数
|
|
276
|
+
export function createShadowManager(projectRoot: string): ShadowManager {
|
|
277
|
+
return new ShadowManager(projectRoot);
|
|
278
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { mkdirSync, existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createChildLogger } from '../../utils/logger.js';
|
|
4
|
+
import { getShadowSkillPath, getShadowMetaPath, getShadowJournalPath, getSnapshotsDir } from '../../utils/path.js';
|
|
5
|
+
import { createMarkdownSkill } from '../../storage/markdown.js';
|
|
6
|
+
import { createSQLiteStorage } from '../../storage/sqlite.js';
|
|
7
|
+
import { createJournalStore } from '../../storage/ndjson.js';
|
|
8
|
+
import type { ProjectSkillShadow, ShadowStatus, OriginSkill } from '../../types/index.js';
|
|
9
|
+
|
|
10
|
+
const logger = createChildLogger('shadow-registry');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shadow Skill 注册表
|
|
14
|
+
* 负责管理项目中的 shadow skills
|
|
15
|
+
*/
|
|
16
|
+
export class ShadowRegistry {
|
|
17
|
+
private db;
|
|
18
|
+
private projectRoot: string;
|
|
19
|
+
|
|
20
|
+
constructor(projectRoot: string) {
|
|
21
|
+
this.projectRoot = projectRoot;
|
|
22
|
+
const dbPath = join(projectRoot, '.evo', 'state', 'sessions.db');
|
|
23
|
+
this.db = createSQLiteStorage(dbPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 初始化数据库
|
|
28
|
+
*/
|
|
29
|
+
async init(): Promise<void> {
|
|
30
|
+
await this.db.init();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取项目的 shadow skill
|
|
35
|
+
*/
|
|
36
|
+
async get(skillId: string): Promise<ProjectSkillShadow | null> {
|
|
37
|
+
return this.db.getShadowSkill(this.projectRoot, skillId);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 列出项目的所有 shadow skills
|
|
42
|
+
*/
|
|
43
|
+
async list(): Promise<ProjectSkillShadow[]> {
|
|
44
|
+
return this.db.listShadowSkills(this.projectRoot);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 从 origin 创建 shadow
|
|
49
|
+
*/
|
|
50
|
+
async fork(origin: OriginSkill): Promise<ProjectSkillShadow> {
|
|
51
|
+
const shadowId = `${origin.skill_id}@${this.getProjectId()}`;
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
|
|
54
|
+
// 创建 shadow 目录结构
|
|
55
|
+
const shadowDir = join(this.projectRoot, '.evo', 'skills', origin.skill_id);
|
|
56
|
+
const snapshotsDir = join(shadowDir, 'snapshots');
|
|
57
|
+
|
|
58
|
+
if (!existsSync(shadowDir)) {
|
|
59
|
+
mkdirSync(shadowDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
if (!existsSync(snapshotsDir)) {
|
|
62
|
+
mkdirSync(snapshotsDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 复制 origin 内容到 shadow
|
|
66
|
+
const shadowPath = getShadowSkillPath(this.projectRoot, origin.skill_id);
|
|
67
|
+
const shadowSkill = createMarkdownSkill(shadowPath);
|
|
68
|
+
shadowSkill.copyFromOrigin(origin.origin_path);
|
|
69
|
+
|
|
70
|
+
// 创建 meta.json
|
|
71
|
+
const metaPath = getShadowMetaPath(this.projectRoot, origin.skill_id);
|
|
72
|
+
const meta: ProjectSkillShadow = {
|
|
73
|
+
shadow_id: shadowId,
|
|
74
|
+
project_id: this.projectRoot,
|
|
75
|
+
skill_id: origin.skill_id,
|
|
76
|
+
origin_skill_id: origin.skill_id,
|
|
77
|
+
origin_version_at_fork: origin.origin_version,
|
|
78
|
+
shadow_path: shadowPath,
|
|
79
|
+
current_revision: 0,
|
|
80
|
+
status: 'active' as ShadowStatus,
|
|
81
|
+
created_at: now,
|
|
82
|
+
last_optimized_at: '',
|
|
83
|
+
};
|
|
84
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
|
|
85
|
+
|
|
86
|
+
// 保存到数据库
|
|
87
|
+
this.db.upsertShadowSkill(meta);
|
|
88
|
+
|
|
89
|
+
logger.info(`Shadow skill created: ${shadowId}`, {
|
|
90
|
+
origin: origin.skill_id,
|
|
91
|
+
path: shadowPath,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return meta;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 检查 shadow 是否存在
|
|
99
|
+
*/
|
|
100
|
+
async exists(skillId: string): Promise<boolean> {
|
|
101
|
+
const shadow = await this.get(skillId);
|
|
102
|
+
return shadow !== null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 更新 shadow 状态
|
|
107
|
+
*/
|
|
108
|
+
async updateStatus(shadowId: string, status: ShadowStatus): Promise<void> {
|
|
109
|
+
this.db.updateShadowStatus(shadowId, status);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 更新 shadow revision
|
|
114
|
+
*/
|
|
115
|
+
async updateRevision(shadowId: string, revision: number): Promise<void> {
|
|
116
|
+
this.db.updateShadowRevision(shadowId, revision);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 读取 shadow skill 内容
|
|
121
|
+
*/
|
|
122
|
+
async readContent(skillId: string): Promise<string | null> {
|
|
123
|
+
const shadowPath = getShadowSkillPath(this.projectRoot, skillId);
|
|
124
|
+
if (!existsSync(shadowPath)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const shadowSkill = createMarkdownSkill(shadowPath);
|
|
130
|
+
return shadowSkill.read();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.warn(`Failed to read shadow content: ${skillId}`, { error });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 写入 shadow skill 内容
|
|
139
|
+
*/
|
|
140
|
+
async writeContent(skillId: string, content: string): Promise<void> {
|
|
141
|
+
const shadowPath = getShadowSkillPath(this.projectRoot, skillId);
|
|
142
|
+
const shadowSkill = createMarkdownSkill(shadowPath);
|
|
143
|
+
shadowSkill.write(content);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 获取 shadow 的 journal
|
|
148
|
+
*/
|
|
149
|
+
getJournal(skillId: string) {
|
|
150
|
+
const journalPath = getShadowJournalPath(this.projectRoot, skillId);
|
|
151
|
+
return createJournalStore(journalPath);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 获取 shadow 的 snapshots 目录
|
|
156
|
+
*/
|
|
157
|
+
getSnapshotsDir(skillId: string): string {
|
|
158
|
+
return getSnapshotsDir(this.projectRoot, skillId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 获取项目 ID(使用项目根目录的哈希)
|
|
163
|
+
*/
|
|
164
|
+
private getProjectId(): string {
|
|
165
|
+
const { shortHash } = require('../../utils/hash.js');
|
|
166
|
+
return shortHash(this.projectRoot);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 确保项目 .evo 目录结构存在
|
|
171
|
+
*/
|
|
172
|
+
async ensureProjectStructure(): Promise<void> {
|
|
173
|
+
const evoDir = join(this.projectRoot, '.evo');
|
|
174
|
+
const dirs = [
|
|
175
|
+
join(evoDir, 'skills'),
|
|
176
|
+
join(evoDir, 'state'),
|
|
177
|
+
join(evoDir, 'config'),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (const dir of dirs) {
|
|
181
|
+
if (!existsSync(dir)) {
|
|
182
|
+
mkdirSync(dir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
logger.debug('Project structure ensured', { projectRoot: this.projectRoot });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 关闭数据库连接
|
|
191
|
+
*/
|
|
192
|
+
close(): void {
|
|
193
|
+
this.db.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 导出工厂函数
|
|
198
|
+
export function createShadowRegistry(projectRoot: string): ShadowRegistry {
|
|
199
|
+
return new ShadowRegistry(projectRoot);
|
|
200
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { watch, type FSWatcher } from 'chokidar';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createChildLogger } from '../utils/logger.js';
|
|
5
|
+
import { createShadowManager } from '../core/shadow-manager/index.js';
|
|
6
|
+
import { createCodexObserver } from '../core/observer/codex-observer.js';
|
|
7
|
+
|
|
8
|
+
const logger = createChildLogger('daemon');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 后台守护进程
|
|
12
|
+
* 负责监听文件变化和定时任务
|
|
13
|
+
*/
|
|
14
|
+
export class Daemon {
|
|
15
|
+
private projectRoot: string;
|
|
16
|
+
private shadowManager;
|
|
17
|
+
private codexObserver;
|
|
18
|
+
private watcher: FSWatcher | null = null;
|
|
19
|
+
private isRunning: boolean = false;
|
|
20
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(projectRoot: string) {
|
|
23
|
+
this.projectRoot = projectRoot;
|
|
24
|
+
this.shadowManager = createShadowManager(projectRoot);
|
|
25
|
+
this.codexObserver = createCodexObserver();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 启动守护进程
|
|
30
|
+
*/
|
|
31
|
+
async start(): Promise<void> {
|
|
32
|
+
if (this.isRunning) {
|
|
33
|
+
logger.warn('Daemon already running');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.isRunning = true;
|
|
38
|
+
logger.info('Starting daemon', { projectRoot: this.projectRoot });
|
|
39
|
+
|
|
40
|
+
// 初始化 shadow manager
|
|
41
|
+
await this.shadowManager.init();
|
|
42
|
+
|
|
43
|
+
// 设置 trace 回调
|
|
44
|
+
this.codexObserver.onTrace((trace) => {
|
|
45
|
+
this.shadowManager.processTrace(trace).catch((error) => {
|
|
46
|
+
logger.error('Failed to process trace', { error });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 启动 codex observer
|
|
51
|
+
await this.codexObserver.start();
|
|
52
|
+
|
|
53
|
+
// 监听项目 .sea 目录变化
|
|
54
|
+
this.startFileWatcher();
|
|
55
|
+
|
|
56
|
+
// 启动定时清理任务
|
|
57
|
+
this.startCleanupTask();
|
|
58
|
+
|
|
59
|
+
logger.info('Daemon started successfully');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 启动文件监听器
|
|
64
|
+
*/
|
|
65
|
+
private startFileWatcher(): void {
|
|
66
|
+
const seaDir = join(this.projectRoot, '.sea');
|
|
67
|
+
|
|
68
|
+
if (!existsSync(seaDir)) {
|
|
69
|
+
logger.debug('.sea directory not found, skipping file watcher');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.watcher = watch(seaDir, {
|
|
74
|
+
persistent: true,
|
|
75
|
+
ignoreInitial: true,
|
|
76
|
+
awaitWriteFinish: {
|
|
77
|
+
stabilityThreshold: 200,
|
|
78
|
+
pollInterval: 100,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.watcher.on('change', (path) => {
|
|
83
|
+
logger.debug('File changed', { path });
|
|
84
|
+
// 可以在这里处理文件变化
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.watcher.on('error', (error) => {
|
|
88
|
+
logger.error('File watcher error', { error });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
logger.info('File watcher started', { path: seaDir });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 启动定时清理任务
|
|
96
|
+
*/
|
|
97
|
+
private startCleanupTask(): void {
|
|
98
|
+
// 每小时清理一次旧 traces
|
|
99
|
+
this.cleanupInterval = setInterval(
|
|
100
|
+
async () => {
|
|
101
|
+
try {
|
|
102
|
+
const retentionDays = 30;
|
|
103
|
+
const cleaned = await this.shadowManager.cleanupOldTraces(retentionDays);
|
|
104
|
+
if (cleaned > 0) {
|
|
105
|
+
logger.info(`Cleaned ${cleaned} old traces`);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger.error('Cleanup task failed', { error });
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
60 * 60 * 1000 // 1 小时
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
logger.info('Cleanup task started');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 停止守护进程
|
|
119
|
+
*/
|
|
120
|
+
async stop(): Promise<void> {
|
|
121
|
+
if (!this.isRunning) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.isRunning = false;
|
|
126
|
+
logger.info('Stopping daemon');
|
|
127
|
+
|
|
128
|
+
// 停止 codex observer
|
|
129
|
+
await this.codexObserver.stop();
|
|
130
|
+
|
|
131
|
+
// 停止文件监听器
|
|
132
|
+
if (this.watcher) {
|
|
133
|
+
await this.watcher.close();
|
|
134
|
+
this.watcher = null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 停止定时任务
|
|
138
|
+
if (this.cleanupInterval) {
|
|
139
|
+
clearInterval(this.cleanupInterval);
|
|
140
|
+
this.cleanupInterval = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 关闭 shadow manager
|
|
144
|
+
this.shadowManager.close();
|
|
145
|
+
|
|
146
|
+
logger.info('Daemon stopped');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 检查是否正在运行
|
|
151
|
+
*/
|
|
152
|
+
isActive(): boolean {
|
|
153
|
+
return this.isRunning;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 获取 shadow manager
|
|
158
|
+
*/
|
|
159
|
+
getShadowManager() {
|
|
160
|
+
return this.shadowManager;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 获取 codex observer
|
|
165
|
+
*/
|
|
166
|
+
getCodexObserver() {
|
|
167
|
+
return this.codexObserver;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 导出工厂函数
|
|
172
|
+
export function createDaemon(projectRoot: string): Daemon {
|
|
173
|
+
return new Daemon(projectRoot);
|
|
174
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 存储层入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { SQLiteStorage, createSQLiteStorage } from './sqlite.js';
|
|
6
|
+
export { NDJSONReader, NDJSONWriter, TraceStore, JournalStore, createTraceStore, createJournalStore } from './ndjson.js';
|
|
7
|
+
export { MarkdownSkill, createMarkdownSkill } from './markdown.js';
|