@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,151 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { isAbsolute } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { isResolvedCase, resolvedCaseIssue } from "../case-validation";
5
+ import type { HostAdapter, ResolvedCase } from "../engine";
6
+ import { prepareInstrumentation } from "../instrumentation";
7
+ import { isRecord, isStringArray } from "../value-guards";
8
+ import { InvocationError, type Invocation } from "./invocation";
9
+
10
+ export function parseCase(value: unknown): ResolvedCase {
11
+ if (!isResolvedCase(value))
12
+ throw new InvocationError(
13
+ `invalid resolved case file: ${resolvedCaseIssue(value) ?? "invalid case"}`,
14
+ );
15
+ return value;
16
+ }
17
+
18
+ export async function loadCase(path: string): Promise<ResolvedCase> {
19
+ try {
20
+ return parseCase(JSON.parse(await readFile(path, "utf8")));
21
+ } catch (cause) {
22
+ const detail = cause instanceof InvocationError ? `: ${cause.message}` : "";
23
+ throw new InvocationError(
24
+ `resolved case file is unreadable or invalid at ${path}${detail}; supply --case-file with readable JSON and correct the named field`,
25
+ {
26
+ cause,
27
+ },
28
+ );
29
+ }
30
+ }
31
+
32
+ async function loadJson(path: string, label: string): Promise<unknown> {
33
+ try {
34
+ return JSON.parse(await readFile(path, "utf8"));
35
+ } catch (cause) {
36
+ throw new InvocationError(`${label} is unreadable or invalid`, { cause });
37
+ }
38
+ }
39
+
40
+ function validExtensionCommand(value: unknown): value is string[] {
41
+ return (
42
+ isStringArray(value) &&
43
+ value.length > 0 &&
44
+ value.every((part) => part.length > 0) &&
45
+ isAbsolute(value[0] ?? "")
46
+ );
47
+ }
48
+
49
+ async function loadExtensionCommand(path: string): Promise<string[]> {
50
+ const command = await loadJson(path, "extension command file");
51
+ if (!validExtensionCommand(command))
52
+ throw new InvocationError(
53
+ "extension command must be an absolute argv array",
54
+ );
55
+ return command;
56
+ }
57
+
58
+ export async function loadExtensionOptions(
59
+ selected: NonNullable<Invocation["extension"]>,
60
+ ) {
61
+ const command = await loadExtensionCommand(selected.commandFile);
62
+ const configuration = selected.configurationFile
63
+ ? await loadJson(selected.configurationFile, "extension configuration file")
64
+ : {};
65
+ const redactedConfiguration = selected.redactedConfigurationFile
66
+ ? await loadJson(
67
+ selected.redactedConfigurationFile,
68
+ "redacted extension configuration file",
69
+ )
70
+ : {};
71
+ if (!isRecord(configuration) || !isRecord(redactedConfiguration))
72
+ throw new InvocationError("extension configuration must be JSON objects");
73
+ return { command, configuration, redactedConfiguration };
74
+ }
75
+
76
+ export async function loadPreparationSources(selected: Invocation) {
77
+ if (!selected.caseSourceRoot || !selected.caseSourceMapFile) return undefined;
78
+ const refs = await loadJson(
79
+ selected.caseSourceMapFile,
80
+ "case source map file",
81
+ );
82
+ if (
83
+ !isRecord(refs) ||
84
+ !Object.values(refs).every(
85
+ (value) => typeof value === "string" && value.startsWith("file:///"),
86
+ )
87
+ )
88
+ throw new InvocationError("case source map must contain file URLs");
89
+ return {
90
+ root: selected.caseSourceRoot,
91
+ refs: refs as Record<string, string>,
92
+ };
93
+ }
94
+
95
+ async function adapterModule(path: string): Promise<unknown> {
96
+ try {
97
+ return await import(pathToFileURL(path).href);
98
+ } catch (cause) {
99
+ throw new InvocationError(
100
+ `host adapter module could not be loaded at ${path}; check that the module exists and imports successfully, then correct --adapter-module or the selected grading adapter option`,
101
+ { cause },
102
+ );
103
+ }
104
+ }
105
+
106
+ export async function loadHost(path: string): Promise<HostAdapter> {
107
+ const module = await adapterModule(path);
108
+ const host = isRecord(module) ? module.default : null;
109
+ if (!validHostAdapter(host))
110
+ throw new InvocationError(
111
+ `host adapter module has no valid default adapter at ${path}; export a default adapter with string id, model, effort and a run function`,
112
+ );
113
+ validateHostInstrumentation(host);
114
+ validateHostCapabilities(host.hostCapabilities);
115
+ return host as unknown as HostAdapter;
116
+ }
117
+
118
+ function validateHostInstrumentation(host: Record<string, unknown>): void {
119
+ try {
120
+ prepareInstrumentation(
121
+ [],
122
+ (host.instrumentation as HostAdapter["instrumentation"]) ?? [],
123
+ [],
124
+ "passive",
125
+ );
126
+ } catch (cause) {
127
+ throw new InvocationError("host adapter instrumentation is invalid", {
128
+ cause,
129
+ });
130
+ }
131
+ }
132
+
133
+ function validHostAdapter(value: unknown): value is Record<string, unknown> {
134
+ return (
135
+ isRecord(value) &&
136
+ typeof value.id === "string" &&
137
+ typeof value.model === "string" &&
138
+ typeof value.effort === "string" &&
139
+ typeof value.run === "function"
140
+ );
141
+ }
142
+
143
+ function validateHostCapabilities(value: unknown): void {
144
+ if (value === undefined) return;
145
+ if (
146
+ !isStringArray(value) ||
147
+ value.some((id) => !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/.test(id)) ||
148
+ new Set(value).size !== value.length
149
+ )
150
+ throw new InvocationError("host adapter capabilities are invalid");
151
+ }
@@ -0,0 +1,163 @@
1
+ import { join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { configurationRoot } from "../hosts/codex-configuration";
4
+ import {
5
+ InvocationError,
6
+ parseInvocation,
7
+ type Invocation,
8
+ } from "./invocation";
9
+ import {
10
+ loadRuntimeConfiguration,
11
+ runtimeNativeGoalEnabled,
12
+ } from "../runtime/config";
13
+ import {
14
+ canonicalRuntimeRoot,
15
+ requireRuntimeReadRoots,
16
+ } from "../runtime/paths";
17
+ import { loadCase, loadPreparationSources } from "./loading";
18
+ import { candidateHost, secondaryHost, selectedConcurrency } from "./hosts";
19
+
20
+ type PreparationSources = Awaited<ReturnType<typeof loadPreparationSources>>;
21
+
22
+ function protectMappedSources(
23
+ invocation: Invocation,
24
+ preparationSources: PreparationSources,
25
+ ): void {
26
+ const sourceRoots = mappedSourceRoots(preparationSources);
27
+ for (const native of [
28
+ invocation.codex,
29
+ invocation.claude,
30
+ invocation.semanticCodex,
31
+ invocation.advisoryCodex,
32
+ ]) {
33
+ if (native)
34
+ native.additionalProtectedRoots = [
35
+ ...native.additionalProtectedRoots,
36
+ ...sourceRoots,
37
+ ];
38
+ }
39
+ invocation.shellIsolation?.protectedRoots.push(...sourceRoots);
40
+ }
41
+
42
+ export async function prepareRunInvocation(argv: string[]) {
43
+ const invocation = await parseInvocation(argv);
44
+ warnDeprecatedRuntimeOptions(invocation);
45
+ const preparationSources = await loadPreparationSources(invocation);
46
+ protectMappedSources(invocation, preparationSources);
47
+ const runtimePolicy = await loadRuntimeConfiguration(
48
+ invocation.projectRoot,
49
+ invocation.runtimeConfigFile,
50
+ await invocationProtectedRuntimeRoots(invocation),
51
+ );
52
+ requireRuntimeOptionCompatibility(invocation, runtimePolicy);
53
+ selectRuntimeTransport(invocation, runtimePolicy);
54
+ const configRoot = await configurationRoot(invocation.configRoot);
55
+ await validateInvocationRuntime(invocation, runtimePolicy);
56
+ const agentConcurrencyLimit = await selectedConcurrency(
57
+ invocation,
58
+ configRoot,
59
+ );
60
+ const caseData = invocation.caseFile
61
+ ? await loadCase(invocation.caseFile)
62
+ : undefined;
63
+ const host = await candidateHost(invocation, agentConcurrencyLimit);
64
+ const semanticHost = await secondaryHost(
65
+ invocation.semanticAdapterModule,
66
+ invocation.semanticCodex,
67
+ agentConcurrencyLimit,
68
+ );
69
+ const advisoryHost = await secondaryHost(
70
+ invocation.advisoryAdapterModule,
71
+ invocation.advisoryCodex,
72
+ agentConcurrencyLimit,
73
+ );
74
+ return {
75
+ invocation,
76
+ runtimePolicy,
77
+ preparationSources,
78
+ caseData,
79
+ host,
80
+ semanticHost,
81
+ advisoryHost,
82
+ agentConcurrencyLimit,
83
+ };
84
+ }
85
+
86
+ function selectRuntimeTransport(
87
+ invocation: Invocation,
88
+ policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
89
+ ): void {
90
+ if (!runtimeNativeGoalEnabled(policy)) return;
91
+ if (invocation.codex) selectCodexGoalTransport(invocation.codex);
92
+ }
93
+
94
+ function selectCodexGoalTransport(
95
+ codex: NonNullable<Invocation["codex"]>,
96
+ ): void {
97
+ if (codex.entrypoint === "exec")
98
+ throw new InvocationError(
99
+ "native-goal runtime policy requires Codex app-server",
100
+ );
101
+ codex.entrypoint = "app-server";
102
+ }
103
+
104
+ function warnDeprecatedRuntimeOptions(invocation: Invocation): void {
105
+ for (const option of invocation.deprecatedRuntimeOptions)
106
+ console.warn(
107
+ `--${option} is deprecated; declare the runtime in sevro.json or --runtime-config-file instead`,
108
+ );
109
+ }
110
+
111
+ function requireRuntimeOptionCompatibility(
112
+ invocation: Invocation,
113
+ policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
114
+ ): void {
115
+ if (policy && invocation.deprecatedRuntimeOptions.length)
116
+ throw new InvocationError(
117
+ "runtime configuration cannot be combined with deprecated runtime options",
118
+ );
119
+ }
120
+
121
+ export type PreparedInvocation = Awaited<
122
+ ReturnType<typeof prepareRunInvocation>
123
+ >;
124
+
125
+ async function validateInvocationRuntime(
126
+ invocation: Invocation,
127
+ policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
128
+ ): Promise<void> {
129
+ if (!policy) return;
130
+ const protectedRoots = await invocationProtectedRuntimeRoots(invocation);
131
+ requireRuntimeReadRoots(policy.readOnlyRoots, protectedRoots);
132
+ requireRuntimeReadRoots(
133
+ (policy.seeds ?? []).map((seed) => seed.source),
134
+ protectedRoots,
135
+ );
136
+ }
137
+
138
+ async function invocationProtectedRuntimeRoots(
139
+ invocation: Invocation,
140
+ ): Promise<string[]> {
141
+ const native = [
142
+ invocation.codex,
143
+ invocation.claude,
144
+ invocation.semanticCodex,
145
+ invocation.advisoryCodex,
146
+ ];
147
+ const protectedRoots = [
148
+ join(import.meta.dir, "..", ".."),
149
+ invocation.projectRoot,
150
+ invocation.configRoot,
151
+ invocation.resultsRoot,
152
+ invocation.runStateRoot,
153
+ ...native.flatMap((host) => host?.additionalProtectedRoots ?? []),
154
+ ...(invocation.shellIsolation?.protectedRoots ?? []),
155
+ ];
156
+ return Promise.all(protectedRoots.map(canonicalRuntimeRoot));
157
+ }
158
+
159
+ function mappedSourceRoots(preparationSources: PreparationSources): string[] {
160
+ return Object.values(preparationSources?.refs ?? {}).map((url) =>
161
+ fileURLToPath(url),
162
+ );
163
+ }
@@ -0,0 +1,91 @@
1
+ import { EvaluationConfigurationError } from "../engine";
2
+ import { InvocationError } from "./invocation";
3
+ import { assertCliResult } from "../schema";
4
+ import type { ReportData } from "../schema-types";
5
+ import { displayMeasurements, displayTrialSummary } from "./summary";
6
+
7
+ function failure(code: 64 | 70, message: string) {
8
+ const result = {
9
+ format: "sevro.cli-result.v1",
10
+ runId: null,
11
+ execution: { status: "not_run" },
12
+ grading: { status: "not_requested" },
13
+ task: { verdict: "not_assessed" },
14
+ exitCode: code,
15
+ evidencePath: null,
16
+ cases: [],
17
+ diagnostic: {
18
+ code: code === 64 ? "sevro.invocation.invalid" : "sevro.runner.error",
19
+ message: message.slice(0, 4096),
20
+ },
21
+ };
22
+ assertCliResult(result);
23
+ return result;
24
+ }
25
+
26
+ export function display(
27
+ result: unknown,
28
+ json: boolean,
29
+ completion?: { resultFile: string; report: ReportData },
30
+ ): void {
31
+ assertCliResult(result);
32
+ if (result.diagnostic)
33
+ process.stderr.write(`sevro: ${result.diagnostic.message}\n`);
34
+ if (json) {
35
+ process.stdout.write(`${JSON.stringify(result)}\n`);
36
+ return;
37
+ }
38
+ process.stdout.write(
39
+ `execution=${result.execution.status} grading=${result.grading.status} task=${result.task.verdict}\n`,
40
+ );
41
+ displayTrialSummary(result);
42
+ displayDomainOutcomes(result.cases);
43
+ if (completion) {
44
+ displayMeasurements(completion.report);
45
+ process.stdout.write(`result=${completion.resultFile}\n`);
46
+ }
47
+ if (result.evidencePath)
48
+ process.stdout.write(`evidence=${result.evidencePath}\n`);
49
+ }
50
+
51
+ function displayDomainOutcomes(
52
+ cases: Array<{
53
+ caseId: string;
54
+ trials: Array<{
55
+ trial: number;
56
+ domainOutcomes?: Array<{ id: string; status: string }>;
57
+ }>;
58
+ }>,
59
+ ): void {
60
+ for (const selected of cases)
61
+ for (const trial of selected.trials)
62
+ for (const outcome of trial.domainOutcomes ?? [])
63
+ process.stdout.write(
64
+ `domain case=${selected.caseId} trial=${trial.trial} outcome=${outcome.id} status=${outcome.status}\n`,
65
+ );
66
+ }
67
+
68
+ export function runnerFailureCategory(error: unknown): 64 | 70 {
69
+ return error instanceof EvaluationConfigurationError ||
70
+ error instanceof InvocationError
71
+ ? 64
72
+ : 70;
73
+ }
74
+
75
+ export function displayFailure(
76
+ error: unknown,
77
+ json: boolean,
78
+ code: 64 | 70,
79
+ fallback: string,
80
+ ): void {
81
+ display(
82
+ failure(code, error instanceof Error ? error.message : fallback),
83
+ json,
84
+ );
85
+ process.stderr.write(
86
+ code === 64
87
+ ? "Correct the named option or input and retry. Run sevro run --help for examples and required options.\n"
88
+ : "Check the reported path and ensure --results-root and --run-state-root are writable directories, then retry.\n",
89
+ );
90
+ process.exitCode = code;
91
+ }
@@ -0,0 +1,6 @@
1
+ import type { EvaluationProgress } from "../evaluation/types";
2
+
3
+ export function displayProgress(event: EvaluationProgress): void {
4
+ const trial = event.trial === undefined ? "" : ` trial=${event.trial}`;
5
+ process.stderr.write(`sevro: ${event.stage} ${event.status}${trial}\n`);
6
+ }
@@ -0,0 +1,133 @@
1
+ import { lstat, readFile, realpath } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { isRecord, isStringArray } from "../value-guards";
4
+ import { PATH_OPTIONS, RUN_OPTIONS, type RunValues } from "./run-options";
5
+ import { overrideRunSelections } from "./run-overrides";
6
+
7
+ const NUMBER_OPTIONS = new Set([
8
+ "trials",
9
+ "jobs",
10
+ "threshold",
11
+ "codex-timeout-ms",
12
+ ]);
13
+ const CLI_ONLY = new Set(["json", "run-manifest-file"]);
14
+ const LIST_OPTIONS = new Set([
15
+ "extension-source-file",
16
+ "replace-builtin-grader",
17
+ "advisory-exclude",
18
+ "protected-root",
19
+ ]);
20
+ const FILE_LIMIT = 64 * 1024;
21
+
22
+ function fieldError(field: string, requirement: string): never {
23
+ throw new Error(`run manifest ${field}: ${requirement}`);
24
+ }
25
+
26
+ function manifestSettings(value: unknown): Record<string, unknown> {
27
+ if (!isRecord(value)) fieldError("$", "expected an object");
28
+ validateManifestFields(value);
29
+ if (value.format !== "sevro.run.v1")
30
+ fieldError("format", 'expected "sevro.run.v1"');
31
+ if (!isRecord(value.settings))
32
+ fieldError("settings", "expected an object of run options");
33
+ return value.settings;
34
+ }
35
+
36
+ function validateManifestFields(value: Record<string, unknown>): void {
37
+ for (const key of Object.keys(value))
38
+ if (!["format", "settings"].includes(key))
39
+ fieldError(key, "unknown field; use format and settings");
40
+ }
41
+
42
+ function finiteNumber(value: unknown): boolean {
43
+ return typeof value === "number" && Number.isFinite(value);
44
+ }
45
+
46
+ function nonemptyString(value: unknown): boolean {
47
+ return typeof value === "string" && Boolean(value.trim());
48
+ }
49
+
50
+ function nonemptyStringList(value: unknown): boolean {
51
+ return isStringArray(value) && value.every(nonemptyString);
52
+ }
53
+
54
+ function validateSetting(name: string, value: unknown): void {
55
+ if (!Object.hasOwn(RUN_OPTIONS, name) || CLI_ONLY.has(name))
56
+ fieldError(
57
+ `settings.${name}`,
58
+ "unknown saved option; see sevro run --help",
59
+ );
60
+ if (NUMBER_OPTIONS.has(name)) {
61
+ if (!finiteNumber(value))
62
+ fieldError(`settings.${name}`, "expected a finite number");
63
+ return;
64
+ }
65
+ validateOrdinarySetting(name, value);
66
+ }
67
+
68
+ function validateOrdinarySetting(name: string, value: unknown): void {
69
+ if (LIST_OPTIONS.has(name)) {
70
+ if (!nonemptyStringList(value))
71
+ fieldError(`settings.${name}`, "expected an array of nonempty strings");
72
+ return;
73
+ }
74
+ const option = RUN_OPTIONS[name as keyof typeof RUN_OPTIONS];
75
+ validateScalar(name, value, option.type);
76
+ }
77
+
78
+ function validateScalar(name: string, value: unknown, type: string): void {
79
+ if (type === "boolean") {
80
+ if (typeof value !== "boolean")
81
+ fieldError(`settings.${name}`, "expected a boolean");
82
+ } else if (!nonemptyString(value))
83
+ fieldError(`settings.${name}`, "expected a nonempty string");
84
+ }
85
+
86
+ function resolvedSetting(name: string, value: unknown, base: string) {
87
+ const pathOption = (PATH_OPTIONS as readonly string[]).includes(name);
88
+ if (pathOption) return resolve(base, value as string);
89
+ if (["extension-source-file", "protected-root"].includes(name))
90
+ return (value as string[]).map((path) => resolve(base, path));
91
+ return NUMBER_OPTIONS.has(name) ? String(value) : value;
92
+ }
93
+
94
+ async function readManifest(path: string): Promise<RunValues> {
95
+ const stat = await lstat(path);
96
+ if (!stat.isFile() || stat.size > FILE_LIMIT)
97
+ fieldError("$", "use a regular, non-symlink JSON file of at most 64 KiB");
98
+ const source = await readFile(path, "utf8");
99
+ let value: unknown;
100
+ try {
101
+ value = JSON.parse(source);
102
+ } catch (cause) {
103
+ throw new Error("run manifest $: invalid JSON; check the JSON syntax", {
104
+ cause,
105
+ });
106
+ }
107
+ const settings = manifestSettings(value);
108
+ const base = dirname(await realpath(path));
109
+ const resolved: Record<string, unknown> = {};
110
+ for (const [name, setting] of Object.entries(settings)) {
111
+ validateSetting(name, setting);
112
+ resolved[name] = resolvedSetting(name, setting, base);
113
+ }
114
+ return resolved;
115
+ }
116
+
117
+ /** Explicit flags replace saved selections and lists; defaults are applied later. */
118
+ export async function savedRunValues(explicit: RunValues): Promise<RunValues> {
119
+ const file = explicit["run-manifest-file"];
120
+ if (!file) return explicit;
121
+ const path = resolve(file);
122
+ try {
123
+ const saved = await readManifest(path);
124
+ overrideRunSelections(saved, explicit);
125
+ return { ...saved, ...explicit };
126
+ } catch (cause) {
127
+ const detail = cause instanceof Error ? cause.message : "cannot read file";
128
+ throw new Error(
129
+ `--run-manifest-file ${path}: ${detail}; correct the named field or select a readable manifest`,
130
+ { cause },
131
+ );
132
+ }
133
+ }
@@ -0,0 +1,101 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ export const RUN_OPTIONS = {
4
+ json: { type: "boolean" },
5
+ "run-manifest-file": { type: "string" },
6
+ "retain-host-diagnostics": { type: "boolean" },
7
+ dry: { type: "boolean" },
8
+ "case-file": { type: "string" },
9
+ "case-id": { type: "string" },
10
+ "extension-command-file": { type: "string" },
11
+ "extension-source-file": { type: "string", multiple: true },
12
+ "extension-configuration-file": { type: "string" },
13
+ "extension-redacted-configuration-file": { type: "string" },
14
+ "task-verdict-policy": { type: "string" },
15
+ "replace-builtin-grader": { type: "string", multiple: true },
16
+ "case-source-root": { type: "string" },
17
+ "case-source-map-file": { type: "string" },
18
+ "adapter-module": { type: "string" },
19
+ "semantic-adapter-module": { type: "string" },
20
+ "semantic-host": { type: "string" },
21
+ "semantic-model": { type: "string" },
22
+ "semantic-effort": { type: "string" },
23
+ "advisory-adapter-module": { type: "string" },
24
+ "advisory-host": { type: "string" },
25
+ "advisory-model": { type: "string" },
26
+ "advisory-effort": { type: "string" },
27
+ "advisory-exclude": { type: "string", multiple: true },
28
+ host: { type: "string" },
29
+ "codex-bin": { type: "string" },
30
+ "codex-entrypoint": { type: "string" },
31
+ "codex-timeout-ms": { type: "string" },
32
+ "codex-auth-file": { type: "string" },
33
+ "claude-bin": { type: "string" },
34
+ "claude-credential-file": { type: "string" },
35
+ "claude-uv-cache-dir": { type: "string" },
36
+ "claude-project-settings": { type: "boolean" },
37
+ "toolchain-bin-dir": { type: "string" },
38
+ model: { type: "string" },
39
+ effort: { type: "string" },
40
+ "shell-isolation": { type: "boolean" },
41
+ "protected-root": { type: "string", multiple: true },
42
+ "project-root": { type: "string" },
43
+ "config-root": { type: "string" },
44
+ "runtime-config-file": { type: "string" },
45
+ "results-root": { type: "string" },
46
+ "run-state-root": { type: "string" },
47
+ "runner-build-digest": { type: "string" },
48
+ "runner-checkout-root": { type: "string" },
49
+ "project-digest": { type: "string" },
50
+ condition: { type: "string" },
51
+ trials: { type: "string" },
52
+ jobs: { type: "string" },
53
+ threshold: { type: "string" },
54
+ } as const;
55
+
56
+ export const PATH_OPTIONS = [
57
+ "run-manifest-file",
58
+ "case-file",
59
+ "extension-command-file",
60
+ "extension-configuration-file",
61
+ "extension-redacted-configuration-file",
62
+ "case-source-root",
63
+ "case-source-map-file",
64
+ "adapter-module",
65
+ "semantic-adapter-module",
66
+ "advisory-adapter-module",
67
+ "codex-bin",
68
+ "codex-auth-file",
69
+ "claude-bin",
70
+ "claude-credential-file",
71
+ "claude-uv-cache-dir",
72
+ "toolchain-bin-dir",
73
+ "project-root",
74
+ "config-root",
75
+ "runtime-config-file",
76
+ "results-root",
77
+ "run-state-root",
78
+ "runner-checkout-root",
79
+ ] as const;
80
+
81
+ export function runArguments(argv: string[]) {
82
+ return parseArgs({
83
+ args: argv,
84
+ allowPositionals: true,
85
+ allowNegative: true,
86
+ strict: true,
87
+ options: RUN_OPTIONS,
88
+ });
89
+ }
90
+
91
+ export type RunValues = ReturnType<typeof runArguments>["values"];
92
+
93
+ /** Preserve the last explicit output selection even when parsing fails. */
94
+ export function jsonOutputRequested(argv: string[]): boolean {
95
+ let json = false;
96
+ for (const argument of argv) {
97
+ if (argument === "--json") json = true;
98
+ if (argument === "--no-json") json = false;
99
+ }
100
+ return json;
101
+ }