claude-git-hooks 2.15.5 → 2.16.0

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.
@@ -4,24 +4,28 @@
4
4
  *
5
5
  * Workflow:
6
6
  * 1. Validate prerequisites (clean working directory, valid branch, remote)
7
- * 2. Detect project type and current version
8
- * 3. Calculate new version with optional suffix
9
- * 4. Update version file(s)
10
- * 5. [Optional] Generate and update CHANGELOG
11
- * 6. Stage and commit changes (skipped with --no-commit)
12
- * 7. Create annotated Git tag (skipped with --no-tag or --no-commit)
13
- * 8. Push tag to remote (only if --push flag provided, otherwise pushed by create-pr)
7
+ * 2. Discover all version files recursively
8
+ * 3. [If mismatch OR --interactive] File selection menu
9
+ * 4. Calculate new version OR apply suffix operation
10
+ * 5. [Dry-run check]
11
+ * 6. Confirm with user
12
+ * 7. Snapshot files for rollback
13
+ * 8. Update version files
14
+ * 9. [Optional] Generate and update CHANGELOG
15
+ * 10. Stage and commit changes (skipped with --no-commit)
16
+ * 11. Create annotated Git tag (skipped with --no-tag or --no-commit)
17
+ * 12. Push tag to remote (only if --push flag provided, otherwise pushed by create-pr)
14
18
  */
15
19
 
16
20
  import { execSync } from 'child_process';
17
21
  import fs from 'fs';
18
22
  import path from 'path';
19
23
  import {
20
- detectProjectType,
21
- getCurrentVersion,
24
+ discoverVersionFiles,
22
25
  incrementVersion,
23
- updateVersion,
24
- getDiscoveredPaths
26
+ modifySuffix,
27
+ updateVersionFiles,
28
+ validateVersionFormat
25
29
  } from '../utils/version-manager.js';
26
30
  import {
27
31
  createTag,
@@ -42,12 +46,20 @@ import {
42
46
  createCommit
43
47
  } from '../utils/git-operations.js';
44
48
  import { getConfig } from '../config.js';
45
- import { showInfo, showSuccess, showError, showWarning, promptConfirmation } from '../utils/interactive-ui.js';
49
+ import {
50
+ showInfo,
51
+ showSuccess,
52
+ showError,
53
+ showWarning,
54
+ promptConfirmation,
55
+ promptMenu,
56
+ promptToggleList,
57
+ promptEditField
58
+ } from '../utils/interactive-ui.js';
46
59
  import logger from '../utils/logger.js';
47
60
  import {
48
61
  colors,
49
62
  error,
50
- info,
51
63
  checkGitRepo
52
64
  } from './helpers.js';
53
65
 
@@ -103,7 +115,7 @@ function validatePrerequisites() {
103
115
  logger.error('bump-version - validatePrerequisites', 'Validation failed', err);
104
116
  return {
105
117
  valid: false,
106
- errors: [`Validation error: ${ err.message}`]
118
+ errors: [`Validation error: ${err.message}`]
107
119
  };
108
120
  }
109
121
  }
@@ -113,7 +125,7 @@ function validatePrerequisites() {
113
125
  * Why: Extracts bump type and options from CLI args
114
126
  *
115
127
  * @param {Array<string>} args - CLI arguments
116
- * @returns {Object} Parsed args: { bumpType, suffix, updateChangelog, baseBranch, dryRun, noTag, push, noCommit }
128
+ * @returns {Object} Parsed args
117
129
  */
118
130
  function parseArguments(args) {
119
131
  logger.debug('bump-version - parseArguments', 'Parsing arguments', { args });
@@ -121,31 +133,45 @@ function parseArguments(args) {
121
133
  const parsed = {
122
134
  bumpType: null,
123
135
  suffix: null,
136
+ removeSuffix: false,
137
+ setSuffix: null,
138
+ interactive: false,
124
139
  updateChangelog: false,
125
140
  baseBranch: 'main',
126
141
  dryRun: false,
127
142
  noTag: false,
128
- push: false, // Changed: default to NOT push (use --push to enable)
143
+ push: false,
129
144
  noCommit: false
130
145
  };
131
146
 
132
- // First argument should be bump type
147
+ // First argument should be bump type (or suffix-only flags)
133
148
  if (args.length === 0) {
134
149
  return parsed;
135
150
  }
136
151
 
152
+ let i = 0;
153
+
154
+ // Check if first arg is bump type
137
155
  const firstArg = args[0].toLowerCase();
138
156
  if (['major', 'minor', 'patch'].includes(firstArg)) {
139
157
  parsed.bumpType = firstArg;
158
+ i = 1;
140
159
  }
141
160
 
142
161
  // Parse options
143
- for (let i = 1; i < args.length; i++) {
162
+ for (; i < args.length; i++) {
144
163
  const arg = args[i];
145
164
 
146
165
  if (arg === '--suffix' && i + 1 < args.length) {
147
166
  parsed.suffix = args[i + 1];
148
167
  i++; // Skip next arg
168
+ } else if (arg === '--remove-suffix') {
169
+ parsed.removeSuffix = true;
170
+ } else if (arg === '--set-suffix' && i + 1 < args.length) {
171
+ parsed.setSuffix = args[i + 1];
172
+ i++; // Skip next arg
173
+ } else if (arg === '--interactive') {
174
+ parsed.interactive = true;
149
175
  } else if (arg === '--update-changelog') {
150
176
  parsed.updateChangelog = true;
151
177
  // Check if next arg is a branch name (not starting with --)
@@ -169,6 +195,50 @@ function parseArguments(args) {
169
195
  return parsed;
170
196
  }
171
197
 
198
+ /**
199
+ * Displays version files discovery table
200
+ * Why: Show user all discovered version files
201
+ *
202
+ * @param {Object} discovery - DiscoveryResult from discoverVersionFiles()
203
+ */
204
+ function displayDiscoveryTable(discovery) {
205
+ console.log('');
206
+ console.log(`${colors.blue}═══════════════════════════════════════════════════════════════${colors.reset}`);
207
+ console.log(`${colors.blue} Version Files Discovery ${colors.reset}`);
208
+ console.log(`${colors.blue}═══════════════════════════════════════════════════════════════${colors.reset}`);
209
+ console.log('');
210
+
211
+ if (discovery.files.length === 0) {
212
+ console.log(' No version files found.');
213
+ console.log('');
214
+ return;
215
+ }
216
+
217
+ // Table header
218
+ console.log(` ${'#'.padEnd(3)} ${'File'.padEnd(35)} ${'Type'.padEnd(15)} ${'Version'.padEnd(15)}`);
219
+ console.log(` ${'-'.repeat(3)} ${'-'.repeat(35)} ${'-'.repeat(15)} ${'-'.repeat(15)}`);
220
+
221
+ // Table rows
222
+ discovery.files.forEach((file, index) => {
223
+ const num = `${index + 1}`.padEnd(3);
224
+ const filePath = file.relativePath.padEnd(35);
225
+ const fileType = file.projectLabel.padEnd(15);
226
+ const version = file.version ? file.version.padEnd(15) : 'N/A'.padEnd(15);
227
+ const mismatchMarker = discovery.mismatch && file.version !== discovery.resolvedVersion ? ' ⚠️ ' : '';
228
+
229
+ console.log(` ${num} ${filePath} ${fileType} ${version}${mismatchMarker}`);
230
+ });
231
+
232
+ console.log('');
233
+ console.log(` Resolved version: ${discovery.resolvedVersion || 'N/A'}`);
234
+
235
+ if (discovery.mismatch) {
236
+ console.log(` ${colors.yellow}⚠️ Version mismatch detected${colors.reset}`);
237
+ }
238
+
239
+ console.log('');
240
+ }
241
+
172
242
  /**
173
243
  * Displays dry run preview
174
244
  * Why: Shows what would change without applying
@@ -181,15 +251,21 @@ function showDryRunPreview(info) {
181
251
  console.log(`${colors.yellow} DRY RUN - No changes will be made ${colors.reset}`);
182
252
  console.log(`${colors.yellow}═════════════════════════════════════════════════${colors.reset}`);
183
253
  console.log('');
184
- console.log(`${colors.blue}Project Type:${colors.reset} ${info.projectType}`);
254
+
255
+ displayDiscoveryTable(info.discovery);
256
+
257
+ console.log(`${colors.blue}Operation:${colors.reset} ${info.operation}`);
185
258
  console.log(`${colors.blue}Current Version:${colors.reset} ${info.currentVersion}`);
186
259
  console.log(`${colors.green}New Version:${colors.reset} ${info.newVersion}`);
187
260
  console.log(`${colors.blue}Tag Name:${colors.reset} ${info.tagName}`);
188
261
  console.log('');
189
262
 
190
- if (info.files.length > 0) {
263
+ if (info.selectedFiles.length > 0) {
191
264
  console.log(`${colors.blue}Files to update:${colors.reset}`);
192
- info.files.forEach(file => console.log(` - ${file}`));
265
+ info.selectedFiles.forEach(file => {
266
+ const targetVer = file.targetVersion || info.newVersion;
267
+ console.log(` - ${file.relativePath} (${file.version} → ${targetVer})`);
268
+ });
193
269
  console.log('');
194
270
  }
195
271
 
@@ -202,6 +278,100 @@ function showDryRunPreview(info) {
202
278
  console.log('');
203
279
  }
204
280
 
281
+ /**
282
+ * Prompts user for file selection when mismatch or --interactive
283
+ * Why: Let user choose which files to update
284
+ *
285
+ * @param {Object} discovery - DiscoveryResult
286
+ * @returns {Promise<Array>} Selected files
287
+ */
288
+ async function promptFileSelection(discovery) {
289
+ console.log('');
290
+ showWarning('Version mismatch detected or interactive mode enabled');
291
+ console.log('');
292
+
293
+ const options = [
294
+ { key: 'a', label: 'Update all files' },
295
+ { key: 's', label: 'Select which files to update' },
296
+ { key: 'e', label: 'Edit version per file (advanced)' },
297
+ { key: 'c', label: 'Cancel' }
298
+ ];
299
+
300
+ const choice = await promptMenu('Choose action:', options, 'a');
301
+
302
+ if (choice === 'c') {
303
+ showInfo('Version bump cancelled');
304
+ process.exit(0);
305
+ }
306
+
307
+ if (choice === 'a') {
308
+ // All files selected
309
+ return discovery.files;
310
+ }
311
+
312
+ if (choice === 's') {
313
+ // Toggle list selection
314
+ const items = discovery.files.map((file, index) => ({
315
+ key: `${index}`,
316
+ label: `${file.relativePath} (${file.projectLabel}) - ${file.version || 'N/A'}`,
317
+ selected: file.selected
318
+ }));
319
+
320
+ const selectedKeys = await promptToggleList(
321
+ 'Select files to update (type number to toggle, Enter to confirm):',
322
+ items
323
+ );
324
+
325
+ // Filter files by selected keys
326
+ return discovery.files.filter((_, index) => selectedKeys.includes(`${index}`));
327
+ }
328
+
329
+ if (choice === 'e') {
330
+ // Per-file version editing
331
+ showInfo('Enter target version for each file (press Enter to keep calculated version)');
332
+ console.log('');
333
+
334
+ for (const file of discovery.files) {
335
+ const input = await promptEditField(
336
+ `${file.relativePath} (${file.projectLabel})`,
337
+ file.version || 'N/A'
338
+ );
339
+
340
+ // If user entered something different from current version, store it
341
+ if (input !== file.version && input !== 'N/A') {
342
+ if (!validateVersionFormat(input)) {
343
+ showWarning(`Invalid version format: ${input} — skipping ${file.relativePath}`);
344
+ continue;
345
+ }
346
+ file.targetVersion = input;
347
+ }
348
+ }
349
+
350
+ return discovery.files;
351
+ }
352
+
353
+ return discovery.files;
354
+ }
355
+
356
+ /**
357
+ * Restores files from a snapshot map
358
+ * Why: Centralized rollback logic used by multiple error paths
359
+ *
360
+ * @param {Map<string, string>} snapshot - Map of filePath → original content
361
+ */
362
+ function restoreSnapshot(snapshot) {
363
+ snapshot.forEach((content, filePath) => {
364
+ try {
365
+ fs.writeFileSync(filePath, content, 'utf8');
366
+ } catch (rollbackErr) {
367
+ logger.error('bump-version', 'Rollback failed', {
368
+ path: filePath,
369
+ error: rollbackErr.message
370
+ });
371
+ }
372
+ });
373
+ }
374
+
205
375
  /**
206
376
  * Bump version command
207
377
  * @param {Array<string>} args - Command arguments
@@ -212,25 +382,40 @@ export async function runBumpVersion(args) {
212
382
  // Parse arguments
213
383
  const options = parseArguments(args);
214
384
 
215
- if (!options.bumpType) {
385
+ // Determine if this is a suffix-only operation
386
+ const isSuffixOnly = (options.removeSuffix || options.setSuffix) && !options.bumpType;
387
+
388
+ // Validate arguments
389
+ if (!options.bumpType && !isSuffixOnly) {
216
390
  error('Usage: claude-hooks bump-version <major|minor|patch> [options]');
391
+ error(' or: claude-hooks bump-version --remove-suffix [options]');
392
+ error(' or: claude-hooks bump-version --set-suffix <value> [options]');
217
393
  console.log('');
218
394
  console.log('Options:');
219
- console.log(' --suffix <value> Add version suffix (e.g., SNAPSHOT, RC)');
395
+ console.log(' --suffix <value> Add version suffix (e.g., SNAPSHOT, RC)');
396
+ console.log(' --remove-suffix Remove version suffix (2.15.5-SNAPSHOT → 2.15.5)');
397
+ console.log(' --set-suffix <value> Set/replace suffix (2.15.5 → 2.15.5-SNAPSHOT)');
398
+ console.log(' --interactive Force interactive file selection menu');
220
399
  console.log(' --update-changelog [branch] Generate CHANGELOG entry (default branch: main)');
221
- console.log(' --dry-run Preview changes without applying');
222
- console.log(' --no-tag Skip Git tag creation');
223
- console.log(' --push Push tag to remote (default: tags stay local)');
224
- console.log(' --no-commit Skip automatic commit (manual workflow)');
400
+ console.log(' --dry-run Preview changes without applying');
401
+ console.log(' --no-tag Skip Git tag creation');
402
+ console.log(' --push Push tag to remote (default: tags stay local)');
403
+ console.log(' --no-commit Skip automatic commit (manual workflow)');
225
404
  console.log('');
226
405
  console.log('Examples:');
227
406
  console.log(' claude-hooks bump-version minor --suffix SNAPSHOT');
228
407
  console.log(' claude-hooks bump-version patch --update-changelog');
408
+ console.log(' claude-hooks bump-version --remove-suffix');
409
+ console.log(' claude-hooks bump-version --set-suffix RC1');
229
410
  console.log(' claude-hooks bump-version major --dry-run');
230
411
  return;
231
412
  }
232
413
 
233
- showInfo(`🔢 Bumping version (${options.bumpType})...`);
414
+ if (isSuffixOnly) {
415
+ showInfo('🔧 Performing suffix-only operation...');
416
+ } else {
417
+ showInfo(`🔢 Bumping version (${options.bumpType})...`);
418
+ }
234
419
  console.log('');
235
420
 
236
421
  // Step 1: Validate prerequisites
@@ -252,86 +437,92 @@ export async function runBumpVersion(args) {
252
437
 
253
438
  showSuccess('Prerequisites validated');
254
439
 
255
- // Step 2: Detect project type and current version
256
- logger.debug('bump-version', 'Step 2: Detecting project type and version');
257
- const projectType = detectProjectType();
440
+ // Step 2: Discover all version files
441
+ logger.debug('bump-version', 'Step 2: Discovering version files');
442
+ const discovery = discoverVersionFiles();
258
443
 
259
- if (projectType === 'none') {
444
+ if (discovery.files.length === 0) {
260
445
  showError('No version files found');
261
446
  console.log('');
262
- console.log('This command requires either:');
263
- console.log(' - package.json (Node.js project)');
264
- console.log(' - pom.xml (Maven project)');
447
+ console.log('This command requires at least one of:');
448
+ console.log(' - package.json (Node.js)');
449
+ console.log(' - pom.xml (Maven)');
450
+ console.log(' - build.gradle (Gradle)');
451
+ console.log(' - build.gradle.kts (Gradle Kotlin)');
452
+ console.log(' - pyproject.toml (Python)');
453
+ console.log(' - Cargo.toml (Rust)');
454
+ console.log(' - version.sbt (Scala/sbt)');
265
455
  console.log('');
266
- console.log('Searched in repository root and one level deep in subdirectories.');
456
+ console.log('Searched recursively up to 3 levels deep.');
267
457
  console.log('');
268
458
  process.exit(1);
269
459
  }
270
460
 
271
- // Show discovered paths
272
- const paths = getDiscoveredPaths();
273
- const repoRoot = getRepoRoot();
274
- if (paths.packageJson) {
275
- const relativePath = path.relative(repoRoot, paths.packageJson);
276
- info(`Found package.json: ${relativePath}`);
277
- }
278
- if (paths.pomXml) {
279
- const relativePath = path.relative(repoRoot, paths.pomXml);
280
- info(`Found pom.xml: ${relativePath}`);
281
- }
461
+ // Display discovery table
462
+ displayDiscoveryTable(discovery);
282
463
 
283
- const versions = getCurrentVersion(projectType);
284
- const currentVersion = versions.resolved;
464
+ const currentVersion = discovery.resolvedVersion;
285
465
 
286
466
  if (!currentVersion) {
287
- showError('Could not determine current version');
467
+ showError('Could not determine current version from discovered files');
288
468
  process.exit(1);
289
469
  }
290
470
 
291
- // Warn if monorepo has version mismatch
292
- if (versions.mismatch) {
293
- showWarning('Version mismatch detected in monorepo:');
294
- console.log(` package.json: ${versions.packageJson}`);
295
- console.log(` pom.xml: ${versions.pomXml}`);
296
- console.log('');
297
- console.log('Both files will be updated to the new version.');
471
+ showInfo(`Current version: ${currentVersion}`);
472
+
473
+ // Step 3: File selection (if mismatch or interactive mode)
474
+ let selectedFiles = discovery.files.filter(f => f.selected);
475
+
476
+ if (discovery.mismatch || options.interactive) {
477
+ selectedFiles = await promptFileSelection(discovery);
478
+
479
+ if (selectedFiles.length === 0) {
480
+ showError('No files selected');
481
+ process.exit(1);
482
+ }
483
+
484
+ showSuccess(`${selectedFiles.length} file(s) selected for update`);
298
485
  console.log('');
299
486
  }
300
487
 
301
- showInfo(`Project: ${projectType}`);
302
- showInfo(`Current version: ${currentVersion}`);
488
+ // Step 4: Calculate new version or apply suffix operation
489
+ logger.debug('bump-version', 'Step 4: Calculating new version');
490
+ let newVersion;
491
+ let operation;
492
+
493
+ if (isSuffixOnly) {
494
+ if (options.removeSuffix) {
495
+ newVersion = modifySuffix(currentVersion, { remove: true });
496
+ operation = 'Remove suffix';
497
+ } else if (options.setSuffix) {
498
+ newVersion = modifySuffix(currentVersion, { set: options.setSuffix });
499
+ operation = `Set suffix to ${options.setSuffix}`;
500
+ }
501
+ } else {
502
+ newVersion = incrementVersion(currentVersion, options.bumpType, options.suffix || options.setSuffix);
503
+ operation = `Bump ${options.bumpType}`;
504
+ }
303
505
 
304
- // Step 3: Calculate new version
305
- logger.debug('bump-version', 'Step 3: Calculating new version');
306
- const newVersion = incrementVersion(currentVersion, options.bumpType, options.suffix);
307
506
  const tagName = formatTagName(newVersion);
308
507
 
309
508
  showSuccess(`New version: ${newVersion}`);
310
509
  console.log('');
311
510
 
312
- // Prepare files list
313
- const filesToUpdate = [];
314
- if (projectType === 'node' || projectType === 'both') {
315
- filesToUpdate.push('package.json');
316
- }
317
- if (projectType === 'maven' || projectType === 'both') {
318
- filesToUpdate.push('pom.xml');
319
- }
320
-
321
- // Dry run preview
511
+ // Step 5: Dry run preview
322
512
  if (options.dryRun) {
323
513
  showDryRunPreview({
324
- projectType,
514
+ discovery,
515
+ operation,
325
516
  currentVersion,
326
517
  newVersion,
327
518
  tagName,
328
- files: filesToUpdate,
519
+ selectedFiles,
329
520
  updateChangelog: options.updateChangelog
330
521
  });
331
522
  return;
332
523
  }
333
524
 
334
- // Confirm with user
525
+ // Step 6: Confirm with user
335
526
  const shouldContinue = await promptConfirmation(
336
527
  `Update version to ${newVersion}?`,
337
528
  true
@@ -350,23 +541,53 @@ export async function runBumpVersion(args) {
350
541
  const defaultBranch = config.github?.pr?.defaultBase || '{target-branch}';
351
542
  logger.debug('bump-version', 'Default branch for PR', { defaultBranch });
352
543
 
353
- // Step 4: Update version files
354
- logger.debug('bump-version', 'Step 4: Updating version files');
355
- showInfo('Updating version files...');
356
- updateVersion(projectType, newVersion);
544
+ // Step 7: Snapshot files for rollback
545
+ logger.debug('bump-version', 'Step 7: Creating snapshot for rollback');
546
+ const snapshot = new Map();
547
+ selectedFiles.forEach(file => {
548
+ try {
549
+ const content = fs.readFileSync(file.path, 'utf8');
550
+ snapshot.set(file.path, content);
551
+ } catch (err) {
552
+ logger.error('bump-version', 'Failed to snapshot file', {
553
+ path: file.path,
554
+ error: err.message
555
+ });
556
+ }
557
+ });
357
558
 
358
- filesToUpdate.forEach(file => {
359
- showSuccess(`✓ Updated ${file}`);
559
+ logger.debug('bump-version', 'Snapshot created', {
560
+ fileCount: snapshot.size
360
561
  });
361
562
 
362
- console.log('');
563
+ // Step 8: Update version files
564
+ logger.debug('bump-version', 'Step 8: Updating version files');
565
+ showInfo('Updating version files...');
363
566
 
364
- // Step 5: Generate CHANGELOG (if requested)
567
+ try {
568
+ updateVersionFiles(selectedFiles, newVersion);
569
+
570
+ selectedFiles.forEach(file => {
571
+ const targetVer = file.targetVersion || newVersion;
572
+ showSuccess(`✓ Updated ${file.relativePath} → ${targetVer}`);
573
+ });
574
+
575
+ console.log('');
576
+ } catch (err) {
577
+ // Rollback on failure
578
+ showError(`Failed to update version files: ${err.message}`);
579
+ showWarning('Rolling back changes...');
580
+ restoreSnapshot(snapshot);
581
+ showSuccess('Changes rolled back');
582
+ process.exit(1);
583
+ }
584
+
585
+ // Step 9: Generate CHANGELOG (if requested)
365
586
  if (options.updateChangelog) {
366
- logger.debug('bump-version', 'Step 5: Generating CHANGELOG');
587
+ logger.debug('bump-version', 'Step 9: Generating CHANGELOG');
367
588
  showInfo('Generating CHANGELOG entry...');
368
589
 
369
- const isReleaseVersion = !options.suffix; // Final version if no suffix
590
+ const isReleaseVersion = !options.suffix && !options.setSuffix;
370
591
 
371
592
  const changelogResult = await generateChangelogEntry({
372
593
  version: newVersion,
@@ -389,24 +610,15 @@ export async function runBumpVersion(args) {
389
610
  console.log('');
390
611
  }
391
612
 
392
- // Step 6: Stage and commit changes
613
+ // Step 10: Stage and commit changes
393
614
  let commitCreated = false;
394
615
 
395
616
  if (!options.noCommit) {
396
- logger.debug('bump-version', 'Step 6: Staging and committing changes');
617
+ logger.debug('bump-version', 'Step 10: Staging and committing changes');
397
618
  showInfo('Staging and committing changes...');
398
619
 
399
620
  // Collect files to stage
400
- const filesToStage = [];
401
-
402
- // Add discovered version files
403
- const paths = getDiscoveredPaths();
404
- if (paths.packageJson) {
405
- filesToStage.push(paths.packageJson);
406
- }
407
- if (paths.pomXml) {
408
- filesToStage.push(paths.pomXml);
409
- }
621
+ const filesToStage = selectedFiles.map(f => f.path);
410
622
 
411
623
  // Add CHANGELOG if it was updated
412
624
  if (options.updateChangelog) {
@@ -425,26 +637,39 @@ export async function runBumpVersion(args) {
425
637
  console.log('Files that should be staged:');
426
638
  filesToStage.forEach(f => console.log(` - ${path.relative(getRepoRoot(), f)}`));
427
639
  console.log('');
428
- console.log('You can stage and commit manually:');
429
- console.log(` git add ${filesToUpdate.join(' ')}`);
430
- if (options.updateChangelog) {
431
- console.log(' git add CHANGELOG.md');
432
- }
433
- console.log(` git commit -m "chore(version): bump to ${newVersion}"`);
640
+
641
+ // Rollback version changes
642
+ showWarning('Rolling back version changes...');
643
+ restoreSnapshot(snapshot);
644
+ showSuccess('Changes rolled back');
434
645
  process.exit(1);
435
646
  }
436
647
 
437
648
  showSuccess(`✓ Staged ${stageResult.stagedFiles.length} file(s)`);
438
649
 
439
650
  // Create commit
440
- const commitMessage = `chore(version): bump to ${newVersion}`;
651
+ const commitMessage = isSuffixOnly
652
+ ? `chore(version): update suffix to ${newVersion}`
653
+ : `chore(version): bump to ${newVersion}`;
654
+
441
655
  const commitResult = createCommit(commitMessage, { noVerify: true });
442
656
 
443
657
  if (!commitResult.success) {
444
658
  showError(`Failed to create commit: ${commitResult.error}`);
445
659
  console.log('');
446
- console.log('Files have been staged. You can commit manually:');
447
- console.log(` git commit -m "chore(version): bump to ${newVersion}"`);
660
+
661
+ // Rollback version changes and unstage
662
+ showWarning('Rolling back version changes...');
663
+ restoreSnapshot(snapshot);
664
+
665
+ // Unstage files
666
+ try {
667
+ execSync('git reset HEAD', { encoding: 'utf8' });
668
+ } catch (unstageErr) {
669
+ logger.error('bump-version', 'Failed to unstage', unstageErr);
670
+ }
671
+
672
+ showSuccess('Changes rolled back');
448
673
  process.exit(1);
449
674
  }
450
675
 
@@ -456,9 +681,9 @@ export async function runBumpVersion(args) {
456
681
  console.log('');
457
682
  }
458
683
 
459
- // Step 7: Create Git tag (if not disabled)
684
+ // Step 11: Create Git tag (if not disabled)
460
685
  if (!options.noTag) {
461
- logger.debug('bump-version', 'Step 7: Creating Git tag');
686
+ logger.debug('bump-version', 'Step 11: Creating Git tag');
462
687
 
463
688
  // Prevent tag creation if changes not committed
464
689
  if (options.noCommit) {
@@ -495,9 +720,9 @@ export async function runBumpVersion(args) {
495
720
  showSuccess(`✓ Tag created: ${tagName}`);
496
721
  console.log('');
497
722
 
498
- // Step 8: Push tag (if --push flag provided)
723
+ // Step 12: Push tag (if --push flag provided)
499
724
  if (options.push) {
500
- logger.debug('bump-version', 'Step 8: Pushing tag to remote');
725
+ logger.debug('bump-version', 'Step 12: Pushing tag to remote');
501
726
  showInfo('Pushing tag to remote...');
502
727
 
503
728
  const pushResult = pushTags(null, tagName);
@@ -530,6 +755,15 @@ export async function runBumpVersion(args) {
530
755
  console.log(`${colors.green} Version Bump Complete ✅ ${colors.reset}`);
531
756
  console.log(`${colors.green}════════════════════════════════════════════════${colors.reset}`);
532
757
  console.log('');
758
+
759
+ // Show updated files table
760
+ console.log(`${colors.blue}Updated files:${colors.reset}`);
761
+ selectedFiles.forEach(file => {
762
+ const targetVer = file.targetVersion || newVersion;
763
+ console.log(` ✓ ${file.relativePath} (${file.projectLabel}) - ${file.version} → ${targetVer}`);
764
+ });
765
+ console.log('');
766
+
533
767
  console.log(`${colors.blue}Old version:${colors.reset} ${currentVersion}`);
534
768
  console.log(`${colors.blue}New version:${colors.reset} ${newVersion}`);
535
769
  console.log(`${colors.blue}Tag:${colors.reset} ${tagName}`);
@@ -539,7 +773,7 @@ export async function runBumpVersion(args) {
539
773
  if (options.noCommit) {
540
774
  console.log('Next steps:');
541
775
  console.log(' 1. Review changes: git diff');
542
- console.log(` 2. Stage files: git add ${filesToUpdate.join(' ')}`);
776
+ console.log(' 2. Stage files: git add <files>');
543
777
  if (options.updateChangelog) {
544
778
  console.log(' git add CHANGELOG.md');
545
779
  }
@@ -566,9 +800,7 @@ export async function runBumpVersion(args) {
566
800
  logger.error('bump-version', 'Version bump failed', err);
567
801
  showError(`Version bump failed: ${err.message}`);
568
802
  console.log('');
569
- console.log('The operation was interrupted. You may need to:');
570
- console.log(' - Revert changes: git checkout .');
571
- console.log(` - Delete tag if created: git tag -d ${ tagName}`);
803
+ console.log('The operation was interrupted.');
572
804
  console.log('');
573
805
  process.exit(1);
574
806
  }