@claude-flow/cli 3.5.43 → 3.5.44
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/README.md +199 -196
- package/dist/src/commands/claims.d.ts.map +1 -1
- package/dist/src/commands/claims.js +352 -34
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +66 -34
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/deployment.d.ts.map +1 -1
- package/dist/src/commands/deployment.js +538 -46
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +531 -39
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/providers.d.ts.map +1 -1
- package/dist/src/commands/providers.js +163 -29
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +2 -3
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +3 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-server.d.ts +3 -1
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +31 -4
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.d.ts +15 -0
- package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/guidance-tools.js +617 -0
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +1 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/services/config-file-manager.d.ts +37 -0
- package/dist/src/services/config-file-manager.d.ts.map +1 -0
- package/dist/src/services/config-file-manager.js +224 -0
- package/dist/src/services/config-file-manager.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Migration tools for V2 to V3 transition
|
|
4
4
|
*/
|
|
5
5
|
import { output } from '../output.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
6
8
|
// Migration targets
|
|
7
9
|
const MIGRATION_TARGETS = [
|
|
8
10
|
{ value: 'config', label: 'Configuration', hint: 'Migrate configuration files' },
|
|
@@ -17,13 +19,118 @@ const MIGRATION_TARGETS = [
|
|
|
17
19
|
const statusCommand = {
|
|
18
20
|
name: 'status',
|
|
19
21
|
description: 'Check migration status',
|
|
20
|
-
action: async (
|
|
21
|
-
|
|
22
|
+
action: async (ctx) => {
|
|
23
|
+
const cwd = ctx.cwd || process.cwd();
|
|
24
|
+
const components = [];
|
|
25
|
+
// Check v2 config: claude-flow.config.json with version "2" or missing version
|
|
26
|
+
const v2ConfigPath = path.join(cwd, 'claude-flow.config.json');
|
|
27
|
+
const v3ConfigDir = path.join(cwd, '.claude-flow');
|
|
28
|
+
let hasV2Config = false;
|
|
29
|
+
let hasV3Config = false;
|
|
30
|
+
try {
|
|
31
|
+
if (fs.existsSync(v2ConfigPath)) {
|
|
32
|
+
const raw = fs.readFileSync(v2ConfigPath, 'utf-8');
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (parsed.version === '2' || parsed.version === 2 || !parsed.version) {
|
|
35
|
+
hasV2Config = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore parse errors */ }
|
|
40
|
+
try {
|
|
41
|
+
hasV3Config = fs.existsSync(v3ConfigDir) && fs.statSync(v3ConfigDir).isDirectory();
|
|
42
|
+
}
|
|
43
|
+
catch { /* ignore */ }
|
|
44
|
+
if (hasV2Config && hasV3Config) {
|
|
45
|
+
components.push({ component: 'Config', status: 'v2 + v3', migrationNeeded: 'no' });
|
|
46
|
+
}
|
|
47
|
+
else if (hasV2Config) {
|
|
48
|
+
components.push({ component: 'Config', status: 'v2', migrationNeeded: 'yes' });
|
|
49
|
+
}
|
|
50
|
+
else if (hasV3Config) {
|
|
51
|
+
components.push({ component: 'Config', status: 'v3', migrationNeeded: 'no' });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
components.push({ component: 'Config', status: 'missing', migrationNeeded: 'no' });
|
|
55
|
+
}
|
|
56
|
+
// Check v2 memory: ./data/memory/*.json or memory.db
|
|
57
|
+
const v2MemoryDir = path.join(cwd, 'data', 'memory');
|
|
58
|
+
let hasV2MemoryJson = false;
|
|
59
|
+
let hasV2MemoryDb = false;
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(v2MemoryDir)) {
|
|
62
|
+
const files = fs.readdirSync(v2MemoryDir);
|
|
63
|
+
hasV2MemoryJson = files.some(f => f.endsWith('.json'));
|
|
64
|
+
hasV2MemoryDb = files.includes('memory.db');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch { /* ignore */ }
|
|
68
|
+
if (hasV2MemoryJson || hasV2MemoryDb) {
|
|
69
|
+
components.push({ component: 'Memory', status: 'v2', migrationNeeded: 'yes' });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
components.push({ component: 'Memory', status: 'missing', migrationNeeded: 'no' });
|
|
73
|
+
}
|
|
74
|
+
// Check v2 sessions: ./data/sessions/
|
|
75
|
+
const v2SessionsDir = path.join(cwd, 'data', 'sessions');
|
|
76
|
+
let hasV2Sessions = false;
|
|
77
|
+
try {
|
|
78
|
+
if (fs.existsSync(v2SessionsDir)) {
|
|
79
|
+
const files = fs.readdirSync(v2SessionsDir);
|
|
80
|
+
hasV2Sessions = files.length > 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { /* ignore */ }
|
|
84
|
+
if (hasV2Sessions) {
|
|
85
|
+
components.push({ component: 'Sessions', status: 'v2', migrationNeeded: 'yes' });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
components.push({ component: 'Sessions', status: 'missing', migrationNeeded: 'no' });
|
|
89
|
+
}
|
|
90
|
+
// Check migration state
|
|
91
|
+
const migrationStatePath = path.join(cwd, '.claude-flow', 'migration-state.json');
|
|
92
|
+
let migrationState = null;
|
|
93
|
+
try {
|
|
94
|
+
if (fs.existsSync(migrationStatePath)) {
|
|
95
|
+
const raw = fs.readFileSync(migrationStatePath, 'utf-8');
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
migrationState = parsed.status || 'unknown';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
if (migrationState) {
|
|
102
|
+
components.push({ component: 'Migration State', status: migrationState, migrationNeeded: 'no' });
|
|
103
|
+
}
|
|
104
|
+
// Display results
|
|
105
|
+
if (ctx.flags.format === 'json') {
|
|
106
|
+
output.printJson({ components, migrationState });
|
|
107
|
+
return { success: true, data: { components, migrationState } };
|
|
108
|
+
}
|
|
109
|
+
output.writeln();
|
|
110
|
+
output.writeln(output.bold('Migration Status'));
|
|
111
|
+
output.writeln();
|
|
112
|
+
output.printTable({
|
|
113
|
+
columns: [
|
|
114
|
+
{ key: 'component', header: 'Component', width: 20 },
|
|
115
|
+
{ key: 'status', header: 'Status', width: 15 },
|
|
116
|
+
{ key: 'migrationNeeded', header: 'Migration Needed', width: 20 }
|
|
117
|
+
],
|
|
118
|
+
data: components.map(c => ({
|
|
119
|
+
component: c.component,
|
|
120
|
+
status: formatMigrationStatus(c.status),
|
|
121
|
+
migrationNeeded: c.migrationNeeded === 'yes' ? output.warning('yes') : output.dim('no')
|
|
122
|
+
})),
|
|
123
|
+
border: false
|
|
124
|
+
});
|
|
125
|
+
const needsMigration = components.some(c => c.migrationNeeded === 'yes');
|
|
22
126
|
output.writeln();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
127
|
+
if (needsMigration) {
|
|
128
|
+
output.printInfo('V2 artifacts detected. Run "claude-flow migrate run" to migrate.');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
output.printSuccess('No migration needed.');
|
|
132
|
+
}
|
|
133
|
+
return { success: true, data: { components, needsMigration } };
|
|
27
134
|
}
|
|
28
135
|
};
|
|
29
136
|
// Run migration
|
|
@@ -58,14 +165,200 @@ const runCommand = {
|
|
|
58
165
|
default: false
|
|
59
166
|
}
|
|
60
167
|
],
|
|
61
|
-
action: async (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
168
|
+
action: async (ctx) => {
|
|
169
|
+
const cwd = ctx.cwd || process.cwd();
|
|
170
|
+
const dryRun = ctx.flags['dry-run'] === true;
|
|
171
|
+
const skipBackup = ctx.flags.backup === false;
|
|
172
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
173
|
+
const v3Dir = path.join(cwd, '.claude-flow');
|
|
174
|
+
const backupDir = path.join(v3Dir, 'backup', `v2-${timestamp}`);
|
|
175
|
+
const migrationStatePath = path.join(v3Dir, 'migration-state.json');
|
|
176
|
+
const migrated = [];
|
|
177
|
+
const skipped = [];
|
|
178
|
+
output.writeln();
|
|
179
|
+
output.writeln(output.bold('V2 to V3 Migration'));
|
|
180
|
+
if (dryRun) {
|
|
181
|
+
output.printWarning('Dry run mode — no changes will be made.');
|
|
182
|
+
}
|
|
183
|
+
output.writeln();
|
|
184
|
+
// Ensure .claude-flow directory exists
|
|
185
|
+
if (!dryRun) {
|
|
186
|
+
fs.mkdirSync(v3Dir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
// --- Backup ---
|
|
189
|
+
if (!skipBackup && !dryRun) {
|
|
190
|
+
output.writeln(output.dim('Creating backup...'));
|
|
191
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
// --- Config migration ---
|
|
194
|
+
const v2ConfigPath = path.join(cwd, 'claude-flow.config.json');
|
|
195
|
+
try {
|
|
196
|
+
if (fs.existsSync(v2ConfigPath)) {
|
|
197
|
+
const raw = fs.readFileSync(v2ConfigPath, 'utf-8');
|
|
198
|
+
const parsed = JSON.parse(raw);
|
|
199
|
+
if (parsed.version === '2' || parsed.version === 2 || !parsed.version) {
|
|
200
|
+
if (dryRun) {
|
|
201
|
+
output.printInfo(`Would migrate config: ${v2ConfigPath}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Backup
|
|
205
|
+
if (!skipBackup) {
|
|
206
|
+
fs.copyFileSync(v2ConfigPath, path.join(backupDir, 'claude-flow.config.json'));
|
|
207
|
+
}
|
|
208
|
+
// Transform to v3 format
|
|
209
|
+
const v3Config = { ...parsed, version: '3' };
|
|
210
|
+
// Rename swarm.mode -> swarm.topology if present
|
|
211
|
+
if (v3Config.swarm && typeof v3Config.swarm === 'object') {
|
|
212
|
+
const swarm = v3Config.swarm;
|
|
213
|
+
if ('mode' in swarm && !('topology' in swarm)) {
|
|
214
|
+
swarm.topology = swarm.mode;
|
|
215
|
+
delete swarm.mode;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Rename memory.type -> memory.backend if present
|
|
219
|
+
if (v3Config.memory && typeof v3Config.memory === 'object') {
|
|
220
|
+
const mem = v3Config.memory;
|
|
221
|
+
if ('type' in mem && !('backend' in mem)) {
|
|
222
|
+
mem.backend = mem.type;
|
|
223
|
+
delete mem.type;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const v3ConfigPath = path.join(v3Dir, 'config.json');
|
|
227
|
+
fs.writeFileSync(v3ConfigPath, JSON.stringify(v3Config, null, 2));
|
|
228
|
+
output.printSuccess(`Config migrated to ${v3ConfigPath}`);
|
|
229
|
+
}
|
|
230
|
+
migrated.push('config');
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
output.printInfo('Config already at v3 — skipping.');
|
|
234
|
+
skipped.push('config');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
output.writeln(output.dim('No v2 config found — skipping config migration.'));
|
|
239
|
+
skipped.push('config');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
output.printError('Config migration failed', String(err));
|
|
244
|
+
skipped.push('config');
|
|
245
|
+
}
|
|
246
|
+
// --- Memory migration ---
|
|
247
|
+
const v2MemoryDir = path.join(cwd, 'data', 'memory');
|
|
248
|
+
try {
|
|
249
|
+
if (fs.existsSync(v2MemoryDir)) {
|
|
250
|
+
const files = fs.readdirSync(v2MemoryDir);
|
|
251
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
252
|
+
const hasDb = files.includes('memory.db');
|
|
253
|
+
if (jsonFiles.length > 0 || hasDb) {
|
|
254
|
+
if (dryRun) {
|
|
255
|
+
output.printInfo(`Would migrate memory: ${jsonFiles.length} JSON files, ${hasDb ? '1 DB' : 'no DB'}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Backup memory files
|
|
259
|
+
if (!skipBackup) {
|
|
260
|
+
const memBackup = path.join(backupDir, 'data', 'memory');
|
|
261
|
+
fs.mkdirSync(memBackup, { recursive: true });
|
|
262
|
+
for (const f of files) {
|
|
263
|
+
const src = path.join(v2MemoryDir, f);
|
|
264
|
+
if (fs.statSync(src).isFile()) {
|
|
265
|
+
fs.copyFileSync(src, path.join(memBackup, f));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
output.printSuccess(`Memory files backed up (${jsonFiles.length} JSON, ${hasDb ? '1 DB' : '0 DB'}).`);
|
|
270
|
+
output.printInfo('Run "claude-flow memory init --force" to import v2 memory into v3 AgentDB.');
|
|
271
|
+
}
|
|
272
|
+
migrated.push('memory');
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
output.writeln(output.dim('No v2 memory files found — skipping.'));
|
|
276
|
+
skipped.push('memory');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
output.writeln(output.dim('No v2 memory directory found — skipping.'));
|
|
281
|
+
skipped.push('memory');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
output.printError('Memory migration failed', String(err));
|
|
286
|
+
skipped.push('memory');
|
|
287
|
+
}
|
|
288
|
+
// --- Session migration ---
|
|
289
|
+
const v2SessionsDir = path.join(cwd, 'data', 'sessions');
|
|
290
|
+
try {
|
|
291
|
+
if (fs.existsSync(v2SessionsDir)) {
|
|
292
|
+
const files = fs.readdirSync(v2SessionsDir);
|
|
293
|
+
if (files.length > 0) {
|
|
294
|
+
if (dryRun) {
|
|
295
|
+
output.printInfo(`Would migrate sessions: ${files.length} files from ${v2SessionsDir}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const v3SessionsDir = path.join(v3Dir, 'sessions');
|
|
299
|
+
fs.mkdirSync(v3SessionsDir, { recursive: true });
|
|
300
|
+
// Backup
|
|
301
|
+
if (!skipBackup) {
|
|
302
|
+
const sessBackup = path.join(backupDir, 'data', 'sessions');
|
|
303
|
+
fs.mkdirSync(sessBackup, { recursive: true });
|
|
304
|
+
for (const f of files) {
|
|
305
|
+
const src = path.join(v2SessionsDir, f);
|
|
306
|
+
if (fs.statSync(src).isFile()) {
|
|
307
|
+
fs.copyFileSync(src, path.join(sessBackup, f));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Copy to v3 location
|
|
312
|
+
for (const f of files) {
|
|
313
|
+
const src = path.join(v2SessionsDir, f);
|
|
314
|
+
if (fs.statSync(src).isFile()) {
|
|
315
|
+
fs.copyFileSync(src, path.join(v3SessionsDir, f));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
output.printSuccess(`Sessions migrated: ${files.length} files to ${v3SessionsDir}`);
|
|
319
|
+
}
|
|
320
|
+
migrated.push('sessions');
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
output.writeln(output.dim('No v2 session files found — skipping.'));
|
|
324
|
+
skipped.push('sessions');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
output.writeln(output.dim('No v2 sessions directory found — skipping.'));
|
|
329
|
+
skipped.push('sessions');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
output.printError('Session migration failed', String(err));
|
|
334
|
+
skipped.push('sessions');
|
|
335
|
+
}
|
|
336
|
+
// --- Save migration state ---
|
|
337
|
+
if (!dryRun && migrated.length > 0) {
|
|
338
|
+
const state = {
|
|
339
|
+
status: 'completed',
|
|
340
|
+
timestamp,
|
|
341
|
+
backupPath: skipBackup ? null : backupDir,
|
|
342
|
+
migrated,
|
|
343
|
+
skipped
|
|
344
|
+
};
|
|
345
|
+
fs.writeFileSync(migrationStatePath, JSON.stringify(state, null, 2));
|
|
346
|
+
output.writeln();
|
|
347
|
+
output.printSuccess(`Migration state saved to ${migrationStatePath}`);
|
|
348
|
+
}
|
|
349
|
+
// Summary
|
|
350
|
+
output.writeln();
|
|
351
|
+
if (dryRun) {
|
|
352
|
+
output.printInfo(`Dry run complete. ${migrated.length} component(s) would be migrated.`);
|
|
353
|
+
}
|
|
354
|
+
else if (migrated.length > 0) {
|
|
355
|
+
output.printSuccess(`Migration complete. ${migrated.length} component(s) migrated: ${migrated.join(', ')}`);
|
|
356
|
+
output.printInfo('Run "claude-flow migrate verify" to validate the migration.');
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
output.printInfo('Nothing to migrate.');
|
|
360
|
+
}
|
|
361
|
+
return { success: true, data: { migrated, skipped, dryRun } };
|
|
69
362
|
}
|
|
70
363
|
};
|
|
71
364
|
// Verify migration
|
|
@@ -80,13 +373,126 @@ const verifyCommand = {
|
|
|
80
373
|
default: false
|
|
81
374
|
}
|
|
82
375
|
],
|
|
83
|
-
action: async (
|
|
84
|
-
|
|
376
|
+
action: async (ctx) => {
|
|
377
|
+
const cwd = ctx.cwd || process.cwd();
|
|
378
|
+
const v3Dir = path.join(cwd, '.claude-flow');
|
|
379
|
+
const migrationStatePath = path.join(v3Dir, 'migration-state.json');
|
|
380
|
+
const checks = [];
|
|
381
|
+
let allPassed = true;
|
|
382
|
+
output.writeln();
|
|
383
|
+
output.writeln(output.bold('Migration Verification'));
|
|
85
384
|
output.writeln();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
385
|
+
// Check 1: Migration state file exists
|
|
386
|
+
let migrationState = null;
|
|
387
|
+
try {
|
|
388
|
+
if (fs.existsSync(migrationStatePath)) {
|
|
389
|
+
const raw = fs.readFileSync(migrationStatePath, 'utf-8');
|
|
390
|
+
migrationState = JSON.parse(raw);
|
|
391
|
+
checks.push({ check: 'Migration state file', result: 'passed' });
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
checks.push({ check: 'Migration state file', result: 'failed' });
|
|
395
|
+
allPassed = false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
checks.push({ check: 'Migration state file', result: 'failed' });
|
|
400
|
+
allPassed = false;
|
|
401
|
+
}
|
|
402
|
+
// Check 2: V3 config exists and is valid JSON
|
|
403
|
+
const v3ConfigPath = path.join(v3Dir, 'config.json');
|
|
404
|
+
try {
|
|
405
|
+
if (fs.existsSync(v3ConfigPath)) {
|
|
406
|
+
const raw = fs.readFileSync(v3ConfigPath, 'utf-8');
|
|
407
|
+
JSON.parse(raw); // validate JSON
|
|
408
|
+
checks.push({ check: 'V3 config (valid JSON)', result: 'passed' });
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
// Config might not have been migrated if there was no v2 config
|
|
412
|
+
const wasMigrated = migrationState &&
|
|
413
|
+
Array.isArray(migrationState.migrated) &&
|
|
414
|
+
migrationState.migrated.includes('config');
|
|
415
|
+
if (wasMigrated) {
|
|
416
|
+
checks.push({ check: 'V3 config (valid JSON)', result: 'failed' });
|
|
417
|
+
allPassed = false;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
checks.push({ check: 'V3 config (valid JSON)', result: 'skipped' });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
checks.push({ check: 'V3 config (valid JSON)', result: 'failed' });
|
|
426
|
+
allPassed = false;
|
|
427
|
+
}
|
|
428
|
+
// Check 3: Backup exists
|
|
429
|
+
if (migrationState && migrationState.backupPath) {
|
|
430
|
+
const backupPath = migrationState.backupPath;
|
|
431
|
+
try {
|
|
432
|
+
if (fs.existsSync(backupPath) && fs.statSync(backupPath).isDirectory()) {
|
|
433
|
+
checks.push({ check: 'Backup directory', result: 'passed' });
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
checks.push({ check: 'Backup directory', result: 'failed' });
|
|
437
|
+
allPassed = false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
checks.push({ check: 'Backup directory', result: 'failed' });
|
|
442
|
+
allPassed = false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (migrationState && migrationState.backupPath === null) {
|
|
446
|
+
checks.push({ check: 'Backup directory', result: 'skipped (backup was disabled)' });
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
checks.push({ check: 'Backup directory', result: 'failed' });
|
|
450
|
+
allPassed = false;
|
|
451
|
+
}
|
|
452
|
+
// Check 4: V3 sessions directory if sessions were migrated
|
|
453
|
+
if (migrationState &&
|
|
454
|
+
Array.isArray(migrationState.migrated) &&
|
|
455
|
+
migrationState.migrated.includes('sessions')) {
|
|
456
|
+
const v3Sessions = path.join(v3Dir, 'sessions');
|
|
457
|
+
try {
|
|
458
|
+
if (fs.existsSync(v3Sessions) && fs.readdirSync(v3Sessions).length > 0) {
|
|
459
|
+
checks.push({ check: 'V3 sessions directory', result: 'passed' });
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
checks.push({ check: 'V3 sessions directory', result: 'failed' });
|
|
463
|
+
allPassed = false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
checks.push({ check: 'V3 sessions directory', result: 'failed' });
|
|
468
|
+
allPassed = false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Display
|
|
472
|
+
if (ctx.flags.format === 'json') {
|
|
473
|
+
output.printJson({ checks, allPassed });
|
|
474
|
+
return { success: allPassed, data: { checks, allPassed } };
|
|
475
|
+
}
|
|
476
|
+
output.printTable({
|
|
477
|
+
columns: [
|
|
478
|
+
{ key: 'check', header: 'Check', width: 30 },
|
|
479
|
+
{ key: 'result', header: 'Result', width: 35 }
|
|
480
|
+
],
|
|
481
|
+
data: checks.map(c => ({
|
|
482
|
+
check: c.check,
|
|
483
|
+
result: formatMigrationStatus(c.result)
|
|
484
|
+
})),
|
|
485
|
+
border: false
|
|
486
|
+
});
|
|
487
|
+
output.writeln();
|
|
488
|
+
if (allPassed) {
|
|
489
|
+
output.printSuccess('All verification checks passed.');
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
output.printError('Some verification checks failed.');
|
|
493
|
+
output.printInfo('Run "claude-flow migrate run" to re-run the migration, or "migrate rollback" to restore from backup.');
|
|
494
|
+
}
|
|
495
|
+
return { success: allPassed, data: { checks, allPassed }, exitCode: allPassed ? 0 : 1 };
|
|
90
496
|
}
|
|
91
497
|
};
|
|
92
498
|
// Rollback migration
|
|
@@ -107,13 +513,94 @@ const rollbackCommand = {
|
|
|
107
513
|
default: false
|
|
108
514
|
}
|
|
109
515
|
],
|
|
110
|
-
action: async (
|
|
111
|
-
|
|
516
|
+
action: async (ctx) => {
|
|
517
|
+
const cwd = ctx.cwd || process.cwd();
|
|
518
|
+
const v3Dir = path.join(cwd, '.claude-flow');
|
|
519
|
+
const migrationStatePath = path.join(v3Dir, 'migration-state.json');
|
|
520
|
+
output.writeln();
|
|
521
|
+
output.writeln(output.bold('Migration Rollback'));
|
|
112
522
|
output.writeln();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
523
|
+
// Read migration state
|
|
524
|
+
let migrationState;
|
|
525
|
+
try {
|
|
526
|
+
if (!fs.existsSync(migrationStatePath)) {
|
|
527
|
+
output.printError('No migration state found.', 'Run "migrate run" first before attempting rollback.');
|
|
528
|
+
return { success: false, exitCode: 1 };
|
|
529
|
+
}
|
|
530
|
+
const raw = fs.readFileSync(migrationStatePath, 'utf-8');
|
|
531
|
+
migrationState = JSON.parse(raw);
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
output.printError('Failed to read migration state', String(err));
|
|
535
|
+
return { success: false, exitCode: 1 };
|
|
536
|
+
}
|
|
537
|
+
const backupPath = migrationState.backupPath;
|
|
538
|
+
if (!backupPath) {
|
|
539
|
+
output.printError('No backup path in migration state.', 'Migration was run with --no-backup. Cannot rollback.');
|
|
540
|
+
return { success: false, exitCode: 1 };
|
|
541
|
+
}
|
|
542
|
+
if (!fs.existsSync(backupPath)) {
|
|
543
|
+
output.printError('Backup directory not found.', `Expected: ${backupPath}`);
|
|
544
|
+
return { success: false, exitCode: 1 };
|
|
545
|
+
}
|
|
546
|
+
const restored = [];
|
|
547
|
+
try {
|
|
548
|
+
// Restore config
|
|
549
|
+
const backupConfig = path.join(backupPath, 'claude-flow.config.json');
|
|
550
|
+
if (fs.existsSync(backupConfig)) {
|
|
551
|
+
const destConfig = path.join(cwd, 'claude-flow.config.json');
|
|
552
|
+
fs.copyFileSync(backupConfig, destConfig);
|
|
553
|
+
// Remove v3 config
|
|
554
|
+
const v3Config = path.join(v3Dir, 'config.json');
|
|
555
|
+
if (fs.existsSync(v3Config)) {
|
|
556
|
+
fs.unlinkSync(v3Config);
|
|
557
|
+
}
|
|
558
|
+
output.printSuccess('Restored: config');
|
|
559
|
+
restored.push('config');
|
|
560
|
+
}
|
|
561
|
+
// Restore memory
|
|
562
|
+
const backupMemory = path.join(backupPath, 'data', 'memory');
|
|
563
|
+
if (fs.existsSync(backupMemory)) {
|
|
564
|
+
const destMemory = path.join(cwd, 'data', 'memory');
|
|
565
|
+
fs.mkdirSync(destMemory, { recursive: true });
|
|
566
|
+
const files = fs.readdirSync(backupMemory);
|
|
567
|
+
for (const f of files) {
|
|
568
|
+
fs.copyFileSync(path.join(backupMemory, f), path.join(destMemory, f));
|
|
569
|
+
}
|
|
570
|
+
output.printSuccess(`Restored: memory (${files.length} files)`);
|
|
571
|
+
restored.push('memory');
|
|
572
|
+
}
|
|
573
|
+
// Restore sessions
|
|
574
|
+
const backupSessions = path.join(backupPath, 'data', 'sessions');
|
|
575
|
+
if (fs.existsSync(backupSessions)) {
|
|
576
|
+
const destSessions = path.join(cwd, 'data', 'sessions');
|
|
577
|
+
fs.mkdirSync(destSessions, { recursive: true });
|
|
578
|
+
const files = fs.readdirSync(backupSessions);
|
|
579
|
+
for (const f of files) {
|
|
580
|
+
fs.copyFileSync(path.join(backupSessions, f), path.join(destSessions, f));
|
|
581
|
+
}
|
|
582
|
+
// Remove v3 sessions
|
|
583
|
+
const v3Sessions = path.join(v3Dir, 'sessions');
|
|
584
|
+
if (fs.existsSync(v3Sessions)) {
|
|
585
|
+
const v3Files = fs.readdirSync(v3Sessions);
|
|
586
|
+
for (const f of v3Files) {
|
|
587
|
+
fs.unlinkSync(path.join(v3Sessions, f));
|
|
588
|
+
}
|
|
589
|
+
fs.rmdirSync(v3Sessions);
|
|
590
|
+
}
|
|
591
|
+
output.printSuccess(`Restored: sessions (${files.length} files)`);
|
|
592
|
+
restored.push('sessions');
|
|
593
|
+
}
|
|
594
|
+
// Delete migration state
|
|
595
|
+
fs.unlinkSync(migrationStatePath);
|
|
596
|
+
output.writeln();
|
|
597
|
+
output.printSuccess(`Rollback complete. Restored: ${restored.join(', ') || 'nothing to restore'}`);
|
|
598
|
+
return { success: true, data: { restored } };
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
output.printError('Rollback failed', String(err));
|
|
602
|
+
return { success: false, exitCode: 1 };
|
|
603
|
+
}
|
|
117
604
|
}
|
|
118
605
|
};
|
|
119
606
|
// Breaking changes info
|
|
@@ -218,20 +705,25 @@ export const migrateCommand = {
|
|
|
218
705
|
};
|
|
219
706
|
// Helper functions
|
|
220
707
|
function formatMigrationStatus(status) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
708
|
+
if (status === 'migrated' || status === 'passed' || status === 'completed') {
|
|
709
|
+
return output.success(status);
|
|
710
|
+
}
|
|
711
|
+
if (status === 'pending' || status === 'partial') {
|
|
712
|
+
return output.warning(status);
|
|
713
|
+
}
|
|
714
|
+
if (status === 'failed') {
|
|
715
|
+
return output.error(status);
|
|
716
|
+
}
|
|
717
|
+
if (status === 'not-required' || status.startsWith('skipped') || status === 'v3' || status === 'missing') {
|
|
718
|
+
return output.dim(status);
|
|
719
|
+
}
|
|
720
|
+
if (status === 'v2') {
|
|
721
|
+
return output.warning(status);
|
|
722
|
+
}
|
|
723
|
+
if (status === 'v2 + v3') {
|
|
724
|
+
return output.success(status);
|
|
234
725
|
}
|
|
726
|
+
return status;
|
|
235
727
|
}
|
|
236
728
|
function getMigrationSteps(target) {
|
|
237
729
|
const allSteps = [
|