@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.mjs
CHANGED
|
@@ -93,7 +93,8 @@ function collectEvalReport(input, options = {}) {
|
|
|
93
93
|
const cases = workspace.cases.map(collectEvalCase);
|
|
94
94
|
const failures = cases.filter((testCase) => testCase.status === "failed");
|
|
95
95
|
const evalScores = cases.map((testCase) => testCase.eval?.avgScore).filter((score) => isFiniteNumber(score));
|
|
96
|
-
const usage =
|
|
96
|
+
const usage = sumAppUsage(cases);
|
|
97
|
+
const judgeUsage = sumJudgeUsage(cases);
|
|
97
98
|
const durationMs = workspace.runs[0]?.durationMs;
|
|
98
99
|
return {
|
|
99
100
|
status: input.success && failures.length === 0 ? "passed" : "failed",
|
|
@@ -113,6 +114,7 @@ function collectEvalReport(input, options = {}) {
|
|
|
113
114
|
minimum: Math.min(...evalScores)
|
|
114
115
|
} : void 0,
|
|
115
116
|
usage,
|
|
117
|
+
judgeUsage,
|
|
116
118
|
cases,
|
|
117
119
|
failures
|
|
118
120
|
};
|
|
@@ -237,24 +239,67 @@ function stringifyReason(value) {
|
|
|
237
239
|
}
|
|
238
240
|
return typeof value === "string" ? value : stringifyValue(value, 4e3);
|
|
239
241
|
}
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
+
function emptyUsage() {
|
|
243
|
+
return {
|
|
242
244
|
inputTokens: 0,
|
|
243
245
|
outputTokens: 0,
|
|
244
246
|
reasoningTokens: 0,
|
|
245
247
|
totalTokens: 0,
|
|
246
248
|
toolCalls: 0
|
|
247
249
|
};
|
|
250
|
+
}
|
|
251
|
+
function addRunUsage(total, usage) {
|
|
252
|
+
total.inputTokens += usage?.inputTokens ?? 0;
|
|
253
|
+
total.outputTokens += usage?.outputTokens ?? 0;
|
|
254
|
+
total.reasoningTokens += usage?.reasoningTokens ?? 0;
|
|
255
|
+
total.totalTokens += usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0);
|
|
256
|
+
if (usage?.costUsd !== void 0) {
|
|
257
|
+
total.costUsd = (total.costUsd ?? 0) + usage.costUsd;
|
|
258
|
+
}
|
|
259
|
+
total.toolCalls += usage?.toolCalls ?? 0;
|
|
260
|
+
}
|
|
261
|
+
function sumAppUsage(cases) {
|
|
262
|
+
const usage = emptyUsage();
|
|
263
|
+
const runUsages = cases.map(appUsageForCase).filter((item) => item !== void 0);
|
|
264
|
+
for (const runUsage of runUsages) {
|
|
265
|
+
addRunUsage(usage, runUsage);
|
|
266
|
+
}
|
|
267
|
+
omitPartialCost(usage, runUsages);
|
|
268
|
+
return usage;
|
|
269
|
+
}
|
|
270
|
+
function appUsageForCase(testCase) {
|
|
271
|
+
const usage = testCase.harness?.usage;
|
|
272
|
+
const effectiveToolCalls = toolCallCount(testCase);
|
|
273
|
+
if (!usage && effectiveToolCalls === 0) {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
...usage,
|
|
278
|
+
...effectiveToolCalls > 0 ? { toolCalls: effectiveToolCalls } : {}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function sumJudgeUsage(cases) {
|
|
282
|
+
const usage = emptyUsage();
|
|
283
|
+
const runUsages = [];
|
|
248
284
|
for (const testCase of cases) {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
285
|
+
for (const score of testCase.eval?.scores ?? []) {
|
|
286
|
+
for (const run of score.judgeRuns ?? []) {
|
|
287
|
+
runUsages.push(run.usage);
|
|
288
|
+
addRunUsage(usage, run.usage);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
255
291
|
}
|
|
292
|
+
omitPartialCost(usage, runUsages);
|
|
256
293
|
return usage;
|
|
257
294
|
}
|
|
295
|
+
function omitPartialCost(total, usages) {
|
|
296
|
+
if (usages.some(hasUsageWithoutCost)) {
|
|
297
|
+
total.costUsd = void 0;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function hasUsageWithoutCost(usage) {
|
|
301
|
+
return usage?.costUsd === void 0 && ((usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0)) > 0 || (usage?.toolCalls ?? 0) > 0);
|
|
302
|
+
}
|
|
258
303
|
function toolCallCount(testCase) {
|
|
259
304
|
const usageToolCalls = testCase.harness?.usage?.toolCalls;
|
|
260
305
|
if (usageToolCalls !== void 0) {
|
|
@@ -398,8 +443,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
398
443
|
status: "failed",
|
|
399
444
|
enforced: true,
|
|
400
445
|
passRate,
|
|
401
|
-
title: "Eval
|
|
402
|
-
message: `${formatNumber(nonEvalFailures)}
|
|
446
|
+
title: "Eval run failed",
|
|
447
|
+
message: `${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases; ${counts}`
|
|
403
448
|
};
|
|
404
449
|
}
|
|
405
450
|
if (report.totals.evalTotal === 0) {
|
|
@@ -408,7 +453,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
408
453
|
status: "failed",
|
|
409
454
|
enforced: true,
|
|
410
455
|
passRate: null,
|
|
411
|
-
title: "Eval
|
|
456
|
+
title: "Eval run failed",
|
|
412
457
|
message: "no eval cases were reported"
|
|
413
458
|
};
|
|
414
459
|
}
|
|
@@ -418,8 +463,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
418
463
|
status: "failed",
|
|
419
464
|
enforced: true,
|
|
420
465
|
passRate,
|
|
421
|
-
title: "Eval
|
|
422
|
-
message: `
|
|
466
|
+
title: "Eval run failed",
|
|
467
|
+
message: `Vitest failed without reporting a failed test; ${counts}`
|
|
423
468
|
};
|
|
424
469
|
}
|
|
425
470
|
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
@@ -429,7 +474,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
429
474
|
enforced: true,
|
|
430
475
|
passRate,
|
|
431
476
|
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
432
|
-
message: `
|
|
477
|
+
message: `pass rate is below the minimum: ${counts}; minimum ${formatPercent(minPassRate)}`
|
|
433
478
|
};
|
|
434
479
|
}
|
|
435
480
|
if (minScoreAverage !== void 0) {
|
|
@@ -440,8 +485,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
440
485
|
status: "failed",
|
|
441
486
|
enforced: true,
|
|
442
487
|
passRate,
|
|
443
|
-
title: "
|
|
444
|
-
message: `no score
|
|
488
|
+
title: "Average score unavailable",
|
|
489
|
+
message: `no average score was reported; minimum ${formatScore(minScoreAverage)}`
|
|
445
490
|
};
|
|
446
491
|
}
|
|
447
492
|
if (average + Number.EPSILON < minScoreAverage) {
|
|
@@ -450,8 +495,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
450
495
|
status: "failed",
|
|
451
496
|
enforced: true,
|
|
452
497
|
passRate,
|
|
453
|
-
title: `
|
|
454
|
-
message: `
|
|
498
|
+
title: `Average score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
499
|
+
message: `average score is below the minimum: ${counts}; minimum ${formatScore(minScoreAverage)}`
|
|
455
500
|
};
|
|
456
501
|
}
|
|
457
502
|
}
|
|
@@ -461,7 +506,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
461
506
|
enforced: true,
|
|
462
507
|
passRate,
|
|
463
508
|
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
464
|
-
message: `
|
|
509
|
+
message: `requirements met: ${counts}${formatMinimumSuffix(minPassRate, minScoreAverage)}`
|
|
465
510
|
};
|
|
466
511
|
}
|
|
467
512
|
function computePassRate(report) {
|
|
@@ -494,7 +539,7 @@ function resolveMinPassRate(policy) {
|
|
|
494
539
|
}
|
|
495
540
|
function defaultCheckTitle(report) {
|
|
496
541
|
if (report.failures.length === 0 && report.status === "passed") {
|
|
497
|
-
return "No eval
|
|
542
|
+
return "No eval cases failed";
|
|
498
543
|
}
|
|
499
544
|
if (report.failures.length === 0) {
|
|
500
545
|
return "Vitest run failed";
|
|
@@ -503,10 +548,10 @@ function defaultCheckTitle(report) {
|
|
|
503
548
|
}
|
|
504
549
|
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
505
550
|
if (minPassRate !== void 0) {
|
|
506
|
-
return `Eval pass rate ${formatPercent(passRate)} \u2014
|
|
551
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 minimum ${formatPercent(minPassRate)}`;
|
|
507
552
|
}
|
|
508
553
|
if (minScoreAverage !== void 0) {
|
|
509
|
-
return `
|
|
554
|
+
return `Average score ${formatScore(report.score?.average)} \u2014 minimum ${formatScore(minScoreAverage)}`;
|
|
510
555
|
}
|
|
511
556
|
return defaultCheckTitle(report);
|
|
512
557
|
}
|
|
@@ -515,15 +560,15 @@ function formatEvalCounts(report, passRate) {
|
|
|
515
560
|
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
516
561
|
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
517
562
|
report.totals.evalTotal
|
|
518
|
-
)} passed (${passRateText}),
|
|
563
|
+
)} passed (${passRateText}), average score ${scoreText}`;
|
|
519
564
|
}
|
|
520
|
-
function
|
|
565
|
+
function formatMinimumSuffix(minPassRate, minScoreAverage) {
|
|
521
566
|
const parts = [];
|
|
522
567
|
if (minPassRate !== void 0) {
|
|
523
|
-
parts.push(`pass rate
|
|
568
|
+
parts.push(`minimum pass rate ${formatPercent(minPassRate)}`);
|
|
524
569
|
}
|
|
525
570
|
if (minScoreAverage !== void 0) {
|
|
526
|
-
parts.push(`
|
|
571
|
+
parts.push(`minimum average score ${formatScore(minScoreAverage)}`);
|
|
527
572
|
}
|
|
528
573
|
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
529
574
|
}
|
|
@@ -558,23 +603,23 @@ function renderJobSummary(report, options = {}) {
|
|
|
558
603
|
""
|
|
559
604
|
];
|
|
560
605
|
if (report.failures.length > 0) {
|
|
561
|
-
const failureHeading = options.gate?.ok === true ? "###
|
|
606
|
+
const failureHeading = options.gate?.ok === true ? "### Cases Below Target" : "### Failures";
|
|
562
607
|
lines.push(failureHeading, "");
|
|
563
608
|
failures.forEach((testCase, index) => {
|
|
564
609
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
565
610
|
});
|
|
566
611
|
if (report.failures.length > failures.length) {
|
|
567
|
-
const omittedLabel = options.gate?.ok === true ? "
|
|
612
|
+
const omittedLabel = options.gate?.ok === true ? "cases below target" : "failures";
|
|
568
613
|
lines.push(
|
|
569
614
|
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
570
615
|
""
|
|
571
616
|
);
|
|
572
617
|
}
|
|
573
618
|
} else if (report.totals.evalTotal > 0) {
|
|
574
|
-
lines.push("### Failures", "", "No eval
|
|
619
|
+
lines.push("### Failures", "", "No eval cases failed.", "");
|
|
575
620
|
}
|
|
576
621
|
if (report.totals.evalTotal === 0) {
|
|
577
|
-
lines.push("No eval
|
|
622
|
+
lines.push("No eval results were found in the Vitest JSON report.", "");
|
|
578
623
|
}
|
|
579
624
|
return `${lines.join("\n")}
|
|
580
625
|
`;
|
|
@@ -584,9 +629,9 @@ function formatCountLine(passed, failed, total) {
|
|
|
584
629
|
}
|
|
585
630
|
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
586
631
|
const rows = [
|
|
587
|
-
["Status", gate?.status ?? report.status],
|
|
632
|
+
["Status", capitalize(gate?.status ?? report.status)],
|
|
588
633
|
[
|
|
589
|
-
"
|
|
634
|
+
"Eval cases",
|
|
590
635
|
formatCountLine(
|
|
591
636
|
report.totals.evalPassed,
|
|
592
637
|
report.totals.evalFailed,
|
|
@@ -595,23 +640,31 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
595
640
|
]
|
|
596
641
|
];
|
|
597
642
|
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
598
|
-
rows.push(["Pass
|
|
643
|
+
rows.push(["Pass rate", formatPercent(gate.passRate)]);
|
|
599
644
|
} else if (report.totals.evalTotal > 0) {
|
|
600
645
|
rows.push([
|
|
601
|
-
"Pass
|
|
646
|
+
"Pass rate",
|
|
602
647
|
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
603
648
|
]);
|
|
604
649
|
}
|
|
605
650
|
if (report.score) {
|
|
606
651
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
607
652
|
}
|
|
653
|
+
if (hasUsage(report.usage) || hasUsage(report.judgeUsage)) {
|
|
654
|
+
rows.push(["App usage", formatUsage(report.usage)]);
|
|
655
|
+
rows.push(["Judge usage", formatUsage(report.judgeUsage)]);
|
|
656
|
+
rows.push([
|
|
657
|
+
"Total usage",
|
|
658
|
+
formatUsage(sumUsage(report.usage, report.judgeUsage))
|
|
659
|
+
]);
|
|
660
|
+
}
|
|
608
661
|
if (gate?.enforced) {
|
|
609
|
-
rows.push(["
|
|
662
|
+
rows.push(["Requirements", gate.message]);
|
|
610
663
|
}
|
|
611
664
|
if (nonEvalFailures > 0) {
|
|
612
665
|
rows.push([
|
|
613
|
-
"Other
|
|
614
|
-
`${formatNumber(nonEvalFailures)}
|
|
666
|
+
"Other test failures",
|
|
667
|
+
`${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases`
|
|
615
668
|
]);
|
|
616
669
|
}
|
|
617
670
|
rows.push(["Duration", formatDuration(report.durationMs)]);
|
|
@@ -624,7 +677,53 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
624
677
|
];
|
|
625
678
|
}
|
|
626
679
|
function formatScoreSummary(score) {
|
|
627
|
-
return `
|
|
680
|
+
return `average ${formatScore(score.average)}${score.minimum === void 0 ? "" : `, lowest ${formatScore(score.minimum)}`}`;
|
|
681
|
+
}
|
|
682
|
+
function capitalize(value) {
|
|
683
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
684
|
+
}
|
|
685
|
+
function hasUsage(usage) {
|
|
686
|
+
return usage.totalTokens > 0 || usage.costUsd !== void 0 || usage.toolCalls > 0;
|
|
687
|
+
}
|
|
688
|
+
function sumUsage(app, judge) {
|
|
689
|
+
const appCostKnown = !hasUsageWithoutCost2(app);
|
|
690
|
+
const judgeCostKnown = !hasUsageWithoutCost2(judge);
|
|
691
|
+
return {
|
|
692
|
+
inputTokens: app.inputTokens + judge.inputTokens,
|
|
693
|
+
outputTokens: app.outputTokens + judge.outputTokens,
|
|
694
|
+
reasoningTokens: app.reasoningTokens + judge.reasoningTokens,
|
|
695
|
+
totalTokens: app.totalTokens + judge.totalTokens,
|
|
696
|
+
...appCostKnown && judgeCostKnown ? { costUsd: (app.costUsd ?? 0) + (judge.costUsd ?? 0) } : {},
|
|
697
|
+
toolCalls: app.toolCalls + judge.toolCalls
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function hasUsageWithoutCost2(usage) {
|
|
701
|
+
return usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0);
|
|
702
|
+
}
|
|
703
|
+
function formatUsage(usage) {
|
|
704
|
+
const parts = [];
|
|
705
|
+
if (usage.totalTokens > 0) {
|
|
706
|
+
parts.push(`${formatNumber(usage.totalTokens)} tokens`);
|
|
707
|
+
}
|
|
708
|
+
if (usage.costUsd !== void 0) {
|
|
709
|
+
parts.push(
|
|
710
|
+
usage.costUsd.toLocaleString("en-US", {
|
|
711
|
+
style: "currency",
|
|
712
|
+
currency: "USD",
|
|
713
|
+
minimumFractionDigits: 2,
|
|
714
|
+
maximumFractionDigits: 6
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
if (hasUsageWithoutCost2(usage)) {
|
|
719
|
+
parts.push("cost unavailable");
|
|
720
|
+
}
|
|
721
|
+
if (usage.toolCalls > 0) {
|
|
722
|
+
parts.push(
|
|
723
|
+
`${formatNumber(usage.toolCalls)} tool call${usage.toolCalls === 1 ? "" : "s"}`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return parts.join(", ") || "none";
|
|
628
727
|
}
|
|
629
728
|
function escapeTableCell(value) {
|
|
630
729
|
return value.replace(/\r?\n/g, " ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
@@ -704,7 +803,7 @@ function renderFailureBlock(testCase, {
|
|
|
704
803
|
["Case", `${number}. ${testCase.displayName}`],
|
|
705
804
|
["Status", testCase.status],
|
|
706
805
|
["Location", formatLocation(testCase.displayFile, testCase.location)],
|
|
707
|
-
["
|
|
806
|
+
["App runner", testCase.harness?.name ?? "n/a"],
|
|
708
807
|
["Score", formatScore(failure?.score ?? testCase.eval?.avgScore)],
|
|
709
808
|
["Judge", failure?.judgeName ?? "n/a"]
|
|
710
809
|
];
|
|
@@ -742,7 +841,7 @@ function renderFailureBlock(testCase, {
|
|
|
742
841
|
if (finalOutput !== void 0) {
|
|
743
842
|
lines.push(
|
|
744
843
|
...renderAsciiSection(
|
|
745
|
-
"
|
|
844
|
+
"Output",
|
|
746
845
|
stringifyValue(finalOutput, maxOutputChars).split(/\r?\n/)
|
|
747
846
|
),
|
|
748
847
|
""
|
|
@@ -770,7 +869,7 @@ function renderFailureBlock(testCase, {
|
|
|
770
869
|
if (testCase.harness?.errors.length) {
|
|
771
870
|
lines.push(
|
|
772
871
|
...renderAsciiSection(
|
|
773
|
-
"
|
|
872
|
+
"App errors",
|
|
774
873
|
stringifyValue(testCase.harness.errors, maxReasonChars).split(/\r?\n/)
|
|
775
874
|
),
|
|
776
875
|
""
|
|
@@ -819,7 +918,9 @@ function formatCaseUsage(testCase) {
|
|
|
819
918
|
parts.push(`${formatNumber(totalTokens)} tokens`);
|
|
820
919
|
}
|
|
821
920
|
if (toolCalls > 0) {
|
|
822
|
-
parts.push(
|
|
921
|
+
parts.push(
|
|
922
|
+
`${formatNumber(toolCalls)} tool call${toolCalls === 1 ? "" : "s"}`
|
|
923
|
+
);
|
|
823
924
|
}
|
|
824
925
|
if (testCase.harness?.timingMs !== void 0) {
|
|
825
926
|
parts.push(formatDuration(testCase.harness.timingMs));
|
|
@@ -992,16 +1093,22 @@ function mergeEvalReports(reports) {
|
|
|
992
1093
|
minimum: Math.min(...scoredCases)
|
|
993
1094
|
} : void 0,
|
|
994
1095
|
usage: mergeUsage(reports.map((report) => report.usage)),
|
|
1096
|
+
judgeUsage: mergeUsage(reports.map((report) => report.judgeUsage)),
|
|
995
1097
|
cases,
|
|
996
1098
|
failures
|
|
997
1099
|
};
|
|
998
1100
|
}
|
|
999
1101
|
function mergeUsage(usages) {
|
|
1102
|
+
const costs = usages.map((usage) => usage.costUsd).filter((cost) => cost !== void 0);
|
|
1103
|
+
const costComplete = !usages.some(
|
|
1104
|
+
(usage) => usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0)
|
|
1105
|
+
);
|
|
1000
1106
|
return {
|
|
1001
1107
|
inputTokens: sum(usages, (usage) => usage.inputTokens),
|
|
1002
1108
|
outputTokens: sum(usages, (usage) => usage.outputTokens),
|
|
1003
1109
|
reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
|
|
1004
1110
|
totalTokens: sum(usages, (usage) => usage.totalTokens),
|
|
1111
|
+
...costComplete && costs.length > 0 ? { costUsd: costs.reduce((total, cost) => total + cost, 0) } : {},
|
|
1005
1112
|
toolCalls: sum(usages, (usage) => usage.toolCalls)
|
|
1006
1113
|
};
|
|
1007
1114
|
}
|