@iowarp/clio-coder 0.3.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.
Files changed (226) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +224 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +9 -0
  6. package/README.md +798 -0
  7. package/SECURITY.md +72 -0
  8. package/assets/clio-coder-logo-128.webp +0 -0
  9. package/damage-control-rules.yaml +419 -0
  10. package/dist/acp-UMLFVA3F.js +92 -0
  11. package/dist/agents-Q4MYPMUW.js +91 -0
  12. package/dist/auth-O6HYIJ6J.js +521 -0
  13. package/dist/chunk-262G75JS.js +35 -0
  14. package/dist/chunk-26BZQOAD.js +1281 -0
  15. package/dist/chunk-2J63S4SF.js +508 -0
  16. package/dist/chunk-3DANZDGR.js +717 -0
  17. package/dist/chunk-4UQA7NCT.js +29 -0
  18. package/dist/chunk-527KG6XR.js +497 -0
  19. package/dist/chunk-5LDRNKX2.js +1063 -0
  20. package/dist/chunk-5N2FG33Q.js +25 -0
  21. package/dist/chunk-67MTHP2E.js +135 -0
  22. package/dist/chunk-6CWDTGUC.js +20 -0
  23. package/dist/chunk-7BHLZB3A.js +2115 -0
  24. package/dist/chunk-7RBKDI66.js +348 -0
  25. package/dist/chunk-AMFR5YA3.js +541 -0
  26. package/dist/chunk-BBUH4VAA.js +1224 -0
  27. package/dist/chunk-BYEU76JP.js +899 -0
  28. package/dist/chunk-CLJ5HLUD.js +458 -0
  29. package/dist/chunk-D5YD55AR.js +116 -0
  30. package/dist/chunk-DXQNI4PC.js +61 -0
  31. package/dist/chunk-E3NYWENM.js +1004 -0
  32. package/dist/chunk-GNGDQYDU.js +34688 -0
  33. package/dist/chunk-GOTUR54M.js +9 -0
  34. package/dist/chunk-HBU5MTAM.js +41 -0
  35. package/dist/chunk-HMYNFFY4.js +28 -0
  36. package/dist/chunk-JPOWPFCU.js +1010 -0
  37. package/dist/chunk-JWHCJDCI.js +1215 -0
  38. package/dist/chunk-KBR4MZZR.js +41 -0
  39. package/dist/chunk-KKKPTZLM.js +93 -0
  40. package/dist/chunk-ME6DNWIU.js +66 -0
  41. package/dist/chunk-NI4DEJMC.js +88 -0
  42. package/dist/chunk-O4EJEDHO.js +659 -0
  43. package/dist/chunk-PIDUD6M2.js +31 -0
  44. package/dist/chunk-PS4PFJQP.js +29459 -0
  45. package/dist/chunk-QV47YRF4.js +48 -0
  46. package/dist/chunk-RQDWMVRB.js +279 -0
  47. package/dist/chunk-TFSSEXL6.js +136 -0
  48. package/dist/chunk-TKHQ4DGZ.js +8290 -0
  49. package/dist/chunk-TPOCL34A.js +2876 -0
  50. package/dist/chunk-UGYAX5YI.js +565 -0
  51. package/dist/chunk-UHTSULZS.js +461 -0
  52. package/dist/chunk-UU3R62TT.js +128 -0
  53. package/dist/chunk-UWIJNAOB.js +3906 -0
  54. package/dist/chunk-VOO7NYPP.js +914 -0
  55. package/dist/chunk-VPAWTYLY.js +117 -0
  56. package/dist/chunk-WD6AJM35.js +1216 -0
  57. package/dist/chunk-X3BR7HWV.js +115 -0
  58. package/dist/chunk-X3NE4WVW.js +120 -0
  59. package/dist/chunk-XNISANGE.js +1395 -0
  60. package/dist/chunk-XV4ZJ6ZM.js +3177 -0
  61. package/dist/cli/index.js +236 -0
  62. package/dist/clio-KIQ5SNDS.js +53 -0
  63. package/dist/components-JVHMUBEB.js +653 -0
  64. package/dist/config-ZFCDBMDC.js +372 -0
  65. package/dist/configure-G4E3A2PG.js +27 -0
  66. package/dist/context-CDXTP2MP.js +293 -0
  67. package/dist/context-E3KIFVXI.js +185 -0
  68. package/dist/context-clear-3F4PLXOS.js +102 -0
  69. package/dist/context-index-Q7YSYTR3.js +106 -0
  70. package/dist/docs-YIETIWZI.js +280 -0
  71. package/dist/doctor-M5HJJZOL.js +61 -0
  72. package/dist/domains/agents/builtins/architect.md +33 -0
  73. package/dist/domains/agents/builtins/coder.md +31 -0
  74. package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
  75. package/dist/domains/agents/builtins/debugger.md +30 -0
  76. package/dist/domains/agents/builtins/documenter.md +31 -0
  77. package/dist/domains/agents/builtins/git-master.md +30 -0
  78. package/dist/domains/agents/builtins/provenance.md +30 -0
  79. package/dist/domains/agents/builtins/researcher.md +71 -0
  80. package/dist/domains/agents/builtins/scout.md +42 -0
  81. package/dist/domains/agents/builtins/tester.md +31 -0
  82. package/dist/domains/agents/builtins/verifier.md +30 -0
  83. package/dist/domains/agents/builtins/wiki-writer.md +41 -0
  84. package/dist/eval-B3KZZESM.js +2674 -0
  85. package/dist/evidence-V67CHM35.js +233 -0
  86. package/dist/evolve-YDZSUQYA.js +518 -0
  87. package/dist/extensions-SRG7XCAH.js +207 -0
  88. package/dist/fleet-CA2CRTVG.js +760 -0
  89. package/dist/fleet-preflight-CLIAX7YR.js +21 -0
  90. package/dist/init-2OZDJE2D.js +227 -0
  91. package/dist/memory-3PIQQAKX.js +207 -0
  92. package/dist/models-DY35XI7Y.js +237 -0
  93. package/dist/paths-5OMXW7Z4.js +57 -0
  94. package/dist/preload-KZVHET2B.js +11 -0
  95. package/dist/reset-PIFYNOS3.js +216 -0
  96. package/dist/run-3VSPP24F.js +735 -0
  97. package/dist/share-D36RQCXM.js +241 -0
  98. package/dist/skills-F2MRLELY.js +445 -0
  99. package/dist/skills-eval-E2ZTW4PL.js +932 -0
  100. package/dist/targets-DZMEZAH4.js +977 -0
  101. package/dist/trace-7NYCUI2J.js +250 -0
  102. package/dist/uninstall-AD3JWHBB.js +322 -0
  103. package/dist/upgrade-WYYBKGDY.js +301 -0
  104. package/dist/usage-ULIDAGFF.js +755 -0
  105. package/dist/version-ROZ6CZKH.js +16 -0
  106. package/dist/wiki-generate-PKFIX6OB.js +377 -0
  107. package/dist/worker/entry.js +1739 -0
  108. package/docs/README.md +93 -0
  109. package/docs/acp.md +120 -0
  110. package/docs/alcf-provider.md +72 -0
  111. package/docs/architecture.md +172 -0
  112. package/docs/artifact-versions.md +54 -0
  113. package/docs/built-in-agents.md +265 -0
  114. package/docs/capacity-and-scheduling.md +97 -0
  115. package/docs/commands-and-modes.md +554 -0
  116. package/docs/config-knobs-audit.md +115 -0
  117. package/docs/configuration-and-targets.md +812 -0
  118. package/docs/context-engine.md +236 -0
  119. package/docs/dispatch-architecture-rationale.md +126 -0
  120. package/docs/documentation-coverage.md +46 -0
  121. package/docs/documentation-guide.md +166 -0
  122. package/docs/environment-variables.md +105 -0
  123. package/docs/eval-runner.md +205 -0
  124. package/docs/evals-internal.md +298 -0
  125. package/docs/evidence-and-memory.md +243 -0
  126. package/docs/evolution.md +143 -0
  127. package/docs/exit-codes-and-output.md +74 -0
  128. package/docs/extensions-and-sharing.md +306 -0
  129. package/docs/fleet-demo-runbook.md +179 -0
  130. package/docs/fleet-dispatch.md +591 -0
  131. package/docs/glossary.md +75 -0
  132. package/docs/html/agents_blueprint.html +936 -0
  133. package/docs/html/alcf_blueprint.html +324 -0
  134. package/docs/html/architecture_blueprint.html +850 -0
  135. package/docs/html/commands_blueprint.html +794 -0
  136. package/docs/html/config_knobs_audit_blueprint.html +178 -0
  137. package/docs/html/configuration_blueprint.html +1080 -0
  138. package/docs/html/context_blueprint.html +603 -0
  139. package/docs/html/documentation_blueprint.html +832 -0
  140. package/docs/html/environment_blueprint.html +404 -0
  141. package/docs/html/eval_blueprint.html +743 -0
  142. package/docs/html/evals_internal_blueprint.html +190 -0
  143. package/docs/html/evolution_blueprint.html +674 -0
  144. package/docs/html/extensions_blueprint.html +2065 -0
  145. package/docs/html/fleet_dispatch_blueprint.html +286 -0
  146. package/docs/html/index.html +919 -0
  147. package/docs/html/lifecycle_blueprint.html +723 -0
  148. package/docs/html/memory_blueprint.html +699 -0
  149. package/docs/html/middleware_blueprint.html +664 -0
  150. package/docs/html/models_blueprint.html +2366 -0
  151. package/docs/html/observability_blueprint.html +683 -0
  152. package/docs/html/provider_adapter_blueprint.html +245 -0
  153. package/docs/html/safety_blueprint.html +1386 -0
  154. package/docs/html/shared.css +571 -0
  155. package/docs/html/shared.js +143 -0
  156. package/docs/html/skills_blueprint.html +671 -0
  157. package/docs/html/soak_blueprint.html +182 -0
  158. package/docs/html/tool_usage_blueprint.html +350 -0
  159. package/docs/html/tools_blueprint.html +2249 -0
  160. package/docs/html/trace_blueprint.html +235 -0
  161. package/docs/html/tui_design_blueprint.html +314 -0
  162. package/docs/html/validation_blueprint.html +961 -0
  163. package/docs/html/worker_dispatch_blueprint.html +231 -0
  164. package/docs/installation-and-lifecycle.md +308 -0
  165. package/docs/middleware-and-components.md +148 -0
  166. package/docs/model-catalog.md +189 -0
  167. package/docs/observability.md +233 -0
  168. package/docs/proactive-memory.md +452 -0
  169. package/docs/prompt-envelope-and-tools.md +142 -0
  170. package/docs/provider-adapter-cookbook.md +148 -0
  171. package/docs/release-cut-checklist.md +138 -0
  172. package/docs/safety-model.md +357 -0
  173. package/docs/scientific-validation.md +105 -0
  174. package/docs/session-lifecycle.md +156 -0
  175. package/docs/skills-marketplace.md +46 -0
  176. package/docs/tool-usage.md +527 -0
  177. package/docs/trace-store.md +132 -0
  178. package/docs/troubleshooting.md +33 -0
  179. package/docs/tui-design.md +239 -0
  180. package/docs/worker-dispatch-mechanics.md +242 -0
  181. package/package.json +132 -0
  182. package/skills/README.md +408 -0
  183. package/skills/git/commit-crafting/SKILL.md +79 -0
  184. package/skills/git/commit-crafting/evals.md +92 -0
  185. package/skills/git/create-pr/SKILL.md +116 -0
  186. package/skills/git/create-pr/evals.md +114 -0
  187. package/skills/git/investigate-issue/SKILL.md +139 -0
  188. package/skills/git/investigate-issue/evals.md +94 -0
  189. package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
  190. package/skills/git/resolve-merge-conflicts/evals.md +58 -0
  191. package/skills/git/review-changes/SKILL.md +103 -0
  192. package/skills/git/review-changes/evals.md +85 -0
  193. package/skills/git/worktree-create/SKILL.md +92 -0
  194. package/skills/git/worktree-create/evals.md +97 -0
  195. package/skills/git/worktree-create/references/worktree-setup.md +66 -0
  196. package/skills/git/worktree-merge/SKILL.md +95 -0
  197. package/skills/git/worktree-merge/evals.md +114 -0
  198. package/skills/skill-marketplace.json +261 -0
  199. package/skills/workflow/cut-it/SKILL.md +86 -0
  200. package/skills/workflow/cut-it/evals.md +42 -0
  201. package/src/domains/agents/builtins/architect.md +33 -0
  202. package/src/domains/agents/builtins/coder.md +31 -0
  203. package/src/domains/agents/builtins/context-bootstrap.md +38 -0
  204. package/src/domains/agents/builtins/debugger.md +30 -0
  205. package/src/domains/agents/builtins/documenter.md +31 -0
  206. package/src/domains/agents/builtins/git-master.md +30 -0
  207. package/src/domains/agents/builtins/provenance.md +30 -0
  208. package/src/domains/agents/builtins/researcher.md +71 -0
  209. package/src/domains/agents/builtins/scout.md +42 -0
  210. package/src/domains/agents/builtins/tester.md +31 -0
  211. package/src/domains/agents/builtins/verifier.md +30 -0
  212. package/src/domains/agents/builtins/wiki-writer.md +41 -0
  213. package/src/domains/agents/fleets/build-review.md +34 -0
  214. package/src/domains/agents/fleets/build-test.md +35 -0
  215. package/src/domains/agents/fleets/sdlc.md +86 -0
  216. package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
  217. package/src/domains/prompts/fragments/identity/clio.md +26 -0
  218. package/src/domains/prompts/fragments/operating/contract.md +64 -0
  219. package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
  220. package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
  221. package/src/domains/prompts/fragments/safety/read-only.md +13 -0
  222. package/src/domains/prompts/fragments/safety/suggest.md +13 -0
  223. package/src/domains/prompts/fragments/wiki/page.md +75 -0
  224. package/src/domains/prompts/fragments/wiki/plan.md +48 -0
  225. package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
  226. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
@@ -0,0 +1,2674 @@
1
+ import {
2
+ renderCodewikiDigest
3
+ } from "./chunk-X3BR7HWV.js";
4
+ import {
5
+ EvalTaskFileError,
6
+ createEvalId,
7
+ evalClioProvenance,
8
+ evalEnvironmentProvenance,
9
+ loadEvalArtifactV4,
10
+ loadEvalTaskFile,
11
+ verifyReceiptIntegrity,
12
+ writeEvalArtifactV4
13
+ } from "./chunk-7BHLZB3A.js";
14
+ import {
15
+ detectProjectProfile,
16
+ enumerateWorkspaceFiles,
17
+ readCodewiki,
18
+ structuralCodewikiHash
19
+ } from "./chunk-TPOCL34A.js";
20
+ import "./chunk-WD6AJM35.js";
21
+ import "./chunk-QV47YRF4.js";
22
+ import {
23
+ shellQuote
24
+ } from "./chunk-GOTUR54M.js";
25
+ import "./chunk-PIDUD6M2.js";
26
+ import {
27
+ printError
28
+ } from "./chunk-67MTHP2E.js";
29
+ import "./chunk-5LDRNKX2.js";
30
+ import {
31
+ InvalidIdError
32
+ } from "./chunk-5N2FG33Q.js";
33
+ import "./chunk-VPAWTYLY.js";
34
+ import {
35
+ clioDataDir
36
+ } from "./chunk-X3NE4WVW.js";
37
+ import "./chunk-DXQNI4PC.js";
38
+
39
+ // src/cli/eval.ts
40
+ import { resolve as resolve7 } from "node:path";
41
+
42
+ // src/domains/eval/compare/compare.ts
43
+ function compareEvalArtifactsV4(baseline, candidate) {
44
+ const baselineTokens = baseline.summary.tokens;
45
+ const candidateTokens = candidate.summary.tokens;
46
+ return {
47
+ baselineEvalId: baseline.evalId,
48
+ candidateEvalId: candidate.evalId,
49
+ passRateDelta: candidate.summary.passRate - baseline.summary.passRate,
50
+ tokenDelta: baselineTokens.measured && candidateTokens.measured ? candidateTokens.total - baselineTokens.total : null,
51
+ wallTimeDelta: candidate.summary.wallTimeMs - baseline.summary.wallTimeMs
52
+ };
53
+ }
54
+ function renderEvalComparisonV4(summary) {
55
+ return [
56
+ `baseline eval: ${summary.baselineEvalId}`,
57
+ `candidate eval: ${summary.candidateEvalId}`,
58
+ `pass-rate delta: ${(summary.passRateDelta * 100).toFixed(2)}%`,
59
+ `token delta: ${summary.tokenDelta === null ? "unmeasured" : summary.tokenDelta}`,
60
+ `wall-time delta ms: ${summary.wallTimeDelta}`,
61
+ ""
62
+ ].join("\n");
63
+ }
64
+
65
+ // src/domains/eval/compare/thresholds.ts
66
+ import { readFileSync } from "node:fs";
67
+ import { parse as parseYaml } from "yaml";
68
+ function loadThresholds(path) {
69
+ const parsed = parseYaml(readFileSync(path, "utf8"));
70
+ if (isRecord(parsed) && Array.isArray(parsed.fail)) return { fail: parsed.fail };
71
+ if (isRecord(parsed) && isRecord(parsed.thresholds) && Array.isArray(parsed.thresholds.fail)) {
72
+ return { fail: parsed.thresholds.fail };
73
+ }
74
+ throw new Error(`invalid thresholds file: ${path}`);
75
+ }
76
+ function resolveMetricAssertion(assertion, metrics, artifact) {
77
+ const actual = metricValue(assertion.metric, metrics, artifact);
78
+ return { actual, unresolved: actual === null, holds: comparisonHolds(assertion, actual) };
79
+ }
80
+ function comparisonHolds(assertion, actual) {
81
+ switch (assertion.op) {
82
+ case "lt":
83
+ return typeof actual === "number" && typeof assertion.value === "number" && actual < assertion.value;
84
+ case "lte":
85
+ return typeof actual === "number" && typeof assertion.value === "number" && actual <= assertion.value;
86
+ case "gt":
87
+ return typeof actual === "number" && typeof assertion.value === "number" && actual > assertion.value;
88
+ case "gte":
89
+ return typeof actual === "number" && typeof assertion.value === "number" && actual >= assertion.value;
90
+ case "eq":
91
+ return actual === assertion.value;
92
+ case "neq":
93
+ return actual !== assertion.value;
94
+ }
95
+ }
96
+ function metricValue(metric, metrics, artifact) {
97
+ if (metric in metrics) return metrics[metric];
98
+ if (artifact !== void 0) {
99
+ if (metric === "result.pass") return artifact.summary.failed === 0;
100
+ if (metric === "latency.wallMs") return artifact.summary.wallTimeMs;
101
+ if (metric === "tokens.total") return artifact.summary.tokens.measured ? artifact.summary.tokens.total : null;
102
+ if (metric.startsWith("summary.")) return summaryValue(artifact, metric.slice("summary.".length));
103
+ }
104
+ return null;
105
+ }
106
+ function summaryValue(artifact, path) {
107
+ let current = artifact.summary;
108
+ for (const segment of path.split(".")) {
109
+ if (!isRecord(current) || !(segment in current)) return null;
110
+ current = current[segment];
111
+ }
112
+ return typeof current === "number" || typeof current === "string" || typeof current === "boolean" ? current : null;
113
+ }
114
+ function isRecord(value) {
115
+ return typeof value === "object" && value !== null && !Array.isArray(value);
116
+ }
117
+
118
+ // src/domains/eval/compare/gates.ts
119
+ function evaluateGate(artifact, thresholds) {
120
+ const failures = [];
121
+ for (const assertion of thresholds.fail) {
122
+ const whole = resolveMetricAssertion(assertion, {}, artifact);
123
+ if (!whole.unresolved) {
124
+ if (whole.holds) failures.push({ assertion, actual: whole.actual, unresolved: false });
125
+ continue;
126
+ }
127
+ if (artifact.results.length === 0) {
128
+ failures.push({ assertion, actual: null, unresolved: true });
129
+ continue;
130
+ }
131
+ for (const result of artifact.results) {
132
+ const perRun = resolveMetricAssertion(assertion, result.metrics);
133
+ if (!perRun.unresolved && !perRun.holds) continue;
134
+ failures.push({
135
+ assertion,
136
+ actual: perRun.actual,
137
+ unresolved: perRun.unresolved,
138
+ taskId: result.taskId,
139
+ repeatIndex: result.repeatIndex
140
+ });
141
+ }
142
+ }
143
+ return { pass: failures.length === 0, failures };
144
+ }
145
+ function renderGateFailure(failure) {
146
+ const run = failure.taskId === void 0 ? "" : ` [${failure.taskId}#${failure.repeatIndex ?? 0}]`;
147
+ const { metric, op, value } = failure.assertion;
148
+ return failure.unresolved ? ` ${metric}${run}: unresolved metric (fail closed)
149
+ ` : ` ${metric} ${op} ${JSON.stringify(value)}${run}: actual ${JSON.stringify(failure.actual)}
150
+ `;
151
+ }
152
+
153
+ // src/domains/eval/reports/json.ts
154
+ function renderEvalJsonReportV4(artifact) {
155
+ return `${JSON.stringify(artifact, null, 2)}
156
+ `;
157
+ }
158
+
159
+ // src/domains/eval/reports/junit.ts
160
+ function renderEvalJunitReportV4(artifact) {
161
+ const cases = artifact.results.map((result) => {
162
+ const name = escapeXml(
163
+ `${result.taskId}[${result.target.id}:${result.target.model ?? "default"}:${result.repeatIndex}]`
164
+ );
165
+ if (result.pass) return ` <testcase name="${name}" />`;
166
+ return ` <testcase name="${name}"><failure message="${escapeXml(result.failureClass ?? "failed")}" /></testcase>`;
167
+ }).join("\n");
168
+ return [
169
+ `<testsuite name="${escapeXml(artifact.suite.id)}" tests="${artifact.summary.runs}" failures="${artifact.summary.failed}">`,
170
+ cases,
171
+ "</testsuite>",
172
+ ""
173
+ ].join("\n");
174
+ }
175
+ function escapeXml(value) {
176
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
177
+ }
178
+
179
+ // src/domains/eval/reports/markdown.ts
180
+ function renderEvalMarkdownReportV4(artifact) {
181
+ const lines = [
182
+ `# Eval ${artifact.evalId}`,
183
+ "",
184
+ `Suite: ${artifact.suite.id}`,
185
+ `Target: ${artifact.matrix.target}`,
186
+ `Pass rate: ${(artifact.summary.passRate * 100).toFixed(2)}%`,
187
+ "",
188
+ "| Task | Target | Model | Repeat | Pass | Failure |",
189
+ "|---|---|---|---:|---|---|",
190
+ ...artifact.results.map(
191
+ (result) => `| ${result.taskId} | ${result.target.id} | ${result.target.model ?? ""} | ${result.repeatIndex} | ${result.pass ? "pass" : "fail"} | ${result.failureClass ?? ""} |`
192
+ ),
193
+ ""
194
+ ];
195
+ return `${lines.join("\n")}`;
196
+ }
197
+
198
+ // src/domains/eval/reports/swe-jsonl.ts
199
+ function renderEvalSweJsonlReportV4(artifact) {
200
+ return artifact.results.map(
201
+ (result) => JSON.stringify({
202
+ instance_id: result.taskId,
203
+ model_name_or_path: artifact.matrix.model ?? artifact.matrix.target,
204
+ model_patch: typeof result.artifacts.patch === "string" ? result.artifacts.patch : "",
205
+ status: result.pass ? "pass" : "fail",
206
+ pass: result.pass
207
+ })
208
+ ).join("\n").concat(artifact.results.length === 0 ? "" : "\n");
209
+ }
210
+
211
+ // src/domains/eval/reports/text.ts
212
+ function renderEvalTextReportV4(artifact) {
213
+ const tokens = artifact.summary.tokens;
214
+ return [
215
+ `eval: ${artifact.evalId}`,
216
+ `suite: ${artifact.suite.id}`,
217
+ `target: ${artifact.matrix.target}`,
218
+ `model: ${artifact.matrix.model ?? "none"}`,
219
+ `runs: ${artifact.summary.runs}`,
220
+ `passed: ${artifact.summary.passed}`,
221
+ `failed: ${artifact.summary.failed}`,
222
+ `pass rate: ${(artifact.summary.passRate * 100).toFixed(2)}%`,
223
+ // A run whose child Clio work is out of the harness's sight has no token
224
+ // count. Saying "0" would claim it cost nothing, so the count is
225
+ // reported next to how many runs it actually covers.
226
+ !tokens.measured ? `tokens total: unmeasured (0 of ${tokens.runs} runs reported usage)` : tokens.measuredRuns === tokens.runs ? `tokens total: ${tokens.total}` : `tokens total: ${tokens.total} (measured in ${tokens.measuredRuns} of ${tokens.runs} runs)`,
227
+ `wall time ms: ${artifact.summary.wallTimeMs}`,
228
+ ""
229
+ ].join("\n");
230
+ }
231
+
232
+ // src/domains/eval/suites/load.ts
233
+ import { createHash } from "node:crypto";
234
+ import { readFile } from "node:fs/promises";
235
+ import { dirname, resolve } from "node:path";
236
+ import { parse as parseYaml2 } from "yaml";
237
+
238
+ // src/domains/eval/schema/suite.ts
239
+ var EVAL_SUITE_V2_VERSION = 2;
240
+
241
+ // src/domains/eval/schema/validate.ts
242
+ var RUNNER_KINDS = /* @__PURE__ */ new Set(["clio-run", "context-index", "context-init", "external-command"]);
243
+ var WORKSPACE_KINDS = /* @__PURE__ */ new Set(["local", "git", "temp-copy"]);
244
+ var OPS = /* @__PURE__ */ new Set(["lt", "lte", "gt", "gte", "eq", "neq"]);
245
+ function validateEvalSuiteV2(value) {
246
+ const issues = [];
247
+ if (!isRecord2(value)) return { valid: false, issues: [{ path: "$", message: "expected object" }] };
248
+ if (value.version !== EVAL_SUITE_V2_VERSION) issues.push({ path: "$.version", message: "expected version 2" });
249
+ const suite = readSuiteInfo(value.suite, "$.suite", issues);
250
+ const matrix = readMatrix(value.matrix, "$.matrix", issues);
251
+ const tasks = readTasks(value.tasks, "$.tasks", issues);
252
+ const thresholds = readThresholds(value.thresholds, "$.thresholds", issues);
253
+ if (issues.length > 0 || suite === null || matrix === null || tasks === null) return { valid: false, issues };
254
+ return {
255
+ valid: true,
256
+ suite: {
257
+ version: 2,
258
+ suite,
259
+ matrix,
260
+ tasks,
261
+ ...thresholds === void 0 ? {} : { thresholds }
262
+ }
263
+ };
264
+ }
265
+ function readSuiteInfo(value, path, issues) {
266
+ if (!isRecord2(value)) {
267
+ issues.push({ path, message: "expected object" });
268
+ return null;
269
+ }
270
+ const id = readId(value, path, "id", issues);
271
+ const title = readNonEmptyString(value, path, "title", issues);
272
+ const visibility = readNonEmptyString(value, path, "visibility", issues);
273
+ if (id === null || title === null || visibility === null) return null;
274
+ const description = optionalString(value, "description");
275
+ const provenance = isRecord2(value.provenance) ? value.provenance : void 0;
276
+ return {
277
+ id,
278
+ title,
279
+ visibility,
280
+ ...description === void 0 ? {} : { description },
281
+ ...provenance ? { provenance } : {}
282
+ };
283
+ }
284
+ function readMatrix(value, path, issues) {
285
+ if (!isRecord2(value)) {
286
+ issues.push({ path, message: "expected object" });
287
+ return null;
288
+ }
289
+ const repeats = readPositiveInteger(value, path, "repeats", issues);
290
+ if (!Array.isArray(value.targets) || value.targets.length === 0) {
291
+ issues.push({ path: `${path}.targets`, message: "expected non-empty array" });
292
+ return null;
293
+ }
294
+ const targets = value.targets.flatMap((target, index) => {
295
+ if (!isRecord2(target)) {
296
+ issues.push({ path: `${path}.targets[${index}]`, message: "expected object" });
297
+ return [];
298
+ }
299
+ const id = readId(target, `${path}.targets[${index}]`, "id", issues);
300
+ if (id === null) return [];
301
+ const suiteTarget = { id };
302
+ const model = optionalString(target, "model");
303
+ const thinking = optionalString(target, "thinking");
304
+ return [
305
+ { ...suiteTarget, ...model === void 0 ? {} : { model }, ...thinking === void 0 ? {} : { thinking } }
306
+ ];
307
+ });
308
+ if (repeats === null || targets.length === 0) return null;
309
+ const maxCostUsd = value.maxCostUsd;
310
+ if (maxCostUsd !== void 0 && (typeof maxCostUsd !== "number" || !Number.isFinite(maxCostUsd) || maxCostUsd < 0)) {
311
+ issues.push({ path: `${path}.maxCostUsd`, message: "expected non-negative number" });
312
+ return null;
313
+ }
314
+ return { targets, repeats, ...maxCostUsd === void 0 ? {} : { maxCostUsd } };
315
+ }
316
+ function readTasks(value, path, issues) {
317
+ if (!Array.isArray(value) || value.length === 0) {
318
+ issues.push({ path, message: "expected non-empty array" });
319
+ return null;
320
+ }
321
+ const seen = /* @__PURE__ */ new Set();
322
+ const tasks = [];
323
+ for (let index = 0; index < value.length; index += 1) {
324
+ const task = readTask(value[index], `${path}[${index}]`, issues);
325
+ if (task === null) continue;
326
+ if (seen.has(task.id)) issues.push({ path: `${path}[${index}].id`, message: `duplicate task id: ${task.id}` });
327
+ seen.add(task.id);
328
+ tasks.push(task);
329
+ }
330
+ return tasks.length > 0 ? tasks : null;
331
+ }
332
+ function readTask(value, path, issues) {
333
+ if (!isRecord2(value)) {
334
+ issues.push({ path, message: "expected object" });
335
+ return null;
336
+ }
337
+ const id = readId(value, path, "id", issues);
338
+ const workspace = readWorkspace(value.workspace, `${path}.workspace`, issues);
339
+ const runner = readRunner(value.runner, `${path}.runner`, issues);
340
+ const timeoutMs = readPositiveInteger(value, path, "timeoutMs", issues);
341
+ if (id === null || workspace === null || runner === null || timeoutMs === null) return null;
342
+ return {
343
+ id,
344
+ tags: readOptionalStringArray(value, "tags", `${path}.tags`, issues),
345
+ workspace,
346
+ runner,
347
+ verify: readVerify(value.verify, `${path}.verify`, issues),
348
+ metrics: readMetrics(value.metrics, `${path}.metrics`, issues),
349
+ timeoutMs
350
+ };
351
+ }
352
+ function readWorkspace(value, path, issues) {
353
+ if (!isRecord2(value)) {
354
+ issues.push({ path, message: "expected object" });
355
+ return null;
356
+ }
357
+ const kind = value.kind;
358
+ if (typeof kind !== "string" || !WORKSPACE_KINDS.has(kind)) {
359
+ issues.push({ path: `${path}.kind`, message: "expected local, git, or temp-copy" });
360
+ return null;
361
+ }
362
+ const pathValue = optionalString(value, "path");
363
+ const url = optionalString(value, "url");
364
+ const commit = optionalString(value, "commit");
365
+ const checkout = optionalString(value, "checkout");
366
+ return {
367
+ kind,
368
+ ...pathValue === void 0 ? {} : { path: pathValue },
369
+ ...url === void 0 ? {} : { url },
370
+ ...commit === void 0 ? {} : { commit },
371
+ ...checkout === void 0 ? {} : { checkout },
372
+ excludes: readOptionalStringArray(value, "excludes", `${path}.excludes`, issues),
373
+ setup: readOptionalStringArray(value, "setup", `${path}.setup`, issues)
374
+ };
375
+ }
376
+ function readRunner(value, path, issues) {
377
+ if (!isRecord2(value)) {
378
+ issues.push({ path, message: "expected object" });
379
+ return null;
380
+ }
381
+ const kind = value.kind;
382
+ if (typeof kind !== "string" || !RUNNER_KINDS.has(kind)) {
383
+ issues.push({ path: `${path}.kind`, message: "expected clio-run, context-index, context-init, or external-command" });
384
+ return null;
385
+ }
386
+ const prompt = optionalString(value, "prompt");
387
+ const agent = optionalString(value, "agent");
388
+ if (agent !== void 0 && kind !== "clio-run") {
389
+ issues.push({ path: `${path}.agent`, message: "agent is only valid on the clio-run runner" });
390
+ }
391
+ const command = optionalString(value, "command");
392
+ return {
393
+ kind,
394
+ ...prompt === void 0 ? {} : { prompt },
395
+ ...agent === void 0 ? {} : { agent },
396
+ ...command === void 0 ? {} : { command },
397
+ commands: readOptionalStringArray(value, "commands", `${path}.commands`, issues),
398
+ args: readOptionalStringArray(value, "args", `${path}.args`, issues),
399
+ ...typeof value.timeoutMs === "number" ? { timeoutMs: value.timeoutMs } : {}
400
+ };
401
+ }
402
+ function readVerify(value, path, issues) {
403
+ if (value === void 0) return {};
404
+ if (!isRecord2(value)) {
405
+ issues.push({ path, message: "expected object" });
406
+ return {};
407
+ }
408
+ return {
409
+ commands: readOptionalStringArray(value, "commands", `${path}.commands`, issues),
410
+ measure: readOptionalStringArray(value, "measure", `${path}.measure`, issues),
411
+ assertions: readAssertions(value.assertions, `${path}.assertions`, issues),
412
+ forbidPaths: readOptionalStringArray(value, "forbidPaths", `${path}.forbidPaths`, issues)
413
+ };
414
+ }
415
+ function readMetrics(value, path, issues) {
416
+ if (value === void 0) return { collect: [] };
417
+ if (!isRecord2(value)) {
418
+ issues.push({ path, message: "expected object" });
419
+ return { collect: [] };
420
+ }
421
+ return { collect: readOptionalStringArray(value, "collect", `${path}.collect`, issues) };
422
+ }
423
+ function readThresholds(value, path, issues) {
424
+ if (value === void 0) return void 0;
425
+ if (!isRecord2(value)) {
426
+ issues.push({ path, message: "expected object" });
427
+ return void 0;
428
+ }
429
+ return { fail: readAssertions(value.fail, `${path}.fail`, issues) };
430
+ }
431
+ function readAssertions(value, path, issues) {
432
+ if (value === void 0) return [];
433
+ if (!Array.isArray(value)) {
434
+ issues.push({ path, message: "expected array" });
435
+ return [];
436
+ }
437
+ const out = [];
438
+ for (let index = 0; index < value.length; index += 1) {
439
+ const entry = value[index];
440
+ if (!isRecord2(entry)) {
441
+ issues.push({ path: `${path}[${index}]`, message: "expected object" });
442
+ continue;
443
+ }
444
+ const metric = readNonEmptyString(entry, `${path}[${index}]`, "metric", issues);
445
+ const op = entry.op;
446
+ if (typeof op !== "string" || !OPS.has(op)) {
447
+ issues.push({ path: `${path}[${index}].op`, message: "expected lt, lte, gt, gte, eq, or neq" });
448
+ continue;
449
+ }
450
+ const valueField = entry.value;
451
+ if (metric === null || !["number", "string", "boolean"].includes(typeof valueField)) {
452
+ issues.push({ path: `${path}[${index}].value`, message: "expected number, string, or boolean" });
453
+ continue;
454
+ }
455
+ out.push({ metric, op, value: valueField });
456
+ }
457
+ return out;
458
+ }
459
+ function readId(record, path, field, issues) {
460
+ const value = readNonEmptyString(record, path, field, issues);
461
+ if (value !== null && !/^[A-Za-z0-9._-]+$/.test(value)) {
462
+ issues.push({
463
+ path: `${path}.${field}`,
464
+ message: "expected id with letters, numbers, dots, underscores, or hyphens"
465
+ });
466
+ }
467
+ return value;
468
+ }
469
+ function readNonEmptyString(record, path, field, issues) {
470
+ const value = record[field];
471
+ if (typeof value !== "string" || value.trim().length === 0) {
472
+ issues.push({ path: `${path}.${field}`, message: "expected non-empty string" });
473
+ return null;
474
+ }
475
+ return value;
476
+ }
477
+ function readPositiveInteger(record, path, field, issues) {
478
+ const value = record[field];
479
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
480
+ issues.push({ path: `${path}.${field}`, message: "expected positive integer" });
481
+ return null;
482
+ }
483
+ return value;
484
+ }
485
+ function readOptionalStringArray(record, field, path, issues) {
486
+ const value = record[field];
487
+ if (value === void 0) return [];
488
+ if (!Array.isArray(value)) {
489
+ issues.push({ path, message: "expected string array" });
490
+ return [];
491
+ }
492
+ return value.flatMap((entry, index) => {
493
+ if (typeof entry === "string" && entry.trim().length > 0) return [entry];
494
+ issues.push({ path: `${path}[${index}]`, message: "expected non-empty string" });
495
+ return [];
496
+ });
497
+ }
498
+ function optionalString(record, field) {
499
+ const value = record[field];
500
+ return typeof value === "string" && value.length > 0 ? value : void 0;
501
+ }
502
+ function isRecord2(value) {
503
+ return typeof value === "object" && value !== null && !Array.isArray(value);
504
+ }
505
+
506
+ // src/domains/eval/suites/load.ts
507
+ var EvalSuiteFileError = class extends Error {
508
+ issues;
509
+ constructor(issues) {
510
+ super(`eval suite invalid (${issues.length} ${issues.length === 1 ? "issue" : "issues"})`);
511
+ this.name = "EvalSuiteFileError";
512
+ this.issues = [...issues];
513
+ }
514
+ };
515
+ async function loadEvalSuiteFile(path) {
516
+ const resolved = resolve(path);
517
+ const raw = await readFile(resolved, "utf8");
518
+ let parsed;
519
+ try {
520
+ parsed = parseYaml2(raw);
521
+ } catch (error) {
522
+ const message = error instanceof Error ? error.message : String(error);
523
+ throw new EvalSuiteFileError([{ path: "$", message: `invalid YAML: ${message}` }]);
524
+ }
525
+ const result = validateEvalSuiteV2(parsed);
526
+ if (!result.valid) throw new EvalSuiteFileError(result.issues);
527
+ return {
528
+ path: resolved,
529
+ baseDir: dirname(resolved),
530
+ hash: sha256Hex(raw),
531
+ suite: result.suite
532
+ };
533
+ }
534
+ async function loadV1TaskFileAsSuite(path, repeatOverride) {
535
+ const loaded = await loadEvalTaskFile(path);
536
+ const suite = {
537
+ version: 2,
538
+ suite: {
539
+ id: "v1-task-file",
540
+ title: "v1 task file",
541
+ visibility: "local",
542
+ description: "Compatibility adapter for version 1 eval task files.",
543
+ provenance: { taskFileVersion: 1 }
544
+ },
545
+ matrix: {
546
+ targets: [{ id: "local" }],
547
+ repeats: repeatOverride ?? 1
548
+ },
549
+ tasks: loaded.taskFile.tasks.map((task) => ({
550
+ id: task.id,
551
+ tags: task.tags,
552
+ workspace: { kind: "local", path: task.cwd },
553
+ runner: { kind: "external-command", commands: task.setup },
554
+ verify: { commands: task.verifier },
555
+ metrics: { collect: ["latency.wallMs", "verifier.exitCode"] },
556
+ timeoutMs: task.timeoutMs
557
+ }))
558
+ };
559
+ return {
560
+ path: loaded.path,
561
+ baseDir: loaded.baseDir,
562
+ hash: loaded.contentHash,
563
+ suite
564
+ };
565
+ }
566
+ function sha256Hex(content) {
567
+ return createHash("sha256").update(content, "utf8").digest("hex");
568
+ }
569
+
570
+ // src/domains/eval/suites/resolve.ts
571
+ function resolveSuiteForRun(suite, options) {
572
+ const targets = resolveTargets(suite.matrix.targets, options);
573
+ return {
574
+ ...suite,
575
+ matrix: {
576
+ ...suite.matrix,
577
+ targets
578
+ }
579
+ };
580
+ }
581
+ function artifactMatrixIdentity(targets) {
582
+ if (targets.length === 1) {
583
+ const target = targets[0];
584
+ return {
585
+ target: target?.id ?? "unknown",
586
+ model: target?.model ?? null,
587
+ thinking: target?.thinking ?? null
588
+ };
589
+ }
590
+ return { target: "multiple", model: null, thinking: null };
591
+ }
592
+ function resolveTargets(targets, options) {
593
+ const filtered = options.target === void 0 ? [...targets] : targets.filter((target) => target.id === options.target);
594
+ const selected = filtered.length > 0 ? filtered : options.target === void 0 ? [...targets] : [{ id: options.target }];
595
+ return selected.map((target) => ({
596
+ ...target,
597
+ ...options.model === void 0 ? {} : { model: options.model }
598
+ }));
599
+ }
600
+
601
+ // src/domains/eval/suites/run.ts
602
+ import { mkdtemp as mkdtemp3, rm as rm3 } from "node:fs/promises";
603
+ import { tmpdir as tmpdir3 } from "node:os";
604
+ import { resolve as resolve6 } from "node:path";
605
+
606
+ // src/domains/eval/metrics/context.ts
607
+ function collectContextMetrics(cwd) {
608
+ try {
609
+ const codewiki = readCodewiki(cwd);
610
+ if (!codewiki) throw new Error("codewiki unavailable");
611
+ const visiblePaths = new Set(enumerateWorkspaceFiles(cwd));
612
+ const artifactSourceFiles = codewiki.files.filter((file) => file.lang !== "config");
613
+ const indexedFiles = artifactSourceFiles.filter((file) => visiblePaths.has(file.path)).length;
614
+ const staleFiles = artifactSourceFiles.length - indexedFiles;
615
+ const sourceFiles = detectProjectProfile(cwd).sourceFiles;
616
+ const digest = renderCodewikiDigest(codewiki);
617
+ return {
618
+ "context.indexedFiles": indexedFiles,
619
+ "context.artifactFiles": artifactSourceFiles.length,
620
+ "context.staleFiles": staleFiles,
621
+ "context.coverage": sourceFiles === 0 ? artifactSourceFiles.length === 0 ? 1 : 0 : indexedFiles / sourceFiles,
622
+ "context.structuralHash": structuralCodewikiHash(codewiki),
623
+ "context.digestTokens": Math.ceil(digest.length / 4)
624
+ };
625
+ } catch {
626
+ return {
627
+ "context.indexedFiles": 0,
628
+ "context.artifactFiles": 0,
629
+ "context.staleFiles": 0,
630
+ "context.coverage": 0,
631
+ "context.structuralHash": null,
632
+ "context.digestTokens": 0
633
+ };
634
+ }
635
+ }
636
+
637
+ // src/domains/eval/metrics/fleet-loop-stream.ts
638
+ var EMPTY_FLEET_LOOP_OBSERVATION = { summaryCount: 0, loops: [] };
639
+ var DECLARED_LOOP_REASONS = /* @__PURE__ */ new Set([
640
+ "resolved",
641
+ "loop_bound_exhausted",
642
+ "loop_step_failed",
643
+ "loop_not_reached"
644
+ ]);
645
+ function createFleetLoopFold() {
646
+ let summaryCount = 0;
647
+ let loops = [];
648
+ let pending = "";
649
+ const consume = (line) => {
650
+ if (line.trim().length === 0) return;
651
+ let parsed;
652
+ try {
653
+ parsed = JSON.parse(line);
654
+ } catch {
655
+ return;
656
+ }
657
+ if (!isRecord3(parsed) || typeof parsed.fleet !== "string" || !Array.isArray(parsed.loops)) return;
658
+ summaryCount += 1;
659
+ const unneeded = Array.isArray(parsed.unneeded) ? parsed.unneeded.length : 0;
660
+ const skipped = Array.isArray(parsed.skipped) ? parsed.skipped.length : 0;
661
+ loops = parsed.loops.flatMap((entry) => {
662
+ const loop = parseLoop(entry, unneeded, skipped);
663
+ return loop === null ? [] : [loop];
664
+ });
665
+ };
666
+ return {
667
+ push(chunk) {
668
+ pending += chunk;
669
+ for (; ; ) {
670
+ const newline = pending.indexOf("\n");
671
+ if (newline === -1) break;
672
+ consume(pending.slice(0, newline).replace(/\r$/u, ""));
673
+ pending = pending.slice(newline + 1);
674
+ }
675
+ },
676
+ observation() {
677
+ if (pending.length > 0) {
678
+ consume(pending.replace(/\r$/u, ""));
679
+ pending = "";
680
+ }
681
+ return { summaryCount, loops: loops.map((loop) => ({ ...loop })) };
682
+ }
683
+ };
684
+ }
685
+ function addFleetLoopObservations(left, right) {
686
+ const summaryCount = left.summaryCount + right.summaryCount;
687
+ return { summaryCount, loops: summaryCount === 1 ? [...left.loops, ...right.loops] : [] };
688
+ }
689
+ function fleetLoopMetricEntries(observation) {
690
+ if (observation.summaryCount !== 1 || observation.loops.length === 0) return {};
691
+ const loops = observation.loops;
692
+ return {
693
+ "loop.count": loops.length,
694
+ "loop.attemptsSpent": sum(loops.map((loop) => loop.attempts)),
695
+ "loop.repairsSpent": sum(loops.map((loop) => loop.repairs)),
696
+ "loop.resolved": loops.every((loop) => loop.resolved),
697
+ "loop.reasonExhausted": loops.every((loop) => loop.resolved || loop.reason === "loop_bound_exhausted"),
698
+ "loop.reasonDeclared": loops.every(
699
+ (loop) => DECLARED_LOOP_REASONS.has(loop.reason) && loop.resolved === (loop.reason === "resolved")
700
+ ),
701
+ "loop.unneededNodes": loops[0]?.unneeded ?? 0,
702
+ "loop.skippedNodes": loops[0]?.skipped ?? 0
703
+ };
704
+ }
705
+ function fleetLoopReceiptAgreement(loopMetrics, journalMetrics) {
706
+ const repairs = loopMetrics["loop.repairsSpent"];
707
+ const recoveries = journalMetrics["receipt.recoveryCount"];
708
+ if (typeof repairs !== "number" || typeof recoveries !== "number") return {};
709
+ return { "loop.receiptsMatchRepairs": repairs === recoveries };
710
+ }
711
+ function parseLoop(entry, unneeded, skipped) {
712
+ if (!isRecord3(entry)) return null;
713
+ const { attempts, repairs, resolved, reason } = entry;
714
+ if (!isFiniteInteger(attempts) || attempts < 0) return null;
715
+ if (!isFiniteInteger(repairs) || repairs < 0) return null;
716
+ if (typeof resolved !== "boolean" || typeof reason !== "string" || reason.length === 0) return null;
717
+ return { attempts, repairs, resolved, reason, unneeded, skipped };
718
+ }
719
+ function sum(values) {
720
+ return values.reduce((total, value) => total + value, 0);
721
+ }
722
+ function isFiniteInteger(value) {
723
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
724
+ }
725
+ function isRecord3(value) {
726
+ return typeof value === "object" && value !== null && !Array.isArray(value);
727
+ }
728
+
729
+ // src/domains/eval/metrics/invariants.ts
730
+ import { existsSync, readdirSync, readFileSync as readFileSync2 } from "node:fs";
731
+ import { join } from "node:path";
732
+ function readRunJournal(stateDir) {
733
+ if (!existsSync(stateDir)) return null;
734
+ return {
735
+ envelopes: readEnvelopes(join(stateDir, "runs.json")),
736
+ ...readReceipts(join(stateDir, "receipts"))
737
+ };
738
+ }
739
+ function readEnvelopes(runsPath) {
740
+ const envelopes = /* @__PURE__ */ new Map();
741
+ if (!existsSync(runsPath)) return envelopes;
742
+ let parsed;
743
+ try {
744
+ parsed = JSON.parse(readFileSync2(runsPath, "utf8"));
745
+ } catch {
746
+ return envelopes;
747
+ }
748
+ if (!Array.isArray(parsed)) return envelopes;
749
+ for (const entry of parsed) {
750
+ if (isRecord4(entry) && typeof entry.id === "string") envelopes.set(entry.id, entry);
751
+ }
752
+ return envelopes;
753
+ }
754
+ function readReceipts(receiptsDir) {
755
+ if (!existsSync(receiptsDir)) return { receipts: [], receiptFiles: 0 };
756
+ let names;
757
+ try {
758
+ names = readdirSync(receiptsDir).filter((name) => name.endsWith(".json"));
759
+ } catch {
760
+ return { receipts: [], receiptFiles: 0 };
761
+ }
762
+ const receipts = [];
763
+ for (const name of names) {
764
+ try {
765
+ const parsed = JSON.parse(readFileSync2(join(receiptsDir, name), "utf8"));
766
+ if (isReceiptShaped(parsed)) receipts.push(parsed);
767
+ } catch {
768
+ }
769
+ }
770
+ return { receipts, receiptFiles: names.length };
771
+ }
772
+ function receiptInvariantMetrics(journal, processExitCode) {
773
+ if (journal === null) return {};
774
+ const sealed = journal.receiptFiles > 0;
775
+ const roots = journal.receipts.filter(isRootReceipt);
776
+ const base = {
777
+ "receipt.count": journal.receiptFiles,
778
+ "receipt.sealed": sealed,
779
+ "receipt.rootCount": roots.length,
780
+ // Every attempt after the first is `recovery`. A bounded loop's repairs
781
+ // are exactly those attempts, so this is what a loop's attempt accounting
782
+ // is checked against.
783
+ "receipt.recoveryCount": journal.receipts.filter((receipt) => receipt.executionRole === "recovery").length
784
+ };
785
+ if (!sealed) return base;
786
+ return {
787
+ ...base,
788
+ "receipt.integrityValid": integrityValid(journal),
789
+ "receipt.outcomeMatchesExit": outcomeMatchesExit(journal, roots, processExitCode)
790
+ };
791
+ }
792
+ function receiptUsageMetrics(journal) {
793
+ if (journal === null) return {};
794
+ if (journal.receiptFiles === 0) return { "receiptUsage.measured": false };
795
+ if (!integrityValid(journal)) return { "receiptUsage.measured": false };
796
+ let totalTokens = 0;
797
+ let costUsd = 0;
798
+ for (const receipt of journal.receipts) {
799
+ totalTokens += finiteNumber(receipt.tokenCount);
800
+ costUsd += finiteNumber(receipt.costUsd);
801
+ }
802
+ return {
803
+ "receiptUsage.measured": true,
804
+ "receiptUsage.receiptCount": journal.receipts.length,
805
+ "receiptUsage.totalTokens": totalTokens,
806
+ "receiptUsage.costUsd": costUsd
807
+ };
808
+ }
809
+ function finiteNumber(value) {
810
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
811
+ }
812
+ function integrityValid(journal) {
813
+ if (journal.receipts.length !== journal.receiptFiles) return false;
814
+ return journal.receipts.every((receipt) => {
815
+ const envelope = journal.envelopes.get(receipt.runId);
816
+ if (envelope === void 0) return false;
817
+ return verifyReceiptIntegrity(receipt, envelope).ok;
818
+ });
819
+ }
820
+ function outcomeMatchesExit(journal, roots, processExitCode) {
821
+ if (journal.receipts.length !== journal.receiptFiles) return false;
822
+ const selfConsistent = journal.receipts.every(
823
+ (receipt) => receipt.outcome === "succeeded" === (receipt.exitCode === 0)
824
+ );
825
+ if (!selfConsistent) return false;
826
+ const root = roots.length === 1 ? roots[0] : void 0;
827
+ if (root === void 0) return true;
828
+ return root.exitCode === 0 === (processExitCode === 0);
829
+ }
830
+ function isRootReceipt(receipt) {
831
+ return receipt.lineage === void 0 || receipt.lineage.rootRunId === receipt.runId;
832
+ }
833
+ function sessionInvariantMetrics(stateDir) {
834
+ const sessionDirs = findSessionDirs(join(stateDir, "sessions"));
835
+ if (sessionDirs.length === 0) return {};
836
+ let formatVersion = Number.POSITIVE_INFINITY;
837
+ let toolPairsUnmatched = 0;
838
+ let assistantBetweenCallAndResult = 0;
839
+ let compactionSummaryPresent = false;
840
+ let answeredFromPreCompaction = false;
841
+ let turnsAfterCompaction = 0;
842
+ for (const sessionDir of sessionDirs) {
843
+ const transcript = readSessionTranscript(join(sessionDir, "current.jsonl"));
844
+ formatVersion = Math.min(formatVersion, transcript.formatVersion);
845
+ toolPairsUnmatched += transcript.toolPairsUnmatched;
846
+ assistantBetweenCallAndResult += transcript.assistantBetweenCallAndResult;
847
+ compactionSummaryPresent ||= transcript.compactionSummaryPresent;
848
+ answeredFromPreCompaction ||= transcript.answeredFromPreCompaction;
849
+ turnsAfterCompaction += transcript.turnsAfterCompaction;
850
+ }
851
+ return {
852
+ "ledger.formatVersion": formatVersion,
853
+ "ledger.toolPairsUnmatched": toolPairsUnmatched,
854
+ "ledger.assistantBetweenCallAndResult": assistantBetweenCallAndResult,
855
+ "ledger.sessionCount": sessionDirs.length,
856
+ "continuity.compactionSummaryPresent": compactionSummaryPresent,
857
+ "continuity.answeredFromPreCompaction": answeredFromPreCompaction,
858
+ "continuity.turnsAfterCompaction": turnsAfterCompaction
859
+ };
860
+ }
861
+ function findSessionDirs(sessionsRoot) {
862
+ try {
863
+ const dirs = [];
864
+ for (const cwdDir of readdirSync(sessionsRoot, { withFileTypes: true })) {
865
+ if (!cwdDir.isDirectory()) continue;
866
+ const cwdPath = join(sessionsRoot, cwdDir.name);
867
+ try {
868
+ for (const sessionDir of readdirSync(cwdPath, { withFileTypes: true })) {
869
+ if (sessionDir.isDirectory()) dirs.push(join(cwdPath, sessionDir.name));
870
+ }
871
+ } catch {
872
+ }
873
+ }
874
+ return dirs;
875
+ } catch {
876
+ return [];
877
+ }
878
+ }
879
+ function readSessionTranscript(path) {
880
+ let lines;
881
+ try {
882
+ lines = readFileSync2(path, "utf8").split(/\r?\n/u);
883
+ } catch {
884
+ return {
885
+ formatVersion: 0,
886
+ toolPairsUnmatched: 0,
887
+ assistantBetweenCallAndResult: 0,
888
+ compactionSummaryPresent: false,
889
+ answeredFromPreCompaction: false,
890
+ turnsAfterCompaction: 0
891
+ };
892
+ }
893
+ const entries = lines.map(parseJsonRecord);
894
+ const header = entries[0];
895
+ const formatVersion = header?.type === "session" && Number.isInteger(header.version) && header.version > 0 ? header.version : 0;
896
+ const pendingCalls = /* @__PURE__ */ new Map();
897
+ let invalidCalls = 0;
898
+ let orphanResults = 0;
899
+ let assistantGeneration = 0;
900
+ let assistantBetweenCallAndResult = 0;
901
+ const readCalls = /* @__PURE__ */ new Map();
902
+ const completedReads = [];
903
+ let latestCompactionIndex = -1;
904
+ for (const [entryIndex, entry] of entries.entries()) {
905
+ if (isCompactionSummary(entry)) latestCompactionIndex = entryIndex;
906
+ if (entry?.kind !== "message") continue;
907
+ if (entry.role === "assistant") {
908
+ assistantGeneration += 1;
909
+ continue;
910
+ }
911
+ if (entry.role !== "tool_call" && entry.role !== "tool_result") continue;
912
+ const payload = isRecord4(entry.payload) ? entry.payload : void 0;
913
+ const toolCallId = payload?.toolCallId;
914
+ if (entry.role === "tool_call") {
915
+ if (typeof toolCallId !== "string" || toolCallId.length === 0) {
916
+ invalidCalls += 1;
917
+ continue;
918
+ }
919
+ const calls2 = pendingCalls.get(toolCallId) ?? [];
920
+ calls2.push(assistantGeneration);
921
+ pendingCalls.set(toolCallId, calls2);
922
+ if (payload?.name === "read") {
923
+ const args = isRecord4(payload.args) ? payload.args : void 0;
924
+ const readPath2 = args?.path;
925
+ if (typeof readPath2 === "string" && readPath2.trim().length > 0) {
926
+ const paths = readCalls.get(toolCallId) ?? [];
927
+ paths.push(readPath2.trim());
928
+ readCalls.set(toolCallId, paths);
929
+ }
930
+ }
931
+ continue;
932
+ }
933
+ if (typeof toolCallId !== "string" || toolCallId.length === 0) {
934
+ orphanResults += 1;
935
+ continue;
936
+ }
937
+ const calls = pendingCalls.get(toolCallId);
938
+ const callGeneration = calls?.shift();
939
+ if (callGeneration === void 0) {
940
+ orphanResults += 1;
941
+ continue;
942
+ }
943
+ if (calls?.length === 0) pendingCalls.delete(toolCallId);
944
+ if (assistantGeneration > callGeneration) assistantBetweenCallAndResult += 1;
945
+ const readPaths = readCalls.get(toolCallId);
946
+ const readPath = readPaths?.shift();
947
+ if (readPaths?.length === 0) readCalls.delete(toolCallId);
948
+ if (readPath !== void 0 && payload?.isError !== true) completedReads.push({ path: readPath, entryIndex });
949
+ }
950
+ let danglingCalls = invalidCalls;
951
+ for (const calls of pendingCalls.values()) danglingCalls += calls.length;
952
+ const pathsReadBeforeCompaction = new Set(
953
+ completedReads.filter((read) => read.entryIndex < latestCompactionIndex).map((read) => read.path)
954
+ );
955
+ const answeredFromPreCompaction = completedReads.some(
956
+ (read) => read.entryIndex > latestCompactionIndex && pathsReadBeforeCompaction.has(read.path)
957
+ );
958
+ return {
959
+ formatVersion,
960
+ toolPairsUnmatched: danglingCalls + orphanResults,
961
+ assistantBetweenCallAndResult,
962
+ compactionSummaryPresent: latestCompactionIndex >= 0,
963
+ answeredFromPreCompaction: latestCompactionIndex >= 0 && answeredFromPreCompaction,
964
+ turnsAfterCompaction: latestCompactionIndex < 0 ? 0 : entries.slice(latestCompactionIndex + 1).filter((entry) => entry?.kind === "message").length
965
+ };
966
+ }
967
+ function isCompactionSummary(entry) {
968
+ return entry?.kind === "compactionSummary" && typeof entry.summary === "string" && typeof entry.tokensBefore === "number" && Number.isFinite(entry.tokensBefore) && typeof entry.firstKeptTurnId === "string";
969
+ }
970
+ function parseJsonRecord(line) {
971
+ if (line.trim().length === 0) return void 0;
972
+ try {
973
+ const parsed = JSON.parse(line);
974
+ return isRecord4(parsed) ? parsed : void 0;
975
+ } catch {
976
+ return void 0;
977
+ }
978
+ }
979
+ function processInvariantMetrics(journal) {
980
+ if (journal === null) return {};
981
+ const attested = journal.receipts.flatMap(
982
+ (receipt) => receipt.attestation === void 0 ? [] : [receipt.attestation]
983
+ );
984
+ if (attested.length === 0) return {};
985
+ const orphaned = attested.filter((attestation) => {
986
+ if (isProcessAlive(attestation.pid)) return true;
987
+ const group = attestation.processGroupId;
988
+ return group !== null && group === attestation.pid && isProcessAlive(-group);
989
+ });
990
+ return { "process.attestedWorkers": attested.length, "process.orphanedChildren": orphaned.length };
991
+ }
992
+ function isProcessAlive(pid) {
993
+ if (!Number.isInteger(pid) || pid === 0) return false;
994
+ try {
995
+ process.kill(pid, 0);
996
+ return true;
997
+ } catch (error) {
998
+ return error.code !== "ESRCH";
999
+ }
1000
+ }
1001
+ function writeBoundaryInvariantMetrics(stateDir) {
1002
+ const files = findWriteBoundaryVerdicts(join(stateDir, "write-boundaries"));
1003
+ if (files.length === 0) return {};
1004
+ let sealed = true;
1005
+ let violationsDetected = 0;
1006
+ let violationsRolledBack = 0;
1007
+ let rollbackIncomplete = 0;
1008
+ for (const file of files) {
1009
+ const verdict = parseJsonFile(file);
1010
+ if (verdict === void 0) {
1011
+ sealed = false;
1012
+ continue;
1013
+ }
1014
+ if (!isNonEmptyString(verdict.digest) || !isNonEmptyString(verdict.baselineHead)) sealed = false;
1015
+ if (verdict.reason === WRITES_BOUNDARY_VIOLATION) violationsDetected += 1;
1016
+ if (verdict.status === "rolled-back") violationsRolledBack += 1;
1017
+ if (verdict.status === "rollback-incomplete") rollbackIncomplete += 1;
1018
+ }
1019
+ return {
1020
+ "boundary.verdictCount": files.length,
1021
+ "boundary.verdictSealed": sealed,
1022
+ "boundary.violationsDetected": violationsDetected,
1023
+ "boundary.violationsRolledBack": violationsRolledBack,
1024
+ "boundary.rollbackIncomplete": rollbackIncomplete
1025
+ };
1026
+ }
1027
+ var WRITES_BOUNDARY_VIOLATION = "writes_boundary_violation";
1028
+ function findWriteBoundaryVerdicts(root) {
1029
+ const files = [];
1030
+ try {
1031
+ for (const rootDir of readdirSync(root, { withFileTypes: true })) {
1032
+ if (!rootDir.isDirectory()) continue;
1033
+ const dir = join(root, rootDir.name);
1034
+ try {
1035
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1036
+ if (entry.isFile() && entry.name.endsWith(".json")) files.push(join(dir, entry.name));
1037
+ }
1038
+ } catch {
1039
+ }
1040
+ }
1041
+ } catch {
1042
+ return [];
1043
+ }
1044
+ return files;
1045
+ }
1046
+ function parseJsonFile(path) {
1047
+ try {
1048
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1049
+ return isRecord4(parsed) ? parsed : void 0;
1050
+ } catch {
1051
+ return void 0;
1052
+ }
1053
+ }
1054
+ function isNonEmptyString(value) {
1055
+ return typeof value === "string" && value.length > 0;
1056
+ }
1057
+ var EMPTY_STREAM_INVARIANTS = {
1058
+ messageUpdateCount: 0,
1059
+ cumulativeSnapshots: 0,
1060
+ usageMessages: 0,
1061
+ repeatedResponses: 0,
1062
+ identifiedResponses: 0,
1063
+ messageTokenTotal: 0,
1064
+ segmentTokenTotal: 0,
1065
+ measuredSegments: 0
1066
+ };
1067
+ function addStreamInvariants(left, right) {
1068
+ return {
1069
+ messageUpdateCount: left.messageUpdateCount + right.messageUpdateCount,
1070
+ cumulativeSnapshots: left.cumulativeSnapshots + right.cumulativeSnapshots,
1071
+ usageMessages: left.usageMessages + right.usageMessages,
1072
+ repeatedResponses: left.repeatedResponses + right.repeatedResponses,
1073
+ identifiedResponses: left.identifiedResponses + right.identifiedResponses,
1074
+ messageTokenTotal: left.messageTokenTotal + right.messageTokenTotal,
1075
+ segmentTokenTotal: left.segmentTokenTotal + right.segmentTokenTotal,
1076
+ measuredSegments: left.measuredSegments + right.measuredSegments
1077
+ };
1078
+ }
1079
+ function createStreamInvariantFold() {
1080
+ const state = {
1081
+ messageUpdateCount: 0,
1082
+ cumulativeSnapshots: 0,
1083
+ usageMessages: 0,
1084
+ repeatedResponses: 0,
1085
+ identifiedResponses: 0,
1086
+ messageTokenTotal: 0,
1087
+ segmentTokenTotal: 0,
1088
+ measuredSegments: 0
1089
+ };
1090
+ const seenResponses = /* @__PURE__ */ new Set();
1091
+ let pending = "";
1092
+ const consume = (line) => {
1093
+ if (line.trim().length === 0) return;
1094
+ let event;
1095
+ try {
1096
+ event = JSON.parse(line);
1097
+ } catch {
1098
+ return;
1099
+ }
1100
+ if (!isRecord4(event)) return;
1101
+ if (event.type === "message_update") {
1102
+ state.messageUpdateCount += 1;
1103
+ const assistantEvent = isRecord4(event.assistantMessageEvent) ? event.assistantMessageEvent : void 0;
1104
+ if (event.message !== void 0 || assistantEvent?.partial !== void 0) state.cumulativeSnapshots += 1;
1105
+ return;
1106
+ }
1107
+ if (event.type === "agent_end") {
1108
+ if (Array.isArray(event.messages)) state.cumulativeSnapshots += 1;
1109
+ const usage2 = isRecord4(event.usage) ? event.usage : void 0;
1110
+ if (usage2 === void 0 || usage2.measured !== true) return;
1111
+ state.measuredSegments += 1;
1112
+ state.segmentTokenTotal += numberField(usage2, "totalTokens");
1113
+ return;
1114
+ }
1115
+ if (event.type !== "message_end") return;
1116
+ const message = isRecord4(event.message) ? event.message : void 0;
1117
+ if (message === void 0 || message.role !== "assistant") return;
1118
+ const usage = isRecord4(message.usage) ? message.usage : void 0;
1119
+ if (usage === void 0) return;
1120
+ state.usageMessages += 1;
1121
+ const input = numberField(usage, "input");
1122
+ const output = numberField(usage, "output");
1123
+ const cacheRead = numberField(usage, "cacheRead");
1124
+ const cacheWrite = numberField(usage, "cacheWrite");
1125
+ const totalTokens = numberField(usage, "totalTokens");
1126
+ state.messageTokenTotal += totalTokens > 0 ? totalTokens : input + output + cacheRead + cacheWrite;
1127
+ const responseId = message.responseId;
1128
+ if (typeof responseId !== "string" || responseId.length === 0) return;
1129
+ state.identifiedResponses += 1;
1130
+ if (seenResponses.has(responseId)) state.repeatedResponses += 1;
1131
+ seenResponses.add(responseId);
1132
+ };
1133
+ return {
1134
+ push(chunk) {
1135
+ pending += chunk;
1136
+ for (; ; ) {
1137
+ const newline = pending.indexOf("\n");
1138
+ if (newline === -1) break;
1139
+ consume(pending.slice(0, newline).replace(/\r$/u, ""));
1140
+ pending = pending.slice(newline + 1);
1141
+ }
1142
+ },
1143
+ invariants() {
1144
+ if (pending.length > 0) {
1145
+ consume(pending.replace(/\r$/u, ""));
1146
+ pending = "";
1147
+ }
1148
+ return { ...state };
1149
+ }
1150
+ };
1151
+ }
1152
+ function streamInvariantMetrics(stream) {
1153
+ return {
1154
+ "stream.messageUpdateCount": stream.messageUpdateCount,
1155
+ "stream.cumulativeSnapshots": stream.cumulativeSnapshots,
1156
+ ...stream.identifiedResponses === 0 ? {} : { "stream.usageDoubleCounted": stream.repeatedResponses > 0 },
1157
+ ...stream.measuredSegments === 0 ? {} : { "stream.segmentUsageMatchesMessages": stream.segmentTokenTotal === stream.messageTokenTotal }
1158
+ };
1159
+ }
1160
+ function numberField(record, field) {
1161
+ const value = record[field];
1162
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1163
+ }
1164
+ function isReceiptShaped(value) {
1165
+ if (!isRecord4(value)) return false;
1166
+ return typeof value.runId === "string" && typeof value.agentId === "string" && typeof value.exitCode === "number" && typeof value.outcome === "string" && isRecord4(value.integrity);
1167
+ }
1168
+ function isRecord4(value) {
1169
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1170
+ }
1171
+
1172
+ // src/domains/eval/metrics/latency.ts
1173
+ function wallTimeMetric(metrics) {
1174
+ const value = metrics["latency.wallMs"];
1175
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1176
+ }
1177
+
1178
+ // src/domains/eval/schema/artifact.ts
1179
+ var ZERO_TOKEN_METRICS_V4 = {
1180
+ input: 0,
1181
+ output: 0,
1182
+ total: 0,
1183
+ cacheRead: 0,
1184
+ cacheWrite: 0
1185
+ };
1186
+
1187
+ // src/domains/eval/metrics/coverage.ts
1188
+ function tokenMeasurementCoverage(results) {
1189
+ let measured = 0;
1190
+ for (const result of results) {
1191
+ if (result.metrics["tokens.measured"] === true) measured += 1;
1192
+ }
1193
+ return { total: results.length, measured };
1194
+ }
1195
+
1196
+ // src/domains/eval/metrics/tokens.ts
1197
+ function tokenAccountingFrom(results) {
1198
+ const coverage = tokenMeasurementCoverage(results);
1199
+ if (coverage.measured === 0) return { measured: false, runs: coverage.total, measuredRuns: 0 };
1200
+ const totals = results.reduce(
1201
+ (total, result) => result.metrics["tokens.measured"] === true ? addTokenMetrics(total, tokenMetricsFrom(result.metrics)) : total,
1202
+ zeroTokenMetrics()
1203
+ );
1204
+ return { measured: true, runs: coverage.total, measuredRuns: coverage.measured, ...totals };
1205
+ }
1206
+ function tokenMetricsFrom(metrics) {
1207
+ return {
1208
+ input: numberMetric(metrics, "tokens.input"),
1209
+ output: numberMetric(metrics, "tokens.output"),
1210
+ total: numberMetric(metrics, "tokens.total"),
1211
+ cacheRead: numberMetric(metrics, "tokens.cacheRead"),
1212
+ cacheWrite: numberMetric(metrics, "tokens.cacheWrite")
1213
+ };
1214
+ }
1215
+ function addTokenMetrics(left, right) {
1216
+ return {
1217
+ input: left.input + right.input,
1218
+ output: left.output + right.output,
1219
+ total: left.total + right.total,
1220
+ cacheRead: left.cacheRead + right.cacheRead,
1221
+ cacheWrite: left.cacheWrite + right.cacheWrite
1222
+ };
1223
+ }
1224
+ function zeroTokenMetrics() {
1225
+ return { ...ZERO_TOKEN_METRICS_V4 };
1226
+ }
1227
+ function numberMetric(metrics, key) {
1228
+ const value = metrics[key];
1229
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1230
+ }
1231
+
1232
+ // src/domains/eval/metrics/tool-calls.ts
1233
+ function zeroToolCallMetrics() {
1234
+ return {
1235
+ "tools.totalCalls": 0,
1236
+ "tools.failed": 0,
1237
+ "tools.blocked": 0
1238
+ };
1239
+ }
1240
+
1241
+ // src/domains/eval/metrics/evidence.ts
1242
+ function receiptFromRunJsonStdout(stdout) {
1243
+ let searchEnd = stdout.length;
1244
+ while (searchEnd > 0) {
1245
+ const start = stdout.lastIndexOf("\n{", searchEnd - 1);
1246
+ if (start === -1) break;
1247
+ const candidate = stdout.slice(start + 1);
1248
+ try {
1249
+ const parsed = JSON.parse(candidate);
1250
+ if (isReceiptShaped2(parsed)) return parsed;
1251
+ } catch {
1252
+ }
1253
+ if (start === 0) break;
1254
+ searchEnd = start;
1255
+ }
1256
+ return null;
1257
+ }
1258
+ function isReceiptShaped2(value) {
1259
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1260
+ const record = value;
1261
+ return typeof record.runId === "string" && typeof record.agentId === "string" && typeof record.exitCode === "number" && typeof record.integrity === "object" && record.integrity !== null;
1262
+ }
1263
+ function evidenceMetricsFromReceipt(receipt) {
1264
+ return {
1265
+ "evidence.verification": receipt.verification.state,
1266
+ ...receipt.findingsSummary === void 0 ? {} : { "evidence.firstPassSuccess": receipt.findingsSummary.firstPassSuccess === true },
1267
+ "evidence.quality.typedValidationCount": receipt.quality.typedValidations.length,
1268
+ "evidence.responseSchema.digest": receipt.quality.responseSchema.schemaDigest ?? "none",
1269
+ ...typeof receipt.costUsd === "number" && Number.isFinite(receipt.costUsd) ? { "cost.usd": receipt.costUsd } : {}
1270
+ };
1271
+ }
1272
+ function dispatchCountFromJsonl(stdout) {
1273
+ let executionEnds = 0;
1274
+ let canonicalFinishes = 0;
1275
+ const seenExecutionEnds = /* @__PURE__ */ new Set();
1276
+ const seenCanonicalFinishes = /* @__PURE__ */ new Set();
1277
+ let sawCanonicalFinishEvent = false;
1278
+ for (const event of jsonlEvents(stdout)) {
1279
+ if (event.type === "tool_execution_end") {
1280
+ if (stringField(event, "toolName") !== "dispatch") continue;
1281
+ const callId2 = stringField(event, "toolCallId");
1282
+ if (callId2 !== void 0) {
1283
+ if (seenExecutionEnds.has(callId2)) continue;
1284
+ seenExecutionEnds.add(callId2);
1285
+ }
1286
+ executionEnds += 1;
1287
+ continue;
1288
+ }
1289
+ if (event.type !== "clio_tool_finish" || !isRecord5(event.payload)) continue;
1290
+ sawCanonicalFinishEvent = true;
1291
+ if (stringField(event.payload, "tool") !== "dispatch") continue;
1292
+ const callId = stringField(event.payload, "toolCallId") ?? stringField(event, "toolCallId");
1293
+ if (callId !== void 0) {
1294
+ if (seenCanonicalFinishes.has(callId)) continue;
1295
+ seenCanonicalFinishes.add(callId);
1296
+ }
1297
+ canonicalFinishes += 1;
1298
+ }
1299
+ return sawCanonicalFinishEvent ? canonicalFinishes : executionEnds;
1300
+ }
1301
+ function scoutDispatchCountFromJsonl(stdout) {
1302
+ const seen = /* @__PURE__ */ new Set();
1303
+ let count = 0;
1304
+ for (const event of jsonlEvents(stdout)) {
1305
+ if (event.type !== "tool_execution_start" || stringField(event, "toolName") !== "dispatch") continue;
1306
+ if (!isRecord5(event.args) || !dispatchArgsIncludeAgent(event.args, "scout")) continue;
1307
+ const callId = stringField(event, "toolCallId");
1308
+ if (callId !== void 0) {
1309
+ if (seen.has(callId)) continue;
1310
+ seen.add(callId);
1311
+ }
1312
+ count += 1;
1313
+ }
1314
+ return count;
1315
+ }
1316
+ function dispatchArgsIncludeAgent(args, expectedAgent) {
1317
+ const sharedAgent = stringField(args, "agent") ?? stringField(args, "agent_id") ?? stringField(args, "agentId");
1318
+ let tasks = args.tasks;
1319
+ if (typeof tasks === "string") {
1320
+ const trimmed = tasks.trim();
1321
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
1322
+ try {
1323
+ tasks = JSON.parse(trimmed);
1324
+ } catch {
1325
+ }
1326
+ }
1327
+ }
1328
+ const taskList = Array.isArray(tasks) ? tasks : tasks === void 0 ? [{ task: args.task }] : [tasks];
1329
+ return taskList.some((task) => {
1330
+ if (typeof task === "string") return sharedAgent?.toLowerCase() === expectedAgent;
1331
+ if (!isRecord5(task)) return false;
1332
+ const agent = stringField(task, "agent") ?? stringField(task, "agent_id") ?? stringField(task, "agentId") ?? sharedAgent;
1333
+ return agent?.toLowerCase() === expectedAgent;
1334
+ });
1335
+ }
1336
+ function wikiStaleAcknowledgedFromJsonl(stdout) {
1337
+ let sawWikiLookup = false;
1338
+ for (const event of jsonlEvents(stdout)) {
1339
+ if (event.type !== "tool_execution_start") continue;
1340
+ const toolName = stringField(event, "toolName");
1341
+ if (toolName === "code_nav" && isRecord5(event.args) && event.args.mode === "wiki") {
1342
+ sawWikiLookup = true;
1343
+ continue;
1344
+ }
1345
+ if (sawWikiLookup && (toolName === "read" || toolName === "grep")) return true;
1346
+ }
1347
+ return false;
1348
+ }
1349
+ function* jsonlEvents(stdout) {
1350
+ for (const line of stdout.split(/\r?\n/)) {
1351
+ if (line.trim().length === 0) continue;
1352
+ try {
1353
+ const parsed = JSON.parse(line);
1354
+ if (isRecord5(parsed)) yield parsed;
1355
+ } catch {
1356
+ }
1357
+ }
1358
+ }
1359
+ function stringField(record, field) {
1360
+ const value = record[field];
1361
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1362
+ }
1363
+ function isRecord5(value) {
1364
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1365
+ }
1366
+
1367
+ // src/domains/eval/metrics/token-stream.ts
1368
+ var UNMEASURED_TOKEN_USAGE = {
1369
+ measured: false,
1370
+ tokens: { input: 0, output: 0, total: 0, cacheRead: 0, cacheWrite: 0 },
1371
+ costUsd: 0
1372
+ };
1373
+ function createTokenUsageFold() {
1374
+ const tokens = { input: 0, output: 0, total: 0, cacheRead: 0, cacheWrite: 0 };
1375
+ let costUsd = 0;
1376
+ let measured = false;
1377
+ let pending = "";
1378
+ const consume = (line) => {
1379
+ if (line.trim().length === 0) return;
1380
+ let event;
1381
+ try {
1382
+ event = JSON.parse(line);
1383
+ } catch {
1384
+ return;
1385
+ }
1386
+ if (!isRecord6(event) || event.type !== "message_end") return;
1387
+ const message = isRecord6(event.message) ? event.message : void 0;
1388
+ if (message === void 0 || message.role !== "assistant") return;
1389
+ const usage = isRecord6(message.usage) ? message.usage : void 0;
1390
+ if (usage === void 0) return;
1391
+ measured = true;
1392
+ const input = numberField2(usage, "input");
1393
+ const output = numberField2(usage, "output");
1394
+ const cacheRead = numberField2(usage, "cacheRead");
1395
+ const cacheWrite = numberField2(usage, "cacheWrite");
1396
+ const totalTokens = numberField2(usage, "totalTokens");
1397
+ tokens.input += input;
1398
+ tokens.output += output;
1399
+ tokens.cacheRead += cacheRead;
1400
+ tokens.cacheWrite += cacheWrite;
1401
+ tokens.total += totalTokens > 0 ? totalTokens : input + output + cacheRead + cacheWrite;
1402
+ if (isRecord6(usage.cost)) costUsd += numberField2(usage.cost, "total");
1403
+ };
1404
+ return {
1405
+ push(chunk) {
1406
+ pending += chunk;
1407
+ for (; ; ) {
1408
+ const newline = pending.indexOf("\n");
1409
+ if (newline === -1) break;
1410
+ consume(pending.slice(0, newline).replace(/\r$/u, ""));
1411
+ pending = pending.slice(newline + 1);
1412
+ }
1413
+ },
1414
+ usage() {
1415
+ if (pending.length > 0) {
1416
+ consume(pending.replace(/\r$/u, ""));
1417
+ pending = "";
1418
+ }
1419
+ return { measured, tokens: { ...tokens }, costUsd };
1420
+ }
1421
+ };
1422
+ }
1423
+ function addTokenStreamUsage(left, right) {
1424
+ return {
1425
+ measured: left.measured || right.measured,
1426
+ tokens: {
1427
+ input: left.tokens.input + right.tokens.input,
1428
+ output: left.tokens.output + right.tokens.output,
1429
+ total: left.tokens.total + right.tokens.total,
1430
+ cacheRead: left.tokens.cacheRead + right.tokens.cacheRead,
1431
+ cacheWrite: left.tokens.cacheWrite + right.tokens.cacheWrite
1432
+ },
1433
+ costUsd: left.costUsd + right.costUsd
1434
+ };
1435
+ }
1436
+ function tokenMetricEntries(usage) {
1437
+ if (!usage.measured) return { "tokens.measured": false };
1438
+ return {
1439
+ "tokens.measured": true,
1440
+ "tokens.input": usage.tokens.input,
1441
+ "tokens.output": usage.tokens.output,
1442
+ "tokens.total": usage.tokens.total,
1443
+ "tokens.cacheRead": usage.tokens.cacheRead,
1444
+ "tokens.cacheWrite": usage.tokens.cacheWrite,
1445
+ ...usage.costUsd > 0 ? { "cost.usd": usage.costUsd } : {}
1446
+ };
1447
+ }
1448
+ function numberField2(record, field) {
1449
+ const value = record[field];
1450
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1451
+ }
1452
+ function isRecord6(value) {
1453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1454
+ }
1455
+
1456
+ // src/domains/eval/runners/external-command.ts
1457
+ import { spawn } from "node:child_process";
1458
+
1459
+ // src/domains/eval/metrics/chaos-stream.ts
1460
+ var EMPTY_CHAOS_OBSERVATION = { markerCount: 0, marker: null };
1461
+ function createChaosFold() {
1462
+ let markerCount = 0;
1463
+ let marker = null;
1464
+ let pending = "";
1465
+ const consume = (line) => {
1466
+ if (line.trim().length === 0) return;
1467
+ let parsed;
1468
+ try {
1469
+ parsed = JSON.parse(line);
1470
+ } catch {
1471
+ return;
1472
+ }
1473
+ if (!isRecord7(parsed) || parsed.type !== "clio_soak_chaos") return;
1474
+ markerCount += 1;
1475
+ marker = parseChaosMarker(parsed);
1476
+ };
1477
+ return {
1478
+ push(chunk) {
1479
+ pending += chunk;
1480
+ for (; ; ) {
1481
+ const newline = pending.indexOf("\n");
1482
+ if (newline === -1) break;
1483
+ consume(pending.slice(0, newline).replace(/\r$/u, ""));
1484
+ pending = pending.slice(newline + 1);
1485
+ }
1486
+ },
1487
+ observation() {
1488
+ if (pending.length > 0) {
1489
+ consume(pending.replace(/\r$/u, ""));
1490
+ pending = "";
1491
+ }
1492
+ return { markerCount, marker: marker === null ? null : { ...marker } };
1493
+ }
1494
+ };
1495
+ }
1496
+ function addChaosObservations(left, right) {
1497
+ const markerCount = left.markerCount + right.markerCount;
1498
+ return {
1499
+ markerCount,
1500
+ marker: markerCount === 1 ? left.marker ?? right.marker : null
1501
+ };
1502
+ }
1503
+ function chaosMetricEntries(observation) {
1504
+ if (observation.markerCount !== 1 || observation.marker === null) return {};
1505
+ return {
1506
+ "chaos.seed": observation.marker.seed,
1507
+ "chaos.faultInjected": observation.marker.faultInjected,
1508
+ "chaos.exitCode": observation.marker.exitCode,
1509
+ "process.orphanedChildren": observation.marker.orphanedChildren
1510
+ };
1511
+ }
1512
+ function parseChaosMarker(event) {
1513
+ if (event.type !== "clio_soak_chaos") return null;
1514
+ const { seed, faultInjected, exitCode, orphanedChildren } = event;
1515
+ if (!isFiniteInteger2(seed) || typeof faultInjected !== "boolean" || !isFiniteInteger2(exitCode)) return null;
1516
+ if (!isFiniteInteger2(orphanedChildren) || orphanedChildren < 0) return null;
1517
+ return { seed, faultInjected, exitCode, orphanedChildren };
1518
+ }
1519
+ function isFiniteInteger2(value) {
1520
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
1521
+ }
1522
+ function isRecord7(value) {
1523
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1524
+ }
1525
+
1526
+ // src/domains/eval/runners/external-command.ts
1527
+ var OUTPUT_LIMIT = 2e5;
1528
+ var OUTPUT_HEAD_LIMIT = 2e4;
1529
+ var OUTPUT_TRUNCATION_MARKER = "\n[output middle truncated; tail preserved]\n";
1530
+ var METRIC_JSONL_LIMIT = 256e3;
1531
+ var METRIC_JSONL_LINE_LIMIT = 64e3;
1532
+ async function runExternalCommandRunner(runner, cwd, timeoutMs, env) {
1533
+ const runnerCommands = runner.commands ?? [];
1534
+ const commands = runnerCommands.length > 0 ? runnerCommands : runner.command === void 0 ? [] : [runner.command];
1535
+ let stdout = "";
1536
+ let stderr = "";
1537
+ let wallTimeMs = 0;
1538
+ let usage = UNMEASURED_TOKEN_USAGE;
1539
+ let streamInvariants = EMPTY_STREAM_INVARIANTS;
1540
+ let chaos = EMPTY_CHAOS_OBSERVATION;
1541
+ let fleetLoops = EMPTY_FLEET_LOOP_OBSERVATION;
1542
+ for (const command of commands) {
1543
+ const result = await runShellCommand(command, cwd, runner.timeoutMs ?? timeoutMs, env);
1544
+ stdout = appendLimited(stdout, result.stdout);
1545
+ stderr = appendLimited(stderr, result.stderr);
1546
+ wallTimeMs += result.wallTimeMs;
1547
+ usage = addTokenStreamUsage(usage, result.usage);
1548
+ streamInvariants = addStreamInvariants(streamInvariants, result.streamInvariants);
1549
+ chaos = addChaosObservations(chaos, result.chaos);
1550
+ fleetLoops = addFleetLoopObservations(fleetLoops, result.fleetLoops);
1551
+ if (result.exitCode !== 0) {
1552
+ return {
1553
+ assignmentId: null,
1554
+ terminalReceiptDigest: null,
1555
+ exitCode: result.exitCode,
1556
+ stdout,
1557
+ stderr,
1558
+ wallTimeMs,
1559
+ metrics: {
1560
+ "latency.wallMs": wallTimeMs,
1561
+ ...tokenMetricEntries(usage),
1562
+ ...streamInvariantMetrics(streamInvariants),
1563
+ ...chaosMetricEntries(chaos),
1564
+ ...fleetLoopMetricEntries(fleetLoops),
1565
+ "verifier.exitCode": result.exitCode
1566
+ },
1567
+ artifacts: chaosArtifacts(chaos)
1568
+ };
1569
+ }
1570
+ }
1571
+ return {
1572
+ assignmentId: null,
1573
+ terminalReceiptDigest: null,
1574
+ exitCode: 0,
1575
+ stdout,
1576
+ stderr,
1577
+ wallTimeMs,
1578
+ metrics: {
1579
+ "latency.wallMs": wallTimeMs,
1580
+ ...tokenMetricEntries(usage),
1581
+ ...streamInvariantMetrics(streamInvariants),
1582
+ ...chaosMetricEntries(chaos),
1583
+ ...fleetLoopMetricEntries(fleetLoops),
1584
+ "verifier.exitCode": 0
1585
+ },
1586
+ artifacts: chaosArtifacts(chaos)
1587
+ };
1588
+ }
1589
+ function runShellCommand(command, cwd, timeoutMs, env) {
1590
+ const started = Date.now();
1591
+ return new Promise((resolve8) => {
1592
+ let stdout = "";
1593
+ let stderr = "";
1594
+ const metricCapture = createJsonlMetricCapture();
1595
+ const usageFold = createTokenUsageFold();
1596
+ const streamFold = createStreamInvariantFold();
1597
+ const chaosFold = createChaosFold();
1598
+ const fleetLoopFold = createFleetLoopFold();
1599
+ let timedOut = false;
1600
+ let settled = false;
1601
+ const child = spawn(command, {
1602
+ cwd,
1603
+ shell: true,
1604
+ stdio: ["ignore", "pipe", "pipe"],
1605
+ // The overlay is additive: an eval item pins where Clio writes its
1606
+ // journal, and inherits everything else the operator's shell provides.
1607
+ env: env === void 0 ? process.env : { ...process.env, ...env }
1608
+ });
1609
+ child.stdout.setEncoding("utf8");
1610
+ child.stderr.setEncoding("utf8");
1611
+ child.stdout.on("data", (chunk) => {
1612
+ stdout = appendLimited(stdout, chunk);
1613
+ metricCapture.push(chunk);
1614
+ usageFold.push(chunk);
1615
+ streamFold.push(chunk);
1616
+ chaosFold.push(chunk);
1617
+ fleetLoopFold.push(chunk);
1618
+ });
1619
+ child.stderr.on("data", (chunk) => {
1620
+ stderr = appendLimited(stderr, chunk);
1621
+ });
1622
+ const timer = setTimeout(() => {
1623
+ timedOut = true;
1624
+ child.kill("SIGTERM");
1625
+ setTimeout(() => child.kill("SIGKILL"), 1e3);
1626
+ }, timeoutMs);
1627
+ const finish = (exitCode) => {
1628
+ if (settled) return;
1629
+ settled = true;
1630
+ clearTimeout(timer);
1631
+ resolve8({
1632
+ command,
1633
+ exitCode,
1634
+ stdout,
1635
+ metricJsonl: metricCapture.finish(),
1636
+ usage: usageFold.usage(),
1637
+ streamInvariants: streamFold.invariants(),
1638
+ chaos: chaosFold.observation(),
1639
+ fleetLoops: fleetLoopFold.observation(),
1640
+ stderr,
1641
+ wallTimeMs: Math.max(0, Date.now() - started),
1642
+ timedOut
1643
+ });
1644
+ };
1645
+ child.on("error", (error) => {
1646
+ stderr = appendLimited(stderr, error.message);
1647
+ finish(1);
1648
+ });
1649
+ child.on("close", (code) => {
1650
+ finish(typeof code === "number" ? code : timedOut ? 124 : 1);
1651
+ });
1652
+ });
1653
+ }
1654
+ function appendLimited(current, chunk) {
1655
+ const next = `${current}${chunk}`;
1656
+ if (next.length <= OUTPUT_LIMIT) return next;
1657
+ const tailLimit = OUTPUT_LIMIT - OUTPUT_HEAD_LIMIT - OUTPUT_TRUNCATION_MARKER.length;
1658
+ return `${next.slice(0, OUTPUT_HEAD_LIMIT)}${OUTPUT_TRUNCATION_MARKER}${next.slice(-tailLimit)}`;
1659
+ }
1660
+ function createJsonlMetricCapture() {
1661
+ const lines = [];
1662
+ let storedBytes = 0;
1663
+ let pending = "";
1664
+ let discardingOversizedLine = false;
1665
+ let finished = false;
1666
+ const store = (line) => {
1667
+ let parsed;
1668
+ try {
1669
+ parsed = JSON.parse(line);
1670
+ } catch {
1671
+ return;
1672
+ }
1673
+ if (!isRecord8(parsed)) return;
1674
+ const compact = compactMetricEvent(parsed);
1675
+ if (compact === null) return;
1676
+ const encoded = JSON.stringify(compact);
1677
+ if (encoded.length > METRIC_JSONL_LINE_LIMIT) return;
1678
+ lines.push(encoded);
1679
+ storedBytes += encoded.length + 1;
1680
+ while (storedBytes > METRIC_JSONL_LIMIT && lines.length > 1) {
1681
+ const removed = lines.shift();
1682
+ if (removed !== void 0) storedBytes -= removed.length + 1;
1683
+ }
1684
+ };
1685
+ const push = (chunk) => {
1686
+ if (finished || chunk.length === 0) return;
1687
+ let remaining = chunk;
1688
+ if (discardingOversizedLine) {
1689
+ const newline = remaining.indexOf("\n");
1690
+ if (newline === -1) return;
1691
+ remaining = remaining.slice(newline + 1);
1692
+ discardingOversizedLine = false;
1693
+ }
1694
+ pending += remaining;
1695
+ for (; ; ) {
1696
+ const newline = pending.indexOf("\n");
1697
+ if (newline === -1) break;
1698
+ store(pending.slice(0, newline).replace(/\r$/u, ""));
1699
+ pending = pending.slice(newline + 1);
1700
+ }
1701
+ if (pending.length > METRIC_JSONL_LINE_LIMIT) {
1702
+ pending = "";
1703
+ discardingOversizedLine = true;
1704
+ }
1705
+ };
1706
+ return {
1707
+ push,
1708
+ finish() {
1709
+ if (!finished && !discardingOversizedLine && pending.length > 0) store(pending.replace(/\r$/u, ""));
1710
+ finished = true;
1711
+ pending = "";
1712
+ return lines.join("\n");
1713
+ }
1714
+ };
1715
+ }
1716
+ function compactMetricEvent(event) {
1717
+ const type = event.type;
1718
+ if (type === "clio_soak_chaos") {
1719
+ const marker = parseChaosMarker(event);
1720
+ return marker === null ? null : { type, ...marker };
1721
+ }
1722
+ if (type === "tool_execution_start") {
1723
+ const toolName = stringField2(event, "toolName");
1724
+ if (toolName !== "dispatch" && toolName !== "code_nav" && toolName !== "read" && toolName !== "grep") {
1725
+ return null;
1726
+ }
1727
+ return {
1728
+ type,
1729
+ ...stringField2(event, "toolCallId") !== void 0 ? { toolCallId: stringField2(event, "toolCallId") } : {},
1730
+ toolName,
1731
+ ...toolName === "dispatch" && isRecord8(event.args) ? { args: event.args } : toolName === "code_nav" && isRecord8(event.args) ? { args: { mode: event.args.mode } } : {}
1732
+ };
1733
+ }
1734
+ if (type === "tool_execution_end") {
1735
+ return {
1736
+ type,
1737
+ ...stringField2(event, "toolCallId") !== void 0 ? { toolCallId: stringField2(event, "toolCallId") } : {},
1738
+ ...stringField2(event, "toolName") !== void 0 ? { toolName: stringField2(event, "toolName") } : {},
1739
+ ...event.isError === true ? { isError: true } : {},
1740
+ ...stringField2(event, "outcome") !== void 0 ? { outcome: stringField2(event, "outcome") } : {}
1741
+ };
1742
+ }
1743
+ if (type !== "clio_tool_finish" || !isRecord8(event.payload)) return null;
1744
+ return {
1745
+ type,
1746
+ payload: {
1747
+ ...stringField2(event.payload, "tool") !== void 0 ? { tool: stringField2(event.payload, "tool") } : {},
1748
+ ...stringField2(event.payload, "toolCallId") !== void 0 ? { toolCallId: stringField2(event.payload, "toolCallId") } : stringField2(event, "toolCallId") !== void 0 ? { toolCallId: stringField2(event, "toolCallId") } : {},
1749
+ ...stringField2(event.payload, "outcome") !== void 0 ? { outcome: stringField2(event.payload, "outcome") } : {}
1750
+ }
1751
+ };
1752
+ }
1753
+ function chaosArtifacts(observation) {
1754
+ return observation.markerCount === 1 && observation.marker !== null ? { chaosSeed: String(observation.marker.seed) } : {};
1755
+ }
1756
+ function isRecord8(value) {
1757
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1758
+ }
1759
+ function stringField2(record, field) {
1760
+ const value = record[field];
1761
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1762
+ }
1763
+
1764
+ // src/domains/eval/runners/clio-run.ts
1765
+ async function runClioRunRunner(runner, cwd, clioEntry, timeoutMs, target, env) {
1766
+ const prompt = runner.prompt ?? "";
1767
+ const args = [
1768
+ shellQuote(clioEntry),
1769
+ "run",
1770
+ "--json",
1771
+ ...runner.agent === void 0 ? [] : ["--agent", shellQuote(runner.agent)],
1772
+ "--target",
1773
+ shellQuote(target.id),
1774
+ ...target.model === void 0 ? [] : ["--model", shellQuote(target.model)],
1775
+ ...target.thinking === void 0 ? [] : ["--thinking", shellQuote(target.thinking)],
1776
+ shellQuote(prompt)
1777
+ ];
1778
+ const result = await runShellCommand(`${process.execPath} ${args.join(" ")}`, cwd, runner.timeoutMs ?? timeoutMs, env);
1779
+ const tokens = result.usage;
1780
+ const toolMetricStream = result.metricJsonl.length > 0 ? result.metricJsonl : result.stdout;
1781
+ const tools = toolCallMetricsFromJsonl(toolMetricStream);
1782
+ const receipt = receiptFromRunJsonStdout(result.stdout);
1783
+ return {
1784
+ assignmentId: receipt === null ? null : receipt.lineage?.rootRunId ?? receipt.runId,
1785
+ terminalReceiptDigest: receipt?.integrity.digest ?? null,
1786
+ exitCode: result.exitCode,
1787
+ stdout: result.stdout,
1788
+ stderr: result.stderr,
1789
+ wallTimeMs: result.wallTimeMs,
1790
+ metrics: {
1791
+ "latency.wallMs": result.wallTimeMs,
1792
+ ...tokenMetricEntries(tokens),
1793
+ // Folded live for the same reason the usage is: the structural
1794
+ // promise these check is broken by a run whose middle is truncated.
1795
+ ...streamInvariantMetrics(result.streamInvariants),
1796
+ "tools.totalCalls": tools.totalCalls,
1797
+ "tools.failed": tools.failed,
1798
+ "tools.blocked": tools.blocked,
1799
+ "verifier.exitCode": result.exitCode,
1800
+ "dispatch.count": dispatchCountFromJsonl(toolMetricStream),
1801
+ "dispatch.scoutCount": scoutDispatchCountFromJsonl(toolMetricStream),
1802
+ "wiki.staleAcknowledged": wikiStaleAcknowledgedFromJsonl(toolMetricStream),
1803
+ ...receipt === null ? {} : evidenceMetricsFromReceipt(receipt),
1804
+ ...receipt === null ? {} : { "evidence.qualityLabel": receipt.quality.typedValidations.length > 0 ? "measured" : "unmeasured" }
1805
+ },
1806
+ artifacts: {
1807
+ stdout: result.stdout,
1808
+ stderr: result.stderr,
1809
+ ...receipt === null ? {} : { receipt: JSON.stringify(receipt) }
1810
+ }
1811
+ };
1812
+ }
1813
+ function toolCallMetricsFromJsonl(stdout) {
1814
+ const executionEnds = { totalCalls: 0, failed: 0, blocked: 0 };
1815
+ const canonicalFinishes = { totalCalls: 0, failed: 0, blocked: 0 };
1816
+ const seenExecutionEnds = /* @__PURE__ */ new Set();
1817
+ const seenCanonicalFinishes = /* @__PURE__ */ new Set();
1818
+ for (const line of stdout.split(/\r?\n/)) {
1819
+ if (line.trim().length === 0) continue;
1820
+ let event;
1821
+ try {
1822
+ const parsed = JSON.parse(line);
1823
+ if (!isRecord9(parsed)) continue;
1824
+ event = parsed;
1825
+ } catch {
1826
+ continue;
1827
+ }
1828
+ if (event.type === "tool_execution_end") {
1829
+ const callId2 = stringField3(event, "toolCallId");
1830
+ if (callId2 !== void 0) {
1831
+ if (seenExecutionEnds.has(callId2)) continue;
1832
+ seenExecutionEnds.add(callId2);
1833
+ }
1834
+ recordToolOutcome(executionEnds, toolOutcome(event) ?? (event.isError === true ? "error" : "ok"));
1835
+ continue;
1836
+ }
1837
+ if (event.type !== "clio_tool_finish" || !isRecord9(event.payload)) continue;
1838
+ const outcome = toolOutcome(event.payload);
1839
+ if (outcome === void 0) continue;
1840
+ const callId = stringField3(event.payload, "toolCallId") ?? stringField3(event, "toolCallId");
1841
+ if (callId !== void 0) {
1842
+ if (seenCanonicalFinishes.has(callId)) continue;
1843
+ seenCanonicalFinishes.add(callId);
1844
+ }
1845
+ recordToolOutcome(canonicalFinishes, outcome);
1846
+ }
1847
+ return canonicalFinishes.totalCalls > 0 ? canonicalFinishes : executionEnds;
1848
+ }
1849
+ function recordToolOutcome(metrics, outcome) {
1850
+ metrics.totalCalls += 1;
1851
+ if (outcome === "error") metrics.failed += 1;
1852
+ else if (outcome === "blocked") metrics.blocked += 1;
1853
+ }
1854
+ function toolOutcome(record) {
1855
+ const outcome = record.outcome;
1856
+ return outcome === "ok" || outcome === "error" || outcome === "blocked" ? outcome : void 0;
1857
+ }
1858
+ function stringField3(record, field) {
1859
+ const value = record[field];
1860
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1861
+ }
1862
+ function isRecord9(value) {
1863
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1864
+ }
1865
+
1866
+ // src/domains/eval/runners/context-index.ts
1867
+ async function runContextIndexRunner(cwd, clioEntry, timeoutMs, target, env) {
1868
+ const result = await runShellCommand(
1869
+ `${shellQuote(process.execPath)} ${shellQuote(clioEntry)} context index --json`,
1870
+ cwd,
1871
+ timeoutMs,
1872
+ env
1873
+ );
1874
+ const parsed = parseContextIndexOutput(result.stdout);
1875
+ return {
1876
+ assignmentId: null,
1877
+ terminalReceiptDigest: null,
1878
+ exitCode: result.exitCode,
1879
+ stdout: result.stdout,
1880
+ stderr: result.stderr,
1881
+ wallTimeMs: result.wallTimeMs,
1882
+ metrics: {
1883
+ "latency.wallMs": result.wallTimeMs,
1884
+ "context.indexedFiles": parsed.indexedSourceFiles ?? 0,
1885
+ "context.coverage": parsed.coverage ?? 0,
1886
+ "context.structuralHash": parsed.structuralHash ?? null,
1887
+ "verifier.exitCode": result.exitCode
1888
+ },
1889
+ artifacts: {
1890
+ stdout: result.stdout,
1891
+ stderr: result.stderr,
1892
+ target: target.id
1893
+ }
1894
+ };
1895
+ }
1896
+ function parseContextIndexOutput(stdout) {
1897
+ try {
1898
+ const parsed = JSON.parse(stdout);
1899
+ const metrics = {};
1900
+ if (typeof parsed.indexedSourceFiles === "number") metrics.indexedSourceFiles = parsed.indexedSourceFiles;
1901
+ if (typeof parsed.coverage === "number") metrics.coverage = parsed.coverage;
1902
+ if (typeof parsed.structuralHash === "string") metrics.structuralHash = parsed.structuralHash;
1903
+ return metrics;
1904
+ } catch {
1905
+ return {};
1906
+ }
1907
+ }
1908
+
1909
+ // src/domains/eval/runners/context-init.ts
1910
+ import { existsSync as existsSync2, statSync } from "node:fs";
1911
+ import { join as join2 } from "node:path";
1912
+ async function runContextInitRunner(runner, cwd, clioEntry, timeoutMs, target, env) {
1913
+ const extraArgs = runner.args ?? [];
1914
+ const command = [
1915
+ process.execPath,
1916
+ clioEntry,
1917
+ "context",
1918
+ "init",
1919
+ "--yes",
1920
+ "--json",
1921
+ "--target",
1922
+ target.id,
1923
+ ...target.model === void 0 ? [] : ["--model", target.model],
1924
+ ...target.thinking === void 0 ? [] : ["--thinking", target.thinking],
1925
+ ...extraArgs
1926
+ ].map(shellQuote).join(" ");
1927
+ const result = await runShellCommand(command, cwd, runner.timeoutMs ?? timeoutMs, env);
1928
+ const payload = parseInitPayload(result.stdout);
1929
+ const candidateGeneration = recordField(payload, "generation");
1930
+ const generation = isValidGenerationPayload(payload, candidateGeneration) ? candidateGeneration : null;
1931
+ const routeError = generation ? generationRouteError(generation, target) : null;
1932
+ const payloadError = generation ? routeError : "context-init runner did not receive a valid JSON generation result";
1933
+ const exitCode = result.exitCode === 0 && payloadError ? 1 : result.exitCode;
1934
+ const stderr = payloadError ? `${result.stderr}${result.stderr.endsWith("\n") || result.stderr.length === 0 ? "" : "\n"}${payloadError}
1935
+ ` : result.stderr;
1936
+ const run = recordField(generation, "run");
1937
+ const tokens = recordField(run, "tokens");
1938
+ const effectiveTarget = stringField4(run, "targetId");
1939
+ const effectiveModel = stringField4(run, "wireModelId");
1940
+ const effectiveRuntime = stringField4(run, "runtimeId");
1941
+ const effectiveRuntimeKind = stringField4(run, "runtimeKind");
1942
+ const effectiveThinking = stringField4(run, "thinkingLevel");
1943
+ const structuredOutputMode = stringField4(run, "structuredOutputMode");
1944
+ const clioMdPath = join2(cwd, "CLIO-CODER.md");
1945
+ const clioMdBytes = existsSync2(clioMdPath) ? statSync(clioMdPath).size : 0;
1946
+ return {
1947
+ assignmentId: null,
1948
+ terminalReceiptDigest: null,
1949
+ exitCode,
1950
+ stdout: result.stdout,
1951
+ stderr,
1952
+ wallTimeMs: result.wallTimeMs,
1953
+ metrics: {
1954
+ "latency.wallMs": result.wallTimeMs,
1955
+ "latency.modelMs": numberField3(run, "durationMs"),
1956
+ "tokens.input": numberField3(tokens, "input"),
1957
+ "tokens.output": numberField3(tokens, "output"),
1958
+ "tokens.total": numberField3(tokens, "total"),
1959
+ "tokens.cacheRead": numberField3(tokens, "cacheRead"),
1960
+ "tokens.cacheWrite": numberField3(tokens, "cacheWrite"),
1961
+ "tools.totalCalls": numberField3(run, "toolCalls"),
1962
+ "tools.failed": numberField3(run, "toolFailures"),
1963
+ "tools.blocked": numberField3(run, "toolBlocked"),
1964
+ "context.clioMdBytes": clioMdBytes,
1965
+ "context.initMode": stringField4(generation, "mode") ?? "unknown",
1966
+ "context.initParserOutcome": stringField4(generation, "parserOutcome") ?? "unknown",
1967
+ "context.initFallback": stringField4(generation, "fallbackReason") !== null,
1968
+ "context.initPromptBytes": numberField3(run, "promptBytes"),
1969
+ "context.initOutputBytes": numberField3(run, "outputBytes"),
1970
+ "context.initTargetId": effectiveTarget,
1971
+ "context.initModelId": effectiveModel,
1972
+ "context.initRuntimeId": effectiveRuntime,
1973
+ "context.initRuntimeKind": effectiveRuntimeKind,
1974
+ "context.initThinkingLevel": effectiveThinking,
1975
+ "context.initStructuredOutputMode": structuredOutputMode,
1976
+ "verifier.exitCode": exitCode
1977
+ },
1978
+ artifacts: {
1979
+ stdout: result.stdout,
1980
+ stderr,
1981
+ requestedTarget: target.id,
1982
+ requestedModel: target.model ?? null,
1983
+ requestedThinking: target.thinking ?? null,
1984
+ effectiveTarget,
1985
+ effectiveModel,
1986
+ effectiveRuntime,
1987
+ effectiveThinking,
1988
+ bootstrapRunId: stringField4(run, "runId")
1989
+ }
1990
+ };
1991
+ }
1992
+ var GENERATION_MODES = /* @__PURE__ */ new Set(["model", "heuristic", "existing"]);
1993
+ var PARSER_OUTCOMES = /* @__PURE__ */ new Set(["parsed", "rejected", "not-run"]);
1994
+ function isNonnegativeFiniteNumber(value) {
1995
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1996
+ }
1997
+ function isValidRunPayload(value) {
1998
+ const run = recordField(value);
1999
+ if (!run) return false;
2000
+ for (const key of ["durationMs", "promptBytes", "outputBytes"]) {
2001
+ if (!isNonnegativeFiniteNumber(run[key])) return false;
2002
+ }
2003
+ if (run.structuredOutputMode !== "native-schema" && run.structuredOutputMode !== "prompt-parser") return false;
2004
+ for (const key of ["toolCalls", "toolFailures", "toolBlocked"]) {
2005
+ if (run[key] !== void 0 && !isNonnegativeFiniteNumber(run[key])) return false;
2006
+ }
2007
+ const tokens = recordField(run, "tokens");
2008
+ if (run.tokens !== void 0 && !tokens) return false;
2009
+ if (tokens) {
2010
+ for (const key of ["total", "input", "output", "cacheRead", "cacheWrite", "reasoning"]) {
2011
+ if (tokens[key] !== void 0 && !isNonnegativeFiniteNumber(tokens[key])) return false;
2012
+ }
2013
+ }
2014
+ return true;
2015
+ }
2016
+ function hasReceiptIdentity(run) {
2017
+ if (!run) return false;
2018
+ return ["runId", "targetId", "wireModelId", "runtimeId", "runtimeKind", "thinkingLevel"].every(
2019
+ (key) => typeof run[key] === "string" && run[key].trim().length > 0
2020
+ );
2021
+ }
2022
+ function isValidGenerationPayload(payload, generation) {
2023
+ if (payload?.version !== 1 || !generation) return false;
2024
+ const mode = generation.mode;
2025
+ const parserOutcome = generation.parserOutcome;
2026
+ if (typeof mode !== "string" || !GENERATION_MODES.has(mode)) return false;
2027
+ if (typeof parserOutcome !== "string" || !PARSER_OUTCOMES.has(parserOutcome)) return false;
2028
+ if (generation.fallbackReason !== void 0 && (typeof generation.fallbackReason !== "string" || generation.fallbackReason.trim().length === 0)) {
2029
+ return false;
2030
+ }
2031
+ const runPresent = generation.run !== void 0;
2032
+ if (runPresent && !isValidRunPayload(generation.run)) return false;
2033
+ const run = recordField(generation, "run");
2034
+ if (mode === "model" && (parserOutcome !== "parsed" || !hasReceiptIdentity(run))) return false;
2035
+ if ((parserOutcome === "parsed" || parserOutcome === "rejected") && !runPresent) return false;
2036
+ if (parserOutcome === "rejected" && !hasReceiptIdentity(run)) return false;
2037
+ return true;
2038
+ }
2039
+ function generationRouteError(generation, target) {
2040
+ const run = recordField(generation, "run");
2041
+ if (!run) return null;
2042
+ const actualTarget = stringField4(run, "targetId");
2043
+ const actualModel = stringField4(run, "wireModelId");
2044
+ const actualThinking = stringField4(run, "thinkingLevel");
2045
+ if (actualTarget !== null && actualTarget !== target.id) {
2046
+ return `context-init runner requested target '${target.id}' but the bootstrap receipt used '${actualTarget}'`;
2047
+ }
2048
+ if (target.model !== void 0 && actualModel !== null && actualModel !== target.model) {
2049
+ return `context-init runner requested model '${target.model}' but the bootstrap receipt used '${actualModel}'`;
2050
+ }
2051
+ if (target.thinking !== void 0 && actualThinking !== null && actualThinking !== target.thinking) {
2052
+ return `context-init runner requested thinking '${target.thinking}' but the bootstrap receipt used '${actualThinking}'`;
2053
+ }
2054
+ return null;
2055
+ }
2056
+ function parseInitPayload(stdout) {
2057
+ try {
2058
+ const parsed = JSON.parse(stdout);
2059
+ return recordField(parsed);
2060
+ } catch {
2061
+ return null;
2062
+ }
2063
+ }
2064
+ function recordField(value, field) {
2065
+ const selected = field && typeof value === "object" && value !== null && !Array.isArray(value) ? value[field] : value;
2066
+ return typeof selected === "object" && selected !== null && !Array.isArray(selected) ? selected : null;
2067
+ }
2068
+ function numberField3(value, field) {
2069
+ const record = recordField(value);
2070
+ const selected = record?.[field];
2071
+ return typeof selected === "number" && Number.isFinite(selected) ? selected : null;
2072
+ }
2073
+ function stringField4(value, field) {
2074
+ const record = recordField(value);
2075
+ const selected = record?.[field];
2076
+ return typeof selected === "string" && selected.length > 0 ? selected : null;
2077
+ }
2078
+
2079
+ // src/domains/eval/verifiers/command.ts
2080
+ async function runCommandVerifiers(commands, cwd, timeoutMs) {
2081
+ let stdout = "";
2082
+ let stderr = "";
2083
+ let wallTimeMs = 0;
2084
+ for (const command of commands) {
2085
+ const result = await runShellCommand(command, cwd, timeoutMs);
2086
+ stdout += result.stdout;
2087
+ stderr += result.stderr;
2088
+ wallTimeMs += result.wallTimeMs;
2089
+ if (result.exitCode !== 0) return { pass: false, exitCode: result.exitCode, stdout, stderr, wallTimeMs };
2090
+ }
2091
+ return { pass: true, exitCode: 0, stdout, stderr, wallTimeMs };
2092
+ }
2093
+
2094
+ // src/domains/eval/verifiers/file-exists.ts
2095
+ import { existsSync as existsSync3 } from "node:fs";
2096
+ import { resolve as resolve2 } from "node:path";
2097
+ function forbiddenPathHits(cwd, paths) {
2098
+ return paths.filter((path) => existsSync3(resolve2(cwd, path)));
2099
+ }
2100
+
2101
+ // src/domains/eval/verifiers/patch.ts
2102
+ import { spawnSync } from "node:child_process";
2103
+ function collectPatchMetrics(cwd) {
2104
+ const diff = spawnSync("git", ["diff", "--", "."], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
2105
+ const text = diff.status === 0 && typeof diff.stdout === "string" ? diff.stdout : "";
2106
+ const files = text.split(/\r?\n/).filter((line) => line.startsWith("diff --git ")).map((line) => line.split(" b/")[1] ?? "");
2107
+ return {
2108
+ bytes: Buffer.byteLength(text, "utf8"),
2109
+ filesChanged: files.length,
2110
+ testFilesModified: files.filter((file) => /(^|\/)(test|tests|spec|__tests__)(\/|$)|\.(test|spec)\./.test(file)).length
2111
+ };
2112
+ }
2113
+
2114
+ // src/domains/eval/workspaces/git.ts
2115
+ import { spawn as spawn2 } from "node:child_process";
2116
+ import { mkdtemp, rm } from "node:fs/promises";
2117
+ import { tmpdir } from "node:os";
2118
+ import { resolve as resolve3 } from "node:path";
2119
+ async function prepareGitWorkspace(workspace) {
2120
+ if (workspace.url === void 0) throw new Error("git workspace requires url");
2121
+ const dest = await mkdtemp(resolve3(tmpdir(), "clio-eval-git-"));
2122
+ try {
2123
+ await runGit(["clone", "--quiet", workspace.url, dest], process.cwd());
2124
+ const ref = workspace.checkout ?? workspace.commit;
2125
+ if (ref !== void 0) await runGit(["checkout", "--quiet", ref], dest);
2126
+ return {
2127
+ dir: dest,
2128
+ cleanup: async () => {
2129
+ await rm(dest, { recursive: true, force: true });
2130
+ }
2131
+ };
2132
+ } catch (error) {
2133
+ await rm(dest, { recursive: true, force: true });
2134
+ throw error;
2135
+ }
2136
+ }
2137
+ function runGit(args, cwd) {
2138
+ return new Promise((resolveRun, rejectRun) => {
2139
+ const child = spawn2("git", [...args], { cwd, stdio: ["ignore", "ignore", "pipe"] });
2140
+ let stderr = "";
2141
+ child.stderr.setEncoding("utf8");
2142
+ child.stderr.on("data", (chunk) => {
2143
+ stderr += chunk;
2144
+ });
2145
+ child.on("error", rejectRun);
2146
+ child.on("close", (code) => {
2147
+ if (code === 0) resolveRun();
2148
+ else rejectRun(new Error(`git ${args.join(" ")} failed: ${stderr.trim()}`));
2149
+ });
2150
+ });
2151
+ }
2152
+
2153
+ // src/domains/eval/workspaces/local.ts
2154
+ import { access } from "node:fs/promises";
2155
+ import { resolve as resolve4 } from "node:path";
2156
+ async function prepareLocalWorkspace(baseDir, workspace) {
2157
+ const dir = resolve4(baseDir, workspace.path ?? ".");
2158
+ await access(dir);
2159
+ return { dir, cleanup: async () => {
2160
+ } };
2161
+ }
2162
+
2163
+ // src/domains/eval/workspaces/temp-copy.ts
2164
+ import { cp, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
2165
+ import { tmpdir as tmpdir2 } from "node:os";
2166
+ import { relative, resolve as resolve5 } from "node:path";
2167
+ async function prepareTempCopyWorkspace(baseDir, workspace) {
2168
+ const source = resolve5(baseDir, workspace.path ?? ".");
2169
+ const dest = await mkdtemp2(resolve5(tmpdir2(), "clio-eval-workspace-"));
2170
+ const excludes = workspace.excludes ?? [];
2171
+ await cp(source, dest, {
2172
+ recursive: true,
2173
+ filter: (path) => !isExcluded(relative(source, path), excludes)
2174
+ });
2175
+ return {
2176
+ dir: dest,
2177
+ cleanup: async () => {
2178
+ await rm2(dest, { recursive: true, force: true });
2179
+ }
2180
+ };
2181
+ }
2182
+ function isExcluded(rel, excludes) {
2183
+ const normalized = rel.replaceAll("\\", "/");
2184
+ return excludes.some((entry) => normalized === entry || normalized.startsWith(`${entry.replaceAll("\\", "/")}/`));
2185
+ }
2186
+
2187
+ // src/domains/eval/suites/matrix.ts
2188
+ function expandEvalMatrix(suite) {
2189
+ const runs = [];
2190
+ for (let repeatIndex = 0; repeatIndex < suite.matrix.repeats; repeatIndex += 1) {
2191
+ for (const target of suite.matrix.targets) {
2192
+ for (const task of suite.tasks) {
2193
+ runs.push({ task, target, repeatIndex });
2194
+ }
2195
+ }
2196
+ }
2197
+ return runs;
2198
+ }
2199
+
2200
+ // src/domains/eval/suites/run.ts
2201
+ var EvalWorkspaceSetupError = class extends Error {
2202
+ constructor(exitCode, stderr) {
2203
+ super(`workspace setup failed (exit ${exitCode}): ${stderr.trim()}`);
2204
+ this.name = "EvalWorkspaceSetupError";
2205
+ }
2206
+ };
2207
+ async function runEvalSuiteV2(loaded, options) {
2208
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
2209
+ const started = now();
2210
+ const evalId = createEvalId(started, loaded.hash);
2211
+ const results = [];
2212
+ const maxCostUsd = loaded.suite.matrix.maxCostUsd;
2213
+ let spentUsd = 0;
2214
+ for (const item of expandEvalMatrix(loaded.suite)) {
2215
+ if (maxCostUsd !== void 0 && spentUsd > maxCostUsd) {
2216
+ results.push(budgetExhaustedResult(item.task.id, item.target, item.repeatIndex, spentUsd, maxCostUsd));
2217
+ continue;
2218
+ }
2219
+ const result = await runMatrixItem(loaded, item.task, item.target, item.repeatIndex, options.clioEntry);
2220
+ spentUsd += resultCostUsd(result);
2221
+ results.push(result);
2222
+ }
2223
+ return buildArtifact(loaded, evalId, results, options.clioEntry);
2224
+ }
2225
+ function resultCostUsd(result) {
2226
+ const value = result.metrics["cost.usd"];
2227
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
2228
+ }
2229
+ function budgetExhaustedResult(taskId, target, repeatIndex, spentUsd, maxCostUsd) {
2230
+ return {
2231
+ assignmentId: null,
2232
+ terminalReceiptDigest: null,
2233
+ taskId,
2234
+ repeatIndex,
2235
+ target: { id: target.id, model: target.model ?? null, thinking: target.thinking ?? null },
2236
+ pass: false,
2237
+ failureClass: "budget_exhausted",
2238
+ metrics: {
2239
+ "result.pass": false,
2240
+ "result.failureClass": "budget_exhausted",
2241
+ "verifier.exitCode": 1,
2242
+ "latency.wallMs": 0
2243
+ },
2244
+ artifacts: {
2245
+ error: `matrix cost budget exhausted: spent $${spentUsd.toFixed(4)} of max $${maxCostUsd.toFixed(4)} before this item`
2246
+ }
2247
+ };
2248
+ }
2249
+ async function runMatrixItem(loaded, task, target, repeatIndex, clioEntry) {
2250
+ let workspace = null;
2251
+ const stateDir = await mkdtemp3(resolve6(tmpdir3(), "clio-eval-state-"));
2252
+ try {
2253
+ workspace = await prepareWorkspace(loaded.baseDir, task);
2254
+ const setup = await runCommandVerifiers(task.workspace.setup ?? [], workspace.dir, task.timeoutMs);
2255
+ if (!setup.pass) throw new EvalWorkspaceSetupError(setup.exitCode, setup.stderr);
2256
+ const runner = await runTaskRunner(task, target, workspace.dir, clioEntry, {
2257
+ CLIO_CODER_STATE_DIR: stateDir,
2258
+ CLIO_CODER_ENTRY: clioEntry
2259
+ });
2260
+ const patch = collectPatchMetrics(workspace.dir);
2261
+ const receiptExitCode = receiptProcessExitCode(runner);
2262
+ const journalMetrics = invariantMetrics(stateDir, receiptExitCode);
2263
+ const metrics = {
2264
+ ...zeroToolCallMetrics(),
2265
+ ...collectContextMetrics(workspace.dir),
2266
+ // A runner may have exact measurements from command output. Those win
2267
+ // over the generic post-run artifact collector.
2268
+ ...runner.metrics,
2269
+ ...journalMetrics,
2270
+ // The one reading that needs both sides: what the loop reported it
2271
+ // spent, and what the journal shows it sealed.
2272
+ ...fleetLoopReceiptAgreement(runner.metrics, journalMetrics),
2273
+ "patch.bytes": patch.bytes,
2274
+ "patch.filesChanged": patch.filesChanged,
2275
+ "patch.testFilesModified": patch.testFilesModified,
2276
+ "result.pass": runner.exitCode === 0,
2277
+ "result.failureClass": runner.exitCode === 0 ? null : "runner_failed",
2278
+ ...await measureTaskOutcome(task, workspace.dir)
2279
+ };
2280
+ const verifier = await runVerifiers(task, workspace.dir, metrics);
2281
+ const pass = runner.exitCode === 0 && verifier.pass;
2282
+ const failureClass = pass ? null : runner.exitCode !== 0 ? "runner_failed" : verifier.failureClass;
2283
+ metrics["verifier.exitCode"] = verifier.exitCode;
2284
+ metrics["result.pass"] = pass;
2285
+ metrics["result.failureClass"] = failureClass;
2286
+ return {
2287
+ assignmentId: runner.assignmentId,
2288
+ terminalReceiptDigest: runner.terminalReceiptDigest,
2289
+ taskId: task.id,
2290
+ repeatIndex,
2291
+ target: { id: target.id, model: target.model ?? null, thinking: target.thinking ?? null },
2292
+ pass,
2293
+ failureClass,
2294
+ metrics,
2295
+ artifacts: {
2296
+ ...runner.artifacts,
2297
+ ...verifier.stdout.length > 0 ? { verifierStdout: verifier.stdout } : {},
2298
+ ...verifier.stderr.length > 0 ? { verifierStderr: verifier.stderr } : {}
2299
+ }
2300
+ };
2301
+ } catch (error) {
2302
+ const failureClass = error instanceof EvalWorkspaceSetupError ? "setup_failed" : "command_error";
2303
+ return {
2304
+ assignmentId: null,
2305
+ terminalReceiptDigest: null,
2306
+ taskId: task.id,
2307
+ repeatIndex,
2308
+ target: { id: target.id, model: target.model ?? null, thinking: target.thinking ?? null },
2309
+ pass: false,
2310
+ failureClass,
2311
+ metrics: {
2312
+ "result.pass": false,
2313
+ "result.failureClass": failureClass,
2314
+ "verifier.exitCode": 1,
2315
+ "latency.wallMs": 0
2316
+ },
2317
+ artifacts: { error: error instanceof Error ? error.message : String(error) }
2318
+ };
2319
+ } finally {
2320
+ await workspace?.cleanup();
2321
+ await rm3(stateDir, { recursive: true, force: true });
2322
+ }
2323
+ }
2324
+ function receiptProcessExitCode(runner) {
2325
+ const chaosExitCode = runner.metrics["chaos.exitCode"];
2326
+ return typeof chaosExitCode === "number" && Number.isInteger(chaosExitCode) ? chaosExitCode : runner.exitCode;
2327
+ }
2328
+ function invariantMetrics(stateDir, runnerExitCode) {
2329
+ const journal = readRunJournal(stateDir);
2330
+ return {
2331
+ ...receiptInvariantMetrics(journal, runnerExitCode),
2332
+ ...receiptUsageMetrics(journal),
2333
+ ...sessionInvariantMetrics(stateDir),
2334
+ ...processInvariantMetrics(journal),
2335
+ ...writeBoundaryInvariantMetrics(stateDir)
2336
+ };
2337
+ }
2338
+ async function prepareWorkspace(baseDir, task) {
2339
+ if (task.workspace.kind === "local") return prepareLocalWorkspace(baseDir, task.workspace);
2340
+ if (task.workspace.kind === "git") return prepareGitWorkspace(task.workspace);
2341
+ return prepareTempCopyWorkspace(baseDir, task.workspace);
2342
+ }
2343
+ async function runTaskRunner(task, target, cwd, clioEntry, env) {
2344
+ if (task.runner.kind === "external-command") return runExternalCommandRunner(task.runner, cwd, task.timeoutMs, env);
2345
+ if (task.runner.kind === "context-index") return runContextIndexRunner(cwd, clioEntry, task.timeoutMs, target, env);
2346
+ if (task.runner.kind === "context-init")
2347
+ return runContextInitRunner(task.runner, cwd, clioEntry, task.timeoutMs, target, env);
2348
+ return runClioRunRunner(task.runner, cwd, clioEntry, task.timeoutMs, target, env);
2349
+ }
2350
+ async function measureTaskOutcome(task, cwd) {
2351
+ const commands = task.verify.measure ?? [];
2352
+ if (commands.length === 0) return {};
2353
+ const result = await runCommandVerifiers(commands, cwd, task.timeoutMs);
2354
+ return { "task.exitCode": result.exitCode, "task.solved": result.exitCode === 0 };
2355
+ }
2356
+ async function runVerifiers(task, cwd, metrics) {
2357
+ const commandResult = await runCommandVerifiers(task.verify.commands ?? [], cwd, task.timeoutMs);
2358
+ if (!commandResult.pass) {
2359
+ return {
2360
+ pass: false,
2361
+ exitCode: commandResult.exitCode,
2362
+ failureClass: "verifier_failed",
2363
+ stdout: commandResult.stdout,
2364
+ stderr: commandResult.stderr
2365
+ };
2366
+ }
2367
+ const forbidden = forbiddenPathHits(cwd, task.verify.forbidPaths ?? []);
2368
+ if (forbidden.length > 0) {
2369
+ return {
2370
+ pass: false,
2371
+ exitCode: 1,
2372
+ failureClass: "forbidden_path",
2373
+ stdout: "",
2374
+ stderr: `forbidden paths exist: ${forbidden.join(", ")}`
2375
+ };
2376
+ }
2377
+ for (const assertion of task.verify.assertions ?? []) {
2378
+ const resolution = resolveMetricAssertion(assertion, metrics);
2379
+ if (!resolution.unresolved && resolution.holds) continue;
2380
+ return {
2381
+ pass: false,
2382
+ exitCode: 1,
2383
+ failureClass: resolution.unresolved ? "assertion_unresolved" : "assertion_failed",
2384
+ stdout: "",
2385
+ stderr: resolution.unresolved ? `assertion unresolved (fail closed): ${assertion.metric} was not measured by this run` : assertionMessage(assertion, resolution.actual)
2386
+ };
2387
+ }
2388
+ return { pass: true, exitCode: 0, failureClass: null, stdout: commandResult.stdout, stderr: commandResult.stderr };
2389
+ }
2390
+ function buildArtifact(loaded, evalId, results, clioEntry) {
2391
+ const passed = results.filter((result) => result.pass).length;
2392
+ return {
2393
+ version: 4,
2394
+ evalId,
2395
+ suite: { id: loaded.suite.suite.id, hash: loaded.hash },
2396
+ clio: evalClioProvenance({ entry: clioEntry }),
2397
+ environment: evalEnvironmentProvenance(),
2398
+ matrix: artifactMatrixIdentity(loaded.suite.matrix.targets),
2399
+ summary: {
2400
+ runs: results.length,
2401
+ passed,
2402
+ failed: results.length - passed,
2403
+ passRate: results.length === 0 ? 0 : passed / results.length,
2404
+ tokens: tokenAccountingFrom(results),
2405
+ wallTimeMs: results.reduce((sum2, result) => sum2 + wallTimeMetric(result.metrics), 0)
2406
+ },
2407
+ results
2408
+ };
2409
+ }
2410
+ function assertionMessage(assertion, actual) {
2411
+ return `assertion failed: ${assertion.metric} ${assertion.op} ${String(assertion.value)} (actual ${JSON.stringify(actual)})`;
2412
+ }
2413
+
2414
+ // src/cli/eval.ts
2415
+ var HELP = `clio-coder eval <command>
2416
+
2417
+ Commands:
2418
+ clio-coder eval validate --suite <suite.yaml>
2419
+ clio-coder eval run --suite <suite.yaml> [--target <id>] [--model <id>] [--out <path>] [--clio-entry <path>]
2420
+ clio-coder eval run --task-file <tasks.yaml> [--repeat <n>] [--out <path>] [--clio-entry <path>]
2421
+ clio-coder eval report <evalId> --format text|json|md|swe-jsonl|junit
2422
+ clio-coder eval compare <baselineEvalId> <candidateEvalId>
2423
+ clio-coder eval gate <candidateEvalId> --baseline <baselineEvalId> [--thresholds <file>]
2424
+ `;
2425
+ function parseEvalArgs(args) {
2426
+ const parsed = { repeat: 1, compareIds: [], format: "text", help: false };
2427
+ for (let index = 0; index < args.length; index += 1) {
2428
+ const arg = args[index];
2429
+ if (arg === void 0) continue;
2430
+ if (arg === "--help" || arg === "-h") {
2431
+ parsed.help = true;
2432
+ continue;
2433
+ }
2434
+ if (parsed.command === void 0) {
2435
+ if (arg === "validate" || arg === "run" || arg === "report" || arg === "compare" || arg === "gate") {
2436
+ parsed.command = arg;
2437
+ continue;
2438
+ }
2439
+ throw new Error(`unknown eval command: ${arg}`);
2440
+ }
2441
+ if (parsed.command === "validate") {
2442
+ if (arg === "--suite") {
2443
+ parsed.suite = requiredValue(args, index, "--suite");
2444
+ index += 1;
2445
+ continue;
2446
+ }
2447
+ throw new Error(`unknown eval validate argument: ${arg}`);
2448
+ }
2449
+ if (parsed.command === "run") {
2450
+ if (arg === "--suite") {
2451
+ parsed.suite = requiredValue(args, index, "--suite");
2452
+ index += 1;
2453
+ continue;
2454
+ }
2455
+ if (arg === "--task-file") {
2456
+ parsed.taskFile = requiredValue(args, index, "--task-file");
2457
+ index += 1;
2458
+ continue;
2459
+ }
2460
+ if (arg === "--target") {
2461
+ parsed.target = requiredValue(args, index, "--target");
2462
+ index += 1;
2463
+ continue;
2464
+ }
2465
+ if (arg === "--model") {
2466
+ parsed.model = requiredValue(args, index, "--model");
2467
+ index += 1;
2468
+ continue;
2469
+ }
2470
+ if (arg === "--out") {
2471
+ parsed.out = requiredValue(args, index, "--out");
2472
+ index += 1;
2473
+ continue;
2474
+ }
2475
+ if (arg === "--clio-entry") {
2476
+ parsed.clioEntry = requiredValue(args, index, "--clio-entry");
2477
+ index += 1;
2478
+ continue;
2479
+ }
2480
+ if (arg === "--repeat") {
2481
+ parsed.repeat = positiveInteger(requiredValue(args, index, "--repeat"), "--repeat");
2482
+ index += 1;
2483
+ continue;
2484
+ }
2485
+ throw new Error(`unknown eval run argument: ${arg}`);
2486
+ }
2487
+ if (parsed.command === "report") {
2488
+ if (arg === "--format") {
2489
+ parsed.format = reportFormat(requiredValue(args, index, "--format"));
2490
+ index += 1;
2491
+ continue;
2492
+ }
2493
+ if (parsed.evalId === void 0 && !arg.startsWith("-")) {
2494
+ parsed.evalId = arg;
2495
+ continue;
2496
+ }
2497
+ throw new Error(`unexpected eval report argument: ${arg}`);
2498
+ }
2499
+ if (parsed.command === "compare") {
2500
+ if (!arg.startsWith("-")) {
2501
+ parsed.compareIds.push(arg);
2502
+ continue;
2503
+ }
2504
+ throw new Error(`unexpected eval compare argument: ${arg}`);
2505
+ }
2506
+ if (parsed.command === "gate") {
2507
+ if (arg === "--baseline") {
2508
+ parsed.baseline = requiredValue(args, index, "--baseline");
2509
+ index += 1;
2510
+ continue;
2511
+ }
2512
+ if (arg === "--thresholds") {
2513
+ parsed.thresholds = requiredValue(args, index, "--thresholds");
2514
+ index += 1;
2515
+ continue;
2516
+ }
2517
+ if (parsed.evalId === void 0 && !arg.startsWith("-")) {
2518
+ parsed.evalId = arg;
2519
+ continue;
2520
+ }
2521
+ throw new Error(`unexpected eval gate argument: ${arg}`);
2522
+ }
2523
+ }
2524
+ if (parsed.help) return parsed;
2525
+ if (parsed.command === void 0) throw new Error("eval requires a command");
2526
+ if (parsed.command === "validate" && parsed.suite === void 0) throw new Error("validate requires --suite <path>");
2527
+ if (parsed.command === "run" && parsed.suite === void 0 === (parsed.taskFile === void 0)) {
2528
+ throw new Error("run requires exactly one of --suite or --task-file");
2529
+ }
2530
+ if (parsed.command === "report" && parsed.evalId === void 0) throw new Error("report requires an eval id");
2531
+ if (parsed.command === "compare" && parsed.compareIds.length !== 2) {
2532
+ throw new Error("compare requires <baselineEvalId> <candidateEvalId>");
2533
+ }
2534
+ if (parsed.command === "gate" && (parsed.evalId === void 0 || parsed.baseline === void 0)) {
2535
+ throw new Error("gate requires <candidateEvalId> --baseline <baselineEvalId>");
2536
+ }
2537
+ return parsed;
2538
+ }
2539
+ async function runEvalCommand(args) {
2540
+ let parsed;
2541
+ try {
2542
+ parsed = parseEvalArgs(args);
2543
+ } catch (error) {
2544
+ printError(error instanceof Error ? error.message : String(error));
2545
+ process.stderr.write(HELP);
2546
+ return 2;
2547
+ }
2548
+ if (parsed.help) {
2549
+ process.stdout.write(HELP);
2550
+ return 0;
2551
+ }
2552
+ if (parsed.command === "validate") return runEvalValidate(parsed);
2553
+ if (parsed.command === "run") return runEvalRun(parsed);
2554
+ if (parsed.command === "report") return runEvalReportCommand(parsed);
2555
+ if (parsed.command === "compare") return runEvalCompareCommand(parsed);
2556
+ if (parsed.command === "gate") return runEvalGateCommand(parsed);
2557
+ printError("eval requires a command");
2558
+ return 2;
2559
+ }
2560
+ async function runEvalValidate(parsed) {
2561
+ try {
2562
+ const loaded = await loadEvalSuiteFile(parsed.suite ?? "");
2563
+ process.stdout.write(`valid suite: ${loaded.suite.suite.id}
2564
+ `);
2565
+ return 0;
2566
+ } catch (error) {
2567
+ return handleEvalLoadError(error);
2568
+ }
2569
+ }
2570
+ async function runEvalRun(parsed) {
2571
+ try {
2572
+ const loaded = parsed.suite !== void 0 ? await loadEvalSuiteFile(parsed.suite) : await loadV1TaskFileAsSuite(parsed.taskFile ?? "", parsed.repeat);
2573
+ const resolveOptions = {};
2574
+ if (parsed.target !== void 0) resolveOptions.target = parsed.target;
2575
+ if (parsed.model !== void 0) resolveOptions.model = parsed.model;
2576
+ const suite = resolveSuiteForRun(loaded.suite, resolveOptions);
2577
+ const clioEntry = resolve7(parsed.clioEntry ?? process.argv[1] ?? "dist/cli/index.js");
2578
+ const artifact = await runEvalSuiteV2({ ...loaded, suite }, { clioEntry });
2579
+ const artifactPath = await writeEvalArtifactV4(clioDataDir(), artifact, parsed.out);
2580
+ process.stdout.write(`${renderEvalTextReportV4(artifact)}artifact: ${artifactPath}
2581
+ `);
2582
+ const gate = suite.thresholds === void 0 ? null : evaluateGate(artifact, suite.thresholds);
2583
+ if (gate !== null && !gate.pass) {
2584
+ process.stdout.write(`gate: fail (${gate.failures.length} threshold failure)
2585
+ `);
2586
+ for (const failure of gate.failures) process.stdout.write(renderGateFailure(failure));
2587
+ }
2588
+ return artifact.summary.failed === 0 && (gate === null || gate.pass) ? 0 : 1;
2589
+ } catch (error) {
2590
+ return handleEvalLoadError(error, 1);
2591
+ }
2592
+ }
2593
+ async function runEvalReportCommand(parsed) {
2594
+ try {
2595
+ const dataDir = clioDataDir();
2596
+ const artifact = await loadEvalArtifactV4(dataDir, parsed.evalId ?? "");
2597
+ process.stdout.write(renderArtifactReport(artifact, parsed.format, dataDir));
2598
+ return 0;
2599
+ } catch (error) {
2600
+ printError(error instanceof Error ? error.message : String(error));
2601
+ return error instanceof InvalidIdError ? 2 : 1;
2602
+ }
2603
+ }
2604
+ async function runEvalCompareCommand(parsed) {
2605
+ const baselineEvalId = parsed.compareIds[0] ?? "";
2606
+ const candidateEvalId = parsed.compareIds[1] ?? "";
2607
+ try {
2608
+ const dataDir = clioDataDir();
2609
+ const baseline = await loadEvalArtifactV4(dataDir, baselineEvalId);
2610
+ const candidate = await loadEvalArtifactV4(dataDir, candidateEvalId);
2611
+ process.stdout.write(renderEvalComparisonV4(compareEvalArtifactsV4(baseline, candidate)));
2612
+ return 0;
2613
+ } catch (error) {
2614
+ printError(error instanceof Error ? error.message : String(error));
2615
+ return error instanceof InvalidIdError ? 2 : 1;
2616
+ }
2617
+ }
2618
+ async function runEvalGateCommand(parsed) {
2619
+ try {
2620
+ const dataDir = clioDataDir();
2621
+ const candidate = await loadEvalArtifactV4(dataDir, parsed.evalId ?? "");
2622
+ await loadEvalArtifactV4(dataDir, parsed.baseline ?? "");
2623
+ const thresholds = parsed.thresholds === void 0 ? { fail: [{ metric: "result.pass", op: "eq", value: false }] } : loadThresholds(parsed.thresholds);
2624
+ const gate = evaluateGate(candidate, thresholds);
2625
+ if (gate.pass) {
2626
+ process.stdout.write("gate: pass\n");
2627
+ return 0;
2628
+ }
2629
+ process.stdout.write(`gate: fail (${gate.failures.length} threshold failure)
2630
+ `);
2631
+ for (const failure of gate.failures) process.stdout.write(renderGateFailure(failure));
2632
+ return 1;
2633
+ } catch (error) {
2634
+ printError(error instanceof Error ? error.message : String(error));
2635
+ return error instanceof InvalidIdError ? 2 : 1;
2636
+ }
2637
+ }
2638
+ function renderArtifactReport(artifact, format, _dataDir) {
2639
+ if (format === "json") return renderEvalJsonReportV4(artifact);
2640
+ if (format === "md") return renderEvalMarkdownReportV4(artifact);
2641
+ if (format === "swe-jsonl") return renderEvalSweJsonlReportV4(artifact);
2642
+ if (format === "junit") return renderEvalJunitReportV4(artifact);
2643
+ return renderEvalTextReportV4(artifact);
2644
+ }
2645
+ function handleEvalLoadError(error, fallback = 2) {
2646
+ if (error instanceof EvalSuiteFileError || error instanceof EvalTaskFileError) {
2647
+ printError(error.message);
2648
+ for (const issue of error.issues) process.stderr.write(` ${issue.path}: ${issue.message}
2649
+ `);
2650
+ return 2;
2651
+ }
2652
+ printError(error instanceof Error ? error.message : String(error));
2653
+ return fallback;
2654
+ }
2655
+ function requiredValue(args, index, flag) {
2656
+ const value = args[index + 1];
2657
+ if (value === void 0 || value.startsWith("-")) throw new Error(`${flag} requires a value`);
2658
+ return value;
2659
+ }
2660
+ function positiveInteger(value, flag) {
2661
+ const parsed = Number.parseInt(value, 10);
2662
+ if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== value) {
2663
+ throw new Error(`${flag} requires a positive integer`);
2664
+ }
2665
+ return parsed;
2666
+ }
2667
+ function reportFormat(value) {
2668
+ if (value === "text" || value === "json" || value === "md" || value === "swe-jsonl" || value === "junit") return value;
2669
+ throw new Error("--format must be text, json, md, swe-jsonl, or junit");
2670
+ }
2671
+ export {
2672
+ runEvalCommand
2673
+ };
2674
+ //# sourceMappingURL=eval-B3KZZESM.js.map