@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
@@ -1,50 +1,53 @@
1
- import { rm } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { pathToFileURL } from "node:url";
4
- import { createFixture, hostArtifacts, usage } from "./evaluation-fixture";
1
+ import { clearFixtureContents } from "../fixtures/cleanup";
2
+ import type { CandidateArtifactRoots } from "../candidate-artifacts";
3
+ import { createFixture } from "../fixtures/create";
4
+ import { hostArtifacts } from "../evidence/host-results";
5
+ import { usage } from "../evidence/usage";
6
+ import { gradingRuntimePolicy, scopedHostArtifacts } from "./grading-host";
5
7
  import {
6
- gradingRuntimePolicy,
7
- scopedHostArtifacts,
8
- } from "./evaluation-grading-host";
9
- import type { HostAdapter, HostResult, ResolvedCase } from "./evaluation-types";
10
- import { readSemanticArtifact } from "./graders/artifact";
8
+ HostExecutionFailure,
9
+ type HostAdapter,
10
+ type HostResult,
11
+ } from "./types";
12
+ import { readSemanticArtifact } from "../graders/artifact";
11
13
  import {
12
14
  parseSemanticVerdicts,
13
15
  semanticCheckGroups,
14
16
  semanticPrompt,
15
17
  type PreparedSemanticCheck,
16
18
  type SemanticVerdict,
17
- } from "./graders/semantic";
18
- import { sha256 } from "./identity";
19
- import type { CheckOutcome } from "./results";
20
- import type { RuntimePolicy } from "./runtime-config";
19
+ } from "../graders/semantic";
20
+ import { sha256 } from "../identity";
21
+ import type { CheckOutcome } from "../results";
22
+ import type {
23
+ TrialEvidenceWriter,
24
+ TrialObservation,
25
+ } from "../evidence/trial-writer";
26
+ import type { GradingFailure, GradingResult } from "./grading";
27
+ import type { RuntimePolicy } from "../runtime/config";
28
+ import { HostDiagnosticPersistenceError } from "../host-diagnostics";
21
29
 
22
- interface SemanticObservation {
23
- id: string;
24
- source: string;
25
- completeness: "complete" | "partial";
26
- data: Record<string, unknown>;
27
- }
28
30
  interface SemanticGradingInput {
29
- checks: PreparedSemanticCheck[];
30
- declaredChecks: ResolvedCase["checks"];
31
- host: HostAdapter | undefined;
32
- message: string | null;
33
- candidateWorkspace: string;
34
- candidateTranscriptRoot?: string;
35
- projectRoot: string;
36
- reservedWorkspace?: string;
37
- runDir: string;
38
- trial: number;
39
- runtimePolicy?: RuntimePolicy;
40
- signal?: AbortSignal;
31
+ readonly checks: readonly PreparedSemanticCheck[];
32
+ readonly declaredChecks: readonly { id: string }[];
33
+ readonly host: HostAdapter | undefined;
34
+ readonly message: string | null;
35
+ readonly candidateWorkspace: string;
36
+ readonly candidateTranscriptRoot?: string;
37
+ readonly candidateArtifactRoots?: CandidateArtifactRoots;
38
+ readonly projectRoot: string;
39
+ readonly reservedWorkspace?: string;
40
+ readonly runtimePolicy?: RuntimePolicy;
41
+ readonly signal?: AbortSignal;
41
42
  }
42
43
  interface SemanticGradingEffects {
43
- artifactRefs: { id: string; path: string; sha256: string }[];
44
- canGrade(): boolean;
45
- failGrading(message: string, cancelable?: boolean): void;
46
- retainArtifacts(artifacts: ReturnType<typeof hostArtifacts>): Promise<void>;
47
- writeTrialFile(path: string, bytes: string | Uint8Array): Promise<void>;
44
+ readonly evidence: Pick<
45
+ TrialEvidenceWriter,
46
+ "artifactRefs" | "retainArtifacts" | "retainSemanticVerdicts"
47
+ >;
48
+ /** Close admission at detection, before retaining or cleaning auxiliary state. */
49
+ onFailure(failure: GradingFailure): void;
50
+ retainWorkspace(workspace: string): Promise<void>;
48
51
  }
49
52
  type SemanticGroup = ReturnType<typeof semanticCheckGroups>[number];
50
53
 
@@ -94,16 +97,25 @@ function semanticHostPrompt(
94
97
  );
95
98
  }
96
99
  class SemanticGrading {
97
- private observations: SemanticObservation[] = [];
100
+ private observations: TrialObservation[] = [];
98
101
  private checks: CheckOutcome[] = [];
102
+ private failure: GradingFailure | null = null;
99
103
  constructor(
100
104
  private readonly input: SemanticGradingInput,
101
105
  private readonly effects: SemanticGradingEffects,
102
106
  ) {}
103
- async run() {
104
- for (const group of semanticCheckGroups(this.input.checks))
107
+ async run(): Promise<GradingResult> {
108
+ for (const group of semanticCheckGroups([...this.input.checks]))
105
109
  await this.gradeSemanticGroup(group);
106
- return { checks: this.checks, observations: this.observations };
110
+ return {
111
+ checks: this.checks,
112
+ observations: this.observations,
113
+ failure: this.failure,
114
+ };
115
+ }
116
+ private failGrading(message: string, cancelable = true): void {
117
+ this.failure = { message, cancelable };
118
+ this.effects.onFailure(this.failure);
107
119
  }
108
120
  private async semanticInput(
109
121
  pattern: string | undefined,
@@ -115,16 +127,12 @@ class SemanticGrading {
115
127
 
116
128
  private async semanticWorkspace(): Promise<string> {
117
129
  return createFixture(
118
- { files: {} },
119
- [],
120
- undefined,
121
- null,
122
- null,
123
- null,
124
- null,
125
- this.input.projectRoot,
126
- this.input.signal,
127
- this.input.reservedWorkspace,
130
+ { kind: "inline", files: {} },
131
+ {
132
+ projectRoot: this.input.projectRoot,
133
+ signal: this.input.signal,
134
+ reservedWorkspace: this.input.reservedWorkspace,
135
+ },
128
136
  );
129
137
  }
130
138
 
@@ -141,53 +149,43 @@ class SemanticGrading {
141
149
  source = semanticSource(artifact);
142
150
  const host = this.input.host;
143
151
  if (!host) throw new Error("semantic host is unavailable");
144
- const response = await host.run({
145
- candidateTranscriptRoot: this.input.candidateTranscriptRoot,
146
- prompt: semanticHostPrompt(message, group, artifact),
147
- runtimePolicy: gradingRuntimePolicy(
148
- this.input.runtimePolicy,
149
- pattern === undefined,
150
- ),
151
- runtimeRole: "semantic",
152
- workspace,
153
- condition: "passive",
154
- signal: this.input.signal,
155
- });
152
+ const response = await host
153
+ .run({
154
+ retainHostDiagnostics: (bytes) =>
155
+ this.effects.evidence.retainArtifacts([
156
+ {
157
+ id: `sevro.semantic${suffix}.sevro.host.diagnostics`,
158
+ bytes: Buffer.from(bytes),
159
+ },
160
+ ]),
161
+ candidateTranscriptRoot: this.input.candidateTranscriptRoot,
162
+ candidateArtifactRoots: this.input.candidateArtifactRoots,
163
+ prompt: semanticHostPrompt(message, group, artifact),
164
+ runtimePolicy: gradingRuntimePolicy(
165
+ this.input.runtimePolicy,
166
+ pattern === undefined,
167
+ ),
168
+ runtimeRole: "semantic",
169
+ workspace,
170
+ condition: "passive",
171
+ signal: this.input.signal,
172
+ })
173
+ .catch((cause: unknown) => {
174
+ if (cause instanceof HostExecutionFailure) return cause.result;
175
+ throw cause;
176
+ });
156
177
  const artifacts = scopedHostArtifacts(
157
178
  response,
158
179
  `sevro.semantic${suffix}.`,
159
- this.effects.artifactRefs,
180
+ this.effects.evidence.artifactRefs,
160
181
  this.input.declaredChecks,
161
182
  `sevro.semantic.verdicts${suffix}`,
162
183
  );
163
184
  return { result: response, artifacts, source, hostId: host.id };
164
- } catch {
165
- this.effects.failGrading("semantic grading did not complete");
185
+ } catch (cause) {
186
+ if (cause instanceof HostDiagnosticPersistenceError) throw cause;
187
+ this.failGrading("semantic grading did not complete");
166
188
  return { result: null, artifacts: [], source, hostId: null };
167
- } finally {
168
- await rm(workspace, { recursive: true, force: true });
169
- }
170
- }
171
-
172
- private async retainSemanticVerdicts(
173
- result: HostResult,
174
- suffix: string,
175
- ): Promise<void> {
176
- if (
177
- result.finalMessage !== null &&
178
- Buffer.byteLength(result.finalMessage, "utf8") <= 1024 * 1024
179
- ) {
180
- const bytes = Buffer.from(result.finalMessage, "utf8");
181
- const path = join(
182
- this.input.runDir,
183
- `trial-${this.input.trial}-semantic-verdicts${suffix}.json`,
184
- );
185
- await this.effects.writeTrialFile(path, bytes);
186
- this.effects.artifactRefs.push({
187
- id: `sevro.semantic.verdicts${suffix}`,
188
- path: pathToFileURL(path).href,
189
- sha256: sha256(bytes),
190
- });
191
189
  }
192
190
  }
193
191
 
@@ -223,14 +221,19 @@ class SemanticGrading {
223
221
  pattern: string | undefined,
224
222
  source: Record<string, unknown>,
225
223
  hostId: string,
226
- ): void {
224
+ ): boolean {
227
225
  try {
228
226
  const message = this.completeSemanticGraderMessage(result);
229
227
  const verdicts = parseSemanticVerdicts(message, group);
230
228
  for (const verdict of verdicts)
231
229
  this.recordSemanticVerdict(verdict, pattern, source, hostId);
230
+ return true;
232
231
  } catch {
233
- this.effects.failGrading("semantic grading did not complete", false);
232
+ this.failGrading(
233
+ result.failureMessage ?? "semantic grading did not complete",
234
+ result.processFailure?.reason === "cancelled",
235
+ );
236
+ return false;
234
237
  }
235
238
  }
236
239
 
@@ -243,7 +246,7 @@ class SemanticGrading {
243
246
  private async gradeSemanticGroup(group: SemanticGroup): Promise<void> {
244
247
  const pattern = group[0].artifactPath;
245
248
  const suffix = semanticGroupSuffix(pattern);
246
- if (!this.effects.canGrade()) return;
249
+ if (this.failure) return;
247
250
  const message = this.input.message;
248
251
  if (message === null) {
249
252
  this.checks.push(
@@ -257,29 +260,44 @@ class SemanticGrading {
257
260
  return;
258
261
  }
259
262
  const workspace = await this.semanticWorkspace();
260
- const outcome = await this.runSemanticHost(
261
- group,
262
- pattern,
263
- suffix,
264
- workspace,
265
- message,
266
- );
267
- if (outcome.result === null) return;
268
- await this.effects.retainArtifacts(outcome.artifacts);
269
- this.observations.push({
270
- id: `sevro.observation.semantic.usage${suffix}`,
271
- source: outcome.hostId,
272
- completeness: outcome.result.usageComplete ? "complete" : "partial",
273
- data: usage(outcome.result),
274
- });
275
- await this.retainSemanticVerdicts(outcome.result, suffix);
276
- this.parseSemanticResults(
277
- outcome.result,
278
- group,
279
- pattern,
280
- outcome.source,
281
- outcome.hostId,
282
- );
263
+ let successful = false;
264
+ try {
265
+ const outcome = await this.runSemanticHost(
266
+ group,
267
+ pattern,
268
+ suffix,
269
+ workspace,
270
+ message,
271
+ );
272
+ if (outcome.result === null) return;
273
+ await this.effects.evidence.retainArtifacts(outcome.artifacts);
274
+ this.observations.push({
275
+ id: `sevro.observation.semantic.usage${suffix}`,
276
+ source: outcome.hostId,
277
+ completeness: outcome.result.usageComplete ? "complete" : "partial",
278
+ data: usage(outcome.result),
279
+ });
280
+ await this.effects.evidence.retainSemanticVerdicts(
281
+ outcome.result.finalMessage,
282
+ suffix,
283
+ );
284
+ successful = this.parseSemanticResults(
285
+ outcome.result,
286
+ group,
287
+ pattern,
288
+ outcome.source,
289
+ outcome.hostId,
290
+ );
291
+ } finally {
292
+ await this.finishWorkspace(workspace, successful);
293
+ }
294
+ }
295
+ private async finishWorkspace(
296
+ workspace: string,
297
+ successful: boolean,
298
+ ): Promise<void> {
299
+ if (successful) await clearFixtureContents(workspace);
300
+ else await this.effects.retainWorkspace(workspace);
283
301
  }
284
302
  }
285
303
  export async function gradeSemanticChecks(