claude-git-hooks 2.3.0 → 2.3.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 CHANGED
@@ -5,6 +5,68 @@ 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.3.1] - 2025-11-13
9
+
10
+ ### 🔄 Changed
11
+
12
+ - **Configuration Priority** - Updated merge order: `defaults < user config < preset config`
13
+ - **Rationale**: User sets general preferences in `.claude/config.json`, preset provides tech-stack-specific overrides (highest priority)
14
+ - **Effect**: Presets now correctly override user settings (e.g., preset's `model: sonnet` wins over user's `model: haiku`)
15
+ - **Files updated**: `lib/config.js:116` (merge logic), README.md:222 (documentation)
16
+
17
+ ### ✨ Added
18
+
19
+ - **Installation Diagnostics Utility** - New `lib/utils/installation-diagnostics.js`
20
+ - **Purpose**: Reusable error diagnostics with actionable remediation steps
21
+ - **Exports**: `formatError()`, `getInstallationDiagnostics()`, `isInstallationHealthy()`
22
+ - **Use case**: Detects installation in wrong directory (common cause of "presets not found" errors)
23
+ - **Extensible**: Documented future enhancements (file permissions, Claude CLI check, etc.)
24
+
25
+ - **Enhanced Error Messages** - Better guidance when installation fails
26
+ - **Presets not found** (`lib/utils/preset-loader.js:194`): Shows current vs repo root directory
27
+ - **Hook scripts missing** (`templates/pre-commit:76`, `templates/prepare-commit-msg:76`): Early `.claude/` check with remediation steps
28
+ - **npm package issues**: Suggests verifying `npm list -g claude-git-hooks`
29
+
30
+ - **Bash Wrapper Early Validation** - Hooks now check `.claude/` exists before attempting execution
31
+ - **Why**: Fails fast with helpful message if installation incomplete or performed from wrong directory
32
+ - **Effect**: Users immediately know to `cd` to repo root and reinstall
33
+
34
+ - **Claude Error Diagnostics** - New `lib/utils/claude-diagnostics.js`
35
+ - **Purpose**: Detects and formats Claude CLI errors with actionable solutions
36
+ - **Error types**: Rate limit, authentication, network, invalid response, generic
37
+ - **Exports**: `detectClaudeError()`, `formatClaudeError()`, `ClaudeErrorType`, `isRecoverableError()`
38
+ - **Integration**: `lib/utils/claude-client.js:147` automatically detects and formats errors
39
+ - **Effect**: Users see clear guidance (e.g., "Rate limit resets in 2 hours, switch to haiku model")
40
+
41
+ ### 📚 Documentation
42
+
43
+ - **CUSTOMIZATION_GUIDE.md** - Comprehensive preset creation guide in `/templates`
44
+ - Visual flowchart showing prompt assembly (8-step diagram)
45
+ - Step-by-step preset creation tutorial
46
+ - Placeholder system reference with examples
47
+ - Preset kickstart prompt for Claude-assisted generation
48
+ - **Burst limit warning**: Explains why git hooks hit rate limits when manual CLI doesn't
49
+ - Referenced in README.md:333 for discoverability
50
+
51
+ - **Utility Modules Reference** - New section in README.md:596
52
+ - Table of all `lib/utils/` modules with purpose and key exports
53
+ - Usage examples for other Claude instances
54
+ - Promotes code reuse across projects
55
+
56
+ - **README-NPM.md** - Complete rewrite for npm package page
57
+ - **Length**: 266 lines (up from 145 lines, but more focused)
58
+ - **Updated**: v2.3.0 presets, v2.2.0 config centralization, v2.0.0 cross-platform
59
+ - **Added**: Configuration priority explanation, troubleshooting, "What's New" section
60
+ - **Removed**: Outdated v1.x references, environment variable examples
61
+
62
+ ### 🎯 User Experience
63
+
64
+ - **Clearer error context**: Errors now show current directory vs repository root
65
+ - **Actionable solutions**: Every error includes specific commands to fix the issue
66
+ - **Installation verification**: Early checks prevent confusing downstream errors
67
+ - **Better documentation**: Users can find utility functions and customization guides easily
68
+ - **Claude error clarity**: Rate limits, auth failures, and network errors now show specific remediation steps
69
+
8
70
  ## [2.3.0] - 2025-11-11
9
71
 
10
72
  ### ✨ Added
package/README.md CHANGED
@@ -224,11 +224,13 @@ EOF
224
224
  ```
225
225
  defaults (lib/config.js)
226
226
 
227
- preset config (templates/presets/backend/config.json)
227
+ user config (.claude/config.json) ← General preferences
228
228
 
229
- user config (.claude/config.json) ← MÁXIMA PRIORIDAD
229
+ preset config (.claude/presets/{name}/config.json) ← MÁXIMA PRIORIDAD (tech-stack specific)
230
230
  ```
231
231
 
232
+ **Rationale**: User sets general preferences, preset provides tech-stack-specific overrides.
233
+
232
234
  **Nota v2.2.0+:** Variables de entorno ya NO son soportadas. Usar `.claude/config.json` en su lugar.
233
235
 
234
236
  ### 🎯 Casos de Uso Específicos
@@ -324,6 +326,16 @@ git commit -m "fix: resolver issues"
324
326
  - `.claude/CLAUDE_PRE_COMMIT_SONAR.md` - Criterios (backend/frontend/data/db)
325
327
  - `.claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md` - Template del prompt
326
328
 
329
+ **Crear o modificar presets personalizados:**
330
+
331
+ ```bash
332
+ # Ver guía completa de customización
333
+ cat templates/CUSTOMIZATION_GUIDE.md
334
+
335
+ # O en GitHub
336
+ # https://github.com/pablorovito/claude-git-hooks/blob/main/templates/CUSTOMIZATION_GUIDE.md
337
+ ```
338
+
327
339
  **Ejemplo:**
328
340
 
329
341
  ```bash
@@ -556,16 +568,20 @@ claude-git-hooks/
556
568
  ├── bin/
557
569
  │ └── claude-hooks # CLI principal (ES6 modules)
558
570
  ├── lib/ # 🆕 Código Node.js modular
571
+ │ ├── config.js # Configuración centralizada con merge
559
572
  │ ├── hooks/
560
573
  │ │ ├── pre-commit.js # Hook de análisis (Node.js)
561
574
  │ │ └── prepare-commit-msg.js # Hook de mensajes (Node.js)
562
575
  │ └── utils/
563
576
  │ ├── logger.js # Sistema de logging centralizado
564
577
  │ ├── git-operations.js # Operaciones git abstractas
565
- │ ├── file-operations.js # I/O con filtro SKIP_ANALYSIS_LINE
578
+ │ ├── file-utils.js # Utilidades de sistema de archivos
566
579
  │ ├── claude-client.js # Cliente Claude CLI
567
580
  │ ├── prompt-builder.js # Constructor de prompts
568
- └── resolution-prompt.js # Generador de resolution prompts
581
+ ├── resolution-prompt.js # Generador de resolution prompts
582
+ │ ├── preset-loader.js # Cargador de presets
583
+ │ ├── installation-diagnostics.js # Diagnósticos de instalación
584
+ │ └── claude-diagnostics.js # Diagnósticos de errores Claude CLI
569
585
  ├── templates/
570
586
  │ ├── pre-commit # Bash wrapper (llama a lib/hooks/pre-commit.js)
571
587
  │ ├── prepare-commit-msg # Bash wrapper (llama a lib/hooks/prepare-commit-msg.js)
@@ -588,6 +604,68 @@ claude-git-hooks/
588
604
  └── .gitignore # Archivos ignorados por git
589
605
  ```
590
606
 
607
+ ### 🔌 Utility Modules Reference
608
+
609
+ **Purpose**: Reusable modules for extending claude-hooks or building similar tools
610
+
611
+ #### Core Utilities
612
+
613
+ | Module | Purpose | Key Exports | Usage Context |
614
+ |--------|---------|-------------|---------------|
615
+ | **`config.js`** | Centralized configuration management | `getConfig()`, `defaults` | Priority merging: defaults < user < preset |
616
+ | **`logger.js`** | Structured logging with debug support | `info()`, `warning()`, `error()`, `debug()` | Console output with context tracking |
617
+ | **`git-operations.js`** | Git command abstractions | `getRepoRoot()`, `getStagedFiles()`, `getDiff()` | Safe git operations with error handling |
618
+ | **`file-utils.js`** | File system operations | `ensureDir()`, `writeFile()` | Repo-root-relative path handling |
619
+ | **`claude-client.js`** | Claude CLI integration | `analyzeCode()`, `analyzeCodeParallel()` | Prompt execution with timeout/retry |
620
+ | **`prompt-builder.js`** | Template-based prompts | `buildAnalysisPrompt()`, `loadTemplate()` | Dynamic prompt construction from .md files |
621
+ | **`preset-loader.js`** | Preset system | `loadPreset()`, `listPresets()`, `loadTemplate()` | Metadata, config, template loading |
622
+ | **`resolution-prompt.js`** | Issue resolution prompts | `generateResolutionPrompt()` | AI-friendly error remediation prompts |
623
+ | **`installation-diagnostics.js`** | Installation error diagnostics | `formatError()`, `getInstallationDiagnostics()` | Installation error formatting with remediation |
624
+ | **`claude-diagnostics.js`** | Claude CLI error diagnostics | `detectClaudeError()`, `formatClaudeError()` | Rate limit, auth, network error detection |
625
+
626
+ #### Using Utilities in Other Claude Instances
627
+
628
+ **Example: Check installation health**
629
+ ```javascript
630
+ import { formatError } from './lib/utils/installation-diagnostics.js';
631
+
632
+ try {
633
+ // ... operation that may fail
634
+ } catch (error) {
635
+ console.error(formatError('Operation failed', ['Additional context line']));
636
+ process.exit(1);
637
+ }
638
+ ```
639
+
640
+ **Example: Load configuration**
641
+ ```javascript
642
+ import { getConfig } from './lib/config.js';
643
+
644
+ const config = await getConfig();
645
+ const maxFiles = config.analysis.maxFiles;
646
+ ```
647
+
648
+ **Example: Execute git operations**
649
+ ```javascript
650
+ import { getRepoRoot, getStagedFiles } from './lib/utils/git-operations.js';
651
+
652
+ const repoRoot = getRepoRoot();
653
+ const files = getStagedFiles({ extensions: ['.js', '.ts'] });
654
+ ```
655
+
656
+ **Example: Build custom prompts**
657
+ ```javascript
658
+ import { buildAnalysisPrompt } from './lib/utils/prompt-builder.js';
659
+
660
+ const prompt = await buildAnalysisPrompt({
661
+ templateName: 'CUSTOM_PROMPT.md',
662
+ files: filesData,
663
+ metadata: { REPO_NAME: 'my-repo' }
664
+ });
665
+ ```
666
+
667
+ **Note**: All utilities follow single-responsibility principle and include JSDoc documentation inline.
668
+
591
669
  ### Configuración del Entorno de Desarrollo
592
670
 
593
671
  ```bash
package/lib/config.js CHANGED
@@ -2,13 +2,14 @@
2
2
  * File: config.js
3
3
  * Purpose: Centralized configuration management
4
4
  *
5
- * Priority: .claude/config.json > defaults
5
+ * Priority: preset config > .claude/config.json > defaults
6
6
  *
7
7
  * Key features:
8
8
  * - Single source of truth for all configurable values
9
9
  * - No environment variables (except OS for platform detection)
10
10
  * - Preset-aware (allowedExtensions come from preset templates)
11
- * - Override via .claude/config.json per project
11
+ * - User config (.claude/config.json) provides general preferences per project
12
+ * - Preset config provides tech-stack-specific overrides (highest priority)
12
13
  */
13
14
 
14
15
  import fs from 'fs';
@@ -79,9 +80,9 @@ const defaults = {
79
80
 
80
81
  /**
81
82
  * Loads user configuration from .claude/config.json
82
- * Merges with defaults and preset config (preset -> user config takes priority)
83
+ * Merges with defaults and preset config (preset takes highest priority)
83
84
  *
84
- * Priority: defaults < preset config < user config
85
+ * Priority: defaults < user config < preset config
85
86
  *
86
87
  * @param {string} baseDir - Base directory to search for config (default: cwd)
87
88
  * @returns {Promise<Object>} Merged configuration
@@ -112,8 +113,8 @@ const loadUserConfig = async (baseDir = process.cwd()) => {
112
113
  }
113
114
  }
114
115
 
115
- // Merge: defaults < preset < user
116
- return deepMerge(deepMerge(defaults, presetConfig), userConfig);
116
+ // Merge: defaults < user < preset
117
+ return deepMerge(deepMerge(defaults, userConfig), presetConfig);
117
118
  };
118
119
 
119
120
  /**
@@ -312,7 +312,8 @@ const main = async () => {
312
312
 
313
313
  // Step 5: Analyze with Claude (parallel or single)
314
314
  let result;
315
-
315
+ // TODO: This can be refactored so no conditional is needed.
316
+ // Lists can have 0...N items, e.g. iterating a list of 1 element is akin to single execution.
316
317
  if (subagentsEnabled && filesData.length >= 3) {
317
318
  // Parallel execution: split files into batches
318
319
  logger.info(`Using parallel execution with batch size ${batchSize}`);
@@ -12,6 +12,7 @@
12
12
  * - child_process: For executing Claude CLI
13
13
  * - fs/promises: For debug file writing
14
14
  * - logger: Debug and error logging
15
+ * - claude-diagnostics: Error detection and formatting
15
16
  */
16
17
 
17
18
  import { spawn, execSync } from 'child_process';
@@ -20,6 +21,7 @@ import path from 'path';
20
21
  import os from 'os';
21
22
  import logger from './logger.js';
22
23
  import config from '../config.js';
24
+ import { detectClaudeError, formatClaudeError, ClaudeErrorType } from './claude-diagnostics.js';
23
25
 
24
26
  /**
25
27
  * Custom error for Claude client failures
@@ -141,18 +143,25 @@ const executeClaude = (prompt, { timeout = 120000 } = {}) => {
141
143
  );
142
144
  resolve(stdout);
143
145
  } else {
146
+ // Detect specific error type
147
+ const errorInfo = detectClaudeError(stdout, stderr, code);
148
+
144
149
  logger.error(
145
150
  'claude-client - executeClaude',
146
- `Claude CLI failed with exit code ${code}`,
147
- new ClaudeClientError('Claude CLI execution failed', {
151
+ `Claude CLI failed: ${errorInfo.type}`,
152
+ new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
148
153
  output: { stdout, stderr },
149
- context: { exitCode: code, duration }
154
+ context: { exitCode: code, duration, errorType: errorInfo.type }
150
155
  })
151
156
  );
152
157
 
153
- reject(new ClaudeClientError('Claude CLI execution failed', {
158
+ // Show formatted error to user
159
+ const formattedError = formatClaudeError(errorInfo);
160
+ console.error('\n' + formattedError + '\n');
161
+
162
+ reject(new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
154
163
  output: { stdout, stderr },
155
- context: { exitCode: code, duration }
164
+ context: { exitCode: code, duration, errorInfo }
156
165
  }));
157
166
  }
158
167
  });
@@ -0,0 +1,266 @@
1
+ /**
2
+ * File: claude-diagnostics.js
3
+ * Purpose: Reusable Claude CLI error diagnostics and formatting
4
+ *
5
+ * Key features:
6
+ * - Detects common Claude CLI error patterns
7
+ * - Provides actionable remediation steps
8
+ * - Extensible for future error types
9
+ *
10
+ * Usage:
11
+ * import { detectClaudeError, formatClaudeError } from './claude-diagnostics.js';
12
+ *
13
+ * const errorInfo = detectClaudeError(stdout, stderr, exitCode);
14
+ * if (errorInfo) {
15
+ * console.error(formatClaudeError(errorInfo));
16
+ * }
17
+ */
18
+
19
+ /**
20
+ * Error types that can be detected
21
+ */
22
+ export const ClaudeErrorType = {
23
+ RATE_LIMIT: 'RATE_LIMIT',
24
+ AUTH_FAILED: 'AUTH_FAILED',
25
+ TIMEOUT: 'TIMEOUT',
26
+ NETWORK: 'NETWORK',
27
+ INVALID_RESPONSE: 'INVALID_RESPONSE',
28
+ GENERIC: 'GENERIC'
29
+ };
30
+
31
+ /**
32
+ * Detects Claude CLI error type and extracts relevant information
33
+ * Why: Centralized error detection for consistent handling
34
+ *
35
+ * Future enhancements:
36
+ * - Network connectivity errors
37
+ * - Authentication expiration
38
+ * - Model availability errors
39
+ * - Token limit exceeded errors
40
+ *
41
+ * @param {string} stdout - Claude CLI stdout
42
+ * @param {string} stderr - Claude CLI stderr
43
+ * @param {number} exitCode - Process exit code
44
+ * @returns {Object|null} Error information or null if no specific error detected
45
+ */
46
+ export const detectClaudeError = (stdout = '', stderr = '', exitCode = 1) => {
47
+ // 1. Rate limit detection
48
+ const rateLimitMatch = stdout.match(/Claude AI usage limit reached\|(\d+)/);
49
+ if (rateLimitMatch) {
50
+ const resetTimestamp = parseInt(rateLimitMatch[1], 10);
51
+ const resetDate = new Date(resetTimestamp * 1000);
52
+ const now = new Date();
53
+ const minutesUntilReset = Math.ceil((resetDate - now) / 60000);
54
+
55
+ return {
56
+ type: ClaudeErrorType.RATE_LIMIT,
57
+ exitCode,
58
+ resetTimestamp,
59
+ resetDate,
60
+ minutesUntilReset
61
+ };
62
+ }
63
+
64
+ // 2. Authentication failure detection
65
+ if (stdout.includes('not authenticated') || stderr.includes('not authenticated') ||
66
+ stdout.includes('authentication failed') || stderr.includes('authentication failed')) {
67
+ return {
68
+ type: ClaudeErrorType.AUTH_FAILED,
69
+ exitCode
70
+ };
71
+ }
72
+
73
+ // 3. Network errors
74
+ if (stderr.includes('ENOTFOUND') || stderr.includes('ECONNREFUSED') ||
75
+ stderr.includes('network error') || stderr.includes('connection refused')) {
76
+ return {
77
+ type: ClaudeErrorType.NETWORK,
78
+ exitCode
79
+ };
80
+ }
81
+
82
+ // 4. Invalid response (JSON parsing errors)
83
+ if (stdout.includes('SyntaxError') || stdout.includes('Unexpected token')) {
84
+ return {
85
+ type: ClaudeErrorType.INVALID_RESPONSE,
86
+ exitCode
87
+ };
88
+ }
89
+
90
+ // 5. Generic error
91
+ return {
92
+ type: ClaudeErrorType.GENERIC,
93
+ exitCode,
94
+ stdout: stdout.substring(0, 200), // First 200 chars
95
+ stderr: stderr.substring(0, 200)
96
+ };
97
+ };
98
+
99
+ /**
100
+ * Formats Claude error message with diagnostics and remediation steps
101
+ * Why: Provides consistent, actionable error messages
102
+ *
103
+ * @param {Object} errorInfo - Output from detectClaudeError()
104
+ * @returns {string} Formatted error message
105
+ */
106
+ export const formatClaudeError = (errorInfo) => {
107
+ const lines = [];
108
+
109
+ switch (errorInfo.type) {
110
+ case ClaudeErrorType.RATE_LIMIT:
111
+ return formatRateLimitError(errorInfo);
112
+
113
+ case ClaudeErrorType.AUTH_FAILED:
114
+ return formatAuthError(errorInfo);
115
+
116
+ case ClaudeErrorType.NETWORK:
117
+ return formatNetworkError(errorInfo);
118
+
119
+ case ClaudeErrorType.INVALID_RESPONSE:
120
+ return formatInvalidResponseError(errorInfo);
121
+
122
+ case ClaudeErrorType.GENERIC:
123
+ default:
124
+ return formatGenericError(errorInfo);
125
+ }
126
+ };
127
+
128
+ /**
129
+ * Format rate limit error
130
+ */
131
+ const formatRateLimitError = ({ resetDate, minutesUntilReset }) => {
132
+ const lines = [];
133
+
134
+ lines.push('❌ Claude API usage limit reached');
135
+ lines.push('');
136
+ lines.push('Rate limit details:');
137
+ lines.push(` Reset time: ${resetDate.toLocaleString()}`);
138
+
139
+ if (minutesUntilReset > 60) {
140
+ const hours = Math.ceil(minutesUntilReset / 60);
141
+ lines.push(` Time until reset: ~${hours} hour${hours > 1 ? 's' : ''}`);
142
+ } else if (minutesUntilReset > 0) {
143
+ lines.push(` Time until reset: ~${minutesUntilReset} minute${minutesUntilReset !== 1 ? 's' : ''}`);
144
+ } else {
145
+ lines.push(' Limit should be reset now');
146
+ }
147
+
148
+ lines.push('');
149
+ lines.push('Options:');
150
+ lines.push(' 1. Wait for rate limit to reset');
151
+ lines.push(' 2. Skip analysis for now:');
152
+ lines.push(' git commit --no-verify -m "your message"');
153
+ lines.push(' 3. Reduce API usage by switching to haiku model:');
154
+ lines.push(' Edit .claude/config.json:');
155
+ lines.push(' { "subagents": { "model": "haiku" } }');
156
+
157
+ return lines.join('\n');
158
+ };
159
+
160
+ /**
161
+ * Format authentication error
162
+ */
163
+ const formatAuthError = ({ exitCode }) => {
164
+ const lines = [];
165
+
166
+ lines.push('❌ Claude CLI authentication failed');
167
+ lines.push('');
168
+ lines.push('Possible causes:');
169
+ lines.push(' 1. Not logged in to Claude CLI');
170
+ lines.push(' 2. Authentication token expired');
171
+ lines.push(' 3. Invalid API credentials');
172
+ lines.push('');
173
+ lines.push('Solution:');
174
+ lines.push(' claude auth login');
175
+ lines.push('');
176
+ lines.push('Then try your commit again.');
177
+
178
+ return lines.join('\n');
179
+ };
180
+
181
+ /**
182
+ * Format network error
183
+ */
184
+ const formatNetworkError = ({ exitCode }) => {
185
+ const lines = [];
186
+
187
+ lines.push('❌ Network error connecting to Claude API');
188
+ lines.push('');
189
+ lines.push('Possible causes:');
190
+ lines.push(' 1. No internet connection');
191
+ lines.push(' 2. Firewall blocking Claude API');
192
+ lines.push(' 3. Claude API temporarily unavailable');
193
+ lines.push('');
194
+ lines.push('Solutions:');
195
+ lines.push(' 1. Check your internet connection');
196
+ lines.push(' 2. Verify firewall settings');
197
+ lines.push(' 3. Try again in a few moments');
198
+ lines.push(' 4. Skip analysis: git commit --no-verify -m "message"');
199
+
200
+ return lines.join('\n');
201
+ };
202
+
203
+ /**
204
+ * Format invalid response error
205
+ */
206
+ const formatInvalidResponseError = ({ exitCode }) => {
207
+ const lines = [];
208
+
209
+ lines.push('❌ Claude returned invalid response');
210
+ lines.push('');
211
+ lines.push('This usually means:');
212
+ lines.push(' - Claude did not return valid JSON');
213
+ lines.push(' - Response format does not match expected schema');
214
+ lines.push('');
215
+ lines.push('Solutions:');
216
+ lines.push(' 1. Check debug output: .claude/out/debug-claude-response.json');
217
+ lines.push(' 2. Try again (may be temporary issue)');
218
+ lines.push(' 3. Skip analysis: git commit --no-verify -m "message"');
219
+
220
+ return lines.join('\n');
221
+ };
222
+
223
+ /**
224
+ * Format generic error
225
+ */
226
+ const formatGenericError = ({ exitCode, stdout, stderr }) => {
227
+ const lines = [];
228
+
229
+ lines.push('❌ Claude CLI execution failed');
230
+ lines.push('');
231
+ lines.push(`Exit code: ${exitCode}`);
232
+
233
+ if (stdout && stdout.trim()) {
234
+ lines.push('');
235
+ lines.push('Output:');
236
+ lines.push(` ${stdout.trim()}`);
237
+ }
238
+
239
+ if (stderr && stderr.trim()) {
240
+ lines.push('');
241
+ lines.push('Error:');
242
+ lines.push(` ${stderr.trim()}`);
243
+ }
244
+
245
+ lines.push('');
246
+ lines.push('Solutions:');
247
+ lines.push(' 1. Verify Claude CLI is installed: claude --version');
248
+ lines.push(' 2. Check authentication: claude auth login');
249
+ lines.push(' 3. Enable debug mode in .claude/config.json:');
250
+ lines.push(' { "system": { "debug": true } }');
251
+ lines.push(' 4. Skip analysis: git commit --no-verify -m "message"');
252
+
253
+ return lines.join('\n');
254
+ };
255
+
256
+ /**
257
+ * Checks if Claude CLI error is recoverable
258
+ * Why: Some errors (rate limit) should wait, others (auth) should fail immediately
259
+ *
260
+ * @param {Object} errorInfo - Output from detectClaudeError()
261
+ * @returns {boolean} True if error might resolve with retry
262
+ */
263
+ export const isRecoverableError = (errorInfo) => {
264
+ return errorInfo.type === ClaudeErrorType.RATE_LIMIT ||
265
+ errorInfo.type === ClaudeErrorType.NETWORK;
266
+ };