claude-git-hooks 2.14.1 → 2.14.4

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,6 +5,32 @@ 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.14.4] - 2026-02-06
9
+
10
+ ### 🔧 Changed
11
+ - The bump-version command now uses the configured default branch (from github.pr.defaultBase) in the 'Next steps' output instead of hardcoded 'main'
12
+
13
+
14
+ ## [2.14.3] - 2026-02-06
15
+
16
+
17
+ ## [2.14.2] - 2026-02-06
18
+
19
+ ### ✨ Added
20
+ - New `--push` flag for `bump-version` command to explicitly push tags to remote when desired
21
+
22
+ ### 🔧 Changed
23
+ - Changed `bump-version` default behavior to keep tags local instead of automatically pushing to remote (#65)
24
+ - Tags are now pushed by `create-pr` command or manually with `--push` flag, providing better control over the release workflow
25
+ - Updated documentation in README.md, README-NPM.md, and CLAUDE.md to reflect new tag push behavior
26
+
27
+ ### ⚠️ Deprecated
28
+ - The `--no-push` flag for `bump-version` is no longer needed as tags now stay local by default
29
+
30
+ ### 🗑️ Removed
31
+ - Removed RFC-001 documentation file (pr-metadata-engine-refactor.md) as the refactor has been completed
32
+
33
+
8
34
  ## [2.14.1] - 2026-02-06
9
35
 
10
36
  ### 🔧 Changed
package/README.md CHANGED
@@ -111,6 +111,9 @@ claude-hooks bump-version major --update-changelog
111
111
  # Preview without applying
112
112
  claude-hooks bump-version patch --dry-run
113
113
 
114
+ # Push tag immediately (otherwise pushed by create-pr)
115
+ claude-hooks bump-version patch --push
116
+
114
117
  # Manual workflow (skip automatic commit)
115
118
  claude-hooks bump-version patch --no-commit
116
119
  ```
@@ -121,7 +124,7 @@ claude-hooks bump-version patch --no-commit
121
124
  - Generates CHANGELOG entry with Claude (analyzes commits)
122
125
  - Commits changes automatically with conventional commit format
123
126
  - Creates annotated Git tag with `v` prefix (e.g., `v2.7.0`)
124
- - Pushes tag to remote automatically
127
+ - Tags stay local by default (use `--push` to push immediately, or let `create-pr` handle it)
125
128
 
126
129
  **Version workflow:**
127
130
  ```
@@ -10,7 +10,7 @@
10
10
  * 5. [Optional] Generate and update CHANGELOG
11
11
  * 6. Stage and commit changes (skipped with --no-commit)
12
12
  * 7. Create annotated Git tag (skipped with --no-tag or --no-commit)
13
- * 8. Push tag to remote (skipped with --no-push)
13
+ * 8. Push tag to remote (only if --push flag provided, otherwise pushed by create-pr)
14
14
  */
15
15
 
16
16
  import { execSync } from 'child_process';
@@ -21,7 +21,6 @@ import {
21
21
  getCurrentVersion,
22
22
  incrementVersion,
23
23
  updateVersion,
24
- parseVersion,
25
24
  getDiscoveredPaths
26
25
  } from '../utils/version-manager.js';
27
26
  import {
@@ -48,9 +47,7 @@ import logger from '../utils/logger.js';
48
47
  import {
49
48
  colors,
50
49
  error,
51
- success,
52
50
  info,
53
- warning,
54
51
  checkGitRepo
55
52
  } from './helpers.js';
56
53
 
@@ -116,7 +113,7 @@ function validatePrerequisites() {
116
113
  * Why: Extracts bump type and options from CLI args
117
114
  *
118
115
  * @param {Array<string>} args - CLI arguments
119
- * @returns {Object} Parsed args: { bumpType, suffix, updateChangelog, baseBranch, dryRun, noTag, noPush }
116
+ * @returns {Object} Parsed args: { bumpType, suffix, updateChangelog, baseBranch, dryRun, noTag, push, noCommit }
120
117
  */
121
118
  function parseArguments(args) {
122
119
  logger.debug('bump-version - parseArguments', 'Parsing arguments', { args });
@@ -128,7 +125,7 @@ function parseArguments(args) {
128
125
  baseBranch: 'main',
129
126
  dryRun: false,
130
127
  noTag: false,
131
- noPush: false,
128
+ push: false, // Changed: default to NOT push (use --push to enable)
132
129
  noCommit: false
133
130
  };
134
131
 
@@ -160,8 +157,8 @@ function parseArguments(args) {
160
157
  parsed.dryRun = true;
161
158
  } else if (arg === '--no-tag') {
162
159
  parsed.noTag = true;
163
- } else if (arg === '--no-push') {
164
- parsed.noPush = true;
160
+ } else if (arg === '--push') {
161
+ parsed.push = true;
165
162
  } else if (arg === '--no-commit') {
166
163
  parsed.noCommit = true;
167
164
  }
@@ -223,7 +220,7 @@ export async function runBumpVersion(args) {
223
220
  console.log(' --update-changelog [branch] Generate CHANGELOG entry (default branch: main)');
224
221
  console.log(' --dry-run Preview changes without applying');
225
222
  console.log(' --no-tag Skip Git tag creation');
226
- console.log(' --no-push Create tag but don\'t push to remote');
223
+ console.log(' --push Push tag to remote (default: tags stay local)');
227
224
  console.log(' --no-commit Skip automatic commit (manual workflow)');
228
225
  console.log('');
229
226
  console.log('Examples:');
@@ -348,6 +345,11 @@ export async function runBumpVersion(args) {
348
345
  console.log('');
349
346
 
350
347
  try {
348
+ // Load config for default branch
349
+ const config = await getConfig();
350
+ const defaultBranch = config.github?.pr?.defaultBase || '{target-branch}';
351
+ logger.debug('bump-version', 'Default branch for PR', { defaultBranch });
352
+
351
353
  // Step 4: Update version files
352
354
  logger.debug('bump-version', 'Step 4: Updating version files');
353
355
  showInfo('Updating version files...');
@@ -364,7 +366,6 @@ export async function runBumpVersion(args) {
364
366
  logger.debug('bump-version', 'Step 5: Generating CHANGELOG');
365
367
  showInfo('Generating CHANGELOG entry...');
366
368
 
367
- const config = await getConfig();
368
369
  const isReleaseVersion = !options.suffix; // Final version if no suffix
369
370
 
370
371
  const changelogResult = await generateChangelogEntry({
@@ -494,9 +495,9 @@ export async function runBumpVersion(args) {
494
495
  showSuccess(`✓ Tag created: ${tagName}`);
495
496
  console.log('');
496
497
 
497
- // Step 7: Push tag (if not disabled)
498
- if (!options.noPush) {
499
- logger.debug('bump-version', 'Step 7: Pushing tag to remote');
498
+ // Step 8: Push tag (if --push flag provided)
499
+ if (options.push) {
500
+ logger.debug('bump-version', 'Step 8: Pushing tag to remote');
500
501
  showInfo('Pushing tag to remote...');
501
502
 
502
503
  const pushResult = pushTags(null, tagName);
@@ -510,10 +511,10 @@ export async function runBumpVersion(args) {
510
511
  console.log(` git push origin ${tagName}`);
511
512
  }
512
513
  } else {
513
- showInfo('Tag push skipped (--no-push)');
514
+ showInfo('Tag created locally (use --push to push immediately)');
514
515
  console.log('');
515
- console.log('To push the tag later:');
516
- console.log(` git push origin ${tagName}`);
516
+ console.log('Tag will be pushed automatically when you run:');
517
+ console.log(' claude-hooks create-pr [branch]');
517
518
  }
518
519
  } else {
519
520
  showError(`Failed to create tag: ${tagResult.error}`);
@@ -546,18 +547,18 @@ export async function runBumpVersion(args) {
546
547
  console.log(` 4. Create tag: git tag -a ${tagName} -m "Release version ${newVersion}"`);
547
548
  console.log(` 5. Push: git push origin $(git branch --show-current) ${tagName}`);
548
549
  console.log('');
549
- } else if (commitCreated && !options.noTag && !options.noPush) {
550
- console.log('All done! Changes committed and tag pushed to remote.');
550
+ } else if (commitCreated && !options.noTag && options.push) {
551
+ console.log('All done! Changes committed, tagged, and pushed to remote.');
551
552
  console.log('');
552
553
  console.log('Next steps:');
553
- console.log(' 1. Create PR: claude-hooks create-pr main');
554
+ console.log(` 1. Create PR: claude-hooks create-pr ${defaultBranch}`);
554
555
  console.log('');
555
556
  } else {
556
557
  console.log('Next steps:');
557
- if (commitCreated && options.noPush) {
558
- console.log(` 1. Push changes: git push origin $(git branch --show-current) ${tagName}`);
558
+ if (commitCreated && !options.push) {
559
+ console.log(` 1. Create PR: claude-hooks create-pr ${defaultBranch}`);
560
+ console.log(' (will automatically push tag during PR creation)');
559
561
  }
560
- console.log(' 2. Create PR: claude-hooks create-pr main');
561
562
  console.log('');
562
563
  }
563
564
 
@@ -300,39 +300,181 @@ export async function runCreatePr(args) {
300
300
  }
301
301
  }
302
302
 
303
- // Step 5.7: Check for unpushed tags (Issue #44)
304
- logger.debug('create-pr', 'Step 5.7: Checking for unpushed tags');
305
- const { compareLocalAndRemoteTags, pushTags: pushTagsUtil } = await import('../utils/git-tag-manager.js');
303
+ // Step 5.7: Smart tag pushing (Issue #44)
304
+ logger.debug('create-pr', 'Step 5.7: Checking and pushing unpushed tags');
305
+ const {
306
+ compareLocalAndRemoteTags,
307
+ pushTags: pushTagsUtil,
308
+ getLatestLocalTag,
309
+ getLatestRemoteTag,
310
+ parseTagVersion
311
+ } = await import('../utils/git-tag-manager.js');
312
+ const { compareVersions } = await import('../utils/version-manager.js');
313
+
306
314
  const tagComparison = await compareLocalAndRemoteTags();
307
315
 
308
316
  if (tagComparison.localNewer.length > 0) {
309
- showWarning(`Local tags not pushed: ${tagComparison.localNewer.join(', ')}`);
310
- console.log('');
317
+ // Get latest local and remote tags for comparison
318
+ const latestLocalTag = getLatestLocalTag();
319
+ const latestRemoteTag = await getLatestRemoteTag();
320
+
321
+ const localVersion = latestLocalTag ? parseTagVersion(latestLocalTag) : null;
322
+ const remoteVersion = latestRemoteTag ? parseTagVersion(latestRemoteTag) : null;
323
+
324
+ logger.debug('create-pr', 'Tag comparison details', {
325
+ localTag: latestLocalTag,
326
+ remoteTag: latestRemoteTag,
327
+ localVersion,
328
+ remoteVersion,
329
+ unpushedTags: tagComparison.localNewer
330
+ });
311
331
 
312
- const shouldPushTags = await promptConfirmation(
313
- 'Push tags to remote?',
314
- true // default yes
315
- );
332
+ let shouldPush = false;
333
+ let userChoice = null;
334
+
335
+ // Case 1: Local tag > Remote tag → Auto-push
336
+ if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) > 0) {
337
+ logger.debug('create-pr', 'Local version > remote version, auto-pushing', {
338
+ localVersion,
339
+ remoteVersion
340
+ });
341
+
342
+ showInfo(`Local tag ${latestLocalTag} is newer than remote ${latestRemoteTag}`);
343
+ showInfo('Auto-pushing tag to remote...');
344
+ shouldPush = true;
345
+
346
+ // Case 2: Local tag = Remote tag → Prompt with warning
347
+ } else if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) === 0) {
348
+ logger.debug('create-pr', 'Local version = remote version, prompting user', {
349
+ localVersion,
350
+ remoteVersion
351
+ });
352
+
353
+ showWarning('Local and remote tags have the same version:');
354
+ console.log(` Local: ${latestLocalTag} (${localVersion})`);
355
+ console.log(` Remote: ${latestRemoteTag} (${remoteVersion})`);
356
+ console.log('');
357
+ console.log('This might indicate the tag was already pushed.');
358
+ console.log('');
359
+
360
+ userChoice = await promptMenu(
361
+ 'What would you like to do?',
362
+ [
363
+ { key: 'c', label: 'Continue without pushing' },
364
+ { key: 'p', label: 'Force push tag' },
365
+ { key: 'a', label: 'Abort PR creation' }
366
+ ],
367
+ 'c'
368
+ );
369
+
370
+ if (userChoice === 'a') {
371
+ showInfo('PR creation cancelled');
372
+ process.exit(0);
373
+ } else if (userChoice === 'p') {
374
+ shouldPush = true;
375
+ }
376
+
377
+ // Case 3: Local tag < Remote tag → Prompt with error
378
+ } else if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) < 0) {
379
+ logger.debug('create-pr', 'Local version < remote version, prompting user', {
380
+ localVersion,
381
+ remoteVersion
382
+ });
383
+
384
+ showError('Local tag is older than remote tag:');
385
+ console.log(` Local: ${latestLocalTag} (${localVersion})`);
386
+ console.log(` Remote: ${latestRemoteTag} (${remoteVersion})`);
387
+ console.log('');
388
+ console.log('This usually means you need to pull latest tags:');
389
+ console.log(' git fetch --tags');
390
+ console.log('');
391
+
392
+ userChoice = await promptMenu(
393
+ 'What would you like to do?',
394
+ [
395
+ { key: 'a', label: 'Abort PR creation' },
396
+ { key: 'c', label: 'Continue anyway (not recommended)' }
397
+ ],
398
+ 'a'
399
+ );
400
+
401
+ if (userChoice === 'a') {
402
+ showInfo('PR creation cancelled');
403
+ process.exit(0);
404
+ }
405
+
406
+ // Case 4: No version comparison possible → Prompt with info
407
+ } else {
408
+ logger.debug('create-pr', 'Cannot compare versions, prompting user', {
409
+ localTag: latestLocalTag,
410
+ remoteTag: latestRemoteTag,
411
+ hasLocalVersion: !!localVersion,
412
+ hasRemoteVersion: !!remoteVersion
413
+ });
414
+
415
+ showWarning('Unable to compare tag versions:');
416
+ console.log(` Local tag: ${latestLocalTag || 'none'}`);
417
+ console.log(` Remote tag: ${latestRemoteTag || 'none'}`);
418
+ console.log('');
419
+ console.log('Unpushed tags:', tagComparison.localNewer.join(', '));
420
+ console.log('');
421
+
422
+ userChoice = await promptMenu(
423
+ 'What would you like to do?',
424
+ [
425
+ { key: 'p', label: 'Push tags to remote' },
426
+ { key: 'c', label: 'Continue without pushing' },
427
+ { key: 'a', label: 'Abort PR creation' }
428
+ ],
429
+ 'p'
430
+ );
431
+
432
+ if (userChoice === 'a') {
433
+ showInfo('PR creation cancelled');
434
+ process.exit(0);
435
+ } else if (userChoice === 'p') {
436
+ shouldPush = true;
437
+ }
438
+ }
439
+
440
+ // Execute push if decided
441
+ if (shouldPush) {
442
+ console.log('');
443
+ showInfo('Pushing tags to remote...');
316
444
 
317
- if (shouldPushTags) {
318
- showInfo('Pushing tags...');
319
445
  const pushResult = pushTagsUtil(null, tagComparison.localNewer);
320
446
 
321
447
  if (pushResult.success) {
322
- showSuccess('Tags pushed successfully');
448
+ showSuccess('Tags pushed successfully');
449
+ logger.debug('create-pr', 'Tags pushed', {
450
+ pushed: tagComparison.localNewer
451
+ });
323
452
  } else {
324
453
  showError(`Failed to push some tags: ${pushResult.error}`);
325
454
  if (pushResult.failed.length > 0) {
326
455
  console.log('Failed tags:');
327
456
  pushResult.failed.forEach(f => console.log(` - ${f.tag}: ${f.error}`));
328
457
  }
458
+
459
+ const shouldContinue = await promptConfirmation(
460
+ 'Continue creating PR despite push failure?',
461
+ false
462
+ );
463
+
464
+ if (!shouldContinue) {
465
+ showInfo('PR creation cancelled');
466
+ process.exit(0);
467
+ }
329
468
  }
330
469
 
331
470
  console.log('');
332
- } else {
333
- showInfo('Tag push skipped');
471
+ } else if (userChoice !== 'c') {
472
+ // Auto-push failed or user chose to continue
473
+ logger.debug('create-pr', 'Tag push skipped', { reason: 'user choice or condition' });
334
474
  console.log('');
335
475
  }
476
+ } else {
477
+ logger.debug('create-pr', 'No unpushed tags found, continuing');
336
478
  }
337
479
 
338
480
  // Step 6: Generate PR metadata using engine
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "claude-git-hooks",
3
- "version": "2.14.1",
4
- "description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
5
- "type": "module",
6
- "bin": {
7
- "claude-hooks": "./bin/claude-hooks"
8
- },
9
- "scripts": {
10
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
11
- "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
12
- "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
13
- "lint": "eslint lib/ bin/",
14
- "lint:fix": "eslint lib/ bin/ --fix",
15
- "format": "prettier --write \"lib/**/*.js\" \"bin/**\""
16
- },
17
- "keywords": [
18
- "git",
19
- "hooks",
20
- "claude",
21
- "ai",
22
- "code-review",
23
- "commit-messages",
24
- "pre-commit",
25
- "automation"
26
- ],
27
- "author": "Pablo Rovito",
28
- "license": "MIT",
29
- "repository": {
30
- "type": "git",
31
- "url": "https://github.com/pablorovito/claude-git-hooks.git"
32
- },
33
- "engines": {
34
- "node": ">=16.9.0"
35
- },
36
- "engineStrict": false,
37
- "os": [
38
- "darwin",
39
- "linux",
40
- "win32"
41
- ],
42
- "preferGlobal": true,
43
- "files": [
44
- "bin/",
45
- "lib/",
46
- "templates/",
47
- "README.md",
48
- "CHANGELOG.md",
49
- "LICENSE"
50
- ],
51
- "dependencies": {
52
- "@octokit/rest": "^21.0.0"
53
- },
54
- "devDependencies": {
55
- "@types/jest": "^29.5.0",
56
- "eslint": "^8.57.0",
57
- "jest": "^29.7.0",
58
- "prettier": "^3.2.0"
59
- }
60
- }
1
+ {
2
+ "name": "claude-git-hooks",
3
+ "version": "2.14.4",
4
+ "description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-hooks": "./bin/claude-hooks"
8
+ },
9
+ "scripts": {
10
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
11
+ "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
12
+ "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
13
+ "lint": "eslint lib/ bin/",
14
+ "lint:fix": "eslint lib/ bin/ --fix",
15
+ "format": "prettier --write \"lib/**/*.js\" \"bin/**\""
16
+ },
17
+ "keywords": [
18
+ "git",
19
+ "hooks",
20
+ "claude",
21
+ "ai",
22
+ "code-review",
23
+ "commit-messages",
24
+ "pre-commit",
25
+ "automation"
26
+ ],
27
+ "author": "Pablo Rovito",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/pablorovito/claude-git-hooks.git"
32
+ },
33
+ "engines": {
34
+ "node": ">=16.9.0"
35
+ },
36
+ "engineStrict": false,
37
+ "os": [
38
+ "darwin",
39
+ "linux",
40
+ "win32"
41
+ ],
42
+ "preferGlobal": true,
43
+ "files": [
44
+ "bin/",
45
+ "lib/",
46
+ "templates/",
47
+ "README.md",
48
+ "CHANGELOG.md",
49
+ "LICENSE"
50
+ ],
51
+ "dependencies": {
52
+ "@octokit/rest": "^21.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/jest": "^29.5.0",
56
+ "eslint": "^8.57.0",
57
+ "jest": "^29.7.0",
58
+ "prettier": "^3.2.0"
59
+ }
60
+ }