migrate-agent-naming.js
6.06 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
#!/usr/bin/env node
/**
* migrate-agent-naming.js — 存量 system agent 按 27-NAMING §3.4 归位
*
* 依据: docs/todo/system-design/27-naming-conventions.md §3.4(R-15.3 定案)
* 命名轴是「节点在哪一层图里」,把散落的老 agent 归到它实际所处的层级:
*
* innovator → org-innovator type='cxo',是 org graph 层的节点
* hunter → innovator-hunt retrain-pipeline.js:161,innovator work graph 第 1 阶段
* digester → innovator-digest 同上第 2 阶段(行 179,输入是 hunter 产出)
* evolver → innovator-evolver 同上第 3 阶段(行 196)
*
* 迁移内容:
* 目录 system/agents/{旧}/ → system/agents/{新}/
* system/templates/{旧}/ → system/templates/{新}/
* DB system_agents 的 id 与 legacy_path
* 实例 data/companies/*\/agents/{旧}/ 与 company.json.agents[](若有公司雇过)
*
* ⚠️ 明确不动的东西(改了会坏):
* - skill 内部子目录名 `{skillDir}/hunter/knowhow.md` —— 那是 hunt/digest/evolve
* 三阶段的产物目录约定,不是 agent 名(open-api.js:402、retrain-pipeline.js:148/374、
* evolution-loop.js:178、sync-skills.js:62 的 INTERNAL_DIRS 等)
* - skill 命名空间的白名单 —— skill-dir-manager.js 的 CORE_SKILLS、
* migrate-trained-skills.js 的 SYSTEM_SKILLS,那里的 'innovator' 指的是同名 skill
* - `evolver-youtube-crawler` —— 27-NAMING §3.4 列为待定,按精确目录名匹配,不会误伤
* - conversations/ 与 system/tasklog/ 下的历史记录 —— 运行态数据,不改写历史
*
* 用法:
* node scripts/migrate-agent-naming.js --dry-run
* node scripts/migrate-agent-naming.js
*
* 幂等: 已迁移过(新名存在且旧名不存在)会被跳过,可重复执行。
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
const DRY = process.argv.includes('--dry-run');
/** 旧名 → 新名。顺序无关,全部按精确名匹配,不做前缀/正则匹配。 */
const RENAMES = {
innovator: 'org-innovator',
hunter: 'innovator-hunt',
digester: 'innovator-digest',
evolver: 'innovator-evolver',
};
const log = (...a) => console.log(DRY ? '[dry-run]' : '[migrate]', ...a);
const stats = { dirs: 0, dbRows: 0, companyInstances: 0, skipped: 0 };
/** 用 git mv 保留历史;不在 git 索引里的目录退回 fs.renameSync */
function moveDir(from, to) {
if (DRY) return;
try {
execFileSync('git', ['mv', from, to], { cwd: ROOT, stdio: 'pipe' });
} catch {
fs.renameSync(path.join(ROOT, from), path.join(ROOT, to));
}
}
function migrateDirs() {
for (const [oldName, newName] of Object.entries(RENAMES)) {
for (const base of ['system/agents', 'system/templates']) {
const from = `${base}/${oldName}`;
const to = `${base}/${newName}`;
const fromAbs = path.join(ROOT, from);
const toAbs = path.join(ROOT, to);
if (!fs.existsSync(fromAbs)) {
if (fs.existsSync(toAbs)) { stats.skipped++; } // 已迁移过
continue;
}
if (fs.existsSync(toAbs)) {
log(`⚠️ 跳过(新旧同时存在,需人工处理): ${from} / ${to}`);
stats.skipped++;
continue;
}
log(`目录: ${from} → ${to}`);
moveDir(from, to);
stats.dirs++;
}
}
}
function migrateDb() {
const { getDb } = require(path.join(ROOT, 'server/services/graph-db'));
const db = getDb();
const sel = db.prepare('SELECT id, legacy_path FROM system_agents WHERE id = ?');
const upd = db.prepare('UPDATE system_agents SET id = ?, legacy_path = ?, updated_at = ? WHERE id = ?');
const now = new Date().toISOString();
const tx = db.transaction(() => {
for (const [oldName, newName] of Object.entries(RENAMES)) {
const row = sel.get(oldName);
if (!row) continue;
const newPath = (row.legacy_path || '').replace(
new RegExp(`(^|/)${oldName}$`), `$1${newName}`
);
log(`DB: system_agents ${oldName} → ${newName}(legacy_path: ${row.legacy_path} → ${newPath})`);
if (!DRY) upd.run(newName, newPath, now, oldName);
stats.dbRows++;
}
});
tx();
}
/** 有公司雇过这四个的话,同步改私有目录名与 company.json.agents[] */
function migrateCompanyInstances() {
const companiesDir = path.join(ROOT, 'data/companies');
if (!fs.existsSync(companiesDir)) return;
for (const d of fs.readdirSync(companiesDir, { withFileTypes: true })) {
if (!d.isDirectory()) continue;
const cid = d.name;
for (const [oldName, newName] of Object.entries(RENAMES)) {
const from = path.join(companiesDir, cid, 'agents', oldName);
const to = path.join(companiesDir, cid, 'agents', newName);
if (!fs.existsSync(from) || fs.existsSync(to)) continue;
log(`实例: ${cid}/agents/${oldName} → ${newName}`);
if (!DRY) fs.renameSync(from, to);
stats.companyInstances++;
}
const cjPath = path.join(companiesDir, cid, 'company.json');
if (!fs.existsSync(cjPath)) continue;
let cj;
try { cj = JSON.parse(fs.readFileSync(cjPath, 'utf-8')); } catch { continue; }
if (!Array.isArray(cj.agents)) continue;
const next = cj.agents.map(a => RENAMES[a] || a);
if (JSON.stringify(next) !== JSON.stringify(cj.agents)) {
log(`实例: ${cid}/company.json agents[] 已更新`);
if (!DRY) {
cj.agents = next;
fs.writeFileSync(cjPath, JSON.stringify(cj, null, 2) + '\n', 'utf-8');
}
}
}
}
function main() {
log(DRY ? '=== DRY RUN(不写盘)===' : '=== 开始迁移 ===');
migrateDirs();
migrateDb();
migrateCompanyInstances();
console.log('\n' + (DRY ? '[dry-run] ' : '') + '汇总:', JSON.stringify(stats));
if (DRY) console.log('这是 dry-run,未做任何改动。去掉 --dry-run 实际执行。');
else console.log('完成。代码引用点(retrain-pipeline.js / agent-chat.js / company.js)需另行修改,见 27-NAMING §3.4。');
}
if (require.main === module) main();
module.exports = { RENAMES };