anpord 0.1.10 → 0.1.12

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 (58) hide show
  1. package/README.md +56 -78
  2. package/dist/bin.cjs +6 -82
  3. package/dist/bin.mjs +6 -82
  4. package/dist/cli-runtime.cjs +10 -12
  5. package/dist/cli-runtime.d.cts +1 -1
  6. package/dist/cli-runtime.d.mts +1 -1
  7. package/dist/cli-runtime.mjs +10 -12
  8. package/dist/cli.cjs +1 -1
  9. package/dist/cli.d.cts +1 -1
  10. package/dist/cli.d.mts +1 -1
  11. package/dist/cli.mjs +1 -1
  12. package/dist/client-BnimnYiJ.cjs +345 -0
  13. package/dist/{client-C9m0uLCz.d.cts → client-Byoa41pX.d.mts} +346 -34
  14. package/dist/{client-TFCLuMv8.d.mts → client-CFTj9ad0.d.cts} +346 -34
  15. package/dist/client-D22IiHGL.mjs +304 -0
  16. package/dist/{compiler-aFvZixb7.cjs → compiler-BFxukiJy.cjs} +62 -34
  17. package/dist/{compiler-CKnNV-Ct.mjs → compiler-CBMPXIzV.mjs} +52 -24
  18. package/dist/config.cjs +1 -1
  19. package/dist/config.d.cts +1 -1
  20. package/dist/config.d.mts +1 -1
  21. package/dist/config.mjs +1 -1
  22. package/dist/{define-Dxk8YZ4T.d.cts → define-BEiYXrC5.d.cts} +2 -2
  23. package/dist/{define-Dxk8YZ4T.d.mts → define-BEiYXrC5.d.mts} +2 -2
  24. package/dist/{define-Bh3A5AZv.cjs → define-CHx2lRp5.cjs} +5 -5
  25. package/dist/{define-Chq7EOpT.mjs → define-CLhfETBd.mjs} +5 -5
  26. package/dist/{errors-C9bQUcA3.mjs → errors-B0YknR5V.mjs} +3 -19
  27. package/dist/{errors-Boum34f5.cjs → errors-BX1wry8K.cjs} +3 -19
  28. package/dist/{errors-BK3f7Rdr.d.cts → errors-aGrd21jy.d.cts} +0 -3
  29. package/dist/{errors-BK3f7Rdr.d.mts → errors-aGrd21jy.d.mts} +0 -3
  30. package/dist/eval-judges-BkmiVLLq.cjs +59 -0
  31. package/dist/eval-judges-CwTvTq7N.mjs +42 -0
  32. package/dist/eval-judges-DPPUftbh.d.cts +48 -0
  33. package/dist/eval-judges-DPPUftbh.d.mts +48 -0
  34. package/dist/eval.cjs +1 -1
  35. package/dist/eval.d.cts +41 -7
  36. package/dist/eval.d.mts +41 -7
  37. package/dist/eval.mjs +1 -1
  38. package/dist/evals-B04M0apj.cjs +682 -0
  39. package/dist/{evals-BEgEmfiO.d.mts → evals-BUBzpke_.d.cts} +53 -9
  40. package/dist/{evals-BEgEmfiO.d.cts → evals-BUBzpke_.d.mts} +53 -9
  41. package/dist/evals-CvOTyvCX.mjs +539 -0
  42. package/dist/index.cjs +23 -99
  43. package/dist/index.d.cts +13 -33
  44. package/dist/index.d.mts +13 -33
  45. package/dist/index.mjs +23 -99
  46. package/dist/source.d.cts +1 -1
  47. package/dist/source.d.mts +1 -1
  48. package/dist/{types-BFF8MwhF.d.cts → types-nr4-1c3W.d.cts} +88 -33
  49. package/dist/{types-DPDjM69Q.d.mts → types-oXWOHA-X.d.mts} +88 -33
  50. package/dist/validators.cjs +10 -0
  51. package/dist/validators.d.cts +8 -0
  52. package/dist/validators.d.mts +8 -0
  53. package/dist/validators.mjs +9 -0
  54. package/package.json +11 -1
  55. package/dist/client-5K3dMI5Q.mjs +0 -849
  56. package/dist/client-Ci0woWLW.cjs +0 -890
  57. package/dist/harness-profile-BMjsN460.mjs +0 -43
  58. package/dist/harness-profile-CDQpPLPd.cjs +0 -78
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # anpord
2
2
 
3
- TypeScript SDK for [Anpord](https://anpord.com). Run coding agent evals across harnesses, models, and sandboxes, and manage versioned prompts from the same client.
3
+ TypeScript SDK for [Anpord](https://anpord.com). Define coding-agent evals, run them across agents and sandboxes, and manage versioned prompts.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,116 +8,71 @@ TypeScript SDK for [Anpord](https://anpord.com). Run coding agent evals across h
8
8
  npm install anpord
9
9
  ```
10
10
 
11
- ## Run an eval
11
+ ## Define an eval
12
12
 
13
13
  ```ts
14
- import { Anpord } from "anpord";
15
-
16
- const anpord = new Anpord({ apiKey: process.env.ANPORD_API_KEY });
14
+ import { defineEval, empty } from "anpord";
17
15
 
18
- const run = await anpord.evals.startAndWait({
16
+ export default defineEval({
17
+ name: "greeting",
18
+ source: empty,
19
+ prompt: "{{task}}",
19
20
  cases: [
20
21
  {
21
- name: "writes the requested file",
22
+ name: "writes hello",
22
23
  variables: { task: "Create hello.txt containing exactly hello" },
23
- verify: "test \"$(cat hello.txt)\" = hello",
24
+ verify: 'test "$(cat hello.txt)" = hello',
24
25
  },
25
26
  ],
26
- prompt: "{{task}}",
27
- tasks: [{ harness: "codex", model: "gpt-5.6-sol", provider: "daytona" }],
27
+ tasks: [{ harness: "codex", model: "model-id", provider: "daytona" }],
28
28
  trials: 3,
29
29
  });
30
-
31
- for (const cell of run.cells) {
32
- console.log(cell.caseName, cell.distribution?.passRate);
33
- }
34
30
  ```
35
31
 
36
- For a TypeScript validator, export the function from its own file:
37
-
38
- ```ts
39
- import type { Validator } from "anpord";
40
-
41
- export const hasGreeting: Validator = async ({ readText }) =>
42
- (await readText("hello.txt")) === "hello";
32
+ ```sh
33
+ npx anpord eval ./greeting.eval.ts
43
34
  ```
44
35
 
45
- Reference it directly from `greeting.eval.ts`:
36
+ With no file, the CLI discovers every `**/*.eval.ts` suite.
46
37
 
47
- ```ts
48
- import { defineEval } from "anpord";
49
- import { hasGreeting } from "./validators/greeting";
38
+ ## Mock MCP and CLI
50
39
 
51
- export default defineEval({
52
- name: "greeting",
53
- cases: [
54
- {
55
- name: "greeting",
56
- variables: { task: "Write hello.txt" },
57
- validate: hasGreeting,
58
- },
59
- ],
60
- prompt: "{{task}}",
61
- tasks: [{ harness: "codex", model: "gpt-5.6-sol", provider: "daytona" }],
62
- trials: 3,
63
- });
64
- ```
40
+ Install Zod for the examples:
65
41
 
66
42
  ```sh
67
- npx anpord eval
43
+ npm install zod
68
44
  ```
69
45
 
70
- With no paths, the CLI discovers every `**/*.eval.ts` file and starts the
71
- suites concurrently. Pass files or directories to run a smaller set.
72
-
73
- ## Mock MCP servers
74
-
75
- Define local MCP dependencies once and Anpord gives every built-in agent trial
76
- a fresh stdio server:
77
-
78
46
  ```ts
79
47
  import { z } from "zod";
80
- import { defineEval } from "anpord";
81
48
  import { server, tool } from "anpord/mcp";
82
49
 
83
- const api = server({
84
- name: "example",
50
+ const User = z.object({ id: z.string(), name: z.string() });
51
+
52
+ export const usersMcp = server({
53
+ name: "users",
85
54
  version: "1.0.0",
86
55
  tools: [
87
56
  tool({
88
57
  name: "users_get",
89
58
  inputSchema: z.object({ id: z.string() }),
90
- outputSchema: z.object({ id: z.string(), name: z.string() }),
59
+ outputSchema: User,
91
60
  handler: ({ id }) => ({ id, name: "Ada" }),
92
61
  }),
93
62
  ],
94
63
  });
95
-
96
- export default defineEval({
97
- mcp: [api],
98
- // cases, prompt, tasks, and trials
99
- });
100
64
  ```
101
65
 
102
- Handlers return plain values or promises. Validators can inspect exact calls
103
- with `await context.mcp.calls("example")`. No service credentials are needed.
104
-
105
- ## Mock CLIs
106
-
107
- Define a local executable with the same Standard Schema fixtures and attach it
108
- to any eval:
109
-
110
66
  ```ts
111
67
  import { z } from "zod";
112
- import { defineEval } from "anpord";
113
68
  import { cli, command } from "anpord/cli";
114
69
 
115
- const client = cli({
116
- name: "example",
70
+ export const usersCli = cli({
71
+ path: "users",
117
72
  version: "1.0.0",
118
73
  commands: [
119
74
  command({
120
- name: "users get",
75
+ path: ["get"],
121
76
  inputSchema: z.object({ id: z.string() }),
122
77
  outputSchema: z.object({ id: z.string(), name: z.string() }),
123
78
  options: { id: { type: "string" } },
@@ -125,24 +80,47 @@ const client = cli({
125
80
  }),
126
81
  ],
127
82
  });
83
+ ```
128
84
 
129
- export default defineEval({
130
- cli: [client],
131
- // cases, prompt, tasks, and trials
85
+ Attach definitions with `mcp: [usersMcp]` or `cli: [usersCli]`. Handlers use plain TypeScript. Schemas infer handler types and validate calls at runtime.
86
+
87
+ ## Model judges
88
+
89
+ ```ts
90
+ import { judge } from "anpord/validators";
91
+
92
+ const correctness = judge({
93
+ name: "correctness",
94
+ harness: "codex",
95
+ model: "gpt-5.6-sol",
96
+ rubric: "The answer matches the reference without inventing facts.",
97
+ expected: "Ada",
98
+ choices: { correct: 1, incorrect: 0 },
132
99
  });
133
100
  ```
134
101
 
135
- Every built-in agent receives the executable on `PATH`. Validators can inspect
136
- calls with `await context.cli.calls("example")`.
102
+ Set `validate: correctness`, or combine it with code: `validate: [checkToolCalls, correctness]`. Every check must pass. Judges use the organization's harness connection, including Codex subscriptions. For direct OpenAI calls, use `provider: "openai"` and configure an OpenAI key in the environment connection.
137
103
 
138
- ## Resolve a prompt
104
+ See [model judges](https://docs.anpord.com/evals/judges) for isolation, authentication, and unscored failures.
105
+
106
+ ## Use the API
139
107
 
140
108
  ```ts
141
- const prompt = await anpord.prompts.get({ id: "support-reply" });
142
- console.log(prompt.content, prompt.version);
109
+ import { Anpord } from "anpord";
110
+ import { compileEval } from "anpord/eval";
111
+
112
+ const anpord = new Anpord();
113
+ const run = await anpord.evals.startAndWait(
114
+ await compileEval("./greeting.eval.ts")
115
+ );
116
+
117
+ console.log(run.status, run.cells);
118
+ await anpord.dispose();
143
119
  ```
144
120
 
145
- See the [documentation](https://docs.anpord.com) for eval concepts, prompt releases, SDK methods, and the API reference.
121
+ The client reads `ANPORD_API_KEY`.
122
+
123
+ See the [documentation](https://docs.anpord.com) for cases, validators, profiles, mock interfaces, prompt releases, and the API reference.
146
124
 
147
125
  ## License
148
126
 
package/dist/bin.cjs CHANGED
@@ -1,23 +1,19 @@
1
1
  #!/usr/bin/env node
2
- const require_client = require("./client-Ci0woWLW.cjs");
3
- const require_errors = require("./errors-Boum34f5.cjs");
2
+ const require_client = require("./client-BnimnYiJ.cjs");
3
+ const require_errors = require("./errors-BX1wry8K.cjs");
4
4
  const require_config = require("./config.cjs");
5
- const require_compiler = require("./compiler-aFvZixb7.cjs");
5
+ const require_compiler = require("./compiler-BFxukiJy.cjs");
6
6
  let _effect_cli = require("@effect/cli");
7
7
  let _effect_platform = require("@effect/platform");
8
8
  let _effect_platform_node = require("@effect/platform-node");
9
9
  let effect = require("effect");
10
10
  let yaml = require("yaml");
11
11
  //#region package.json
12
- var version$1 = "0.1.10";
12
+ var version$1 = "0.1.12";
13
13
  //#endregion
14
14
  //#region ../template/src/extract.ts
15
- /**
16
- * Distinct variable names in source order, so callers can count or list them.
17
- *
18
- * Escaped braces are read here too, so a name the renderer will treat as
19
- * literal text is never reported as a variable the editor should draw.
20
- */
15
+ /** Reads escapes too, so text the renderer leaves literal is never reported as
16
+ * a variable. */
21
17
  function extractVariables(template) {
22
18
  const names = [];
23
19
  for (const [, open, close, name] of template.matchAll(require_errors.tokenMatcher())) if (open === void 0 && close === void 0 && name !== void 0) names.push(name);
@@ -25,13 +21,6 @@ function extractVariables(template) {
25
21
  }
26
22
  //#endregion
27
23
  //#region src/cli/declarations.ts
28
- /**
29
- * The generated file has to open by importing the module it augments.
30
- *
31
- * Without it `declare module` shadows the module rather than adding to it, and
32
- * every import of the SDK stops resolving with an error naming the missing
33
- * export rather than the file that hid it.
34
- */
35
24
  const PREAMBLE = [
36
25
  "// Generated by `anpord generate`. Do not edit.",
37
26
  "// Run it again after changing which variables a prompt uses.",
@@ -40,7 +29,6 @@ const PREAMBLE = [
40
29
  "declare module \"anpord\" {",
41
30
  " interface AnpordPromptVariables {"
42
31
  ].join("\n");
43
- /** Quoted, because an id may carry characters an identifier cannot. */
44
32
  const entry = (id, names) => {
45
33
  return ` "${id}": ${names.length === 0 ? "Record<string, never>" : `{ ${names.map((name) => `"${name}": string`).join("; ")} }`};`;
46
34
  };
@@ -108,8 +96,6 @@ const promptContent = (prompt) => effect.Effect.sync(() => {
108
96
  process.stdout.write(prompt.content);
109
97
  if (!prompt.content.endsWith("\n")) process.stdout.write("\n");
110
98
  });
111
- /** A result the caller may pipe into another tool, so it belongs on stdout
112
- * alongside the prompt content rather than beside the status messages. */
113
99
  const row = (line) => effect.Effect.sync(() => {
114
100
  process.stdout.write(`${line}\n`);
115
101
  });
@@ -192,8 +178,6 @@ var CaseFileNotJson = class extends effect.Data.TaggedError("CaseFileNotJson") {
192
178
  return `${this.path} is not JSON: ${this.reason}`;
193
179
  }
194
180
  };
195
- /** Names the case rather than the byte offset, because the author fixing it
196
- * reads the file by case, not by position. */
197
181
  var CaseFileNotEvalsJson = class extends effect.Data.TaggedError("CaseFileNotEvalsJson") {
198
182
  get message() {
199
183
  return `${this.path} is not an evals-json file: ${this.reason}`;
@@ -206,29 +190,17 @@ var CaseFileEmpty = class extends effect.Data.TaggedError("CaseFileEmpty") {
206
190
  };
207
191
  //#endregion
208
192
  //#region src/imports/typescript-literal.ts
209
- /** A backslash-escaped double-quoted literal. Backslash first, so the escapes
210
- * added after it are not themselves escaped. */
211
193
  const quoted = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}"`;
212
- /** A template literal, which keeps a multi-line prompt readable in the
213
- * generated file. A backtick ends the literal and `${` opens a substitution,
214
- * so both are escaped; a lone `$` is left alone because only the pair means
215
- * anything. A trailing backslash would escape the closing backtick. */
216
194
  const templated = (value) => `\`${value.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${")}\``;
217
- /** A comment cannot carry the sequence that closes it, and a newline would end
218
- * a line comment and let the rest of the text run as code. */
219
195
  const commentSafe = (value) => value.replaceAll("*/", "*\\/").replaceAll(/\r?\n/g, " ");
220
196
  //#endregion
221
197
  //#region src/imports/unwritten-check.ts
222
198
  const PLACEHOLDER = "unwritten";
223
- /** The author's own words, kept whole, because they are the specification for
224
- * the check that replaces the line beneath them. */
225
199
  const proseLine = (text) => [
226
200
  " /* Write this check, then delete the line under it: */",
227
201
  ` /* ${commentSafe(text)} */`,
228
202
  ` ${PLACEHOLDER}(${quoted(text)}),`
229
203
  ].join("\n");
230
- /** Local rather than imported, so the generated file carries its own proof
231
- * that an unconverted assertion fails. Deleting the last call deletes it. */
232
204
  const placeholderBlock = [
233
205
  "/* A check nobody has written yet. It is false, so the case stays red until",
234
206
  " the sentence above it becomes a real check. The argument is that",
@@ -259,8 +231,6 @@ const nameOf = (subject) => `${slug$1(subject.name ?? "case", "case")}-${subject
259
231
  const scorerLine = (assertion) => [` /* ${commentSafe(assertion.text)} */`, ` ${SCORER_OF[assertion.kind]}(answer, [${assertion.needles.map(quoted).join(", ")}]),`].join("\n");
260
232
  const scorerLines = (subject) => subject.assertions.map((assertion) => isStructured(assertion) ? scorerLine(assertion) : proseLine(assertion)).join("\n");
261
233
  const expectationComment = (subject) => subject.expected_output === void 0 ? [] : [` /* The file's expected output: ${commentSafe(subject.expected_output)} */`];
262
- /** The JSON names fixture directories by convention and carries none of their
263
- * contents, so the author supplies the files the name stood for. */
264
234
  const sourceComment = (subject) => subject.files.length === 0 ? " /* This case named no fixture directory. Add the files it starts from. */" : ` /* This case named ${subject.files.map(commentSafe).join(", ")}. The JSON carries the directory names, not their contents, so add the files here. */`;
265
235
  const caseBlock$1 = (subject) => [
266
236
  " {",
@@ -319,16 +289,12 @@ const renderEvalSuite = (file) => {
319
289
  };
320
290
  //#endregion
321
291
  //#region src/imports/evals-json-schema.ts
322
- /** The three checks that convert mechanically, because each is a needle list
323
- * and a rule over it rather than a description of intent. */
324
292
  const AssertionKind = effect.Schema.Literal("content_contains_any", "content_contains_all", "content_contains_none");
325
293
  const StructuredAssertion = effect.Schema.Struct({
326
294
  kind: AssertionKind,
327
295
  needles: effect.Schema.Array(effect.Schema.String),
328
296
  text: effect.Schema.String
329
297
  });
330
- /** Both dialects occur, sometimes in sibling files: a bare string is prose one
331
- * person wrote for another, an object is a mechanical check. */
332
298
  const Assertion = effect.Schema.Union(effect.Schema.String, StructuredAssertion);
333
299
  const EvalsJsonCase = effect.Schema.Struct({
334
300
  assertions: effect.Schema.Array(Assertion),
@@ -356,8 +322,6 @@ const idAt = (parsed, index) => {
356
322
  const found = parsed?.evals?.[index];
357
323
  return found?.id === void 0 ? `${index}` : String(found.id);
358
324
  };
359
- /** The case as its author sees it. A path into the decoded value tells them
360
- * nothing; the id is what they search the file for. */
361
325
  const located = (parsed, path) => {
362
326
  const [head, index, ...rest] = path;
363
327
  if (head !== "evals" || typeof index !== "number") return path.map(String).join(".");
@@ -393,8 +357,6 @@ var CaseFileNotYaml = class extends effect.Data.TaggedError("CaseFileNotYaml") {
393
357
  return `${this.path} is not YAML: ${this.reason}`;
394
358
  }
395
359
  };
396
- /** Names the file rather than a path into the decoded value, because one case
397
- * is one file and the file name is what the author searches for. */
398
360
  var CaseFileNotYamlCase = class extends effect.Data.TaggedError("CaseFileNotYamlCase") {
399
361
  get message() {
400
362
  return `${this.path} is not a yaml case: ${this.reason}`;
@@ -411,20 +373,12 @@ const slug = (value, fallback) => {
411
373
  const cleaned = value.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
412
374
  return cleaned === "" ? fallback : cleaned;
413
375
  };
414
- /** Every line is prose a person wrote for a model judge, so none of it
415
- * converts and the whole list is what a human still owes. A case with no
416
- * lines owes one too: something has to say what a good answer is. */
417
376
  const tallyOf = (files) => ({
418
377
  cases: files.length,
419
378
  converted: 0,
420
379
  needsAuthor: files.reduce((total, file) => total + Math.max(file.subject.judge_context.length, 1), 0)
421
380
  });
422
- /** Anpord has no step budget, so the number is kept as a note rather than
423
- * dropped: the suite it came from ran under it, and a case that needed ten
424
- * steps is a different case from one that needed a hundred. */
425
381
  const budgetComment = (subject) => ` /* The file allowed ${subject.max_steps} steps. Anpord does not cap steps, so this is a note, not a limit. */`;
426
- /** A file with no judge context says nothing about what a good answer is, so
427
- * it is owed a check like any other rather than passing by default. */
428
382
  const UNJUDGED = "This case named no judge context. Write what a good answer is.";
429
383
  const judgeLines = (subject) => (subject.judge_context.length === 0 ? [UNJUDGED] : subject.judge_context).map((line) => proseLine(line)).join("\n");
430
384
  const caseBlock = (file) => [
@@ -446,8 +400,6 @@ const caseBlock = (file) => [
446
400
  " },",
447
401
  " },"
448
402
  ].join("\n");
449
- /** A directory has no name of its own in the files, so a suite built from
450
- * several is named generically and the author renames it. */
451
403
  const suiteName = (files) => files.length === 1 ? slug(files[0]?.subject.name ?? "", "imported-suite") : "imported-suite";
452
404
  const renderYamlSuite = (files) => `${[
453
405
  "import { defineEval, files } from \"anpord\";",
@@ -469,12 +421,7 @@ const renderYamlSuite = (files) => `${[
469
421
  ].join("\n")}\n`;
470
422
  //#endregion
471
423
  //#region src/imports/yaml-cases-schema.ts
472
- /** The runner's own default when a file omits the field, kept here so an
473
- * imported case carries the budget it actually ran under. */
474
424
  const DEFAULT_MAX_STEPS = 15;
475
- /** A missing `judge_context` is a case whose author wrote nothing about how to
476
- * judge it, which the runner reads as one generic line. It is not an error, so
477
- * it decodes to an empty list and the generated case says so. */
478
425
  const YamlCase = effect.Schema.Struct({
479
426
  judge_context: effect.Schema.optionalWith(effect.Schema.Array(effect.Schema.String), { default: () => [] }),
480
427
  max_steps: effect.Schema.optionalWith(effect.Schema.Int, { default: () => DEFAULT_MAX_STEPS }),
@@ -484,10 +431,6 @@ const YamlCase = effect.Schema.Struct({
484
431
  const decodeYamlCase = effect.Schema.decodeUnknown(YamlCase, { errors: "all" });
485
432
  //#endregion
486
433
  //#region src/imports/yaml-document.ts
487
- /** A YAML document can parse into a value while still carrying errors the
488
- * author needs to see, so the errors are read rather than the throw relied on.
489
- * The value is returned as `unknown` because the schema, not the parser,
490
- * decides what shape it has. */
491
434
  const parseYamlDocument = (body) => effect.Effect.try({
492
435
  catch: (cause) => cause instanceof Error ? cause.message : String(cause),
493
436
  try: () => {
@@ -519,8 +462,6 @@ const readCase = (path) => effect.Effect.gen(function* () {
519
462
  subject: yield* decode(path, parsed)
520
463
  };
521
464
  });
522
- /** Sorted, so the same directory always produces the same suite and a diff of
523
- * two imports shows what changed rather than what moved. */
524
465
  const caseFilesIn = (directory) => effect.Effect.gen(function* () {
525
466
  return (yield* (yield* _effect_platform.FileSystem.FileSystem).readDirectory(directory).pipe(effect.Effect.mapError((cause) => new CaseDirectoryUnreadable({
526
467
  cause,
@@ -547,20 +488,13 @@ const importYamlCases = (path) => effect.Effect.gen(function* () {
547
488
  }).pipe(effect.Effect.withSpan("Imports.yamlCases"));
548
489
  //#endregion
549
490
  //#region src/cli/import-formats.ts
550
- /** A format is an entry here, so adding one adds an entry rather than editing
551
- * the command. Each name carries the importer it names, so the parsed option
552
- * is the importer itself and no lookup can miss. */
553
491
  const FORMATS = [["evals-json", importEvalsJson], ["yaml", importYamlCases]];
554
492
  //#endregion
555
493
  //#region src/cli/eval-import.ts
556
- /** A path rather than a file, because a format may keep one case per file and
557
- * import a directory of them as one suite. */
558
494
  const caseFile = _effect_cli.Args.path({ name: "path" }).pipe(_effect_cli.Args.withDescription("The case file, or a directory of them, to read"));
559
495
  const format = _effect_cli.Options.choiceWithValue("format", FORMATS).pipe(_effect_cli.Options.withDescription("The case file's format"));
560
496
  const out$1 = _effect_cli.Options.file("out").pipe(_effect_cli.Options.withDescription("Where to write the suite; it goes to stdout when omitted"), _effect_cli.Options.optional);
561
497
  const plural = (count, one) => `${count} ${count === 1 ? one : `${one}s`}`;
562
- /** The count of unwritten checks leads, because a suite that reports a pass
563
- * for a check nobody wrote is the failure this product exists to prevent. */
564
498
  const summaryOf = (tally) => {
565
499
  const read = `Read ${plural(tally.cases, "case")}, converted ${plural(tally.converted, "assertion")}.`;
566
500
  return tally.needsAuthor === 0 ? read : `${read} ${plural(tally.needsAuthor, "assertion")} could not be converted and ${tally.needsAuthor === 1 ? "needs" : "need"} a human: each is written as prose, and the suite fails until you write the check it describes.`;
@@ -835,12 +769,6 @@ const promote = _effect_cli.Command.make("promote", {
835
769
  } });
836
770
  return yield* note(`${id} v${pin} is now ${to}`);
837
771
  })).pipe(_effect_cli.Command.withDescription("Point a channel at a version"));
838
- /**
839
- * Read from the stream rather than by opening `/dev/stdin` as a file. The path
840
- * only names the pipe, so reading it races whoever is writing: a body arriving
841
- * in more than one chunk, which is what a pipe does under load, was read as
842
- * whatever had landed by then.
843
- */
844
772
  const readStdin = effect.Effect.async((resume) => {
845
773
  let body = "";
846
774
  process.stdin.setEncoding("utf8");
@@ -865,8 +793,6 @@ const push = _effect_cli.Command.make("push", {
865
793
  return yield* note(`${id} is now v${prompt.version}`);
866
794
  })).pipe(_effect_cli.Command.withDescription("Add a version to a prompt"));
867
795
  const out = _effect_cli.Options.file("out").pipe(_effect_cli.Options.withDescription("Where to write the declarations"), _effect_cli.Options.withDefault("anpord-env.d.ts"));
868
- /** Reading one prompt at a time because the list carries no content, bounded
869
- * so a large organisation does not open a connection per prompt. */
870
796
  const READ_AT_ONCE = 8;
871
797
  const writeDeclarations = ({ out: path }) => effect.Effect.gen(function* () {
872
798
  const api = yield* require_client.AnpordApi;
@@ -878,8 +804,6 @@ const writeDeclarations = ({ out: path }) => effect.Effect.gen(function* () {
878
804
  });
879
805
  const DESCRIPTION = "Write TypeScript declarations for prompt variables";
880
806
  const generate = _effect_cli.Command.make("generate", { out }, writeDeclarations).pipe(_effect_cli.Command.withDescription(DESCRIPTION));
881
- /** A second command rather than an alias, because a command carries one name
882
- * and the shorter one is what anybody types twice. */
883
807
  const gen = _effect_cli.Command.make("gen", { out }, writeDeclarations).pipe(_effect_cli.Command.withDescription(DESCRIPTION));
884
808
  const withClient = (command) => _effect_cli.Command.provide(command, require_config.ClientLayer);
885
809
  const commands = [