myaidev-method 0.2.24-3 → 0.2.25

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/bin/cli.js CHANGED
@@ -1044,6 +1044,9 @@ program
1044
1044
  .option('--force', 'Force update all files without prompting')
1045
1045
  .option('--dry-run', 'Show what would be updated without making changes')
1046
1046
  .option('--verbose', 'Show detailed progress')
1047
+ .option('--smart', 'Smart update: keep customizations, add new files (no prompts)')
1048
+ .option('--accept-all', 'Accept all updates: replace all files (no prompts)')
1049
+ .option('--keep-mine', 'Keep all customizations: only add new files (no prompts)')
1047
1050
  .action(async (options) => {
1048
1051
  const ora = (await import('ora')).default;
1049
1052
  const { fileURLToPath } = await import('url');
@@ -1056,122 +1059,128 @@ program
1056
1059
  try {
1057
1060
  const cwd = process.cwd();
1058
1061
 
1062
+ // Display branding banner
1063
+ console.log(getASCIIBanner());
1064
+ console.log(chalk.cyan.bold(' ⚔ Update Manager\n'));
1065
+
1059
1066
  // Detect existing installation
1060
- console.log(chalk.cyan('\nšŸ” Detecting MyAIDev Method installation...\n'));
1067
+ const spinner = ora('Detecting MyAIDev Method installation...').start();
1061
1068
  const installation = await updateManager.detectExistingInstallation(cwd);
1062
1069
 
1063
1070
  if (!installation) {
1064
- console.log(chalk.red('āŒ No MyAIDev Method installation found'));
1071
+ spinner.fail('No MyAIDev Method installation found');
1065
1072
  console.log(chalk.gray(' Run: npx myaidev-method@latest init --claude'));
1066
1073
  process.exit(1);
1067
1074
  }
1068
1075
 
1069
- console.log(chalk.green(`āœ“ Found ${installation.type} installation`));
1070
- console.log(chalk.gray(` Current version: ${installation.currentVersion}`));
1076
+ spinner.succeed(`Found ${installation.type} installation (v${installation.currentVersion})`);
1071
1077
 
1072
1078
  // Get package version
1073
1079
  const packageJson = await fs.readJson(path.join(packageRoot, 'package.json'));
1074
1080
  const newVersion = packageJson.version;
1075
1081
 
1076
- console.log(chalk.gray(` Latest version: ${newVersion}`));
1077
-
1078
1082
  // Check if already up to date
1079
1083
  if (installation.currentVersion === newVersion && !options.force) {
1080
- console.log(chalk.green('\nāœ… Already up to date!'));
1084
+ console.log(chalk.green('\nāœ… Already up to date!\n'));
1081
1085
  process.exit(0);
1082
1086
  }
1083
1087
 
1084
- if (options.dryRun) {
1085
- console.log(chalk.yellow('\nšŸ“‹ DRY RUN MODE - No changes will be made\n'));
1086
- } else if (!options.force) {
1087
- console.log(chalk.yellow('\nāš ļø This will update your MyAIDev Method installation'));
1088
- console.log(chalk.gray(' Modified files will require your confirmation\n'));
1088
+ // Generate change summary
1089
+ const summarySpinner = ora('Analyzing changes...').start();
1090
+ const components = ['commands', 'agents', 'scripts', 'lib', 'mcp', 'docs'];
1091
+ const summary = await updateManager.generateChangeSummary(
1092
+ components, cwd, installation.type, packageRoot
1093
+ );
1094
+ summarySpinner.succeed('Change analysis complete');
1089
1095
 
1090
- const { confirm } = await inquirer.prompt([
1091
- {
1092
- type: 'confirm',
1093
- name: 'confirm',
1094
- message: 'Continue with update?',
1095
- default: true
1096
- }
1097
- ]);
1096
+ // Display summary dashboard
1097
+ console.log('');
1098
+ updateManager.displayChangeSummary(summary, installation.currentVersion, newVersion);
1098
1099
 
1099
- if (!confirm) {
1100
- console.log(chalk.gray('Update cancelled'));
1101
- process.exit(0);
1102
- }
1100
+ // Dry run mode - just show summary and exit
1101
+ if (options.dryRun) {
1102
+ console.log(chalk.yellow('\nšŸ“‹ DRY RUN MODE - No changes will be made\n'));
1103
+ console.log(chalk.gray('Summary:'));
1104
+ console.log(chalk.gray(` • ${summary.unchanged.length} unchanged files (would skip)`));
1105
+ console.log(chalk.gray(` • ${summary.new.length} new files (would add)`));
1106
+ console.log(chalk.gray(` • ${summary.modified.length} modified files (would need decision)`));
1107
+ console.log(chalk.gray(` • ${summary.deleted.length} removed files (would optionally delete)`));
1108
+ process.exit(0);
1103
1109
  }
1104
1110
 
1105
- // Create backup unless dry-run
1106
- let backupDir;
1107
- if (!options.dryRun) {
1108
- console.log(chalk.cyan('\nšŸ’¾ Creating backup...'));
1109
- backupDir = await updateManager.createBackup(cwd, installation.type);
1110
- console.log(chalk.green(`āœ“ Backup created at: ${path.basename(backupDir)}`));
1111
+ // Determine strategy (CLI flag or prompt)
1112
+ let strategy;
1113
+ if (options.smart) {
1114
+ strategy = 'smart';
1115
+ console.log(chalk.cyan('\nšŸš€ Using Smart Update strategy (--smart flag)\n'));
1116
+ } else if (options.acceptAll) {
1117
+ strategy = 'accept-all';
1118
+ console.log(chalk.cyan('\nšŸ“„ Using Accept All strategy (--accept-all flag)\n'));
1119
+ } else if (options.keepMine) {
1120
+ strategy = 'keep-all';
1121
+ console.log(chalk.cyan('\nšŸ›”ļø Using Keep All strategy (--keep-mine flag)\n'));
1122
+ } else if (options.force) {
1123
+ strategy = 'accept-all';
1124
+ console.log(chalk.cyan('\nšŸ“„ Using Accept All strategy (--force flag)\n'));
1125
+ } else {
1126
+ strategy = await updateManager.promptUpdateStrategy(summary);
1111
1127
  }
1112
1128
 
1113
- // Update components
1114
- const allStats = {};
1115
-
1116
- console.log(chalk.cyan('\nšŸ“¦ Updating components...\n'));
1117
-
1118
- // Commands
1119
- console.log(chalk.blue('Commands:'));
1120
- allStats.commands = await updateManager.updateComponent(
1121
- 'commands', cwd, installation.type, packageRoot, options
1122
- );
1123
-
1124
- // Agents
1125
- console.log(chalk.blue('\nAgents:'));
1126
- allStats.agents = await updateManager.updateComponent(
1127
- 'agents', cwd, installation.type, packageRoot, options
1128
- );
1129
-
1130
- // Scripts
1131
- console.log(chalk.blue('\nScripts:'));
1132
- allStats.scripts = await updateManager.updateComponent(
1133
- 'scripts', cwd, installation.type, packageRoot, options
1134
- );
1135
-
1136
- // Libraries
1137
- console.log(chalk.blue('\nLibraries:'));
1138
- allStats.lib = await updateManager.updateComponent(
1139
- 'lib', cwd, installation.type, packageRoot, options
1140
- );
1141
-
1142
- // MCP configurations
1143
- console.log(chalk.blue('\nMCP Configurations:'));
1144
- allStats.mcp = await updateManager.updateComponent(
1145
- 'mcp', cwd, installation.type, packageRoot, options
1146
- );
1147
-
1148
- // Documentation
1149
- console.log(chalk.blue('\nDocumentation:'));
1150
- allStats.docs = await updateManager.updateComponent(
1151
- 'docs', cwd, installation.type, packageRoot, options
1152
- );
1129
+ // Create timestamped backup directory
1130
+ const backupDir = await updateManager.createTimestampedBackup(cwd, installation.type);
1131
+
1132
+ // Execute selected strategy
1133
+ let results;
1134
+ console.log(chalk.cyan('\nšŸ“¦ Applying updates...\n'));
1135
+
1136
+ if (strategy === 'auto' || (summary.modified.length === 0)) {
1137
+ // No conflicts - just add new files
1138
+ results = await updateManager.executeKeepAll(summary, { verbose: options.verbose });
1139
+ } else if (strategy === 'smart') {
1140
+ results = await updateManager.executeSmartUpdate(summary, backupDir, { verbose: options.verbose });
1141
+ } else if (strategy === 'accept-all') {
1142
+ results = await updateManager.executeAcceptAll(summary, backupDir, { verbose: options.verbose });
1143
+ } else if (strategy === 'keep-all') {
1144
+ results = await updateManager.executeKeepAll(summary, { verbose: options.verbose });
1145
+ } else if (strategy === 'by-category') {
1146
+ results = await updateManager.executeCategoryUpdate(summary, backupDir, { verbose: options.verbose });
1147
+ } else if (strategy === 'individual') {
1148
+ // Fall back to old file-by-file for this mode
1149
+ results = { updated: [], kept: [], added: [], backedUp: [] };
1150
+ for (const f of summary.new) {
1151
+ await fs.ensureDir(path.dirname(f.targetPath));
1152
+ await fs.copy(f.sourcePath, f.targetPath);
1153
+ results.added.push(f.file);
1154
+ if (options.verbose) {
1155
+ console.log(chalk.green(` āž• ${f.file} (new)`));
1156
+ }
1157
+ }
1158
+ for (const f of summary.modified) {
1159
+ const action = await updateManager.promptForConflict(f.file, f.targetPath, f.sourcePath);
1160
+ if (action === 'replace') {
1161
+ await fs.copy(f.sourcePath, f.targetPath);
1162
+ results.updated.push(f.file);
1163
+ } else if (action === 'backup') {
1164
+ const backupPath = path.join(backupDir, f.componentType, f.file);
1165
+ await fs.ensureDir(path.dirname(backupPath));
1166
+ await fs.copy(f.targetPath, backupPath);
1167
+ results.backedUp.push(f.file);
1168
+ await fs.copy(f.sourcePath, f.targetPath);
1169
+ results.updated.push(f.file);
1170
+ } else {
1171
+ results.kept.push(f.file);
1172
+ }
1173
+ }
1174
+ }
1153
1175
 
1154
1176
  // Update dependencies
1155
- if (!options.dryRun) {
1156
- await updateManager.updateDependencies(cwd);
1157
- }
1177
+ await updateManager.updateDependencies(cwd);
1158
1178
 
1159
1179
  // Save new version
1160
- if (!options.dryRun) {
1161
- await updateManager.saveVersion(cwd, installation.type, newVersion);
1162
- }
1163
-
1164
- // Show report
1165
- updateManager.generateUpdateReport(allStats);
1180
+ await updateManager.saveVersion(cwd, installation.type, newVersion);
1166
1181
 
1167
- if (options.dryRun) {
1168
- console.log(chalk.yellow('This was a dry run. No changes were made.'));
1169
- } else {
1170
- console.log(chalk.green(`āœ… Successfully updated to v${newVersion}`));
1171
- if (backupDir) {
1172
- console.log(chalk.gray(` Backup available at: ${path.basename(backupDir)}`));
1173
- }
1174
- }
1182
+ // Display final results
1183
+ updateManager.displayUpdateResults(results, backupDir, newVersion);
1175
1184
 
1176
1185
  } catch (error) {
1177
1186
  console.error(chalk.red('\nāŒ Update failed:'));
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myaidev-method",
3
- "version": "0.2.24-3",
3
+ "version": "0.2.25",
4
4
  "description": "Comprehensive development framework with SPARC methodology for AI-assisted software development, security testing (PTES, OWASP, penetration testing, compliance auditing), AI visual content generation (Gemini, OpenAI GPT Image 1.5, Imagen, FLUX 2, Veo 3), OpenStack VM management, multi-platform publishing (WordPress, PayloadCMS, Astro, Docusaurus, Mintlify), and Coolify deployment",
5
5
  "mcpName": "io.github.myaione/myaidev-method",
6
6
  "main": "src/index.js",
@@ -9,6 +9,27 @@ import { createHash } from 'crypto';
9
9
  * Handles intelligent updates with conflict resolution
10
10
  */
11
11
 
12
+ /**
13
+ * Display ASCII art branding banner
14
+ */
15
+ export function displayUpdateBanner() {
16
+ const banner = `
17
+ ${chalk.cyan('╔═══════════════════════════════════════════════════════════════════════╗')}
18
+ ${chalk.cyan('ā•‘')} ${chalk.cyan('ā•‘')}
19
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•— ā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•— ā–ˆā–ˆā•—')} ${chalk.cyan('ā•‘')}
20
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•— ā–ˆā–ˆā•”ā•ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘')} ${chalk.cyan('ā•‘')}
21
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā–ˆā–ˆā•”ā–ˆā–ˆā–ˆā–ˆā•”ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•”ā• ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘')} ${chalk.cyan('ā•‘')}
22
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā•”ā• ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā• ā•šā–ˆā–ˆā•— ā–ˆā–ˆā•”ā•')} ${chalk.cyan('ā•‘')}
23
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā–ˆā–ˆā•‘ ā•šā•ā• ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā•šā–ˆā–ˆā–ˆā–ˆā•”ā•')} ${chalk.cyan('ā•‘')}
24
+ ${chalk.cyan('ā•‘')} ${chalk.white.bold('ā•šā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•šā•ā•ā•šā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•')} ${chalk.cyan('ā•‘')}
25
+ ${chalk.cyan('ā•‘')} ${chalk.cyan('ā•‘')}
26
+ ${chalk.cyan('ā•‘')} ${chalk.gray('M E T H O D')} ${chalk.yellow('⚔ Update Manager')} ${chalk.cyan('ā•‘')}
27
+ ${chalk.cyan('ā•‘')} ${chalk.cyan('ā•‘')}
28
+ ${chalk.cyan('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•')}
29
+ `;
30
+ console.log(banner);
31
+ }
32
+
12
33
  /**
13
34
  * Detect existing MyAIDev Method installation
14
35
  * @param {string} projectDir - Project directory path
@@ -384,3 +405,595 @@ export function generateUpdateReport(allStats) {
384
405
 
385
406
  console.log(chalk.cyan('═══════════════════════════════════════\n'));
386
407
  }
408
+
409
+ /**
410
+ * Count lines in a file
411
+ * @param {string} filePath - Path to file
412
+ * @returns {number} Line count
413
+ */
414
+ async function getLineCount(filePath) {
415
+ if (!await fs.pathExists(filePath)) return 0;
416
+ const content = await fs.readFile(filePath, 'utf-8');
417
+ return content.split('\n').length;
418
+ }
419
+
420
+ /**
421
+ * Get files for a component type
422
+ * @param {string} componentType - Type of component
423
+ * @param {string} dir - Directory to scan
424
+ * @returns {Array} List of files
425
+ */
426
+ async function getComponentFiles(componentType, dir) {
427
+ if (!await fs.pathExists(dir)) return [];
428
+
429
+ if (componentType === 'docs') {
430
+ return ['USER_GUIDE.md', 'PUBLISHING_GUIDE.md', 'COOLIFY_DEPLOYMENT.md', 'DEV_WORKFLOW_GUIDE.md',
431
+ 'MCP_INTEGRATION.md', 'WORDPRESS_ADMIN_SCRIPTS.md', 'TECHNICAL_ARCHITECTURE.md',
432
+ 'WORDPRESS_INTEGRATION.md', 'wordpress-api-examples.md'];
433
+ }
434
+
435
+ const allFiles = await fs.readdir(dir);
436
+ return allFiles.filter(file => {
437
+ if (componentType === 'commands') return file.endsWith('.md') || file.endsWith('.toml');
438
+ if (componentType === 'agents') return file.endsWith('.md');
439
+ if (componentType === 'scripts') return file.endsWith('.js') && file !== 'init-project.js';
440
+ if (componentType === 'lib') return file.endsWith('.js');
441
+ if (componentType === 'mcp') return file.endsWith('.js') || file.endsWith('.json');
442
+ return true;
443
+ });
444
+ }
445
+
446
+ /**
447
+ * Generate comprehensive change summary
448
+ * @param {Array} components - Component types to analyze
449
+ * @param {string} projectDir - Project directory
450
+ * @param {string} cliType - CLI type (claude/gemini/codex)
451
+ * @param {string} sourceDir - Source package directory
452
+ * @returns {Object} Categorized change summary
453
+ */
454
+ export async function generateChangeSummary(components, projectDir, cliType, sourceDir) {
455
+ const summary = {
456
+ unchanged: [],
457
+ new: [],
458
+ modified: [],
459
+ deleted: [],
460
+ byCategory: {}
461
+ };
462
+
463
+ for (const componentType of components) {
464
+ let targetDir, sourceSubDir;
465
+
466
+ switch (componentType) {
467
+ case 'commands':
468
+ targetDir = path.join(projectDir, `.${cliType}`, 'commands');
469
+ sourceSubDir = path.join(sourceDir, 'src', 'templates', cliType, 'commands');
470
+ break;
471
+ case 'agents':
472
+ targetDir = path.join(projectDir, `.${cliType}`, 'agents');
473
+ sourceSubDir = path.join(sourceDir, 'src', 'templates', 'claude', 'agents');
474
+ break;
475
+ case 'scripts':
476
+ targetDir = path.join(projectDir, '.myaidev-method', 'scripts');
477
+ sourceSubDir = path.join(sourceDir, 'src', 'scripts');
478
+ break;
479
+ case 'lib':
480
+ targetDir = path.join(projectDir, '.myaidev-method', 'lib');
481
+ sourceSubDir = path.join(sourceDir, 'src', 'lib');
482
+ break;
483
+ case 'mcp':
484
+ targetDir = path.join(projectDir, `.${cliType}`, 'mcp');
485
+ sourceSubDir = path.join(sourceDir, '.claude', 'mcp');
486
+ break;
487
+ case 'docs':
488
+ targetDir = projectDir;
489
+ sourceSubDir = sourceDir;
490
+ break;
491
+ default:
492
+ continue;
493
+ }
494
+
495
+ if (!await fs.pathExists(sourceSubDir)) continue;
496
+
497
+ summary.byCategory[componentType] = {
498
+ unchanged: [],
499
+ new: [],
500
+ modified: [],
501
+ deleted: []
502
+ };
503
+
504
+ const sourceFiles = await getComponentFiles(componentType, sourceSubDir);
505
+ const targetFiles = await getComponentFiles(componentType, targetDir);
506
+
507
+ // Check files from source (new or modified)
508
+ for (const file of sourceFiles) {
509
+ const sourcePath = path.join(sourceSubDir, file);
510
+ const targetPath = path.join(targetDir, file);
511
+
512
+ if (!await fs.pathExists(sourcePath)) continue;
513
+ const sourceStats = await fs.stat(sourcePath);
514
+ if (sourceStats.isDirectory()) continue;
515
+
516
+ const status = await compareFiles(targetPath, sourcePath);
517
+ const localLines = await getLineCount(targetPath);
518
+ const newLines = await getLineCount(sourcePath);
519
+
520
+ const fileInfo = {
521
+ file,
522
+ componentType,
523
+ sourcePath,
524
+ targetPath,
525
+ localLines,
526
+ newLines
527
+ };
528
+
529
+ if (status === 'identical') {
530
+ summary.unchanged.push(fileInfo);
531
+ summary.byCategory[componentType].unchanged.push(fileInfo);
532
+ } else if (status === 'new') {
533
+ summary.new.push(fileInfo);
534
+ summary.byCategory[componentType].new.push(fileInfo);
535
+ } else if (status === 'modified') {
536
+ summary.modified.push(fileInfo);
537
+ summary.byCategory[componentType].modified.push(fileInfo);
538
+ }
539
+ }
540
+
541
+ // Check for deleted files (in target but not in source)
542
+ for (const file of targetFiles) {
543
+ if (!sourceFiles.includes(file)) {
544
+ const targetPath = path.join(targetDir, file);
545
+ const localLines = await getLineCount(targetPath);
546
+ const fileInfo = {
547
+ file,
548
+ componentType,
549
+ sourcePath: null,
550
+ targetPath,
551
+ localLines,
552
+ newLines: 0
553
+ };
554
+ summary.deleted.push(fileInfo);
555
+ summary.byCategory[componentType].deleted.push(fileInfo);
556
+ }
557
+ }
558
+ }
559
+
560
+ return summary;
561
+ }
562
+
563
+ /**
564
+ * Display visual summary dashboard
565
+ * @param {Object} summary - Change summary from generateChangeSummary
566
+ * @param {string} currentVersion - Current installed version
567
+ * @param {string} newVersion - New available version
568
+ */
569
+ export function displayChangeSummary(summary, currentVersion, newVersion) {
570
+ const boxWidth = 69;
571
+ const topBorder = 'ā•”' + '═'.repeat(boxWidth) + 'ā•—';
572
+ const midBorder = 'ā• ' + '═'.repeat(boxWidth) + 'ā•£';
573
+ const botBorder = 'ā•š' + '═'.repeat(boxWidth) + 'ā•';
574
+ const innerTop = 'ā”Œ' + '─'.repeat(boxWidth - 4) + '┐';
575
+ const innerBot = 'ā””' + '─'.repeat(boxWidth - 4) + 'ā”˜';
576
+
577
+ console.log(chalk.cyan(topBorder));
578
+ console.log(chalk.cyan('ā•‘') + chalk.white.bold(` MyAIDev Method Update `.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
579
+ console.log(chalk.cyan(midBorder));
580
+
581
+ // Version info
582
+ const versionLine = ` šŸ“¦ Current: ${currentVersion} → Available: ${newVersion}`;
583
+ console.log(chalk.cyan('ā•‘') + chalk.white(versionLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
584
+ console.log(chalk.cyan('ā•‘') + ' '.repeat(boxWidth) + chalk.cyan('ā•‘'));
585
+
586
+ // Change summary
587
+ console.log(chalk.cyan('ā•‘') + chalk.white.bold(' šŸ“Š CHANGE SUMMARY'.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
588
+
589
+ const unchangedLine = ` ā”œā”€ āœ… Unchanged: ${summary.unchanged.length} files (auto-skip)`;
590
+ const newLine = ` ā”œā”€ šŸ†• New files: ${summary.new.length} files (auto-add)`;
591
+ const modifiedLine = ` ā”œā”€ āš ļø Modified: ${summary.modified.length} files (need decision)`;
592
+ const deletedLine = ` └─ šŸ—‘ļø Removed: ${summary.deleted.length} files (optional delete)`;
593
+
594
+ console.log(chalk.cyan('ā•‘') + chalk.gray(unchangedLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
595
+ console.log(chalk.cyan('ā•‘') + chalk.green(newLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
596
+ console.log(chalk.cyan('ā•‘') + chalk.yellow(modifiedLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
597
+ console.log(chalk.cyan('ā•‘') + chalk.red(deletedLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
598
+
599
+ // Show modified files by category if any
600
+ if (summary.modified.length > 0) {
601
+ console.log(chalk.cyan('ā•‘') + ' '.repeat(boxWidth) + chalk.cyan('ā•‘'));
602
+ console.log(chalk.cyan('ā•‘') + chalk.white.bold(' Modified files by category:'.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
603
+ console.log(chalk.cyan('ā•‘') + ' ' + chalk.gray(innerTop) + chalk.cyan('ā•‘'));
604
+
605
+ for (const [category, data] of Object.entries(summary.byCategory)) {
606
+ if (data.modified.length === 0) continue;
607
+
608
+ const catName = category.toUpperCase();
609
+ const catLine = ` │ ${catName} (${data.modified.length} files)`;
610
+ console.log(chalk.cyan('ā•‘') + chalk.white.bold(catLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
611
+
612
+ for (let i = 0; i < data.modified.length && i < 3; i++) {
613
+ const f = data.modified[i];
614
+ const prefix = i === data.modified.length - 1 || i === 2 ? '└─' : 'ā”œā”€';
615
+ const lineInfo = f.localLines !== f.newLines ?
616
+ `[${f.localLines} → ${f.newLines} lines]` : '[customized]';
617
+ const fileLine = ` │ ${prefix} ${f.file.slice(0, 30).padEnd(30)} ${lineInfo}`;
618
+ console.log(chalk.cyan('ā•‘') + chalk.gray(fileLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
619
+ }
620
+
621
+ if (data.modified.length > 3) {
622
+ const moreLine = ` │ └─ ... (${data.modified.length - 3} more)`;
623
+ console.log(chalk.cyan('ā•‘') + chalk.gray(moreLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
624
+ }
625
+
626
+ console.log(chalk.cyan('ā•‘') + ' │'.padEnd(boxWidth + 1) + chalk.cyan('ā•‘'));
627
+ }
628
+
629
+ console.log(chalk.cyan('ā•‘') + ' ' + chalk.gray(innerBot) + chalk.cyan('ā•‘'));
630
+ }
631
+
632
+ console.log(chalk.cyan(botBorder));
633
+ }
634
+
635
+ /**
636
+ * Prompt for update strategy
637
+ * @param {Object} summary - Change summary
638
+ * @returns {string} Selected strategy
639
+ */
640
+ export async function promptUpdateStrategy(summary) {
641
+ const modifiedCount = summary.modified.length;
642
+
643
+ if (modifiedCount === 0) {
644
+ console.log(chalk.green('\nāœ“ No conflicts to resolve. All files will be updated automatically.\n'));
645
+ return 'auto';
646
+ }
647
+
648
+ console.log('');
649
+ const answer = await inquirer.prompt([
650
+ {
651
+ type: 'list',
652
+ name: 'strategy',
653
+ message: `How would you like to handle the ${modifiedCount} modified file${modifiedCount > 1 ? 's' : ''}?`,
654
+ choices: [
655
+ {
656
+ name: `šŸš€ Smart Update ${chalk.gray('(Recommended)')}` +
657
+ `\n ${chalk.dim('Keep your customizations, add new files, backup modified')}`,
658
+ value: 'smart',
659
+ short: 'Smart Update'
660
+ },
661
+ {
662
+ name: 'šŸ“„ Accept All Updates' +
663
+ `\n ${chalk.dim('Replace all files with new versions (backups created)')}`,
664
+ value: 'accept-all',
665
+ short: 'Accept All'
666
+ },
667
+ {
668
+ name: 'šŸ›”ļø Keep All Customizations' +
669
+ `\n ${chalk.dim('Skip all modified files, only add new files')}`,
670
+ value: 'keep-all',
671
+ short: 'Keep All'
672
+ },
673
+ {
674
+ name: 'šŸ” Review Each Category' +
675
+ `\n ${chalk.dim('Batch decisions per category (commands, agents, scripts)')}`,
676
+ value: 'by-category',
677
+ short: 'By Category'
678
+ },
679
+ {
680
+ name: 'šŸ“‹ Review Individual Files' +
681
+ `\n ${chalk.dim(`Traditional file-by-file review (${modifiedCount} prompts)`)}`,
682
+ value: 'individual',
683
+ short: 'Individual'
684
+ }
685
+ ]
686
+ }
687
+ ]);
688
+
689
+ return answer.strategy;
690
+ }
691
+
692
+ /**
693
+ * Prompt for category decision
694
+ * @param {string} category - Category name
695
+ * @param {Array} files - Modified files in category
696
+ * @returns {string} Decision: 'keep' | 'update' | 'individual'
697
+ */
698
+ export async function promptCategoryDecision(category, files) {
699
+ const boxWidth = 69;
700
+ const topBorder = 'ā•”' + '═'.repeat(boxWidth) + 'ā•—';
701
+ const midBorder = 'ā• ' + '═'.repeat(boxWidth) + 'ā•£';
702
+ const botBorder = 'ā•š' + '═'.repeat(boxWidth) + 'ā•';
703
+
704
+ console.log(chalk.cyan(topBorder));
705
+ console.log(chalk.cyan('ā•‘') + chalk.white.bold(` ${category.toUpperCase()} (${files.length} modified files)`.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
706
+ console.log(chalk.cyan(midBorder));
707
+
708
+ for (let i = 0; i < files.length; i++) {
709
+ const f = files[i];
710
+ const line = ` ${i + 1}. ${f.file}`;
711
+ console.log(chalk.cyan('ā•‘') + chalk.white(line.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
712
+ const infoLine = ` Your version: ${f.localLines} lines | New version: ${f.newLines} lines`;
713
+ console.log(chalk.cyan('ā•‘') + chalk.gray(infoLine.padEnd(boxWidth)) + chalk.cyan('ā•‘'));
714
+ if (i < files.length - 1) {
715
+ console.log(chalk.cyan('ā•‘') + ' '.repeat(boxWidth) + chalk.cyan('ā•‘'));
716
+ }
717
+ }
718
+
719
+ console.log(chalk.cyan(botBorder));
720
+
721
+ const answer = await inquirer.prompt([
722
+ {
723
+ type: 'list',
724
+ name: 'decision',
725
+ message: `What would you like to do with ${category.toUpperCase()}?`,
726
+ choices: [
727
+ { name: `Keep all my customizations (skip ${files.length} files)`, value: 'keep' },
728
+ { name: `Update all (replace ${files.length} files, backup originals)`, value: 'update' },
729
+ { name: `Review these ${files.length} files individually`, value: 'individual' }
730
+ ]
731
+ }
732
+ ]);
733
+
734
+ return answer.decision;
735
+ }
736
+
737
+ /**
738
+ * Execute smart update strategy
739
+ * @param {Object} summary - Change summary
740
+ * @param {string} backupDir - Backup directory path
741
+ * @param {Object} options - Update options
742
+ * @returns {Object} Results
743
+ */
744
+ export async function executeSmartUpdate(summary, backupDir, options = {}) {
745
+ const results = {
746
+ updated: [],
747
+ kept: [],
748
+ added: [],
749
+ backedUp: []
750
+ };
751
+
752
+ // Add all new files
753
+ for (const f of summary.new) {
754
+ await fs.ensureDir(path.dirname(f.targetPath));
755
+ await fs.copy(f.sourcePath, f.targetPath);
756
+ results.added.push(f.file);
757
+ if (options.verbose) {
758
+ console.log(chalk.green(` āž• ${f.file} (new)`));
759
+ }
760
+ }
761
+
762
+ // Keep all modified files (smart = preserve customizations)
763
+ for (const f of summary.modified) {
764
+ // Create backup
765
+ const backupPath = path.join(backupDir, f.componentType, f.file);
766
+ await fs.ensureDir(path.dirname(backupPath));
767
+ await fs.copy(f.targetPath, backupPath);
768
+ results.backedUp.push(f.file);
769
+ results.kept.push(f.file);
770
+ if (options.verbose) {
771
+ console.log(chalk.gray(` šŸ›”ļø ${f.file} (kept, backup created)`));
772
+ }
773
+ }
774
+
775
+ // Update unchanged files (they're identical so no-op, but count them)
776
+ results.updated = summary.unchanged.map(f => f.file);
777
+
778
+ return results;
779
+ }
780
+
781
+ /**
782
+ * Execute accept-all strategy
783
+ * @param {Object} summary - Change summary
784
+ * @param {string} backupDir - Backup directory path
785
+ * @param {Object} options - Update options
786
+ * @returns {Object} Results
787
+ */
788
+ export async function executeAcceptAll(summary, backupDir, options = {}) {
789
+ const results = {
790
+ updated: [],
791
+ kept: [],
792
+ added: [],
793
+ backedUp: []
794
+ };
795
+
796
+ // Add all new files
797
+ for (const f of summary.new) {
798
+ await fs.ensureDir(path.dirname(f.targetPath));
799
+ await fs.copy(f.sourcePath, f.targetPath);
800
+ results.added.push(f.file);
801
+ if (options.verbose) {
802
+ console.log(chalk.green(` āž• ${f.file} (new)`));
803
+ }
804
+ }
805
+
806
+ // Replace all modified files with backups
807
+ for (const f of summary.modified) {
808
+ // Create backup
809
+ const backupPath = path.join(backupDir, f.componentType, f.file);
810
+ await fs.ensureDir(path.dirname(backupPath));
811
+ await fs.copy(f.targetPath, backupPath);
812
+ results.backedUp.push(f.file);
813
+
814
+ // Replace with new version
815
+ await fs.copy(f.sourcePath, f.targetPath);
816
+ results.updated.push(f.file);
817
+ if (options.verbose) {
818
+ console.log(chalk.blue(` āœ… ${f.file} (updated, backup created)`));
819
+ }
820
+ }
821
+
822
+ return results;
823
+ }
824
+
825
+ /**
826
+ * Execute keep-all strategy
827
+ * @param {Object} summary - Change summary
828
+ * @param {Object} options - Update options
829
+ * @returns {Object} Results
830
+ */
831
+ export async function executeKeepAll(summary, options = {}) {
832
+ const results = {
833
+ updated: [],
834
+ kept: [],
835
+ added: [],
836
+ backedUp: []
837
+ };
838
+
839
+ // Add all new files
840
+ for (const f of summary.new) {
841
+ await fs.ensureDir(path.dirname(f.targetPath));
842
+ await fs.copy(f.sourcePath, f.targetPath);
843
+ results.added.push(f.file);
844
+ if (options.verbose) {
845
+ console.log(chalk.green(` āž• ${f.file} (new)`));
846
+ }
847
+ }
848
+
849
+ // Keep all modified files
850
+ results.kept = summary.modified.map(f => f.file);
851
+ if (options.verbose) {
852
+ for (const f of summary.modified) {
853
+ console.log(chalk.gray(` šŸ›”ļø ${f.file} (kept)`));
854
+ }
855
+ }
856
+
857
+ return results;
858
+ }
859
+
860
+ /**
861
+ * Execute category-based update
862
+ * @param {Object} summary - Change summary
863
+ * @param {string} backupDir - Backup directory path
864
+ * @param {Object} options - Update options
865
+ * @returns {Object} Results
866
+ */
867
+ export async function executeCategoryUpdate(summary, backupDir, options = {}) {
868
+ const results = {
869
+ updated: [],
870
+ kept: [],
871
+ added: [],
872
+ backedUp: []
873
+ };
874
+
875
+ // Add all new files first
876
+ for (const f of summary.new) {
877
+ await fs.ensureDir(path.dirname(f.targetPath));
878
+ await fs.copy(f.sourcePath, f.targetPath);
879
+ results.added.push(f.file);
880
+ if (options.verbose) {
881
+ console.log(chalk.green(` āž• ${f.file} (new)`));
882
+ }
883
+ }
884
+
885
+ // Process each category with modified files
886
+ for (const [category, data] of Object.entries(summary.byCategory)) {
887
+ if (data.modified.length === 0) continue;
888
+
889
+ const decision = await promptCategoryDecision(category, data.modified);
890
+
891
+ if (decision === 'keep') {
892
+ results.kept.push(...data.modified.map(f => f.file));
893
+ for (const f of data.modified) {
894
+ if (options.verbose) {
895
+ console.log(chalk.gray(` šŸ›”ļø ${f.file} (kept)`));
896
+ }
897
+ }
898
+ } else if (decision === 'update') {
899
+ for (const f of data.modified) {
900
+ // Create backup
901
+ const backupPath = path.join(backupDir, f.componentType, f.file);
902
+ await fs.ensureDir(path.dirname(backupPath));
903
+ await fs.copy(f.targetPath, backupPath);
904
+ results.backedUp.push(f.file);
905
+
906
+ // Replace with new version
907
+ await fs.copy(f.sourcePath, f.targetPath);
908
+ results.updated.push(f.file);
909
+ if (options.verbose) {
910
+ console.log(chalk.blue(` āœ… ${f.file} (updated)`));
911
+ }
912
+ }
913
+ } else {
914
+ // Individual review for this category
915
+ for (const f of data.modified) {
916
+ const action = await promptForConflict(f.file, f.targetPath, f.sourcePath);
917
+
918
+ if (action === 'replace') {
919
+ await fs.copy(f.sourcePath, f.targetPath);
920
+ results.updated.push(f.file);
921
+ } else if (action === 'backup') {
922
+ const backupPath = path.join(backupDir, f.componentType, f.file);
923
+ await fs.ensureDir(path.dirname(backupPath));
924
+ await fs.copy(f.targetPath, backupPath);
925
+ results.backedUp.push(f.file);
926
+ await fs.copy(f.sourcePath, f.targetPath);
927
+ results.updated.push(f.file);
928
+ } else {
929
+ results.kept.push(f.file);
930
+ }
931
+ }
932
+ }
933
+ }
934
+
935
+ return results;
936
+ }
937
+
938
+ /**
939
+ * Display final update results
940
+ * @param {Object} results - Update results
941
+ * @param {string} backupDir - Backup directory path
942
+ * @param {string} newVersion - New version number
943
+ */
944
+ export function displayUpdateResults(results, backupDir, newVersion) {
945
+ const boxWidth = 69;
946
+ const topBorder = 'ā•”' + '═'.repeat(boxWidth) + 'ā•—';
947
+ const midBorder = 'ā• ' + '═'.repeat(boxWidth) + 'ā•£';
948
+ const botBorder = 'ā•š' + '═'.repeat(boxWidth) + 'ā•';
949
+
950
+ console.log('');
951
+ console.log(chalk.green(topBorder));
952
+ console.log(chalk.green('ā•‘') + chalk.white.bold(' āœ… Update Complete! '.padEnd(boxWidth)) + chalk.green('ā•‘'));
953
+ console.log(chalk.green(midBorder));
954
+
955
+ console.log(chalk.green('ā•‘') + chalk.white.bold(' šŸ“Š Results:'.padEnd(boxWidth)) + chalk.green('ā•‘'));
956
+
957
+ if (results.updated.length > 0) {
958
+ const line = ` ā”œā”€ Updated: ${results.updated.length} files`;
959
+ console.log(chalk.green('ā•‘') + chalk.blue(line.padEnd(boxWidth)) + chalk.green('ā•‘'));
960
+ }
961
+
962
+ if (results.kept.length > 0) {
963
+ const line = ` ā”œā”€ Kept: ${results.kept.length} files (your customizations preserved)`;
964
+ console.log(chalk.green('ā•‘') + chalk.gray(line.padEnd(boxWidth)) + chalk.green('ā•‘'));
965
+ }
966
+
967
+ if (results.added.length > 0) {
968
+ const line = ` ā”œā”€ Added: ${results.added.length} new files`;
969
+ console.log(chalk.green('ā•‘') + chalk.green(line.padEnd(boxWidth)) + chalk.green('ā•‘'));
970
+ }
971
+
972
+ if (results.backedUp.length > 0) {
973
+ const relativePath = path.relative(process.cwd(), backupDir);
974
+ const line = ` └─ Backed up: ${results.backedUp.length} files → ${relativePath}/`;
975
+ console.log(chalk.green('ā•‘') + chalk.yellow(line.padEnd(boxWidth)) + chalk.green('ā•‘'));
976
+ }
977
+
978
+ console.log(chalk.green('ā•‘') + ' '.repeat(boxWidth) + chalk.green('ā•‘'));
979
+
980
+ // What's new section
981
+ console.log(chalk.green('ā•‘') + chalk.white.bold(` šŸ†• Updated to v${newVersion}`.padEnd(boxWidth)) + chalk.green('ā•‘'));
982
+ console.log(chalk.green('ā•‘') + chalk.gray(' Run /myai-configure --status to see your configuration'.padEnd(boxWidth)) + chalk.green('ā•‘'));
983
+
984
+ console.log(chalk.green(botBorder));
985
+ console.log('');
986
+ }
987
+
988
+ /**
989
+ * Create timestamped backup directory
990
+ * @param {string} projectDir - Project directory
991
+ * @param {string} cliType - CLI type
992
+ * @returns {string} Backup directory path
993
+ */
994
+ export async function createTimestampedBackup(projectDir, cliType) {
995
+ const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
996
+ const backupDir = path.join(projectDir, `.${cliType}`, 'backups', date);
997
+ await fs.ensureDir(backupDir);
998
+ return backupDir;
999
+ }