claude-git-hooks 2.13.0 → 2.14.1
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 +38 -0
- package/README.md +16 -9
- package/lib/commands/analyze-diff.js +32 -153
- package/lib/commands/bump-version.js +154 -47
- package/lib/commands/create-pr.js +15 -80
- package/lib/commands/helpers.js +7 -0
- package/lib/utils/claude-client.js +6 -1
- package/lib/utils/claude-diagnostics.js +2 -1
- package/lib/utils/git-operations.js +408 -1
- package/lib/utils/pr-metadata-engine.js +474 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ 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.1] - 2026-02-06
|
|
9
|
+
|
|
10
|
+
### 🔧 Changed
|
|
11
|
+
- Improved `bump-version` command workflow - now automatically stages and commits version changes with conventional commit format before tag creation
|
|
12
|
+
- Enhanced `bump-version` command flexibility - added `--no-commit` flag for manual workflows where users prefer to commit changes themselves
|
|
13
|
+
- Improved error handling in `bump-version` - provides clearer guidance when staging or committing fails, with manual fallback instructions
|
|
14
|
+
- Enhanced next-steps guidance in `bump-version` output - adapts instructions based on flags used (--no-commit, --no-push, --no-tag)
|
|
15
|
+
|
|
16
|
+
### 🐛 Fixed
|
|
17
|
+
- Fixed `bump-version` workflow inconsistency - prevented tag creation when using `--no-commit` flag, as tags should only be created after changes are committed
|
|
18
|
+
- Fixed potential git history issues - version bump commits now use `--no-verify` flag to bypass pre-commit hooks and prevent circular execution
|
|
19
|
+
|
|
20
|
+
### 🗑️ Removed
|
|
21
|
+
- Removed redundant fallback warnings from `analyze-diff` and `create-pr` commands - warnings were unnecessary as base branch resolution now throws clear errors with suggestions instead
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [2.14.0] - 2026-02-06
|
|
25
|
+
|
|
26
|
+
### ✨ Added
|
|
27
|
+
- PR metadata engine (`pr-metadata-engine.js`) - centralized module for generating PR titles, descriptions, and test plans from branch diffs with timeout resilience (#63)
|
|
28
|
+
- Tiered diff reduction system - automatically truncates large diffs using context reduction, proportional budgets, and stat-only summaries to prevent timeouts
|
|
29
|
+
- Extended git operations - added `fetchRemote()`, `branchExists()`, `resolveBaseBranch()`, `getChangedFilesBetweenRefs()`, `getDiffBetweenRefs()`, and `getCommitsBetweenRefs()` to `git-operations.js`
|
|
30
|
+
- RFC-001 documentation - comprehensive design specification for PR metadata engine refactor with timeout resilience strategy
|
|
31
|
+
|
|
32
|
+
### 🔧 Changed
|
|
33
|
+
- Refactored `analyze-diff` command to use shared PR metadata engine - reduced from 262 to ~80 lines by eliminating duplicate git logic
|
|
34
|
+
- Refactored `create-pr` command to use shared PR metadata engine - eliminated duplicate diff extraction logic (lines 340-420)
|
|
35
|
+
- Improved timeout error handling - timeout errors now include `errorInfo` and are classified as recoverable for automatic retry with exponential backoff
|
|
36
|
+
- Updated CLAUDE.md with PR metadata engine architecture, new git operations exports, and tiered diff reduction strategy
|
|
37
|
+
|
|
38
|
+
### 🐛 Fixed
|
|
39
|
+
- Fixed timeout failures in `analyze-diff` for large diffs - now uses tiered reduction instead of monolithic diff processing
|
|
40
|
+
- Fixed inconsistent behavior between `analyze-diff` and `create-pr` commands - both now share identical analysis logic via unified engine
|
|
41
|
+
|
|
42
|
+
### 🗑️ Removed
|
|
43
|
+
- Removed unreliable `SUBAGENT_INSTRUCTION` text hint - replaced with deterministic tiered diff reduction for parallel processing
|
|
44
|
+
|
|
45
|
+
|
|
8
46
|
## [2.13.0] - 2026-02-05
|
|
9
47
|
|
|
10
48
|
### ✨ Added
|
package/README.md
CHANGED
|
@@ -110,12 +110,16 @@ claude-hooks bump-version major --update-changelog
|
|
|
110
110
|
|
|
111
111
|
# Preview without applying
|
|
112
112
|
claude-hooks bump-version patch --dry-run
|
|
113
|
+
|
|
114
|
+
# Manual workflow (skip automatic commit)
|
|
115
|
+
claude-hooks bump-version patch --no-commit
|
|
113
116
|
```
|
|
114
117
|
|
|
115
118
|
**What it does:**
|
|
116
119
|
- Detects project type (Node.js, Maven, or monorepo with both)
|
|
117
120
|
- Updates `package.json` and/or `pom.xml`
|
|
118
121
|
- Generates CHANGELOG entry with Claude (analyzes commits)
|
|
122
|
+
- Commits changes automatically with conventional commit format
|
|
119
123
|
- Creates annotated Git tag with `v` prefix (e.g., `v2.7.0`)
|
|
120
124
|
- Pushes tag to remote automatically
|
|
121
125
|
|
|
@@ -253,7 +257,8 @@ claude-hooks --help # Full command reference
|
|
|
253
257
|
| `analysis-engine.js` | **Shared analysis logic** - file data, orchestration, results (v2.13.0) | `buildFilesData()`, `runAnalysis()`, `consolidateResults()`, `displayResults()` |
|
|
254
258
|
| `claude-client.js` | **Claude CLI wrapper** - spawn, retry, parallel execution | `analyzeCode()`, `analyzeCodeParallel()`, `executeClaudeWithRetry()` |
|
|
255
259
|
| `prompt-builder.js` | **Prompt construction** - load templates, replace variables | `buildAnalysisPrompt()`, `loadPrompt()` |
|
|
256
|
-
| `git-operations.js` | **Git abstractions** - staged files, diff,
|
|
260
|
+
| `git-operations.js` | **Git abstractions** - staged files, diff, branch comparison | `getStagedFiles()`, `getDiff()`, `getRepoRoot()`, `resolveBaseBranch()`, `getDiffBetweenRefs()` |
|
|
261
|
+
| `pr-metadata-engine.js` | **PR metadata generation** - branch context, diff reduction (v2.14.0) | `getBranchContext()`, `buildDiffPayload()`, `generatePRMetadata()`, `analyzeBranchForPR()` |
|
|
257
262
|
| `github-api.js` | **Octokit integration** - PR creation, token validation | `createPullRequest()`, `validateToken()`, `saveGitHubToken()` |
|
|
258
263
|
| `github-client.js` | **GitHub helpers** - CODEOWNERS parsing, reviewers | `getReviewersForFiles()`, `parseGitHubRepo()` |
|
|
259
264
|
| `preset-loader.js` | **Preset system** - load tech-stack configurations | `loadPreset()`, `listPresets()` |
|
|
@@ -290,16 +295,18 @@ git commit -m "auto" → templates/prepare-commit-msg (bash wrapper)
|
|
|
290
295
|
→ write to COMMIT_EDITMSG
|
|
291
296
|
```
|
|
292
297
|
|
|
293
|
-
#### PR
|
|
298
|
+
#### PR Metadata Generation (analyze-diff / create-pr)
|
|
294
299
|
|
|
295
300
|
```
|
|
296
|
-
claude-hooks create-pr → bin/claude-hooks (router)
|
|
297
|
-
→ lib/commands/create-pr.js
|
|
298
|
-
→
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
claude-hooks analyze-diff|create-pr → bin/claude-hooks (router)
|
|
302
|
+
→ lib/commands/analyze-diff.js or create-pr.js (thin wrapper)
|
|
303
|
+
→ analyzeBranchForPR() (pr-metadata-engine.js)
|
|
304
|
+
→ resolveBaseBranch() (git-operations.js)
|
|
305
|
+
→ getDiffBetweenRefs() + getCommitsBetweenRefs()
|
|
306
|
+
→ buildDiffPayload() with tiered reduction (context → proportional → stat-only)
|
|
307
|
+
→ executeClaudeWithRetry() → PRMetadata
|
|
308
|
+
→ analyze-diff: formats to console + saves JSON
|
|
309
|
+
→ create-pr: additionally creates PR via Octokit API
|
|
303
310
|
```
|
|
304
311
|
|
|
305
312
|
### Config Priority
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
* Purpose: Analyze differences between branches and generate PR info
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
6
|
import fs from 'fs';
|
|
8
|
-
import {
|
|
9
|
-
import { loadPrompt } from '../utils/prompt-builder.js';
|
|
7
|
+
import { analyzeBranchForPR } from '../utils/pr-metadata-engine.js';
|
|
10
8
|
import { getConfig } from '../config.js';
|
|
9
|
+
import logger from '../utils/logger.js';
|
|
11
10
|
import {
|
|
12
11
|
colors,
|
|
13
12
|
error,
|
|
14
|
-
success,
|
|
15
13
|
info,
|
|
16
14
|
warning,
|
|
17
15
|
checkGitRepo
|
|
@@ -27,156 +25,38 @@ export async function runAnalyzeDiff(args) {
|
|
|
27
25
|
return;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
//
|
|
28
|
+
// Enable debug mode from config
|
|
31
29
|
const config = await getConfig();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!currentBranch) {
|
|
36
|
-
error('You are not in a valid branch.');
|
|
37
|
-
return;
|
|
30
|
+
if (config.system?.debug) {
|
|
31
|
+
logger.setDebugMode(true);
|
|
38
32
|
}
|
|
39
33
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let baseBranch, compareWith, contextDescription;
|
|
44
|
-
|
|
45
|
-
if (args[0]) {
|
|
46
|
-
// Case with argument: compare current branch vs origin/specified-branch
|
|
47
|
-
const targetBranch = args[0];
|
|
48
|
-
baseBranch = `origin/${targetBranch}`;
|
|
49
|
-
compareWith = `${baseBranch}...HEAD`;
|
|
50
|
-
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
51
|
-
|
|
52
|
-
// Check that the origin branch exists
|
|
53
|
-
try {
|
|
54
|
-
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
55
|
-
} catch (e) {
|
|
56
|
-
error(`Branch ${baseBranch} does not exist.`);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
// Case without argument: compare current branch vs origin/current-branch
|
|
61
|
-
baseBranch = `origin/${currentBranch}`;
|
|
62
|
-
compareWith = `${baseBranch}...HEAD`;
|
|
63
|
-
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
64
|
-
|
|
65
|
-
// Check that the origin branch exists
|
|
66
|
-
try {
|
|
67
|
-
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
68
|
-
} catch (e) {
|
|
69
|
-
// Try fallback to origin/develop
|
|
70
|
-
baseBranch = 'origin/develop';
|
|
71
|
-
compareWith = `${baseBranch}...HEAD`;
|
|
72
|
-
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
76
|
-
warning(`Branch origin/${currentBranch} does not exist. Using ${baseBranch} as fallback.`);
|
|
77
|
-
} catch (e2) {
|
|
78
|
-
// Try fallback to origin/main
|
|
79
|
-
baseBranch = 'origin/main';
|
|
80
|
-
compareWith = `${baseBranch}...HEAD`;
|
|
81
|
-
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
34
|
+
// Parse target branch from arguments
|
|
35
|
+
const targetBranch = args[0];
|
|
82
36
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
warning(`No origin/develop branch. Using ${baseBranch} as fallback.`);
|
|
86
|
-
} catch (e3) {
|
|
87
|
-
error('Could not find a valid comparison branch (tried origin/current, origin/develop, origin/main).');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
info(`Analyzing: ${contextDescription}...`);
|
|
37
|
+
info(targetBranch ? `Analyzing differences with ${targetBranch}...` : 'Analyzing differences...');
|
|
38
|
+
const startTime = Date.now();
|
|
95
39
|
|
|
96
|
-
// Get modified files
|
|
97
|
-
let diffFiles;
|
|
98
40
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
|
|
104
|
-
const unstagedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
|
|
41
|
+
// Call PR metadata engine
|
|
42
|
+
const { success: engineSuccess, result, error: engineError } = await analyzeBranchForPR(targetBranch, {
|
|
43
|
+
hook: 'analyze-diff'
|
|
44
|
+
});
|
|
105
45
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log('Staged changes:', stagedFiles || 'none');
|
|
109
|
-
console.log('Unstaged changes:', unstagedFiles || 'none');
|
|
110
|
-
} else {
|
|
111
|
-
success('✅ No differences. Your branch is synchronized.');
|
|
112
|
-
}
|
|
46
|
+
if (!engineSuccess) {
|
|
47
|
+
error(engineError || 'Failed to analyze branch');
|
|
113
48
|
return;
|
|
114
49
|
}
|
|
115
|
-
} catch (e) {
|
|
116
|
-
error('Error getting differences: ' + e.message);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Get the complete diff
|
|
121
|
-
let fullDiff, commits;
|
|
122
|
-
try {
|
|
123
|
-
fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
|
|
124
|
-
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
125
|
-
} catch (e) {
|
|
126
|
-
error('Error getting diff or commits: ' + e.message);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Check if subagents should be used
|
|
131
|
-
const useSubagents = config.subagents.enabled;
|
|
132
|
-
const subagentModel = config.subagents.model;
|
|
133
|
-
let subagentBatchSize = config.subagents.batchSize;
|
|
134
|
-
// Validate batch size (must be >= 1)
|
|
135
|
-
if (subagentBatchSize < 1) {
|
|
136
|
-
subagentBatchSize = 1;
|
|
137
|
-
}
|
|
138
|
-
const subagentInstruction = useSubagents
|
|
139
|
-
? `\n\nIMPORTANT PARALLEL PROCESSING: If analyzing 3+ files, process them in batches of ${subagentBatchSize}. For EACH batch, create that many subagents in parallel using Task tool (send single message with multiple Task calls). Each subagent analyzes one file and provides insights. After ALL batches complete, consolidate into SINGLE JSON with ONE cohesive PR title/description. Model: ${subagentModel}. Example: 4 files with BATCH_SIZE=1 → 4 sequential batches of 1 subagent each. Example: 4 files with BATCH_SIZE=3 → batch 1 has 3 parallel subagents (files 1-3), batch 2 has 1 subagent (file 4).\n`
|
|
140
|
-
: '';
|
|
141
|
-
|
|
142
|
-
// Truncate full diff if too large
|
|
143
|
-
const truncatedDiff = fullDiff.length > 50000
|
|
144
|
-
? fullDiff.substring(0, 50000) + '\n... (truncated diff)'
|
|
145
|
-
: fullDiff;
|
|
146
50
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
COMMITS: commits,
|
|
152
|
-
DIFF_FILES: diffFiles,
|
|
153
|
-
FULL_DIFF: truncatedDiff
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
info('Sending to Claude for analysis...');
|
|
157
|
-
const startTime = Date.now();
|
|
158
|
-
|
|
159
|
-
// Prepare telemetry context
|
|
160
|
-
const filesChanged = diffFiles.split('\n').length;
|
|
161
|
-
const telemetryContext = {
|
|
162
|
-
fileCount: filesChanged,
|
|
163
|
-
batchSize: filesChanged,
|
|
164
|
-
totalBatches: 1,
|
|
165
|
-
model: subagentModel || 'sonnet',
|
|
166
|
-
hook: 'analyze-diff'
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
// Use cross-platform executeClaudeWithRetry from claude-client.js with telemetry
|
|
171
|
-
const response = await executeClaudeWithRetry(prompt, {
|
|
172
|
-
timeout: 180000, // 3 minutes for diff analysis
|
|
173
|
-
telemetryContext
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Extract JSON from response using claude-client utility
|
|
177
|
-
const result = extractJSON(response);
|
|
51
|
+
// Log truncation details if applicable
|
|
52
|
+
if (result.context.isTruncated) {
|
|
53
|
+
logger.debug('analyze-diff', 'Diff was truncated', result.context.truncationDetails);
|
|
54
|
+
}
|
|
178
55
|
|
|
179
56
|
// Show the results
|
|
57
|
+
const contextDescription = `${result.context.currentBranch} vs ${result.context.baseBranch}`;
|
|
58
|
+
const filesChanged = result.context.filesCount;
|
|
59
|
+
|
|
180
60
|
console.log('');
|
|
181
61
|
console.log('════════════════════════════════════════════════════════════════');
|
|
182
62
|
console.log(' DIFFERENCES ANALYSIS ');
|
|
@@ -215,10 +95,15 @@ export async function runAnalyzeDiff(args) {
|
|
|
215
95
|
|
|
216
96
|
// Save the results in a file with context
|
|
217
97
|
const outputData = {
|
|
218
|
-
|
|
98
|
+
prTitle: result.prTitle,
|
|
99
|
+
prDescription: result.prDescription,
|
|
100
|
+
suggestedBranchName: result.suggestedBranchName,
|
|
101
|
+
changeType: result.changeType,
|
|
102
|
+
breakingChanges: result.breakingChanges,
|
|
103
|
+
testingNotes: result.testingNotes,
|
|
219
104
|
context: {
|
|
220
|
-
currentBranch,
|
|
221
|
-
baseBranch,
|
|
105
|
+
currentBranch: result.context.currentBranch,
|
|
106
|
+
baseBranch: result.context.baseBranch,
|
|
222
107
|
contextDescription,
|
|
223
108
|
filesChanged,
|
|
224
109
|
timestamp: new Date().toISOString()
|
|
@@ -243,13 +128,7 @@ export async function runAnalyzeDiff(args) {
|
|
|
243
128
|
|
|
244
129
|
// Contextual suggestions
|
|
245
130
|
console.log('');
|
|
246
|
-
if (
|
|
247
|
-
// Case of local changes without push
|
|
248
|
-
console.log(`💡 ${colors.yellow}To create new branch with these changes:${colors.reset}`);
|
|
249
|
-
console.log(` git checkout -b ${result.suggestedBranchName}`);
|
|
250
|
-
console.log(` git push -u origin ${result.suggestedBranchName}`);
|
|
251
|
-
} else if (currentBranch !== result.suggestedBranchName) {
|
|
252
|
-
// Normal case of comparison between branches
|
|
131
|
+
if (result.context.currentBranch !== result.suggestedBranchName) {
|
|
253
132
|
console.log(`💡 ${colors.yellow}For renaming your current branch:${colors.reset}`);
|
|
254
133
|
console.log(` git branch -m ${result.suggestedBranchName}`);
|
|
255
134
|
}
|
|
@@ -257,6 +136,6 @@ export async function runAnalyzeDiff(args) {
|
|
|
257
136
|
console.log(`💡 ${colors.yellow}Tip:${colors.reset} Use this information to create your PR on GitHub.`);
|
|
258
137
|
|
|
259
138
|
} catch (e) {
|
|
260
|
-
error(
|
|
139
|
+
error(`Error analyzing diff: ${e.message}`);
|
|
261
140
|
}
|
|
262
141
|
}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* 3. Calculate new version with optional suffix
|
|
9
9
|
* 4. Update version file(s)
|
|
10
10
|
* 5. [Optional] Generate and update CHANGELOG
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
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 (skipped with --no-push)
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { execSync } from 'child_process';
|
|
@@ -37,7 +38,9 @@ import {
|
|
|
37
38
|
getRepoRoot,
|
|
38
39
|
getCurrentBranch,
|
|
39
40
|
verifyRemoteExists,
|
|
40
|
-
getRemoteName
|
|
41
|
+
getRemoteName,
|
|
42
|
+
stageFiles,
|
|
43
|
+
createCommit
|
|
41
44
|
} from '../utils/git-operations.js';
|
|
42
45
|
import { getConfig } from '../config.js';
|
|
43
46
|
import { showInfo, showSuccess, showError, showWarning, promptConfirmation } from '../utils/interactive-ui.js';
|
|
@@ -125,7 +128,8 @@ function parseArguments(args) {
|
|
|
125
128
|
baseBranch: 'main',
|
|
126
129
|
dryRun: false,
|
|
127
130
|
noTag: false,
|
|
128
|
-
noPush: false
|
|
131
|
+
noPush: false,
|
|
132
|
+
noCommit: false
|
|
129
133
|
};
|
|
130
134
|
|
|
131
135
|
// First argument should be bump type
|
|
@@ -158,6 +162,8 @@ function parseArguments(args) {
|
|
|
158
162
|
parsed.noTag = true;
|
|
159
163
|
} else if (arg === '--no-push') {
|
|
160
164
|
parsed.noPush = true;
|
|
165
|
+
} else if (arg === '--no-commit') {
|
|
166
|
+
parsed.noCommit = true;
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
|
|
@@ -218,6 +224,7 @@ export async function runBumpVersion(args) {
|
|
|
218
224
|
console.log(' --dry-run Preview changes without applying');
|
|
219
225
|
console.log(' --no-tag Skip Git tag creation');
|
|
220
226
|
console.log(' --no-push Create tag but don\'t push to remote');
|
|
227
|
+
console.log(' --no-commit Skip automatic commit (manual workflow)');
|
|
221
228
|
console.log('');
|
|
222
229
|
console.log('Examples:');
|
|
223
230
|
console.log(' claude-hooks bump-version minor --suffix SNAPSHOT');
|
|
@@ -381,58 +388,136 @@ export async function runBumpVersion(args) {
|
|
|
381
388
|
console.log('');
|
|
382
389
|
}
|
|
383
390
|
|
|
384
|
-
// Step 6:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
391
|
+
// Step 6: Stage and commit changes
|
|
392
|
+
let commitCreated = false;
|
|
393
|
+
|
|
394
|
+
if (!options.noCommit) {
|
|
395
|
+
logger.debug('bump-version', 'Step 6: Staging and committing changes');
|
|
396
|
+
showInfo('Staging and committing changes...');
|
|
397
|
+
|
|
398
|
+
// Collect files to stage
|
|
399
|
+
const filesToStage = [];
|
|
400
|
+
|
|
401
|
+
// Add discovered version files
|
|
402
|
+
const paths = getDiscoveredPaths();
|
|
403
|
+
if (paths.packageJson) {
|
|
404
|
+
filesToStage.push(paths.packageJson);
|
|
405
|
+
}
|
|
406
|
+
if (paths.pomXml) {
|
|
407
|
+
filesToStage.push(paths.pomXml);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Add CHANGELOG if it was updated
|
|
411
|
+
if (options.updateChangelog) {
|
|
412
|
+
const changelogPath = path.join(getRepoRoot(), 'CHANGELOG.md');
|
|
413
|
+
if (fs.existsSync(changelogPath)) {
|
|
414
|
+
filesToStage.push(changelogPath);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Stage files
|
|
419
|
+
const stageResult = stageFiles(filesToStage);
|
|
420
|
+
|
|
421
|
+
if (!stageResult.success) {
|
|
422
|
+
showError(`Failed to stage files: ${stageResult.error}`);
|
|
423
|
+
console.log('');
|
|
424
|
+
console.log('Files that should be staged:');
|
|
425
|
+
filesToStage.forEach(f => console.log(` - ${path.relative(getRepoRoot(), f)}`));
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log('You can stage and commit manually:');
|
|
428
|
+
console.log(` git add ${filesToUpdate.join(' ')}`);
|
|
429
|
+
if (options.updateChangelog) {
|
|
430
|
+
console.log(' git add CHANGELOG.md');
|
|
403
431
|
}
|
|
432
|
+
console.log(` git commit -m "chore(version): bump to ${newVersion}"`);
|
|
433
|
+
process.exit(1);
|
|
404
434
|
}
|
|
405
435
|
|
|
406
|
-
|
|
407
|
-
const tagResult = createTag(newVersion, tagMessage, { force: exists });
|
|
436
|
+
showSuccess(`✓ Staged ${stageResult.stagedFiles.length} file(s)`);
|
|
408
437
|
|
|
409
|
-
|
|
410
|
-
|
|
438
|
+
// Create commit
|
|
439
|
+
const commitMessage = `chore(version): bump to ${newVersion}`;
|
|
440
|
+
const commitResult = createCommit(commitMessage, { noVerify: true });
|
|
441
|
+
|
|
442
|
+
if (!commitResult.success) {
|
|
443
|
+
showError(`Failed to create commit: ${commitResult.error}`);
|
|
411
444
|
console.log('');
|
|
445
|
+
console.log('Files have been staged. You can commit manually:');
|
|
446
|
+
console.log(` git commit -m "chore(version): bump to ${newVersion}"`);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
412
449
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
450
|
+
showSuccess('✓ Changes committed');
|
|
451
|
+
commitCreated = true;
|
|
452
|
+
console.log('');
|
|
453
|
+
} else {
|
|
454
|
+
showInfo('Commit skipped (--no-commit)');
|
|
455
|
+
console.log('');
|
|
456
|
+
}
|
|
417
457
|
|
|
418
|
-
|
|
458
|
+
// Step 7: Create Git tag (if not disabled)
|
|
459
|
+
if (!options.noTag) {
|
|
460
|
+
logger.debug('bump-version', 'Step 7: Creating Git tag');
|
|
419
461
|
|
|
420
|
-
|
|
421
|
-
|
|
462
|
+
// Prevent tag creation if changes not committed
|
|
463
|
+
if (options.noCommit) {
|
|
464
|
+
showWarning('Tag creation skipped: --no-commit requires manual commit before tagging');
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log('After committing, you can create the tag:');
|
|
467
|
+
console.log(` git tag -a ${tagName} -m "Release version ${newVersion}"`);
|
|
468
|
+
console.log(` git push origin ${tagName}`);
|
|
469
|
+
console.log('');
|
|
470
|
+
} else {
|
|
471
|
+
showInfo('Creating Git tag...');
|
|
472
|
+
|
|
473
|
+
// Check if tag already exists
|
|
474
|
+
const exists = await tagExists(tagName, 'local');
|
|
475
|
+
if (exists) {
|
|
476
|
+
showWarning(`Tag ${tagName} already exists locally`);
|
|
477
|
+
const shouldOverwrite = await promptConfirmation(
|
|
478
|
+
'Overwrite existing tag?',
|
|
479
|
+
false
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
if (!shouldOverwrite) {
|
|
483
|
+
showInfo('Tag creation skipped');
|
|
484
|
+
console.log('');
|
|
485
|
+
console.log('Version files updated successfully, but tag was not created.');
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const tagMessage = `Release version ${newVersion}`;
|
|
491
|
+
const tagResult = createTag(newVersion, tagMessage, { force: exists });
|
|
492
|
+
|
|
493
|
+
if (tagResult.success) {
|
|
494
|
+
showSuccess(`✓ Tag created: ${tagName}`);
|
|
495
|
+
console.log('');
|
|
496
|
+
|
|
497
|
+
// Step 7: Push tag (if not disabled)
|
|
498
|
+
if (!options.noPush) {
|
|
499
|
+
logger.debug('bump-version', 'Step 7: Pushing tag to remote');
|
|
500
|
+
showInfo('Pushing tag to remote...');
|
|
501
|
+
|
|
502
|
+
const pushResult = pushTags(null, tagName);
|
|
503
|
+
|
|
504
|
+
if (pushResult.success) {
|
|
505
|
+
showSuccess('✓ Tag pushed to remote');
|
|
506
|
+
} else {
|
|
507
|
+
showError(`Failed to push tag: ${pushResult.error}`);
|
|
508
|
+
console.log('');
|
|
509
|
+
console.log('You can push the tag manually:');
|
|
510
|
+
console.log(` git push origin ${tagName}`);
|
|
511
|
+
}
|
|
422
512
|
} else {
|
|
423
|
-
|
|
513
|
+
showInfo('Tag push skipped (--no-push)');
|
|
424
514
|
console.log('');
|
|
425
|
-
console.log('
|
|
515
|
+
console.log('To push the tag later:');
|
|
426
516
|
console.log(` git push origin ${tagName}`);
|
|
427
517
|
}
|
|
428
518
|
} else {
|
|
429
|
-
|
|
430
|
-
console.log('');
|
|
431
|
-
console.log('To push the tag later:');
|
|
432
|
-
console.log(` git push origin ${tagName}`);
|
|
519
|
+
showError(`Failed to create tag: ${tagResult.error}`);
|
|
433
520
|
}
|
|
434
|
-
} else {
|
|
435
|
-
showError(`Failed to create tag: ${tagResult.error}`);
|
|
436
521
|
}
|
|
437
522
|
} else {
|
|
438
523
|
showInfo('Tag creation skipped (--no-tag)');
|
|
@@ -448,11 +533,33 @@ export async function runBumpVersion(args) {
|
|
|
448
533
|
console.log(`${colors.blue}New version:${colors.reset} ${newVersion}`);
|
|
449
534
|
console.log(`${colors.blue}Tag:${colors.reset} ${tagName}`);
|
|
450
535
|
console.log('');
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
536
|
+
|
|
537
|
+
// Conditional next steps
|
|
538
|
+
if (options.noCommit) {
|
|
539
|
+
console.log('Next steps:');
|
|
540
|
+
console.log(' 1. Review changes: git diff');
|
|
541
|
+
console.log(` 2. Stage files: git add ${filesToUpdate.join(' ')}`);
|
|
542
|
+
if (options.updateChangelog) {
|
|
543
|
+
console.log(' git add CHANGELOG.md');
|
|
544
|
+
}
|
|
545
|
+
console.log(` 3. Commit: git commit -m "chore(version): bump to ${newVersion}"`);
|
|
546
|
+
console.log(` 4. Create tag: git tag -a ${tagName} -m "Release version ${newVersion}"`);
|
|
547
|
+
console.log(` 5. Push: git push origin $(git branch --show-current) ${tagName}`);
|
|
548
|
+
console.log('');
|
|
549
|
+
} else if (commitCreated && !options.noTag && !options.noPush) {
|
|
550
|
+
console.log('All done! Changes committed and tag pushed to remote.');
|
|
551
|
+
console.log('');
|
|
552
|
+
console.log('Next steps:');
|
|
553
|
+
console.log(' 1. Create PR: claude-hooks create-pr main');
|
|
554
|
+
console.log('');
|
|
555
|
+
} else {
|
|
556
|
+
console.log('Next steps:');
|
|
557
|
+
if (commitCreated && options.noPush) {
|
|
558
|
+
console.log(` 1. Push changes: git push origin $(git branch --show-current) ${tagName}`);
|
|
559
|
+
}
|
|
560
|
+
console.log(' 2. Create PR: claude-hooks create-pr main');
|
|
561
|
+
console.log('');
|
|
562
|
+
}
|
|
456
563
|
|
|
457
564
|
} catch (err) {
|
|
458
565
|
logger.error('bump-version', 'Version bump failed', err);
|