66 * backup [--name <label>] - Full backup of ~/.claude
77 * diff --release <path> - Compare local install against a release
88 * backup --user-only --release <path> - Back up only user data (not in release)
9+ * [--exclude 'path'] - Exclude specific files from backup
910 * list - List all backups (full and user-only)
1011 * restore <backup-name> - Restore from a full backup
1112 * migrate <backup> - Analyze backup for migration candidates
12- * migrate --auto --release <path> [--backup <p >] - Auto-restore user data after upgrade
13+ * migrate --auto [--backup <path >] - Auto-restore user data after release update
1314 *
14- * Upgrade Flow:
15+ * Migration Flow:
1516 * 1. bun BackupRestore.ts backup --name "pre-v4.1"
1617 * 2. bun BackupRestore.ts diff --release ./Releases/v4.1.0/.claude
1718 * 3. bun BackupRestore.ts backup --user-only --release ./Releases/v4.1.0/.claude
2223 *
2324 * Examples:
2425 * bun BackupRestore.ts backup
25- * bun BackupRestore.ts backup --name "before-upgrade "
26+ * bun BackupRestore.ts backup --name "before-update "
2627 * bun BackupRestore.ts backup --user-only --release ./Releases/v4.1.0/.claude
2728 * bun BackupRestore.ts list
2829 * bun BackupRestore.ts diff --release ./Releases/v4.1.0/.claude
2930 * bun BackupRestore.ts restore claude-backup-20260114-153000
3031 * bun BackupRestore.ts migrate claude-backup-20260114-153000
31- * bun BackupRestore.ts migrate --auto --release ./Releases/v4.1.0/.claude
32+ * bun BackupRestore.ts migrate --auto
3233 */
3334
3435import { existsSync , readdirSync , statSync , readFileSync , cpSync , rmSync , mkdirSync , writeFileSync } from "fs" ;
@@ -45,9 +46,6 @@ const USER_BACKUP_PREFIX = "claude-user-backup-";
4546// Files not restored during migrate --auto (regenerated automatically)
4647const MIGRATE_SKIP_RESTORE = new Set ( [ "CLAUDE.md" ] ) ;
4748
48- // Directories excluded from user-only backup (currently none — all user data is preserved)
49- const BACKUP_EXCLUDE_DIRS = new Set < string > ( ) ;
50-
5149// Files ignored during diff (OS metadata, not meaningful)
5250const DIFF_IGNORE = new Set ( [ ".DS_Store" , "Thumbs.db" ] ) ;
5351
@@ -341,7 +339,7 @@ function cmdDiff(releaseDir: string): DiffResult {
341339
342340 console . log ( ) ;
343341 console . log ( "══════════════════════════════════════════════════════" ) ;
344- console . log ( " PAI UPGRADE · Diff" ) ;
342+ console . log ( " PAI · Release Diff" ) ;
345343 console . log ( "══════════════════════════════════════════════════════" ) ;
346344 console . log ( ) ;
347345 console . log ( ` Release: ${ releaseDir } ` ) ;
@@ -366,11 +364,11 @@ function cmdDiff(releaseDir: string): DiffResult {
366364 for ( const file of userConflicts ) console . log ( ` ~ ${ file } ` ) ;
367365 console . log ( ) ;
368366 console . log ( " These files exist locally with different content." ) ;
369- console . log ( " Use backup --user-only to save them before upgrading ." ) ;
367+ console . log ( " Use backup --user-only to save them before migration ." ) ;
370368 }
371369 } else {
372370 console . log ( ) ;
373- console . log ( " No conflicts. Upgrade is safe to proceed." ) ;
371+ console . log ( " No conflicts. Migration is safe to proceed." ) ;
374372 }
375373
376374 console . log ( ) ;
@@ -384,28 +382,28 @@ function createUserOnlyBackup(releaseDir: string, customName?: string, excludeFi
384382 if ( ! existsSync ( releaseDir ) ) { console . error ( `Error: Release directory not found: ${ releaseDir } ` ) ; return null ; }
385383
386384 const result = diffRelease ( releaseDir ) ;
385+ const fileSet = new Set ( result . conflicts ) ;
387386
388387 // Always include settings.json and CLAUDE.md
389- if ( existsSync ( join ( CLAUDE_DIR , "settings.json" ) ) && ! result . conflicts . includes ( "settings.json" ) ) result . conflicts . push ( "settings.json" ) ;
390- if ( existsSync ( join ( CLAUDE_DIR , "CLAUDE.md" ) ) && ! result . conflicts . includes ( "CLAUDE.md" ) ) result . conflicts . push ( "CLAUDE.md" ) ;
388+ if ( existsSync ( join ( CLAUDE_DIR , "settings.json" ) ) && ! fileSet . has ( "settings.json" ) ) { result . conflicts . push ( "settings.json" ) ; fileSet . add ( "settings.json" ) ; }
389+ if ( existsSync ( join ( CLAUDE_DIR , "CLAUDE.md" ) ) && ! fileSet . has ( "CLAUDE.md" ) ) { result . conflicts . push ( "CLAUDE.md" ) ; fileSet . add ( "CLAUDE.md" ) ; }
391390
392391 // Always include PAI/USER/ and MEMORY/ (entire directories)
393392 for ( const dir of [ join ( CLAUDE_DIR , "PAI" , "USER" ) , join ( CLAUDE_DIR , "MEMORY" ) ] ) {
394393 if ( ! existsSync ( dir ) ) continue ;
395394 for ( const f of listFiles ( dir , CLAUDE_DIR ) ) {
396- if ( ! result . conflicts . includes ( f ) && ! DIFF_IGNORE . has ( basename ( f ) ) ) result . conflicts . push ( f ) ;
395+ if ( ! fileSet . has ( f ) && ! DIFF_IGNORE . has ( basename ( f ) ) ) { result . conflicts . push ( f ) ; fileSet . add ( f ) ; }
397396 }
398397 }
399398
400399 // Include all local files not in the release (user-generated data)
401400 const releaseFileSet = new Set ( listFiles ( releaseDir ) ) ;
402401 for ( const f of listFiles ( CLAUDE_DIR ) ) {
403- if ( result . conflicts . includes ( f ) ) continue ;
402+ if ( fileSet . has ( f ) ) continue ;
404403 if ( DIFF_IGNORE . has ( basename ( f ) ) ) continue ;
405404 if ( f . startsWith ( "PAI/USER/" ) || f . startsWith ( "PAI-Install/" ) ) continue ;
406- if ( BACKUP_EXCLUDE_DIRS . has ( f . split ( "/" ) [ 0 ] ) ) continue ;
407405 if ( releaseFileSet . has ( f ) ) continue ;
408- result . conflicts . push ( f ) ;
406+ result . conflicts . push ( f ) ; fileSet . add ( f ) ;
409407 }
410408
411409 // Apply user-specified excludes
@@ -452,7 +450,7 @@ function createUserOnlyBackup(releaseDir: string, customName?: string, excludeFi
452450 console . log ( ` Location: ~/${ backupName } ` ) ;
453451 console . log ( ) ;
454452 console . log ( " Review the backup directory and remove any files you" ) ;
455- console . log ( " don't want restored after upgrade ." ) ;
453+ console . log ( " don't want restored after migration ." ) ;
456454 console . log ( ) ;
457455 console . log ( "══════════════════════════════════════════════════════" ) ;
458456 console . log ( ) ;
@@ -461,11 +459,11 @@ function createUserOnlyBackup(releaseDir: string, customName?: string, excludeFi
461459
462460/**
463461 * migrate --auto --release <path> --backup <path>
464- * Auto-restore user data from a user-only backup after cp -r upgrade .
462+ * Auto-restore user data from a user-only backup after applying a new release .
465463 * Restores all files from the backup except CLAUDE.md (regenerated by BuildCLAUDE.ts).
466464 * Then runs BuildCLAUDE.ts.
467465 */
468- function cmdMigrateAuto ( releaseDir : string , backupPath : string ) : void {
466+ function cmdMigrateAuto ( backupPath : string ) : void {
469467 if ( ! existsSync ( backupPath ) ) { console . error ( `Error: Backup not found: ${ backupPath } ` ) ; process . exit ( 1 ) ; }
470468 if ( ! existsSync ( CLAUDE_DIR ) ) { console . error ( "Error: ~/.claude directory does not exist. Run cp -r first." ) ; process . exit ( 1 ) ; }
471469
@@ -483,7 +481,7 @@ function cmdMigrateAuto(releaseDir: string, backupPath: string): void {
483481 let filesToRestore : string [ ] ;
484482 if ( existsSync ( manifestPath ) ) {
485483 const manifest = JSON . parse ( readFileSync ( manifestPath , "utf-8" ) ) ;
486- filesToRestore = manifest . files || manifest . conflicts || [ ] ;
484+ filesToRestore = manifest . files || [ ] ;
487485 } else {
488486 filesToRestore = listFiles ( backupPath ) . filter ( ( f ) => f !== "backup-manifest.json" ) ;
489487 }
@@ -552,22 +550,24 @@ Commands:
552550 backup [--name <label>] Full backup of ~/.claude
553551 diff --release <path> Compare local install against a release
554552 backup --user-only --release <path> Back up only user data (not in release)
553+ [--exclude 'path'] [--exclude 'path'] Exclude specific files from user-only backup
555554 list List all backups (full and user-only)
556555 restore <backup-name> Restore from a full backup
557556 migrate <backup> Analyze backup for migration candidates
558- migrate --auto --release <path> [--backup <p >] Auto-restore user data after upgrade
557+ migrate --auto [--backup <path >] Auto-restore user data after release update
559558
560559Examples:
561560 bun BackupRestore.ts backup
562- bun BackupRestore.ts backup --name "before-upgrade "
561+ bun BackupRestore.ts backup --name "before-update "
563562 bun BackupRestore.ts diff --release ./Releases/v4.1.0/.claude
564563 bun BackupRestore.ts backup --user-only --release ./Releases/v4.1.0/.claude
564+ bun BackupRestore.ts backup --user-only --release ./Releases/v4.1.0/.claude --exclude 'PAI/SKILL.md'
565565 bun BackupRestore.ts list
566566 bun BackupRestore.ts restore claude-backup-20260114-153000
567567 bun BackupRestore.ts migrate claude-backup-20260114-153000
568- bun BackupRestore.ts migrate --auto --release ./Releases/v4.1.0/.claude
568+ bun BackupRestore.ts migrate --auto
569569
570- Upgrade Flow:
570+ Migration Flow:
571571 1. bun BackupRestore.ts backup --name "pre-v4.1"
572572 2. bun BackupRestore.ts diff --release ./Releases/v4.1.0/.claude
573573 3. bun BackupRestore.ts backup --user-only --release ./Releases/v4.1.0/.claude
@@ -656,7 +656,6 @@ switch (command) {
656656
657657 case "migrate" : {
658658 if ( args . includes ( "--auto" ) ) {
659- const releaseDir = getArgValue ( "--release" ) ;
660659 // --backup is optional; if not provided, find the latest user-only backup
661660 const backupIdx = args . indexOf ( "--backup" ) ;
662661 let backupPath : string ;
@@ -671,7 +670,7 @@ switch (command) {
671670 backupPath = latest . path ;
672671 console . log ( `Using latest user-only backup: ${ latest . name } \n` ) ;
673672 }
674- cmdMigrateAuto ( releaseDir , backupPath ) ;
673+ cmdMigrateAuto ( backupPath ) ;
675674 } else {
676675 const backupName = args [ 1 ] ;
677676 if ( ! backupName ) {
0 commit comments