@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.
- package/CONTRIBUTING.md +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
package/src/report.ts
CHANGED
|
@@ -1,86 +1,26 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CliResultData,
|
|
3
|
+
ReportData,
|
|
4
|
+
RunEvidenceData,
|
|
5
|
+
} from "./schema-types";
|
|
1
6
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute } from "node:path";
|
|
7
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
8
|
import { parseArgs } from "node:util";
|
|
4
9
|
import { canonicalJson } from "./identity";
|
|
5
10
|
import { assertCliResult, assertReport, assertRunEvidence } from "./schema";
|
|
11
|
+
import { canonicalRuntimeRoot } from "./runtime/paths";
|
|
12
|
+
import { nativeTranscriptRetention } from "./transcript-retention";
|
|
6
13
|
|
|
7
|
-
type
|
|
8
|
-
type
|
|
9
|
-
type
|
|
10
|
-
type Trial =
|
|
11
|
-
trial: number;
|
|
12
|
-
execution: State;
|
|
13
|
-
grading: State;
|
|
14
|
-
task: Verdict;
|
|
15
|
-
domainOutcomes?: DomainOutcome[];
|
|
16
|
-
};
|
|
17
|
-
type Case = {
|
|
18
|
-
caseId: string;
|
|
19
|
-
execution: State;
|
|
20
|
-
grading: State;
|
|
21
|
-
task: Verdict;
|
|
22
|
-
trials: Trial[];
|
|
23
|
-
};
|
|
24
|
-
type CliResult = {
|
|
25
|
-
format: string;
|
|
26
|
-
runId: string | null;
|
|
27
|
-
execution: State;
|
|
28
|
-
grading: State;
|
|
29
|
-
task: Verdict;
|
|
30
|
-
exitCode: number;
|
|
31
|
-
evidencePath: string | null;
|
|
32
|
-
cases: Case[];
|
|
33
|
-
};
|
|
34
|
-
type Usage = {
|
|
35
|
-
inputTokens: number | null;
|
|
36
|
-
outputTokens: number | null;
|
|
37
|
-
costUsd: number | null;
|
|
38
|
-
complete: boolean;
|
|
39
|
-
};
|
|
40
|
-
type TrialEvidence = {
|
|
41
|
-
caseId: string;
|
|
42
|
-
trial: number;
|
|
43
|
-
executionMode: string;
|
|
44
|
-
candidateDurationMs?: number | null;
|
|
45
|
-
usage: Usage;
|
|
46
|
-
};
|
|
47
|
-
type Evidence = {
|
|
48
|
-
runId: string;
|
|
49
|
-
result: CliResult;
|
|
50
|
-
runner: { buildDigest: string };
|
|
51
|
-
evaluationIdentity: { digest: string };
|
|
52
|
-
condition: { requested: string; actual: string };
|
|
53
|
-
routes: Array<{ role: string; host: string; model: string; effort: string }>;
|
|
54
|
-
trials: TrialEvidence[];
|
|
55
|
-
};
|
|
14
|
+
type CliResult = CliResultData;
|
|
15
|
+
type Evidence = RunEvidenceData;
|
|
16
|
+
type Case = CliResult["cases"][number];
|
|
17
|
+
type Trial = Case["trials"][number];
|
|
56
18
|
|
|
57
|
-
export type ReportRow =
|
|
58
|
-
resultFile: string;
|
|
59
|
-
evidencePath: string | null;
|
|
60
|
-
runId: string | null;
|
|
61
|
-
caseId: string;
|
|
62
|
-
execution: string;
|
|
63
|
-
grading: string;
|
|
64
|
-
task: string;
|
|
65
|
-
exitCode: number;
|
|
66
|
-
trialCount: number;
|
|
67
|
-
taskPassRate: number | null;
|
|
68
|
-
candidateDurationMs: number | null;
|
|
69
|
-
inputTokens: number | null;
|
|
70
|
-
outputTokens: number | null;
|
|
71
|
-
costUsd: number | null;
|
|
72
|
-
runnerBuildDigest: string | null;
|
|
73
|
-
evaluationDigest: string | null;
|
|
74
|
-
requestedCondition: string | null;
|
|
75
|
-
actualCondition: string | null;
|
|
76
|
-
candidateRoute: { host: string; model: string; effort: string } | null;
|
|
77
|
-
routes: Array<{ role: string; host: string; model: string; effort: string }>;
|
|
78
|
-
domainOutcomes: Array<{ trial: number; id: string; status: string }>;
|
|
79
|
-
};
|
|
19
|
+
export type ReportRow = ReportData["rows"][number];
|
|
80
20
|
|
|
81
21
|
function sumKnown(values: Array<number | null | undefined>): number | null {
|
|
82
22
|
return values.length && values.every((value) => value != null)
|
|
83
|
-
? values.reduce<number>((total, value) => total + value
|
|
23
|
+
? values.reduce<number>((total, value) => total + value, 0)
|
|
84
24
|
: null;
|
|
85
25
|
}
|
|
86
26
|
|
|
@@ -104,24 +44,17 @@ function passRate(trials: Trial[]): number | null {
|
|
|
104
44
|
async function loadResult(path: string) {
|
|
105
45
|
if (!isAbsolute(path))
|
|
106
46
|
throw new Error("report result paths must be absolute");
|
|
107
|
-
const result = JSON.parse(await readFile(path, "utf8"))
|
|
47
|
+
const result: unknown = JSON.parse(await readFile(path, "utf8"));
|
|
108
48
|
assertCliResult(result);
|
|
109
|
-
return result
|
|
49
|
+
return result;
|
|
110
50
|
}
|
|
111
51
|
|
|
112
52
|
async function loadEvidence(result: CliResult): Promise<Evidence | null> {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
if (!isAbsolute(result.evidencePath))
|
|
119
|
-
throw new Error("evidence path must be absolute");
|
|
120
|
-
const value = JSON.parse(
|
|
121
|
-
await readFile(result.evidencePath, "utf8"),
|
|
122
|
-
) as unknown;
|
|
53
|
+
const path = retainedEvidencePath(result);
|
|
54
|
+
if (path === null) return null;
|
|
55
|
+
const value: unknown = JSON.parse(await readFile(path, "utf8"));
|
|
123
56
|
assertRunEvidence(value);
|
|
124
|
-
const evidence = value
|
|
57
|
+
const evidence = value;
|
|
125
58
|
if (
|
|
126
59
|
evidence.runId !== result.runId ||
|
|
127
60
|
canonicalJson(evidence.result) !== canonicalJson(result)
|
|
@@ -130,6 +63,17 @@ async function loadEvidence(result: CliResult): Promise<Evidence | null> {
|
|
|
130
63
|
return evidence;
|
|
131
64
|
}
|
|
132
65
|
|
|
66
|
+
function retainedEvidencePath(result: CliResult): string | null {
|
|
67
|
+
if (!result.evidencePath) {
|
|
68
|
+
if (result.cases.length || result.exitCode === 0)
|
|
69
|
+
throw new Error("completed result has no retained evidence");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!isAbsolute(result.evidencePath))
|
|
73
|
+
throw new Error("evidence path must be absolute");
|
|
74
|
+
return result.evidencePath;
|
|
75
|
+
}
|
|
76
|
+
|
|
133
77
|
function trialEvidence(
|
|
134
78
|
caseId: string,
|
|
135
79
|
trials: Trial[],
|
|
@@ -153,15 +97,11 @@ function reportRow(
|
|
|
153
97
|
result: CliResult,
|
|
154
98
|
selected: Case,
|
|
155
99
|
evidence: Evidence | null,
|
|
100
|
+
transcripts: Awaited<ReturnType<typeof nativeTranscriptRetention>>,
|
|
156
101
|
): ReportRow {
|
|
157
102
|
const retained = trialEvidence(selected.caseId, selected.trials, evidence);
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
if (evidence && candidateRoutes.length !== 1)
|
|
161
|
-
throw new Error("retained candidate route is ambiguous");
|
|
162
|
-
const completeUsage =
|
|
163
|
-
retained.length > 0 && retained.every((trial) => trial.usage.complete);
|
|
164
|
-
const route = candidateRoutes[0];
|
|
103
|
+
const candidateRoute = retainedCandidateRoute(evidence);
|
|
104
|
+
const usage = retainedUsage(retained);
|
|
165
105
|
return {
|
|
166
106
|
resultFile: path,
|
|
167
107
|
evidencePath: result.evidencePath,
|
|
@@ -176,23 +116,17 @@ function reportRow(
|
|
|
176
116
|
candidateDurationMs: sumKnown(
|
|
177
117
|
retained.map((trial) => trial.candidateDurationMs),
|
|
178
118
|
),
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
requestedCondition: evidence?.condition.requested ?? null,
|
|
191
|
-
actualCondition: evidence?.condition.actual ?? null,
|
|
192
|
-
candidateRoute: route
|
|
193
|
-
? { host: route.host, model: route.model, effort: route.effort }
|
|
194
|
-
: null,
|
|
195
|
-
routes: evidence?.routes ?? [],
|
|
119
|
+
...usage,
|
|
120
|
+
...retainedIdentity(evidence),
|
|
121
|
+
candidateRoute,
|
|
122
|
+
nativeTranscripts: {
|
|
123
|
+
retained: transcripts.filter(
|
|
124
|
+
(item) => item.caseId === selected.caseId && !item.pruned,
|
|
125
|
+
).length,
|
|
126
|
+
pruned: transcripts.filter(
|
|
127
|
+
(item) => item.caseId === selected.caseId && item.pruned,
|
|
128
|
+
).length,
|
|
129
|
+
},
|
|
196
130
|
domainOutcomes: selected.trials.flatMap((trial) =>
|
|
197
131
|
(trial.domainOutcomes ?? []).map((outcome) => ({
|
|
198
132
|
trial: trial.trial,
|
|
@@ -203,21 +137,58 @@ function reportRow(
|
|
|
203
137
|
};
|
|
204
138
|
}
|
|
205
139
|
|
|
140
|
+
function retainedCandidateRoute(
|
|
141
|
+
evidence: Evidence | null,
|
|
142
|
+
): ReportRow["candidateRoute"] {
|
|
143
|
+
if (!evidence) return null;
|
|
144
|
+
const routes = evidence.routes.filter((route) => route.role === "candidate"),
|
|
145
|
+
[route] = routes;
|
|
146
|
+
if (routes.length !== 1 || route === undefined)
|
|
147
|
+
throw new Error("retained candidate route is ambiguous");
|
|
148
|
+
return { host: route.host, model: route.model, effort: route.effort };
|
|
149
|
+
}
|
|
150
|
+
function retainedUsage(retained: Evidence["trials"]) {
|
|
151
|
+
const complete =
|
|
152
|
+
retained.length > 0 && retained.every((trial) => trial.usage.complete);
|
|
153
|
+
return {
|
|
154
|
+
inputTokens: complete
|
|
155
|
+
? sumKnown(retained.map((trial) => trial.usage.inputTokens))
|
|
156
|
+
: null,
|
|
157
|
+
outputTokens: complete
|
|
158
|
+
? sumKnown(retained.map((trial) => trial.usage.outputTokens))
|
|
159
|
+
: null,
|
|
160
|
+
costUsd: complete
|
|
161
|
+
? sumKnown(retained.map((trial) => trial.usage.costUsd))
|
|
162
|
+
: null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function retainedIdentity(evidence: Evidence | null) {
|
|
166
|
+
if (!evidence)
|
|
167
|
+
return {
|
|
168
|
+
runnerBuildDigest: null,
|
|
169
|
+
evaluationDigest: null,
|
|
170
|
+
requestedCondition: null,
|
|
171
|
+
actualCondition: null,
|
|
172
|
+
routes: [],
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
runnerBuildDigest: evidence.runner.buildDigest,
|
|
176
|
+
evaluationDigest: evidence.evaluationIdentity.digest,
|
|
177
|
+
requestedCondition: evidence.condition.requested,
|
|
178
|
+
actualCondition: evidence.condition.actual,
|
|
179
|
+
routes: evidence.routes,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
206
183
|
export async function createReport(paths: string[]) {
|
|
207
184
|
if (!paths.length || new Set(paths).size !== paths.length)
|
|
208
185
|
throw new Error("report needs distinct result files");
|
|
209
186
|
const rows: ReportRow[] = [];
|
|
210
|
-
const inputs:
|
|
211
|
-
resultFile: string;
|
|
212
|
-
execution: string;
|
|
213
|
-
grading: string;
|
|
214
|
-
task: string;
|
|
215
|
-
exitCode: number;
|
|
216
|
-
caseCount: number;
|
|
217
|
-
}> = [];
|
|
187
|
+
const inputs: ReportData["inputs"] = [];
|
|
218
188
|
for (const path of paths) {
|
|
219
189
|
const result = await loadResult(path);
|
|
220
190
|
const evidence = await loadEvidence(result);
|
|
191
|
+
const transcripts = await nativeTranscriptRetention(evidence);
|
|
221
192
|
inputs.push({
|
|
222
193
|
resultFile: path,
|
|
223
194
|
execution: result.execution.status,
|
|
@@ -227,7 +198,7 @@ export async function createReport(paths: string[]) {
|
|
|
227
198
|
caseCount: result.cases.length,
|
|
228
199
|
});
|
|
229
200
|
for (const selected of result.cases)
|
|
230
|
-
rows.push(reportRow(path, result, selected, evidence));
|
|
201
|
+
rows.push(reportRow(path, result, selected, evidence, transcripts));
|
|
231
202
|
}
|
|
232
203
|
const report = {
|
|
233
204
|
format: "sevro.report.v1",
|
|
@@ -279,15 +250,13 @@ export function renderReport(
|
|
|
279
250
|
"",
|
|
280
251
|
"| Case | Task | Execution | Grading | Pass rate | Candidate time | Candidate tokens | Candidate cost | Condition | Candidate route |",
|
|
281
252
|
"| --- | --- | --- | --- | ---: | ---: | ---: | ---: | --- | --- |",
|
|
282
|
-
...report.rows.map(
|
|
283
|
-
(row) =>
|
|
284
|
-
`| ${cell(row.caseId)} | ${row.task} | ${row.execution} | ${row.grading} | ${row.taskPassRate === null ? "unknown" : `${(row.taskPassRate * 100).toFixed(0)}%`} | ${seconds(row.candidateDurationMs)} | ${measured(row.inputTokens)} / ${measured(row.outputTokens)} | ${row.costUsd === null ? "unknown" : `$${row.costUsd.toFixed(4)}`} | ${row.requestedCondition ?? "unknown"} / ${row.actualCondition ?? "unknown"} | ${row.candidateRoute ? cell(`${row.candidateRoute.host}/${row.candidateRoute.model}@${row.candidateRoute.effort}`) : "unknown"} |`,
|
|
285
|
-
),
|
|
253
|
+
...report.rows.map(caseOutcomeLine),
|
|
286
254
|
"",
|
|
287
255
|
"## Retained evidence",
|
|
288
256
|
"",
|
|
289
257
|
...report.rows.map(
|
|
290
|
-
(row) =>
|
|
258
|
+
(row) =>
|
|
259
|
+
`- ${cell(row.caseId)}: ${row.evidencePath ?? "unavailable"}${transcriptSummary(row)}`,
|
|
291
260
|
),
|
|
292
261
|
"",
|
|
293
262
|
];
|
|
@@ -324,28 +293,91 @@ export function renderReport(
|
|
|
324
293
|
return lines.join("\n");
|
|
325
294
|
}
|
|
326
295
|
|
|
296
|
+
function transcriptSummary(row: ReportRow): string {
|
|
297
|
+
if (!row.nativeTranscripts) return "";
|
|
298
|
+
return `; native transcripts: ${row.nativeTranscripts.retained} retained, ${row.nativeTranscripts.pruned} pruned`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function passRateCell(rate: number | null): string {
|
|
302
|
+
return rate === null ? "unknown" : `${(rate * 100).toFixed(0)}%`;
|
|
303
|
+
}
|
|
304
|
+
function costCell(cost: number | null): string {
|
|
305
|
+
return cost === null ? "unknown" : `$${cost.toFixed(4)}`;
|
|
306
|
+
}
|
|
307
|
+
function routeCell(route: ReportRow["candidateRoute"]): string {
|
|
308
|
+
return route
|
|
309
|
+
? cell(`${route.host}/${route.model}@${route.effort}`)
|
|
310
|
+
: "unknown";
|
|
311
|
+
}
|
|
312
|
+
function conditionCell(row: ReportRow): string {
|
|
313
|
+
return `${row.requestedCondition ?? "unknown"} / ${row.actualCondition ?? "unknown"}`;
|
|
314
|
+
}
|
|
315
|
+
function caseOutcomeLine(row: ReportRow): string {
|
|
316
|
+
return `| ${cell(row.caseId)} | ${row.task} | ${row.execution} | ${row.grading} | ${passRateCell(row.taskPassRate)} | ${seconds(row.candidateDurationMs)} | ${measured(row.inputTokens)} / ${measured(row.outputTokens)} | ${costCell(row.costUsd)} | ${conditionCell(row)} | ${routeCell(row.candidateRoute)} |`;
|
|
317
|
+
}
|
|
318
|
+
|
|
327
319
|
export async function reportCommand(argv: string[]): Promise<number> {
|
|
328
320
|
try {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
allowPositionals: true,
|
|
332
|
-
strict: true,
|
|
333
|
-
options: {
|
|
334
|
-
json: { type: "boolean" },
|
|
335
|
-
"result-file": { type: "string", multiple: true },
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
if (positionals.length !== 1 || positionals[0] !== "report")
|
|
339
|
-
throw new Error("expected the report command");
|
|
340
|
-
const report = await createReport(values["result-file"] ?? []);
|
|
321
|
+
const values = reportInvocation(argv);
|
|
322
|
+
const report = await createReport(await reportPaths(values));
|
|
341
323
|
process.stdout.write(
|
|
342
324
|
values.json ? `${JSON.stringify(report)}\n` : renderReport(report),
|
|
343
325
|
);
|
|
344
326
|
return 0;
|
|
345
327
|
} catch (error) {
|
|
346
|
-
process.stderr.write(
|
|
347
|
-
`${error instanceof Error ? error.message : "invalid report input"}\n`,
|
|
348
|
-
);
|
|
328
|
+
process.stderr.write(`${reportErrorMessage(error)}\n`);
|
|
349
329
|
return 64;
|
|
350
330
|
}
|
|
351
331
|
}
|
|
332
|
+
|
|
333
|
+
function reportInvocation(argv: string[]) {
|
|
334
|
+
const { values, positionals } = parseArgs({
|
|
335
|
+
args: argv,
|
|
336
|
+
allowPositionals: true,
|
|
337
|
+
strict: true,
|
|
338
|
+
options: {
|
|
339
|
+
json: { type: "boolean" },
|
|
340
|
+
"result-file": { type: "string", multiple: true },
|
|
341
|
+
latest: { type: "boolean" },
|
|
342
|
+
"results-root": { type: "string" },
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
if (positionals.length !== 1 || positionals[0] !== "report")
|
|
346
|
+
throw new Error("expected the report command");
|
|
347
|
+
return values;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function reportPaths(
|
|
351
|
+
values: ReturnType<typeof reportInvocation>,
|
|
352
|
+
): Promise<string[]> {
|
|
353
|
+
validateReportSource(values);
|
|
354
|
+
const paths = selectedReportPaths(values);
|
|
355
|
+
if (paths.some((path) => !path.trim()))
|
|
356
|
+
throw new Error("--result-file must name a readable retained result file");
|
|
357
|
+
return Promise.all(paths.map((path) => canonicalRuntimeRoot(resolve(path))));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function validateReportSource(
|
|
361
|
+
values: ReturnType<typeof reportInvocation>,
|
|
362
|
+
): void {
|
|
363
|
+
if (values.latest && values["result-file"] !== undefined)
|
|
364
|
+
throw new Error("--latest and --result-file are exclusive");
|
|
365
|
+
if (values["results-root"] !== undefined && !values.latest)
|
|
366
|
+
throw new Error("--results-root requires --latest");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function selectedReportPaths(
|
|
370
|
+
values: ReturnType<typeof reportInvocation>,
|
|
371
|
+
): string[] {
|
|
372
|
+
return values.latest
|
|
373
|
+
? [
|
|
374
|
+
join(
|
|
375
|
+
values["results-root"] ?? join(process.cwd(), ".sevro", "results"),
|
|
376
|
+
"latest.json",
|
|
377
|
+
),
|
|
378
|
+
]
|
|
379
|
+
: (values["result-file"] ?? []);
|
|
380
|
+
}
|
|
381
|
+
function reportErrorMessage(error: unknown): string {
|
|
382
|
+
return error instanceof Error ? error.message : "invalid report input";
|
|
383
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExtensionCase } from "./extension-session";
|
|
2
|
+
import type { RepositoryFixture } from "./fixtures/repository";
|
|
3
|
+
|
|
4
|
+
function repositoryOverlay(fixture: RepositoryFixture) {
|
|
5
|
+
return {
|
|
6
|
+
...(fixture.files ? { files: fixture.files } : {}),
|
|
7
|
+
...(fixture.staged ? { staged: fixture.staged } : {}),
|
|
8
|
+
...(fixture.commitFiles ? { commitFiles: true } : {}),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function repositoryTools(fixture: RepositoryFixture) {
|
|
13
|
+
return {
|
|
14
|
+
...(fixture.hooks ? { hooks: fixture.hooks } : {}),
|
|
15
|
+
...(fixture.bin ? { bin: fixture.bin } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Translate negotiated fixture declarations into the standalone case representation. */
|
|
20
|
+
export function resolvedFixture(fixture: ExtensionCase["fixture"]) {
|
|
21
|
+
switch (fixture.kind) {
|
|
22
|
+
case "inline":
|
|
23
|
+
return { files: fixture.files };
|
|
24
|
+
case "repository":
|
|
25
|
+
return {
|
|
26
|
+
sourceRef: fixture.sourceRef,
|
|
27
|
+
...repositoryOverlay(fixture),
|
|
28
|
+
...repositoryTools(fixture),
|
|
29
|
+
};
|
|
30
|
+
case "generated":
|
|
31
|
+
return fixture;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { lstat } from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
export async function retainedFile(path: string) {
|
|
6
|
+
const info = await lstat(path).catch((cause: unknown) => {
|
|
7
|
+
if ((cause as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
8
|
+
throw cause;
|
|
9
|
+
});
|
|
10
|
+
if (info && !info.isFile())
|
|
11
|
+
throw new Error(`retained storage must be a regular file: ${path}`);
|
|
12
|
+
return info;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Serialize result publication and pruning across CLI processes. Closing rolls back the lock transaction. */
|
|
16
|
+
export async function withResultsLock<T>(
|
|
17
|
+
root: string,
|
|
18
|
+
action: () => Promise<T>,
|
|
19
|
+
): Promise<T> {
|
|
20
|
+
await retainedFile(join(root, ".retention-lock.sqlite"));
|
|
21
|
+
const database = new Database(join(root, ".retention-lock.sqlite"));
|
|
22
|
+
try {
|
|
23
|
+
database.run("PRAGMA busy_timeout = 5000");
|
|
24
|
+
database.run("BEGIN IMMEDIATE");
|
|
25
|
+
return await action();
|
|
26
|
+
} finally {
|
|
27
|
+
database.close();
|
|
28
|
+
}
|
|
29
|
+
}
|