@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/dist/index.mjs CHANGED
@@ -269,9 +269,10 @@ var DEFAULT_MAX_CHECK_ANNOTATIONS = 50;
269
269
  var MAX_CHECK_FIELD_LENGTH = 64e3;
270
270
  function renderWorkflowCommands(report, options = {}) {
271
271
  const maxAnnotations = options.maxAnnotations ?? DEFAULT_MAX_WORKFLOW_ANNOTATIONS;
272
+ const command = caseAnnotationCommand(options.gate);
272
273
  return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map(
273
274
  (testCase) => formatWorkflowCommand({
274
- command: "error",
275
+ command,
275
276
  properties: {
276
277
  file: testCase.displayFile,
277
278
  line: String(testCase.location.line),
@@ -287,11 +288,12 @@ function buildCheckAnnotations(report, options = {}) {
287
288
  options.maxAnnotations ?? DEFAULT_MAX_CHECK_ANNOTATIONS,
288
289
  DEFAULT_MAX_CHECK_ANNOTATIONS
289
290
  );
291
+ const annotationLevel = caseAnnotationLevel(options.gate);
290
292
  return report.failures.filter(hasAnnotationLocation).slice(0, maxAnnotations).map((testCase) => ({
291
293
  path: testCase.displayFile,
292
294
  start_line: testCase.location.line,
293
295
  end_line: testCase.location.line,
294
- annotation_level: "failure",
296
+ annotation_level: annotationLevel,
295
297
  title: truncate(
296
298
  `${testCase.primaryFailure?.judgeName ?? "vitest-evals"} - ${testCase.displayName}`,
297
299
  255
@@ -303,6 +305,12 @@ function buildCheckAnnotations(report, options = {}) {
303
305
  raw_details: truncate(formatRawDetails(testCase), MAX_CHECK_FIELD_LENGTH)
304
306
  }));
305
307
  }
308
+ function caseAnnotationCommand(gate) {
309
+ return gate?.ok ? "warning" : "error";
310
+ }
311
+ function caseAnnotationLevel(gate) {
312
+ return gate?.ok ? "warning" : "failure";
313
+ }
306
314
  function hasAnnotationLocation(testCase) {
307
315
  return Boolean(testCase.location);
308
316
  }
@@ -362,6 +370,167 @@ function formatWorkflowCommand({
362
370
  return `::${command} ${renderedProperties}::${escapeCommandData(message)}`;
363
371
  }
364
372
 
373
+ // src/gate.ts
374
+ function evaluateEvalGate(report, policy = {}) {
375
+ const minPassRate = resolveMinPassRate(policy);
376
+ const minScoreAverage = policy.minScoreAverage;
377
+ const enforced = minPassRate !== void 0 || minScoreAverage !== void 0 || policy.failOnFailures === true;
378
+ const passRate = computePassRate(report);
379
+ const counts = formatEvalCounts(report, passRate);
380
+ if (!enforced) {
381
+ const ok = report.status === "passed";
382
+ return {
383
+ ok,
384
+ status: ok ? "passed" : "failed",
385
+ enforced: false,
386
+ passRate,
387
+ title: defaultCheckTitle(report),
388
+ message: ok ? `eval report passed: ${counts}` : `eval report failed: ${counts}`
389
+ };
390
+ }
391
+ const nonEvalFailures = Math.max(
392
+ 0,
393
+ report.totals.failed - report.totals.evalFailed
394
+ );
395
+ if (nonEvalFailures > 0) {
396
+ return {
397
+ ok: false,
398
+ status: "failed",
399
+ enforced: true,
400
+ passRate,
401
+ title: "Eval report hard failure",
402
+ message: `${formatNumber(nonEvalFailures)} non-eval test failure${nonEvalFailures === 1 ? "" : "s"}; ${counts}`
403
+ };
404
+ }
405
+ if (report.totals.evalTotal === 0) {
406
+ return {
407
+ ok: false,
408
+ status: "failed",
409
+ enforced: true,
410
+ passRate: null,
411
+ title: "Eval report hard failure",
412
+ message: "no eval cases were reported"
413
+ };
414
+ }
415
+ if (report.status === "failed" && report.totals.failed === 0 && report.failures.length === 0) {
416
+ return {
417
+ ok: false,
418
+ status: "failed",
419
+ enforced: true,
420
+ passRate,
421
+ title: "Eval report hard failure",
422
+ message: `vitest run failed without counted test failures; ${counts}`
423
+ };
424
+ }
425
+ if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
426
+ return {
427
+ ok: false,
428
+ status: "failed",
429
+ enforced: true,
430
+ passRate,
431
+ title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
432
+ message: `eval pass rate below floor: ${counts}; required >= ${formatPercent(minPassRate)}`
433
+ };
434
+ }
435
+ if (minScoreAverage !== void 0) {
436
+ const average = report.score?.average;
437
+ if (average === void 0 || !Number.isFinite(average)) {
438
+ return {
439
+ ok: false,
440
+ status: "failed",
441
+ enforced: true,
442
+ passRate,
443
+ title: "Eval score gate failed",
444
+ message: `no score average available; required avg score >= ${formatScore(minScoreAverage)}`
445
+ };
446
+ }
447
+ if (average + Number.EPSILON < minScoreAverage) {
448
+ return {
449
+ ok: false,
450
+ status: "failed",
451
+ enforced: true,
452
+ passRate,
453
+ title: `Avg score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
454
+ message: `avg score below floor: ${counts}; required avg score >= ${formatScore(minScoreAverage)}`
455
+ };
456
+ }
457
+ }
458
+ return {
459
+ ok: true,
460
+ status: "passed",
461
+ enforced: true,
462
+ passRate,
463
+ title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
464
+ message: `eval gate passed: ${counts}${formatFloorSuffix(minPassRate, minScoreAverage)}`
465
+ };
466
+ }
467
+ function computePassRate(report) {
468
+ if (report.totals.evalTotal <= 0) {
469
+ return null;
470
+ }
471
+ return report.totals.evalPassed / report.totals.evalTotal;
472
+ }
473
+ function formatPercent(value) {
474
+ if (value == null || !Number.isFinite(value)) {
475
+ return "n/a";
476
+ }
477
+ return `${(value * 100).toFixed(1)}%`;
478
+ }
479
+ function renderGateWorkflowCommand(gate) {
480
+ if (gate.ok || !gate.enforced) {
481
+ return void 0;
482
+ }
483
+ return `::error title=${escapeCommandProperty(gate.title)}::${escapeCommandData(gate.message)}`;
484
+ }
485
+ function resolveMinPassRate(policy) {
486
+ const configured = policy.minPassRate;
487
+ if (policy.failOnFailures) {
488
+ if (configured === void 0) {
489
+ return 1;
490
+ }
491
+ return Math.max(configured, 1);
492
+ }
493
+ return configured;
494
+ }
495
+ function defaultCheckTitle(report) {
496
+ if (report.failures.length === 0 && report.status === "passed") {
497
+ return "No eval failures";
498
+ }
499
+ if (report.failures.length === 0) {
500
+ return "Vitest run failed";
501
+ }
502
+ return `${report.failures.length} eval failure${report.failures.length === 1 ? "" : "s"}`;
503
+ }
504
+ function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
505
+ if (minPassRate !== void 0) {
506
+ return `Eval pass rate ${formatPercent(passRate)} \u2014 floor ${formatPercent(minPassRate)}`;
507
+ }
508
+ if (minScoreAverage !== void 0) {
509
+ return `Avg score ${formatScore(report.score?.average)} \u2014 floor ${formatScore(minScoreAverage)}`;
510
+ }
511
+ return defaultCheckTitle(report);
512
+ }
513
+ function formatEvalCounts(report, passRate) {
514
+ const scoreText = report.score?.average === void 0 ? "n/a" : formatScore(report.score.average);
515
+ const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
516
+ return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
517
+ report.totals.evalTotal
518
+ )} passed (${passRateText}), avg score ${scoreText}`;
519
+ }
520
+ function formatFloorSuffix(minPassRate, minScoreAverage) {
521
+ const parts = [];
522
+ if (minPassRate !== void 0) {
523
+ parts.push(`pass rate floor ${formatPercent(minPassRate)}`);
524
+ }
525
+ if (minScoreAverage !== void 0) {
526
+ parts.push(`avg score floor ${formatScore(minScoreAverage)}`);
527
+ }
528
+ return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
529
+ }
530
+
531
+ // src/github.ts
532
+ import { readFileSync } from "fs";
533
+
365
534
  // src/summary.ts
366
535
  var DEFAULT_MAX_FAILURES = 20;
367
536
  var DEFAULT_MAX_REASON_CHARS = 8e3;
@@ -382,20 +551,22 @@ function renderJobSummary(report, options = {}) {
382
551
  const lines = [
383
552
  "# vitest-evals",
384
553
  "",
385
- ...renderSummaryTable(report, nonEvalFailures),
554
+ ...renderSummaryTable(report, nonEvalFailures, options.gate),
386
555
  "",
387
556
  ...renderScoreDistribution(report),
388
557
  "## Results",
389
558
  ""
390
559
  ];
391
560
  if (report.failures.length > 0) {
392
- lines.push("### Failures", "");
561
+ const failureHeading = options.gate?.ok === true ? "### Quality Misses" : "### Failures";
562
+ lines.push(failureHeading, "");
393
563
  failures.forEach((testCase, index) => {
394
564
  lines.push(...renderFailureDetails(testCase, index + 1, options), "");
395
565
  });
396
566
  if (report.failures.length > failures.length) {
567
+ const omittedLabel = options.gate?.ok === true ? "quality misses" : "failures";
397
568
  lines.push(
398
- `${report.failures.length - failures.length} more failures omitted from this summary.`,
569
+ `${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
399
570
  ""
400
571
  );
401
572
  }
@@ -411,9 +582,9 @@ function renderJobSummary(report, options = {}) {
411
582
  function formatCountLine(passed, failed, total) {
412
583
  return `${formatNumber(passed)} passed, ${formatNumber(failed)} failed, ${formatNumber(total)} total`;
413
584
  }
414
- function renderSummaryTable(report, nonEvalFailures) {
585
+ function renderSummaryTable(report, nonEvalFailures, gate) {
415
586
  const rows = [
416
- ["Status", report.status],
587
+ ["Status", gate?.status ?? report.status],
417
588
  [
418
589
  "Evals",
419
590
  formatCountLine(
@@ -423,9 +594,20 @@ function renderSummaryTable(report, nonEvalFailures) {
423
594
  )
424
595
  ]
425
596
  ];
597
+ if (gate?.passRate !== void 0 && gate.passRate !== null) {
598
+ rows.push(["Pass Rate", formatPercent(gate.passRate)]);
599
+ } else if (report.totals.evalTotal > 0) {
600
+ rows.push([
601
+ "Pass Rate",
602
+ formatPercent(report.totals.evalPassed / report.totals.evalTotal)
603
+ ]);
604
+ }
426
605
  if (report.score) {
427
606
  rows.push(["Score", formatScoreSummary(report.score)]);
428
607
  }
608
+ if (gate?.enforced) {
609
+ rows.push(["Gate", gate.message]);
610
+ }
429
611
  if (nonEvalFailures > 0) {
430
612
  rows.push([
431
613
  "Other Failures",
@@ -649,10 +831,44 @@ function formatCaseUsage(testCase) {
649
831
  var DEFAULT_CHECK_NAME = "vitest-evals";
650
832
  var MAX_CHECK_SUMMARY_LENGTH = 64e3;
651
833
  var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
834
+ function resolveCheckSha(env = process.env, options = {}) {
835
+ const explicit = options.sha?.trim() || env.GITHUB_PR_HEAD_SHA?.trim();
836
+ if (explicit) {
837
+ return explicit;
838
+ }
839
+ const eventPath = options.eventPath?.trim() || env.GITHUB_EVENT_PATH?.trim();
840
+ if (eventPath) {
841
+ try {
842
+ const event = JSON.parse(readFileSync(eventPath, "utf8"));
843
+ const headSha = event.pull_request?.head?.sha;
844
+ if (typeof headSha === "string" && headSha.trim()) {
845
+ return headSha.trim();
846
+ }
847
+ } catch {
848
+ }
849
+ }
850
+ return env.GITHUB_SHA?.trim() || void 0;
851
+ }
852
+ function resolveCheckDetailsUrl(env = process.env, options = {}) {
853
+ const explicit = options.detailsUrl?.trim();
854
+ if (explicit) {
855
+ return explicit;
856
+ }
857
+ const server = env.GITHUB_SERVER_URL?.replace(/\/$/, "");
858
+ const repository = env.GITHUB_REPOSITORY?.trim();
859
+ const runId = env.GITHUB_RUN_ID?.trim();
860
+ if (!server || !repository || !runId) {
861
+ return void 0;
862
+ }
863
+ return `${server}/${repository}/actions/runs/${runId}`;
864
+ }
652
865
  async function publishCheckRun(report, options = {}) {
653
866
  const token = options.token ?? process.env.GITHUB_TOKEN;
654
867
  const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
655
- const sha = options.sha ?? process.env.GITHUB_SHA;
868
+ const sha = resolveCheckSha(process.env, { sha: options.sha });
869
+ const detailsUrl = resolveCheckDetailsUrl(process.env, {
870
+ detailsUrl: options.detailsUrl
871
+ });
656
872
  if (!token) {
657
873
  return { status: "skipped", reason: "missing GITHUB_TOKEN" };
658
874
  }
@@ -660,7 +876,10 @@ async function publishCheckRun(report, options = {}) {
660
876
  return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
661
877
  }
662
878
  if (!sha && options.checkRunId === void 0) {
663
- return { status: "skipped", reason: "missing GITHUB_SHA" };
879
+ return {
880
+ status: "skipped",
881
+ reason: "missing commit SHA (set --sha / options.sha, GITHUB_PR_HEAD_SHA, pull_request.head.sha, or GITHUB_SHA)"
882
+ };
664
883
  }
665
884
  const [owner, repo] = repository.split("/");
666
885
  if (!owner || !repo) {
@@ -669,7 +888,7 @@ async function publishCheckRun(report, options = {}) {
669
888
  reason: `invalid GitHub repository: ${repository}`
670
889
  };
671
890
  }
672
- const payload = buildCheckRunPayload(report, options);
891
+ const payload = buildCheckRunPayload(report, options, detailsUrl);
673
892
  const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
674
893
  const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
675
894
  const response = await fetch(requestUrl, {
@@ -684,6 +903,7 @@ async function publishCheckRun(report, options = {}) {
684
903
  options.checkRunId === void 0 ? {
685
904
  name: options.name ?? DEFAULT_CHECK_NAME,
686
905
  head_sha: sha,
906
+ ...options.externalId ? { external_id: options.externalId } : {},
687
907
  ...payload
688
908
  } : payload
689
909
  )
@@ -698,27 +918,31 @@ async function publishCheckRun(report, options = {}) {
698
918
  return {
699
919
  status: options.checkRunId === void 0 ? "created" : "updated",
700
920
  id: data.id,
701
- htmlUrl: data.html_url
921
+ htmlUrl: data.html_url,
922
+ sha
702
923
  };
703
924
  }
704
- function buildCheckRunPayload(report, options) {
925
+ function buildCheckRunPayload(report, options, detailsUrl) {
926
+ const gate = options.gate ?? evaluateEvalGate(report);
705
927
  const annotations = buildCheckAnnotations(report, {
706
- maxAnnotations: options.maxAnnotations
928
+ maxAnnotations: options.maxAnnotations,
929
+ gate
707
930
  });
708
- 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"}`;
709
931
  return {
710
932
  status: "completed",
711
- conclusion: report.status === "passed" ? "success" : "failure",
933
+ conclusion: gate.ok ? "success" : "failure",
712
934
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
935
+ ...detailsUrl ? { details_url: detailsUrl } : {},
713
936
  output: {
714
- title,
937
+ title: gate.title,
715
938
  summary: truncateCheckSummary(
716
939
  renderJobSummary(report, {
717
940
  ...options,
718
941
  maxFailures: options.maxFailures ?? 5,
719
942
  maxReasonChars: options.maxReasonChars ?? 4e3,
720
943
  maxOutputChars: options.maxOutputChars ?? 2e3,
721
- maxToolCalls: options.maxToolCalls ?? 10
944
+ maxToolCalls: options.maxToolCalls ?? 10,
945
+ gate
722
946
  })
723
947
  ),
724
948
  annotations
@@ -731,11 +955,191 @@ function truncateCheckSummary(summary) {
731
955
  }
732
956
  return `${summary.slice(0, MAX_CHECK_SUMMARY_LENGTH - CHECK_SUMMARY_TRUNCATION_SUFFIX.length).trimEnd()}${CHECK_SUMMARY_TRUNCATION_SUFFIX}`;
733
957
  }
958
+
959
+ // src/report.ts
960
+ import { appendFile } from "fs/promises";
961
+ import {
962
+ readVitestJsonReportFile,
963
+ resolveResultFiles
964
+ } from "@vitest-evals/core/node";
965
+
966
+ // src/merge.ts
967
+ function mergeEvalReports(reports) {
968
+ const cases = reports.flatMap((report) => report.cases);
969
+ const failures = reports.flatMap((report) => report.failures);
970
+ const scoredCases = cases.map((testCase) => testCase.eval?.avgScore).filter(
971
+ (score) => typeof score === "number" && Number.isFinite(score)
972
+ );
973
+ const startedAtValues = reports.map((report) => report.startedAt).filter(
974
+ (startedAt2) => typeof startedAt2 === "number" && Number.isFinite(startedAt2)
975
+ );
976
+ const startedAt = startedAtValues.length > 0 ? Math.min(...startedAtValues) : void 0;
977
+ return {
978
+ status: reports.some((report) => report.status === "failed") ? "failed" : "passed",
979
+ startedAt,
980
+ durationMs: mergeDuration(reports),
981
+ totals: {
982
+ total: sum(reports, (report) => report.totals.total),
983
+ passed: sum(reports, (report) => report.totals.passed),
984
+ failed: sum(reports, (report) => report.totals.failed),
985
+ skipped: sum(reports, (report) => report.totals.skipped),
986
+ evalTotal: sum(reports, (report) => report.totals.evalTotal),
987
+ evalPassed: sum(reports, (report) => report.totals.evalPassed),
988
+ evalFailed: sum(reports, (report) => report.totals.evalFailed)
989
+ },
990
+ score: scoredCases.length > 0 ? {
991
+ average: scoredCases.reduce((total, score) => total + score, 0) / scoredCases.length,
992
+ minimum: Math.min(...scoredCases)
993
+ } : void 0,
994
+ usage: mergeUsage(reports.map((report) => report.usage)),
995
+ cases,
996
+ failures
997
+ };
998
+ }
999
+ function mergeUsage(usages) {
1000
+ return {
1001
+ inputTokens: sum(usages, (usage) => usage.inputTokens),
1002
+ outputTokens: sum(usages, (usage) => usage.outputTokens),
1003
+ reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
1004
+ totalTokens: sum(usages, (usage) => usage.totalTokens),
1005
+ toolCalls: sum(usages, (usage) => usage.toolCalls)
1006
+ };
1007
+ }
1008
+ function mergeDuration(reports) {
1009
+ const durations = reports.map((report) => report.durationMs).filter(
1010
+ (durationMs) => typeof durationMs === "number" && Number.isFinite(durationMs)
1011
+ );
1012
+ const intervals = reports.map((report) => {
1013
+ if (typeof report.startedAt !== "number" || !Number.isFinite(report.startedAt) || typeof report.durationMs !== "number" || !Number.isFinite(report.durationMs)) {
1014
+ return void 0;
1015
+ }
1016
+ return {
1017
+ start: report.startedAt,
1018
+ end: report.startedAt + report.durationMs
1019
+ };
1020
+ }).filter(
1021
+ (interval) => Boolean(interval)
1022
+ );
1023
+ if (intervals.length > 0 && intervals.length === durations.length) {
1024
+ return Math.max(...intervals.map((interval) => interval.end)) - Math.min(...intervals.map((interval) => interval.start));
1025
+ }
1026
+ return durations.length > 0 ? durations.reduce((total, durationMs) => total + durationMs, 0) : void 0;
1027
+ }
1028
+ function sum(items, select) {
1029
+ return items.reduce((total, item) => total + select(item), 0);
1030
+ }
1031
+
1032
+ // src/report.ts
1033
+ async function publishEvalReport(options) {
1034
+ const resultFiles = await resolveResultFiles(options.resultPatterns, {
1035
+ cwd: options.cwd
1036
+ });
1037
+ if (resultFiles.length === 0) {
1038
+ throw new Error(
1039
+ `No eval result files matched: ${options.resultPatterns.join(", ")}`
1040
+ );
1041
+ }
1042
+ const reports = await Promise.all(
1043
+ resultFiles.map(async (resultFile) => {
1044
+ const json = await readVitestJsonReportFile(resultFile);
1045
+ return collectEvalReport(json, {
1046
+ workspace: options.workspace
1047
+ });
1048
+ })
1049
+ );
1050
+ const report = mergeEvalReports(reports);
1051
+ const gate = evaluateEvalGate(report, {
1052
+ failOnFailures: options.failOnFailures,
1053
+ minPassRate: options.minPassRate,
1054
+ minScoreAverage: options.minScoreAverage
1055
+ });
1056
+ const summary = renderJobSummary(report, {
1057
+ maxFailures: options.maxFailures,
1058
+ maxOutputChars: options.maxOutputChars,
1059
+ maxReasonChars: options.maxReasonChars,
1060
+ maxToolCalls: options.maxToolCalls,
1061
+ gate
1062
+ });
1063
+ if (options.summaryEnabled !== false) {
1064
+ if (options.summaryPath) {
1065
+ await appendFile(options.summaryPath, `${summary}
1066
+ `);
1067
+ } else {
1068
+ console.log(summary);
1069
+ }
1070
+ }
1071
+ if (options.annotations) {
1072
+ const gateCommand = renderGateWorkflowCommand(gate);
1073
+ if (gateCommand) {
1074
+ console.log(gateCommand);
1075
+ }
1076
+ for (const command of renderWorkflowCommands(report, {
1077
+ maxAnnotations: options.maxAnnotations,
1078
+ gate
1079
+ })) {
1080
+ console.log(command);
1081
+ }
1082
+ }
1083
+ let checkRun;
1084
+ if (options.checkRun) {
1085
+ try {
1086
+ checkRun = await publishCheckRun(report, {
1087
+ checkRunId: options.checkRunId,
1088
+ maxAnnotations: options.maxAnnotations,
1089
+ maxFailures: options.maxFailures,
1090
+ maxOutputChars: options.maxOutputChars,
1091
+ maxReasonChars: options.maxReasonChars,
1092
+ maxToolCalls: options.maxToolCalls,
1093
+ name: options.checkName,
1094
+ repository: options.repository,
1095
+ sha: options.sha,
1096
+ detailsUrl: options.detailsUrl,
1097
+ externalId: options.externalId,
1098
+ token: options.token,
1099
+ gate
1100
+ });
1101
+ if (checkRun.status === "skipped") {
1102
+ options.warn?.(`GitHub Check Run skipped: ${checkRun.reason}`);
1103
+ } else if (checkRun.htmlUrl) {
1104
+ console.log(`published check run: ${checkRun.htmlUrl}`);
1105
+ } else if (checkRun.id !== void 0) {
1106
+ console.log(`published check run id: ${checkRun.id}`);
1107
+ }
1108
+ } catch (error) {
1109
+ const message = error instanceof Error ? error.message : String(error);
1110
+ if (options.failOnCheckError) {
1111
+ throw error;
1112
+ }
1113
+ options.warn?.(message);
1114
+ }
1115
+ }
1116
+ const gateFailed = gate.enforced && !gate.ok;
1117
+ const wantsSoftFail = options.softFail ?? options.checkRun === true;
1118
+ const softFail = wantsSoftFail && checkRunPublished(checkRun);
1119
+ const shouldFail = gateFailed && !softFail;
1120
+ return {
1121
+ report,
1122
+ resultFiles,
1123
+ gate,
1124
+ shouldFail,
1125
+ checkRun
1126
+ };
1127
+ }
1128
+ function checkRunPublished(checkRun) {
1129
+ return checkRun?.status === "created" || checkRun?.status === "updated";
1130
+ }
734
1131
  export {
735
1132
  buildCheckAnnotations,
736
1133
  collectEvalReport,
1134
+ computePassRate,
1135
+ evaluateEvalGate,
1136
+ formatPercent,
737
1137
  publishCheckRun,
1138
+ publishEvalReport,
1139
+ renderGateWorkflowCommand,
738
1140
  renderJobSummary,
739
- renderWorkflowCommands
1141
+ renderWorkflowCommands,
1142
+ resolveCheckDetailsUrl,
1143
+ resolveCheckSha
740
1144
  };
741
1145
  //# sourceMappingURL=index.mjs.map