migrate-trained-skills.js
7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#!/usr/bin/env node
/**
* 迁移脚本:将已有训练的 skill 从 data/skills/ 移到 data/trained_skills/user_{id}/
*
* 用法:
* node scripts/migrate-trained-skills.js --dry-run # 预览
* node scripts/migrate-trained-skills.js --execute # 执行迁移
* node scripts/migrate-trained-skills.js --execute --user 2 # 指定归属用户
*/
const fs = require('fs');
const path = require('path');
// ── 配置 ──
const DATA_DIR = path.resolve(__dirname, '../../data');
const SKILLS_DATA_DIR = path.join(DATA_DIR, 'skills');
const TRAINED_SKILLS_DIR = path.join(DATA_DIR, 'trained_skills');
const DB_PATH = path.join(DATA_DIR, 'skills.sqlite');
// 系统 Agent 名单(不迁移)
const SYSTEM_SKILLS = new Set([
'master', 'coder', 'innovator', 'innovator-evolver', 'evolving',
'editor', 'verifier', 'gateway', 'benchmark',
'frontend-tester', 'frontend-orch', 'backend-orch',
'multi-agent-system', 'novel_writer', 'dual_column_layout',
'skill-creator', 'task-monitor', 'feishu-bot', 'whatsapp-bot',
'novel_outline', 'comedy_writing', 'novel_humor_insertion',
'aliyun_oss_operator', 'brd_writer', 'anime_screenwriter',
'lemon_skill_store', 'arenask',
// system L1/L2/L3 agents
'l1-api-request', 'l1-data-extract', 'l1-diff-checker',
'l1-file-read', 'l1-kv-store', 'l1-search-engine',
'l1-shell-exec', 'l1-text-transform', 'l1-web-fetch',
'l1-webhook-push', 'l2-digest', 'l2-price-tracker',
'l2-web-research', 'l3-deep-research', 'l3-hunt',
]);
// ── 命令行参数 ──
const args = process.argv.slice(2);
const dryRun = !args.includes('--execute');
const userIdArg = args.includes('--user') ? args[args.indexOf('--user') + 1] : null;
const DEFAULT_USER_ID = userIdArg || '2'; // 默认归属用户 id=2 (yibo)
console.log(`\n${'='.repeat(60)}`);
console.log(` 训练 Skill 迁移工具`);
console.log(` 模式: ${dryRun ? '🔍 DRY RUN (预览)' : '🚀 EXECUTE (执行)'}`);
console.log(` 默认用户: user_${DEFAULT_USER_ID}`);
console.log(`${'='.repeat(60)}\n`);
// ── Step 1: 找出需要迁移的 skill ──
// 判定标准:data/skills/ 中有 hunt/digest 实际训练数据的 skill,
// 或者名称以 l1-/l2-/l3- 开头的系统训练 skill
function findTrainedSkills() {
if (!fs.existsSync(SKILLS_DATA_DIR)) {
console.log('❌ data/skills/ 不存在,无需迁移');
return [];
}
const dirs = fs.readdirSync(SKILLS_DATA_DIR, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
const trained = [];
for (const skillName of dirs) {
if (SYSTEM_SKILLS.has(skillName)) continue;
const skillDir = path.join(SKILLS_DATA_DIR, skillName);
// 检查是否有实际的训练产物数据
const huntDir = path.join(skillDir, 'hunt');
const hunterDir = path.join(skillDir, 'hunter');
const digestDir = path.join(skillDir, 'digest');
const sourceDir = path.join(skillDir, 'source');
const hasHuntData = _hasFiles(huntDir) || _hasFiles(hunterDir);
const hasDigestData = _hasFiles(digestDir);
const hasSourceData = _hasFiles(sourceDir);
const hasSkillMd = fs.existsSync(path.join(skillDir, 'SKILL.md'));
const isLevelPrefixed = /^l[123]-/.test(skillName);
// 只迁移有训练数据的 skill(有 hunt/digest 内容 或 l1-/l2-/l3- 前缀的自训练 skill)
if (hasHuntData || hasDigestData || isLevelPrefixed) {
trained.push({
skillName,
sourceDir: skillDir,
hasSkillMd,
hasHuntData,
hasDigestData,
hasSourceData,
isLevelPrefixed,
});
}
}
return trained;
}
/** 检查目录是否存在且有文件 */
function _hasFiles(dir) {
if (!fs.existsSync(dir)) return false;
try {
const entries = fs.readdirSync(dir);
return entries.length > 0;
} catch { return false; }
}
// ── Step 2: 迁移 ──
function migrateSkill(skill, userId) {
const targetDir = path.join(TRAINED_SKILLS_DIR, `user_${userId}`, skill.skillName);
console.log(` 📦 ${skill.skillName}`);
console.log(` From: ${skill.sourceDir}`);
console.log(` To: ${targetDir}`);
console.log(` Has: ${[
skill.hasSkillMd && 'SKILL.md',
skill.hasHuntData && 'hunt/',
skill.hasDigestData && 'digest/',
skill.hasSourceData && 'source/',
skill.isLevelPrefixed && '(l-prefix)',
].filter(Boolean).join(', ')}`);
if (dryRun) {
console.log(` ⏭️ 跳过 (dry-run)\n`);
return { migrated: false, targetDir };
}
// 创建目标目录
fs.mkdirSync(targetDir, { recursive: true });
// 递归复制
copyDirRecursive(skill.sourceDir, targetDir);
console.log(` ✅ 复制完成\n`);
return { migrated: true, targetDir };
}
function copyDirRecursive(src, dest) {
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirRecursive(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
// ── Step 3: 更新 SQLite ──
function updateDatabase(results) {
if (dryRun) {
console.log('\n📋 数据库更新 (dry-run 跳过):');
results.forEach(r => {
console.log(` UPDATE skills SET directory='${r.targetDir}', user_id='${DEFAULT_USER_ID}', storage_type='trained' WHERE skill_name='${r.skillName}'`);
});
return;
}
try {
const Database = require('better-sqlite3');
const db = new Database(DB_PATH);
const stmt = db.prepare(`
UPDATE skills
SET directory = ?, user_id = ?, storage_type = 'trained', last_updated = ?
WHERE skill_name = ?
`);
const now = new Date().toISOString();
let updated = 0;
for (const r of results) {
const info = stmt.run(r.targetDir, DEFAULT_USER_ID, now, r.skillName);
if (info.changes > 0) {
console.log(` ✅ ${r.skillName} → user_${DEFAULT_USER_ID}`);
updated++;
} else {
console.log(` ⚠️ ${r.skillName} 在数据库中未找到`);
}
}
db.close();
console.log(`\n📊 数据库更新: ${updated}/${results.length} 条记录`);
} catch (err) {
console.error(`\n❌ 数据库更新失败: ${err.message}`);
}
}
// ── Main ──
function main() {
const trained = findTrainedSkills();
if (trained.length === 0) {
console.log('✅ 没有找到需要迁移的训练 Skill');
return;
}
console.log(`🔍 发现 ${trained.length} 个训练 Skill:\n`);
const results = [];
for (const skill of trained) {
const { migrated, targetDir } = migrateSkill(skill, DEFAULT_USER_ID);
results.push({ skillName: skill.skillName, targetDir, migrated });
}
// 更新数据库
updateDatabase(results.filter(r => r.migrated || dryRun));
console.log(`\n${'─'.repeat(60)}`);
if (dryRun) {
console.log(`\n💡 这是 DRY RUN。运行 --execute 真正执行迁移:`);
console.log(` node scripts/migrate-trained-skills.js --execute\n`);
} else {
console.log(`\n✅ 迁移完成!共迁移 ${results.filter(r => r.migrated).length} 个 Skill`);
console.log(`\n⚠️ 原始目录 data/skills/ 中的文件已保留(未删除)。`);
console.log(` 如确认无误后可手动删除原始目录。\n`);
}
}
main();