@bjoernrochel/sevro 0.1.0-rc.7 → 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 (230) 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 +17 -3
  7. package/docs/codex-host.md +155 -49
  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 +141 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +307 -18
  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 +63 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +206 -0
  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 +13 -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} +133 -115
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/evaluation/types.ts +207 -0
  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} +15 -76
  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/fixtures/workspace-state.ts +212 -0
  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 +57 -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 +91 -14
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +46 -16
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +122 -73
  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 -54
  158. package/src/hosts/codex-profile.ts +50 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +141 -19
  161. package/src/hosts/codex.ts +221 -179
  162. package/src/hosts/isolation-common.ts +15 -11
  163. package/src/hosts/isolation-roots.ts +13 -20
  164. package/src/hosts/linux-sandbox.ts +27 -3
  165. package/src/hosts/mac-sandbox.ts +111 -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 +221 -45
  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 +34 -15
  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/run-owner.ts +4 -4
  186. package/src/{runtime-config.ts → runtime/config.ts} +182 -44
  187. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  188. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  189. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  190. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  191. package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
  192. package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
  193. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  194. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  195. package/src/runtime/shared-cache.ts +130 -0
  196. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  197. package/src/schema-types.ts +73 -161
  198. package/src/shell.ts +4 -0
  199. package/src/transcript-retention.ts +146 -0
  200. package/src/value-guards.ts +22 -0
  201. package/docs/assets/sevro-logo.png +0 -0
  202. package/docs/assets/sevro-symbol.svg +0 -1
  203. package/docs/assets/sevro-wordmark.svg +0 -1
  204. package/docs/cache-ownership-validation.md +0 -49
  205. package/docs/claude-continuation-validation.md +0 -47
  206. package/docs/concurrency-validation.md +0 -86
  207. package/docs/configuration-root-validation.md +0 -70
  208. package/docs/engine-slice.md +0 -114
  209. package/docs/evidence/runtime-startup-30/README.md +0 -185
  210. package/docs/evidence/runtime-startup-30/after.json +0 -319
  211. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  212. package/docs/evidence/runtime-startup-30/before.json +0 -403
  213. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  214. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  216. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  217. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  218. package/docs/evidence.md +0 -17
  219. package/docs/fixture-cleanup-validation.md +0 -70
  220. package/docs/guide-verification.json +0 -239
  221. package/src/evaluation-context.ts +0 -257
  222. package/src/evaluation-fixture.ts +0 -313
  223. package/src/evaluation-run.ts +0 -198
  224. package/src/evaluation-trial.ts +0 -946
  225. package/src/evaluation-types.ts +0 -149
  226. package/src/hosts/claude-nested-skills.ts +0 -272
  227. package/src/hosts/codex-command-output.ts +0 -510
  228. package/src/hosts/codex-executor-command.ts +0 -291
  229. package/src/hosts/codex-skill-reads.ts +0 -609
  230. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
package/src/cli.ts CHANGED
@@ -1,595 +1,17 @@
1
1
  #!/usr/bin/env bun
2
- import { readFile } from "node:fs/promises";
3
- import { isAbsolute, join } from "node:path";
4
- import { fileURLToPath, pathToFileURL } from "node:url";
5
- import { isResolvedCase } from "./case-validation";
6
- import {
7
- EvaluationConfigurationError,
8
- runEvaluation,
9
- type HostAdapter,
10
- type ResolvedCase,
11
- } from "./engine";
12
- import { openExtensionSession, type ExtensionCase } from "./extension-session";
13
- import { createClaudeHost } from "./hosts/claude";
14
- import { createCodexHost } from "./hosts/codex";
15
- import {
16
- codexAgentConcurrency,
17
- configurationRoot,
18
- } from "./hosts/codex-configuration";
19
- import { prepareInstrumentation } from "./instrumentation";
20
- import {
21
- packageBuildDigest,
22
- projectIdentityDigest,
23
- projectProvenance,
24
- } from "./provenance";
2
+ import { main } from "./cli/command";
25
3
  import { reportCommand } from "./report";
26
- import { resolvedFixture } from "./resolved-case";
27
- import { assertCliResult } from "./schema";
28
- import { isRecord, isStringArray } from "./value-guards";
29
-
30
- import { InvocationError, parseInvocation } from "./cli-invocation";
31
- import {
32
- loadRuntimeConfiguration,
33
- runtimeNativeGoalEnabled,
34
- } from "./runtime-config";
35
- import { canonicalRuntimeRoot, requireRuntimeReadRoots } from "./runtime-paths";
36
-
37
- function parseCase(value: unknown): ResolvedCase {
38
- if (!isResolvedCase(value))
39
- throw new InvocationError("invalid resolved case file");
40
- return value;
41
- }
42
-
43
- async function loadCase(path: string): Promise<ResolvedCase> {
44
- try {
45
- return parseCase(JSON.parse(await readFile(path, "utf8")));
46
- } catch (cause) {
47
- throw new InvocationError("resolved case file is unreadable or invalid", {
48
- cause,
49
- });
50
- }
51
- }
52
-
53
- async function loadJson(path: string, label: string): Promise<unknown> {
54
- try {
55
- return JSON.parse(await readFile(path, "utf8"));
56
- } catch (cause) {
57
- throw new InvocationError(`${label} is unreadable or invalid`, { cause });
58
- }
59
- }
60
-
61
- function validExtensionCommand(value: unknown): value is string[] {
62
- return (
63
- isStringArray(value) &&
64
- value.length > 0 &&
65
- value.every((part) => part.length > 0) &&
66
- isAbsolute(value[0] ?? "")
67
- );
68
- }
69
-
70
- async function loadExtensionCommand(path: string): Promise<string[]> {
71
- const command = await loadJson(path, "extension command file");
72
- if (!validExtensionCommand(command))
73
- throw new InvocationError(
74
- "extension command must be an absolute argv array",
75
- );
76
- return command;
77
- }
78
-
79
- async function loadExtensionOptions(
80
- selected: NonNullable<ReturnType<typeof parseInvocation>["extension"]>,
81
- ) {
82
- const command = await loadExtensionCommand(selected.commandFile);
83
- const configuration = selected.configurationFile
84
- ? await loadJson(selected.configurationFile, "extension configuration file")
85
- : {};
86
- const redactedConfiguration = selected.redactedConfigurationFile
87
- ? await loadJson(
88
- selected.redactedConfigurationFile,
89
- "redacted extension configuration file",
90
- )
91
- : {};
92
- if (!isRecord(configuration) || !isRecord(redactedConfiguration))
93
- throw new InvocationError("extension configuration must be JSON objects");
94
- return { command, configuration, redactedConfiguration };
95
- }
96
-
97
- async function loadPreparationSources(
98
- selected: ReturnType<typeof parseInvocation>,
99
- ) {
100
- if (!selected.caseSourceRoot || !selected.caseSourceMapFile) return undefined;
101
- const refs = await loadJson(
102
- selected.caseSourceMapFile,
103
- "case source map file",
104
- );
105
- if (
106
- !isRecord(refs) ||
107
- !Object.values(refs).every(
108
- (value) => typeof value === "string" && value.startsWith("file:///"),
109
- )
110
- )
111
- throw new InvocationError("case source map must contain file URLs");
112
- return {
113
- root: selected.caseSourceRoot,
114
- refs: refs as Record<string, string>,
115
- };
116
- }
117
-
118
- function selectExtensionCase(
119
- cases: ExtensionCase[],
120
- caseId: string,
121
- ): { caseData: ResolvedCase; resolvedCase: ExtensionCase } {
122
- const resolvedCase = cases.find((item) => item.id === caseId);
123
- if (!resolvedCase)
124
- throw new InvocationError("extension did not resolve the selected case");
125
- const caseData = parseCase({
126
- ...resolvedCase,
127
- fixture: resolvedFixture(resolvedCase.fixture),
128
- });
129
- return { caseData, resolvedCase };
130
- }
131
-
132
- async function adapterModule(path: string): Promise<unknown> {
133
- try {
134
- return await import(pathToFileURL(path).href);
135
- } catch (cause) {
136
- throw new InvocationError("host adapter module could not be loaded", {
137
- cause,
138
- });
139
- }
140
- }
141
-
142
- async function loadHost(path: string): Promise<HostAdapter> {
143
- const module = await adapterModule(path);
144
- const host = isRecord(module) ? module.default : null;
145
- if (!validHostAdapter(host))
146
- throw new InvocationError(
147
- "host adapter module has no valid default adapter",
148
- );
149
- validateHostInstrumentation(host);
150
- validateHostCapabilities(host.hostCapabilities);
151
- return host as unknown as HostAdapter;
152
- }
153
-
154
- function validateHostInstrumentation(host: Record<string, unknown>): void {
155
- try {
156
- prepareInstrumentation(
157
- [],
158
- (host.instrumentation as HostAdapter["instrumentation"]) ?? [],
159
- [],
160
- "passive",
161
- );
162
- } catch (cause) {
163
- throw new InvocationError("host adapter instrumentation is invalid", {
164
- cause,
165
- });
166
- }
167
- }
168
-
169
- function validHostAdapter(value: unknown): value is Record<string, unknown> {
170
- return (
171
- isRecord(value) &&
172
- typeof value.id === "string" &&
173
- typeof value.model === "string" &&
174
- typeof value.effort === "string" &&
175
- typeof value.run === "function"
176
- );
177
- }
178
-
179
- function validateHostCapabilities(value: unknown): void {
180
- if (value === undefined) return;
181
- if (
182
- !isStringArray(value) ||
183
- value.some((id) => !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/.test(id)) ||
184
- new Set(value).size !== value.length
185
- )
186
- throw new InvocationError("host adapter capabilities are invalid");
187
- }
188
-
189
- function failure(code: 64 | 70, message: string) {
190
- const result = {
191
- format: "sevro.cli-result.v1",
192
- runId: null,
193
- execution: { status: "not_run" },
194
- grading: { status: "not_requested" },
195
- task: { verdict: "not_assessed" },
196
- exitCode: code,
197
- evidencePath: null,
198
- cases: [],
199
- diagnostic: {
200
- code: code === 64 ? "sevro.invocation.invalid" : "sevro.runner.error",
201
- message: message.slice(0, 4096),
202
- },
203
- };
204
- assertCliResult(result);
205
- return result;
206
- }
207
-
208
- function display(value: unknown, json: boolean): void {
209
- if (json) {
210
- process.stdout.write(`${JSON.stringify(value)}\n`);
211
- return;
212
- }
213
- const result = value as {
214
- execution: { status: string };
215
- grading: { status: string };
216
- task: { verdict: string };
217
- evidencePath: string | null;
218
- diagnostic?: { code: string; message: string };
219
- cases: Array<{
220
- caseId: string;
221
- trials: Array<{
222
- trial: number;
223
- domainOutcomes: Array<{ id: string; status: string }>;
224
- }>;
225
- }>;
226
- };
227
- process.stdout.write(
228
- `execution=${result.execution.status} grading=${result.grading.status} task=${result.task.verdict}\n`,
229
- );
230
- displayDomainOutcomes(result.cases);
231
- if (result.diagnostic?.code === "sevro.grader.error")
232
- process.stdout.write(`diagnostic=${result.diagnostic.message}\n`);
233
- if (result.evidencePath)
234
- process.stdout.write(`evidence=${result.evidencePath}\n`);
235
- }
236
-
237
- function displayDomainOutcomes(
238
- cases: Array<{
239
- caseId: string;
240
- trials: Array<{
241
- trial: number;
242
- domainOutcomes: Array<{ id: string; status: string }>;
243
- }>;
244
- }>,
245
- ): void {
246
- for (const selected of cases)
247
- for (const trial of selected.trials)
248
- for (const outcome of trial.domainOutcomes)
249
- process.stdout.write(
250
- `domain case=${selected.caseId} trial=${trial.trial} outcome=${outcome.id} status=${outcome.status}\n`,
251
- );
252
- }
253
-
254
- type Invocation = ReturnType<typeof parseInvocation>;
255
- type PreparationSources = Awaited<ReturnType<typeof loadPreparationSources>>;
256
-
257
- function protectMappedSources(
258
- invocation: Invocation,
259
- preparationSources: PreparationSources,
260
- ): void {
261
- const sourceRoots = mappedSourceRoots(preparationSources);
262
- for (const native of [
263
- invocation.codex,
264
- invocation.claude,
265
- invocation.semanticCodex,
266
- invocation.advisoryCodex,
267
- ]) {
268
- if (native)
269
- native.additionalProtectedRoots = [
270
- ...native.additionalProtectedRoots,
271
- ...sourceRoots,
272
- ];
273
- }
274
- invocation.shellIsolation?.protectedRoots.push(...sourceRoots);
275
- }
276
-
277
- async function selectedConcurrency(
278
- invocation: Invocation,
279
- configRoot: string,
280
- ): Promise<number | null> {
281
- return [
282
- invocation.codex,
283
- invocation.semanticCodex,
284
- invocation.advisoryCodex,
285
- ].some(Boolean)
286
- ? codexAgentConcurrency(configRoot)
287
- : null;
288
- }
289
-
290
- async function candidateHost(
291
- invocation: Invocation,
292
- agentConcurrencyLimit: number | null,
293
- ): Promise<HostAdapter> {
294
- if (invocation.codex)
295
- return createCodexHost({ ...invocation.codex, agentConcurrencyLimit });
296
- if (invocation.claude) return createClaudeHost(invocation.claude);
297
- if (!invocation.adapterModule)
298
- throw new InvocationError("missing --adapter-module or --host");
299
- return loadHost(invocation.adapterModule);
300
- }
301
-
302
- async function secondaryHost(
303
- module: string | undefined,
304
- codex: Invocation["semanticCodex"],
305
- agentConcurrencyLimit: number | null,
306
- ): Promise<HostAdapter | undefined> {
307
- if (module) return loadHost(module);
308
- if (codex) return createCodexHost({ ...codex, agentConcurrencyLimit });
309
- return undefined;
310
- }
311
-
312
- async function prepareRunInvocation(argv: string[]) {
313
- const invocation = parseInvocation(argv);
314
- warnDeprecatedRuntimeOptions(invocation);
315
- const preparationSources = await loadPreparationSources(invocation);
316
- protectMappedSources(invocation, preparationSources);
317
- const runtimePolicy = await loadRuntimeConfiguration(
318
- invocation.projectRoot,
319
- invocation.runtimeConfigFile,
320
- await invocationProtectedRuntimeRoots(invocation),
321
- );
322
- requireRuntimeOptionCompatibility(invocation, runtimePolicy);
323
- selectRuntimeTransport(invocation, runtimePolicy);
324
- const configRoot = await configurationRoot(invocation.configRoot);
325
- await validateInvocationRuntime(invocation, runtimePolicy);
326
- const agentConcurrencyLimit = await selectedConcurrency(
327
- invocation,
328
- configRoot,
329
- );
330
- const caseData = invocation.caseFile
331
- ? await loadCase(invocation.caseFile)
332
- : undefined;
333
- const host = await candidateHost(invocation, agentConcurrencyLimit);
334
- const semanticHost = await secondaryHost(
335
- invocation.semanticAdapterModule,
336
- invocation.semanticCodex,
337
- agentConcurrencyLimit,
338
- );
339
- const advisoryHost = await secondaryHost(
340
- invocation.advisoryAdapterModule,
341
- invocation.advisoryCodex,
342
- agentConcurrencyLimit,
343
- );
344
- return {
345
- invocation,
346
- runtimePolicy,
347
- preparationSources,
348
- caseData,
349
- host,
350
- semanticHost,
351
- advisoryHost,
352
- };
353
- }
354
-
355
- function selectRuntimeTransport(
356
- invocation: Invocation,
357
- policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
358
- ): void {
359
- if (!runtimeNativeGoalEnabled(policy)) return;
360
- if (invocation.codex) selectCodexGoalTransport(invocation.codex);
361
- }
362
-
363
- function selectCodexGoalTransport(
364
- codex: NonNullable<Invocation["codex"]>,
365
- ): void {
366
- if (codex.entrypoint === "exec")
367
- throw new InvocationError(
368
- "native-goal runtime policy requires Codex app-server",
369
- );
370
- codex.entrypoint = "app-server";
371
- }
372
-
373
- function warnDeprecatedRuntimeOptions(invocation: Invocation): void {
374
- for (const option of invocation.deprecatedRuntimeOptions)
375
- console.warn(
376
- `--${option} is deprecated; declare the runtime in sevro.json or --runtime-config-file instead`,
377
- );
378
- }
379
-
380
- function requireRuntimeOptionCompatibility(
381
- invocation: Invocation,
382
- policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
383
- ): void {
384
- if (policy && invocation.deprecatedRuntimeOptions.length)
385
- throw new InvocationError(
386
- "runtime configuration cannot be combined with deprecated runtime options",
387
- );
388
- }
389
-
390
- type PreparedInvocation = Awaited<ReturnType<typeof prepareRunInvocation>>;
391
-
392
- async function validateInvocationRuntime(
393
- invocation: Invocation,
394
- policy: Awaited<ReturnType<typeof loadRuntimeConfiguration>>,
395
- ): Promise<void> {
396
- if (!policy) return;
397
- const protectedRoots = await invocationProtectedRuntimeRoots(invocation);
398
- requireRuntimeReadRoots(policy.readOnlyRoots, protectedRoots);
399
- requireRuntimeReadRoots(
400
- (policy.seeds ?? []).map((seed) => seed.source),
401
- protectedRoots,
402
- );
403
- }
404
-
405
- async function invocationProtectedRuntimeRoots(
406
- invocation: Invocation,
407
- ): Promise<string[]> {
408
- const native = [
409
- invocation.codex,
410
- invocation.claude,
411
- invocation.semanticCodex,
412
- invocation.advisoryCodex,
413
- ];
414
- const protectedRoots = [
415
- join(import.meta.dir, ".."),
416
- invocation.projectRoot,
417
- invocation.configRoot,
418
- invocation.resultsRoot,
419
- invocation.runStateRoot,
420
- ...native.flatMap((host) => host?.additionalProtectedRoots ?? []),
421
- ...(invocation.shellIsolation?.protectedRoots ?? []),
422
- ];
423
- return Promise.all(protectedRoots.map(canonicalRuntimeRoot));
424
- }
425
-
426
- async function resolveSelectedExtension(
427
- prepared: PreparedInvocation,
428
- selected: NonNullable<Invocation["extension"]>,
429
- signal: AbortSignal,
430
- ) {
431
- const { invocation, host } = prepared;
432
- const options = await loadExtensionOptions(selected);
433
- const hostCapabilities = [
434
- ...(host.instrumentation ?? []).map((item) => item.id),
435
- ...(host.hostCapabilities ?? []),
436
- ];
437
- const session = await openExtensionSession({
438
- ...options,
439
- sourceFiles: selected.sourceFiles,
440
- engineCapabilities: [
441
- "sevro.host.exec",
442
- "sevro.fixture.setup",
443
- "sevro.case.host-route",
444
- ],
445
- hostCapabilities,
446
- taskVerdictPolicy: selected.taskVerdictPolicy,
447
- replaceBuiltinGraders: selected.replaceBuiltinGraders,
448
- signal,
449
- });
450
- const chosen = selectExtensionCase(
451
- await session.resolve(
452
- pathToFileURL(invocation.projectRoot).href,
453
- { caseIds: [selected.caseId] },
454
- {
455
- id: host.id,
456
- model: host.model,
457
- effort: host.effort,
458
- capabilities: hostCapabilities,
459
- },
460
- ),
461
- selected.caseId,
462
- );
463
- return {
464
- caseData: chosen.caseData,
465
- extension: { session, resolvedCase: chosen.resolvedCase },
466
- };
467
- }
468
-
469
- async function selectedCase(prepared: PreparedInvocation, signal: AbortSignal) {
470
- if (prepared.invocation.extension)
471
- return resolveSelectedExtension(
472
- prepared,
473
- prepared.invocation.extension,
474
- signal,
475
- );
476
- if (!prepared.caseData)
477
- throw new InvocationError("invalid resolved case file");
478
- return { caseData: prepared.caseData, extension: undefined };
479
- }
480
-
481
- async function evaluateInvocation(
482
- prepared: PreparedInvocation,
483
- signal: AbortSignal,
484
- ) {
485
- const { invocation, preparationSources, host, semanticHost, advisoryHost } =
486
- prepared;
487
- const { caseData, extension } = await selectedCase(prepared, signal);
488
- return runEvaluation({
489
- runtimePolicy: prepared.runtimePolicy,
490
- projectRoot: invocation.projectRoot,
491
- resultsRoot: invocation.resultsRoot,
492
- runStateRoot: invocation.runStateRoot,
493
- case: caseData,
494
- extension,
495
- preparationSources,
496
- host,
497
- semanticHost,
498
- advisoryHost,
499
- advisoryExcludedPaths: invocation.advisoryExcludedPaths,
500
- shellIsolation: invocation.shellIsolation,
501
- runnerBuildDigest:
502
- invocation.runnerBuildDigest ?? (await packageBuildDigest()),
503
- runnerCheckoutRoot: invocation.runnerCheckoutRoot,
504
- projectDigest:
505
- invocation.projectDigest ??
506
- (await projectIdentityDigest(
507
- invocation.projectRoot,
508
- await projectProvenance(invocation.projectRoot),
509
- [invocation.resultsRoot, invocation.runStateRoot],
510
- )),
511
- condition: invocation.condition,
512
- trialCount: invocation.trialCount,
513
- jobs: invocation.jobs,
514
- passThreshold: invocation.passThreshold,
515
- dry: invocation.dry,
516
- signal,
517
- });
518
- }
519
-
520
- class CommandCancellation {
521
- private readonly controller = new AbortController();
522
- private readonly interrupt = () => {
523
- this.controller.abort("SIGINT");
524
- };
525
- private readonly terminate = () => {
526
- this.controller.abort("SIGTERM");
527
- };
528
- get signal(): AbortSignal {
529
- return this.controller.signal;
530
- }
531
- constructor() {
532
- process.on("SIGINT", this.interrupt);
533
- process.on("SIGTERM", this.terminate);
534
- }
535
- dispose(): void {
536
- process.off("SIGINT", this.interrupt);
537
- process.off("SIGTERM", this.terminate);
538
- }
539
- }
540
-
541
- function runnerFailureCategory(error: unknown): 64 | 70 {
542
- return error instanceof EvaluationConfigurationError ||
543
- error instanceof InvocationError
544
- ? 64
545
- : 70;
546
- }
547
-
548
- function displayFailure(
549
- error: unknown,
550
- json: boolean,
551
- code: 64 | 70,
552
- fallback: string,
553
- ): void {
554
- display(
555
- failure(code, error instanceof Error ? error.message : fallback),
556
- json,
557
- );
558
- process.exitCode = code;
559
- }
560
-
561
- async function main(argv: string[]): Promise<void> {
562
- const json = argv.includes("--json");
563
- let prepared: PreparedInvocation;
564
- try {
565
- prepared = await prepareRunInvocation(argv);
566
- } catch (error) {
567
- displayFailure(error, json, 64, "invalid invocation");
568
- return;
569
- }
570
- const cancellation = new CommandCancellation();
571
- try {
572
- const { result } = await evaluateInvocation(prepared, cancellation.signal);
573
- display(result, prepared.invocation.json);
574
- process.exitCode = result.exitCode;
575
- } catch (error) {
576
- displayFailure(
577
- error,
578
- prepared.invocation.json,
579
- runnerFailureCategory(error),
580
- "runner failure",
581
- );
582
- } finally {
583
- cancellation.dispose();
584
- }
585
- }
586
-
587
- if (process.argv[2] === "report")
4
+ import { informationCommand } from "./cli/help";
5
+ import { demoCommand } from "./cli/demo";
6
+ import { initCommand } from "./cli/init";
7
+ import { pruneCommand } from "./prune";
8
+
9
+ if (informationCommand(process.argv.slice(2))) process.exitCode = 0;
10
+ else if (process.argv[2] === "demo") await demoCommand(process.argv.slice(3));
11
+ else if (process.argv[2] === "init")
12
+ process.exitCode = await initCommand(process.argv.slice(3));
13
+ else if (process.argv[2] === "report")
588
14
  process.exitCode = await reportCommand(process.argv.slice(2));
15
+ else if (process.argv[2] === "prune")
16
+ process.exitCode = await pruneCommand(process.argv.slice(3));
589
17
  else await main(process.argv.slice(2));
590
-
591
- function mappedSourceRoots(preparationSources: PreparationSources): string[] {
592
- return Object.values(preparationSources?.refs ?? {}).map((url) =>
593
- fileURLToPath(url),
594
- );
595
- }
@@ -0,0 +1,94 @@
1
+ /** Shared structures; public schemas and runtime guards still validate external data. */
2
+ export type ExecutionStatus = "completed" | "failed" | "cancelled" | "not_run";
3
+ export type GradingStatus =
4
+ "completed" | "error" | "unavailable" | "not_requested";
5
+ export type TaskVerdict = "passed" | "failed" | "not_assessed";
6
+ export type OutcomeStatus = "passed" | "failed" | "unavailable";
7
+ export type ExitCode = 0 | 1 | 2 | 3 | 4 | 64 | 70 | 130 | 143;
8
+
9
+ export interface Assessment {
10
+ execution: { status: ExecutionStatus };
11
+ grading: { status: GradingStatus };
12
+ task: { verdict: TaskVerdict };
13
+ }
14
+
15
+ export type CheckDeclaration = {
16
+ id: string;
17
+ grader: string;
18
+ configuration: Record<string, unknown>;
19
+ };
20
+
21
+ /** Runner check outcomes include their grader and may lack evidence references. */
22
+ export interface CheckOutcome {
23
+ id: string;
24
+ grader: string;
25
+ status: OutcomeStatus;
26
+ detail?: string;
27
+ evidenceRefs?: string[];
28
+ }
29
+
30
+ /** Extension checks require references; their grader comes from the declaration. */
31
+ export type ExtensionCheckOutcome = {
32
+ id: string;
33
+ status: OutcomeStatus;
34
+ detail?: string;
35
+ evidenceRefs: string[];
36
+ };
37
+
38
+ /** Domain outcomes are retained independently of task checks and verdicts. */
39
+ export type DomainOutcome = {
40
+ id: string;
41
+ status: OutcomeStatus;
42
+ detail?: string;
43
+ evidenceRefs: string[];
44
+ data?: Record<string, unknown>;
45
+ };
46
+
47
+ export type ObservationCompleteness = "complete" | "partial" | "unavailable";
48
+
49
+ export type ObservationEvidence = {
50
+ id: string;
51
+ source: string;
52
+ completeness: ObservationCompleteness;
53
+ data: Record<string, unknown>;
54
+ };
55
+
56
+ export type MetricEvidence = {
57
+ id: string;
58
+ value: number | null;
59
+ unit: string;
60
+ };
61
+
62
+ /** Retained measurements keep unknown values and completeness separate. */
63
+ export type UsageEvidence = {
64
+ inputTokens: number | null;
65
+ outputTokens: number | null;
66
+ costUsd: number | null;
67
+ complete: boolean;
68
+ };
69
+
70
+ export type RawResultReference = {
71
+ source: string;
72
+ path: string | null;
73
+ sha256: string | null;
74
+ };
75
+
76
+ export type ArtifactReference = {
77
+ id: string;
78
+ path: string;
79
+ sha256: string;
80
+ gitExclude?: boolean;
81
+ executable?: boolean;
82
+ };
83
+
84
+ export type EvidenceRoute = {
85
+ role: "candidate" | "semantic" | "advisory";
86
+ host: string;
87
+ model: string;
88
+ effort: string;
89
+ };
90
+
91
+ export type Diagnostic = {
92
+ code: string;
93
+ message: string;
94
+ };