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,264 @@
|
|
|
1
|
+
import { readdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
import { createChildLogger } from '../../utils/logger.js';
|
|
4
|
+
import { hashFile } from '../../utils/hash.js';
|
|
5
|
+
import { expandHome, isDirectory, isFile } from '../../utils/path.js';
|
|
6
|
+
import { configManager } from '../../config/index.js';
|
|
7
|
+
import type { OriginSkill } from '../../types/index.js';
|
|
8
|
+
|
|
9
|
+
const logger = createChildLogger('origin-registry');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Origin Skill 注册表
|
|
13
|
+
* 负责扫描和管理用户本机已安装的 skills
|
|
14
|
+
*/
|
|
15
|
+
export class OriginRegistry {
|
|
16
|
+
private skills: Map<string, OriginSkill> = new Map();
|
|
17
|
+
private lastScanTime: number = 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 扫描所有配置的 skill 目录
|
|
21
|
+
*/
|
|
22
|
+
async scan(): Promise<OriginSkill[]> {
|
|
23
|
+
const paths = configManager.getOriginPaths();
|
|
24
|
+
const allSkills: OriginSkill[] = [];
|
|
25
|
+
|
|
26
|
+
for (const skillPath of paths) {
|
|
27
|
+
const expandedPath = expandHome(skillPath);
|
|
28
|
+
if (!existsSync(expandedPath)) {
|
|
29
|
+
logger.debug(`Skill path not found: ${expandedPath}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const skills = await this.scanDirectory(expandedPath);
|
|
35
|
+
allSkills.push(...skills);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logger.warn(`Failed to scan directory: ${expandedPath}`, { error });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 更新内部缓存
|
|
42
|
+
this.skills.clear();
|
|
43
|
+
for (const skill of allSkills) {
|
|
44
|
+
this.skills.set(skill.skill_id, skill);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.lastScanTime = Date.now();
|
|
48
|
+
logger.info(`Scanned ${allSkills.length} origin skills`);
|
|
49
|
+
|
|
50
|
+
return allSkills;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 扫描单个目录
|
|
55
|
+
*/
|
|
56
|
+
private async scanDirectory(dirPath: string): Promise<OriginSkill[]> {
|
|
57
|
+
const skills: OriginSkill[] = [];
|
|
58
|
+
|
|
59
|
+
if (!isDirectory(dirPath)) {
|
|
60
|
+
return skills;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const entries = readdirSync(dirPath);
|
|
64
|
+
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const fullPath = join(dirPath, entry);
|
|
67
|
+
|
|
68
|
+
// 检查是否是目录(skill 通常是目录形式)
|
|
69
|
+
if (isDirectory(fullPath)) {
|
|
70
|
+
const skill = this.readSkillFromDirectory(fullPath, entry);
|
|
71
|
+
if (skill) {
|
|
72
|
+
skills.push(skill);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// 或者是单个 .md 文件
|
|
76
|
+
else if (entry.endsWith('.md') && isFile(fullPath)) {
|
|
77
|
+
const skillId = basename(entry, '.md');
|
|
78
|
+
const skill = this.readSkillFromFile(fullPath, skillId);
|
|
79
|
+
if (skill) {
|
|
80
|
+
skills.push(skill);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return skills;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 从目录读取 skill
|
|
90
|
+
*/
|
|
91
|
+
private readSkillFromDirectory(dirPath: string, skillId: string): OriginSkill | null {
|
|
92
|
+
try {
|
|
93
|
+
// 查找主要的 skill 文件(通常是 current.md 或 skill.md)
|
|
94
|
+
const possibleFiles = ['current.md', 'skill.md', `${skillId}.md`];
|
|
95
|
+
let mainFile: string | null = null;
|
|
96
|
+
|
|
97
|
+
for (const filename of possibleFiles) {
|
|
98
|
+
const filePath = join(dirPath, filename);
|
|
99
|
+
if (isFile(filePath)) {
|
|
100
|
+
mainFile = filePath;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!mainFile) {
|
|
106
|
+
logger.debug(`No skill file found in directory: ${dirPath}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const version = hashFile(mainFile);
|
|
111
|
+
const now = new Date().toISOString();
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
skill_id: skillId,
|
|
115
|
+
origin_path: dirPath,
|
|
116
|
+
origin_version: version,
|
|
117
|
+
source: this.detectSource(dirPath),
|
|
118
|
+
installed_at: now,
|
|
119
|
+
last_seen_at: now,
|
|
120
|
+
};
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.warn(`Failed to read skill from directory: ${dirPath}`, { error });
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 从文件读取 skill
|
|
129
|
+
*/
|
|
130
|
+
private readSkillFromFile(filePath: string, skillId: string): OriginSkill | null {
|
|
131
|
+
try {
|
|
132
|
+
const version = hashFile(filePath);
|
|
133
|
+
const now = new Date().toISOString();
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
skill_id: skillId,
|
|
137
|
+
origin_path: filePath,
|
|
138
|
+
origin_version: version,
|
|
139
|
+
source: this.detectSource(filePath),
|
|
140
|
+
installed_at: now,
|
|
141
|
+
last_seen_at: now,
|
|
142
|
+
};
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logger.warn(`Failed to read skill from file: ${filePath}`, { error });
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 检测 skill 来源
|
|
151
|
+
*/
|
|
152
|
+
private detectSource(path: string): 'local' | 'marketplace' | 'git' {
|
|
153
|
+
if (path.includes('.git') || path.includes('github')) {
|
|
154
|
+
return 'git';
|
|
155
|
+
}
|
|
156
|
+
if (path.includes('marketplace') || path.includes('registry')) {
|
|
157
|
+
return 'marketplace';
|
|
158
|
+
}
|
|
159
|
+
return 'local';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取指定 skill
|
|
164
|
+
*/
|
|
165
|
+
get(skillId: string): OriginSkill | null {
|
|
166
|
+
return this.skills.get(skillId) ?? null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 获取所有 skills
|
|
171
|
+
*/
|
|
172
|
+
list(): OriginSkill[] {
|
|
173
|
+
return Array.from(this.skills.values());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 检查 skill 是否有更新
|
|
178
|
+
*/
|
|
179
|
+
checkUpdate(skillId: string): boolean {
|
|
180
|
+
const skill = this.skills.get(skillId);
|
|
181
|
+
if (!skill) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const currentVersion = hashFile(skill.origin_path);
|
|
187
|
+
return currentVersion !== skill.origin_version;
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 读取 origin skill 内容
|
|
195
|
+
*/
|
|
196
|
+
readContent(skillId: string): string | null {
|
|
197
|
+
const skill = this.skills.get(skillId);
|
|
198
|
+
if (!skill) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const { readFileSync } = require('node:fs');
|
|
204
|
+
|
|
205
|
+
// 如果是目录,查找主文件
|
|
206
|
+
if (isDirectory(skill.origin_path)) {
|
|
207
|
+
const possibleFiles = ['current.md', 'skill.md', `${skillId}.md`];
|
|
208
|
+
for (const filename of possibleFiles) {
|
|
209
|
+
const filePath = join(skill.origin_path, filename);
|
|
210
|
+
if (isFile(filePath)) {
|
|
211
|
+
return readFileSync(filePath, 'utf-8');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// 如果是文件,直接读取
|
|
216
|
+
else if (isFile(skill.origin_path)) {
|
|
217
|
+
return readFileSync(skill.origin_path, 'utf-8');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
logger.warn(`Failed to read skill content: ${skillId}`, { error });
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 刷新单个 skill 的版本信息
|
|
229
|
+
*/
|
|
230
|
+
refreshSkill(skillId: string): OriginSkill | null {
|
|
231
|
+
const skill = this.skills.get(skillId);
|
|
232
|
+
if (!skill) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const newVersion = hashFile(skill.origin_path);
|
|
238
|
+
skill.origin_version = newVersion;
|
|
239
|
+
skill.last_seen_at = new Date().toISOString();
|
|
240
|
+
return skill;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
logger.warn(`Failed to refresh skill: ${skillId}`, { error });
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 获取上次扫描时间
|
|
249
|
+
*/
|
|
250
|
+
getLastScanTime(): number {
|
|
251
|
+
return this.lastScanTime;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 检查是否需要重新扫描
|
|
256
|
+
*/
|
|
257
|
+
needsRescan(): boolean {
|
|
258
|
+
const scanInterval = 3600 * 1000; // 1 小时
|
|
259
|
+
return Date.now() - this.lastScanTime > scanInterval;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 导出单例实例
|
|
264
|
+
export const originRegistry = new OriginRegistry();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { PatchResult, ChangeType } from '../../types/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Patch 策略基类
|
|
5
|
+
*/
|
|
6
|
+
export abstract class BaseStrategy {
|
|
7
|
+
protected name: string;
|
|
8
|
+
protected description: string;
|
|
9
|
+
protected changeType: ChangeType;
|
|
10
|
+
|
|
11
|
+
constructor(name: string, description: string, changeType: ChangeType) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.description = description;
|
|
14
|
+
this.changeType = changeType;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 生成 patch
|
|
19
|
+
*/
|
|
20
|
+
abstract generate(currentContent: string, context: Record<string, unknown>): PatchResult;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 获取策略名称
|
|
24
|
+
*/
|
|
25
|
+
getName(): string {
|
|
26
|
+
return this.name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 获取策略描述
|
|
31
|
+
*/
|
|
32
|
+
getDescription(): string {
|
|
33
|
+
return this.description;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 获取变更类型
|
|
38
|
+
*/
|
|
39
|
+
getChangeType(): ChangeType {
|
|
40
|
+
return this.changeType;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 创建成功的 PatchResult
|
|
45
|
+
*/
|
|
46
|
+
protected createSuccessResult(patch: string, newContent: string): PatchResult {
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
patch,
|
|
50
|
+
newContent,
|
|
51
|
+
changeType: this.changeType,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 创建失败的 PatchResult
|
|
57
|
+
*/
|
|
58
|
+
protected createFailureResult(error: string): PatchResult {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
patch: '',
|
|
62
|
+
newContent: '',
|
|
63
|
+
changeType: this.changeType,
|
|
64
|
+
error,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createChildLogger } from '../../utils/logger.js';
|
|
2
|
+
import { configManager } from '../../config/index.js';
|
|
3
|
+
import { BaseStrategy } from './base-strategy.js';
|
|
4
|
+
import { AddFallbackStrategy } from './strategies/add-fallback.js';
|
|
5
|
+
import { PruneNoiseStrategy } from './strategies/prune-noise.js';
|
|
6
|
+
import type { PatchResult, ChangeType } from '../../types/index.js';
|
|
7
|
+
|
|
8
|
+
const logger = createChildLogger('patch-generator');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Patch Generator
|
|
12
|
+
* 负责生成具体的 patch
|
|
13
|
+
*/
|
|
14
|
+
export class PatchGenerator {
|
|
15
|
+
private strategies: Map<ChangeType, BaseStrategy> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.registerDefaultStrategies();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 注册默认策略
|
|
23
|
+
*/
|
|
24
|
+
private registerDefaultStrategies(): void {
|
|
25
|
+
this.registerStrategy(new AddFallbackStrategy());
|
|
26
|
+
this.registerStrategy(new PruneNoiseStrategy());
|
|
27
|
+
logger.info('Default patch strategies registered');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 注册策略
|
|
32
|
+
*/
|
|
33
|
+
registerStrategy(strategy: BaseStrategy): void {
|
|
34
|
+
this.strategies.set(strategy.getChangeType(), strategy);
|
|
35
|
+
logger.info(`Strategy registered: ${strategy.getName()}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 生成 patch
|
|
40
|
+
*/
|
|
41
|
+
generate(
|
|
42
|
+
changeType: ChangeType,
|
|
43
|
+
currentContent: string,
|
|
44
|
+
context: Record<string, unknown>
|
|
45
|
+
): PatchResult {
|
|
46
|
+
const strategy = this.strategies.get(changeType);
|
|
47
|
+
|
|
48
|
+
if (!strategy) {
|
|
49
|
+
logger.warn(`No strategy found for change type: ${changeType}`);
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
patch: '',
|
|
53
|
+
newContent: '',
|
|
54
|
+
changeType,
|
|
55
|
+
error: `No strategy found for change type: ${changeType}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 检查是否允许此类型的 patch
|
|
60
|
+
const allowedTypes = configManager.getPatchConfig().allowed_types;
|
|
61
|
+
if (!allowedTypes.includes(changeType)) {
|
|
62
|
+
logger.warn(`Change type ${changeType} is not allowed`);
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
patch: '',
|
|
66
|
+
newContent: '',
|
|
67
|
+
changeType,
|
|
68
|
+
error: `Change type ${changeType} is not allowed`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.debug(`Generating patch with strategy: ${strategy.getName()}`);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const result = strategy.generate(currentContent, context);
|
|
76
|
+
|
|
77
|
+
if (result.success) {
|
|
78
|
+
logger.info(`Patch generated successfully`, {
|
|
79
|
+
strategy: strategy.getName(),
|
|
80
|
+
changeType,
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
logger.warn(`Patch generation failed`, {
|
|
84
|
+
strategy: strategy.getName(),
|
|
85
|
+
error: result.error,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error(`Strategy execution failed`, { error });
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
patch: '',
|
|
95
|
+
newContent: '',
|
|
96
|
+
changeType,
|
|
97
|
+
error: `Strategy execution failed: ${error}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 获取所有策略
|
|
104
|
+
*/
|
|
105
|
+
getStrategies(): BaseStrategy[] {
|
|
106
|
+
return Array.from(this.strategies.values());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 检查是否支持某个变更类型
|
|
111
|
+
*/
|
|
112
|
+
supportsChangeType(changeType: ChangeType): boolean {
|
|
113
|
+
return this.strategies.has(changeType);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 导出单例实例
|
|
118
|
+
export const patchGenerator = new PatchGenerator();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { BaseStrategy } from '../base-strategy.js';
|
|
2
|
+
import { createUnifiedDiff } from '../../../utils/diff.js';
|
|
3
|
+
import type { PatchResult } from '../../../types/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add Fallback 策略
|
|
7
|
+
* 在相关步骤后添加 fallback 说明
|
|
8
|
+
*/
|
|
9
|
+
export class AddFallbackStrategy extends BaseStrategy {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(
|
|
12
|
+
'add-fallback',
|
|
13
|
+
'Add fallback instructions after relevant steps',
|
|
14
|
+
'add_fallback'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
generate(currentContent: string, context: Record<string, unknown>): PatchResult {
|
|
19
|
+
try {
|
|
20
|
+
const pattern = context.pattern as string;
|
|
21
|
+
const reason = context.reason as string;
|
|
22
|
+
|
|
23
|
+
if (!pattern) {
|
|
24
|
+
return this.createFailureResult('Pattern not provided');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 查找插入点(在文件末尾或特定 section 后)
|
|
28
|
+
const lines = currentContent.split('\n');
|
|
29
|
+
let insertIndex = lines.length;
|
|
30
|
+
|
|
31
|
+
// 尝试找到 "## Fallback" 或类似的 section
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
if (lines[i]?.toLowerCase().includes('fallback') ||
|
|
34
|
+
lines[i]?.toLowerCase().includes('error handling')) {
|
|
35
|
+
// 在这个 section 后插入
|
|
36
|
+
insertIndex = i + 1;
|
|
37
|
+
while (insertIndex < lines.length && lines[insertIndex]?.startsWith('-')) {
|
|
38
|
+
insertIndex++;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 创建新的 fallback 内容
|
|
45
|
+
const fallbackContent = [
|
|
46
|
+
'',
|
|
47
|
+
`## Additional Fallback`,
|
|
48
|
+
'',
|
|
49
|
+
`- If the agent output does not include "${pattern}", manually add it`,
|
|
50
|
+
`- Reason: ${reason}`,
|
|
51
|
+
'',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// 插入内容
|
|
55
|
+
const newLines = [
|
|
56
|
+
...lines.slice(0, insertIndex),
|
|
57
|
+
...fallbackContent,
|
|
58
|
+
...lines.slice(insertIndex),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const newContent = newLines.join('\n');
|
|
62
|
+
const patch = createUnifiedDiff('skill.md', currentContent, newContent);
|
|
63
|
+
|
|
64
|
+
return this.createSuccessResult(patch, newContent);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return this.createFailureResult(`Failed to generate patch: ${error}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { BaseStrategy } from '../base-strategy.js';
|
|
2
|
+
import { createUnifiedDiff } from '../../../utils/diff.js';
|
|
3
|
+
import type { PatchResult } from '../../../types/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prune Noise 策略
|
|
7
|
+
* 删除被忽略的冗余说明
|
|
8
|
+
*/
|
|
9
|
+
export class PruneNoiseStrategy extends BaseStrategy {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(
|
|
12
|
+
'prune-noise',
|
|
13
|
+
'Remove redundant or ignored instructions',
|
|
14
|
+
'prune_noise'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
generate(currentContent: string, context: Record<string, unknown>): PatchResult {
|
|
19
|
+
try {
|
|
20
|
+
const section = context.section as string;
|
|
21
|
+
|
|
22
|
+
if (!section) {
|
|
23
|
+
return this.createFailureResult('Section not provided');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 查找要删除的 section
|
|
27
|
+
const lines = currentContent.split('\n');
|
|
28
|
+
const sectionIndex = lines.findIndex((line) =>
|
|
29
|
+
line.toLowerCase().includes(section.toLowerCase())
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (sectionIndex === -1) {
|
|
33
|
+
return this.createFailureResult(`Section "${section}" not found`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 找到 section 的结束位置
|
|
37
|
+
let endIndex = sectionIndex + 1;
|
|
38
|
+
const sectionLevel = lines[sectionIndex]?.match(/^(#{1,6})/)?.[1]?.length ?? 0;
|
|
39
|
+
|
|
40
|
+
while (endIndex < lines.length) {
|
|
41
|
+
const line = lines[endIndex];
|
|
42
|
+
const match = line?.match(/^(#{1,6})\s/);
|
|
43
|
+
|
|
44
|
+
if (match && match[1].length <= sectionLevel) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
endIndex++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 删除 section
|
|
52
|
+
const newLines = [
|
|
53
|
+
...lines.slice(0, sectionIndex),
|
|
54
|
+
...lines.slice(endIndex),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const newContent = newLines.join('\n');
|
|
58
|
+
const patch = createUnifiedDiff('skill.md', currentContent, newContent);
|
|
59
|
+
|
|
60
|
+
return this.createSuccessResult(patch, newContent);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return this.createFailureResult(`Failed to generate patch: ${error}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|