@vitest-evals/github-reporter 0.15.0 → 0.16.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/README.md +37 -3
- package/dist/cli.js +303 -23
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +303 -23
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +125 -1
- package/dist/index.d.ts +125 -1
- package/dist/index.js +427 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +422 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -22,9 +22,16 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
buildCheckAnnotations: () => buildCheckAnnotations,
|
|
24
24
|
collectEvalReport: () => collectEvalReport,
|
|
25
|
+
computePassRate: () => computePassRate,
|
|
26
|
+
evaluateEvalGate: () => evaluateEvalGate,
|
|
27
|
+
formatPercent: () => formatPercent,
|
|
25
28
|
publishCheckRun: () => publishCheckRun,
|
|
29
|
+
publishEvalReport: () => publishEvalReport,
|
|
30
|
+
renderGateWorkflowCommand: () => renderGateWorkflowCommand,
|
|
26
31
|
renderJobSummary: () => renderJobSummary,
|
|
27
|
-
renderWorkflowCommands: () => renderWorkflowCommands
|
|
32
|
+
renderWorkflowCommands: () => renderWorkflowCommands,
|
|
33
|
+
resolveCheckDetailsUrl: () => resolveCheckDetailsUrl,
|
|
34
|
+
resolveCheckSha: () => resolveCheckSha
|
|
28
35
|
});
|
|
29
36
|
module.exports = __toCommonJS(index_exports);
|
|
30
37
|
|
|
@@ -297,9 +304,10 @@ var DEFAULT_MAX_CHECK_ANNOTATIONS = 50;
|
|
|
297
304
|
var MAX_CHECK_FIELD_LENGTH = 64e3;
|
|
298
305
|
function renderWorkflowCommands(report, options = {}) {
|
|
299
306
|
const maxAnnotations = options.maxAnnotations ?? DEFAULT_MAX_WORKFLOW_ANNOTATIONS;
|
|
307
|
+
const command = caseAnnotationCommand(options.gate);
|
|
300
308
|
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map(
|
|
301
309
|
(testCase) => formatWorkflowCommand({
|
|
302
|
-
command
|
|
310
|
+
command,
|
|
303
311
|
properties: {
|
|
304
312
|
file: testCase.displayFile,
|
|
305
313
|
line: String(testCase.location.line),
|
|
@@ -315,11 +323,12 @@ function buildCheckAnnotations(report, options = {}) {
|
|
|
315
323
|
options.maxAnnotations ?? DEFAULT_MAX_CHECK_ANNOTATIONS,
|
|
316
324
|
DEFAULT_MAX_CHECK_ANNOTATIONS
|
|
317
325
|
);
|
|
326
|
+
const annotationLevel = caseAnnotationLevel(options.gate);
|
|
318
327
|
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map((testCase) => ({
|
|
319
328
|
path: testCase.displayFile,
|
|
320
329
|
start_line: testCase.location.line,
|
|
321
330
|
end_line: testCase.location.line,
|
|
322
|
-
annotation_level:
|
|
331
|
+
annotation_level: annotationLevel,
|
|
323
332
|
title: truncate(
|
|
324
333
|
`${testCase.primaryFailure?.judgeName ?? "vitest-evals"} - ${testCase.displayName}`,
|
|
325
334
|
255
|
|
@@ -331,6 +340,12 @@ function buildCheckAnnotations(report, options = {}) {
|
|
|
331
340
|
raw_details: truncate(formatRawDetails(testCase), MAX_CHECK_FIELD_LENGTH)
|
|
332
341
|
}));
|
|
333
342
|
}
|
|
343
|
+
function caseAnnotationCommand(gate) {
|
|
344
|
+
return gate?.ok ? "warning" : "error";
|
|
345
|
+
}
|
|
346
|
+
function caseAnnotationLevel(gate) {
|
|
347
|
+
return gate?.ok ? "warning" : "failure";
|
|
348
|
+
}
|
|
334
349
|
function hasAnnotationLocation(testCase) {
|
|
335
350
|
return Boolean(testCase.location);
|
|
336
351
|
}
|
|
@@ -390,6 +405,167 @@ function formatWorkflowCommand({
|
|
|
390
405
|
return `::${command} ${renderedProperties}::${escapeCommandData(message)}`;
|
|
391
406
|
}
|
|
392
407
|
|
|
408
|
+
// src/gate.ts
|
|
409
|
+
function evaluateEvalGate(report, policy = {}) {
|
|
410
|
+
const minPassRate = resolveMinPassRate(policy);
|
|
411
|
+
const minScoreAverage = policy.minScoreAverage;
|
|
412
|
+
const enforced = minPassRate !== void 0 || minScoreAverage !== void 0 || policy.failOnFailures === true;
|
|
413
|
+
const passRate = computePassRate(report);
|
|
414
|
+
const counts = formatEvalCounts(report, passRate);
|
|
415
|
+
if (!enforced) {
|
|
416
|
+
const ok = report.status === "passed";
|
|
417
|
+
return {
|
|
418
|
+
ok,
|
|
419
|
+
status: ok ? "passed" : "failed",
|
|
420
|
+
enforced: false,
|
|
421
|
+
passRate,
|
|
422
|
+
title: defaultCheckTitle(report),
|
|
423
|
+
message: ok ? `eval report passed: ${counts}` : `eval report failed: ${counts}`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const nonEvalFailures = Math.max(
|
|
427
|
+
0,
|
|
428
|
+
report.totals.failed - report.totals.evalFailed
|
|
429
|
+
);
|
|
430
|
+
if (nonEvalFailures > 0) {
|
|
431
|
+
return {
|
|
432
|
+
ok: false,
|
|
433
|
+
status: "failed",
|
|
434
|
+
enforced: true,
|
|
435
|
+
passRate,
|
|
436
|
+
title: "Eval report hard failure",
|
|
437
|
+
message: `${formatNumber(nonEvalFailures)} non-eval test failure${nonEvalFailures === 1 ? "" : "s"}; ${counts}`
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (report.totals.evalTotal === 0) {
|
|
441
|
+
return {
|
|
442
|
+
ok: false,
|
|
443
|
+
status: "failed",
|
|
444
|
+
enforced: true,
|
|
445
|
+
passRate: null,
|
|
446
|
+
title: "Eval report hard failure",
|
|
447
|
+
message: "no eval cases were reported"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (report.status === "failed" && report.totals.failed === 0 && report.failures.length === 0) {
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
status: "failed",
|
|
454
|
+
enforced: true,
|
|
455
|
+
passRate,
|
|
456
|
+
title: "Eval report hard failure",
|
|
457
|
+
message: `vitest run failed without counted test failures; ${counts}`
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
status: "failed",
|
|
464
|
+
enforced: true,
|
|
465
|
+
passRate,
|
|
466
|
+
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
467
|
+
message: `eval pass rate below floor: ${counts}; required >= ${formatPercent(minPassRate)}`
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
if (minScoreAverage !== void 0) {
|
|
471
|
+
const average = report.score?.average;
|
|
472
|
+
if (average === void 0 || !Number.isFinite(average)) {
|
|
473
|
+
return {
|
|
474
|
+
ok: false,
|
|
475
|
+
status: "failed",
|
|
476
|
+
enforced: true,
|
|
477
|
+
passRate,
|
|
478
|
+
title: "Eval score gate failed",
|
|
479
|
+
message: `no score average available; required avg score >= ${formatScore(minScoreAverage)}`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (average + Number.EPSILON < minScoreAverage) {
|
|
483
|
+
return {
|
|
484
|
+
ok: false,
|
|
485
|
+
status: "failed",
|
|
486
|
+
enforced: true,
|
|
487
|
+
passRate,
|
|
488
|
+
title: `Avg score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
489
|
+
message: `avg score below floor: ${counts}; required avg score >= ${formatScore(minScoreAverage)}`
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
ok: true,
|
|
495
|
+
status: "passed",
|
|
496
|
+
enforced: true,
|
|
497
|
+
passRate,
|
|
498
|
+
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
499
|
+
message: `eval gate passed: ${counts}${formatFloorSuffix(minPassRate, minScoreAverage)}`
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function computePassRate(report) {
|
|
503
|
+
if (report.totals.evalTotal <= 0) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
return report.totals.evalPassed / report.totals.evalTotal;
|
|
507
|
+
}
|
|
508
|
+
function formatPercent(value) {
|
|
509
|
+
if (value == null || !Number.isFinite(value)) {
|
|
510
|
+
return "n/a";
|
|
511
|
+
}
|
|
512
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
513
|
+
}
|
|
514
|
+
function renderGateWorkflowCommand(gate) {
|
|
515
|
+
if (gate.ok || !gate.enforced) {
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
return `::error title=${escapeCommandProperty(gate.title)}::${escapeCommandData(gate.message)}`;
|
|
519
|
+
}
|
|
520
|
+
function resolveMinPassRate(policy) {
|
|
521
|
+
const configured = policy.minPassRate;
|
|
522
|
+
if (policy.failOnFailures) {
|
|
523
|
+
if (configured === void 0) {
|
|
524
|
+
return 1;
|
|
525
|
+
}
|
|
526
|
+
return Math.max(configured, 1);
|
|
527
|
+
}
|
|
528
|
+
return configured;
|
|
529
|
+
}
|
|
530
|
+
function defaultCheckTitle(report) {
|
|
531
|
+
if (report.failures.length === 0 && report.status === "passed") {
|
|
532
|
+
return "No eval failures";
|
|
533
|
+
}
|
|
534
|
+
if (report.failures.length === 0) {
|
|
535
|
+
return "Vitest run failed";
|
|
536
|
+
}
|
|
537
|
+
return `${report.failures.length} eval failure${report.failures.length === 1 ? "" : "s"}`;
|
|
538
|
+
}
|
|
539
|
+
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
540
|
+
if (minPassRate !== void 0) {
|
|
541
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 floor ${formatPercent(minPassRate)}`;
|
|
542
|
+
}
|
|
543
|
+
if (minScoreAverage !== void 0) {
|
|
544
|
+
return `Avg score ${formatScore(report.score?.average)} \u2014 floor ${formatScore(minScoreAverage)}`;
|
|
545
|
+
}
|
|
546
|
+
return defaultCheckTitle(report);
|
|
547
|
+
}
|
|
548
|
+
function formatEvalCounts(report, passRate) {
|
|
549
|
+
const scoreText = report.score?.average === void 0 ? "n/a" : formatScore(report.score.average);
|
|
550
|
+
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
551
|
+
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
552
|
+
report.totals.evalTotal
|
|
553
|
+
)} passed (${passRateText}), avg score ${scoreText}`;
|
|
554
|
+
}
|
|
555
|
+
function formatFloorSuffix(minPassRate, minScoreAverage) {
|
|
556
|
+
const parts = [];
|
|
557
|
+
if (minPassRate !== void 0) {
|
|
558
|
+
parts.push(`pass rate floor ${formatPercent(minPassRate)}`);
|
|
559
|
+
}
|
|
560
|
+
if (minScoreAverage !== void 0) {
|
|
561
|
+
parts.push(`avg score floor ${formatScore(minScoreAverage)}`);
|
|
562
|
+
}
|
|
563
|
+
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/github.ts
|
|
567
|
+
var import_node_fs = require("fs");
|
|
568
|
+
|
|
393
569
|
// src/summary.ts
|
|
394
570
|
var DEFAULT_MAX_FAILURES = 20;
|
|
395
571
|
var DEFAULT_MAX_REASON_CHARS = 8e3;
|
|
@@ -410,20 +586,22 @@ function renderJobSummary(report, options = {}) {
|
|
|
410
586
|
const lines = [
|
|
411
587
|
"# vitest-evals",
|
|
412
588
|
"",
|
|
413
|
-
...renderSummaryTable(report, nonEvalFailures),
|
|
589
|
+
...renderSummaryTable(report, nonEvalFailures, options.gate),
|
|
414
590
|
"",
|
|
415
591
|
...renderScoreDistribution(report),
|
|
416
592
|
"## Results",
|
|
417
593
|
""
|
|
418
594
|
];
|
|
419
595
|
if (report.failures.length > 0) {
|
|
420
|
-
|
|
596
|
+
const failureHeading = options.gate?.ok === true ? "### Quality Misses" : "### Failures";
|
|
597
|
+
lines.push(failureHeading, "");
|
|
421
598
|
failures.forEach((testCase, index) => {
|
|
422
599
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
423
600
|
});
|
|
424
601
|
if (report.failures.length > failures.length) {
|
|
602
|
+
const omittedLabel = options.gate?.ok === true ? "quality misses" : "failures";
|
|
425
603
|
lines.push(
|
|
426
|
-
`${report.failures.length - failures.length} more
|
|
604
|
+
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
427
605
|
""
|
|
428
606
|
);
|
|
429
607
|
}
|
|
@@ -439,9 +617,9 @@ function renderJobSummary(report, options = {}) {
|
|
|
439
617
|
function formatCountLine(passed, failed, total) {
|
|
440
618
|
return `${formatNumber(passed)} passed, ${formatNumber(failed)} failed, ${formatNumber(total)} total`;
|
|
441
619
|
}
|
|
442
|
-
function renderSummaryTable(report, nonEvalFailures) {
|
|
620
|
+
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
443
621
|
const rows = [
|
|
444
|
-
["Status", report.status],
|
|
622
|
+
["Status", gate?.status ?? report.status],
|
|
445
623
|
[
|
|
446
624
|
"Evals",
|
|
447
625
|
formatCountLine(
|
|
@@ -451,9 +629,20 @@ function renderSummaryTable(report, nonEvalFailures) {
|
|
|
451
629
|
)
|
|
452
630
|
]
|
|
453
631
|
];
|
|
632
|
+
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
633
|
+
rows.push(["Pass Rate", formatPercent(gate.passRate)]);
|
|
634
|
+
} else if (report.totals.evalTotal > 0) {
|
|
635
|
+
rows.push([
|
|
636
|
+
"Pass Rate",
|
|
637
|
+
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
638
|
+
]);
|
|
639
|
+
}
|
|
454
640
|
if (report.score) {
|
|
455
641
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
456
642
|
}
|
|
643
|
+
if (gate?.enforced) {
|
|
644
|
+
rows.push(["Gate", gate.message]);
|
|
645
|
+
}
|
|
457
646
|
if (nonEvalFailures > 0) {
|
|
458
647
|
rows.push([
|
|
459
648
|
"Other Failures",
|
|
@@ -677,10 +866,44 @@ function formatCaseUsage(testCase) {
|
|
|
677
866
|
var DEFAULT_CHECK_NAME = "vitest-evals";
|
|
678
867
|
var MAX_CHECK_SUMMARY_LENGTH = 64e3;
|
|
679
868
|
var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
|
|
869
|
+
function resolveCheckSha(env = process.env, options = {}) {
|
|
870
|
+
const explicit = options.sha?.trim() || env.GITHUB_PR_HEAD_SHA?.trim();
|
|
871
|
+
if (explicit) {
|
|
872
|
+
return explicit;
|
|
873
|
+
}
|
|
874
|
+
const eventPath = options.eventPath?.trim() || env.GITHUB_EVENT_PATH?.trim();
|
|
875
|
+
if (eventPath) {
|
|
876
|
+
try {
|
|
877
|
+
const event = JSON.parse((0, import_node_fs.readFileSync)(eventPath, "utf8"));
|
|
878
|
+
const headSha = event.pull_request?.head?.sha;
|
|
879
|
+
if (typeof headSha === "string" && headSha.trim()) {
|
|
880
|
+
return headSha.trim();
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return env.GITHUB_SHA?.trim() || void 0;
|
|
886
|
+
}
|
|
887
|
+
function resolveCheckDetailsUrl(env = process.env, options = {}) {
|
|
888
|
+
const explicit = options.detailsUrl?.trim();
|
|
889
|
+
if (explicit) {
|
|
890
|
+
return explicit;
|
|
891
|
+
}
|
|
892
|
+
const server = env.GITHUB_SERVER_URL?.replace(/\/$/, "");
|
|
893
|
+
const repository = env.GITHUB_REPOSITORY?.trim();
|
|
894
|
+
const runId = env.GITHUB_RUN_ID?.trim();
|
|
895
|
+
if (!server || !repository || !runId) {
|
|
896
|
+
return void 0;
|
|
897
|
+
}
|
|
898
|
+
return `${server}/${repository}/actions/runs/${runId}`;
|
|
899
|
+
}
|
|
680
900
|
async function publishCheckRun(report, options = {}) {
|
|
681
901
|
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
682
902
|
const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
|
|
683
|
-
const sha = options.sha
|
|
903
|
+
const sha = resolveCheckSha(process.env, { sha: options.sha });
|
|
904
|
+
const detailsUrl = resolveCheckDetailsUrl(process.env, {
|
|
905
|
+
detailsUrl: options.detailsUrl
|
|
906
|
+
});
|
|
684
907
|
if (!token) {
|
|
685
908
|
return { status: "skipped", reason: "missing GITHUB_TOKEN" };
|
|
686
909
|
}
|
|
@@ -688,7 +911,10 @@ async function publishCheckRun(report, options = {}) {
|
|
|
688
911
|
return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
|
|
689
912
|
}
|
|
690
913
|
if (!sha && options.checkRunId === void 0) {
|
|
691
|
-
return {
|
|
914
|
+
return {
|
|
915
|
+
status: "skipped",
|
|
916
|
+
reason: "missing commit SHA (set --sha / options.sha, GITHUB_PR_HEAD_SHA, pull_request.head.sha, or GITHUB_SHA)"
|
|
917
|
+
};
|
|
692
918
|
}
|
|
693
919
|
const [owner, repo] = repository.split("/");
|
|
694
920
|
if (!owner || !repo) {
|
|
@@ -697,7 +923,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
697
923
|
reason: `invalid GitHub repository: ${repository}`
|
|
698
924
|
};
|
|
699
925
|
}
|
|
700
|
-
const payload = buildCheckRunPayload(report, options);
|
|
926
|
+
const payload = buildCheckRunPayload(report, options, detailsUrl);
|
|
701
927
|
const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
702
928
|
const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
|
|
703
929
|
const response = await fetch(requestUrl, {
|
|
@@ -712,6 +938,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
712
938
|
options.checkRunId === void 0 ? {
|
|
713
939
|
name: options.name ?? DEFAULT_CHECK_NAME,
|
|
714
940
|
head_sha: sha,
|
|
941
|
+
...options.externalId ? { external_id: options.externalId } : {},
|
|
715
942
|
...payload
|
|
716
943
|
} : payload
|
|
717
944
|
)
|
|
@@ -726,27 +953,31 @@ async function publishCheckRun(report, options = {}) {
|
|
|
726
953
|
return {
|
|
727
954
|
status: options.checkRunId === void 0 ? "created" : "updated",
|
|
728
955
|
id: data.id,
|
|
729
|
-
htmlUrl: data.html_url
|
|
956
|
+
htmlUrl: data.html_url,
|
|
957
|
+
sha
|
|
730
958
|
};
|
|
731
959
|
}
|
|
732
|
-
function buildCheckRunPayload(report, options) {
|
|
960
|
+
function buildCheckRunPayload(report, options, detailsUrl) {
|
|
961
|
+
const gate = options.gate ?? evaluateEvalGate(report);
|
|
733
962
|
const annotations = buildCheckAnnotations(report, {
|
|
734
|
-
maxAnnotations: options.maxAnnotations
|
|
963
|
+
maxAnnotations: options.maxAnnotations,
|
|
964
|
+
gate
|
|
735
965
|
});
|
|
736
|
-
const title = report.failures.length === 0 && report.status === "passed" ? "No eval failures" : report.failures.length === 0 ? "Vitest run failed" : `${report.failures.length} eval failure${report.failures.length === 1 ? "" : "s"}`;
|
|
737
966
|
return {
|
|
738
967
|
status: "completed",
|
|
739
|
-
conclusion:
|
|
968
|
+
conclusion: gate.ok ? "success" : "failure",
|
|
740
969
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
970
|
+
...detailsUrl ? { details_url: detailsUrl } : {},
|
|
741
971
|
output: {
|
|
742
|
-
title,
|
|
972
|
+
title: gate.title,
|
|
743
973
|
summary: truncateCheckSummary(
|
|
744
974
|
renderJobSummary(report, {
|
|
745
975
|
...options,
|
|
746
976
|
maxFailures: options.maxFailures ?? 5,
|
|
747
977
|
maxReasonChars: options.maxReasonChars ?? 4e3,
|
|
748
978
|
maxOutputChars: options.maxOutputChars ?? 2e3,
|
|
749
|
-
maxToolCalls: options.maxToolCalls ?? 10
|
|
979
|
+
maxToolCalls: options.maxToolCalls ?? 10,
|
|
980
|
+
gate
|
|
750
981
|
})
|
|
751
982
|
),
|
|
752
983
|
annotations
|
|
@@ -759,12 +990,189 @@ function truncateCheckSummary(summary) {
|
|
|
759
990
|
}
|
|
760
991
|
return `${summary.slice(0, MAX_CHECK_SUMMARY_LENGTH - CHECK_SUMMARY_TRUNCATION_SUFFIX.length).trimEnd()}${CHECK_SUMMARY_TRUNCATION_SUFFIX}`;
|
|
761
992
|
}
|
|
993
|
+
|
|
994
|
+
// src/report.ts
|
|
995
|
+
var import_promises = require("fs/promises");
|
|
996
|
+
var import_node = require("@vitest-evals/core/node");
|
|
997
|
+
|
|
998
|
+
// src/merge.ts
|
|
999
|
+
function mergeEvalReports(reports) {
|
|
1000
|
+
const cases = reports.flatMap((report) => report.cases);
|
|
1001
|
+
const failures = reports.flatMap((report) => report.failures);
|
|
1002
|
+
const scoredCases = cases.map((testCase) => testCase.eval?.avgScore).filter(
|
|
1003
|
+
(score) => typeof score === "number" && Number.isFinite(score)
|
|
1004
|
+
);
|
|
1005
|
+
const startedAtValues = reports.map((report) => report.startedAt).filter(
|
|
1006
|
+
(startedAt2) => typeof startedAt2 === "number" && Number.isFinite(startedAt2)
|
|
1007
|
+
);
|
|
1008
|
+
const startedAt = startedAtValues.length > 0 ? Math.min(...startedAtValues) : void 0;
|
|
1009
|
+
return {
|
|
1010
|
+
status: reports.some((report) => report.status === "failed") ? "failed" : "passed",
|
|
1011
|
+
startedAt,
|
|
1012
|
+
durationMs: mergeDuration(reports),
|
|
1013
|
+
totals: {
|
|
1014
|
+
total: sum(reports, (report) => report.totals.total),
|
|
1015
|
+
passed: sum(reports, (report) => report.totals.passed),
|
|
1016
|
+
failed: sum(reports, (report) => report.totals.failed),
|
|
1017
|
+
skipped: sum(reports, (report) => report.totals.skipped),
|
|
1018
|
+
evalTotal: sum(reports, (report) => report.totals.evalTotal),
|
|
1019
|
+
evalPassed: sum(reports, (report) => report.totals.evalPassed),
|
|
1020
|
+
evalFailed: sum(reports, (report) => report.totals.evalFailed)
|
|
1021
|
+
},
|
|
1022
|
+
score: scoredCases.length > 0 ? {
|
|
1023
|
+
average: scoredCases.reduce((total, score) => total + score, 0) / scoredCases.length,
|
|
1024
|
+
minimum: Math.min(...scoredCases)
|
|
1025
|
+
} : void 0,
|
|
1026
|
+
usage: mergeUsage(reports.map((report) => report.usage)),
|
|
1027
|
+
cases,
|
|
1028
|
+
failures
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function mergeUsage(usages) {
|
|
1032
|
+
return {
|
|
1033
|
+
inputTokens: sum(usages, (usage) => usage.inputTokens),
|
|
1034
|
+
outputTokens: sum(usages, (usage) => usage.outputTokens),
|
|
1035
|
+
reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
|
|
1036
|
+
totalTokens: sum(usages, (usage) => usage.totalTokens),
|
|
1037
|
+
toolCalls: sum(usages, (usage) => usage.toolCalls)
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function mergeDuration(reports) {
|
|
1041
|
+
const durations = reports.map((report) => report.durationMs).filter(
|
|
1042
|
+
(durationMs) => typeof durationMs === "number" && Number.isFinite(durationMs)
|
|
1043
|
+
);
|
|
1044
|
+
const intervals = reports.map((report) => {
|
|
1045
|
+
if (typeof report.startedAt !== "number" || !Number.isFinite(report.startedAt) || typeof report.durationMs !== "number" || !Number.isFinite(report.durationMs)) {
|
|
1046
|
+
return void 0;
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
start: report.startedAt,
|
|
1050
|
+
end: report.startedAt + report.durationMs
|
|
1051
|
+
};
|
|
1052
|
+
}).filter(
|
|
1053
|
+
(interval) => Boolean(interval)
|
|
1054
|
+
);
|
|
1055
|
+
if (intervals.length > 0 && intervals.length === durations.length) {
|
|
1056
|
+
return Math.max(...intervals.map((interval) => interval.end)) - Math.min(...intervals.map((interval) => interval.start));
|
|
1057
|
+
}
|
|
1058
|
+
return durations.length > 0 ? durations.reduce((total, durationMs) => total + durationMs, 0) : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
function sum(items, select) {
|
|
1061
|
+
return items.reduce((total, item) => total + select(item), 0);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/report.ts
|
|
1065
|
+
async function publishEvalReport(options) {
|
|
1066
|
+
const resultFiles = await (0, import_node.resolveResultFiles)(options.resultPatterns, {
|
|
1067
|
+
cwd: options.cwd
|
|
1068
|
+
});
|
|
1069
|
+
if (resultFiles.length === 0) {
|
|
1070
|
+
throw new Error(
|
|
1071
|
+
`No eval result files matched: ${options.resultPatterns.join(", ")}`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
const reports = await Promise.all(
|
|
1075
|
+
resultFiles.map(async (resultFile) => {
|
|
1076
|
+
const json = await (0, import_node.readVitestJsonReportFile)(resultFile);
|
|
1077
|
+
return collectEvalReport(json, {
|
|
1078
|
+
workspace: options.workspace
|
|
1079
|
+
});
|
|
1080
|
+
})
|
|
1081
|
+
);
|
|
1082
|
+
const report = mergeEvalReports(reports);
|
|
1083
|
+
const gate = evaluateEvalGate(report, {
|
|
1084
|
+
failOnFailures: options.failOnFailures,
|
|
1085
|
+
minPassRate: options.minPassRate,
|
|
1086
|
+
minScoreAverage: options.minScoreAverage
|
|
1087
|
+
});
|
|
1088
|
+
const summary = renderJobSummary(report, {
|
|
1089
|
+
maxFailures: options.maxFailures,
|
|
1090
|
+
maxOutputChars: options.maxOutputChars,
|
|
1091
|
+
maxReasonChars: options.maxReasonChars,
|
|
1092
|
+
maxToolCalls: options.maxToolCalls,
|
|
1093
|
+
gate
|
|
1094
|
+
});
|
|
1095
|
+
if (options.summaryEnabled !== false) {
|
|
1096
|
+
if (options.summaryPath) {
|
|
1097
|
+
await (0, import_promises.appendFile)(options.summaryPath, `${summary}
|
|
1098
|
+
`);
|
|
1099
|
+
} else {
|
|
1100
|
+
console.log(summary);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (options.annotations) {
|
|
1104
|
+
const gateCommand = renderGateWorkflowCommand(gate);
|
|
1105
|
+
if (gateCommand) {
|
|
1106
|
+
console.log(gateCommand);
|
|
1107
|
+
}
|
|
1108
|
+
for (const command of renderWorkflowCommands(report, {
|
|
1109
|
+
maxAnnotations: options.maxAnnotations,
|
|
1110
|
+
gate
|
|
1111
|
+
})) {
|
|
1112
|
+
console.log(command);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
let checkRun;
|
|
1116
|
+
if (options.checkRun) {
|
|
1117
|
+
try {
|
|
1118
|
+
checkRun = await publishCheckRun(report, {
|
|
1119
|
+
checkRunId: options.checkRunId,
|
|
1120
|
+
maxAnnotations: options.maxAnnotations,
|
|
1121
|
+
maxFailures: options.maxFailures,
|
|
1122
|
+
maxOutputChars: options.maxOutputChars,
|
|
1123
|
+
maxReasonChars: options.maxReasonChars,
|
|
1124
|
+
maxToolCalls: options.maxToolCalls,
|
|
1125
|
+
name: options.checkName,
|
|
1126
|
+
repository: options.repository,
|
|
1127
|
+
sha: options.sha,
|
|
1128
|
+
detailsUrl: options.detailsUrl,
|
|
1129
|
+
externalId: options.externalId,
|
|
1130
|
+
token: options.token,
|
|
1131
|
+
gate
|
|
1132
|
+
});
|
|
1133
|
+
if (checkRun.status === "skipped") {
|
|
1134
|
+
options.warn?.(`GitHub Check Run skipped: ${checkRun.reason}`);
|
|
1135
|
+
} else if (checkRun.htmlUrl) {
|
|
1136
|
+
console.log(`published check run: ${checkRun.htmlUrl}`);
|
|
1137
|
+
} else if (checkRun.id !== void 0) {
|
|
1138
|
+
console.log(`published check run id: ${checkRun.id}`);
|
|
1139
|
+
}
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1142
|
+
if (options.failOnCheckError) {
|
|
1143
|
+
throw error;
|
|
1144
|
+
}
|
|
1145
|
+
options.warn?.(message);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const gateFailed = gate.enforced && !gate.ok;
|
|
1149
|
+
const wantsSoftFail = options.softFail ?? options.checkRun === true;
|
|
1150
|
+
const softFail = wantsSoftFail && checkRunPublished(checkRun);
|
|
1151
|
+
const shouldFail = gateFailed && !softFail;
|
|
1152
|
+
return {
|
|
1153
|
+
report,
|
|
1154
|
+
resultFiles,
|
|
1155
|
+
gate,
|
|
1156
|
+
shouldFail,
|
|
1157
|
+
checkRun
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function checkRunPublished(checkRun) {
|
|
1161
|
+
return checkRun?.status === "created" || checkRun?.status === "updated";
|
|
1162
|
+
}
|
|
762
1163
|
// Annotate the CommonJS export names for ESM import in node:
|
|
763
1164
|
0 && (module.exports = {
|
|
764
1165
|
buildCheckAnnotations,
|
|
765
1166
|
collectEvalReport,
|
|
1167
|
+
computePassRate,
|
|
1168
|
+
evaluateEvalGate,
|
|
1169
|
+
formatPercent,
|
|
766
1170
|
publishCheckRun,
|
|
1171
|
+
publishEvalReport,
|
|
1172
|
+
renderGateWorkflowCommand,
|
|
767
1173
|
renderJobSummary,
|
|
768
|
-
renderWorkflowCommands
|
|
1174
|
+
renderWorkflowCommands,
|
|
1175
|
+
resolveCheckDetailsUrl,
|
|
1176
|
+
resolveCheckSha
|
|
769
1177
|
});
|
|
770
1178
|
//# sourceMappingURL=index.js.map
|