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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局类型定义入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Origin Skill 类型
|
|
6
|
+
export interface OriginSkill {
|
|
7
|
+
skill_id: string;
|
|
8
|
+
origin_path: string;
|
|
9
|
+
origin_version: string; // file hash
|
|
10
|
+
source: 'local' | 'marketplace' | 'git';
|
|
11
|
+
installed_at: string;
|
|
12
|
+
last_seen_at: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Shadow Skill 状态
|
|
16
|
+
export type ShadowStatus = 'active' | 'frozen' | 'rebasing' | 'needs_attention';
|
|
17
|
+
|
|
18
|
+
// Project Skill Shadow 类型
|
|
19
|
+
export interface ProjectSkillShadow {
|
|
20
|
+
project_id: string;
|
|
21
|
+
skill_id: string;
|
|
22
|
+
shadow_id: string; // "A@repo-x"
|
|
23
|
+
origin_skill_id: string;
|
|
24
|
+
origin_version_at_fork: string;
|
|
25
|
+
shadow_path: string;
|
|
26
|
+
current_revision: number;
|
|
27
|
+
status: ShadowStatus;
|
|
28
|
+
created_at: string;
|
|
29
|
+
last_optimized_at: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Shadow Skill 运行状态
|
|
33
|
+
export interface ShadowSkillState {
|
|
34
|
+
shadow_id: string;
|
|
35
|
+
current_content_hash: string;
|
|
36
|
+
current_revision: number;
|
|
37
|
+
last_hit_at: string;
|
|
38
|
+
last_optimized_at: string;
|
|
39
|
+
hit_count: number;
|
|
40
|
+
success_count: number;
|
|
41
|
+
manual_override_count: number;
|
|
42
|
+
health_score: number; // 0-100
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Change Type - Patch 类型
|
|
46
|
+
export type ChangeType =
|
|
47
|
+
| 'append_context'
|
|
48
|
+
| 'tighten_trigger'
|
|
49
|
+
| 'add_fallback'
|
|
50
|
+
| 'prune_noise'
|
|
51
|
+
| 'rewrite_section';
|
|
52
|
+
|
|
53
|
+
// Evolution Record 演化记录
|
|
54
|
+
export interface EvolutionRecord {
|
|
55
|
+
revision: number;
|
|
56
|
+
shadow_id: string;
|
|
57
|
+
timestamp: string;
|
|
58
|
+
reason: string;
|
|
59
|
+
source_sessions: string[];
|
|
60
|
+
change_type: ChangeType;
|
|
61
|
+
patch: string;
|
|
62
|
+
before_hash: string;
|
|
63
|
+
after_hash: string;
|
|
64
|
+
applied_by: 'auto' | 'manual';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Snapshot 信息
|
|
68
|
+
export interface SnapshotInfo {
|
|
69
|
+
revision: number;
|
|
70
|
+
timestamp: string;
|
|
71
|
+
file_path: string;
|
|
72
|
+
content_hash: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Trace 事件类型
|
|
76
|
+
export type TraceEventType =
|
|
77
|
+
| 'user_input'
|
|
78
|
+
| 'assistant_output'
|
|
79
|
+
| 'tool_call'
|
|
80
|
+
| 'tool_result'
|
|
81
|
+
| 'file_change'
|
|
82
|
+
| 'retry'
|
|
83
|
+
| 'status';
|
|
84
|
+
|
|
85
|
+
// Trace 状态
|
|
86
|
+
export type TraceStatus = 'success' | 'failure' | 'retry' | 'interrupted';
|
|
87
|
+
|
|
88
|
+
// Runtime 类型
|
|
89
|
+
export type RuntimeType = 'codex' | 'opencode' | 'claude';
|
|
90
|
+
|
|
91
|
+
// Trace 数据结构
|
|
92
|
+
export interface Trace {
|
|
93
|
+
trace_id: string;
|
|
94
|
+
runtime: RuntimeType;
|
|
95
|
+
session_id: string;
|
|
96
|
+
turn_id: string;
|
|
97
|
+
event_type: TraceEventType;
|
|
98
|
+
timestamp: string;
|
|
99
|
+
user_input?: string;
|
|
100
|
+
assistant_output?: string;
|
|
101
|
+
tool_name?: string;
|
|
102
|
+
tool_args?: Record<string, unknown>;
|
|
103
|
+
tool_result?: Record<string, unknown>;
|
|
104
|
+
files_changed?: string[];
|
|
105
|
+
status: TraceStatus;
|
|
106
|
+
metadata?: Record<string, unknown>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Evaluation Result 评估结果
|
|
110
|
+
export interface EvaluationResult {
|
|
111
|
+
should_patch: boolean;
|
|
112
|
+
change_type?: ChangeType;
|
|
113
|
+
reason?: string;
|
|
114
|
+
source_sessions: string[];
|
|
115
|
+
confidence: number; // 0-1
|
|
116
|
+
target_section?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Patch Result
|
|
120
|
+
export interface PatchResult {
|
|
121
|
+
success: boolean;
|
|
122
|
+
patch: string; // unified diff
|
|
123
|
+
newContent: string;
|
|
124
|
+
changeType: ChangeType;
|
|
125
|
+
error?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Auto Optimize Policy 自动优化策略
|
|
129
|
+
export interface AutoOptimizePolicy {
|
|
130
|
+
min_signal_count: number;
|
|
131
|
+
min_source_sessions: number;
|
|
132
|
+
min_confidence: number;
|
|
133
|
+
cooldown_hours: number;
|
|
134
|
+
max_patches_per_day: number;
|
|
135
|
+
pause_after_rollback_hours: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Session 信息
|
|
139
|
+
export interface Session {
|
|
140
|
+
session_id: string;
|
|
141
|
+
runtime: RuntimeType;
|
|
142
|
+
project_id: string | null;
|
|
143
|
+
started_at: string;
|
|
144
|
+
ended_at: string | null;
|
|
145
|
+
trace_count: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Snapshot 详情(包含内容)
|
|
149
|
+
export interface Snapshot extends SnapshotInfo {
|
|
150
|
+
shadow_id: string;
|
|
151
|
+
content: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Journal 查询选项
|
|
155
|
+
export interface JournalQueryOptions {
|
|
156
|
+
fromRevision?: number;
|
|
157
|
+
toRevision?: number;
|
|
158
|
+
limit?: number;
|
|
159
|
+
changeType?: ChangeType;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// CLI 输出格式
|
|
163
|
+
export interface CliOutput {
|
|
164
|
+
success: boolean;
|
|
165
|
+
message: string;
|
|
166
|
+
data?: unknown;
|
|
167
|
+
error?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 配置类型
|
|
171
|
+
export interface EVOConfig {
|
|
172
|
+
origin_paths: {
|
|
173
|
+
paths: string[];
|
|
174
|
+
};
|
|
175
|
+
observer: {
|
|
176
|
+
enabled_runtimes: RuntimeType[];
|
|
177
|
+
trace_retention_days: number;
|
|
178
|
+
};
|
|
179
|
+
evaluator: {
|
|
180
|
+
min_signal_count: number;
|
|
181
|
+
min_source_sessions: number;
|
|
182
|
+
min_confidence: number;
|
|
183
|
+
};
|
|
184
|
+
patch: {
|
|
185
|
+
allowed_types: ChangeType[];
|
|
186
|
+
cooldown_hours: number;
|
|
187
|
+
max_patches_per_day: number;
|
|
188
|
+
};
|
|
189
|
+
journal: {
|
|
190
|
+
snapshot_interval: number;
|
|
191
|
+
max_snapshots: number;
|
|
192
|
+
};
|
|
193
|
+
daemon: {
|
|
194
|
+
auto_start: boolean;
|
|
195
|
+
log_level: 'debug' | 'info' | 'warn' | 'error';
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 项目配置
|
|
200
|
+
export interface ProjectConfig {
|
|
201
|
+
project: {
|
|
202
|
+
name: string;
|
|
203
|
+
auto_optimize: boolean;
|
|
204
|
+
};
|
|
205
|
+
skills: Record<
|
|
206
|
+
string,
|
|
207
|
+
{
|
|
208
|
+
auto_optimize?: boolean;
|
|
209
|
+
allowed_patch_types?: ChangeType[];
|
|
210
|
+
}
|
|
211
|
+
>;
|
|
212
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { diffLines, createPatch } from 'diff';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 计算两个字符串的差异
|
|
5
|
+
*/
|
|
6
|
+
export function computeDiff(oldContent: string, newContent: string): string {
|
|
7
|
+
const differences = diffLines(oldContent, newContent);
|
|
8
|
+
return differences
|
|
9
|
+
.map((part) => {
|
|
10
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
11
|
+
return part.value
|
|
12
|
+
.split('\n')
|
|
13
|
+
.filter((line) => line.length > 0)
|
|
14
|
+
.map((line) => `${prefix} ${line}`)
|
|
15
|
+
.join('\n');
|
|
16
|
+
})
|
|
17
|
+
.join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 生成 unified diff 格式的 patch
|
|
22
|
+
*/
|
|
23
|
+
export function createUnifiedDiff(
|
|
24
|
+
fileName: string,
|
|
25
|
+
oldContent: string,
|
|
26
|
+
newContent: string,
|
|
27
|
+
oldHeader?: string,
|
|
28
|
+
newHeader?: string
|
|
29
|
+
): string {
|
|
30
|
+
return createPatch(fileName, oldContent, newContent, oldHeader, newHeader);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 检查内容是否有变化
|
|
35
|
+
*/
|
|
36
|
+
export function hasChanges(oldContent: string, newContent: string): boolean {
|
|
37
|
+
return oldContent !== newContent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 统计差异行数
|
|
42
|
+
*/
|
|
43
|
+
export function countChanges(oldContent: string, newContent: string): {
|
|
44
|
+
added: number;
|
|
45
|
+
removed: number;
|
|
46
|
+
} {
|
|
47
|
+
const differences = diffLines(oldContent, newContent);
|
|
48
|
+
let added = 0;
|
|
49
|
+
let removed = 0;
|
|
50
|
+
|
|
51
|
+
for (const part of differences) {
|
|
52
|
+
if (part.added) {
|
|
53
|
+
added += part.count ?? 0;
|
|
54
|
+
} else if (part.removed) {
|
|
55
|
+
removed += part.count ?? 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { added, removed };
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 计算字符串的 SHA256 哈希
|
|
6
|
+
*/
|
|
7
|
+
export function hashString(content: string): string {
|
|
8
|
+
return createHash('sha256').update(content).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 计算文件的 SHA256 哈希
|
|
13
|
+
*/
|
|
14
|
+
export function hashFile(filePath: string): string {
|
|
15
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
16
|
+
return hashString(content);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 计算 Buffer 的 SHA256 哈希
|
|
21
|
+
*/
|
|
22
|
+
export function hashBuffer(buffer: Buffer): string {
|
|
23
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 生成短哈希(前 8 位)
|
|
28
|
+
*/
|
|
29
|
+
export function shortHash(content: string): string {
|
|
30
|
+
return hashString(content).substring(0, 8);
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
const LOG_DIR = join(homedir(), '.evo', 'logs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建日志目录
|
|
10
|
+
*/
|
|
11
|
+
function ensureLogDir(): void {
|
|
12
|
+
if (!existsSync(LOG_DIR)) {
|
|
13
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 自定义日志格式
|
|
19
|
+
*/
|
|
20
|
+
const logFormat = winston.format.combine(
|
|
21
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
22
|
+
winston.format.errors({ stack: true }),
|
|
23
|
+
winston.format.printf(({ timestamp, level, message, stack }) => {
|
|
24
|
+
const baseMessage = `[${timestamp}] ${level.toUpperCase()}: ${message}`;
|
|
25
|
+
return stack ? `${baseMessage}\n${stack}` : baseMessage;
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 创建主 logger
|
|
31
|
+
*/
|
|
32
|
+
function createLogger(): winston.Logger {
|
|
33
|
+
ensureLogDir();
|
|
34
|
+
|
|
35
|
+
return winston.createLogger({
|
|
36
|
+
level: 'info',
|
|
37
|
+
format: logFormat,
|
|
38
|
+
transports: [
|
|
39
|
+
// 文件日志
|
|
40
|
+
new winston.transports.File({
|
|
41
|
+
filename: join(LOG_DIR, 'error.log'),
|
|
42
|
+
level: 'error',
|
|
43
|
+
maxsize: 5 * 1024 * 1024, // 5MB
|
|
44
|
+
maxFiles: 5,
|
|
45
|
+
}),
|
|
46
|
+
new winston.transports.File({
|
|
47
|
+
filename: join(LOG_DIR, 'combined.log'),
|
|
48
|
+
maxsize: 10 * 1024 * 1024, // 10MB
|
|
49
|
+
maxFiles: 10,
|
|
50
|
+
}),
|
|
51
|
+
// 控制台日志(仅在开发环境)
|
|
52
|
+
new winston.transports.Console({
|
|
53
|
+
format: winston.format.combine(
|
|
54
|
+
winston.format.colorize(),
|
|
55
|
+
logFormat
|
|
56
|
+
),
|
|
57
|
+
silent: process.env.NODE_ENV === 'production',
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 导出单例 logger
|
|
64
|
+
export const logger = createLogger();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 创建带上下文的 logger
|
|
68
|
+
*/
|
|
69
|
+
export function createChildLogger(context: string): winston.Logger {
|
|
70
|
+
return logger.child({ context });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 设置日志级别
|
|
75
|
+
*/
|
|
76
|
+
export function setLogLevel(level: string): void {
|
|
77
|
+
logger.level = level;
|
|
78
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { join, resolve, relative, dirname, basename, extname } from 'node:path';
|
|
2
|
+
import { existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 展开路径中的 ~ 为用户主目录
|
|
7
|
+
*/
|
|
8
|
+
export function expandHome(path: string): string {
|
|
9
|
+
if (path.startsWith('~/') || path === '~') {
|
|
10
|
+
return path.replace('~', homedir());
|
|
11
|
+
}
|
|
12
|
+
return path;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取项目的 .evo 目录路径
|
|
17
|
+
*/
|
|
18
|
+
export function getEvoDir(projectRoot: string): string {
|
|
19
|
+
return join(projectRoot, '.evo');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 获取项目的 skills 目录路径
|
|
24
|
+
*/
|
|
25
|
+
export function getSkillsDir(projectRoot: string): string {
|
|
26
|
+
return join(projectRoot, '.evo', 'skills');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 获取项目的 state 目录路径
|
|
31
|
+
*/
|
|
32
|
+
export function getStateDir(projectRoot: string): string {
|
|
33
|
+
return join(projectRoot, '.evo', 'state');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 获取项目的 config 目录路径
|
|
38
|
+
*/
|
|
39
|
+
export function getConfigDir(projectRoot: string): string {
|
|
40
|
+
return join(projectRoot, '.evo', 'config');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取 shadow skill 的路径
|
|
45
|
+
*/
|
|
46
|
+
export function getShadowSkillPath(projectRoot: string, skillId: string): string {
|
|
47
|
+
return join(projectRoot, '.evo', 'skills', skillId, 'current.md');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取 shadow skill 的 meta.json 路径
|
|
52
|
+
*/
|
|
53
|
+
export function getShadowMetaPath(projectRoot: string, skillId: string): string {
|
|
54
|
+
return join(projectRoot, '.evo', 'skills', skillId, 'meta.json');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 获取 shadow skill 的 journal 路径
|
|
59
|
+
*/
|
|
60
|
+
export function getShadowJournalPath(projectRoot: string, skillId: string): string {
|
|
61
|
+
return join(projectRoot, '.evo', 'skills', skillId, 'journal.ndjson');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 获取 shadow skill 的 snapshots 目录路径
|
|
66
|
+
*/
|
|
67
|
+
export function getSnapshotsDir(projectRoot: string, skillId: string): string {
|
|
68
|
+
return join(projectRoot, '.evo', 'skills', skillId, 'snapshots');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 检查路径是否存在
|
|
73
|
+
*/
|
|
74
|
+
export function pathExists(path: string): boolean {
|
|
75
|
+
return existsSync(path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 检查是否是目录
|
|
80
|
+
*/
|
|
81
|
+
export function isDirectory(path: string): boolean {
|
|
82
|
+
try {
|
|
83
|
+
return statSync(path).isDirectory();
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 检查是否是文件
|
|
91
|
+
*/
|
|
92
|
+
export function isFile(path: string): boolean {
|
|
93
|
+
try {
|
|
94
|
+
return statSync(path).isFile();
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 获取相对路径
|
|
102
|
+
*/
|
|
103
|
+
export function getRelativePath(from: string, to: string): string {
|
|
104
|
+
return relative(from, to);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 获取文件扩展名
|
|
109
|
+
*/
|
|
110
|
+
export function getExtension(path: string): string {
|
|
111
|
+
return extname(path);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取文件名(不含扩展名)
|
|
116
|
+
*/
|
|
117
|
+
export function getFileNameWithoutExt(path: string): string {
|
|
118
|
+
return basename(path, extname(path));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取目录名
|
|
123
|
+
*/
|
|
124
|
+
export function getDirName(path: string): string {
|
|
125
|
+
return dirname(path);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 重新导出常用的 path 函数
|
|
129
|
+
export { join, resolve, relative, dirname, basename, extname };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { ConfigManager } from '../../src/config/index.js';
|
|
3
|
+
import { DEFAULT_CONFIG } from '../../src/config/defaults.js';
|
|
4
|
+
|
|
5
|
+
describe('Config Manager', () => {
|
|
6
|
+
let configManager: ConfigManager;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
configManager = new ConfigManager();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('getGlobalConfig', () => {
|
|
13
|
+
it('should return default config initially', () => {
|
|
14
|
+
const config = configManager.getGlobalConfig();
|
|
15
|
+
expect(config).toEqual(DEFAULT_CONFIG);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return a copy of config', () => {
|
|
19
|
+
const config1 = configManager.getGlobalConfig();
|
|
20
|
+
const config2 = configManager.getGlobalConfig();
|
|
21
|
+
expect(config1).not.toBe(config2);
|
|
22
|
+
expect(config1).toEqual(config2);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('getEvaluatorConfig', () => {
|
|
27
|
+
it('should return evaluator config', () => {
|
|
28
|
+
const config = configManager.getEvaluatorConfig();
|
|
29
|
+
expect(config).toHaveProperty('min_signal_count');
|
|
30
|
+
expect(config).toHaveProperty('min_source_sessions');
|
|
31
|
+
expect(config).toHaveProperty('min_confidence');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('getPatchConfig', () => {
|
|
36
|
+
it('should return patch config', () => {
|
|
37
|
+
const config = configManager.getPatchConfig();
|
|
38
|
+
expect(config).toHaveProperty('allowed_types');
|
|
39
|
+
expect(config).toHaveProperty('cooldown_hours');
|
|
40
|
+
expect(config).toHaveProperty('max_patches_per_day');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('getJournalConfig', () => {
|
|
45
|
+
it('should return journal config', () => {
|
|
46
|
+
const config = configManager.getJournalConfig();
|
|
47
|
+
expect(config).toHaveProperty('snapshot_interval');
|
|
48
|
+
expect(config).toHaveProperty('max_snapshots');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('isSkillFrozen', () => {
|
|
53
|
+
it('should return false when no project config', () => {
|
|
54
|
+
const result = configManager.isSkillFrozen('test-skill');
|
|
55
|
+
expect(result).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('getAllowedPatchTypes', () => {
|
|
60
|
+
it('should return default patch types when no project config', () => {
|
|
61
|
+
const types = configManager.getAllowedPatchTypes('test-skill');
|
|
62
|
+
expect(types).toEqual(DEFAULT_CONFIG.patch.allowed_types);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
expandHome,
|
|
4
|
+
getEvoDir,
|
|
5
|
+
getSkillsDir,
|
|
6
|
+
getStateDir,
|
|
7
|
+
getConfigDir,
|
|
8
|
+
getShadowSkillPath,
|
|
9
|
+
getShadowMetaPath,
|
|
10
|
+
getShadowJournalPath,
|
|
11
|
+
getSnapshotsDir,
|
|
12
|
+
join,
|
|
13
|
+
} from '../../src/utils/path.js';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
|
|
16
|
+
describe('Path Utils', () => {
|
|
17
|
+
describe('expandHome', () => {
|
|
18
|
+
it('should expand ~ to home directory', () => {
|
|
19
|
+
const result = expandHome('~/test');
|
|
20
|
+
expect(result).toBe(join(homedir(), 'test'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle ~ alone', () => {
|
|
24
|
+
const result = expandHome('~');
|
|
25
|
+
expect(result).toBe(homedir());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should not modify paths without ~', () => {
|
|
29
|
+
const result = expandHome('/absolute/path');
|
|
30
|
+
expect(result).toBe('/absolute/path');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getEvoDir', () => {
|
|
35
|
+
it('should return .evo directory path', () => {
|
|
36
|
+
const result = getEvoDir('/project');
|
|
37
|
+
expect(result).toBe('/project/.evo');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getSkillsDir', () => {
|
|
42
|
+
it('should return .evo/skills directory path', () => {
|
|
43
|
+
const result = getSkillsDir('/project');
|
|
44
|
+
expect(result).toBe('/project/.evo/skills');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('getStateDir', () => {
|
|
49
|
+
it('should return .evo/state directory path', () => {
|
|
50
|
+
const result = getStateDir('/project');
|
|
51
|
+
expect(result).toBe('/project/.evo/state');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('getConfigDir', () => {
|
|
56
|
+
it('should return .evo/config directory path', () => {
|
|
57
|
+
const result = getConfigDir('/project');
|
|
58
|
+
expect(result).toBe('/project/.evo/config');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getShadowSkillPath', () => {
|
|
63
|
+
it('should return shadow skill path', () => {
|
|
64
|
+
const result = getShadowSkillPath('/project', 'my-skill');
|
|
65
|
+
expect(result).toBe('/project/.evo/skills/my-skill/current.md');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('getShadowMetaPath', () => {
|
|
70
|
+
it('should return shadow meta path', () => {
|
|
71
|
+
const result = getShadowMetaPath('/project', 'my-skill');
|
|
72
|
+
expect(result).toBe('/project/.evo/skills/my-skill/meta.json');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('getShadowJournalPath', () => {
|
|
77
|
+
it('should return shadow journal path', () => {
|
|
78
|
+
const result = getShadowJournalPath('/project', 'my-skill');
|
|
79
|
+
expect(result).toBe('/project/.evo/skills/my-skill/journal.ndjson');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('getSnapshotsDir', () => {
|
|
84
|
+
it('should return snapshots directory path', () => {
|
|
85
|
+
const result = getSnapshotsDir('/project', 'my-skill');
|
|
86
|
+
expect(result).toBe('/project/.evo/skills/my-skill/snapshots');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|