claude-git-hooks 2.18.0 → 2.19.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 +38 -0
- package/CLAUDE.md +12 -8
- package/README.md +2 -1
- package/bin/claude-hooks +75 -89
- package/lib/cli-metadata.js +301 -0
- package/lib/commands/analyze-diff.js +12 -10
- package/lib/commands/analyze.js +9 -5
- package/lib/commands/bump-version.js +66 -43
- package/lib/commands/create-pr.js +71 -34
- package/lib/commands/debug.js +4 -7
- package/lib/commands/generate-changelog.js +11 -4
- package/lib/commands/help.js +47 -27
- package/lib/commands/helpers.js +66 -43
- package/lib/commands/hooks.js +15 -13
- package/lib/commands/install.js +546 -39
- package/lib/commands/migrate-config.js +8 -11
- package/lib/commands/presets.js +6 -13
- package/lib/commands/setup-github.js +12 -3
- package/lib/commands/telemetry-cmd.js +8 -6
- package/lib/commands/update.js +1 -2
- package/lib/config.js +36 -31
- package/lib/hooks/pre-commit.js +34 -54
- package/lib/hooks/prepare-commit-msg.js +39 -58
- package/lib/utils/analysis-engine.js +28 -21
- package/lib/utils/changelog-generator.js +162 -34
- package/lib/utils/claude-client.js +438 -377
- package/lib/utils/claude-diagnostics.js +20 -10
- package/lib/utils/file-operations.js +51 -79
- package/lib/utils/file-utils.js +46 -9
- package/lib/utils/git-operations.js +140 -123
- package/lib/utils/git-tag-manager.js +24 -23
- package/lib/utils/github-api.js +85 -61
- package/lib/utils/github-client.js +12 -14
- package/lib/utils/installation-diagnostics.js +4 -4
- package/lib/utils/interactive-ui.js +29 -17
- package/lib/utils/logger.js +4 -1
- package/lib/utils/pr-metadata-engine.js +67 -33
- package/lib/utils/preset-loader.js +20 -62
- package/lib/utils/prompt-builder.js +50 -55
- package/lib/utils/resolution-prompt.js +33 -44
- package/lib/utils/sanitize.js +20 -19
- package/lib/utils/task-id.js +27 -40
- package/lib/utils/telemetry.js +29 -17
- package/lib/utils/version-manager.js +173 -126
- package/lib/utils/which-command.js +23 -12
- package/package.json +69 -69
|
@@ -21,7 +21,12 @@ import path from 'path';
|
|
|
21
21
|
import os from 'os';
|
|
22
22
|
import logger from './logger.js';
|
|
23
23
|
import config from '../config.js';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
detectClaudeError,
|
|
26
|
+
formatClaudeError,
|
|
27
|
+
ClaudeErrorType,
|
|
28
|
+
isRecoverableError
|
|
29
|
+
} from './claude-diagnostics.js';
|
|
25
30
|
import { which } from './which-command.js';
|
|
26
31
|
import { recordJsonParseFailure, recordBatchSuccess, rotateTelemetry } from './telemetry.js';
|
|
27
32
|
|
|
@@ -79,7 +84,9 @@ const getClaudeCommand = () => {
|
|
|
79
84
|
// Node 24: Use which() instead of execSync to get absolute path
|
|
80
85
|
const nativePath = which('claude');
|
|
81
86
|
if (nativePath) {
|
|
82
|
-
logger.debug('claude-client - getClaudeCommand', 'Using native Windows Claude CLI', {
|
|
87
|
+
logger.debug('claude-client - getClaudeCommand', 'Using native Windows Claude CLI', {
|
|
88
|
+
path: nativePath
|
|
89
|
+
});
|
|
83
90
|
return { command: nativePath, args: [] };
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -87,15 +94,18 @@ const getClaudeCommand = () => {
|
|
|
87
94
|
|
|
88
95
|
// Fallback to WSL
|
|
89
96
|
if (!isWSLAvailable()) {
|
|
90
|
-
throw new ClaudeClientError(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
throw new ClaudeClientError(
|
|
98
|
+
'Claude CLI not found. Install Claude CLI natively on Windows or via WSL',
|
|
99
|
+
{
|
|
100
|
+
context: {
|
|
101
|
+
platform: 'Windows',
|
|
102
|
+
suggestions: [
|
|
103
|
+
'Native Windows: npm install -g @anthropic-ai/claude-cli',
|
|
104
|
+
'WSL: wsl --install, then install Claude in WSL'
|
|
105
|
+
]
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
|
-
|
|
108
|
+
);
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
// Check if Claude is available in WSL
|
|
@@ -106,8 +116,13 @@ const getClaudeCommand = () => {
|
|
|
106
116
|
// Verify Claude exists in WSL
|
|
107
117
|
// Increased timeout from 5s to 15s to handle system load better
|
|
108
118
|
const wslCheckTimeout = config.system.wslCheckTimeout || 15000;
|
|
109
|
-
execSync(`"${wslPath}" claude --version`, {
|
|
110
|
-
|
|
119
|
+
execSync(`"${wslPath}" claude --version`, {
|
|
120
|
+
stdio: 'ignore',
|
|
121
|
+
timeout: wslCheckTimeout
|
|
122
|
+
});
|
|
123
|
+
logger.debug('claude-client - getClaudeCommand', 'Using WSL Claude CLI', {
|
|
124
|
+
wslPath
|
|
125
|
+
});
|
|
111
126
|
return { command: wslPath, args: ['claude'] };
|
|
112
127
|
} catch (wslError) {
|
|
113
128
|
// Differentiate error types for accurate user feedback
|
|
@@ -116,15 +131,19 @@ const getClaudeCommand = () => {
|
|
|
116
131
|
|
|
117
132
|
// Timeout: Transient system load issue
|
|
118
133
|
if (errorMsg.includes('ETIMEDOUT')) {
|
|
119
|
-
throw new ClaudeClientError(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
throw new ClaudeClientError(
|
|
135
|
+
'Timeout connecting to WSL - system under heavy load',
|
|
136
|
+
{
|
|
137
|
+
context: {
|
|
138
|
+
platform: 'Windows',
|
|
139
|
+
wslPath,
|
|
140
|
+
error: 'ETIMEDOUT',
|
|
141
|
+
timeoutValue: wslCheckTimeout,
|
|
142
|
+
suggestion:
|
|
143
|
+
'System is busy. Wait a moment and try again, or skip analysis: git commit --no-verify'
|
|
144
|
+
}
|
|
126
145
|
}
|
|
127
|
-
|
|
146
|
+
);
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
// Not found: Claude CLI missing in WSL
|
|
@@ -134,7 +153,8 @@ const getClaudeCommand = () => {
|
|
|
134
153
|
platform: 'Windows',
|
|
135
154
|
wslPath,
|
|
136
155
|
error: 'ENOENT',
|
|
137
|
-
suggestion:
|
|
156
|
+
suggestion:
|
|
157
|
+
'Install Claude in WSL: wsl -e bash -c "npm install -g @anthropic-ai/claude-cli"'
|
|
138
158
|
}
|
|
139
159
|
});
|
|
140
160
|
}
|
|
@@ -145,7 +165,8 @@ const getClaudeCommand = () => {
|
|
|
145
165
|
platform: 'Windows',
|
|
146
166
|
wslPath,
|
|
147
167
|
error: errorMsg,
|
|
148
|
-
suggestion:
|
|
168
|
+
suggestion:
|
|
169
|
+
'Check WSL is functioning: wsl --version, or skip analysis: git commit --no-verify'
|
|
149
170
|
}
|
|
150
171
|
});
|
|
151
172
|
}
|
|
@@ -170,7 +191,9 @@ const getClaudeCommand = () => {
|
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
// Fallback to 'claude' if which fails (will error later if not found)
|
|
173
|
-
logger.debug('claude-client - getClaudeCommand', 'which() failed, using fallback', {
|
|
194
|
+
logger.debug('claude-client - getClaudeCommand', 'which() failed, using fallback', {
|
|
195
|
+
command: 'claude'
|
|
196
|
+
});
|
|
174
197
|
return { command: 'claude', args: [] };
|
|
175
198
|
};
|
|
176
199
|
|
|
@@ -185,259 +208,277 @@ const getClaudeCommand = () => {
|
|
|
185
208
|
* @returns {Promise<string>} Claude's response
|
|
186
209
|
* @throws {ClaudeClientError} If execution fails or times out
|
|
187
210
|
*/
|
|
188
|
-
const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
212
|
+
new Promise((resolve, reject) => {
|
|
213
|
+
// Get platform-specific command
|
|
214
|
+
const { command, args } = getClaudeCommand();
|
|
215
|
+
|
|
216
|
+
// Add allowed tools if specified (for MCP tools)
|
|
217
|
+
const finalArgs = [...args];
|
|
218
|
+
if (allowedTools.length > 0) {
|
|
219
|
+
// Format: --allowedTools "mcp__github__create_pull_request,mcp__github__get_file_contents"
|
|
220
|
+
finalArgs.push('--allowedTools', allowedTools.join(','));
|
|
221
|
+
}
|
|
198
222
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
// CRITICAL FIX: Windows .cmd/.bat file handling
|
|
224
|
+
// Why: spawn() cannot execute .cmd/.bat files directly on Windows (ENOENT error)
|
|
225
|
+
// Solution: Wrap with cmd.exe /c when command ends with .cmd or .bat
|
|
226
|
+
// Impact: Only affects Windows npm-installed CLI tools, no impact on other platforms
|
|
227
|
+
let spawnCommand = command;
|
|
228
|
+
let spawnArgs = finalArgs;
|
|
229
|
+
|
|
230
|
+
if (isWindows() && (command.endsWith('.cmd') || command.endsWith('.bat'))) {
|
|
231
|
+
logger.debug('claude-client - executeClaude', 'Wrapping .cmd/.bat with cmd.exe', {
|
|
232
|
+
originalCommand: command,
|
|
233
|
+
originalArgs: finalArgs
|
|
234
|
+
});
|
|
235
|
+
spawnCommand = 'cmd.exe';
|
|
236
|
+
spawnArgs = ['/c', command, ...finalArgs];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const fullCommand =
|
|
240
|
+
spawnArgs.length > 0 ? `${spawnCommand} ${spawnArgs.join(' ')}` : spawnCommand;
|
|
241
|
+
|
|
242
|
+
logger.debug('claude-client - executeClaude', 'Executing Claude CLI', {
|
|
243
|
+
promptLength: prompt.length,
|
|
244
|
+
timeout,
|
|
245
|
+
command: fullCommand,
|
|
246
|
+
isWindows: isWindows(),
|
|
247
|
+
allowedTools
|
|
210
248
|
});
|
|
211
|
-
spawnCommand = 'cmd.exe';
|
|
212
|
-
spawnArgs = ['/c', command, ...finalArgs];
|
|
213
|
-
}
|
|
214
249
|
|
|
215
|
-
|
|
250
|
+
const startTime = Date.now();
|
|
216
251
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
252
|
+
// Why: Use spawn instead of exec to handle large prompts and responses
|
|
253
|
+
// spawn streams data, exec buffers everything in memory
|
|
254
|
+
// Node 24 Fix: Removed shell: true to avoid DEP0190 deprecation warning
|
|
255
|
+
// We now use absolute paths from which(), so shell is not needed
|
|
256
|
+
// Windows .cmd/.bat fix: Wrapped with cmd.exe /c (see above)
|
|
257
|
+
const claude = spawn(spawnCommand, spawnArgs, {
|
|
258
|
+
stdio: ['pipe', 'pipe', 'pipe'] // stdin, stdout, stderr
|
|
259
|
+
});
|
|
222
260
|
|
|
223
|
-
|
|
261
|
+
let stdout = '';
|
|
262
|
+
let stderr = '';
|
|
224
263
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Windows .cmd/.bat fix: Wrapped with cmd.exe /c (see above)
|
|
230
|
-
const claude = spawn(spawnCommand, spawnArgs, {
|
|
231
|
-
stdio: ['pipe', 'pipe', 'pipe'] // stdin, stdout, stderr
|
|
232
|
-
});
|
|
264
|
+
// Collect stdout
|
|
265
|
+
claude.stdout.on('data', (data) => {
|
|
266
|
+
stdout += data.toString();
|
|
267
|
+
});
|
|
233
268
|
|
|
234
|
-
|
|
235
|
-
|
|
269
|
+
// Collect stderr
|
|
270
|
+
claude.stderr.on('data', (data) => {
|
|
271
|
+
stderr += data.toString();
|
|
272
|
+
});
|
|
236
273
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
274
|
+
// Handle process completion
|
|
275
|
+
claude.on('close', (code) => {
|
|
276
|
+
clearTimeout(timeoutId);
|
|
277
|
+
const elapsedTime = Date.now() - startTime;
|
|
278
|
+
|
|
279
|
+
// Check for "Execution error" even when exit code is 0
|
|
280
|
+
// Why: Claude CLI sometimes returns "Execution error" with exit code 0
|
|
281
|
+
// This occurs during API rate limiting or temporary backend issues
|
|
282
|
+
// IMPORTANT: Only check for EXACT match to avoid false positives
|
|
283
|
+
if (stdout.trim() === 'Execution error') {
|
|
284
|
+
const errorInfo = detectClaudeError(stdout, stderr, code);
|
|
285
|
+
|
|
286
|
+
logger.error(
|
|
287
|
+
'claude-client - executeClaude',
|
|
288
|
+
`Claude CLI returned execution error (exit ${code})`,
|
|
289
|
+
new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
290
|
+
output: { stdout, stderr },
|
|
291
|
+
context: {
|
|
292
|
+
exitCode: code,
|
|
293
|
+
elapsedTime,
|
|
294
|
+
timeoutValue: timeout,
|
|
295
|
+
errorType: errorInfo.type
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
);
|
|
241
299
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
// Merge timing info into errorInfo for formatting
|
|
301
|
+
const errorInfoWithTiming = {
|
|
302
|
+
...errorInfo,
|
|
303
|
+
elapsedTime,
|
|
304
|
+
timeoutValue: timeout
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Show formatted error to user
|
|
308
|
+
const formattedError = formatClaudeError(errorInfoWithTiming);
|
|
309
|
+
console.error(`\n${formattedError}\n`);
|
|
310
|
+
|
|
311
|
+
reject(
|
|
312
|
+
new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
313
|
+
output: { stdout, stderr },
|
|
314
|
+
context: {
|
|
315
|
+
exitCode: code,
|
|
316
|
+
elapsedTime,
|
|
317
|
+
timeoutValue: timeout,
|
|
318
|
+
errorInfo
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (code === 0) {
|
|
326
|
+
logger.debug('claude-client - executeClaude', 'Claude CLI execution successful', {
|
|
327
|
+
elapsedTime,
|
|
328
|
+
outputLength: stdout.length
|
|
329
|
+
});
|
|
330
|
+
resolve(stdout);
|
|
331
|
+
} else {
|
|
332
|
+
// Detect specific error type
|
|
333
|
+
const errorInfo = detectClaudeError(stdout, stderr, code);
|
|
334
|
+
|
|
335
|
+
logger.error(
|
|
336
|
+
'claude-client - executeClaude',
|
|
337
|
+
`Claude CLI failed: ${errorInfo.type}`,
|
|
338
|
+
new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
339
|
+
output: { stdout, stderr },
|
|
340
|
+
context: {
|
|
341
|
+
exitCode: code,
|
|
342
|
+
elapsedTime,
|
|
343
|
+
timeoutValue: timeout,
|
|
344
|
+
errorType: errorInfo.type
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
);
|
|
246
348
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
349
|
+
// Merge timing info into errorInfo for formatting
|
|
350
|
+
const errorInfoWithTiming = {
|
|
351
|
+
...errorInfo,
|
|
352
|
+
elapsedTime,
|
|
353
|
+
timeoutValue: timeout
|
|
354
|
+
};
|
|
251
355
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
356
|
+
// Show formatted error to user
|
|
357
|
+
const formattedError = formatClaudeError(errorInfoWithTiming);
|
|
358
|
+
console.error(`\n${formattedError}\n`);
|
|
359
|
+
|
|
360
|
+
reject(
|
|
361
|
+
new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
362
|
+
output: { stdout, stderr },
|
|
363
|
+
context: {
|
|
364
|
+
exitCode: code,
|
|
365
|
+
elapsedTime,
|
|
366
|
+
timeoutValue: timeout,
|
|
367
|
+
errorInfo
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Handle errors
|
|
375
|
+
claude.on('error', (error) => {
|
|
376
|
+
clearTimeout(timeoutId);
|
|
377
|
+
const elapsedTime = Date.now() - startTime;
|
|
258
378
|
|
|
259
379
|
logger.error(
|
|
260
380
|
'claude-client - executeClaude',
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
381
|
+
'Failed to spawn Claude CLI process',
|
|
382
|
+
error
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
reject(
|
|
386
|
+
new ClaudeClientError('Failed to spawn Claude CLI', {
|
|
387
|
+
cause: error,
|
|
264
388
|
context: {
|
|
265
|
-
|
|
389
|
+
command,
|
|
390
|
+
args,
|
|
266
391
|
elapsedTime,
|
|
267
|
-
timeoutValue: timeout
|
|
268
|
-
errorType: errorInfo.type
|
|
392
|
+
timeoutValue: timeout
|
|
269
393
|
}
|
|
270
394
|
})
|
|
271
395
|
);
|
|
396
|
+
});
|
|
272
397
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
timeoutValue: timeout
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
// Show formatted error to user
|
|
281
|
-
const formattedError = formatClaudeError(errorInfoWithTiming);
|
|
282
|
-
console.error(`\n${ formattedError }\n`);
|
|
283
|
-
|
|
284
|
-
reject(new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
285
|
-
output: { stdout, stderr },
|
|
286
|
-
context: {
|
|
287
|
-
exitCode: code,
|
|
288
|
-
elapsedTime,
|
|
289
|
-
timeoutValue: timeout,
|
|
290
|
-
errorInfo
|
|
291
|
-
}
|
|
292
|
-
}));
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
398
|
+
// Set up timeout
|
|
399
|
+
const timeoutId = setTimeout(() => {
|
|
400
|
+
const elapsedTime = Date.now() - startTime;
|
|
401
|
+
claude.kill();
|
|
295
402
|
|
|
296
|
-
|
|
297
|
-
logger.debug(
|
|
403
|
+
logger.error(
|
|
298
404
|
'claude-client - executeClaude',
|
|
299
|
-
'Claude CLI execution
|
|
300
|
-
|
|
405
|
+
'Claude CLI execution timed out',
|
|
406
|
+
new ClaudeClientError('Claude CLI timeout', {
|
|
407
|
+
context: {
|
|
408
|
+
elapsedTime,
|
|
409
|
+
timeoutValue: timeout
|
|
410
|
+
}
|
|
411
|
+
})
|
|
301
412
|
);
|
|
302
|
-
resolve(stdout);
|
|
303
|
-
} else {
|
|
304
|
-
// Detect specific error type
|
|
305
|
-
const errorInfo = detectClaudeError(stdout, stderr, code);
|
|
306
413
|
|
|
307
|
-
|
|
308
|
-
'
|
|
309
|
-
`Claude CLI failed: ${errorInfo.type}`,
|
|
310
|
-
new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
311
|
-
output: { stdout, stderr },
|
|
414
|
+
reject(
|
|
415
|
+
new ClaudeClientError('Claude CLI execution timed out', {
|
|
312
416
|
context: {
|
|
313
|
-
exitCode: code,
|
|
314
417
|
elapsedTime,
|
|
315
418
|
timeoutValue: timeout,
|
|
316
|
-
|
|
419
|
+
errorInfo: {
|
|
420
|
+
type: ClaudeErrorType.TIMEOUT,
|
|
421
|
+
elapsedTime,
|
|
422
|
+
timeoutValue: timeout
|
|
423
|
+
}
|
|
317
424
|
}
|
|
318
425
|
})
|
|
319
426
|
);
|
|
427
|
+
}, timeout);
|
|
320
428
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
...errorInfo,
|
|
324
|
-
elapsedTime,
|
|
325
|
-
timeoutValue: timeout
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// Show formatted error to user
|
|
329
|
-
const formattedError = formatClaudeError(errorInfoWithTiming);
|
|
330
|
-
console.error(`\n${ formattedError }\n`);
|
|
331
|
-
|
|
332
|
-
reject(new ClaudeClientError(`Claude CLI error: ${errorInfo.type}`, {
|
|
333
|
-
output: { stdout, stderr },
|
|
334
|
-
context: {
|
|
335
|
-
exitCode: code,
|
|
336
|
-
elapsedTime,
|
|
337
|
-
timeoutValue: timeout,
|
|
338
|
-
errorInfo
|
|
339
|
-
}
|
|
340
|
-
}));
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Handle errors
|
|
345
|
-
claude.on('error', (error) => {
|
|
346
|
-
clearTimeout(timeoutId);
|
|
347
|
-
const elapsedTime = Date.now() - startTime;
|
|
348
|
-
|
|
349
|
-
logger.error(
|
|
350
|
-
'claude-client - executeClaude',
|
|
351
|
-
'Failed to spawn Claude CLI process',
|
|
352
|
-
error
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
reject(new ClaudeClientError('Failed to spawn Claude CLI', {
|
|
356
|
-
cause: error,
|
|
357
|
-
context: {
|
|
358
|
-
command,
|
|
359
|
-
args,
|
|
360
|
-
elapsedTime,
|
|
361
|
-
timeoutValue: timeout
|
|
362
|
-
}
|
|
363
|
-
}));
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Set up timeout
|
|
367
|
-
const timeoutId = setTimeout(() => {
|
|
368
|
-
const elapsedTime = Date.now() - startTime;
|
|
369
|
-
claude.kill();
|
|
429
|
+
// Write prompt to stdin
|
|
430
|
+
// Why: Claude CLI reads prompt from stdin, not command arguments
|
|
370
431
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
elapsedTime,
|
|
385
|
-
timeoutValue: timeout,
|
|
386
|
-
errorInfo: {
|
|
387
|
-
type: ClaudeErrorType.TIMEOUT,
|
|
388
|
-
elapsedTime,
|
|
389
|
-
timeoutValue: timeout
|
|
432
|
+
// Handle stdin errors (e.g., EOF when process terminates unexpectedly)
|
|
433
|
+
// Why: write() failures can emit 'error' events asynchronously
|
|
434
|
+
// Common in parallel execution with large prompts
|
|
435
|
+
claude.stdin.on('error', (error) => {
|
|
436
|
+
clearTimeout(timeoutId);
|
|
437
|
+
logger.error(
|
|
438
|
+
'claude-client - executeClaude',
|
|
439
|
+
'stdin stream error (process may have terminated early)',
|
|
440
|
+
{
|
|
441
|
+
error: error.message,
|
|
442
|
+
code: error.code,
|
|
443
|
+
promptLength: prompt.length,
|
|
444
|
+
duration: Date.now() - startTime
|
|
390
445
|
}
|
|
391
|
-
|
|
392
|
-
}));
|
|
393
|
-
}, timeout);
|
|
394
|
-
|
|
395
|
-
// Write prompt to stdin
|
|
396
|
-
// Why: Claude CLI reads prompt from stdin, not command arguments
|
|
397
|
-
|
|
398
|
-
// Handle stdin errors (e.g., EOF when process terminates unexpectedly)
|
|
399
|
-
// Why: write() failures can emit 'error' events asynchronously
|
|
400
|
-
// Common in parallel execution with large prompts
|
|
401
|
-
claude.stdin.on('error', (error) => {
|
|
402
|
-
clearTimeout(timeoutId);
|
|
403
|
-
logger.error(
|
|
404
|
-
'claude-client - executeClaude',
|
|
405
|
-
'stdin stream error (process may have terminated early)',
|
|
406
|
-
{
|
|
407
|
-
error: error.message,
|
|
408
|
-
code: error.code,
|
|
409
|
-
promptLength: prompt.length,
|
|
410
|
-
duration: Date.now() - startTime
|
|
411
|
-
}
|
|
412
|
-
);
|
|
446
|
+
);
|
|
413
447
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
448
|
+
reject(
|
|
449
|
+
new ClaudeClientError(
|
|
450
|
+
'Failed to write to Claude stdin - process terminated unexpectedly',
|
|
451
|
+
{
|
|
452
|
+
cause: error,
|
|
453
|
+
context: {
|
|
454
|
+
promptLength: prompt.length,
|
|
455
|
+
errorCode: error.code,
|
|
456
|
+
errorMessage: error.message,
|
|
457
|
+
suggestion: 'Try reducing batch size or number of files per commit'
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
);
|
|
462
|
+
});
|
|
424
463
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
464
|
+
try {
|
|
465
|
+
claude.stdin.write(prompt);
|
|
466
|
+
claude.stdin.end();
|
|
467
|
+
} catch (error) {
|
|
468
|
+
clearTimeout(timeoutId);
|
|
469
|
+
logger.error(
|
|
470
|
+
'claude-client - executeClaude',
|
|
471
|
+
'Failed to write prompt to Claude CLI stdin (synchronous error)',
|
|
472
|
+
error
|
|
473
|
+
);
|
|
435
474
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
475
|
+
reject(
|
|
476
|
+
new ClaudeClientError('Failed to write prompt', {
|
|
477
|
+
cause: error
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
441
482
|
|
|
442
483
|
/**
|
|
443
484
|
* Executes Claude CLI fully interactively
|
|
@@ -448,85 +489,94 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
448
489
|
* @returns {Promise<string>} - Returns 'interactive' since we can't capture output
|
|
449
490
|
* @throws {ClaudeClientError} If execution fails
|
|
450
491
|
*/
|
|
451
|
-
const executeClaudeInteractive = (prompt, { timeout = 300000 } = {}) =>
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
492
|
+
const executeClaudeInteractive = (prompt, { timeout = 300000 } = {}) =>
|
|
493
|
+
new Promise((resolve, reject) => {
|
|
494
|
+
const { command, args } = getClaudeCommand();
|
|
495
|
+
const { spawnSync } = require('child_process');
|
|
496
|
+
const fs = require('fs');
|
|
497
|
+
const path = require('path');
|
|
498
|
+
const os = require('os');
|
|
499
|
+
|
|
500
|
+
// Save prompt to temp file that Claude can read
|
|
501
|
+
const tempDir = os.tmpdir();
|
|
502
|
+
const tempFile = path.join(tempDir, 'claude-pr-instructions.md');
|
|
457
503
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
504
|
+
try {
|
|
505
|
+
fs.writeFileSync(tempFile, prompt);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
logger.error(
|
|
508
|
+
'claude-client - executeClaudeInteractive',
|
|
509
|
+
'Failed to write temp file',
|
|
510
|
+
err
|
|
511
|
+
);
|
|
512
|
+
reject(new ClaudeClientError('Failed to write prompt file', { cause: err }));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
461
515
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
516
|
+
logger.debug(
|
|
517
|
+
'claude-client - executeClaudeInteractive',
|
|
518
|
+
'Starting interactive Claude session',
|
|
519
|
+
{ promptLength: prompt.length, tempFile, command, args }
|
|
520
|
+
);
|
|
469
521
|
|
|
470
|
-
|
|
471
|
-
'
|
|
472
|
-
'
|
|
473
|
-
|
|
474
|
-
|
|
522
|
+
console.log('');
|
|
523
|
+
console.log('╔══════════════════════════════════════════════════════════════════╗');
|
|
524
|
+
console.log('║ 🤖 INTERACTIVE CLAUDE SESSION ║');
|
|
525
|
+
console.log('╠══════════════════════════════════════════════════════════════════╣');
|
|
526
|
+
console.log('║ ║');
|
|
527
|
+
console.log('║ Instructions saved to: ║');
|
|
528
|
+
console.log(`║ ${tempFile.padEnd(62)}║`);
|
|
529
|
+
console.log('║ ║');
|
|
530
|
+
console.log('║ When Claude starts, tell it: ║');
|
|
531
|
+
console.log('║ "Read and execute the instructions in the file above" ║');
|
|
532
|
+
console.log('║ ║');
|
|
533
|
+
console.log('║ • Type "y" if prompted for MCP permissions ║');
|
|
534
|
+
console.log('║ • Type "/exit" when done ║');
|
|
535
|
+
console.log('║ ║');
|
|
536
|
+
console.log('╚══════════════════════════════════════════════════════════════════╝');
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log('Starting Claude...');
|
|
539
|
+
console.log('');
|
|
475
540
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
console.log(`║ ${tempFile.padEnd(62)}║`);
|
|
483
|
-
console.log('║ ║');
|
|
484
|
-
console.log('║ When Claude starts, tell it: ║');
|
|
485
|
-
console.log('║ "Read and execute the instructions in the file above" ║');
|
|
486
|
-
console.log('║ ║');
|
|
487
|
-
console.log('║ • Type "y" if prompted for MCP permissions ║');
|
|
488
|
-
console.log('║ • Type "/exit" when done ║');
|
|
489
|
-
console.log('║ ║');
|
|
490
|
-
console.log('╚══════════════════════════════════════════════════════════════════╝');
|
|
491
|
-
console.log('');
|
|
492
|
-
console.log('Starting Claude...');
|
|
493
|
-
console.log('');
|
|
494
|
-
|
|
495
|
-
// Run Claude fully interactively (no flags - pure interactive mode)
|
|
496
|
-
const result = spawnSync(command, args, {
|
|
497
|
-
stdio: 'inherit', // Full terminal access
|
|
498
|
-
shell: true,
|
|
499
|
-
timeout
|
|
500
|
-
});
|
|
541
|
+
// Run Claude fully interactively (no flags - pure interactive mode)
|
|
542
|
+
const result = spawnSync(command, args, {
|
|
543
|
+
stdio: 'inherit', // Full terminal access
|
|
544
|
+
shell: true,
|
|
545
|
+
timeout
|
|
546
|
+
});
|
|
501
547
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
548
|
+
// Clean up temp file
|
|
549
|
+
try {
|
|
550
|
+
fs.unlinkSync(tempFile);
|
|
551
|
+
} catch (e) {
|
|
552
|
+
logger.debug('claude-client - executeClaudeInteractive', 'Temp file cleanup', {
|
|
553
|
+
error: e.message
|
|
554
|
+
});
|
|
555
|
+
}
|
|
508
556
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
557
|
+
if (result.error) {
|
|
558
|
+
logger.error('claude-client - executeClaudeInteractive', 'Spawn error', result.error);
|
|
559
|
+
reject(new ClaudeClientError('Failed to start Claude', { cause: result.error }));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
514
562
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
563
|
+
if (result.status === 0 || result.status === null) {
|
|
564
|
+
console.log('');
|
|
565
|
+
resolve('interactive-session-completed');
|
|
566
|
+
} else if (result.signal === 'SIGTERM') {
|
|
567
|
+
reject(new ClaudeClientError('Claude session timed out', { context: { timeout } }));
|
|
568
|
+
} else {
|
|
569
|
+
logger.error('claude-client - executeClaudeInteractive', 'Claude exited with error', {
|
|
570
|
+
status: result.status,
|
|
571
|
+
signal: result.signal
|
|
572
|
+
});
|
|
573
|
+
reject(
|
|
574
|
+
new ClaudeClientError(`Claude exited with code ${result.status}`, {
|
|
575
|
+
context: { exitCode: result.status, signal: result.signal }
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
530
580
|
|
|
531
581
|
/**
|
|
532
582
|
* Extracts JSON from Claude's response
|
|
@@ -538,11 +588,9 @@ const executeClaudeInteractive = (prompt, { timeout = 300000 } = {}) => new Prom
|
|
|
538
588
|
* @throws {ClaudeClientError} If no valid JSON found
|
|
539
589
|
*/
|
|
540
590
|
const extractJSON = (response) => {
|
|
541
|
-
logger.debug(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
{ responseLength: response.length }
|
|
545
|
-
);
|
|
591
|
+
logger.debug('claude-client - extractJSON', 'Extracting JSON from response', {
|
|
592
|
+
responseLength: response.length
|
|
593
|
+
});
|
|
546
594
|
|
|
547
595
|
// Why: Try multiple patterns to find JSON
|
|
548
596
|
// Pattern 1: JSON in markdown code block
|
|
@@ -590,7 +638,10 @@ const extractJSON = (response) => {
|
|
|
590
638
|
const jsonText = jsonLines.join('\n');
|
|
591
639
|
try {
|
|
592
640
|
const json = JSON.parse(jsonText);
|
|
593
|
-
logger.debug(
|
|
641
|
+
logger.debug(
|
|
642
|
+
'claude-client - extractJSON',
|
|
643
|
+
'JSON extracted using line matching'
|
|
644
|
+
);
|
|
594
645
|
return json;
|
|
595
646
|
} catch (error) {
|
|
596
647
|
// Try next occurrence
|
|
@@ -651,28 +702,21 @@ const saveDebugResponse = async (prompt, response, filename = config.output.debu
|
|
|
651
702
|
// Display batch optimization status
|
|
652
703
|
try {
|
|
653
704
|
if (prompt.includes('OPTIMIZATION')) {
|
|
654
|
-
console.log(`\n${
|
|
705
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
655
706
|
console.log('✅ BATCH OPTIMIZATION ENABLED');
|
|
656
707
|
console.log('='.repeat(70));
|
|
657
708
|
console.log('Multi-file analysis organized for efficient processing');
|
|
658
709
|
console.log('Check debug file for full prompt and response details');
|
|
659
|
-
console.log(`${'='.repeat(70)
|
|
710
|
+
console.log(`${'='.repeat(70)}\n`);
|
|
660
711
|
}
|
|
661
712
|
} catch (parseError) {
|
|
662
713
|
// Ignore parsing errors, just skip the display
|
|
663
714
|
}
|
|
664
715
|
|
|
665
716
|
logger.info(`📝 Debug output saved to ${filename}`);
|
|
666
|
-
logger.debug(
|
|
667
|
-
'claude-client - saveDebugResponse',
|
|
668
|
-
`Debug response saved to ${filename}`
|
|
669
|
-
);
|
|
717
|
+
logger.debug('claude-client - saveDebugResponse', `Debug response saved to ${filename}`);
|
|
670
718
|
} catch (error) {
|
|
671
|
-
logger.error(
|
|
672
|
-
'claude-client - saveDebugResponse',
|
|
673
|
-
'Failed to save debug response',
|
|
674
|
-
error
|
|
675
|
-
);
|
|
719
|
+
logger.error('claude-client - saveDebugResponse', 'Failed to save debug response', error);
|
|
676
720
|
}
|
|
677
721
|
};
|
|
678
722
|
|
|
@@ -690,7 +734,16 @@ const saveDebugResponse = async (prompt, response, filename = config.output.debu
|
|
|
690
734
|
* @returns {Promise<any>} Result from fn
|
|
691
735
|
* @throws {Error} If fn fails after all retries
|
|
692
736
|
*/
|
|
693
|
-
const withRetry = async (
|
|
737
|
+
const withRetry = async (
|
|
738
|
+
fn,
|
|
739
|
+
{
|
|
740
|
+
maxRetries = 3,
|
|
741
|
+
baseRetryDelay = 2000,
|
|
742
|
+
retryCount = 0,
|
|
743
|
+
operationName = 'operation',
|
|
744
|
+
telemetryContext = null
|
|
745
|
+
} = {}
|
|
746
|
+
) => {
|
|
694
747
|
const retryDelay = baseRetryDelay * Math.pow(2, retryCount);
|
|
695
748
|
const startTime = Date.now();
|
|
696
749
|
|
|
@@ -705,8 +758,12 @@ const withRetry = async (fn, { maxRetries = 3, baseRetryDelay = 2000, retryCount
|
|
|
705
758
|
duration,
|
|
706
759
|
retryAttempt: retryCount,
|
|
707
760
|
totalRetries: maxRetries
|
|
708
|
-
}).catch(err => {
|
|
709
|
-
logger.debug(
|
|
761
|
+
}).catch((err) => {
|
|
762
|
+
logger.debug(
|
|
763
|
+
'claude-client - withRetry',
|
|
764
|
+
'Failed to record success telemetry',
|
|
765
|
+
err
|
|
766
|
+
);
|
|
710
767
|
});
|
|
711
768
|
}
|
|
712
769
|
|
|
@@ -725,8 +782,12 @@ const withRetry = async (fn, { maxRetries = 3, baseRetryDelay = 2000, retryCount
|
|
|
725
782
|
totalRetries: maxRetries,
|
|
726
783
|
responseLength: error.context?.response?.length || 0,
|
|
727
784
|
responsePreview: error.context?.response?.substring(0, 100) || ''
|
|
728
|
-
}).catch(err => {
|
|
729
|
-
logger.debug(
|
|
785
|
+
}).catch((err) => {
|
|
786
|
+
logger.debug(
|
|
787
|
+
'claude-client - withRetry',
|
|
788
|
+
'Failed to record failure telemetry',
|
|
789
|
+
err
|
|
790
|
+
);
|
|
730
791
|
});
|
|
731
792
|
}
|
|
732
793
|
|
|
@@ -736,20 +797,16 @@ const withRetry = async (fn, { maxRetries = 3, baseRetryDelay = 2000, retryCount
|
|
|
736
797
|
const isRecoverable = hasErrorInfo ? isRecoverableError(error.context.errorInfo) : false;
|
|
737
798
|
const canRetry = retryCount < maxRetries;
|
|
738
799
|
|
|
739
|
-
logger.debug(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
errorType: error.context?.errorInfo?.type,
|
|
750
|
-
errorName: error.name
|
|
751
|
-
}
|
|
752
|
-
);
|
|
800
|
+
logger.debug('claude-client - withRetry', `Retry check for ${operationName}`, {
|
|
801
|
+
retryCount,
|
|
802
|
+
maxRetries,
|
|
803
|
+
hasContext,
|
|
804
|
+
hasErrorInfo,
|
|
805
|
+
isRecoverable,
|
|
806
|
+
canRetry,
|
|
807
|
+
errorType: error.context?.errorInfo?.type,
|
|
808
|
+
errorName: error.name
|
|
809
|
+
});
|
|
753
810
|
|
|
754
811
|
const shouldRetry = canRetry && hasContext && hasErrorInfo && isRecoverable;
|
|
755
812
|
|
|
@@ -760,13 +817,21 @@ const withRetry = async (fn, { maxRetries = 3, baseRetryDelay = 2000, retryCount
|
|
|
760
817
|
{ errorType: error.context.errorInfo.type }
|
|
761
818
|
);
|
|
762
819
|
|
|
763
|
-
console.log(
|
|
820
|
+
console.log(
|
|
821
|
+
`\n⏳ Retrying in ${retryDelay / 1000} seconds due to ${error.context.errorInfo.type}...\n`
|
|
822
|
+
);
|
|
764
823
|
|
|
765
824
|
// Wait before retry
|
|
766
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
825
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
767
826
|
|
|
768
827
|
// Retry with incremented count and same telemetry context
|
|
769
|
-
return withRetry(fn, {
|
|
828
|
+
return withRetry(fn, {
|
|
829
|
+
maxRetries,
|
|
830
|
+
baseRetryDelay,
|
|
831
|
+
retryCount: retryCount + 1,
|
|
832
|
+
operationName,
|
|
833
|
+
telemetryContext
|
|
834
|
+
});
|
|
770
835
|
}
|
|
771
836
|
|
|
772
837
|
// Add retry attempt to error context if not already present
|
|
@@ -794,13 +859,10 @@ const withRetry = async (fn, { maxRetries = 3, baseRetryDelay = 2000, retryCount
|
|
|
794
859
|
const executeClaudeWithRetry = async (prompt, options = {}) => {
|
|
795
860
|
const { telemetryContext = {}, ...executeOptions } = options;
|
|
796
861
|
|
|
797
|
-
return withRetry(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
telemetryContext
|
|
802
|
-
}
|
|
803
|
-
);
|
|
862
|
+
return withRetry(() => executeClaude(prompt, executeOptions), {
|
|
863
|
+
operationName: 'executeClaude',
|
|
864
|
+
telemetryContext
|
|
865
|
+
});
|
|
804
866
|
};
|
|
805
867
|
|
|
806
868
|
/**
|
|
@@ -815,17 +877,20 @@ const executeClaudeWithRetry = async (prompt, options = {}) => {
|
|
|
815
877
|
* @returns {Promise<Object>} Parsed analysis result
|
|
816
878
|
* @throws {ClaudeClientError} If analysis fails
|
|
817
879
|
*/
|
|
818
|
-
const analyzeCode = async (
|
|
880
|
+
const analyzeCode = async (
|
|
881
|
+
prompt,
|
|
882
|
+
{ timeout = 120000, saveDebug = config.system.debug, telemetryContext = {} } = {}
|
|
883
|
+
) => {
|
|
819
884
|
const startTime = Date.now();
|
|
820
885
|
|
|
821
|
-
logger.debug(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
);
|
|
886
|
+
logger.debug('claude-client - analyzeCode', 'Starting code analysis', {
|
|
887
|
+
promptLength: prompt.length,
|
|
888
|
+
timeout,
|
|
889
|
+
saveDebug
|
|
890
|
+
});
|
|
826
891
|
|
|
827
892
|
// Rotate telemetry files periodically
|
|
828
|
-
rotateTelemetry().catch(err => {
|
|
893
|
+
rotateTelemetry().catch((err) => {
|
|
829
894
|
logger.debug('claude-client - analyzeCode', 'Failed to rotate telemetry', err);
|
|
830
895
|
});
|
|
831
896
|
|
|
@@ -845,16 +910,12 @@ const analyzeCode = async (prompt, { timeout = 120000, saveDebug = config.system
|
|
|
845
910
|
|
|
846
911
|
const duration = Date.now() - startTime;
|
|
847
912
|
|
|
848
|
-
logger.debug(
|
|
849
|
-
'
|
|
850
|
-
'
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
blockingIssuesCount: result.blockingIssues?.length ?? 0,
|
|
855
|
-
duration
|
|
856
|
-
}
|
|
857
|
-
);
|
|
913
|
+
logger.debug('claude-client - analyzeCode', 'Analysis complete', {
|
|
914
|
+
hasApproved: 'approved' in result,
|
|
915
|
+
hasQualityGate: 'QUALITY_GATE' in result,
|
|
916
|
+
blockingIssuesCount: result.blockingIssues?.length ?? 0,
|
|
917
|
+
duration
|
|
918
|
+
});
|
|
858
919
|
|
|
859
920
|
// Telemetry is now recorded by withRetry wrapper
|
|
860
921
|
return result;
|
|
@@ -893,7 +954,7 @@ const chunkArray = (array, size) => {
|
|
|
893
954
|
const analyzeCodeParallel = async (prompts, options = {}) => {
|
|
894
955
|
const startTime = Date.now();
|
|
895
956
|
|
|
896
|
-
console.log(`\n${
|
|
957
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
897
958
|
console.log(`🚀 PARALLEL EXECUTION: ${prompts.length} Claude processes`);
|
|
898
959
|
console.log('='.repeat(70));
|
|
899
960
|
|
|
@@ -923,7 +984,7 @@ const analyzeCodeParallel = async (prompts, options = {}) => {
|
|
|
923
984
|
|
|
924
985
|
console.log('='.repeat(70));
|
|
925
986
|
console.log(`✅ PARALLEL EXECUTION COMPLETE: ${results.length} results in ${duration}s`);
|
|
926
|
-
console.log(`${'='.repeat(70)
|
|
987
|
+
console.log(`${'='.repeat(70)}\n`);
|
|
927
988
|
|
|
928
989
|
logger.info(`Parallel analysis complete: ${results.length} results in ${duration}s`);
|
|
929
990
|
return results;
|