@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21

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 (243) hide show
  1. package/CONTRIBUTING.md +170 -0
  2. package/LICENSE +63 -136
  3. package/README.md +83 -66
  4. package/docs/README.md +44 -0
  5. package/docs/acknowledgements.md +14 -0
  6. package/docs/architecture.md +146 -0
  7. package/docs/assets/README.md +27 -0
  8. package/docs/assets/sevro-logo.png +0 -0
  9. package/docs/assets/sevro-symbol.svg +1 -0
  10. package/docs/assets/sevro-wordmark.svg +1 -0
  11. package/docs/builtin-graders-v1.md +41 -13
  12. package/docs/codex-host.md +184 -52
  13. package/docs/contracts.md +19 -0
  14. package/docs/creating-cases.md +139 -0
  15. package/docs/development-cli.md +190 -30
  16. package/docs/documentation-quality.md +61 -0
  17. package/docs/evaluation-workflow.md +110 -0
  18. package/docs/evidence/code-mode-controls-71.md +64 -0
  19. package/docs/evidence/code-mode-programs-85.md +64 -0
  20. package/docs/evidence/codex-state-73/README.md +204 -0
  21. package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
  22. package/docs/evidence/codex-state-73/results.json +661 -0
  23. package/docs/evidence/local-http-74.md +104 -0
  24. package/docs/evidence/runtime-startup-30/README.md +185 -0
  25. package/docs/evidence/runtime-startup-30/after.json +319 -0
  26. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
  27. package/docs/evidence/runtime-startup-30/before.json +403 -0
  28. package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
  29. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
  30. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
  31. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
  32. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
  33. package/docs/evidence/skill-reads-70/results.json +823 -0
  34. package/docs/evidence.md +21 -0
  35. package/docs/extension-protocol-v1.md +50 -16
  36. package/docs/fixture-cleanup-validation.md +3 -0
  37. package/docs/getting-started.md +102 -0
  38. package/docs/guide-evaluation.md +89 -0
  39. package/docs/guide-verification.json +239 -0
  40. package/docs/identity-v1.md +35 -1
  41. package/docs/installing.md +88 -0
  42. package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
  43. package/docs/licensing.md +42 -0
  44. package/docs/native-controls.md +76 -5
  45. package/docs/native-hosts.md +228 -0
  46. package/docs/reading-results.md +141 -0
  47. package/docs/recipes.md +489 -0
  48. package/docs/releases.md +81 -40
  49. package/docs/report-v1.md +29 -1
  50. package/docs/results-v1.md +180 -0
  51. package/docs/run-settings-v1.md +147 -0
  52. package/docs/running-evaluations.md +31 -0
  53. package/docs/runtime-v1.md +614 -0
  54. package/docs/specs/repository-guide.md +58 -0
  55. package/docs/troubleshooting.md +30 -0
  56. package/docs/typescript-quality.md +234 -0
  57. package/docs/usability-study.md +114 -0
  58. package/examples/basic/host.ts +2 -2
  59. package/examples/recipes/candidate.ts +26 -0
  60. package/examples/recipes/cases/coding-change.json +21 -0
  61. package/examples/recipes/cases/host-tools.json +21 -0
  62. package/examples/recipes/cases/mise.json +21 -0
  63. package/examples/recipes/cases/node.json +21 -0
  64. package/examples/recipes/cases/python-uv.json +21 -0
  65. package/examples/recipes/cases/semantic.json +17 -0
  66. package/examples/recipes/coding-change/check.ts +23 -0
  67. package/examples/recipes/extension-artifacts.ts +65 -0
  68. package/examples/recipes/extension-methods.ts +174 -0
  69. package/examples/recipes/extension.ts +37 -0
  70. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  71. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  72. package/examples/recipes/repository-skill/README.md +3 -0
  73. package/examples/recipes/run-inputs.ts +188 -0
  74. package/examples/recipes/run-options.ts +131 -0
  75. package/examples/recipes/run.ts +63 -0
  76. package/examples/recipes/runtime/empty.json +1 -0
  77. package/examples/recipes/runtime/goals.json +1 -0
  78. package/examples/recipes/runtime/mise.json +6 -0
  79. package/examples/recipes/runtime/path.json +1 -0
  80. package/examples/recipes/runtime/plugins.json +4 -0
  81. package/examples/recipes/runtime/python-uv.json +10 -0
  82. package/examples/recipes/semantic-judge.ts +29 -0
  83. package/examples/templates/coding/adapter.ts +17 -0
  84. package/examples/templates/coding/case.json +15 -0
  85. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  86. package/examples/templates/repository-skill/extension-data.ts +64 -0
  87. package/examples/templates/repository-skill/extension.ts +31 -0
  88. package/examples/templates/response/adapter.ts +12 -0
  89. package/examples/templates/response/case.json +13 -0
  90. package/package.json +39 -6
  91. package/schemas/report-v1.schema.json +9 -0
  92. package/schemas/run-evidence-v1.schema.json +2 -1
  93. package/schemas/runtime-v1.schema.json +193 -0
  94. package/src/advisory.ts +10 -5
  95. package/src/candidate-artifacts.ts +208 -0
  96. package/src/candidate-codex-state.ts +185 -0
  97. package/src/case-validation.ts +105 -0
  98. package/src/cli/cancellation.ts +20 -0
  99. package/src/cli/command.ts +81 -0
  100. package/src/cli/completion.ts +15 -0
  101. package/src/cli/demo.ts +59 -0
  102. package/src/cli/evaluation.ts +55 -0
  103. package/src/cli/extension.ts +79 -0
  104. package/src/cli/help.ts +222 -0
  105. package/src/cli/hosts.ts +59 -0
  106. package/src/cli/init-settings.ts +30 -0
  107. package/src/cli/init.ts +111 -0
  108. package/src/cli/invocation.ts +627 -0
  109. package/src/cli/loading.ts +151 -0
  110. package/src/cli/preparation.ts +163 -0
  111. package/src/cli/presentation.ts +91 -0
  112. package/src/cli/progress.ts +6 -0
  113. package/src/cli/run-manifest.ts +133 -0
  114. package/src/cli/run-options.ts +101 -0
  115. package/src/cli/run-overrides.ts +83 -0
  116. package/src/cli/settings.ts +160 -0
  117. package/src/cli/summary.ts +43 -0
  118. package/src/cli.ts +13 -843
  119. package/src/contract-types.ts +94 -0
  120. package/src/engine.ts +19 -2373
  121. package/src/evaluation/advisory.ts +150 -0
  122. package/src/evaluation/check-plan.ts +510 -0
  123. package/src/evaluation/collection.ts +88 -0
  124. package/src/evaluation/context.ts +10 -0
  125. package/src/evaluation/grading-host.ts +39 -0
  126. package/src/evaluation/grading.ts +258 -0
  127. package/src/evaluation/invocation-plan.ts +548 -0
  128. package/src/evaluation/plan.ts +736 -0
  129. package/src/evaluation/progress.ts +19 -0
  130. package/src/evaluation/resources.ts +241 -0
  131. package/src/evaluation/run.ts +206 -0
  132. package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
  133. package/src/evaluation/semantic.ts +312 -0
  134. package/src/evaluation/trial.ts +866 -0
  135. package/src/evaluation/types.ts +207 -0
  136. package/src/evidence/host-results.ts +92 -0
  137. package/src/evidence/retained-artifacts.ts +18 -0
  138. package/src/evidence/trial-writer.ts +203 -0
  139. package/src/evidence/usage.ts +17 -0
  140. package/src/extension-client.ts +228 -148
  141. package/src/extension-session.ts +326 -195
  142. package/src/fixtures/advisory.ts +338 -0
  143. package/src/fixtures/bin.ts +57 -0
  144. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  145. package/src/fixtures/create.ts +187 -0
  146. package/src/fixtures/generated.ts +300 -0
  147. package/src/fixtures/git-hooks.ts +24 -0
  148. package/src/fixtures/preparation.ts +270 -0
  149. package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
  150. package/src/fixtures/setup.ts +224 -0
  151. package/src/fixtures/tools.ts +88 -0
  152. package/src/fixtures/workspace-state.ts +212 -0
  153. package/src/generated/cli-result.cjs +2 -0
  154. package/src/generated/cli-result.d.cts +3 -0
  155. package/src/generated/extension.cjs +2 -0
  156. package/src/generated/extension.d.cts +3 -0
  157. package/src/generated/report.cjs +2 -0
  158. package/src/generated/report.d.cts +3 -0
  159. package/src/generated/run-evidence.cjs +2 -0
  160. package/src/generated/run-evidence.d.cts +3 -0
  161. package/src/generated/runtime.cjs +2 -0
  162. package/src/generated/runtime.d.cts +3 -0
  163. package/src/graders/artifact.ts +56 -23
  164. package/src/graders/git-head.ts +79 -75
  165. package/src/graders/output.ts +222 -140
  166. package/src/graders/semantic.ts +102 -43
  167. package/src/graders/shell.ts +379 -207
  168. package/src/host-diagnostic-redaction.ts +52 -0
  169. package/src/host-diagnostics.ts +319 -0
  170. package/src/hosts/claude-continuation.ts +63 -18
  171. package/src/hosts/claude-credential.ts +43 -62
  172. package/src/hosts/claude-events.ts +86 -51
  173. package/src/hosts/claude-keychain.ts +59 -0
  174. package/src/hosts/claude-native-goal.ts +57 -33
  175. package/src/hosts/claude-process.ts +111 -0
  176. package/src/hosts/claude-repository-invocation.ts +208 -117
  177. package/src/hosts/claude-settings.ts +240 -21
  178. package/src/hosts/claude-skill-activations.ts +166 -0
  179. package/src/hosts/claude-state.ts +403 -0
  180. package/src/hosts/claude-tool-calls.ts +171 -106
  181. package/src/hosts/claude.ts +561 -451
  182. package/src/hosts/codex-app-server.ts +326 -178
  183. package/src/hosts/codex-configuration.ts +44 -18
  184. package/src/hosts/codex-events.ts +97 -60
  185. package/src/hosts/codex-exec-controls.ts +516 -0
  186. package/src/hosts/codex-invocation.ts +70 -0
  187. package/src/hosts/codex-marketplace.ts +283 -0
  188. package/src/hosts/codex-native-calls.ts +546 -500
  189. package/src/hosts/codex-process.ts +202 -0
  190. package/src/hosts/codex-profile.ts +257 -28
  191. package/src/hosts/codex-session-files.ts +106 -0
  192. package/src/hosts/codex-state.ts +492 -0
  193. package/src/hosts/codex.ts +783 -832
  194. package/src/hosts/isolation-common.ts +94 -0
  195. package/src/hosts/isolation-roots.ts +54 -27
  196. package/src/hosts/isolation.ts +10 -0
  197. package/src/hosts/linux-sandbox.ts +159 -0
  198. package/src/hosts/mac-sandbox.ts +187 -61
  199. package/src/hosts/native-controls.ts +152 -62
  200. package/src/hosts/process-lifecycle.ts +37 -0
  201. package/src/hosts/protected-worktrees.ts +51 -19
  202. package/src/hosts/skill-hook-catalog.ts +131 -0
  203. package/src/hosts/skill-hook-handler.ts +374 -0
  204. package/src/hosts/skill-hooks.ts +580 -0
  205. package/src/hosts/workspace-fingerprint.ts +55 -35
  206. package/src/identity.ts +102 -48
  207. package/src/instrumentation.ts +109 -48
  208. package/src/native-transcript-state.ts +131 -0
  209. package/src/native-transcript-view.ts +90 -0
  210. package/src/native-transcripts.ts +286 -0
  211. package/src/process-lifecycle.ts +150 -0
  212. package/src/provenance.ts +161 -81
  213. package/src/prune.ts +215 -0
  214. package/src/report.ts +172 -140
  215. package/src/resolved-case.ts +33 -0
  216. package/src/results-lock.ts +29 -0
  217. package/src/results.ts +133 -86
  218. package/src/run-owner.ts +154 -63
  219. package/src/runtime/config.ts +456 -0
  220. package/src/runtime/discovery-apple.ts +90 -0
  221. package/src/runtime/discovery-providers.ts +63 -0
  222. package/src/runtime/discovery.ts +70 -0
  223. package/src/runtime/hook-processes.ts +97 -0
  224. package/src/runtime/hooks.ts +476 -0
  225. package/src/runtime/paths.ts +313 -0
  226. package/src/runtime/query.ts +85 -0
  227. package/src/runtime/seeds.ts +134 -0
  228. package/src/runtime/shared-cache.ts +143 -0
  229. package/src/runtime/state.ts +143 -0
  230. package/src/schema-types.ts +198 -0
  231. package/src/schema.ts +15 -19
  232. package/src/transcript-retention.ts +146 -0
  233. package/src/value-guards.ts +20 -0
  234. package/src/advisory-fixture.ts +0 -282
  235. package/src/fixture-bin.ts +0 -87
  236. package/src/fixture-setup.ts +0 -163
  237. package/src/generated-fixture.ts +0 -220
  238. package/src/git-hooks.ts +0 -63
  239. package/src/hosts/claude-nested-skills.ts +0 -218
  240. package/src/hosts/codex-command-output.ts +0 -399
  241. package/src/hosts/codex-executor-command.ts +0 -195
  242. package/src/hosts/codex-skill-reads.ts +0 -391
  243. package/src/preparation.ts +0 -211
@@ -0,0 +1,627 @@
1
+ import { join, resolve } from "node:path";
2
+ import { canonicalRuntimeRoot } from "../runtime/paths";
3
+ import { PATH_OPTIONS, runArguments } from "./run-options";
4
+ import { savedRunValues } from "./run-manifest";
5
+
6
+ export class InvocationError extends Error {}
7
+
8
+ function requiredOption(value: string | undefined, name: string): string {
9
+ if (!value)
10
+ throw new InvocationError(`missing ${name}; supply ${name} with a value`);
11
+ return value;
12
+ }
13
+
14
+ function absoluteOption(value: string | undefined, name: string): string {
15
+ return requiredOption(value, name);
16
+ }
17
+
18
+ function optionalAbsolute(
19
+ value: string | undefined,
20
+ name: string,
21
+ ): string | undefined {
22
+ return value ? absoluteOption(value, name) : undefined;
23
+ }
24
+
25
+ function optionalDigest(value: string | undefined, name: string) {
26
+ if (value !== undefined && !/^[a-f0-9]{64}$/.test(value))
27
+ throw new InvocationError(`${name} must be a 64-character SHA-256 digest`);
28
+ return value;
29
+ }
30
+
31
+ function conditionOption(value: string | undefined): "passive" | "enforced" {
32
+ const condition = requiredOption(value, "--condition");
33
+ if (condition !== "passive" && condition !== "enforced")
34
+ throw new InvocationError("invalid --condition; use passive or enforced");
35
+ return condition;
36
+ }
37
+
38
+ function cliArguments(argv: string[]) {
39
+ try {
40
+ return runArguments(argv);
41
+ } catch (cause) {
42
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
43
+ throw new InvocationError(`invalid CLI arguments${detail}`, { cause });
44
+ }
45
+ }
46
+
47
+ type Values = ReturnType<typeof cliArguments>["values"];
48
+
49
+ function trialParameters(values: Values) {
50
+ const condition = conditionOption(values.condition);
51
+ const trialCount = Number(requiredOption(values.trials, "--trials"));
52
+ const jobs = Number(requiredOption(values.jobs, "--jobs"));
53
+ const passThreshold = Number(requiredOption(values.threshold, "--threshold"));
54
+ positiveInteger(trialCount, "--trials");
55
+ positiveInteger(jobs, "--jobs");
56
+ if (
57
+ !Number.isFinite(passThreshold) ||
58
+ passThreshold <= 0 ||
59
+ passThreshold > 1
60
+ )
61
+ throw new InvocationError(
62
+ "invalid --threshold; supply a number greater than 0 and at most 1",
63
+ );
64
+ return { condition, trialCount, jobs, passThreshold };
65
+ }
66
+
67
+ function positiveInteger(value: number, option: string): void {
68
+ if (!Number.isSafeInteger(value) || value < 1)
69
+ throw new InvocationError(`invalid ${option}; supply a positive integer`);
70
+ }
71
+
72
+ function hostRoutes(values: Values) {
73
+ const codex = values.host === "codex";
74
+ const claude = values.host === "claude";
75
+ return {
76
+ codex,
77
+ claude,
78
+ builtinHost: codex || claude,
79
+ semanticCodex: values["semantic-host"] === "codex",
80
+ advisoryCodex: values["advisory-host"] === "codex",
81
+ };
82
+ }
83
+
84
+ type Routes = ReturnType<typeof hostRoutes>;
85
+ type CompatibilityRule = {
86
+ invalid: (values: Values, routes: Routes, roots: string[]) => unknown;
87
+ message: string;
88
+ };
89
+ const HOST_COMPATIBILITY: CompatibilityRule[] = [
90
+ {
91
+ invalid: (v, r) =>
92
+ v["codex-entrypoint"] !== undefined &&
93
+ (!r.codex || !["exec", "app-server"].includes(v["codex-entrypoint"])),
94
+ message: "--codex-entrypoint requires --host codex and exec or app-server",
95
+ },
96
+ {
97
+ invalid: (v, r) => v.host && !r.builtinHost,
98
+ message: "unsupported --host; use codex or claude",
99
+ },
100
+ {
101
+ invalid: (v, r) => v["semantic-host"] && !r.semanticCodex,
102
+ message: "unsupported --semantic-host; use codex",
103
+ },
104
+ {
105
+ invalid: (v, r) => v["advisory-host"] && !r.advisoryCodex,
106
+ message: "unsupported --advisory-host; use codex",
107
+ },
108
+ {
109
+ invalid: (v, r) => r.semanticCodex && v["semantic-adapter-module"],
110
+ message: "--semantic-host and --semantic-adapter-module are exclusive",
111
+ },
112
+ {
113
+ invalid: (v, r) => r.advisoryCodex && v["advisory-adapter-module"],
114
+ message: "--advisory-host and --advisory-adapter-module are exclusive",
115
+ },
116
+ {
117
+ invalid: (v, r) => r.builtinHost && v["adapter-module"],
118
+ message: "--host and --adapter-module are exclusive",
119
+ },
120
+ {
121
+ invalid: (v, r) => !r.builtinHost && !v["adapter-module"],
122
+ message: "missing --adapter-module or --host",
123
+ },
124
+ {
125
+ invalid: (v, r) => !r.builtinHost && (v.model || v.effort),
126
+ message: "--model and --effort require a built-in host",
127
+ },
128
+ {
129
+ invalid: (v, r) =>
130
+ !r.semanticCodex && (v["semantic-model"] || v["semantic-effort"]),
131
+ message: "semantic model options require --semantic-host codex",
132
+ },
133
+ {
134
+ invalid: (v, r) =>
135
+ !r.advisoryCodex && (v["advisory-model"] || v["advisory-effort"]),
136
+ message: "advisory model options require --advisory-host codex",
137
+ },
138
+ {
139
+ invalid: (v, r) =>
140
+ v["advisory-exclude"]?.length &&
141
+ !r.advisoryCodex &&
142
+ !v["advisory-adapter-module"],
143
+ message: "--advisory-exclude requires an advisory route",
144
+ },
145
+ {
146
+ invalid: (v, r) =>
147
+ !r.codex &&
148
+ !r.semanticCodex &&
149
+ !r.advisoryCodex &&
150
+ [
151
+ v["codex-bin"],
152
+ v["codex-auth-file"],
153
+ v["codex-timeout-ms"] !== undefined,
154
+ ].some(Boolean),
155
+ message: "Codex options require a Codex host route",
156
+ },
157
+ {
158
+ invalid: (v, r) =>
159
+ !r.claude &&
160
+ (v["claude-bin"] ||
161
+ v["claude-credential-file"] ||
162
+ v["claude-uv-cache-dir"] ||
163
+ v["claude-project-settings"]),
164
+ message: "Claude options require --host claude",
165
+ },
166
+ {
167
+ invalid: (v, r, roots) =>
168
+ roots.length &&
169
+ !v["shell-isolation"] &&
170
+ !r.builtinHost &&
171
+ !r.semanticCodex &&
172
+ !r.advisoryCodex,
173
+ message: "--protected-root requires isolation",
174
+ },
175
+ ];
176
+
177
+ function validateHostCompatibility(
178
+ values: Values,
179
+ routes: Routes,
180
+ roots: string[],
181
+ ): void {
182
+ for (const rule of HOST_COMPATIBILITY)
183
+ if (rule.invalid(values, routes, roots))
184
+ throw new InvocationError(rule.message);
185
+ }
186
+
187
+ function rootParameters(values: Values) {
188
+ const projectRoot = absoluteOption(values["project-root"], "--project-root");
189
+ const configRoot =
190
+ values["config-root"] !== undefined
191
+ ? absoluteOption(values["config-root"], "--config-root")
192
+ : projectRoot;
193
+ const resultsRoot = absoluteOption(values["results-root"], "--results-root");
194
+ const runStateRoot = values["run-state-root"]
195
+ ? absoluteOption(values["run-state-root"], "--run-state-root")
196
+ : resultsRoot;
197
+ return { projectRoot, configRoot, resultsRoot, runStateRoot };
198
+ }
199
+
200
+ type Roots = ReturnType<typeof rootParameters>;
201
+
202
+ function validQualifiedId(id: string): boolean {
203
+ return /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/.test(id);
204
+ }
205
+
206
+ function validateExtensionSelection(values: Values): void {
207
+ const policy = values["task-verdict-policy"];
208
+ if (policy !== undefined && !validQualifiedId(policy))
209
+ throw new InvocationError("invalid --task-verdict-policy");
210
+ const replacements = values["replace-builtin-grader"] ?? [];
211
+ validateReplacements(replacements);
212
+ if (
213
+ Boolean(values["case-file"]) === Boolean(values["extension-command-file"])
214
+ )
215
+ throw new InvocationError(
216
+ "select exactly one of --case-file or --extension-command-file",
217
+ );
218
+ validateExtensionArguments(values);
219
+ validatePairedFiles(values);
220
+ }
221
+
222
+ function validateReplacements(replacements: string[]): void {
223
+ if (
224
+ new Set(replacements).size !== replacements.length ||
225
+ replacements.some((id) => !validQualifiedId(id))
226
+ )
227
+ throw new InvocationError("invalid --replace-builtin-grader");
228
+ }
229
+
230
+ function hasExtensionArguments(values: Values): boolean {
231
+ return (
232
+ [
233
+ values["case-id"],
234
+ values["extension-source-file"]?.length,
235
+ values["extension-configuration-file"],
236
+ values["extension-redacted-configuration-file"],
237
+ ].some(Boolean) ||
238
+ values["task-verdict-policy"] !== undefined ||
239
+ (values["replace-builtin-grader"] ?? []).length > 0
240
+ );
241
+ }
242
+
243
+ function extensionSources(values: Values): string[] {
244
+ const sources = values["extension-source-file"];
245
+ if (!sources?.length)
246
+ throw new InvocationError("missing --extension-source-file");
247
+ return sources;
248
+ }
249
+
250
+ function validateExtensionArguments(values: Values): void {
251
+ if (values["extension-command-file"]) {
252
+ requiredOption(values["case-id"], "--case-id");
253
+ extensionSources(values);
254
+ } else if (hasExtensionArguments(values))
255
+ throw new InvocationError(
256
+ "extension options require --extension-command-file",
257
+ );
258
+ }
259
+
260
+ function validatePairedFiles(values: Values): void {
261
+ if (
262
+ Boolean(values["extension-configuration-file"]) !==
263
+ Boolean(values["extension-redacted-configuration-file"])
264
+ )
265
+ throw new InvocationError(
266
+ "extension configuration requires a redacted file",
267
+ );
268
+ if (
269
+ Boolean(values["case-source-root"]) !==
270
+ Boolean(values["case-source-map-file"])
271
+ )
272
+ throw new InvocationError("case sources require a root and map file");
273
+ }
274
+
275
+ function privateRootDeclarations(
276
+ values: Values,
277
+ configRoot: string,
278
+ protectedRoots: string[],
279
+ ): string[] {
280
+ return [
281
+ configRoot,
282
+ ...protectedRoots,
283
+ values["claude-credential-file"],
284
+ values["codex-auth-file"],
285
+ values["runtime-config-file"],
286
+ values["run-manifest-file"],
287
+ values["case-file"],
288
+ values["extension-command-file"],
289
+ ...(values["extension-source-file"] ?? []),
290
+ values["extension-configuration-file"],
291
+ values["extension-redacted-configuration-file"],
292
+ values["case-source-root"],
293
+ values["case-source-map-file"],
294
+ values["semantic-adapter-module"],
295
+ values["advisory-adapter-module"],
296
+ ].filter((value): value is string => Boolean(value));
297
+ }
298
+
299
+ function commonCodexOptions(
300
+ values: Values,
301
+ routes: Routes,
302
+ roots: Roots,
303
+ privateRoots: string[],
304
+ ) {
305
+ if (![routes.codex, routes.semanticCodex, routes.advisoryCodex].some(Boolean))
306
+ return undefined;
307
+ return {
308
+ binary: absoluteOption(values["codex-bin"], "--codex-bin"),
309
+ authFile: absoluteOption(values["codex-auth-file"], "--codex-auth-file"),
310
+ retainHostDiagnostics: values["retain-host-diagnostics"] === true,
311
+ timeoutMs:
312
+ values["codex-timeout-ms"] === undefined
313
+ ? undefined
314
+ : Number(values["codex-timeout-ms"]),
315
+ projectRoot: roots.projectRoot,
316
+ resultsRoot: roots.resultsRoot,
317
+ additionalProtectedRoots: [...privateRoots, roots.runStateRoot],
318
+ };
319
+ }
320
+
321
+ function claudeHostOptions(
322
+ values: Values,
323
+ routes: Routes,
324
+ roots: Roots,
325
+ privateRoots: string[],
326
+ toolchainBinDir: string | undefined,
327
+ ) {
328
+ if (!routes.claude) return undefined;
329
+ return {
330
+ retainHostDiagnostics: values["retain-host-diagnostics"] === true,
331
+ binary: absoluteOption(values["claude-bin"], "--claude-bin"),
332
+ ...(values["claude-credential-file"]
333
+ ? {
334
+ credentialFile: absoluteOption(
335
+ values["claude-credential-file"],
336
+ "--claude-credential-file",
337
+ ),
338
+ }
339
+ : {}),
340
+ ...(values["claude-uv-cache-dir"]
341
+ ? {
342
+ uvCacheDir: absoluteOption(
343
+ values["claude-uv-cache-dir"],
344
+ "--claude-uv-cache-dir",
345
+ ),
346
+ }
347
+ : {}),
348
+ model: requiredOption(values.model, "--model"),
349
+ effort: requiredOption(values.effort, "--effort"),
350
+ toolchainBinDir,
351
+ projectSettings: values["claude-project-settings"] ?? false,
352
+ projectRoot: roots.projectRoot,
353
+ resultsRoot: roots.resultsRoot,
354
+ additionalProtectedRoots: [...privateRoots, roots.runStateRoot],
355
+ };
356
+ }
357
+
358
+ function extensionInvocation(values: Values) {
359
+ const command = values["extension-command-file"];
360
+ if (!command) return undefined;
361
+ return {
362
+ commandFile: absoluteOption(command, "--extension-command-file"),
363
+ sourceFiles: extensionSources(values),
364
+ caseId: requiredOption(values["case-id"], "--case-id"),
365
+ configurationFile: optionalAbsolute(
366
+ values["extension-configuration-file"],
367
+ "--extension-configuration-file",
368
+ ),
369
+ redactedConfigurationFile: optionalAbsolute(
370
+ values["extension-redacted-configuration-file"],
371
+ "--extension-redacted-configuration-file",
372
+ ),
373
+ taskVerdictPolicy: values["task-verdict-policy"],
374
+ replaceBuiltinGraders: values["replace-builtin-grader"] ?? [],
375
+ };
376
+ }
377
+
378
+ const CODEX_MODEL_KEYS = {
379
+ candidate: ["model", "effort"],
380
+ semantic: ["semantic-model", "semantic-effort"],
381
+ advisory: ["advisory-model", "advisory-effort"],
382
+ } as const;
383
+
384
+ function codexRoleOptions(
385
+ values: Values,
386
+ common: ReturnType<typeof commonCodexOptions>,
387
+ enabled: boolean,
388
+ role: keyof typeof CODEX_MODEL_KEYS,
389
+ ) {
390
+ if (!enabled) return undefined;
391
+ if (!common)
392
+ throw new InvocationError("Codex options require a Codex host route");
393
+ const [modelKey, effortKey] = CODEX_MODEL_KEYS[role];
394
+ return {
395
+ ...(role === "candidate"
396
+ ? { entrypoint: codexEntrypoint(values["codex-entrypoint"]) }
397
+ : {}),
398
+ ...common,
399
+ model: requiredOption(values[modelKey], `--${modelKey}`),
400
+ effort: requiredOption(values[effortKey], `--${effortKey}`),
401
+ };
402
+ }
403
+
404
+ function codexEntrypoint(
405
+ value: string | undefined,
406
+ ): "exec" | "app-server" | undefined {
407
+ if (value === undefined) return undefined;
408
+ if (value !== "exec" && value !== "app-server")
409
+ throw new InvocationError(
410
+ "--codex-entrypoint requires --host codex and exec or app-server",
411
+ );
412
+ return value;
413
+ }
414
+
415
+ function invocationInputs(
416
+ values: Values,
417
+ context: ReturnType<typeof invocationContext>,
418
+ ) {
419
+ const {
420
+ routes,
421
+ roots,
422
+ privateRoots,
423
+ toolchainBinDir,
424
+ codexCommon,
425
+ claudeOptions,
426
+ trials,
427
+ } = context;
428
+ return {
429
+ json: values.json ?? false,
430
+ runManifestFile: values["run-manifest-file"],
431
+ protectedRoots: context.protectedRoots,
432
+ deprecatedRuntimeOptions: (
433
+ ["toolchain-bin-dir", "claude-uv-cache-dir"] as const
434
+ ).filter((name) => values[name] !== undefined),
435
+ dry: values.dry ?? false,
436
+ runtimeConfigFile: optionalAbsolute(
437
+ values["runtime-config-file"],
438
+ "--runtime-config-file",
439
+ ),
440
+ caseFile: optionalAbsolute(values["case-file"], "--case-file"),
441
+ extension: extensionInvocation(values),
442
+ caseSourceRoot: optionalAbsolute(
443
+ values["case-source-root"],
444
+ "--case-source-root",
445
+ ),
446
+ caseSourceMapFile: optionalAbsolute(
447
+ values["case-source-map-file"],
448
+ "--case-source-map-file",
449
+ ),
450
+ adapterModule: routes.builtinHost
451
+ ? undefined
452
+ : absoluteOption(values["adapter-module"], "--adapter-module"),
453
+ semanticAdapterModule: optionalAbsolute(
454
+ values["semantic-adapter-module"],
455
+ "--semantic-adapter-module",
456
+ ),
457
+ semanticCodex: codexRoleOptions(
458
+ values,
459
+ codexCommon,
460
+ routes.semanticCodex,
461
+ "semantic",
462
+ ),
463
+ advisoryAdapterModule: optionalAbsolute(
464
+ values["advisory-adapter-module"],
465
+ "--advisory-adapter-module",
466
+ ),
467
+ advisoryCodex: codexRoleOptions(
468
+ values,
469
+ codexCommon,
470
+ routes.advisoryCodex,
471
+ "advisory",
472
+ ),
473
+ advisoryExcludedPaths: values["advisory-exclude"] ?? [],
474
+ codex: codexRoleOptions(values, codexCommon, routes.codex, "candidate"),
475
+ claude: claudeOptions,
476
+ shellIsolation: shellIsolationOptions(
477
+ values,
478
+ privateRoots,
479
+ toolchainBinDir,
480
+ ),
481
+ ...roots,
482
+ runnerBuildDigest: optionalDigest(
483
+ values["runner-build-digest"],
484
+ "--runner-build-digest",
485
+ ),
486
+ runnerCheckoutRoot: optionalAbsolute(
487
+ values["runner-checkout-root"],
488
+ "--runner-checkout-root",
489
+ ),
490
+ projectDigest: optionalDigest(values["project-digest"], "--project-digest"),
491
+ ...trials,
492
+ };
493
+ }
494
+
495
+ function shellIsolationOptions(
496
+ values: Values,
497
+ protectedRoots: string[],
498
+ toolchainBinDir: string | undefined,
499
+ ) {
500
+ return values["shell-isolation"]
501
+ ? {
502
+ protectedRoots,
503
+ toolchainBinDir,
504
+ uvRuntimeCache: !!values["claude-uv-cache-dir"],
505
+ }
506
+ : undefined;
507
+ }
508
+
509
+ function invocationContext(values: Values) {
510
+ const trials = trialParameters(values);
511
+ const protectedRoots = values["protected-root"] ?? [];
512
+ const toolchainBinDir = optionalAbsolute(
513
+ values["toolchain-bin-dir"],
514
+ "--toolchain-bin-dir",
515
+ );
516
+ const routes = hostRoutes(values);
517
+ validateHostCompatibility(values, routes, protectedRoots);
518
+ const roots = rootParameters(values);
519
+ validateExtensionSelection(values);
520
+ const privateRoots = privateRootDeclarations(
521
+ values,
522
+ roots.configRoot,
523
+ protectedRoots,
524
+ );
525
+ const codexCommon = commonCodexOptions(values, routes, roots, privateRoots);
526
+ const claudeOptions = claudeHostOptions(
527
+ values,
528
+ routes,
529
+ roots,
530
+ privateRoots,
531
+ toolchainBinDir,
532
+ );
533
+ return {
534
+ routes,
535
+ roots,
536
+ protectedRoots,
537
+ privateRoots,
538
+ toolchainBinDir,
539
+ codexCommon,
540
+ claudeOptions,
541
+ trials,
542
+ };
543
+ }
544
+
545
+ /** Decode CLI flags in their documented validation order. */
546
+ export async function parseInvocation(argv: string[]) {
547
+ const parsed = cliArguments(argv);
548
+ if (parsed.positionals.length !== 1 || parsed.positionals[0] !== "run")
549
+ throw new InvocationError("expected the run command");
550
+ parsed.values = await savedRunValues(parsed.values);
551
+ applyRunDefaults(parsed.values);
552
+ validateHostCompatibility(
553
+ parsed.values,
554
+ hostRoutes(parsed.values),
555
+ parsed.values["protected-root"] ?? [],
556
+ );
557
+ applyRootDefaults(parsed.values);
558
+ resolveNativeBinaries(parsed.values);
559
+ await canonicalizePaths(parsed.values);
560
+ return invocationInputs(parsed.values, invocationContext(parsed.values));
561
+ }
562
+
563
+ function resolveNativeBinaries(values: Values): void {
564
+ const routes = hostRoutes(values);
565
+ if ([routes.codex, routes.semanticCodex, routes.advisoryCodex].some(Boolean))
566
+ values["codex-bin"] ??= nativeBinary("codex");
567
+ if (routes.claude) values["claude-bin"] ??= nativeBinary("claude");
568
+ }
569
+
570
+ function nativeBinary(host: "codex" | "claude"): string {
571
+ const binary = Bun.which(host);
572
+ if (!binary)
573
+ throw new InvocationError(
574
+ `selected ${host} executable was not found on PATH; install ${host} or supply --${host}-bin <path>`,
575
+ );
576
+ return binary;
577
+ }
578
+
579
+ export type Invocation = Awaited<ReturnType<typeof parseInvocation>>;
580
+
581
+ function applyRunDefaults(values: Values): void {
582
+ values.condition ??= "passive";
583
+ values.trials ??= "1";
584
+ values.jobs ??= "3";
585
+ values.threshold ??= "1";
586
+ }
587
+
588
+ function applyRootDefaults(values: Values): void {
589
+ values["project-root"] ??= process.cwd();
590
+ values["results-root"] ??= join(
591
+ resolve(values["project-root"]),
592
+ ".sevro",
593
+ "results",
594
+ );
595
+ }
596
+
597
+ async function canonicalOption(path: string, option: string): Promise<string> {
598
+ try {
599
+ return await canonicalRuntimeRoot(resolve(path));
600
+ } catch (cause) {
601
+ const error = cause as NodeJS.ErrnoException;
602
+ throw new InvocationError(
603
+ `--${option} cannot be resolved at ${path}: filesystem ${error.syscall ?? "realpath"} failed (${error.code ?? "unknown"}); supply a usable file or directory path`,
604
+ { cause },
605
+ );
606
+ }
607
+ }
608
+
609
+ async function canonicalizePaths(values: Values): Promise<void> {
610
+ for (const name of PATH_OPTIONS) {
611
+ const value = values[name];
612
+ if (value !== undefined)
613
+ values[name] = await canonicalOption(
614
+ requiredOption(value, `--${name}`),
615
+ name,
616
+ );
617
+ }
618
+ for (const name of ["extension-source-file", "protected-root"] as const) {
619
+ const paths = values[name];
620
+ if (paths)
621
+ values[name] = await Promise.all(
622
+ paths.map((path) =>
623
+ canonicalOption(requiredOption(path, `--${name}`), name),
624
+ ),
625
+ );
626
+ }
627
+ }