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 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() → filter by preset extensions
246
- buildAnalysisPrompt() → analyzeCode() or analyzeCodeParallel()
247
- parse JSON response exit 0 (pass) or exit 1 (block)
248
- → if blocked: generates claude_resolution_prompt.md
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: ['Validation error: ' + err.message]
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(`✓ Tag pushed to remote`);
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(' 2. Commit the version bump: git add . && git commit -m "chore: bump version to ' + newVersion + '"');
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(' - Delete tag if created: git tag -d ' + tagName);
463
+ console.log(` - Delete tag if created: git tag -d ${ tagName}`);
449
464
  console.log('');
450
465
  process.exit(1);
451
466
  }