anpord 0.1.12 → 0.1.13

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.
Files changed (41) hide show
  1. package/dist/bin.cjs +208 -125
  2. package/dist/bin.mjs +208 -125
  3. package/dist/cli-runtime.cjs +2 -2
  4. package/dist/cli-runtime.d.cts +1 -1
  5. package/dist/cli-runtime.d.mts +1 -1
  6. package/dist/cli-runtime.mjs +2 -2
  7. package/dist/{client-D22IiHGL.mjs → client-BfdjFtlV.mjs} +7 -6
  8. package/dist/{client-Byoa41pX.d.mts → client-BgRs8JvW.d.mts} +1891 -227
  9. package/dist/{client-CFTj9ad0.d.cts → client-BirFO3jz.d.cts} +1891 -227
  10. package/dist/{client-BnimnYiJ.cjs → client-C-P2I00z.cjs} +7 -6
  11. package/dist/{compiler-CBMPXIzV.mjs → compiler-D96wI7UP.mjs} +87 -82
  12. package/dist/{compiler-BFxukiJy.cjs → compiler-uwb1pkdK.cjs} +85 -80
  13. package/dist/config.cjs +1 -1
  14. package/dist/config.d.cts +1 -1
  15. package/dist/config.d.mts +1 -1
  16. package/dist/config.mjs +1 -1
  17. package/dist/eval-validations-BRdoZrTQ.mjs +113 -0
  18. package/dist/eval-validations-_MlnQ_uv.cjs +148 -0
  19. package/dist/eval.cjs +1 -1
  20. package/dist/eval.d.cts +33 -13
  21. package/dist/eval.d.mts +33 -13
  22. package/dist/eval.mjs +1 -1
  23. package/dist/evals-B6aT3xgH.d.cts +1364 -0
  24. package/dist/evals-B6aT3xgH.d.mts +1364 -0
  25. package/dist/{evals-CvOTyvCX.mjs → evals-Dcc_x6jW.mjs} +54 -11
  26. package/dist/{evals-B04M0apj.cjs → evals-DouMsrsM.cjs} +71 -16
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.cts +128 -4
  29. package/dist/index.d.mts +128 -4
  30. package/dist/index.mjs +1 -1
  31. package/dist/source.d.cts +1 -1
  32. package/dist/source.d.mts +1 -1
  33. package/dist/{types-oXWOHA-X.d.mts → types-Codjaq-o.d.mts} +65 -19
  34. package/dist/{types-nr4-1c3W.d.cts → types-ISsteq95.d.cts} +65 -19
  35. package/dist/validator-runtime.cjs +238 -0
  36. package/dist/validator-runtime.d.cts +9 -0
  37. package/dist/validator-runtime.d.mts +9 -0
  38. package/dist/validator-runtime.mjs +237 -0
  39. package/package.json +1 -1
  40. package/dist/evals-BUBzpke_.d.cts +0 -647
  41. package/dist/evals-BUBzpke_.d.mts +0 -647
package/dist/bin.cjs CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- const require_client = require("./client-BnimnYiJ.cjs");
2
+ const require_client = require("./client-C-P2I00z.cjs");
3
+ const require_evals = require("./evals-DouMsrsM.cjs");
3
4
  const require_errors = require("./errors-BX1wry8K.cjs");
4
5
  const require_config = require("./config.cjs");
5
- const require_compiler = require("./compiler-BFxukiJy.cjs");
6
+ const require_compiler = require("./compiler-uwb1pkdK.cjs");
6
7
  let _effect_cli = require("@effect/cli");
7
8
  let _effect_platform = require("@effect/platform");
8
9
  let _effect_platform_node = require("@effect/platform-node");
9
10
  let effect = require("effect");
10
11
  let yaml = require("yaml");
11
12
  //#region package.json
12
- var version$1 = "0.1.12";
13
+ var version$1 = "0.1.13";
13
14
  //#endregion
14
15
  //#region ../template/src/extract.ts
15
16
  /** Reads escapes too, so text the renderer leaves literal is never reported as
@@ -58,10 +59,11 @@ const evalFilesIn = (directory) => effect.Effect.gen(function* () {
58
59
  }).pipe(effect.Effect.withSpan("Cli.evalFilesIn"));
59
60
  //#endregion
60
61
  //#region src/cli/eval-gate.ts
62
+ const EvalGate = effect.Schema.Literal("strict", "never", "regressed", "unscored");
61
63
  const regressions = (run) => run.cells.filter((cell) => cell.comparison?.verdict === "regressed");
62
64
  const unscored = (run) => run.cells.filter((cell) => (cell.distribution?.scored ?? 0) === 0);
63
65
  const rate = (value) => `${Math.round(value * 100) / 100}`;
64
- const versionClause$1 = (run, cell, found) => found.baselineHarnessVersion === found.candidateHarnessVersion ? "" : `${run.tasks[cell.taskIndex]?.harness ?? "harness"} ${found.baselineHarnessVersion} → ${found.candidateHarnessVersion}, `;
66
+ const versionClause = (run, cell, found) => found.baselineHarnessVersion === found.candidateHarnessVersion ? "" : `${run.tasks[cell.taskIndex]?.harness ?? "harness"} ${found.baselineHarnessVersion} → ${found.candidateHarnessVersion}, `;
65
67
  const profileClause = (run, cell, found) => {
66
68
  const { baselineProfileVersion, candidateProfileVersion } = found;
67
69
  if (baselineProfileVersion === null || candidateProfileVersion === null || baselineProfileVersion === candidateProfileVersion) return "";
@@ -70,10 +72,20 @@ const profileClause = (run, cell, found) => {
70
72
  const regressionSentence = (run, cell) => {
71
73
  const found = cell.comparison;
72
74
  if (found === null) return `${cell.caseName} regressed against its baseline.`;
73
- return `${cell.caseName} regressed against its baseline: ${versionClause$1(run, cell, found)}${profileClause(run, cell, found)}pass rate ${rate(found.baselinePassRate)} → ${rate(found.candidatePassRate)}.`;
75
+ return `${cell.caseName} regressed against its baseline: ${versionClause(run, cell, found)}${profileClause(run, cell, found)}pass rate ${rate(found.baselinePassRate)} → ${rate(found.candidatePassRate)}.`;
74
76
  };
75
- const problemsWith = (run, failOn) => {
77
+ const problemsWith = (run, failOn, expected) => {
76
78
  if (run.status === "failed") return [run.failure ?? "The run failed."];
79
+ if (run.status !== "finished") return ["The run has not finished."];
80
+ if (run.cells.length === 0) return ["The run recorded no cells."];
81
+ if (failOn === "strict") {
82
+ if (expected && run.cells.length !== expected.cells) return [`Expected ${expected.cells} cells, received ${run.cells.length}.`];
83
+ return run.cells.flatMap((cell) => {
84
+ if (cell.status !== "finished" || cell.trials.length === 0) return [`${cell.caseName} has no complete trial results.`];
85
+ if (expected && cell.trials.length !== expected.trials) return [`${cell.caseName}: expected ${expected.trials} trials, received ${cell.trials.length}.`];
86
+ return cell.trials.flatMap((trial) => trial.status === "passed" && trial.passed ? [] : [`${cell.caseName}, trial ${trial.ordinal}: ${trial.status}.`]);
87
+ });
88
+ }
77
89
  if (failOn === "never") return [];
78
90
  const found = regressions(run).map((cell) => regressionSentence(run, cell));
79
91
  return failOn === "unscored" ? [...found, ...unscored(run).map((cell) => `${cell.caseName} produced no scored trials.`)] : found;
@@ -115,43 +127,43 @@ const DONE = "●";
115
127
  const RUNNING = "◐";
116
128
  const FILLED = "▰";
117
129
  const HOLLOW = "▱";
118
- const PERCENT$1 = 100;
130
+ const PERCENT = 100;
119
131
  const SECONDS = 1e3;
120
132
  const MINUTE = 60;
121
133
  const paint = (colour, text) => `${colour}${text}${RESET}`;
122
- const elapsedOf = (ms) => {
134
+ const formatElapsed = (ms) => {
123
135
  const total = Math.floor(ms / SECONDS);
124
136
  const minutes = Math.floor(total / MINUTE);
125
137
  return minutes === 0 ? `${total}s` : `${minutes}m${String(total % MINUTE).padStart(2, "0")}s`;
126
138
  };
127
- const markOf = (cell) => {
139
+ const formatStatus = (cell) => {
128
140
  if (cell.status === "finished") return paint(GREEN, DONE);
129
141
  return cell.status === "failed" ? paint(RED, DONE) : paint(YELLOW, RUNNING);
130
142
  };
131
- const trialsOf = (cell, trials) => {
143
+ const formatTrialProgress = (cell, trials) => {
132
144
  const settled = cell.trials.filter((trial) => trial.status !== "queued" && trial.status !== "running").length;
133
145
  return `${FILLED.repeat(settled)}${paint(DIM, HOLLOW.repeat(Math.max(0, trials - settled)))}`;
134
146
  };
135
- const rateOf$1 = (cell) => {
147
+ const formatPassRate = (cell) => {
136
148
  const rate = cell.distribution?.passRate;
137
149
  if (rate === void 0 || cell.distribution?.scored === 0) return paint(DIM, "—");
138
- const shown = `${Math.round(rate * PERCENT$1)}%`;
150
+ const shown = `${Math.round(rate * PERCENT)}%`;
139
151
  return paint(rate === 1 ? GREEN : RED, shown);
140
152
  };
141
- const variantOf = (run, cell) => {
153
+ const formatVariant = (run, cell) => {
142
154
  const task = run.tasks[cell.taskIndex];
143
155
  return task === void 0 ? "?" : `${task.harness}/${task.model}`;
144
156
  };
145
- const widest = (run) => run.cells.reduce((width, cell) => Math.max(width, variantOf(run, cell).length), 0);
146
- const gridOf = (run, trials, elapsedMs) => {
157
+ const widest = (run) => run.cells.reduce((width, cell) => Math.max(width, formatVariant(run, cell).length), 0);
158
+ const formatGrid = (run, trials, elapsedMs) => {
147
159
  const width = widest(run);
148
160
  const lines = [];
149
161
  for (const caseName of run.cases) {
150
162
  lines.push(` ${BOLD}${caseName}${RESET}`);
151
- for (const cell of run.cells.filter((one) => one.caseName === caseName)) lines.push(` ${markOf(cell)} ${variantOf(run, cell).padEnd(width)} ${trialsOf(cell, trials)} ${rateOf$1(cell)}`);
163
+ for (const cell of run.cells.filter((one) => one.caseName === caseName)) lines.push(` ${formatStatus(cell)} ${formatVariant(run, cell).padEnd(width)} ${formatTrialProgress(cell, trials)} ${formatPassRate(cell)}`);
152
164
  lines.push("");
153
165
  }
154
- lines.push(paint(DIM, ` ${elapsedOf(elapsedMs)} elapsed`));
166
+ lines.push(paint(DIM, ` ${formatElapsed(elapsedMs)} elapsed`));
155
167
  return lines;
156
168
  };
157
169
  const up = (rows) => `[${rows}A`;
@@ -160,12 +172,12 @@ const liveGrid = (trials, interactive) => effect.Effect.gen(function* () {
160
172
  return (run, elapsedMs) => effect.Effect.gen(function* () {
161
173
  if (!interactive) return;
162
174
  const rows = yield* effect.Ref.getAndSet(drawn, 0);
163
- const lines = gridOf(run, trials, elapsedMs);
175
+ const lines = formatGrid(run, trials, elapsedMs);
164
176
  yield* note(`${rows === 0 ? "" : up(rows)}${lines.join("\n")}`);
165
177
  yield* effect.Ref.set(drawn, lines.length);
166
178
  });
167
179
  });
168
- const summaryOf$1 = (run, trials, drawn) => drawn ? "" : gridOf(run, trials, 0).join("\n");
180
+ const formatGridSummary = (run, trials, drawn) => drawn ? "" : formatGrid(run, trials, 0).join("\n");
169
181
  //#endregion
170
182
  //#region src/imports/evals-json-errors.ts
171
183
  var CaseFileUnreadable = class extends effect.Data.TaggedError("CaseFileUnreadable") {
@@ -282,7 +294,7 @@ const renderEvalSuite = (file) => {
282
294
  " ],",
283
295
  " tasks: [",
284
296
  " /* Name the harness, model and sandbox this suite runs on. */",
285
- " { harness: \"codex\", model: \"gpt-5.6-sol\", provider: \"daytona\" },",
297
+ " { harness: \"codex\", model: \"gpt-5.6-sol\" },",
286
298
  " ],",
287
299
  "});"
288
300
  ].join("\n")}\n`;
@@ -415,7 +427,7 @@ const renderYamlSuite = (files) => `${[
415
427
  " ],",
416
428
  " tasks: [",
417
429
  " /* Name the harness, model and sandbox this suite runs on. */",
418
- " { harness: \"codex\", model: \"gpt-5.6-sol\", provider: \"daytona\" },",
430
+ " { harness: \"codex\", model: \"gpt-5.6-sol\" },",
419
431
  " ],",
420
432
  "});"
421
433
  ].join("\n")}\n`;
@@ -515,12 +527,102 @@ const importEval = _effect_cli.Command.make("import", {
515
527
  return yield* note(summaryOf(suite.tally));
516
528
  }).pipe(effect.Effect.withSpan("Cli.evalImport"))).pipe(_effect_cli.Command.withDescription("Turn a case file a team already wrote into a suite"));
517
529
  //#endregion
530
+ //#region src/cli/eval-outcome.ts
531
+ const EvalOutcome = effect.Schema.Struct({
532
+ file: effect.Schema.String,
533
+ problems: effect.Schema.Array(effect.Schema.String),
534
+ run: effect.Schema.NullOr(require_evals.EvalRun),
535
+ runId: effect.Schema.NullOr(effect.Schema.String)
536
+ });
537
+ effect.Schema.Struct({
538
+ conclusion: effect.Schema.Literal("failure", "neutral", "success"),
539
+ details_url: effect.Schema.optional(effect.Schema.String),
540
+ name: effect.Schema.Literal("anpord"),
541
+ output: effect.Schema.Struct({
542
+ summary: effect.Schema.String,
543
+ title: effect.Schema.String
544
+ })
545
+ });
546
+ const TRUNCATED = "\n\n… truncated";
547
+ const TRAILING_SLASH = /\/$/;
548
+ const TITLES = {
549
+ failure: "Eval gate failed",
550
+ success: "Eval gate passed",
551
+ neutral: "Evals still running"
552
+ };
553
+ const percent = (rate) => rate === void 0 ? "-" : `${Math.round(rate * 100)}%`;
554
+ const escaped = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("|", "\\|").replaceAll(/[\r\n]/g, " ");
555
+ const formatComparison = (run, cell) => {
556
+ const comparison = cell.comparison;
557
+ if (comparison === null) return "-";
558
+ const { baselineHarnessVersion: before, candidateHarnessVersion: after } = comparison;
559
+ const changed = before === after ? "" : ` (${run.tasks[cell.taskIndex]?.harness} ${before} → ${after})`;
560
+ return `${comparison.verdict}${changed}`;
561
+ };
562
+ const formatCellRow = (run, cell) => {
563
+ const rate = cell.distribution?.scored ? cell.distribution.passRate : void 0;
564
+ return `| ${escaped(cell.caseName)} | ${escaped(formatVariant(run, cell))} | ${percent(rate)} | ${percent(cell.comparison?.baselinePassRate)} | ${escaped(formatComparison(run, cell))} |`;
565
+ };
566
+ const runUrl = (webUrl, id) => `${webUrl.replace(TRAILING_SLASH, "")}/evals/${encodeURIComponent(id)}`;
567
+ const formatOutcome = ({ file, problems, run, runId }, webUrl) => [
568
+ `### ${escaped(file)}`,
569
+ "",
570
+ ...runId === null ? [] : [`[View run](${runUrl(webUrl, runId)})`, ""],
571
+ ...problems.map((problem) => `- ${escaped(problem)}`),
572
+ ...run === null ? [] : [
573
+ "",
574
+ "| Case | Variant | Pass rate | Baseline | Verdict |",
575
+ "| --- | --- | --- | --- | --- |",
576
+ ...run.cells.map((cell) => formatCellRow(run, cell))
577
+ ]
578
+ ].join("\n");
579
+ const buildGithubCheck = (outcomes, webUrl) => {
580
+ const failed = outcomes.some((outcome) => outcome.problems.length > 0);
581
+ const completed = outcomes.length > 0 && outcomes.every((outcome) => outcome.run !== null);
582
+ const first = outcomes.find((outcome) => outcome.runId !== null);
583
+ const conclusion = failed ? "failure" : completed ? "success" : "neutral";
584
+ const summary = outcomes.map((outcome) => formatOutcome(outcome, webUrl)).join("\n\n");
585
+ return {
586
+ conclusion,
587
+ details_url: first?.runId ? runUrl(webUrl, first.runId) : void 0,
588
+ name: "anpord",
589
+ output: {
590
+ title: TITLES[conclusion],
591
+ summary: summary.length < 65535 ? summary : `${summary.slice(0, 65521)}${TRUNCATED}`
592
+ }
593
+ };
594
+ };
595
+ //#endregion
596
+ //#region src/cli/eval-report.ts
597
+ const reportJson = effect.Schema.encodeSync(effect.Schema.parseJson(effect.Schema.Array(EvalOutcome)));
598
+ const appendSummary = (text) => effect.Effect.gen(function* () {
599
+ const path = yield* effect.Config.string("GITHUB_STEP_SUMMARY").pipe(effect.Config.option);
600
+ if (effect.Option.isSome(path) && path.value !== "") yield* (yield* _effect_platform.FileSystem.FileSystem).writeFileString(path.value, `${text}\n\n`, { flag: "a" });
601
+ });
602
+ const reportStarted = (file, id) => effect.Effect.gen(function* () {
603
+ const url = runUrl(yield* require_config.webUrlConfig, id);
604
+ yield* note(`${file}: ${url}`);
605
+ yield* appendSummary(`[Run ${id}](${url}) started.`);
606
+ });
607
+ const writeReport = (outcomes, path) => effect.Effect.gen(function* () {
608
+ if (effect.Option.isSome(path)) yield* (yield* _effect_platform.FileSystem.FileSystem).writeFileString(path.value, reportJson(outcomes));
609
+ });
610
+ const reportFinished = (outcomes) => effect.Effect.gen(function* () {
611
+ const report = buildGithubCheck(outcomes, yield* require_config.webUrlConfig);
612
+ yield* appendSummary(`## ${report.output.title}\n\n${report.output.summary}`);
613
+ });
614
+ //#endregion
518
615
  //#region src/cli/eval-run.ts
519
616
  const FIRST_POLL = 2e3;
520
617
  const SLOWEST_POLL = 1e4;
521
618
  const WIDENING = 1.5;
522
619
  const running = (run) => run.status === "running";
523
- const waitForRun = (id, onProgress) => effect.Effect.gen(function* () {
620
+ var EvalWaitTimeout = class extends effect.Data.TaggedError("EvalWaitTimeout") {
621
+ get message() {
622
+ return `Timed out after ${this.seconds}s waiting for ${this.runId}. The remote run was not cancelled.`;
623
+ }
624
+ };
625
+ const waitForRun = (id, onProgress, timeoutSeconds) => effect.Effect.gen(function* () {
524
626
  const api = yield* require_client.AnpordApi;
525
627
  const startedAt = yield* effect.Clock.currentTimeMillis;
526
628
  const gap = yield* effect.Ref.make(FIRST_POLL);
@@ -538,73 +640,28 @@ const waitForRun = (id, onProgress) => effect.Effect.gen(function* () {
538
640
  body: () => waitThenPoll,
539
641
  while: running
540
642
  });
541
- }).pipe(effect.Effect.withSpan("Cli.waitForRun", { attributes: { runId: id } }));
542
- effect.Schema.Struct({
543
- conclusion: effect.Schema.Literal("failure", "neutral", "success"),
544
- details_url: effect.Schema.optional(effect.Schema.String),
545
- name: effect.Schema.Literal("anpord"),
546
- output: effect.Schema.Struct({
547
- summary: effect.Schema.String,
548
- title: effect.Schema.String
643
+ }).pipe(effect.Effect.timeoutFail({
644
+ duration: effect.Duration.seconds(timeoutSeconds),
645
+ onTimeout: () => new EvalWaitTimeout({
646
+ runId: id,
647
+ seconds: timeoutSeconds
549
648
  })
649
+ }), effect.Effect.withSpan("Cli.waitForRun", { attributes: { runId: id } }));
650
+ //#endregion
651
+ //#region src/cli/eval-trigger.ts
652
+ const evalTrigger = effect.Effect.gen(function* () {
653
+ const github = yield* effect.Config.boolean("GITHUB_ACTIONS").pipe(effect.Config.withDefault(false));
654
+ const ci = yield* effect.Config.boolean("CI").pipe(effect.Config.withDefault(false));
655
+ if (!github) return { source: ci ? "ci" : "cli" };
656
+ const repository = yield* effect.Config.string("GITHUB_REPOSITORY");
657
+ const runId = yield* effect.Config.string("GITHUB_RUN_ID");
658
+ const attempt = yield* effect.Config.string("GITHUB_RUN_ATTEMPT").pipe(effect.Config.withDefault("1"));
659
+ const server = yield* effect.Config.string("GITHUB_SERVER_URL").pipe(effect.Config.withDefault("https://github.com"));
660
+ return yield* effect.Schema.decodeUnknown(require_evals.EvalTrigger)({
661
+ source: "ci",
662
+ url: `${server}/${repository}/actions/runs/${runId}/attempts/${attempt}`
663
+ });
550
664
  });
551
- const TRUNCATED = "\n\n… truncated";
552
- const PERCENT = 100;
553
- const ABSENT = "—";
554
- const percent = (rate) => rate === void 0 ? ABSENT : `${Math.round(rate * PERCENT)}%`;
555
- const rateOf = (cell) => cell.distribution === null || cell.distribution.scored === 0 ? ABSENT : percent(cell.distribution.passRate);
556
- const versionsOf = (comparison) => ({
557
- baseline: "baselineHarnessVersion" in comparison && typeof comparison.baselineHarnessVersion === "string" ? comparison.baselineHarnessVersion : void 0,
558
- candidate: "candidateHarnessVersion" in comparison && typeof comparison.candidateHarnessVersion === "string" ? comparison.candidateHarnessVersion : void 0
559
- });
560
- const versionClause = (run, cell) => {
561
- if (cell.comparison === null) return "";
562
- const { baseline, candidate } = versionsOf(cell.comparison);
563
- const harness = run.tasks[cell.taskIndex]?.harness ?? "harness";
564
- return baseline === void 0 || candidate === void 0 || baseline === candidate ? "" : ` (${harness} ${baseline} → ${candidate})`;
565
- };
566
- const verdictOf = (run, cell) => cell.comparison === null ? ABSENT : `${cell.comparison.verdict}${versionClause(run, cell)}`;
567
- const escaped = (text) => text.replaceAll("|", "\\|");
568
- const rowOf = (run, cell) => `| ${escaped(cell.caseName)} | ${escaped(variantOf(run, cell))} | ${rateOf(cell)} | ${percent(cell.comparison?.baselinePassRate)} | ${verdictOf(run, cell)} |`;
569
- const tableOf = (file, run) => [
570
- `### ${escaped(file)}`,
571
- "",
572
- ...run.failure === null ? [] : [`Run failed: ${run.failure}`, ""],
573
- "| Case | Variant | Pass rate | Baseline | Verdict |",
574
- "| --- | --- | --- | --- | --- |",
575
- ...run.cells.map((cell) => rowOf(run, cell))
576
- ].join("\n");
577
- const truncated = (summary) => summary.length < 65535 ? summary : `${summary.slice(0, 65521)}${TRUNCATED}`;
578
- const conclusionOf = (cells) => {
579
- const verdicts = cells.flatMap((cell) => cell.comparison === null ? [] : [cell.comparison.verdict]);
580
- if (verdicts.includes("regressed")) return "failure";
581
- return verdicts.every((verdict) => verdict === "incomparable") ? "neutral" : "success";
582
- };
583
- const TITLES = {
584
- failure: "A cell regressed against its baseline",
585
- neutral: "Nothing to compare against a baseline",
586
- success: "No cell regressed against its baseline"
587
- };
588
- const checkRunOf = (outcomes, webUrl) => {
589
- const finished = outcomes.flatMap((outcome) => effect.Option.match(outcome.run, {
590
- onNone: () => [],
591
- onSome: (run) => [{
592
- file: outcome.file,
593
- run
594
- }]
595
- }));
596
- const conclusion = conclusionOf(finished.flatMap(({ run }) => run.cells));
597
- const first = finished[0];
598
- return {
599
- conclusion,
600
- details_url: first === void 0 ? void 0 : `${webUrl}/evals/${first.run.id}`,
601
- name: "anpord",
602
- output: {
603
- summary: truncated(finished.map(({ file, run }) => tableOf(file, run)).join("\n\n")),
604
- title: TITLES[conclusion]
605
- }
606
- };
607
- };
608
665
  //#endregion
609
666
  //#region src/cli/github-check-client.ts
610
667
  const API = "https://api.github.com";
@@ -665,64 +722,90 @@ const githubContext = effect.Effect.gen(function* () {
665
722
  });
666
723
  //#endregion
667
724
  //#region src/cli/eval-command.ts
668
- const asJson$1 = _effect_cli.Options.boolean("json").pipe(_effect_cli.Options.withDescription("Print the finished run as JSON"));
669
- const evalFile = _effect_cli.Args.text({ name: "file" }).pipe(_effect_cli.Args.withDescription("A TypeScript file that default exports defineEval(...); every *.eval.ts is run when omitted"), _effect_cli.Args.optional);
670
- const noWait = _effect_cli.Options.boolean("no-wait").pipe(_effect_cli.Options.withDescription("Start the run and print its id, without waiting"));
671
- const failOn = _effect_cli.Options.choice("fail-on", [
672
- "never",
673
- "regressed",
674
- "unscored"
675
- ]).pipe(_effect_cli.Options.withDescription("What makes the command exit nonzero"), _effect_cli.Options.withDefault("regressed"));
676
- const runOneEval = (file, options) => effect.Effect.gen(function* () {
677
- const api = yield* require_client.AnpordApi;
678
- const payload = yield* require_compiler.compileEvalEffect(file);
679
- const started = yield* api.evals.start({ payload });
680
- if (options.skipWait) {
681
- yield* json(started);
682
- return {
725
+ const asJson$1 = _effect_cli.Options.boolean("json").pipe(_effect_cli.Options.withDescription("Print each finished run as JSON"));
726
+ const evalFile = _effect_cli.Args.text({ name: "file" }).pipe(_effect_cli.Args.withDescription("A TypeScript eval file; discovers *.eval.ts when omitted"), _effect_cli.Args.optional);
727
+ const noWait = _effect_cli.Options.boolean("no-wait").pipe(_effect_cli.Options.withDescription("Start runs without waiting"));
728
+ const failOn = _effect_cli.Options.choice("fail-on", EvalGate.literals).pipe(_effect_cli.Options.withDescription("strict requires every trial to pass"), _effect_cli.Options.withDefault("strict"));
729
+ const timeout = _effect_cli.Options.integer("timeout").pipe(_effect_cli.Options.withDescription("Maximum seconds to wait per run"), _effect_cli.Options.withSchema(effect.Schema.Int.pipe(effect.Schema.positive())), _effect_cli.Options.withDefault(1200));
730
+ const output = _effect_cli.Options.text("output").pipe(_effect_cli.Options.withDescription("Write a JSON report to this file"), _effect_cli.Options.optional);
731
+ const describe$1 = (error) => error instanceof Error ? error.message : String(error);
732
+ const runOneEval = (file, options, save) => effect.Effect.gen(function* () {
733
+ let runId = null;
734
+ return yield* effect.Effect.gen(function* () {
735
+ const api = yield* require_client.AnpordApi;
736
+ const payload = yield* require_compiler.compileEvalEffect(file);
737
+ const trigger = yield* evalTrigger;
738
+ const started = yield* api.evals.start({ payload: {
739
+ ...payload,
740
+ trigger
741
+ } });
742
+ runId = started.id;
743
+ yield* reportStarted(file, runId);
744
+ const pending = {
683
745
  file,
746
+ runId,
684
747
  problems: [],
685
- run: effect.Option.none()
748
+ run: null
686
749
  };
687
- }
688
- const live = !options.wantsJson && (yield* attended);
689
- if (live || options.label) yield* note(`${file} · run ${started.id}`);
690
- const draw = yield* liveGrid(payload.trials, live);
691
- const run = yield* waitForRun(started.id, draw);
692
- yield* options.wantsJson ? json(run) : note(summaryOf$1(run, payload.trials, live));
693
- return {
750
+ yield* save(pending);
751
+ if (options.skipWait) {
752
+ yield* json(started);
753
+ return pending;
754
+ }
755
+ const live = !options.wantsJson && (yield* attended);
756
+ const draw = yield* liveGrid(payload.trials, live);
757
+ const run = yield* waitForRun(runId, draw, options.timeoutSeconds);
758
+ yield* options.wantsJson ? json(run) : note(formatGridSummary(run, payload.trials, live));
759
+ return {
760
+ file,
761
+ runId,
762
+ run,
763
+ problems: problemsWith(run, options.gate, {
764
+ cells: payload.cases.length * payload.tasks.length,
765
+ trials: payload.trials
766
+ })
767
+ };
768
+ }).pipe(effect.Effect.catchAll((error) => effect.Effect.succeed({
694
769
  file,
695
- problems: problemsWith(run, options.gate),
696
- run: effect.Option.some(run)
697
- };
770
+ runId,
771
+ run: null,
772
+ problems: [describe$1(error)]
773
+ })));
698
774
  });
699
- const describe$1 = (error) => error instanceof Error ? error.message : String(error);
700
775
  const reportToGithub = (outcomes) => effect.Effect.gen(function* () {
701
- if (outcomes.every((outcome) => effect.Option.isNone(outcome.run))) return;
702
776
  const context = yield* githubContext;
703
777
  if (effect.Option.isNone(context)) return;
704
778
  const webUrl = yield* require_config.webUrlConfig;
705
- yield* postCheckRun(context.value, checkRunOf(outcomes, webUrl));
706
- yield* note(`Posted the anpord check on ${context.value.sha}`);
779
+ yield* postCheckRun(context.value, buildGithubCheck(outcomes, webUrl));
707
780
  }).pipe(effect.Effect.catchAll((error) => note(`The GitHub check was not posted. ${describe$1(error)}`)));
708
781
  const runEval = _effect_cli.Command.make("eval", {
709
782
  asJson: asJson$1,
710
783
  evalFile,
711
784
  failOn,
712
- noWait
713
- }, ({ asJson: wantsJson, evalFile: file, failOn: gate, noWait: skipWait }) => effect.Effect.gen(function* () {
785
+ noWait,
786
+ output,
787
+ timeout
788
+ }, ({ asJson: wantsJson, evalFile: file, failOn: gate, noWait: skipWait, output: path, timeout: timeoutSeconds }) => effect.Effect.gen(function* () {
714
789
  const files = yield* effect.Option.match(file, {
715
790
  onNone: () => evalFilesIn("."),
716
791
  onSome: (one) => effect.Effect.succeed([one])
717
792
  });
718
793
  if (files.length === 0) return yield* effect.Effect.fail(new NoEvalFiles());
719
- const outcomes = yield* effect.Effect.forEach(files, (one) => runOneEval(one, {
720
- gate,
721
- label: files.length > 1,
722
- skipWait,
723
- wantsJson
794
+ const outcomes = [];
795
+ yield* effect.Effect.forEach(files, (one, index) => effect.Effect.gen(function* () {
796
+ const save = (outcome) => effect.Effect.gen(function* () {
797
+ outcomes[index] = outcome;
798
+ yield* writeReport(outcomes, path);
799
+ });
800
+ yield* save(yield* runOneEval(one, {
801
+ gate,
802
+ skipWait,
803
+ timeoutSeconds,
804
+ wantsJson
805
+ }, save));
724
806
  }));
725
- yield* reportToGithub(outcomes);
807
+ yield* reportFinished(outcomes);
808
+ if (!skipWait) yield* reportToGithub(outcomes);
726
809
  return yield* failWhen(outcomes.flatMap((outcome) => outcome.problems));
727
810
  }).pipe(effect.Effect.provide(require_config.ClientLayer))).pipe(_effect_cli.Command.withDescription("Compile and run an eval from TypeScript"), _effect_cli.Command.withSubcommands([importEval]));
728
811
  //#endregion