claude-git-hooks 2.18.1 → 2.20.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 +52 -0
- package/CLAUDE.md +85 -38
- package/README.md +52 -18
- package/bin/claude-hooks +75 -89
- package/lib/cli-metadata.js +306 -0
- package/lib/commands/analyze-diff.js +12 -10
- package/lib/commands/analyze.js +9 -5
- package/lib/commands/bump-version.js +56 -40
- package/lib/commands/create-pr.js +71 -34
- package/lib/commands/debug.js +4 -7
- package/lib/commands/diff-batch-info.js +105 -0
- package/lib/commands/generate-changelog.js +3 -2
- 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 -49
- 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 -52
- package/lib/hooks/pre-commit.js +70 -64
- package/lib/hooks/prepare-commit-msg.js +35 -75
- package/lib/utils/analysis-engine.js +77 -54
- package/lib/utils/changelog-generator.js +63 -37
- package/lib/utils/claude-client.js +447 -438
- package/lib/utils/claude-diagnostics.js +20 -10
- package/lib/utils/diff-analysis-orchestrator.js +332 -0
- package/lib/utils/file-operations.js +51 -79
- package/lib/utils/file-utils.js +6 -7
- 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/judge.js +195 -0
- package/lib/utils/logger.js +4 -1
- package/lib/utils/package-info.js +0 -11
- package/lib/utils/pr-metadata-engine.js +67 -33
- package/lib/utils/preset-loader.js +20 -62
- package/lib/utils/prompt-builder.js +57 -68
- package/lib/utils/resolution-prompt.js +34 -52
- package/lib/utils/sanitize.js +20 -19
- package/lib/utils/task-id.js +27 -40
- package/lib/utils/telemetry.js +73 -25
- package/lib/utils/version-manager.js +147 -70
- package/lib/utils/which-command.js +23 -12
- package/package.json +1 -1
- package/templates/CLAUDE_RESOLUTION_PROMPT.md +17 -9
- package/templates/DIFF_ANALYSIS_ORCHESTRATION_PROMPT.md +70 -0
- package/templates/config.advanced.example.json +15 -31
- package/templates/config.example.json +0 -11
package/lib/utils/telemetry.js
CHANGED
|
@@ -88,11 +88,9 @@ const getCurrentLogFile = () => {
|
|
|
88
88
|
*
|
|
89
89
|
* @returns {boolean} True if telemetry is enabled (default: true)
|
|
90
90
|
*/
|
|
91
|
-
const isTelemetryEnabled = () =>
|
|
91
|
+
const isTelemetryEnabled = () =>
|
|
92
92
|
// Enabled by default - only disabled if explicitly set to false
|
|
93
|
-
config.system?.telemetry !== false
|
|
94
|
-
;
|
|
95
|
-
|
|
93
|
+
config.system?.telemetry !== false;
|
|
96
94
|
/**
|
|
97
95
|
* Ensure telemetry directory exists
|
|
98
96
|
* Why: Create on first use
|
|
@@ -115,7 +113,7 @@ const ensureTelemetryDir = async () => {
|
|
|
115
113
|
const appendEvent = async (event) => {
|
|
116
114
|
try {
|
|
117
115
|
const logFile = getCurrentLogFile();
|
|
118
|
-
const line = `${JSON.stringify(event)
|
|
116
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
119
117
|
|
|
120
118
|
// Append to file (create if doesn't exist)
|
|
121
119
|
await fs.appendFile(logFile, line, 'utf8');
|
|
@@ -204,6 +202,8 @@ export const recordJsonParseFailure = async (options) => {
|
|
|
204
202
|
* @param {Object} options - Success context
|
|
205
203
|
* @param {number} options.retryAttempt - Current retry attempt (0-based)
|
|
206
204
|
* @param {number} options.totalRetries - Total retry attempts configured
|
|
205
|
+
* @param {string} options.batchModel - Model used for this specific batch (orchestrated mode)
|
|
206
|
+
* @param {number} options.orchestrationTime - Time spent in orchestration (ms, first batch only)
|
|
207
207
|
*/
|
|
208
208
|
export const recordBatchSuccess = async (options) => {
|
|
209
209
|
await recordEvent(
|
|
@@ -214,7 +214,9 @@ export const recordBatchSuccess = async (options) => {
|
|
|
214
214
|
batchIndex: options.batchIndex ?? -1,
|
|
215
215
|
totalBatches: options.totalBatches || 0,
|
|
216
216
|
model: options.model || 'unknown',
|
|
217
|
+
batchModel: options.batchModel || options.model || 'unknown',
|
|
217
218
|
duration: options.duration || 0,
|
|
219
|
+
orchestrationTime: options.orchestrationTime || 0,
|
|
218
220
|
responseLength: options.responseLength || 0,
|
|
219
221
|
parallelMode: (options.totalBatches || 0) > 1,
|
|
220
222
|
hook: options.hook || 'unknown'
|
|
@@ -240,7 +242,7 @@ const readTelemetryEvents = async (maxDays = 7) => {
|
|
|
240
242
|
const files = await fs.readdir(dir);
|
|
241
243
|
|
|
242
244
|
// Filter to .jsonl files only
|
|
243
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
245
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
244
246
|
|
|
245
247
|
// Sort by date (newest first)
|
|
246
248
|
logFiles.sort().reverse();
|
|
@@ -262,7 +264,11 @@ const readTelemetryEvents = async (maxDays = 7) => {
|
|
|
262
264
|
events.push(JSON.parse(line));
|
|
263
265
|
} catch (parseError) {
|
|
264
266
|
// Skip invalid lines
|
|
265
|
-
logger.debug(
|
|
267
|
+
logger.debug(
|
|
268
|
+
'telemetry - readTelemetryEvents',
|
|
269
|
+
'Invalid JSON line',
|
|
270
|
+
parseError
|
|
271
|
+
);
|
|
266
272
|
}
|
|
267
273
|
}
|
|
268
274
|
}
|
|
@@ -286,7 +292,8 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
286
292
|
if (!isTelemetryEnabled()) {
|
|
287
293
|
return {
|
|
288
294
|
enabled: false,
|
|
289
|
-
message:
|
|
295
|
+
message:
|
|
296
|
+
'Telemetry is disabled. To enable (default), remove or set "system.telemetry: true" in .claude/config.json'
|
|
290
297
|
};
|
|
291
298
|
}
|
|
292
299
|
|
|
@@ -299,26 +306,34 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
299
306
|
totalEvents: events.length,
|
|
300
307
|
jsonParseFailures: 0,
|
|
301
308
|
batchSuccesses: 0,
|
|
302
|
-
|
|
309
|
+
failuresByFileCount: {},
|
|
303
310
|
failuresByModel: {},
|
|
304
311
|
failuresByHook: {},
|
|
305
312
|
successesByHook: {},
|
|
306
313
|
avgFilesPerFailure: 0,
|
|
307
314
|
avgFilesPerSuccess: 0,
|
|
308
|
-
failureRate: 0
|
|
315
|
+
failureRate: 0,
|
|
316
|
+
avgAnalysisTimeByModel: {},
|
|
317
|
+
avgOrchestrationTime: 0
|
|
309
318
|
};
|
|
310
319
|
|
|
311
320
|
let totalFilesInFailures = 0;
|
|
312
321
|
let totalFilesInSuccesses = 0;
|
|
313
322
|
|
|
314
|
-
|
|
323
|
+
// For per-model timing aggregation
|
|
324
|
+
const modelDurations = {}; // model -> [durations]
|
|
325
|
+
let totalOrchestrationTime = 0;
|
|
326
|
+
let orchestrationCount = 0;
|
|
327
|
+
|
|
328
|
+
events.forEach((event) => {
|
|
315
329
|
if (event.type === 'json_parse_failure') {
|
|
316
330
|
stats.jsonParseFailures++;
|
|
317
331
|
totalFilesInFailures += event.data.fileCount || 0;
|
|
318
332
|
|
|
319
|
-
// Group by
|
|
320
|
-
const
|
|
321
|
-
stats.
|
|
333
|
+
// Group by file count
|
|
334
|
+
const fileCount = event.data.fileCount || 0;
|
|
335
|
+
stats.failuresByFileCount[fileCount] =
|
|
336
|
+
(stats.failuresByFileCount[fileCount] || 0) + 1;
|
|
322
337
|
|
|
323
338
|
// Group by model
|
|
324
339
|
const model = event.data.model || 'unknown';
|
|
@@ -327,7 +342,6 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
327
342
|
// Group by hook
|
|
328
343
|
const hook = event.data.hook || 'unknown';
|
|
329
344
|
stats.failuresByHook[hook] = (stats.failuresByHook[hook] || 0) + 1;
|
|
330
|
-
|
|
331
345
|
} else if (event.type === 'batch_success') {
|
|
332
346
|
stats.batchSuccesses++;
|
|
333
347
|
totalFilesInSuccesses += event.data.fileCount || 0;
|
|
@@ -335,21 +349,52 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
335
349
|
// Group by hook
|
|
336
350
|
const hook = event.data.hook || 'unknown';
|
|
337
351
|
stats.successesByHook[hook] = (stats.successesByHook[hook] || 0) + 1;
|
|
352
|
+
|
|
353
|
+
// Accumulate per-model timing
|
|
354
|
+
const batchModel = event.data.batchModel || event.data.model || 'unknown';
|
|
355
|
+
const duration = event.data.duration || 0;
|
|
356
|
+
if (duration > 0) {
|
|
357
|
+
if (!modelDurations[batchModel]) modelDurations[batchModel] = [];
|
|
358
|
+
modelDurations[batchModel].push(duration);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Accumulate orchestration time (stored on first batch only)
|
|
362
|
+
if (event.data.orchestrationTime > 0) {
|
|
363
|
+
totalOrchestrationTime += event.data.orchestrationTime;
|
|
364
|
+
orchestrationCount++;
|
|
365
|
+
}
|
|
338
366
|
}
|
|
339
367
|
});
|
|
340
368
|
|
|
341
369
|
// Calculate averages
|
|
342
370
|
if (stats.jsonParseFailures > 0) {
|
|
343
|
-
stats.avgFilesPerFailure = parseFloat(
|
|
371
|
+
stats.avgFilesPerFailure = parseFloat(
|
|
372
|
+
(totalFilesInFailures / stats.jsonParseFailures).toFixed(2)
|
|
373
|
+
);
|
|
344
374
|
}
|
|
345
375
|
if (stats.batchSuccesses > 0) {
|
|
346
|
-
stats.avgFilesPerSuccess = parseFloat(
|
|
376
|
+
stats.avgFilesPerSuccess = parseFloat(
|
|
377
|
+
(totalFilesInSuccesses / stats.batchSuccesses).toFixed(2)
|
|
378
|
+
);
|
|
347
379
|
}
|
|
348
380
|
|
|
349
381
|
// Calculate failure rate
|
|
350
382
|
const totalAnalyses = stats.jsonParseFailures + stats.batchSuccesses;
|
|
351
383
|
if (totalAnalyses > 0) {
|
|
352
|
-
stats.failureRate = parseFloat(
|
|
384
|
+
stats.failureRate = parseFloat(
|
|
385
|
+
((stats.jsonParseFailures / totalAnalyses) * 100).toFixed(2)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Calculate avg analysis time per model
|
|
390
|
+
for (const [model, durations] of Object.entries(modelDurations)) {
|
|
391
|
+
const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
|
|
392
|
+
stats.avgAnalysisTimeByModel[model] = Math.round(avg);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Calculate avg orchestration time
|
|
396
|
+
if (orchestrationCount > 0) {
|
|
397
|
+
stats.avgOrchestrationTime = Math.round(totalOrchestrationTime / orchestrationCount);
|
|
353
398
|
}
|
|
354
399
|
|
|
355
400
|
return stats;
|
|
@@ -379,7 +424,7 @@ export const rotateTelemetry = async (maxDays = 30) => {
|
|
|
379
424
|
const files = await fs.readdir(dir);
|
|
380
425
|
|
|
381
426
|
// Filter to .jsonl files
|
|
382
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
427
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
383
428
|
|
|
384
429
|
// Calculate cutoff date
|
|
385
430
|
const cutoffDate = new Date();
|
|
@@ -395,7 +440,10 @@ export const rotateTelemetry = async (maxDays = 30) => {
|
|
|
395
440
|
if (fileDate < cutoffStr) {
|
|
396
441
|
const filePath = path.join(dir, file);
|
|
397
442
|
await fs.unlink(filePath);
|
|
398
|
-
logger.debug(
|
|
443
|
+
logger.debug(
|
|
444
|
+
'telemetry - rotateTelemetry',
|
|
445
|
+
`Deleted old telemetry file: ${file}`
|
|
446
|
+
);
|
|
399
447
|
}
|
|
400
448
|
}
|
|
401
449
|
}
|
|
@@ -420,7 +468,7 @@ export const clearTelemetry = async () => {
|
|
|
420
468
|
|
|
421
469
|
// Delete all .jsonl files
|
|
422
470
|
const files = await fs.readdir(dir);
|
|
423
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
471
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
424
472
|
|
|
425
473
|
for (const file of logFiles) {
|
|
426
474
|
const filePath = path.join(dir, file);
|
|
@@ -471,11 +519,11 @@ export const displayStatistics = async () => {
|
|
|
471
519
|
console.log(`📊 Failure rate: ${stats.failureRate}%\n`);
|
|
472
520
|
|
|
473
521
|
if (stats.jsonParseFailures > 0) {
|
|
474
|
-
console.log('━━━ FAILURES BY
|
|
475
|
-
Object.entries(stats.
|
|
522
|
+
console.log('━━━ FAILURES BY FILE COUNT ━━━');
|
|
523
|
+
Object.entries(stats.failuresByFileCount)
|
|
476
524
|
.sort((a, b) => b[1] - a[1])
|
|
477
|
-
.forEach(([
|
|
478
|
-
console.log(`
|
|
525
|
+
.forEach(([fileCount, count]) => {
|
|
526
|
+
console.log(` ${fileCount} file(s): ${count} failures`);
|
|
479
527
|
});
|
|
480
528
|
console.log();
|
|
481
529
|
|