@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/cli.mjs
CHANGED
|
@@ -40,6 +40,18 @@ function parseCliArgs(args, env = process.env) {
|
|
|
40
40
|
case "--fail-on-check-error":
|
|
41
41
|
options.failOnCheckError = true;
|
|
42
42
|
break;
|
|
43
|
+
case "--soft-fail":
|
|
44
|
+
options.softFail = true;
|
|
45
|
+
break;
|
|
46
|
+
case "--no-soft-fail":
|
|
47
|
+
options.softFail = false;
|
|
48
|
+
break;
|
|
49
|
+
case "--min-pass-rate":
|
|
50
|
+
options.minPassRate = readRatio(args, ++index, arg);
|
|
51
|
+
break;
|
|
52
|
+
case "--min-score-average":
|
|
53
|
+
options.minScoreAverage = readRatio(args, ++index, arg);
|
|
54
|
+
break;
|
|
43
55
|
case "--max-annotations":
|
|
44
56
|
options.maxAnnotations = readInteger(args, ++index, arg);
|
|
45
57
|
break;
|
|
@@ -99,6 +111,14 @@ function readInteger(args, index, flag) {
|
|
|
99
111
|
}
|
|
100
112
|
return Number(rawValue);
|
|
101
113
|
}
|
|
114
|
+
function readRatio(args, index, flag) {
|
|
115
|
+
const rawValue = readValue(args, index, flag);
|
|
116
|
+
const parsed = Number(rawValue);
|
|
117
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
118
|
+
throw new Error(`Invalid ratio for ${flag}`);
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
102
122
|
|
|
103
123
|
// src/report.ts
|
|
104
124
|
import { appendFile } from "fs/promises";
|
|
@@ -190,9 +210,10 @@ var DEFAULT_MAX_CHECK_ANNOTATIONS = 50;
|
|
|
190
210
|
var MAX_CHECK_FIELD_LENGTH = 64e3;
|
|
191
211
|
function renderWorkflowCommands(report, options = {}) {
|
|
192
212
|
const maxAnnotations = options.maxAnnotations ?? DEFAULT_MAX_WORKFLOW_ANNOTATIONS;
|
|
213
|
+
const command = caseAnnotationCommand(options.gate);
|
|
193
214
|
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map(
|
|
194
215
|
(testCase) => formatWorkflowCommand({
|
|
195
|
-
command
|
|
216
|
+
command,
|
|
196
217
|
properties: {
|
|
197
218
|
file: testCase.displayFile,
|
|
198
219
|
line: String(testCase.location.line),
|
|
@@ -208,11 +229,12 @@ function buildCheckAnnotations(report, options = {}) {
|
|
|
208
229
|
options.maxAnnotations ?? DEFAULT_MAX_CHECK_ANNOTATIONS,
|
|
209
230
|
DEFAULT_MAX_CHECK_ANNOTATIONS
|
|
210
231
|
);
|
|
232
|
+
const annotationLevel = caseAnnotationLevel(options.gate);
|
|
211
233
|
return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map((testCase) => ({
|
|
212
234
|
path: testCase.displayFile,
|
|
213
235
|
start_line: testCase.location.line,
|
|
214
236
|
end_line: testCase.location.line,
|
|
215
|
-
annotation_level:
|
|
237
|
+
annotation_level: annotationLevel,
|
|
216
238
|
title: truncate(
|
|
217
239
|
`${testCase.primaryFailure?.judgeName ?? "vitest-evals"} - ${testCase.displayName}`,
|
|
218
240
|
255
|
|
@@ -224,6 +246,12 @@ function buildCheckAnnotations(report, options = {}) {
|
|
|
224
246
|
raw_details: truncate(formatRawDetails(testCase), MAX_CHECK_FIELD_LENGTH)
|
|
225
247
|
}));
|
|
226
248
|
}
|
|
249
|
+
function caseAnnotationCommand(gate) {
|
|
250
|
+
return gate?.ok ? "warning" : "error";
|
|
251
|
+
}
|
|
252
|
+
function caseAnnotationLevel(gate) {
|
|
253
|
+
return gate?.ok ? "warning" : "failure";
|
|
254
|
+
}
|
|
227
255
|
function hasAnnotationLocation(testCase) {
|
|
228
256
|
return Boolean(testCase.location);
|
|
229
257
|
}
|
|
@@ -469,6 +497,167 @@ function toolCallCount(testCase) {
|
|
|
469
497
|
return testCase.toolCalls.length;
|
|
470
498
|
}
|
|
471
499
|
|
|
500
|
+
// src/gate.ts
|
|
501
|
+
function evaluateEvalGate(report, policy = {}) {
|
|
502
|
+
const minPassRate = resolveMinPassRate(policy);
|
|
503
|
+
const minScoreAverage = policy.minScoreAverage;
|
|
504
|
+
const enforced = minPassRate !== void 0 || minScoreAverage !== void 0 || policy.failOnFailures === true;
|
|
505
|
+
const passRate = computePassRate(report);
|
|
506
|
+
const counts = formatEvalCounts(report, passRate);
|
|
507
|
+
if (!enforced) {
|
|
508
|
+
const ok = report.status === "passed";
|
|
509
|
+
return {
|
|
510
|
+
ok,
|
|
511
|
+
status: ok ? "passed" : "failed",
|
|
512
|
+
enforced: false,
|
|
513
|
+
passRate,
|
|
514
|
+
title: defaultCheckTitle(report),
|
|
515
|
+
message: ok ? `eval report passed: ${counts}` : `eval report failed: ${counts}`
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const nonEvalFailures = Math.max(
|
|
519
|
+
0,
|
|
520
|
+
report.totals.failed - report.totals.evalFailed
|
|
521
|
+
);
|
|
522
|
+
if (nonEvalFailures > 0) {
|
|
523
|
+
return {
|
|
524
|
+
ok: false,
|
|
525
|
+
status: "failed",
|
|
526
|
+
enforced: true,
|
|
527
|
+
passRate,
|
|
528
|
+
title: "Eval report hard failure",
|
|
529
|
+
message: `${formatNumber(nonEvalFailures)} non-eval test failure${nonEvalFailures === 1 ? "" : "s"}; ${counts}`
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (report.totals.evalTotal === 0) {
|
|
533
|
+
return {
|
|
534
|
+
ok: false,
|
|
535
|
+
status: "failed",
|
|
536
|
+
enforced: true,
|
|
537
|
+
passRate: null,
|
|
538
|
+
title: "Eval report hard failure",
|
|
539
|
+
message: "no eval cases were reported"
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
if (report.status === "failed" && report.totals.failed === 0 && report.failures.length === 0) {
|
|
543
|
+
return {
|
|
544
|
+
ok: false,
|
|
545
|
+
status: "failed",
|
|
546
|
+
enforced: true,
|
|
547
|
+
passRate,
|
|
548
|
+
title: "Eval report hard failure",
|
|
549
|
+
message: `vitest run failed without counted test failures; ${counts}`
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
553
|
+
return {
|
|
554
|
+
ok: false,
|
|
555
|
+
status: "failed",
|
|
556
|
+
enforced: true,
|
|
557
|
+
passRate,
|
|
558
|
+
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
559
|
+
message: `eval pass rate below floor: ${counts}; required >= ${formatPercent(minPassRate)}`
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (minScoreAverage !== void 0) {
|
|
563
|
+
const average = report.score?.average;
|
|
564
|
+
if (average === void 0 || !Number.isFinite(average)) {
|
|
565
|
+
return {
|
|
566
|
+
ok: false,
|
|
567
|
+
status: "failed",
|
|
568
|
+
enforced: true,
|
|
569
|
+
passRate,
|
|
570
|
+
title: "Eval score gate failed",
|
|
571
|
+
message: `no score average available; required avg score >= ${formatScore(minScoreAverage)}`
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (average + Number.EPSILON < minScoreAverage) {
|
|
575
|
+
return {
|
|
576
|
+
ok: false,
|
|
577
|
+
status: "failed",
|
|
578
|
+
enforced: true,
|
|
579
|
+
passRate,
|
|
580
|
+
title: `Avg score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
581
|
+
message: `avg score below floor: ${counts}; required avg score >= ${formatScore(minScoreAverage)}`
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
ok: true,
|
|
587
|
+
status: "passed",
|
|
588
|
+
enforced: true,
|
|
589
|
+
passRate,
|
|
590
|
+
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
591
|
+
message: `eval gate passed: ${counts}${formatFloorSuffix(minPassRate, minScoreAverage)}`
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function computePassRate(report) {
|
|
595
|
+
if (report.totals.evalTotal <= 0) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
return report.totals.evalPassed / report.totals.evalTotal;
|
|
599
|
+
}
|
|
600
|
+
function formatPercent(value) {
|
|
601
|
+
if (value == null || !Number.isFinite(value)) {
|
|
602
|
+
return "n/a";
|
|
603
|
+
}
|
|
604
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
605
|
+
}
|
|
606
|
+
function renderGateWorkflowCommand(gate) {
|
|
607
|
+
if (gate.ok || !gate.enforced) {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
return `::error title=${escapeCommandProperty(gate.title)}::${escapeCommandData(gate.message)}`;
|
|
611
|
+
}
|
|
612
|
+
function resolveMinPassRate(policy) {
|
|
613
|
+
const configured = policy.minPassRate;
|
|
614
|
+
if (policy.failOnFailures) {
|
|
615
|
+
if (configured === void 0) {
|
|
616
|
+
return 1;
|
|
617
|
+
}
|
|
618
|
+
return Math.max(configured, 1);
|
|
619
|
+
}
|
|
620
|
+
return configured;
|
|
621
|
+
}
|
|
622
|
+
function defaultCheckTitle(report) {
|
|
623
|
+
if (report.failures.length === 0 && report.status === "passed") {
|
|
624
|
+
return "No eval failures";
|
|
625
|
+
}
|
|
626
|
+
if (report.failures.length === 0) {
|
|
627
|
+
return "Vitest run failed";
|
|
628
|
+
}
|
|
629
|
+
return `${report.failures.length} eval failure${report.failures.length === 1 ? "" : "s"}`;
|
|
630
|
+
}
|
|
631
|
+
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
632
|
+
if (minPassRate !== void 0) {
|
|
633
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 floor ${formatPercent(minPassRate)}`;
|
|
634
|
+
}
|
|
635
|
+
if (minScoreAverage !== void 0) {
|
|
636
|
+
return `Avg score ${formatScore(report.score?.average)} \u2014 floor ${formatScore(minScoreAverage)}`;
|
|
637
|
+
}
|
|
638
|
+
return defaultCheckTitle(report);
|
|
639
|
+
}
|
|
640
|
+
function formatEvalCounts(report, passRate) {
|
|
641
|
+
const scoreText = report.score?.average === void 0 ? "n/a" : formatScore(report.score.average);
|
|
642
|
+
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
643
|
+
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
644
|
+
report.totals.evalTotal
|
|
645
|
+
)} passed (${passRateText}), avg score ${scoreText}`;
|
|
646
|
+
}
|
|
647
|
+
function formatFloorSuffix(minPassRate, minScoreAverage) {
|
|
648
|
+
const parts = [];
|
|
649
|
+
if (minPassRate !== void 0) {
|
|
650
|
+
parts.push(`pass rate floor ${formatPercent(minPassRate)}`);
|
|
651
|
+
}
|
|
652
|
+
if (minScoreAverage !== void 0) {
|
|
653
|
+
parts.push(`avg score floor ${formatScore(minScoreAverage)}`);
|
|
654
|
+
}
|
|
655
|
+
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/github.ts
|
|
659
|
+
import { readFileSync } from "fs";
|
|
660
|
+
|
|
472
661
|
// src/summary.ts
|
|
473
662
|
var DEFAULT_MAX_FAILURES = 20;
|
|
474
663
|
var DEFAULT_MAX_REASON_CHARS = 8e3;
|
|
@@ -489,20 +678,22 @@ function renderJobSummary(report, options = {}) {
|
|
|
489
678
|
const lines = [
|
|
490
679
|
"# vitest-evals",
|
|
491
680
|
"",
|
|
492
|
-
...renderSummaryTable(report, nonEvalFailures),
|
|
681
|
+
...renderSummaryTable(report, nonEvalFailures, options.gate),
|
|
493
682
|
"",
|
|
494
683
|
...renderScoreDistribution(report),
|
|
495
684
|
"## Results",
|
|
496
685
|
""
|
|
497
686
|
];
|
|
498
687
|
if (report.failures.length > 0) {
|
|
499
|
-
|
|
688
|
+
const failureHeading = options.gate?.ok === true ? "### Quality Misses" : "### Failures";
|
|
689
|
+
lines.push(failureHeading, "");
|
|
500
690
|
failures.forEach((testCase, index) => {
|
|
501
691
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
502
692
|
});
|
|
503
693
|
if (report.failures.length > failures.length) {
|
|
694
|
+
const omittedLabel = options.gate?.ok === true ? "quality misses" : "failures";
|
|
504
695
|
lines.push(
|
|
505
|
-
`${report.failures.length - failures.length} more
|
|
696
|
+
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
506
697
|
""
|
|
507
698
|
);
|
|
508
699
|
}
|
|
@@ -518,9 +709,9 @@ function renderJobSummary(report, options = {}) {
|
|
|
518
709
|
function formatCountLine(passed, failed, total) {
|
|
519
710
|
return `${formatNumber(passed)} passed, ${formatNumber(failed)} failed, ${formatNumber(total)} total`;
|
|
520
711
|
}
|
|
521
|
-
function renderSummaryTable(report, nonEvalFailures) {
|
|
712
|
+
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
522
713
|
const rows = [
|
|
523
|
-
["Status", report.status],
|
|
714
|
+
["Status", gate?.status ?? report.status],
|
|
524
715
|
[
|
|
525
716
|
"Evals",
|
|
526
717
|
formatCountLine(
|
|
@@ -530,9 +721,20 @@ function renderSummaryTable(report, nonEvalFailures) {
|
|
|
530
721
|
)
|
|
531
722
|
]
|
|
532
723
|
];
|
|
724
|
+
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
725
|
+
rows.push(["Pass Rate", formatPercent(gate.passRate)]);
|
|
726
|
+
} else if (report.totals.evalTotal > 0) {
|
|
727
|
+
rows.push([
|
|
728
|
+
"Pass Rate",
|
|
729
|
+
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
730
|
+
]);
|
|
731
|
+
}
|
|
533
732
|
if (report.score) {
|
|
534
733
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
535
734
|
}
|
|
735
|
+
if (gate?.enforced) {
|
|
736
|
+
rows.push(["Gate", gate.message]);
|
|
737
|
+
}
|
|
536
738
|
if (nonEvalFailures > 0) {
|
|
537
739
|
rows.push([
|
|
538
740
|
"Other Failures",
|
|
@@ -756,10 +958,44 @@ function formatCaseUsage(testCase) {
|
|
|
756
958
|
var DEFAULT_CHECK_NAME = "vitest-evals";
|
|
757
959
|
var MAX_CHECK_SUMMARY_LENGTH = 64e3;
|
|
758
960
|
var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
|
|
961
|
+
function resolveCheckSha(env = process.env, options = {}) {
|
|
962
|
+
const explicit = options.sha?.trim() || env.GITHUB_PR_HEAD_SHA?.trim();
|
|
963
|
+
if (explicit) {
|
|
964
|
+
return explicit;
|
|
965
|
+
}
|
|
966
|
+
const eventPath = options.eventPath?.trim() || env.GITHUB_EVENT_PATH?.trim();
|
|
967
|
+
if (eventPath) {
|
|
968
|
+
try {
|
|
969
|
+
const event = JSON.parse(readFileSync(eventPath, "utf8"));
|
|
970
|
+
const headSha = event.pull_request?.head?.sha;
|
|
971
|
+
if (typeof headSha === "string" && headSha.trim()) {
|
|
972
|
+
return headSha.trim();
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return env.GITHUB_SHA?.trim() || void 0;
|
|
978
|
+
}
|
|
979
|
+
function resolveCheckDetailsUrl(env = process.env, options = {}) {
|
|
980
|
+
const explicit = options.detailsUrl?.trim();
|
|
981
|
+
if (explicit) {
|
|
982
|
+
return explicit;
|
|
983
|
+
}
|
|
984
|
+
const server = env.GITHUB_SERVER_URL?.replace(/\/$/, "");
|
|
985
|
+
const repository = env.GITHUB_REPOSITORY?.trim();
|
|
986
|
+
const runId = env.GITHUB_RUN_ID?.trim();
|
|
987
|
+
if (!server || !repository || !runId) {
|
|
988
|
+
return void 0;
|
|
989
|
+
}
|
|
990
|
+
return `${server}/${repository}/actions/runs/${runId}`;
|
|
991
|
+
}
|
|
759
992
|
async function publishCheckRun(report, options = {}) {
|
|
760
993
|
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
761
994
|
const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
|
|
762
|
-
const sha = options.sha
|
|
995
|
+
const sha = resolveCheckSha(process.env, { sha: options.sha });
|
|
996
|
+
const detailsUrl = resolveCheckDetailsUrl(process.env, {
|
|
997
|
+
detailsUrl: options.detailsUrl
|
|
998
|
+
});
|
|
763
999
|
if (!token) {
|
|
764
1000
|
return { status: "skipped", reason: "missing GITHUB_TOKEN" };
|
|
765
1001
|
}
|
|
@@ -767,7 +1003,10 @@ async function publishCheckRun(report, options = {}) {
|
|
|
767
1003
|
return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
|
|
768
1004
|
}
|
|
769
1005
|
if (!sha && options.checkRunId === void 0) {
|
|
770
|
-
return {
|
|
1006
|
+
return {
|
|
1007
|
+
status: "skipped",
|
|
1008
|
+
reason: "missing commit SHA (set --sha / options.sha, GITHUB_PR_HEAD_SHA, pull_request.head.sha, or GITHUB_SHA)"
|
|
1009
|
+
};
|
|
771
1010
|
}
|
|
772
1011
|
const [owner, repo] = repository.split("/");
|
|
773
1012
|
if (!owner || !repo) {
|
|
@@ -776,7 +1015,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
776
1015
|
reason: `invalid GitHub repository: ${repository}`
|
|
777
1016
|
};
|
|
778
1017
|
}
|
|
779
|
-
const payload = buildCheckRunPayload(report, options);
|
|
1018
|
+
const payload = buildCheckRunPayload(report, options, detailsUrl);
|
|
780
1019
|
const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
781
1020
|
const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
|
|
782
1021
|
const response = await fetch(requestUrl, {
|
|
@@ -791,6 +1030,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
791
1030
|
options.checkRunId === void 0 ? {
|
|
792
1031
|
name: options.name ?? DEFAULT_CHECK_NAME,
|
|
793
1032
|
head_sha: sha,
|
|
1033
|
+
...options.externalId ? { external_id: options.externalId } : {},
|
|
794
1034
|
...payload
|
|
795
1035
|
} : payload
|
|
796
1036
|
)
|
|
@@ -805,27 +1045,31 @@ async function publishCheckRun(report, options = {}) {
|
|
|
805
1045
|
return {
|
|
806
1046
|
status: options.checkRunId === void 0 ? "created" : "updated",
|
|
807
1047
|
id: data.id,
|
|
808
|
-
htmlUrl: data.html_url
|
|
1048
|
+
htmlUrl: data.html_url,
|
|
1049
|
+
sha
|
|
809
1050
|
};
|
|
810
1051
|
}
|
|
811
|
-
function buildCheckRunPayload(report, options) {
|
|
1052
|
+
function buildCheckRunPayload(report, options, detailsUrl) {
|
|
1053
|
+
const gate = options.gate ?? evaluateEvalGate(report);
|
|
812
1054
|
const annotations = buildCheckAnnotations(report, {
|
|
813
|
-
maxAnnotations: options.maxAnnotations
|
|
1055
|
+
maxAnnotations: options.maxAnnotations,
|
|
1056
|
+
gate
|
|
814
1057
|
});
|
|
815
|
-
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"}`;
|
|
816
1058
|
return {
|
|
817
1059
|
status: "completed",
|
|
818
|
-
conclusion:
|
|
1060
|
+
conclusion: gate.ok ? "success" : "failure",
|
|
819
1061
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1062
|
+
...detailsUrl ? { details_url: detailsUrl } : {},
|
|
820
1063
|
output: {
|
|
821
|
-
title,
|
|
1064
|
+
title: gate.title,
|
|
822
1065
|
summary: truncateCheckSummary(
|
|
823
1066
|
renderJobSummary(report, {
|
|
824
1067
|
...options,
|
|
825
1068
|
maxFailures: options.maxFailures ?? 5,
|
|
826
1069
|
maxReasonChars: options.maxReasonChars ?? 4e3,
|
|
827
1070
|
maxOutputChars: options.maxOutputChars ?? 2e3,
|
|
828
|
-
maxToolCalls: options.maxToolCalls ?? 10
|
|
1071
|
+
maxToolCalls: options.maxToolCalls ?? 10,
|
|
1072
|
+
gate
|
|
829
1073
|
})
|
|
830
1074
|
),
|
|
831
1075
|
annotations
|
|
@@ -924,11 +1168,17 @@ async function publishEvalReport(options) {
|
|
|
924
1168
|
})
|
|
925
1169
|
);
|
|
926
1170
|
const report = mergeEvalReports(reports);
|
|
1171
|
+
const gate = evaluateEvalGate(report, {
|
|
1172
|
+
failOnFailures: options.failOnFailures,
|
|
1173
|
+
minPassRate: options.minPassRate,
|
|
1174
|
+
minScoreAverage: options.minScoreAverage
|
|
1175
|
+
});
|
|
927
1176
|
const summary = renderJobSummary(report, {
|
|
928
1177
|
maxFailures: options.maxFailures,
|
|
929
1178
|
maxOutputChars: options.maxOutputChars,
|
|
930
1179
|
maxReasonChars: options.maxReasonChars,
|
|
931
|
-
maxToolCalls: options.maxToolCalls
|
|
1180
|
+
maxToolCalls: options.maxToolCalls,
|
|
1181
|
+
gate
|
|
932
1182
|
});
|
|
933
1183
|
if (options.summaryEnabled !== false) {
|
|
934
1184
|
if (options.summaryPath) {
|
|
@@ -939,8 +1189,13 @@ async function publishEvalReport(options) {
|
|
|
939
1189
|
}
|
|
940
1190
|
}
|
|
941
1191
|
if (options.annotations) {
|
|
1192
|
+
const gateCommand = renderGateWorkflowCommand(gate);
|
|
1193
|
+
if (gateCommand) {
|
|
1194
|
+
console.log(gateCommand);
|
|
1195
|
+
}
|
|
942
1196
|
for (const command of renderWorkflowCommands(report, {
|
|
943
|
-
maxAnnotations: options.maxAnnotations
|
|
1197
|
+
maxAnnotations: options.maxAnnotations,
|
|
1198
|
+
gate
|
|
944
1199
|
})) {
|
|
945
1200
|
console.log(command);
|
|
946
1201
|
}
|
|
@@ -958,10 +1213,17 @@ async function publishEvalReport(options) {
|
|
|
958
1213
|
name: options.checkName,
|
|
959
1214
|
repository: options.repository,
|
|
960
1215
|
sha: options.sha,
|
|
961
|
-
|
|
1216
|
+
detailsUrl: options.detailsUrl,
|
|
1217
|
+
externalId: options.externalId,
|
|
1218
|
+
token: options.token,
|
|
1219
|
+
gate
|
|
962
1220
|
});
|
|
963
1221
|
if (checkRun.status === "skipped") {
|
|
964
1222
|
options.warn?.(`GitHub Check Run skipped: ${checkRun.reason}`);
|
|
1223
|
+
} else if (checkRun.htmlUrl) {
|
|
1224
|
+
console.log(`published check run: ${checkRun.htmlUrl}`);
|
|
1225
|
+
} else if (checkRun.id !== void 0) {
|
|
1226
|
+
console.log(`published check run id: ${checkRun.id}`);
|
|
965
1227
|
}
|
|
966
1228
|
} catch (error) {
|
|
967
1229
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -971,12 +1233,21 @@ async function publishEvalReport(options) {
|
|
|
971
1233
|
options.warn?.(message);
|
|
972
1234
|
}
|
|
973
1235
|
}
|
|
1236
|
+
const gateFailed = gate.enforced && !gate.ok;
|
|
1237
|
+
const wantsSoftFail = options.softFail ?? options.checkRun === true;
|
|
1238
|
+
const softFail = wantsSoftFail && checkRunPublished(checkRun);
|
|
1239
|
+
const shouldFail = gateFailed && !softFail;
|
|
974
1240
|
return {
|
|
975
1241
|
report,
|
|
976
1242
|
resultFiles,
|
|
1243
|
+
gate,
|
|
1244
|
+
shouldFail,
|
|
977
1245
|
checkRun
|
|
978
1246
|
};
|
|
979
1247
|
}
|
|
1248
|
+
function checkRunPublished(checkRun) {
|
|
1249
|
+
return checkRun?.status === "created" || checkRun?.status === "updated";
|
|
1250
|
+
}
|
|
980
1251
|
|
|
981
1252
|
// src/cli.ts
|
|
982
1253
|
main().catch((error) => {
|
|
@@ -1000,6 +1271,10 @@ async function main() {
|
|
|
1000
1271
|
checkRunId: options.checkRunId,
|
|
1001
1272
|
checkName: options.checkName,
|
|
1002
1273
|
failOnCheckError: options.failOnCheckError,
|
|
1274
|
+
failOnFailures: options.failOnFailures,
|
|
1275
|
+
softFail: options.softFail,
|
|
1276
|
+
minPassRate: options.minPassRate,
|
|
1277
|
+
minScoreAverage: options.minScoreAverage,
|
|
1003
1278
|
maxAnnotations: options.maxAnnotations,
|
|
1004
1279
|
maxFailures: options.maxFailures,
|
|
1005
1280
|
repository: options.repository,
|
|
@@ -1007,7 +1282,8 @@ async function main() {
|
|
|
1007
1282
|
token: options.token,
|
|
1008
1283
|
warn
|
|
1009
1284
|
});
|
|
1010
|
-
if (
|
|
1285
|
+
if (result.shouldFail) {
|
|
1286
|
+
console.error(result.gate.message);
|
|
1011
1287
|
process.exitCode = 1;
|
|
1012
1288
|
}
|
|
1013
1289
|
}
|
|
@@ -1029,13 +1305,17 @@ function usage() {
|
|
|
1029
1305
|
" --annotations Emit GitHub workflow-command annotations",
|
|
1030
1306
|
" --no-annotations Disable workflow-command annotations",
|
|
1031
1307
|
" --check-run Publish a GitHub Check Run when configured",
|
|
1032
|
-
" --fail-on-failures Exit non-zero when
|
|
1308
|
+
" --fail-on-failures Exit non-zero when any eval case failed",
|
|
1309
|
+
" --min-pass-rate <0-1> Exit non-zero when eval pass rate is below this floor",
|
|
1310
|
+
" --min-score-average <0-1> Exit non-zero when average score is below this floor",
|
|
1311
|
+
" --soft-fail Keep exit 0 when a published Check Run owns gate status",
|
|
1312
|
+
" --no-soft-fail Always exit non-zero when an enforced gate fails",
|
|
1033
1313
|
" --fail-on-check-error Fail when Check Run publishing fails",
|
|
1034
1314
|
" --check-run-id <id> Update an existing Check Run",
|
|
1035
1315
|
" --check-name <name> Check Run name (default: vitest-evals)",
|
|
1036
1316
|
" --token <token> GitHub token (default: GITHUB_TOKEN)",
|
|
1037
1317
|
" --repo <owner/repo> GitHub repository (default: GITHUB_REPOSITORY)",
|
|
1038
|
-
" --sha <sha> Git commit SHA (default: GITHUB_SHA)",
|
|
1318
|
+
" --sha <sha> Git commit SHA (default: PR head, then GITHUB_SHA)",
|
|
1039
1319
|
" --workspace <path> Workspace path for relative annotation files",
|
|
1040
1320
|
" --max-annotations <n> Maximum annotations to emit",
|
|
1041
1321
|
" --max-failures <n> Maximum failures to include in details"
|