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