@vitest-evals/github-reporter 0.16.1 → 0.17.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/dist/cli.js +150 -43
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +150 -43
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +150 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -128,7 +128,8 @@ function collectEvalReport(input, options = {}) {
|
|
|
128
128
|
const cases = workspace.cases.map(collectEvalCase);
|
|
129
129
|
const failures = cases.filter((testCase) => testCase.status === "failed");
|
|
130
130
|
const evalScores = cases.map((testCase) => testCase.eval?.avgScore).filter((score) => isFiniteNumber(score));
|
|
131
|
-
const usage =
|
|
131
|
+
const usage = sumAppUsage(cases);
|
|
132
|
+
const judgeUsage = sumJudgeUsage(cases);
|
|
132
133
|
const durationMs = workspace.runs[0]?.durationMs;
|
|
133
134
|
return {
|
|
134
135
|
status: input.success && failures.length === 0 ? "passed" : "failed",
|
|
@@ -148,6 +149,7 @@ function collectEvalReport(input, options = {}) {
|
|
|
148
149
|
minimum: Math.min(...evalScores)
|
|
149
150
|
} : void 0,
|
|
150
151
|
usage,
|
|
152
|
+
judgeUsage,
|
|
151
153
|
cases,
|
|
152
154
|
failures
|
|
153
155
|
};
|
|
@@ -272,24 +274,67 @@ function stringifyReason(value) {
|
|
|
272
274
|
}
|
|
273
275
|
return typeof value === "string" ? value : stringifyValue(value, 4e3);
|
|
274
276
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
+
function emptyUsage() {
|
|
278
|
+
return {
|
|
277
279
|
inputTokens: 0,
|
|
278
280
|
outputTokens: 0,
|
|
279
281
|
reasoningTokens: 0,
|
|
280
282
|
totalTokens: 0,
|
|
281
283
|
toolCalls: 0
|
|
282
284
|
};
|
|
285
|
+
}
|
|
286
|
+
function addRunUsage(total, usage) {
|
|
287
|
+
total.inputTokens += usage?.inputTokens ?? 0;
|
|
288
|
+
total.outputTokens += usage?.outputTokens ?? 0;
|
|
289
|
+
total.reasoningTokens += usage?.reasoningTokens ?? 0;
|
|
290
|
+
total.totalTokens += usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0);
|
|
291
|
+
if (usage?.costUsd !== void 0) {
|
|
292
|
+
total.costUsd = (total.costUsd ?? 0) + usage.costUsd;
|
|
293
|
+
}
|
|
294
|
+
total.toolCalls += usage?.toolCalls ?? 0;
|
|
295
|
+
}
|
|
296
|
+
function sumAppUsage(cases) {
|
|
297
|
+
const usage = emptyUsage();
|
|
298
|
+
const runUsages = cases.map(appUsageForCase).filter((item) => item !== void 0);
|
|
299
|
+
for (const runUsage of runUsages) {
|
|
300
|
+
addRunUsage(usage, runUsage);
|
|
301
|
+
}
|
|
302
|
+
omitPartialCost(usage, runUsages);
|
|
303
|
+
return usage;
|
|
304
|
+
}
|
|
305
|
+
function appUsageForCase(testCase) {
|
|
306
|
+
const usage = testCase.harness?.usage;
|
|
307
|
+
const effectiveToolCalls = toolCallCount(testCase);
|
|
308
|
+
if (!usage && effectiveToolCalls === 0) {
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
...usage,
|
|
313
|
+
...effectiveToolCalls > 0 ? { toolCalls: effectiveToolCalls } : {}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function sumJudgeUsage(cases) {
|
|
317
|
+
const usage = emptyUsage();
|
|
318
|
+
const runUsages = [];
|
|
283
319
|
for (const testCase of cases) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
320
|
+
for (const score of testCase.eval?.scores ?? []) {
|
|
321
|
+
for (const run of score.judgeRuns ?? []) {
|
|
322
|
+
runUsages.push(run.usage);
|
|
323
|
+
addRunUsage(usage, run.usage);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
290
326
|
}
|
|
327
|
+
omitPartialCost(usage, runUsages);
|
|
291
328
|
return usage;
|
|
292
329
|
}
|
|
330
|
+
function omitPartialCost(total, usages) {
|
|
331
|
+
if (usages.some(hasUsageWithoutCost)) {
|
|
332
|
+
total.costUsd = void 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function hasUsageWithoutCost(usage) {
|
|
336
|
+
return usage?.costUsd === void 0 && ((usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0)) > 0 || (usage?.toolCalls ?? 0) > 0);
|
|
337
|
+
}
|
|
293
338
|
function toolCallCount(testCase) {
|
|
294
339
|
const usageToolCalls = testCase.harness?.usage?.toolCalls;
|
|
295
340
|
if (usageToolCalls !== void 0) {
|
|
@@ -433,8 +478,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
433
478
|
status: "failed",
|
|
434
479
|
enforced: true,
|
|
435
480
|
passRate,
|
|
436
|
-
title: "Eval
|
|
437
|
-
message: `${formatNumber(nonEvalFailures)}
|
|
481
|
+
title: "Eval run failed",
|
|
482
|
+
message: `${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases; ${counts}`
|
|
438
483
|
};
|
|
439
484
|
}
|
|
440
485
|
if (report.totals.evalTotal === 0) {
|
|
@@ -443,7 +488,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
443
488
|
status: "failed",
|
|
444
489
|
enforced: true,
|
|
445
490
|
passRate: null,
|
|
446
|
-
title: "Eval
|
|
491
|
+
title: "Eval run failed",
|
|
447
492
|
message: "no eval cases were reported"
|
|
448
493
|
};
|
|
449
494
|
}
|
|
@@ -453,8 +498,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
453
498
|
status: "failed",
|
|
454
499
|
enforced: true,
|
|
455
500
|
passRate,
|
|
456
|
-
title: "Eval
|
|
457
|
-
message: `
|
|
501
|
+
title: "Eval run failed",
|
|
502
|
+
message: `Vitest failed without reporting a failed test; ${counts}`
|
|
458
503
|
};
|
|
459
504
|
}
|
|
460
505
|
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
@@ -464,7 +509,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
464
509
|
enforced: true,
|
|
465
510
|
passRate,
|
|
466
511
|
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
467
|
-
message: `
|
|
512
|
+
message: `pass rate is below the minimum: ${counts}; minimum ${formatPercent(minPassRate)}`
|
|
468
513
|
};
|
|
469
514
|
}
|
|
470
515
|
if (minScoreAverage !== void 0) {
|
|
@@ -475,8 +520,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
475
520
|
status: "failed",
|
|
476
521
|
enforced: true,
|
|
477
522
|
passRate,
|
|
478
|
-
title: "
|
|
479
|
-
message: `no score
|
|
523
|
+
title: "Average score unavailable",
|
|
524
|
+
message: `no average score was reported; minimum ${formatScore(minScoreAverage)}`
|
|
480
525
|
};
|
|
481
526
|
}
|
|
482
527
|
if (average + Number.EPSILON < minScoreAverage) {
|
|
@@ -485,8 +530,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
485
530
|
status: "failed",
|
|
486
531
|
enforced: true,
|
|
487
532
|
passRate,
|
|
488
|
-
title: `
|
|
489
|
-
message: `
|
|
533
|
+
title: `Average score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
534
|
+
message: `average score is below the minimum: ${counts}; minimum ${formatScore(minScoreAverage)}`
|
|
490
535
|
};
|
|
491
536
|
}
|
|
492
537
|
}
|
|
@@ -496,7 +541,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
496
541
|
enforced: true,
|
|
497
542
|
passRate,
|
|
498
543
|
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
499
|
-
message: `
|
|
544
|
+
message: `requirements met: ${counts}${formatMinimumSuffix(minPassRate, minScoreAverage)}`
|
|
500
545
|
};
|
|
501
546
|
}
|
|
502
547
|
function computePassRate(report) {
|
|
@@ -529,7 +574,7 @@ function resolveMinPassRate(policy) {
|
|
|
529
574
|
}
|
|
530
575
|
function defaultCheckTitle(report) {
|
|
531
576
|
if (report.failures.length === 0 && report.status === "passed") {
|
|
532
|
-
return "No eval
|
|
577
|
+
return "No eval cases failed";
|
|
533
578
|
}
|
|
534
579
|
if (report.failures.length === 0) {
|
|
535
580
|
return "Vitest run failed";
|
|
@@ -538,10 +583,10 @@ function defaultCheckTitle(report) {
|
|
|
538
583
|
}
|
|
539
584
|
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
540
585
|
if (minPassRate !== void 0) {
|
|
541
|
-
return `Eval pass rate ${formatPercent(passRate)} \u2014
|
|
586
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 minimum ${formatPercent(minPassRate)}`;
|
|
542
587
|
}
|
|
543
588
|
if (minScoreAverage !== void 0) {
|
|
544
|
-
return `
|
|
589
|
+
return `Average score ${formatScore(report.score?.average)} \u2014 minimum ${formatScore(minScoreAverage)}`;
|
|
545
590
|
}
|
|
546
591
|
return defaultCheckTitle(report);
|
|
547
592
|
}
|
|
@@ -550,15 +595,15 @@ function formatEvalCounts(report, passRate) {
|
|
|
550
595
|
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
551
596
|
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
552
597
|
report.totals.evalTotal
|
|
553
|
-
)} passed (${passRateText}),
|
|
598
|
+
)} passed (${passRateText}), average score ${scoreText}`;
|
|
554
599
|
}
|
|
555
|
-
function
|
|
600
|
+
function formatMinimumSuffix(minPassRate, minScoreAverage) {
|
|
556
601
|
const parts = [];
|
|
557
602
|
if (minPassRate !== void 0) {
|
|
558
|
-
parts.push(`pass rate
|
|
603
|
+
parts.push(`minimum pass rate ${formatPercent(minPassRate)}`);
|
|
559
604
|
}
|
|
560
605
|
if (minScoreAverage !== void 0) {
|
|
561
|
-
parts.push(`
|
|
606
|
+
parts.push(`minimum average score ${formatScore(minScoreAverage)}`);
|
|
562
607
|
}
|
|
563
608
|
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
564
609
|
}
|
|
@@ -593,23 +638,23 @@ function renderJobSummary(report, options = {}) {
|
|
|
593
638
|
""
|
|
594
639
|
];
|
|
595
640
|
if (report.failures.length > 0) {
|
|
596
|
-
const failureHeading = options.gate?.ok === true ? "###
|
|
641
|
+
const failureHeading = options.gate?.ok === true ? "### Cases Below Target" : "### Failures";
|
|
597
642
|
lines.push(failureHeading, "");
|
|
598
643
|
failures.forEach((testCase, index) => {
|
|
599
644
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
600
645
|
});
|
|
601
646
|
if (report.failures.length > failures.length) {
|
|
602
|
-
const omittedLabel = options.gate?.ok === true ? "
|
|
647
|
+
const omittedLabel = options.gate?.ok === true ? "cases below target" : "failures";
|
|
603
648
|
lines.push(
|
|
604
649
|
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
605
650
|
""
|
|
606
651
|
);
|
|
607
652
|
}
|
|
608
653
|
} else if (report.totals.evalTotal > 0) {
|
|
609
|
-
lines.push("### Failures", "", "No eval
|
|
654
|
+
lines.push("### Failures", "", "No eval cases failed.", "");
|
|
610
655
|
}
|
|
611
656
|
if (report.totals.evalTotal === 0) {
|
|
612
|
-
lines.push("No eval
|
|
657
|
+
lines.push("No eval results were found in the Vitest JSON report.", "");
|
|
613
658
|
}
|
|
614
659
|
return `${lines.join("\n")}
|
|
615
660
|
`;
|
|
@@ -619,9 +664,9 @@ function formatCountLine(passed, failed, total) {
|
|
|
619
664
|
}
|
|
620
665
|
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
621
666
|
const rows = [
|
|
622
|
-
["Status", gate?.status ?? report.status],
|
|
667
|
+
["Status", capitalize(gate?.status ?? report.status)],
|
|
623
668
|
[
|
|
624
|
-
"
|
|
669
|
+
"Eval cases",
|
|
625
670
|
formatCountLine(
|
|
626
671
|
report.totals.evalPassed,
|
|
627
672
|
report.totals.evalFailed,
|
|
@@ -630,23 +675,31 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
630
675
|
]
|
|
631
676
|
];
|
|
632
677
|
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
633
|
-
rows.push(["Pass
|
|
678
|
+
rows.push(["Pass rate", formatPercent(gate.passRate)]);
|
|
634
679
|
} else if (report.totals.evalTotal > 0) {
|
|
635
680
|
rows.push([
|
|
636
|
-
"Pass
|
|
681
|
+
"Pass rate",
|
|
637
682
|
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
638
683
|
]);
|
|
639
684
|
}
|
|
640
685
|
if (report.score) {
|
|
641
686
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
642
687
|
}
|
|
688
|
+
if (hasUsage(report.usage) || hasUsage(report.judgeUsage)) {
|
|
689
|
+
rows.push(["App usage", formatUsage(report.usage)]);
|
|
690
|
+
rows.push(["Judge usage", formatUsage(report.judgeUsage)]);
|
|
691
|
+
rows.push([
|
|
692
|
+
"Total usage",
|
|
693
|
+
formatUsage(sumUsage(report.usage, report.judgeUsage))
|
|
694
|
+
]);
|
|
695
|
+
}
|
|
643
696
|
if (gate?.enforced) {
|
|
644
|
-
rows.push(["
|
|
697
|
+
rows.push(["Requirements", gate.message]);
|
|
645
698
|
}
|
|
646
699
|
if (nonEvalFailures > 0) {
|
|
647
700
|
rows.push([
|
|
648
|
-
"Other
|
|
649
|
-
`${formatNumber(nonEvalFailures)}
|
|
701
|
+
"Other test failures",
|
|
702
|
+
`${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases`
|
|
650
703
|
]);
|
|
651
704
|
}
|
|
652
705
|
rows.push(["Duration", formatDuration(report.durationMs)]);
|
|
@@ -659,7 +712,53 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
659
712
|
];
|
|
660
713
|
}
|
|
661
714
|
function formatScoreSummary(score) {
|
|
662
|
-
return `
|
|
715
|
+
return `average ${formatScore(score.average)}${score.minimum === void 0 ? "" : `, lowest ${formatScore(score.minimum)}`}`;
|
|
716
|
+
}
|
|
717
|
+
function capitalize(value) {
|
|
718
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
719
|
+
}
|
|
720
|
+
function hasUsage(usage) {
|
|
721
|
+
return usage.totalTokens > 0 || usage.costUsd !== void 0 || usage.toolCalls > 0;
|
|
722
|
+
}
|
|
723
|
+
function sumUsage(app, judge) {
|
|
724
|
+
const appCostKnown = !hasUsageWithoutCost2(app);
|
|
725
|
+
const judgeCostKnown = !hasUsageWithoutCost2(judge);
|
|
726
|
+
return {
|
|
727
|
+
inputTokens: app.inputTokens + judge.inputTokens,
|
|
728
|
+
outputTokens: app.outputTokens + judge.outputTokens,
|
|
729
|
+
reasoningTokens: app.reasoningTokens + judge.reasoningTokens,
|
|
730
|
+
totalTokens: app.totalTokens + judge.totalTokens,
|
|
731
|
+
...appCostKnown && judgeCostKnown ? { costUsd: (app.costUsd ?? 0) + (judge.costUsd ?? 0) } : {},
|
|
732
|
+
toolCalls: app.toolCalls + judge.toolCalls
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function hasUsageWithoutCost2(usage) {
|
|
736
|
+
return usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0);
|
|
737
|
+
}
|
|
738
|
+
function formatUsage(usage) {
|
|
739
|
+
const parts = [];
|
|
740
|
+
if (usage.totalTokens > 0) {
|
|
741
|
+
parts.push(`${formatNumber(usage.totalTokens)} tokens`);
|
|
742
|
+
}
|
|
743
|
+
if (usage.costUsd !== void 0) {
|
|
744
|
+
parts.push(
|
|
745
|
+
usage.costUsd.toLocaleString("en-US", {
|
|
746
|
+
style: "currency",
|
|
747
|
+
currency: "USD",
|
|
748
|
+
minimumFractionDigits: 2,
|
|
749
|
+
maximumFractionDigits: 6
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
if (hasUsageWithoutCost2(usage)) {
|
|
754
|
+
parts.push("cost unavailable");
|
|
755
|
+
}
|
|
756
|
+
if (usage.toolCalls > 0) {
|
|
757
|
+
parts.push(
|
|
758
|
+
`${formatNumber(usage.toolCalls)} tool call${usage.toolCalls === 1 ? "" : "s"}`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
return parts.join(", ") || "none";
|
|
663
762
|
}
|
|
664
763
|
function escapeTableCell(value) {
|
|
665
764
|
return value.replace(/\r?\n/g, " ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
@@ -739,7 +838,7 @@ function renderFailureBlock(testCase, {
|
|
|
739
838
|
["Case", `${number}. ${testCase.displayName}`],
|
|
740
839
|
["Status", testCase.status],
|
|
741
840
|
["Location", formatLocation(testCase.displayFile, testCase.location)],
|
|
742
|
-
["
|
|
841
|
+
["App runner", testCase.harness?.name ?? "n/a"],
|
|
743
842
|
["Score", formatScore(failure?.score ?? testCase.eval?.avgScore)],
|
|
744
843
|
["Judge", failure?.judgeName ?? "n/a"]
|
|
745
844
|
];
|
|
@@ -777,7 +876,7 @@ function renderFailureBlock(testCase, {
|
|
|
777
876
|
if (finalOutput !== void 0) {
|
|
778
877
|
lines.push(
|
|
779
878
|
...renderAsciiSection(
|
|
780
|
-
"
|
|
879
|
+
"Output",
|
|
781
880
|
stringifyValue(finalOutput, maxOutputChars).split(/\r?\n/)
|
|
782
881
|
),
|
|
783
882
|
""
|
|
@@ -805,7 +904,7 @@ function renderFailureBlock(testCase, {
|
|
|
805
904
|
if (testCase.harness?.errors.length) {
|
|
806
905
|
lines.push(
|
|
807
906
|
...renderAsciiSection(
|
|
808
|
-
"
|
|
907
|
+
"App errors",
|
|
809
908
|
stringifyValue(testCase.harness.errors, maxReasonChars).split(/\r?\n/)
|
|
810
909
|
),
|
|
811
910
|
""
|
|
@@ -854,7 +953,9 @@ function formatCaseUsage(testCase) {
|
|
|
854
953
|
parts.push(`${formatNumber(totalTokens)} tokens`);
|
|
855
954
|
}
|
|
856
955
|
if (toolCalls > 0) {
|
|
857
|
-
parts.push(
|
|
956
|
+
parts.push(
|
|
957
|
+
`${formatNumber(toolCalls)} tool call${toolCalls === 1 ? "" : "s"}`
|
|
958
|
+
);
|
|
858
959
|
}
|
|
859
960
|
if (testCase.harness?.timingMs !== void 0) {
|
|
860
961
|
parts.push(formatDuration(testCase.harness.timingMs));
|
|
@@ -1024,16 +1125,22 @@ function mergeEvalReports(reports) {
|
|
|
1024
1125
|
minimum: Math.min(...scoredCases)
|
|
1025
1126
|
} : void 0,
|
|
1026
1127
|
usage: mergeUsage(reports.map((report) => report.usage)),
|
|
1128
|
+
judgeUsage: mergeUsage(reports.map((report) => report.judgeUsage)),
|
|
1027
1129
|
cases,
|
|
1028
1130
|
failures
|
|
1029
1131
|
};
|
|
1030
1132
|
}
|
|
1031
1133
|
function mergeUsage(usages) {
|
|
1134
|
+
const costs = usages.map((usage) => usage.costUsd).filter((cost) => cost !== void 0);
|
|
1135
|
+
const costComplete = !usages.some(
|
|
1136
|
+
(usage) => usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0)
|
|
1137
|
+
);
|
|
1032
1138
|
return {
|
|
1033
1139
|
inputTokens: sum(usages, (usage) => usage.inputTokens),
|
|
1034
1140
|
outputTokens: sum(usages, (usage) => usage.outputTokens),
|
|
1035
1141
|
reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
|
|
1036
1142
|
totalTokens: sum(usages, (usage) => usage.totalTokens),
|
|
1143
|
+
...costComplete && costs.length > 0 ? { costUsd: costs.reduce((total, cost) => total + cost, 0) } : {},
|
|
1037
1144
|
toolCalls: sum(usages, (usage) => usage.toolCalls)
|
|
1038
1145
|
};
|
|
1039
1146
|
}
|