claude-git-hooks 2.12.0 → 2.13.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 +16 -1
- package/README.md +36 -4
- package/bin/claude-hooks +8 -0
- package/lib/commands/analyze.js +217 -0
- package/lib/commands/bump-version.js +20 -5
- package/lib/hooks/pre-commit.js +26 -265
- package/lib/utils/analysis-engine.js +469 -0
- package/lib/utils/git-operations.js +130 -1
- package/lib/utils/git-tag-manager.js +58 -8
- package/lib/utils/interactive-ui.js +86 -1
- package/lib/utils/resolution-prompt.js +57 -34
- package/lib/utils/version-manager.js +219 -52
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.13.0] - 2026-02-05
|
|
9
|
+
|
|
10
|
+
### ✨ Added
|
|
11
|
+
- Interactive analysis command `claude-hooks analyze` - review all issues (INFO to BLOCKER) interactively before committing with auto-commit option
|
|
12
|
+
- Shared analysis engine module (`analysis-engine.js`) - centralized file data building, analysis orchestration, and results display for both pre-commit hooks and interactive analysis
|
|
13
|
+
- Support for version files in subdirectories - automatically searches parent directories when version files not found in repo root
|
|
14
|
+
- Auto-commit functionality after interactive analysis - creates commit with auto-generated message when user confirms
|
|
15
|
+
|
|
16
|
+
### 🔧 Changed
|
|
17
|
+
- Improved version detection logic for pom.xml files - enhanced reliability of version extraction in Maven projects
|
|
18
|
+
|
|
19
|
+
### 🐛 Fixed
|
|
20
|
+
- Fixed version search logic for pom.xml files - corrected parsing issues in Maven version detection
|
|
21
|
+
|
|
22
|
+
|
|
8
23
|
## [2.12.0] - 2026-02-03
|
|
9
24
|
|
|
10
25
|
### ✨ Added
|
|
@@ -43,7 +58,7 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.h
|
|
|
43
58
|
|
|
44
59
|
- **New utility modules**:
|
|
45
60
|
- `lib/utils/version-manager.js` - Version detection, parsing, incrementing, validation
|
|
46
|
-
- `lib/utils/git-tag-manager.js` - Git tag operations (create, list, compare, push)
|
|
61
|
+
- `lib/utils/git-tag-manager.js` - Git tag operations (create, list, compare, push, isSemverTag)
|
|
47
62
|
- `lib/utils/changelog-generator.js` - CHANGELOG generation with Claude
|
|
48
63
|
- `templates/GENERATE_CHANGELOG.md` - Claude prompt for changelog analysis
|
|
49
64
|
|
package/README.md
CHANGED
|
@@ -60,6 +60,33 @@ export GITHUB_TOKEN="ghp_..."
|
|
|
60
60
|
|
|
61
61
|
Create token at https://github.com/settings/tokens with scopes: `repo`, `read:org`
|
|
62
62
|
|
|
63
|
+
### Analyze Code (Interactive Review)
|
|
64
|
+
|
|
65
|
+
Run interactive code analysis before committing:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Analyze staged changes (default)
|
|
69
|
+
claude-hooks analyze
|
|
70
|
+
|
|
71
|
+
# Analyze unstaged changes
|
|
72
|
+
claude-hooks analyze --unstaged
|
|
73
|
+
|
|
74
|
+
# Analyze all tracked files
|
|
75
|
+
claude-hooks analyze --all
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**What it does:**
|
|
79
|
+
- Analyzes selected file scope (staged, unstaged, or all)
|
|
80
|
+
- Shows all issues (INFO, MINOR, MAJOR, CRITICAL, BLOCKER)
|
|
81
|
+
- Interactive prompt with options:
|
|
82
|
+
- **Continue**: Creates commit automatically with auto-generated message
|
|
83
|
+
- **Abort**: Generate resolution prompt and fix issues
|
|
84
|
+
- **View**: Show detailed issue list
|
|
85
|
+
- Executes `git commit -m "auto" --no-verify` on confirmation
|
|
86
|
+
- Works outside git hooks (no stdin limitations)
|
|
87
|
+
|
|
88
|
+
**Use case:** Complete analysis-to-commit workflow in one command.
|
|
89
|
+
|
|
63
90
|
### Analyze Diff (without creating PR)
|
|
64
91
|
|
|
65
92
|
```bash
|
|
@@ -223,6 +250,7 @@ claude-hooks --help # Full command reference
|
|
|
223
250
|
|
|
224
251
|
| Module | Purpose | Key Exports |
|
|
225
252
|
|--------|---------|-------------|
|
|
253
|
+
| `analysis-engine.js` | **Shared analysis logic** - file data, orchestration, results (v2.13.0) | `buildFilesData()`, `runAnalysis()`, `consolidateResults()`, `displayResults()` |
|
|
226
254
|
| `claude-client.js` | **Claude CLI wrapper** - spawn, retry, parallel execution | `analyzeCode()`, `analyzeCodeParallel()`, `executeClaudeWithRetry()` |
|
|
227
255
|
| `prompt-builder.js` | **Prompt construction** - load templates, replace variables | `buildAnalysisPrompt()`, `loadPrompt()` |
|
|
228
256
|
| `git-operations.js` | **Git abstractions** - staged files, diff, repo root | `getStagedFiles()`, `getDiff()`, `getRepoRoot()` |
|
|
@@ -242,12 +270,16 @@ claude-hooks --help # Full command reference
|
|
|
242
270
|
```
|
|
243
271
|
git commit → templates/pre-commit (bash wrapper)
|
|
244
272
|
→ lib/hooks/pre-commit.js
|
|
245
|
-
→ getStagedFiles() →
|
|
246
|
-
→
|
|
247
|
-
→
|
|
248
|
-
→ if
|
|
273
|
+
→ getStagedFiles() → filterFiles() by preset extensions + size
|
|
274
|
+
→ buildFilesData() → runAnalysis() (via analysis-engine.js)
|
|
275
|
+
→ displayResults() → show quality gate status
|
|
276
|
+
→ if blocking issues (critical/blocker):
|
|
277
|
+
generates claude_resolution_prompt.md → exit 1 (block)
|
|
278
|
+
→ if non-blocking or no issues: exit 0 (pass)
|
|
249
279
|
```
|
|
250
280
|
|
|
281
|
+
**Note:** For interactive review of non-blocking issues, use `claude-hooks analyze` before committing.
|
|
282
|
+
|
|
251
283
|
#### Commit Message Generation
|
|
252
284
|
|
|
253
285
|
```
|
package/bin/claude-hooks
CHANGED
|
@@ -13,6 +13,7 @@ import { error } from '../lib/commands/helpers.js';
|
|
|
13
13
|
// Import commands
|
|
14
14
|
import { runInstall } from '../lib/commands/install.js';
|
|
15
15
|
import { runEnable, runDisable, runStatus, runUninstall } from '../lib/commands/hooks.js';
|
|
16
|
+
import { runAnalyze } from '../lib/commands/analyze.js';
|
|
16
17
|
import { runAnalyzeDiff } from '../lib/commands/analyze-diff.js';
|
|
17
18
|
import { runCreatePr } from '../lib/commands/create-pr.js';
|
|
18
19
|
import { runSetupGitHub } from '../lib/commands/setup-github.js';
|
|
@@ -51,6 +52,13 @@ async function main() {
|
|
|
51
52
|
case 'status':
|
|
52
53
|
runStatus();
|
|
53
54
|
break;
|
|
55
|
+
case 'analyze':
|
|
56
|
+
await runAnalyze({
|
|
57
|
+
staged: !args.includes('--unstaged') && !args.includes('--all'),
|
|
58
|
+
unstaged: args.includes('--unstaged'),
|
|
59
|
+
all: args.includes('--all')
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
54
62
|
case 'analyze-diff':
|
|
55
63
|
await runAnalyzeDiff(args.slice(1));
|
|
56
64
|
break;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: analyze.js
|
|
3
|
+
* Purpose: On-demand code analysis command (interactive, runs outside git hooks)
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: Git hooks cannot reliably read stdin for interactive prompts
|
|
6
|
+
* (stdin is redirected from /dev/null). This command provides interactive analysis
|
|
7
|
+
* before committing, allowing developers to review all issues and decide whether
|
|
8
|
+
* to proceed or fix them first.
|
|
9
|
+
*
|
|
10
|
+
* Key features:
|
|
11
|
+
* - Runs outside git hook context (stdin works normally)
|
|
12
|
+
* - Analyzes staged, unstaged, or all tracked files
|
|
13
|
+
* - Interactive confirmation with detailed issue view
|
|
14
|
+
* - Generates resolution prompt on abort
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* claude-hooks analyze # Analyze staged files (default)
|
|
18
|
+
* claude-hooks analyze --unstaged # Analyze unstaged changes
|
|
19
|
+
* claude-hooks analyze --all # Analyze all tracked files
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { getStagedFiles, getUnstagedFiles, getAllTrackedFiles, createCommit } from '../utils/git-operations.js';
|
|
23
|
+
import { filterFiles } from '../utils/file-operations.js';
|
|
24
|
+
import {
|
|
25
|
+
buildFilesData,
|
|
26
|
+
runAnalysis,
|
|
27
|
+
hasAnyIssues,
|
|
28
|
+
displayIssueSummary
|
|
29
|
+
} from '../utils/analysis-engine.js';
|
|
30
|
+
import { promptUserConfirmation, promptConfirmation } from '../utils/interactive-ui.js';
|
|
31
|
+
import { generateResolutionPrompt } from '../utils/resolution-prompt.js';
|
|
32
|
+
import { getConfig } from '../config.js';
|
|
33
|
+
import { loadPreset } from '../utils/preset-loader.js';
|
|
34
|
+
import logger from '../utils/logger.js';
|
|
35
|
+
import { error, success, info } from './helpers.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Main analyze command
|
|
39
|
+
* Why: Provides interactive analysis before committing
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options - Command options
|
|
42
|
+
* @param {boolean} options.staged - Analyze staged files (default: true)
|
|
43
|
+
* @param {boolean} options.unstaged - Analyze unstaged files
|
|
44
|
+
* @param {boolean} options.all - Analyze all tracked files
|
|
45
|
+
*/
|
|
46
|
+
export const runAnalyze = async (options = {}) => {
|
|
47
|
+
const { unstaged = false, all = false } = options;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Load configuration
|
|
51
|
+
const config = await getConfig();
|
|
52
|
+
|
|
53
|
+
// Enable debug mode from config
|
|
54
|
+
if (config.system?.debug) {
|
|
55
|
+
logger.setDebugMode(true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Load active preset for file extensions
|
|
59
|
+
const presetName = config.preset || 'default';
|
|
60
|
+
const { metadata } = await loadPreset(presetName);
|
|
61
|
+
const allowedExtensions = metadata.fileExtensions;
|
|
62
|
+
|
|
63
|
+
// Determine scope
|
|
64
|
+
let scopeLabel = 'staged changes';
|
|
65
|
+
if (all) {
|
|
66
|
+
scopeLabel = 'all tracked files';
|
|
67
|
+
} else if (unstaged) {
|
|
68
|
+
scopeLabel = 'unstaged changes';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
info(`Analyzing ${scopeLabel} with '${metadata.displayName}' preset...`);
|
|
72
|
+
|
|
73
|
+
// Get files based on scope
|
|
74
|
+
let files = [];
|
|
75
|
+
if (all) {
|
|
76
|
+
files = getAllTrackedFiles({ extensions: allowedExtensions });
|
|
77
|
+
} else if (unstaged) {
|
|
78
|
+
files = getUnstagedFiles({ extensions: allowedExtensions });
|
|
79
|
+
} else {
|
|
80
|
+
files = getStagedFiles({ extensions: allowedExtensions });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (files.length === 0) {
|
|
84
|
+
info(`No files to analyze in ${scopeLabel}.`);
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logger.debug('analyze', 'Files found', {
|
|
89
|
+
scope: scopeLabel,
|
|
90
|
+
count: files.length,
|
|
91
|
+
extensions: allowedExtensions
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Filter files by size
|
|
95
|
+
const filteredFiles = await filterFiles(files, {
|
|
96
|
+
maxSize: config.analysis?.maxFileSize || 1048576,
|
|
97
|
+
extensions: allowedExtensions
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const validFiles = filteredFiles.filter(f => f.valid);
|
|
101
|
+
const invalidFiles = filteredFiles.filter(f => !f.valid);
|
|
102
|
+
|
|
103
|
+
// Show warnings for skipped files
|
|
104
|
+
if (invalidFiles.length > 0) {
|
|
105
|
+
invalidFiles.forEach(file => {
|
|
106
|
+
logger.warning(`Skipping ${file.path}: ${file.reason}`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (validFiles.length === 0) {
|
|
111
|
+
info(`No valid files found to analyze in ${scopeLabel}.`);
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
info(`Sending ${validFiles.length} file(s) for analysis...`);
|
|
116
|
+
|
|
117
|
+
// Build file data (diff/content) using shared engine
|
|
118
|
+
const filesData = buildFilesData(validFiles, { staged: !unstaged && !all });
|
|
119
|
+
|
|
120
|
+
if (filesData.length === 0) {
|
|
121
|
+
info('No file data could be extracted.');
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run analysis using shared engine
|
|
126
|
+
const result = await runAnalysis(filesData, config, { hook: 'analyze' });
|
|
127
|
+
|
|
128
|
+
// Check results
|
|
129
|
+
if (!hasAnyIssues(result)) {
|
|
130
|
+
console.log('');
|
|
131
|
+
success('No issues found. Code is ready to commit!');
|
|
132
|
+
console.log('');
|
|
133
|
+
|
|
134
|
+
// Prompt user to commit or cancel
|
|
135
|
+
const shouldCommit = await promptConfirmation('Create commit now?', true);
|
|
136
|
+
|
|
137
|
+
if (shouldCommit) {
|
|
138
|
+
info('Creating commit with auto-generated message...');
|
|
139
|
+
console.log('');
|
|
140
|
+
|
|
141
|
+
const commitResult = createCommit('auto', { noVerify: true });
|
|
142
|
+
|
|
143
|
+
if (commitResult.success) {
|
|
144
|
+
success('Commit created successfully!');
|
|
145
|
+
if (commitResult.output) {
|
|
146
|
+
console.log(commitResult.output);
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
process.exit(0);
|
|
150
|
+
} else {
|
|
151
|
+
error(`Commit failed: ${commitResult.error}`);
|
|
152
|
+
console.log('');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
info('Commit cancelled. Staged files remain unchanged.');
|
|
157
|
+
console.log('');
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Display summary
|
|
163
|
+
console.log('');
|
|
164
|
+
console.log('Analysis complete:');
|
|
165
|
+
displayIssueSummary(result);
|
|
166
|
+
console.log('');
|
|
167
|
+
|
|
168
|
+
// Interactive confirmation (works outside git hook)
|
|
169
|
+
const userChoice = await promptUserConfirmation(result);
|
|
170
|
+
|
|
171
|
+
if (userChoice === 'abort') {
|
|
172
|
+
// Generate resolution prompt
|
|
173
|
+
await generateResolutionPrompt(result, {
|
|
174
|
+
fileCount: filesData.length
|
|
175
|
+
});
|
|
176
|
+
console.log('');
|
|
177
|
+
success('Resolution prompt generated: claude_resolution_prompt.md');
|
|
178
|
+
info('Fix issues and run `claude-hooks analyze` again.');
|
|
179
|
+
console.log('');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
} else {
|
|
182
|
+
// User chose to continue - execute commit automatically
|
|
183
|
+
console.log('');
|
|
184
|
+
|
|
185
|
+
// Safeguard: verify staged files still exist
|
|
186
|
+
const currentStagedFiles = getStagedFiles({ extensions: allowedExtensions });
|
|
187
|
+
if (currentStagedFiles.length === 0) {
|
|
188
|
+
error('No staged files found. Did you unstage changes?');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
info('Creating commit with auto-generated message...');
|
|
193
|
+
console.log('');
|
|
194
|
+
|
|
195
|
+
// Execute commit with --no-verify (skip hooks - we already analyzed)
|
|
196
|
+
const commitResult = createCommit('auto', { noVerify: true });
|
|
197
|
+
|
|
198
|
+
if (commitResult.success) {
|
|
199
|
+
success('Commit created successfully!');
|
|
200
|
+
if (commitResult.output) {
|
|
201
|
+
console.log(commitResult.output);
|
|
202
|
+
}
|
|
203
|
+
console.log('');
|
|
204
|
+
process.exit(0);
|
|
205
|
+
} else {
|
|
206
|
+
error(`Commit failed: ${commitResult.error}`);
|
|
207
|
+
console.log('');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
} catch (err) {
|
|
213
|
+
logger.error('analyze', 'Analysis failed', err);
|
|
214
|
+
error(`Analysis failed: ${err.message}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
getCurrentVersion,
|
|
21
21
|
incrementVersion,
|
|
22
22
|
updateVersion,
|
|
23
|
-
parseVersion
|
|
23
|
+
parseVersion,
|
|
24
|
+
getDiscoveredPaths
|
|
24
25
|
} from '../utils/version-manager.js';
|
|
25
26
|
import {
|
|
26
27
|
createTag,
|
|
@@ -102,7 +103,7 @@ function validatePrerequisites() {
|
|
|
102
103
|
logger.error('bump-version - validatePrerequisites', 'Validation failed', err);
|
|
103
104
|
return {
|
|
104
105
|
valid: false,
|
|
105
|
-
errors: [
|
|
106
|
+
errors: [`Validation error: ${ err.message}`]
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -258,9 +259,23 @@ export async function runBumpVersion(args) {
|
|
|
258
259
|
console.log(' - package.json (Node.js project)');
|
|
259
260
|
console.log(' - pom.xml (Maven project)');
|
|
260
261
|
console.log('');
|
|
262
|
+
console.log('Searched in repository root and one level deep in subdirectories.');
|
|
263
|
+
console.log('');
|
|
261
264
|
process.exit(1);
|
|
262
265
|
}
|
|
263
266
|
|
|
267
|
+
// Show discovered paths
|
|
268
|
+
const paths = getDiscoveredPaths();
|
|
269
|
+
const repoRoot = getRepoRoot();
|
|
270
|
+
if (paths.packageJson) {
|
|
271
|
+
const relativePath = path.relative(repoRoot, paths.packageJson);
|
|
272
|
+
info(`Found package.json: ${relativePath}`);
|
|
273
|
+
}
|
|
274
|
+
if (paths.pomXml) {
|
|
275
|
+
const relativePath = path.relative(repoRoot, paths.pomXml);
|
|
276
|
+
info(`Found pom.xml: ${relativePath}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
264
279
|
const versions = getCurrentVersion(projectType);
|
|
265
280
|
const currentVersion = versions.resolved;
|
|
266
281
|
|
|
@@ -403,7 +418,7 @@ export async function runBumpVersion(args) {
|
|
|
403
418
|
const pushResult = pushTags(null, tagName);
|
|
404
419
|
|
|
405
420
|
if (pushResult.success) {
|
|
406
|
-
showSuccess(
|
|
421
|
+
showSuccess('✓ Tag pushed to remote');
|
|
407
422
|
} else {
|
|
408
423
|
showError(`Failed to push tag: ${pushResult.error}`);
|
|
409
424
|
console.log('');
|
|
@@ -435,7 +450,7 @@ export async function runBumpVersion(args) {
|
|
|
435
450
|
console.log('');
|
|
436
451
|
console.log('Next steps:');
|
|
437
452
|
console.log(' 1. Review the changes: git diff');
|
|
438
|
-
console.log(
|
|
453
|
+
console.log(` 2. Commit the version bump: git add . && git commit -m "chore: bump version to ${ newVersion }"`);
|
|
439
454
|
console.log(' 3. Create PR: claude-hooks create-pr main');
|
|
440
455
|
console.log('');
|
|
441
456
|
|
|
@@ -445,7 +460,7 @@ export async function runBumpVersion(args) {
|
|
|
445
460
|
console.log('');
|
|
446
461
|
console.log('The operation was interrupted. You may need to:');
|
|
447
462
|
console.log(' - Revert changes: git checkout .');
|
|
448
|
-
console.log(
|
|
463
|
+
console.log(` - Delete tag if created: git tag -d ${ tagName}`);
|
|
449
464
|
console.log('');
|
|
450
465
|
process.exit(1);
|
|
451
466
|
}
|