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,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';