claude-git-hooks 2.11.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.
package/CHANGELOG.md CHANGED
@@ -5,28 +5,80 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
5
5
  El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.12.0] - 2026-02-03
9
+
10
+ ### ✨ Added
11
+
12
+ - **Automatic version management with `bump-version` command**
13
+ - Detects project type (Node.js, Maven, or monorepo with both)
14
+ - Increments version following semantic versioning (major/minor/patch)
15
+ - Supports version suffixes (SNAPSHOT, RC, custom)
16
+ - Updates package.json and/or pom.xml automatically
17
+ - Generates CHANGELOG entries using Claude AI analysis of commits
18
+ - Creates annotated Git tags with `v` prefix (v2.7.0, v2.7.0-SNAPSHOT)
19
+ - Automatically pushes tags to remote
20
+ - CLI options: `--suffix`, `--update-changelog`, `--dry-run`, `--no-tag`, `--no-push`
21
+
22
+ - **CHANGELOG generation with Claude integration**
23
+ - Standalone `generate-changelog` command for independent CHANGELOG generation without version bumping
24
+ - Support for manual version specification in CHANGELOG generation via command argument
25
+ - Base branch selection for CHANGELOG comparison via `--base-branch` flag
26
+ - Release marking option via `--release` flag to control [Unreleased] header behavior
27
+ - Analyzes commits since last final version tag
28
+ - Categorizes changes by Conventional Commits types
29
+ - Generates human-readable entries in Keep a Changelog format
30
+ - Sections: Added, Changed, Fixed, Security, Deprecated, Removed
31
+ - Places entries in [Unreleased] for suffixed versions, [X.Y.Z] for final versions
32
+
33
+ - **Version alignment validation in `create-pr`** (#44)
34
+ - Checks consistency across package.json, pom.xml, CHANGELOG.md, and git tags
35
+ - Compares local version vs remote version
36
+ - Shows helpful prompts with fix commands if misalignment detected
37
+ - Allows user to continue or abort PR creation based on validation results
38
+
39
+ - **Unpushed tags detection in `create-pr`** (#44)
40
+ - Detects local tags not pushed to remote
41
+ - Prompts user to push tags before creating PR
42
+ - Prevents version inconsistencies between local and remote
43
+
44
+ - **New utility modules**:
45
+ - `lib/utils/version-manager.js` - Version detection, parsing, incrementing, validation
46
+ - `lib/utils/git-tag-manager.js` - Git tag operations (create, list, compare, push)
47
+ - `lib/utils/changelog-generator.js` - CHANGELOG generation with Claude
48
+ - `templates/GENERATE_CHANGELOG.md` - Claude prompt for changelog analysis
49
+
50
+ ### 🔧 Changed
51
+
52
+ - Improved Windows compatibility by quoting git log format string in changelog-generator.js
53
+
54
+ ### 📚 Documentation
55
+
56
+ - Updated help.js with bump-version command documentation and examples
57
+ - Added version workflow examples (development → RC → release)
58
+ - Updated CLAUDE.md with new utility modules and exports
59
+
8
60
  ## [2.11.0] - 2026-02-03
9
61
 
10
62
  ### ✨ Added
11
63
 
12
64
  - **Auto-push functionality in `create-pr` command** - Automatically detects and pushes unpublished branches before creating PR (#59, #34)
13
- - Handles 3 scenarios: unpublished branch, unpushed commits, up-to-date branch
14
- - Interactive confirmation prompt before pushing (configurable)
15
- - Commit preview showing what will be pushed
16
- - Diverged branch detection with helpful error message and resolution steps
17
- - Never uses `git push --force` for security
65
+ - Handles 3 scenarios: unpublished branch, unpushed commits, up-to-date branch
66
+ - Interactive confirmation prompt before pushing (configurable)
67
+ - Commit preview showing what will be pushed
68
+ - Diverged branch detection with helpful error message and resolution steps
69
+ - Never uses `git push --force` for security
18
70
 
19
71
  - **New configuration options** under `github.pr`:
20
- - `autoPush: true` - Enable/disable auto-push (enabled by default)
21
- - `pushConfirm: true` - Prompt for confirmation before push
22
- - `showCommits: true` - Show commit preview before push
23
- - `verifyRemote: true` - Verify remote exists before push
72
+ - `autoPush: true` - Enable/disable auto-push (enabled by default)
73
+ - `pushConfirm: true` - Prompt for confirmation before push
74
+ - `showCommits: true` - Show commit preview before push
75
+ - `verifyRemote: true` - Verify remote exists before push
24
76
 
25
77
  - **New git-operations functions**:
26
- - `getRemoteName()` - Get configured remote name (usually 'origin')
27
- - `verifyRemoteExists()` - Check if remote is configured
28
- - `getBranchPushStatus()` - Detect branch publish status and unpushed commits
29
- - `pushBranch()` - Execute git push with upstream tracking
78
+ - `getRemoteName()` - Get configured remote name (usually 'origin')
79
+ - `verifyRemoteExists()` - Check if remote is configured
80
+ - `getBranchPushStatus()` - Detect branch publish status and unpushed commits
81
+ - `pushBranch()` - Execute git push with upstream tracking
30
82
 
31
83
  ### 🔧 Fixed
32
84
 
@@ -76,13 +128,13 @@ This release implements a **modular, decoupled, reusable** architecture for bett
76
128
 
77
129
  ### 🎯 Benefits
78
130
 
79
- | Aspect | Before | After |
80
- |--------|--------|-------|
81
- | `bin/claude-hooks` size | 2,277 lines | 111 lines |
82
- | Command modules | 1 (`setup-github.js`) | 13 files |
83
- | Testability | Hard (monolithic) | Easy (modular) |
84
- | Finding code | Search entire file | Match filename to command |
85
- | Adding commands | Edit large file | Create new module |
131
+ | Aspect | Before | After |
132
+ | ----------------------- | --------------------- | ------------------------- |
133
+ | `bin/claude-hooks` size | 2,277 lines | 111 lines |
134
+ | Command modules | 1 (`setup-github.js`) | 13 files |
135
+ | Testability | Hard (monolithic) | Easy (modular) |
136
+ | Finding code | Search entire file | Match filename to command |
137
+ | Adding commands | Edit large file | Create new module |
86
138
 
87
139
  ### 📚 Documentation
88
140
 
@@ -94,6 +146,7 @@ This release implements a **modular, decoupled, reusable** architecture for bett
94
146
  ### 🔧 Technical Details
95
147
 
96
148
  **New module structure:**
149
+
97
150
  ```
98
151
  lib/commands/
99
152
  ├── helpers.js (389 lines) - Shared utilities
@@ -111,6 +164,7 @@ lib/commands/
111
164
  ```
112
165
 
113
166
  **Key patterns implemented:**
167
+
114
168
  - **Command Pattern**: Each CLI command is a self-contained module
115
169
  - **Thin Controller**: `bin/claude-hooks` only routes, doesn't implement
116
170
  - **Separation of Concerns**: UI helpers in `helpers.js`, business logic in specific modules
package/README.md CHANGED
@@ -67,6 +67,65 @@ claude-hooks analyze-diff develop
67
67
  # Generates PR metadata without creating
68
68
  ```
69
69
 
70
+ ### Bump Version
71
+
72
+ Automatic version management with CHANGELOG generation and Git tagging:
73
+
74
+ ```bash
75
+ # Bump patch version (1.0.0 → 1.0.1)
76
+ claude-hooks bump-version patch
77
+
78
+ # Bump with suffix
79
+ claude-hooks bump-version minor --suffix SNAPSHOT # → 1.1.0-SNAPSHOT
80
+
81
+ # Bump and generate CHANGELOG
82
+ claude-hooks bump-version major --update-changelog
83
+
84
+ # Preview without applying
85
+ claude-hooks bump-version patch --dry-run
86
+ ```
87
+
88
+ **What it does:**
89
+ - Detects project type (Node.js, Maven, or monorepo with both)
90
+ - Updates `package.json` and/or `pom.xml`
91
+ - Generates CHANGELOG entry with Claude (analyzes commits)
92
+ - Creates annotated Git tag with `v` prefix (e.g., `v2.7.0`)
93
+ - Pushes tag to remote automatically
94
+
95
+ **Version workflow:**
96
+ ```
97
+ 2.7.0 → 2.8.0-SNAPSHOT # Start development
98
+ 2.8.0-SNAPSHOT → 2.8.0-RC # Release candidate
99
+ 2.8.0-RC → 2.8.0 # Final release
100
+ ```
101
+
102
+ **Integration with create-pr:**
103
+ - Validates version alignment (package.json, pom.xml, CHANGELOG, tags)
104
+ - Detects and prompts to push unpushed tags
105
+ - Warns if local version ≤ remote version
106
+
107
+ ### Generate CHANGELOG
108
+
109
+ Standalone CHANGELOG generation (without version bump):
110
+
111
+ ```bash
112
+ # Auto-detect version from package.json/pom.xml
113
+ claude-hooks generate-changelog
114
+
115
+ # Specific version with release marking
116
+ claude-hooks generate-changelog 2.7.0 --release
117
+
118
+ # Compare against different base branch
119
+ claude-hooks generate-changelog --base-branch develop
120
+ ```
121
+
122
+ **What it does:**
123
+ - Analyzes commits since last tag using Claude
124
+ - Categorizes by Conventional Commits types (feat, fix, refactor, etc.)
125
+ - Generates Keep a Changelog format entries
126
+ - Updates CHANGELOG.md automatically
127
+ - Useful when `bump-version --update-changelog` fails
128
+
70
129
  ### Disable/Enable Hooks
71
130
 
72
131
  ```bash
package/bin/claude-hooks CHANGED
@@ -22,6 +22,8 @@ import { runMigrateConfig } from '../lib/commands/migrate-config.js';
22
22
  import { runSetDebug } from '../lib/commands/debug.js';
23
23
  import { runShowTelemetry, runClearTelemetry } from '../lib/commands/telemetry-cmd.js';
24
24
  import { runShowHelp, runShowVersion } from '../lib/commands/help.js';
25
+ import { runBumpVersion } from '../lib/commands/bump-version.js';
26
+ import { runGenerateChangelog } from '../lib/commands/generate-changelog.js';
25
27
 
26
28
  /**
27
29
  * Main CLI router
@@ -75,6 +77,12 @@ async function main() {
75
77
  case 'migrate-config':
76
78
  await runMigrateConfig();
77
79
  break;
80
+ case 'bump-version':
81
+ await runBumpVersion(args.slice(1));
82
+ break;
83
+ case 'generate-changelog':
84
+ await runGenerateChangelog(args.slice(1));
85
+ break;
78
86
  case 'telemetry':
79
87
  // Handle subcommands: telemetry show, telemetry clear
80
88
  if (args[1] === 'show' || args[1] === undefined) {
@@ -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
+ }