claude-git-hooks 2.35.3 → 2.43.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 +135 -0
- package/CLAUDE.md +24 -1389
- package/README.md +113 -0
- package/bin/claude-hooks +11 -7
- package/lib/cli-metadata.js +17 -3
- package/lib/commands/analyze-pr.js +270 -145
- package/lib/commands/analyze.js +151 -3
- package/lib/commands/create-pr.js +345 -134
- package/lib/commands/helpers.js +9 -4
- package/lib/commands/hooks.js +5 -5
- package/lib/commands/install.js +77 -28
- package/lib/commands/lint.js +120 -4
- package/lib/config.js +3 -0
- package/lib/hooks/pre-commit.js +26 -5
- package/lib/hooks/prepare-commit-msg.js +78 -4
- package/lib/utils/analysis-engine.js +12 -6
- package/lib/utils/claude-client.js +222 -12
- package/lib/utils/claude-diagnostics.js +5 -4
- package/lib/utils/cost-tracker.js +128 -0
- package/lib/utils/diff-analysis-orchestrator.js +2 -1
- package/lib/utils/git-operations.js +105 -2
- package/lib/utils/hooks-verified-marker.js +121 -0
- package/lib/utils/interactive-ui.js +4 -4
- package/lib/utils/judge.js +3 -2
- package/lib/utils/langfuse-tracer.js +156 -0
- package/lib/utils/logger.js +30 -5
- package/lib/utils/pr-metadata-engine.js +4 -2
- package/package.json +4 -2
|
@@ -24,15 +24,30 @@ import {
|
|
|
24
24
|
getGitHubToken
|
|
25
25
|
} from '../utils/github-api.js';
|
|
26
26
|
import { executeClaudeWithRetry, extractJSON } from '../utils/claude-client.js';
|
|
27
|
+
import { flush as langfuseFlush } from '../utils/langfuse-tracer.js';
|
|
27
28
|
import { loadPrompt } from '../utils/prompt-builder.js';
|
|
28
29
|
import { loadPreset, listPresets } from '../utils/preset-loader.js';
|
|
29
30
|
import { extractLinearTicketFromTitle, fetchTicket } from '../utils/linear-connector.js';
|
|
30
31
|
import { recordPRAnalysis } from '../utils/pr-statistics.js';
|
|
31
32
|
import { promptConfirmation, promptMenu } from '../utils/interactive-ui.js';
|
|
32
|
-
import {
|
|
33
|
+
import { CostTracker } from '../utils/cost-tracker.js';
|
|
34
|
+
import { colors, error, fatal, info, warning, checkGitRepo } from './helpers.js';
|
|
33
35
|
import logger from '../utils/logger.js';
|
|
34
36
|
import path from 'path';
|
|
35
37
|
|
|
38
|
+
// ─── JSON Error Helper ────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Emit error JSON to stdout and set exit code
|
|
42
|
+
* @param {string} message - Error message
|
|
43
|
+
* @param {number} exitCode - Process exit code (default: 1)
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
function _emitErrorJSON(message, exitCode = 1) {
|
|
47
|
+
process.stdout.write(`${JSON.stringify({ status: 'error', error: message })}\n`);
|
|
48
|
+
process.exitCode = exitCode;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
// ─── Category Normalization ─────────────────────────────────────────────────
|
|
37
52
|
|
|
38
53
|
/**
|
|
@@ -239,20 +254,35 @@ const displayComment = (comment, index, isInline) => {
|
|
|
239
254
|
* @param {Array<string>} args - Command arguments [pr-url, ...flags]
|
|
240
255
|
*/
|
|
241
256
|
export async function runAnalyzePr(args) {
|
|
242
|
-
if (!checkGitRepo()) {
|
|
243
|
-
error('You are not in a Git repository.');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
257
|
// Parse arguments
|
|
248
258
|
const prUrl = args.find((a) => !a.startsWith('--'));
|
|
249
259
|
const cliPreset = args.includes('--preset') ? args[args.indexOf('--preset') + 1] : null;
|
|
250
260
|
const cliModel = args.includes('--model') ? args[args.indexOf('--model') + 1] : null;
|
|
251
261
|
const dryRun = args.includes('--dry-run');
|
|
262
|
+
const headless = args.includes('--headless');
|
|
263
|
+
const fmtIdx = args.indexOf('--format');
|
|
264
|
+
const format = fmtIdx >= 0 ? args[fmtIdx + 1] : null;
|
|
265
|
+
const isJSON = format === 'json';
|
|
266
|
+
|
|
267
|
+
// Validation: --format json requires --headless
|
|
268
|
+
if (isJSON && !headless) {
|
|
269
|
+
_emitErrorJSON('--format json requires --headless', 2);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Activate JSON mode before any output so info/warning route to stderr
|
|
274
|
+
if (isJSON) logger.setJSONMode(true);
|
|
275
|
+
|
|
276
|
+
if (!checkGitRepo()) {
|
|
277
|
+
if (isJSON) { _emitErrorJSON('Not a git repository'); return; }
|
|
278
|
+
error('You are not in a Git repository.');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
252
281
|
|
|
253
282
|
if (!prUrl) {
|
|
283
|
+
if (isJSON) { _emitErrorJSON('No PR URL provided'); return; }
|
|
254
284
|
error(
|
|
255
|
-
'Usage: claude-hooks analyze-pr <pr-url> [--preset <name>] [--model <model>] [--dry-run]'
|
|
285
|
+
'Usage: claude-hooks analyze-pr <pr-url> [--preset <name>] [--model <model>] [--dry-run] [--headless] [--format json]'
|
|
256
286
|
);
|
|
257
287
|
return;
|
|
258
288
|
}
|
|
@@ -260,6 +290,7 @@ export async function runAnalyzePr(args) {
|
|
|
260
290
|
// Step 1: Parse URL
|
|
261
291
|
const parsed = parseGitHubPRUrl(prUrl);
|
|
262
292
|
if (!parsed) {
|
|
293
|
+
if (isJSON) { _emitErrorJSON(`Invalid GitHub PR URL: ${prUrl}`); return; }
|
|
263
294
|
error(`Invalid GitHub PR URL: ${prUrl}`);
|
|
264
295
|
return;
|
|
265
296
|
}
|
|
@@ -277,6 +308,7 @@ export async function runAnalyzePr(args) {
|
|
|
277
308
|
try {
|
|
278
309
|
getGitHubToken();
|
|
279
310
|
} catch {
|
|
311
|
+
if (isJSON) { _emitErrorJSON('GitHub token not configured. Run: claude-hooks setup-github'); return; }
|
|
280
312
|
error('GitHub token not configured. Run: claude-hooks setup-github');
|
|
281
313
|
return;
|
|
282
314
|
}
|
|
@@ -397,12 +429,20 @@ export async function runAnalyzePr(args) {
|
|
|
397
429
|
|
|
398
430
|
// Step 9: Call Claude
|
|
399
431
|
info('Running analysis...');
|
|
432
|
+
const costTracker = headless ? new CostTracker() : null;
|
|
400
433
|
const response = await executeClaudeWithRetry(prompt, {
|
|
401
434
|
timeout,
|
|
402
435
|
model,
|
|
436
|
+
headless,
|
|
437
|
+
costTracker,
|
|
403
438
|
telemetryContext: { hook: 'analyze-pr', fileCount: prFiles.length }
|
|
404
439
|
});
|
|
405
440
|
|
|
441
|
+
// Flush Langfuse traces after headless SDK call (no-op when disabled)
|
|
442
|
+
if (headless) {
|
|
443
|
+
await langfuseFlush().catch(() => {});
|
|
444
|
+
}
|
|
445
|
+
|
|
406
446
|
logger.debug('analyze-pr - runAnalyzePr', 'Claude response received', {
|
|
407
447
|
responseLength: response.length,
|
|
408
448
|
responsePreview: response.substring(0, 300)
|
|
@@ -430,147 +470,155 @@ export async function runAnalyzePr(args) {
|
|
|
430
470
|
category: normalizeCategory(c.category, allCategories, CATEGORY_ALIASES)
|
|
431
471
|
}));
|
|
432
472
|
|
|
433
|
-
// Step 11: Display results
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
console.log('');
|
|
438
|
-
console.log('================================================================');
|
|
439
|
-
console.log(' PR ANALYSIS RESULTS ');
|
|
440
|
-
console.log('================================================================');
|
|
441
|
-
console.log('');
|
|
442
|
-
console.log(`${colors.blue}Verdict:${colors.reset} ${result.verdict || 'comment'}`);
|
|
443
|
-
console.log(`${colors.blue}Summary:${colors.reset} ${result.summary || 'No summary.'}`);
|
|
444
|
-
console.log(`${colors.blue}Preset:${colors.reset} ${presetName}`);
|
|
445
|
-
console.log(`${colors.blue}Model:${colors.reset} ${model}`);
|
|
446
|
-
console.log(
|
|
447
|
-
`${colors.blue}Issues:${colors.reset} ${inlineComments.length} inline, ${generalComments.length} general`
|
|
448
|
-
);
|
|
449
|
-
console.log('');
|
|
473
|
+
// Step 11: Display results (skip in JSON mode — output is the JSON document)
|
|
474
|
+
if (!isJSON) {
|
|
475
|
+
const elapsed = Date.now() - startTime;
|
|
476
|
+
const seconds = (elapsed / 1000).toFixed(1);
|
|
450
477
|
|
|
451
|
-
if (inlineComments.length > 0) {
|
|
452
|
-
console.log(`${colors.green}--- Inline Comments ---${colors.reset}`);
|
|
453
478
|
console.log('');
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
console.log(`${colors.
|
|
479
|
+
console.log('================================================================');
|
|
480
|
+
console.log(' PR ANALYSIS RESULTS ');
|
|
481
|
+
console.log('================================================================');
|
|
482
|
+
console.log('');
|
|
483
|
+
console.log(`${colors.blue}Verdict:${colors.reset} ${result.verdict || 'comment'}`);
|
|
484
|
+
console.log(`${colors.blue}Summary:${colors.reset} ${result.summary || 'No summary.'}`);
|
|
485
|
+
console.log(`${colors.blue}Preset:${colors.reset} ${presetName}`);
|
|
486
|
+
console.log(`${colors.blue}Model:${colors.reset} ${model}`);
|
|
487
|
+
console.log(
|
|
488
|
+
`${colors.blue}Issues:${colors.reset} ${inlineComments.length} inline, ${generalComments.length} general`
|
|
489
|
+
);
|
|
459
490
|
console.log('');
|
|
460
|
-
generalComments.forEach((c, i) => displayComment(c, i + 1, false));
|
|
461
|
-
}
|
|
462
491
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
492
|
+
if (inlineComments.length > 0) {
|
|
493
|
+
console.log(`${colors.green}--- Inline Comments ---${colors.reset}`);
|
|
494
|
+
console.log('');
|
|
495
|
+
inlineComments.forEach((c, i) => displayComment(c, i + 1, true));
|
|
496
|
+
}
|
|
466
497
|
|
|
467
|
-
|
|
468
|
-
|
|
498
|
+
if (generalComments.length > 0) {
|
|
499
|
+
console.log(`${colors.green}--- General Comments ---${colors.reset}`);
|
|
500
|
+
console.log('');
|
|
501
|
+
generalComments.forEach((c, i) => displayComment(c, i + 1, false));
|
|
502
|
+
}
|
|
469
503
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
recordStats({
|
|
474
|
-
prUrl,
|
|
475
|
-
number,
|
|
476
|
-
owner,
|
|
477
|
-
repo,
|
|
478
|
-
presetName,
|
|
479
|
-
result,
|
|
480
|
-
inlineComments,
|
|
481
|
-
generalComments,
|
|
482
|
-
commentsPosted: 0,
|
|
483
|
-
commentsSkipped: inlineComments.length + generalComments.length
|
|
484
|
-
});
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
504
|
+
if (inlineComments.length === 0 && generalComments.length === 0) {
|
|
505
|
+
console.log(`${colors.green}No issues found. This PR looks good!${colors.reset}`);
|
|
506
|
+
}
|
|
487
507
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
recordStats({
|
|
491
|
-
prUrl,
|
|
492
|
-
number,
|
|
493
|
-
owner,
|
|
494
|
-
repo,
|
|
495
|
-
presetName,
|
|
496
|
-
result,
|
|
497
|
-
inlineComments,
|
|
498
|
-
generalComments,
|
|
499
|
-
commentsPosted: 0,
|
|
500
|
-
commentsSkipped: 0
|
|
501
|
-
});
|
|
502
|
-
return;
|
|
508
|
+
console.log(`${colors.blue}Analysis completed in ${seconds}s${colors.reset}`);
|
|
509
|
+
console.log('');
|
|
503
510
|
}
|
|
504
511
|
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
{ key: 'a', label: 'Post all comments as a review' },
|
|
510
|
-
{ key: 's', label: 'Select which comments to post' },
|
|
511
|
-
{ key: 'n', label: 'Skip posting (analysis only)' }
|
|
512
|
-
],
|
|
513
|
-
'a'
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
let confirmedInline = [];
|
|
517
|
-
let confirmedGeneral = [];
|
|
512
|
+
// Step 12: Determine confirmed comments (interactive vs headless)
|
|
513
|
+
let confirmedInline;
|
|
514
|
+
let confirmedGeneral;
|
|
515
|
+
let posted = false;
|
|
518
516
|
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
recordStats({
|
|
522
|
-
prUrl,
|
|
523
|
-
number,
|
|
524
|
-
owner,
|
|
525
|
-
repo,
|
|
526
|
-
presetName,
|
|
527
|
-
result,
|
|
528
|
-
inlineComments,
|
|
529
|
-
generalComments,
|
|
530
|
-
commentsPosted: 0,
|
|
531
|
-
commentsSkipped: inlineComments.length + generalComments.length
|
|
532
|
-
});
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (postChoice === 'a') {
|
|
517
|
+
if (headless) {
|
|
518
|
+
// Headless: post all comments (no interactive selection)
|
|
537
519
|
confirmedInline = inlineComments;
|
|
538
520
|
confirmedGeneral = generalComments;
|
|
539
|
-
} else
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
521
|
+
} else {
|
|
522
|
+
// Interactive: existing prompt-driven workflow
|
|
523
|
+
if (dryRun) {
|
|
524
|
+
info('Dry run mode: skipping comment posting.');
|
|
525
|
+
recordStats({
|
|
526
|
+
prUrl,
|
|
527
|
+
number,
|
|
528
|
+
owner,
|
|
529
|
+
repo,
|
|
530
|
+
presetName,
|
|
531
|
+
result,
|
|
532
|
+
inlineComments,
|
|
533
|
+
generalComments,
|
|
534
|
+
commentsPosted: 0,
|
|
535
|
+
commentsSkipped: inlineComments.length + generalComments.length
|
|
536
|
+
});
|
|
537
|
+
return;
|
|
545
538
|
}
|
|
546
539
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
540
|
+
if (inlineComments.length === 0 && generalComments.length === 0) {
|
|
541
|
+
info('No comments to post.');
|
|
542
|
+
recordStats({
|
|
543
|
+
prUrl,
|
|
544
|
+
number,
|
|
545
|
+
owner,
|
|
546
|
+
repo,
|
|
547
|
+
presetName,
|
|
548
|
+
result,
|
|
549
|
+
inlineComments,
|
|
550
|
+
generalComments,
|
|
551
|
+
commentsPosted: 0,
|
|
552
|
+
commentsSkipped: 0
|
|
553
|
+
});
|
|
554
|
+
return;
|
|
552
555
|
}
|
|
553
|
-
}
|
|
554
556
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
557
|
+
const postChoice = await promptMenu(
|
|
558
|
+
'What would you like to do with the review?',
|
|
559
|
+
[
|
|
560
|
+
{ key: 'a', label: 'Post all comments as a review' },
|
|
561
|
+
{ key: 's', label: 'Select which comments to post' },
|
|
562
|
+
{ key: 'n', label: 'Skip posting (analysis only)' }
|
|
563
|
+
],
|
|
564
|
+
'a'
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
confirmedInline = [];
|
|
568
|
+
confirmedGeneral = [];
|
|
569
|
+
|
|
570
|
+
if (postChoice === 'n') {
|
|
571
|
+
info('Skipping comment posting.');
|
|
572
|
+
recordStats({
|
|
573
|
+
prUrl,
|
|
574
|
+
number,
|
|
575
|
+
owner,
|
|
576
|
+
repo,
|
|
577
|
+
presetName,
|
|
578
|
+
result,
|
|
579
|
+
inlineComments,
|
|
580
|
+
generalComments,
|
|
581
|
+
commentsPosted: 0,
|
|
582
|
+
commentsSkipped: inlineComments.length + generalComments.length
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (postChoice === 'a') {
|
|
588
|
+
confirmedInline = inlineComments;
|
|
589
|
+
confirmedGeneral = generalComments;
|
|
590
|
+
} else if (postChoice === 's') {
|
|
591
|
+
for (const comment of inlineComments) {
|
|
592
|
+
displayComment(comment, inlineComments.indexOf(comment) + 1, true);
|
|
593
|
+
const keep = await promptConfirmation('Post this inline comment?', true);
|
|
594
|
+
if (keep) confirmedInline.push(comment);
|
|
595
|
+
}
|
|
596
|
+
for (const comment of generalComments) {
|
|
597
|
+
displayComment(comment, generalComments.indexOf(comment) + 1, false);
|
|
598
|
+
const keep = await promptConfirmation('Include this general comment?', true);
|
|
599
|
+
if (keep) confirmedGeneral.push(comment);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (confirmedInline.length === 0 && confirmedGeneral.length === 0) {
|
|
604
|
+
info('No comments selected for posting.');
|
|
605
|
+
recordStats({
|
|
606
|
+
prUrl,
|
|
607
|
+
number,
|
|
608
|
+
owner,
|
|
609
|
+
repo,
|
|
610
|
+
presetName,
|
|
611
|
+
result,
|
|
612
|
+
inlineComments,
|
|
613
|
+
generalComments,
|
|
614
|
+
commentsPosted: 0,
|
|
615
|
+
commentsSkipped: inlineComments.length + generalComments.length
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
571
619
|
}
|
|
572
620
|
|
|
573
|
-
// Build review body from general comments
|
|
621
|
+
// Step 13: Build review body from general comments
|
|
574
622
|
let reviewBody = `## PR Analysis (${presetName} preset)\n\n`;
|
|
575
623
|
reviewBody += `**Verdict:** ${result.verdict || 'comment'}\n\n`;
|
|
576
624
|
|
|
@@ -603,17 +651,85 @@ export async function runAnalyzePr(args) {
|
|
|
603
651
|
};
|
|
604
652
|
});
|
|
605
653
|
|
|
606
|
-
|
|
607
|
-
|
|
654
|
+
// Post review (unless dry-run)
|
|
655
|
+
if (!dryRun && (confirmedInline.length > 0 || confirmedGeneral.length > 0)) {
|
|
656
|
+
info('Posting review...');
|
|
657
|
+
await createPullRequestReview(owner, repo, number, reviewBody, apiComments, event);
|
|
658
|
+
posted = true;
|
|
659
|
+
}
|
|
608
660
|
|
|
609
|
-
const totalPosted = confirmedInline.length + confirmedGeneral.length;
|
|
661
|
+
const totalPosted = posted ? confirmedInline.length + confirmedGeneral.length : 0;
|
|
610
662
|
const totalSkipped = inlineComments.length + generalComments.length - totalPosted;
|
|
611
663
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
664
|
+
// Step 14: JSON output
|
|
665
|
+
if (isJSON) {
|
|
666
|
+
const severityCounts = { critical: 0, major: 0, minor: 0, info: 0 };
|
|
667
|
+
const categoryCounts = {};
|
|
668
|
+
[...inlineComments, ...generalComments].forEach((c) => {
|
|
669
|
+
if (severityCounts[c.severity] !== undefined) severityCounts[c.severity]++;
|
|
670
|
+
categoryCounts[c.category] = (categoryCounts[c.category] || 0) + 1;
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
const verdict = result.verdict || 'comment';
|
|
674
|
+
const isFail = severityCounts.critical > 0 || verdict === 'request_changes';
|
|
675
|
+
|
|
676
|
+
const payload = {
|
|
677
|
+
status: isFail ? 'fail' : 'pass',
|
|
678
|
+
verdict,
|
|
679
|
+
summary: result.summary || '',
|
|
680
|
+
preset: presetName,
|
|
681
|
+
model,
|
|
682
|
+
durationMs: Date.now() - startTime,
|
|
683
|
+
issueCount: inlineComments.length + generalComments.length,
|
|
684
|
+
severityCounts,
|
|
685
|
+
categoryCounts,
|
|
686
|
+
inlineComments: inlineComments.map((c) => ({
|
|
687
|
+
path: c.path,
|
|
688
|
+
line: c.line,
|
|
689
|
+
side: c.side || 'RIGHT',
|
|
690
|
+
severity: c.severity,
|
|
691
|
+
category: c.category,
|
|
692
|
+
body: c.body,
|
|
693
|
+
...(c.suggestion ? { suggestion: c.suggestion } : {})
|
|
694
|
+
})),
|
|
695
|
+
generalComments: generalComments.map((c) => ({
|
|
696
|
+
severity: c.severity,
|
|
697
|
+
category: c.category,
|
|
698
|
+
body: c.body
|
|
699
|
+
})),
|
|
700
|
+
reviewBody,
|
|
701
|
+
posted,
|
|
702
|
+
costs: costTracker ? costTracker.toJSON() : null
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
706
|
+
process.exitCode = isFail ? 1 : 0;
|
|
707
|
+
|
|
708
|
+
recordStats({
|
|
709
|
+
prUrl,
|
|
710
|
+
number,
|
|
711
|
+
owner,
|
|
712
|
+
repo,
|
|
713
|
+
presetName,
|
|
714
|
+
result,
|
|
715
|
+
inlineComments,
|
|
716
|
+
generalComments,
|
|
717
|
+
commentsPosted: totalPosted,
|
|
718
|
+
commentsSkipped: totalSkipped
|
|
719
|
+
});
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Non-JSON display output
|
|
724
|
+
if (posted) {
|
|
725
|
+
console.log(
|
|
726
|
+
`${colors.green}Review posted: ${confirmedInline.length} inline comments, ${confirmedGeneral.length} general observations${colors.reset}`
|
|
727
|
+
);
|
|
728
|
+
} else if (dryRun) {
|
|
729
|
+
info('Dry run mode: skipping comment posting.');
|
|
730
|
+
}
|
|
615
731
|
|
|
616
|
-
//
|
|
732
|
+
// Record statistics
|
|
617
733
|
recordStats({
|
|
618
734
|
prUrl,
|
|
619
735
|
number,
|
|
@@ -627,16 +743,25 @@ export async function runAnalyzePr(args) {
|
|
|
627
743
|
commentsSkipped: totalSkipped
|
|
628
744
|
});
|
|
629
745
|
} catch (e) {
|
|
746
|
+
if (isJSON) {
|
|
747
|
+
process.stdout.write(
|
|
748
|
+
`${JSON.stringify({ status: 'error', error: e.message, errorName: e.name })}\n`
|
|
749
|
+
);
|
|
750
|
+
process.exitCode = 1;
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
logger.debug('analyze-pr - runAnalyzePr', 'Full error details', {
|
|
755
|
+
name: e.name,
|
|
756
|
+
message: e.message,
|
|
757
|
+
context: e.context ? JSON.stringify(e.context).substring(0, 500) : undefined,
|
|
758
|
+
stack: e.stack
|
|
759
|
+
});
|
|
760
|
+
|
|
630
761
|
if (e.name === 'GitHubAPIError') {
|
|
631
|
-
|
|
762
|
+
fatal(`GitHub API error: ${e.message}`);
|
|
632
763
|
} else {
|
|
633
|
-
|
|
634
|
-
logger.debug('analyze-pr - runAnalyzePr', 'Full error details', {
|
|
635
|
-
name: e.name,
|
|
636
|
-
message: e.message,
|
|
637
|
-
context: e.context ? JSON.stringify(e.context).substring(0, 500) : undefined,
|
|
638
|
-
stack: e.stack
|
|
639
|
-
});
|
|
764
|
+
fatal(`Error analyzing PR: ${e.message}`);
|
|
640
765
|
}
|
|
641
766
|
}
|
|
642
767
|
}
|