claude-git-hooks 2.13.0 → 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 +64 -0
- package/README.md +20 -10
- package/lib/commands/analyze-diff.js +32 -153
- package/lib/commands/bump-version.js +164 -56
- package/lib/commands/create-pr.js +171 -94
- 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 +60 -60
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,70 @@ 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
|
+
|
|
34
|
+
## [2.14.1] - 2026-02-06
|
|
35
|
+
|
|
36
|
+
### 🔧 Changed
|
|
37
|
+
- Improved `bump-version` command workflow - now automatically stages and commits version changes with conventional commit format before tag creation
|
|
38
|
+
- Enhanced `bump-version` command flexibility - added `--no-commit` flag for manual workflows where users prefer to commit changes themselves
|
|
39
|
+
- Improved error handling in `bump-version` - provides clearer guidance when staging or committing fails, with manual fallback instructions
|
|
40
|
+
- Enhanced next-steps guidance in `bump-version` output - adapts instructions based on flags used (--no-commit, --no-push, --no-tag)
|
|
41
|
+
|
|
42
|
+
### 🐛 Fixed
|
|
43
|
+
- Fixed `bump-version` workflow inconsistency - prevented tag creation when using `--no-commit` flag, as tags should only be created after changes are committed
|
|
44
|
+
- Fixed potential git history issues - version bump commits now use `--no-verify` flag to bypass pre-commit hooks and prevent circular execution
|
|
45
|
+
|
|
46
|
+
### 🗑️ Removed
|
|
47
|
+
- 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
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## [2.14.0] - 2026-02-06
|
|
51
|
+
|
|
52
|
+
### ✨ Added
|
|
53
|
+
- PR metadata engine (`pr-metadata-engine.js`) - centralized module for generating PR titles, descriptions, and test plans from branch diffs with timeout resilience (#63)
|
|
54
|
+
- Tiered diff reduction system - automatically truncates large diffs using context reduction, proportional budgets, and stat-only summaries to prevent timeouts
|
|
55
|
+
- Extended git operations - added `fetchRemote()`, `branchExists()`, `resolveBaseBranch()`, `getChangedFilesBetweenRefs()`, `getDiffBetweenRefs()`, and `getCommitsBetweenRefs()` to `git-operations.js`
|
|
56
|
+
- RFC-001 documentation - comprehensive design specification for PR metadata engine refactor with timeout resilience strategy
|
|
57
|
+
|
|
58
|
+
### 🔧 Changed
|
|
59
|
+
- Refactored `analyze-diff` command to use shared PR metadata engine - reduced from 262 to ~80 lines by eliminating duplicate git logic
|
|
60
|
+
- Refactored `create-pr` command to use shared PR metadata engine - eliminated duplicate diff extraction logic (lines 340-420)
|
|
61
|
+
- Improved timeout error handling - timeout errors now include `errorInfo` and are classified as recoverable for automatic retry with exponential backoff
|
|
62
|
+
- Updated CLAUDE.md with PR metadata engine architecture, new git operations exports, and tiered diff reduction strategy
|
|
63
|
+
|
|
64
|
+
### 🐛 Fixed
|
|
65
|
+
- Fixed timeout failures in `analyze-diff` for large diffs - now uses tiered reduction instead of monolithic diff processing
|
|
66
|
+
- Fixed inconsistent behavior between `analyze-diff` and `create-pr` commands - both now share identical analysis logic via unified engine
|
|
67
|
+
|
|
68
|
+
### 🗑️ Removed
|
|
69
|
+
- Removed unreliable `SUBAGENT_INSTRUCTION` text hint - replaced with deterministic tiered diff reduction for parallel processing
|
|
70
|
+
|
|
71
|
+
|
|
8
72
|
## [2.13.0] - 2026-02-05
|
|
9
73
|
|
|
10
74
|
### ✨ Added
|
package/README.md
CHANGED
|
@@ -110,14 +110,21 @@ 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
|
+
# Push tag immediately (otherwise pushed by create-pr)
|
|
115
|
+
claude-hooks bump-version patch --push
|
|
116
|
+
|
|
117
|
+
# Manual workflow (skip automatic commit)
|
|
118
|
+
claude-hooks bump-version patch --no-commit
|
|
113
119
|
```
|
|
114
120
|
|
|
115
121
|
**What it does:**
|
|
116
122
|
- Detects project type (Node.js, Maven, or monorepo with both)
|
|
117
123
|
- Updates `package.json` and/or `pom.xml`
|
|
118
124
|
- Generates CHANGELOG entry with Claude (analyzes commits)
|
|
125
|
+
- Commits changes automatically with conventional commit format
|
|
119
126
|
- Creates annotated Git tag with `v` prefix (e.g., `v2.7.0`)
|
|
120
|
-
-
|
|
127
|
+
- Tags stay local by default (use `--push` to push immediately, or let `create-pr` handle it)
|
|
121
128
|
|
|
122
129
|
**Version workflow:**
|
|
123
130
|
```
|
|
@@ -253,7 +260,8 @@ claude-hooks --help # Full command reference
|
|
|
253
260
|
| `analysis-engine.js` | **Shared analysis logic** - file data, orchestration, results (v2.13.0) | `buildFilesData()`, `runAnalysis()`, `consolidateResults()`, `displayResults()` |
|
|
254
261
|
| `claude-client.js` | **Claude CLI wrapper** - spawn, retry, parallel execution | `analyzeCode()`, `analyzeCodeParallel()`, `executeClaudeWithRetry()` |
|
|
255
262
|
| `prompt-builder.js` | **Prompt construction** - load templates, replace variables | `buildAnalysisPrompt()`, `loadPrompt()` |
|
|
256
|
-
| `git-operations.js` | **Git abstractions** - staged files, diff,
|
|
263
|
+
| `git-operations.js` | **Git abstractions** - staged files, diff, branch comparison | `getStagedFiles()`, `getDiff()`, `getRepoRoot()`, `resolveBaseBranch()`, `getDiffBetweenRefs()` |
|
|
264
|
+
| `pr-metadata-engine.js` | **PR metadata generation** - branch context, diff reduction (v2.14.0) | `getBranchContext()`, `buildDiffPayload()`, `generatePRMetadata()`, `analyzeBranchForPR()` |
|
|
257
265
|
| `github-api.js` | **Octokit integration** - PR creation, token validation | `createPullRequest()`, `validateToken()`, `saveGitHubToken()` |
|
|
258
266
|
| `github-client.js` | **GitHub helpers** - CODEOWNERS parsing, reviewers | `getReviewersForFiles()`, `parseGitHubRepo()` |
|
|
259
267
|
| `preset-loader.js` | **Preset system** - load tech-stack configurations | `loadPreset()`, `listPresets()` |
|
|
@@ -290,16 +298,18 @@ git commit -m "auto" → templates/prepare-commit-msg (bash wrapper)
|
|
|
290
298
|
→ write to COMMIT_EDITMSG
|
|
291
299
|
```
|
|
292
300
|
|
|
293
|
-
#### PR
|
|
301
|
+
#### PR Metadata Generation (analyze-diff / create-pr)
|
|
294
302
|
|
|
295
303
|
```
|
|
296
|
-
claude-hooks create-pr → bin/claude-hooks (router)
|
|
297
|
-
→ lib/commands/create-pr.js
|
|
298
|
-
→
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
claude-hooks analyze-diff|create-pr → bin/claude-hooks (router)
|
|
305
|
+
→ lib/commands/analyze-diff.js or create-pr.js (thin wrapper)
|
|
306
|
+
→ analyzeBranchForPR() (pr-metadata-engine.js)
|
|
307
|
+
→ resolveBaseBranch() (git-operations.js)
|
|
308
|
+
→ getDiffBetweenRefs() + getCommitsBetweenRefs()
|
|
309
|
+
→ buildDiffPayload() with tiered reduction (context → proportional → stat-only)
|
|
310
|
+
→ executeClaudeWithRetry() → PRMetadata
|
|
311
|
+
→ analyze-diff: formats to console + saves JSON
|
|
312
|
+
→ create-pr: additionally creates PR via Octokit API
|
|
303
313
|
```
|
|
304
314
|
|
|
305
315
|
### 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
|
}
|