claude-git-hooks 2.10.0 → 2.12.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.
@@ -0,0 +1,452 @@
1
+ /**
2
+ * File: bump-version.js
3
+ * Purpose: Bump version command with automatic CHANGELOG and Git tag
4
+ *
5
+ * Workflow:
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. Create annotated Git tag
12
+ * 7. Push tag to remote
13
+ */
14
+
15
+ import { execSync } from 'child_process';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import {
19
+ detectProjectType,
20
+ getCurrentVersion,
21
+ incrementVersion,
22
+ updateVersion,
23
+ parseVersion
24
+ } from '../utils/version-manager.js';
25
+ import {
26
+ createTag,
27
+ pushTags,
28
+ tagExists,
29
+ formatTagName
30
+ } from '../utils/git-tag-manager.js';
31
+ import {
32
+ generateChangelogEntry,
33
+ updateChangelogFile
34
+ } from '../utils/changelog-generator.js';
35
+ import {
36
+ getRepoRoot,
37
+ getCurrentBranch,
38
+ verifyRemoteExists,
39
+ getRemoteName
40
+ } from '../utils/git-operations.js';
41
+ import { getConfig } from '../config.js';
42
+ import { showInfo, showSuccess, showError, showWarning, promptConfirmation } from '../utils/interactive-ui.js';
43
+ import logger from '../utils/logger.js';
44
+ import {
45
+ colors,
46
+ error,
47
+ success,
48
+ info,
49
+ warning,
50
+ checkGitRepo
51
+ } from './helpers.js';
52
+
53
+ /**
54
+ * Validates prerequisites before version bump
55
+ * Why: Prevents version bump in invalid state
56
+ *
57
+ * @returns {Object} Validation result: { valid, errors }
58
+ */
59
+ function validatePrerequisites() {
60
+ logger.debug('bump-version - validatePrerequisites', 'Validating prerequisites');
61
+
62
+ const errors = [];
63
+
64
+ try {
65
+ // Check if git repo
66
+ if (!checkGitRepo()) {
67
+ errors.push('Not a git repository');
68
+ }
69
+
70
+ // Check for clean working directory
71
+ try {
72
+ const status = execSync('git status --porcelain', { encoding: 'utf8' }).trim();
73
+ if (status) {
74
+ errors.push('Working directory has uncommitted changes');
75
+ }
76
+ } catch (err) {
77
+ errors.push('Failed to check git status');
78
+ }
79
+
80
+ // Check for valid branch (not detached HEAD)
81
+ const currentBranch = getCurrentBranch();
82
+ if (!currentBranch || currentBranch === 'unknown') {
83
+ errors.push('Not on a valid branch (detached HEAD?)');
84
+ }
85
+
86
+ // Check for remote
87
+ const remoteName = getRemoteName();
88
+ if (!verifyRemoteExists(remoteName)) {
89
+ errors.push(`Remote '${remoteName}' does not exist`);
90
+ }
91
+
92
+ const valid = errors.length === 0;
93
+
94
+ logger.debug('bump-version - validatePrerequisites', 'Validation complete', {
95
+ valid,
96
+ errorCount: errors.length
97
+ });
98
+
99
+ return { valid, errors };
100
+
101
+ } catch (err) {
102
+ logger.error('bump-version - validatePrerequisites', 'Validation failed', err);
103
+ return {
104
+ valid: false,
105
+ errors: ['Validation error: ' + err.message]
106
+ };
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Parses command line arguments
112
+ * Why: Extracts bump type and options from CLI args
113
+ *
114
+ * @param {Array<string>} args - CLI arguments
115
+ * @returns {Object} Parsed args: { bumpType, suffix, updateChangelog, baseBranch, dryRun, noTag, noPush }
116
+ */
117
+ function parseArguments(args) {
118
+ logger.debug('bump-version - parseArguments', 'Parsing arguments', { args });
119
+
120
+ const parsed = {
121
+ bumpType: null,
122
+ suffix: null,
123
+ updateChangelog: false,
124
+ baseBranch: 'main',
125
+ dryRun: false,
126
+ noTag: false,
127
+ noPush: false
128
+ };
129
+
130
+ // First argument should be bump type
131
+ if (args.length === 0) {
132
+ return parsed;
133
+ }
134
+
135
+ const firstArg = args[0].toLowerCase();
136
+ if (['major', 'minor', 'patch'].includes(firstArg)) {
137
+ parsed.bumpType = firstArg;
138
+ }
139
+
140
+ // Parse options
141
+ for (let i = 1; i < args.length; i++) {
142
+ const arg = args[i];
143
+
144
+ if (arg === '--suffix' && i + 1 < args.length) {
145
+ parsed.suffix = args[i + 1];
146
+ i++; // Skip next arg
147
+ } else if (arg === '--update-changelog') {
148
+ parsed.updateChangelog = true;
149
+ // Check if next arg is a branch name (not starting with --)
150
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
151
+ parsed.baseBranch = args[i + 1];
152
+ i++; // Skip next arg
153
+ }
154
+ } else if (arg === '--dry-run') {
155
+ parsed.dryRun = true;
156
+ } else if (arg === '--no-tag') {
157
+ parsed.noTag = true;
158
+ } else if (arg === '--no-push') {
159
+ parsed.noPush = true;
160
+ }
161
+ }
162
+
163
+ logger.debug('bump-version - parseArguments', 'Arguments parsed', parsed);
164
+
165
+ return parsed;
166
+ }
167
+
168
+ /**
169
+ * Displays dry run preview
170
+ * Why: Shows what would change without applying
171
+ *
172
+ * @param {Object} info - Version info
173
+ */
174
+ function showDryRunPreview(info) {
175
+ console.log('');
176
+ console.log(`${colors.yellow}═════════════════════════════════════════════════${colors.reset}`);
177
+ console.log(`${colors.yellow} DRY RUN - No changes will be made ${colors.reset}`);
178
+ console.log(`${colors.yellow}═════════════════════════════════════════════════${colors.reset}`);
179
+ console.log('');
180
+ console.log(`${colors.blue}Project Type:${colors.reset} ${info.projectType}`);
181
+ console.log(`${colors.blue}Current Version:${colors.reset} ${info.currentVersion}`);
182
+ console.log(`${colors.green}New Version:${colors.reset} ${info.newVersion}`);
183
+ console.log(`${colors.blue}Tag Name:${colors.reset} ${info.tagName}`);
184
+ console.log('');
185
+
186
+ if (info.files.length > 0) {
187
+ console.log(`${colors.blue}Files to update:${colors.reset}`);
188
+ info.files.forEach(file => console.log(` - ${file}`));
189
+ console.log('');
190
+ }
191
+
192
+ if (info.updateChangelog) {
193
+ console.log(`${colors.blue}CHANGELOG:${colors.reset} Will be generated and updated`);
194
+ console.log('');
195
+ }
196
+
197
+ console.log(`${colors.yellow}Run without --dry-run to apply these changes${colors.reset}`);
198
+ console.log('');
199
+ }
200
+
201
+ /**
202
+ * Bump version command
203
+ * @param {Array<string>} args - Command arguments
204
+ */
205
+ export async function runBumpVersion(args) {
206
+ logger.debug('bump-version', 'Starting bump-version command', { args });
207
+
208
+ // Parse arguments
209
+ const options = parseArguments(args);
210
+
211
+ if (!options.bumpType) {
212
+ error('Usage: claude-hooks bump-version <major|minor|patch> [options]');
213
+ console.log('');
214
+ console.log('Options:');
215
+ console.log(' --suffix <value> Add version suffix (e.g., SNAPSHOT, RC)');
216
+ console.log(' --update-changelog [branch] Generate CHANGELOG entry (default branch: main)');
217
+ console.log(' --dry-run Preview changes without applying');
218
+ console.log(' --no-tag Skip Git tag creation');
219
+ console.log(' --no-push Create tag but don\'t push to remote');
220
+ console.log('');
221
+ console.log('Examples:');
222
+ console.log(' claude-hooks bump-version minor --suffix SNAPSHOT');
223
+ console.log(' claude-hooks bump-version patch --update-changelog');
224
+ console.log(' claude-hooks bump-version major --dry-run');
225
+ return;
226
+ }
227
+
228
+ showInfo(`🔢 Bumping version (${options.bumpType})...`);
229
+ console.log('');
230
+
231
+ // Step 1: Validate prerequisites
232
+ logger.debug('bump-version', 'Step 1: Validating prerequisites');
233
+ const validation = validatePrerequisites();
234
+
235
+ if (!validation.valid) {
236
+ showError('Prerequisites validation failed:');
237
+ console.log('');
238
+ validation.errors.forEach(err => console.log(` ❌ ${err}`));
239
+ console.log('');
240
+ console.log('Please fix these issues:');
241
+ console.log(' - Commit or stash uncommitted changes: git stash');
242
+ console.log(' - Checkout a branch: git checkout main');
243
+ console.log(' - Configure remote: git remote add origin <url>');
244
+ console.log('');
245
+ process.exit(1);
246
+ }
247
+
248
+ showSuccess('Prerequisites validated');
249
+
250
+ // Step 2: Detect project type and current version
251
+ logger.debug('bump-version', 'Step 2: Detecting project type and version');
252
+ const projectType = detectProjectType();
253
+
254
+ if (projectType === 'none') {
255
+ showError('No version files found');
256
+ console.log('');
257
+ console.log('This command requires either:');
258
+ console.log(' - package.json (Node.js project)');
259
+ console.log(' - pom.xml (Maven project)');
260
+ console.log('');
261
+ process.exit(1);
262
+ }
263
+
264
+ const versions = getCurrentVersion(projectType);
265
+ const currentVersion = versions.resolved;
266
+
267
+ if (!currentVersion) {
268
+ showError('Could not determine current version');
269
+ process.exit(1);
270
+ }
271
+
272
+ // Warn if monorepo has version mismatch
273
+ if (versions.mismatch) {
274
+ showWarning('Version mismatch detected in monorepo:');
275
+ console.log(` package.json: ${versions.packageJson}`);
276
+ console.log(` pom.xml: ${versions.pomXml}`);
277
+ console.log('');
278
+ console.log('Both files will be updated to the new version.');
279
+ console.log('');
280
+ }
281
+
282
+ showInfo(`Project: ${projectType}`);
283
+ showInfo(`Current version: ${currentVersion}`);
284
+
285
+ // Step 3: Calculate new version
286
+ logger.debug('bump-version', 'Step 3: Calculating new version');
287
+ const newVersion = incrementVersion(currentVersion, options.bumpType, options.suffix);
288
+ const tagName = formatTagName(newVersion);
289
+
290
+ showSuccess(`New version: ${newVersion}`);
291
+ console.log('');
292
+
293
+ // Prepare files list
294
+ const filesToUpdate = [];
295
+ if (projectType === 'node' || projectType === 'both') {
296
+ filesToUpdate.push('package.json');
297
+ }
298
+ if (projectType === 'maven' || projectType === 'both') {
299
+ filesToUpdate.push('pom.xml');
300
+ }
301
+
302
+ // Dry run preview
303
+ if (options.dryRun) {
304
+ showDryRunPreview({
305
+ projectType,
306
+ currentVersion,
307
+ newVersion,
308
+ tagName,
309
+ files: filesToUpdate,
310
+ updateChangelog: options.updateChangelog
311
+ });
312
+ return;
313
+ }
314
+
315
+ // Confirm with user
316
+ const shouldContinue = await promptConfirmation(
317
+ `Update version to ${newVersion}?`,
318
+ true
319
+ );
320
+
321
+ if (!shouldContinue) {
322
+ showInfo('Version bump cancelled');
323
+ process.exit(0);
324
+ }
325
+
326
+ console.log('');
327
+
328
+ try {
329
+ // Step 4: Update version files
330
+ logger.debug('bump-version', 'Step 4: Updating version files');
331
+ showInfo('Updating version files...');
332
+ updateVersion(projectType, newVersion);
333
+
334
+ filesToUpdate.forEach(file => {
335
+ showSuccess(`✓ Updated ${file}`);
336
+ });
337
+
338
+ console.log('');
339
+
340
+ // Step 5: Generate CHANGELOG (if requested)
341
+ if (options.updateChangelog) {
342
+ logger.debug('bump-version', 'Step 5: Generating CHANGELOG');
343
+ showInfo('Generating CHANGELOG entry...');
344
+
345
+ const config = await getConfig();
346
+ const isReleaseVersion = !options.suffix; // Final version if no suffix
347
+
348
+ const changelogResult = await generateChangelogEntry({
349
+ version: newVersion,
350
+ isReleaseVersion,
351
+ baseBranch: options.baseBranch,
352
+ config
353
+ });
354
+
355
+ if (changelogResult.content) {
356
+ const updated = updateChangelogFile(changelogResult.content);
357
+ if (updated) {
358
+ showSuccess('✓ CHANGELOG.md updated');
359
+ } else {
360
+ showWarning('⚠ Failed to update CHANGELOG.md');
361
+ }
362
+ } else {
363
+ showWarning('⚠ No commits found for CHANGELOG');
364
+ }
365
+
366
+ console.log('');
367
+ }
368
+
369
+ // Step 6: Create Git tag (if not disabled)
370
+ if (!options.noTag) {
371
+ logger.debug('bump-version', 'Step 6: Creating Git tag');
372
+ showInfo('Creating Git tag...');
373
+
374
+ // Check if tag already exists
375
+ const exists = await tagExists(tagName, 'local');
376
+ if (exists) {
377
+ showWarning(`Tag ${tagName} already exists locally`);
378
+ const shouldOverwrite = await promptConfirmation(
379
+ 'Overwrite existing tag?',
380
+ false
381
+ );
382
+
383
+ if (!shouldOverwrite) {
384
+ showInfo('Tag creation skipped');
385
+ console.log('');
386
+ console.log('Version files updated successfully, but tag was not created.');
387
+ return;
388
+ }
389
+ }
390
+
391
+ const tagMessage = `Release version ${newVersion}`;
392
+ const tagResult = createTag(newVersion, tagMessage, { force: exists });
393
+
394
+ if (tagResult.success) {
395
+ showSuccess(`✓ Tag created: ${tagName}`);
396
+ console.log('');
397
+
398
+ // Step 7: Push tag (if not disabled)
399
+ if (!options.noPush) {
400
+ logger.debug('bump-version', 'Step 7: Pushing tag to remote');
401
+ showInfo('Pushing tag to remote...');
402
+
403
+ const pushResult = pushTags(null, tagName);
404
+
405
+ if (pushResult.success) {
406
+ showSuccess(`✓ Tag pushed to remote`);
407
+ } else {
408
+ showError(`Failed to push tag: ${pushResult.error}`);
409
+ console.log('');
410
+ console.log('You can push the tag manually:');
411
+ console.log(` git push origin ${tagName}`);
412
+ }
413
+ } else {
414
+ showInfo('Tag push skipped (--no-push)');
415
+ console.log('');
416
+ console.log('To push the tag later:');
417
+ console.log(` git push origin ${tagName}`);
418
+ }
419
+ } else {
420
+ showError(`Failed to create tag: ${tagResult.error}`);
421
+ }
422
+ } else {
423
+ showInfo('Tag creation skipped (--no-tag)');
424
+ }
425
+
426
+ // Success summary
427
+ console.log('');
428
+ console.log(`${colors.green}════════════════════════════════════════════════${colors.reset}`);
429
+ console.log(`${colors.green} Version Bump Complete ✅ ${colors.reset}`);
430
+ console.log(`${colors.green}════════════════════════════════════════════════${colors.reset}`);
431
+ console.log('');
432
+ console.log(`${colors.blue}Old version:${colors.reset} ${currentVersion}`);
433
+ console.log(`${colors.blue}New version:${colors.reset} ${newVersion}`);
434
+ console.log(`${colors.blue}Tag:${colors.reset} ${tagName}`);
435
+ console.log('');
436
+ console.log('Next steps:');
437
+ console.log(' 1. Review the changes: git diff');
438
+ console.log(' 2. Commit the version bump: git add . && git commit -m "chore: bump version to ' + newVersion + '"');
439
+ console.log(' 3. Create PR: claude-hooks create-pr main');
440
+ console.log('');
441
+
442
+ } catch (err) {
443
+ logger.error('bump-version', 'Version bump failed', err);
444
+ showError(`Version bump failed: ${err.message}`);
445
+ console.log('');
446
+ console.log('The operation was interrupted. You may need to:');
447
+ console.log(' - Revert changes: git checkout .');
448
+ console.log(' - Delete tag if created: git tag -d ' + tagName);
449
+ console.log('');
450
+ process.exit(1);
451
+ }
452
+ }
@@ -11,7 +11,8 @@ import { loadPrompt } from '../utils/prompt-builder.js';
11
11
  import { getConfig } from '../config.js';
12
12
  import { getOrPromptTaskId, formatWithTaskId } from '../utils/task-id.js';
13
13
  import { getReviewersForFiles } from '../utils/github-client.js';
14
- import { showPRPreview, promptMenu, showSuccess, showError, showInfo, showWarning } from '../utils/interactive-ui.js';
14
+ import { showPRPreview, promptMenu, showSuccess, showError, showInfo, showWarning, promptConfirmation } from '../utils/interactive-ui.js';
15
+ import { getBranchPushStatus, pushBranch } from '../utils/git-operations.js';
15
16
  import logger from '../utils/logger.js';
16
17
  import {
17
18
  error,
@@ -136,6 +137,205 @@ export async function runCreatePr(args) {
136
137
 
137
138
  logger.debug('create-pr', 'No existing PR found, continuing');
138
139
 
140
+ // Step 5.5: Check branch status and auto-push if needed (v2.11.0)
141
+ logger.debug('create-pr', 'Step 5.5: Checking branch push status');
142
+ const pushStatus = getBranchPushStatus(currentBranch);
143
+ const pushConfig = config.github?.pr;
144
+
145
+ logger.debug('create-pr', 'Branch push status', {
146
+ hasRemote: pushStatus.hasRemote,
147
+ hasUnpushedCommits: pushStatus.hasUnpushedCommits,
148
+ hasDiverged: pushStatus.hasDiverged,
149
+ unpushedCount: pushStatus.unpushedCommits.length
150
+ });
151
+
152
+ if (!pushStatus.hasRemote || pushStatus.hasUnpushedCommits) {
153
+ // Branch needs to be pushed
154
+
155
+ if (pushStatus.hasDiverged) {
156
+ // ABORT: Cannot push diverged branch
157
+ logger.error('create-pr', 'Branch has diverged from remote', {
158
+ branch: currentBranch,
159
+ upstream: pushStatus.upstreamBranch
160
+ });
161
+ showError('Branch has diverged from remote');
162
+ console.log('');
163
+ console.log('Please resolve conflicts manually:');
164
+ console.log(` git pull origin ${currentBranch}`);
165
+ console.log('');
166
+ process.exit(1);
167
+ }
168
+
169
+ if (!pushConfig?.autoPush) {
170
+ // Auto-push disabled - show error
171
+ logger.debug('create-pr', 'Auto-push disabled, aborting', { autoPush: pushConfig?.autoPush });
172
+ showError('Branch not pushed to remote');
173
+ console.log('');
174
+ console.log('Please push your branch first:');
175
+ console.log(` git push -u origin ${currentBranch}`);
176
+ console.log('');
177
+ process.exit(1);
178
+ }
179
+
180
+ // Show push preview
181
+ if (pushConfig?.showCommits && pushStatus.unpushedCommits.length > 0) {
182
+ console.log('');
183
+ showInfo('Commits to push:');
184
+ pushStatus.unpushedCommits.forEach(commit => {
185
+ console.log(` ${commit.sha.substring(0, 7)} ${commit.message}`);
186
+ });
187
+ }
188
+
189
+ // Confirm with user
190
+ if (pushConfig?.pushConfirm) {
191
+ console.log('');
192
+ const shouldPush = await promptConfirmation(
193
+ `Push branch '${currentBranch}' to remote?`,
194
+ true // default yes
195
+ );
196
+
197
+ if (!shouldPush) {
198
+ logger.debug('create-pr', 'User declined push, aborting');
199
+ showInfo('Push cancelled - cannot create PR without pushing');
200
+ process.exit(0);
201
+ }
202
+ }
203
+
204
+ // Execute push
205
+ console.log('');
206
+ showInfo('Pushing branch to remote...');
207
+ logger.debug('create-pr', 'Executing push', {
208
+ branch: currentBranch,
209
+ setUpstream: !pushStatus.hasRemote
210
+ });
211
+
212
+ const pushResult = pushBranch(currentBranch, {
213
+ setUpstream: !pushStatus.hasRemote
214
+ });
215
+
216
+ if (!pushResult.success) {
217
+ logger.error('create-pr', 'Push failed', {
218
+ branch: currentBranch,
219
+ error: pushResult.error
220
+ });
221
+ showError('Failed to push branch');
222
+ console.error('');
223
+ console.error(` ${pushResult.error}`);
224
+ console.error('');
225
+ process.exit(1);
226
+ }
227
+
228
+ logger.debug('create-pr', 'Push completed successfully');
229
+ showSuccess('Branch pushed successfully');
230
+ console.log('');
231
+ } else {
232
+ logger.debug('create-pr', 'Branch already up-to-date with remote, skipping push');
233
+ }
234
+
235
+ // Step 5.6: Version alignment validation (Issue #44)
236
+ logger.debug('create-pr', 'Step 5.6: Validating version alignment');
237
+ const { validateVersionAlignment } = await import('../utils/version-manager.js');
238
+ const versionCheck = await validateVersionAlignment();
239
+
240
+ if (!versionCheck.aligned) {
241
+ showWarning('Version misalignment detected:');
242
+ console.log('');
243
+
244
+ for (const issue of versionCheck.issues) {
245
+ console.log(` ${issue.source}: ${issue.version || 'not found'}`);
246
+ }
247
+
248
+ console.log('');
249
+ console.log('To fix version alignment:');
250
+ console.log(' claude-hooks bump-version patch --update-changelog');
251
+ console.log('');
252
+
253
+ const shouldContinue = await promptConfirmation(
254
+ 'Continue creating PR despite version misalignment?',
255
+ false // default no
256
+ );
257
+
258
+ if (!shouldContinue) {
259
+ showInfo('PR creation cancelled - please align versions first');
260
+ process.exit(0);
261
+ }
262
+ }
263
+
264
+ if (versionCheck.aligned && versionCheck.comparison === 'equal') {
265
+ showWarning('Local version equals remote version');
266
+ console.log(` Local: ${versionCheck.localVersion}`);
267
+ console.log(` Remote: ${versionCheck.remoteVersion}`);
268
+ console.log('');
269
+ console.log('To bump version:');
270
+ console.log(' claude-hooks bump-version patch # or minor/major');
271
+ console.log('');
272
+
273
+ const shouldContinue = await promptConfirmation(
274
+ 'Continue creating PR without version bump?',
275
+ true // default yes (might be non-release PR)
276
+ );
277
+
278
+ if (!shouldContinue) {
279
+ showInfo('PR creation cancelled');
280
+ process.exit(0);
281
+ }
282
+ }
283
+
284
+ if (versionCheck.aligned && versionCheck.comparison === 'less') {
285
+ showError('Local version is less than remote version!');
286
+ console.log(` Local: ${versionCheck.localVersion}`);
287
+ console.log(` Remote: ${versionCheck.remoteVersion}`);
288
+ console.log('');
289
+ console.log('This usually means you need to pull latest changes:');
290
+ console.log(` git pull origin ${baseBranch}`);
291
+ console.log('');
292
+
293
+ const shouldContinue = await promptConfirmation(
294
+ 'Continue creating PR despite version being behind?',
295
+ false // default no
296
+ );
297
+
298
+ if (!shouldContinue) {
299
+ showInfo('PR creation cancelled');
300
+ process.exit(0);
301
+ }
302
+ }
303
+
304
+ // Step 5.7: Check for unpushed tags (Issue #44)
305
+ logger.debug('create-pr', 'Step 5.7: Checking for unpushed tags');
306
+ const { compareLocalAndRemoteTags, pushTags: pushTagsUtil } = await import('../utils/git-tag-manager.js');
307
+ const tagComparison = await compareLocalAndRemoteTags();
308
+
309
+ if (tagComparison.localNewer.length > 0) {
310
+ showWarning(`Local tags not pushed: ${tagComparison.localNewer.join(', ')}`);
311
+ console.log('');
312
+
313
+ const shouldPushTags = await promptConfirmation(
314
+ 'Push tags to remote?',
315
+ true // default yes
316
+ );
317
+
318
+ if (shouldPushTags) {
319
+ showInfo('Pushing tags...');
320
+ const pushResult = pushTagsUtil(null, tagComparison.localNewer);
321
+
322
+ if (pushResult.success) {
323
+ showSuccess('Tags pushed successfully');
324
+ } else {
325
+ showError(`Failed to push some tags: ${pushResult.error}`);
326
+ if (pushResult.failed.length > 0) {
327
+ console.log('Failed tags:');
328
+ pushResult.failed.forEach(f => console.log(` - ${f.tag}: ${f.error}`));
329
+ }
330
+ }
331
+
332
+ console.log('');
333
+ } else {
334
+ showInfo('Tag push skipped');
335
+ console.log('');
336
+ }
337
+ }
338
+
139
339
  // Step 6: Update remote and check for differences
140
340
  logger.debug('create-pr', 'Step 6: Fetching latest changes from remote');
141
341
  execSync('git fetch', { stdio: 'ignore' });