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 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
- - Pushes tag to remote automatically
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, repo root | `getStagedFiles()`, `getDiff()`, `getRepoRoot()` |
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 Creation
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
- validateToken() (github-api.js)
299
- parseGitHubRepo() (github-client.js)
300
- getReviewersForFiles() (CODEOWNERS + config)
301
- executeClaudeWithRetry() (metadata generation)
302
- createPullRequest() (Octokit API)
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 { executeClaudeWithRetry, extractJSON } from '../utils/claude-client.js';
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
- // Load configuration
28
+ // Enable debug mode from config
31
29
  const config = await getConfig();
32
-
33
- const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
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
- // Update remote references
41
- execSync('git fetch', { stdio: 'ignore' });
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
- try {
84
- execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
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
- diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
100
-
101
- if (!diffFiles) {
102
- // Check if there are staged or unstaged changes
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
- if (stagedFiles || unstagedFiles) {
107
- warning('No differences with remote, but you have uncommitted local changes.');
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
- // Load prompt from template
148
- const prompt = await loadPrompt('ANALYZE_DIFF.md', {
149
- CONTEXT_DESCRIPTION: contextDescription,
150
- SUBAGENT_INSTRUCTION: subagentInstruction,
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
- ...result,
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 (!args[0] && contextDescription.includes('local changes without push')) {
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('Error executing Claude: ' + e.message);
139
+ error(`Error analyzing diff: ${e.message}`);
261
140
  }
262
141
  }