static-fire 0.0.0-stage → 0.44.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.
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/bin/static-fire.js +46 -0
- package/package.json +29 -4
- package/pricing.yaml +20 -0
- package/src/adapters/index.ts +153 -0
- package/src/api.ts +218 -0
- package/src/cli.ts +297 -0
- package/src/core/apkit-install.ts +47 -0
- package/src/core/authored.ts +47 -0
- package/src/core/batch-state.ts +25 -0
- package/src/core/batch.ts +435 -0
- package/src/core/batches.ts +221 -0
- package/src/core/checks.ts +63 -0
- package/src/core/commands.ts +42 -0
- package/src/core/compare.ts +203 -0
- package/src/core/config.ts +25 -0
- package/src/core/copying.ts +18 -0
- package/src/core/db.ts +572 -0
- package/src/core/derived.ts +74 -0
- package/src/core/engine.ts +85 -0
- package/src/core/facets.ts +128 -0
- package/src/core/grade.ts +197 -0
- package/src/core/grading-versions.ts +281 -0
- package/src/core/identity.ts +263 -0
- package/src/core/init.ts +231 -0
- package/src/core/judge-schema.ts +37 -0
- package/src/core/judge.ts +166 -0
- package/src/core/label.ts +43 -0
- package/src/core/lock.ts +40 -0
- package/src/core/migrate.ts +288 -0
- package/src/core/new-task.ts +85 -0
- package/src/core/outside-tools.ts +129 -0
- package/src/core/profiles.ts +42 -0
- package/src/core/project.ts +54 -0
- package/src/core/regrade.ts +233 -0
- package/src/core/score.ts +148 -0
- package/src/core/settings.ts +44 -0
- package/src/core/task-file.ts +95 -0
- package/src/core/trial-detail.ts +73 -0
- package/src/core/types.ts +195 -0
- package/src/core/view-query.ts +85 -0
- package/src/core/view.ts +498 -0
- package/src/guide.md +547 -0
- package/src/server.ts +24 -0
- package/web/api.ts +6 -0
- package/web/app.tsx +1019 -0
- package/web/brand/offset-tokens.css +126 -0
- package/web/charts.tsx +190 -0
- package/web/compare.tsx +317 -0
- package/web/encoding.tsx +55 -0
- package/web/filter-menu.ts +164 -0
- package/web/index.html +17 -0
- package/web/pages.ts +16 -0
- package/web/run-status.ts +51 -0
- package/web/styles.css +333 -0
- package/web/suite-notes.ts +20 -0
- package/web/wording.ts +38 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// The primitives (see CONTEXT.md): Suite → Task → Measure → Criterion → Grader → Verdict.
|
|
2
|
+
// Trials, gradings and batches are records in the record store (src/core/db.ts).
|
|
3
|
+
|
|
4
|
+
/** Agent harnesses run a model headless. `human` is a person: its output is a file they wrote. */
|
|
5
|
+
export type AgentHarness = "claude" | "codex" | "pi" | "agy";
|
|
6
|
+
export type Harness = AgentHarness | "human";
|
|
7
|
+
export const HARNESSES: Harness[] = ["claude", "codex", "pi", "agy", "human"];
|
|
8
|
+
|
|
9
|
+
export interface HumanOutput { path: string; note?: string }
|
|
10
|
+
|
|
11
|
+
export interface AgentConfig {
|
|
12
|
+
id: string; // file name in configs/ (the label is built from the fields: core/label.ts)
|
|
13
|
+
harness: Harness;
|
|
14
|
+
model: string; // "by hand" for a human config
|
|
15
|
+
effort?: string;
|
|
16
|
+
provider?: string; // pi only
|
|
17
|
+
profile?: string; // apkit Profile name; omitted = no Profile
|
|
18
|
+
family?: string; // model family for chart colour; defaults to a guess from `model`
|
|
19
|
+
outputs?: Record<string, HumanOutput>; // human only: task id → the file they wrote (absolute path)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface JudgeConfig {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
harness: Exclude<AgentHarness, "pi">; // judge Hosts must support JSON Schema output
|
|
26
|
+
model: string;
|
|
27
|
+
effort?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---- Task definition -------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export type Level = "floor" | "strong" | "ceiling";
|
|
33
|
+
export type Evidence = "output" | "inputs" | "references" | "project" | "sources";
|
|
34
|
+
|
|
35
|
+
export type Check =
|
|
36
|
+
| { type: "regex"; pattern: string; flags?: string; min?: number; max?: number }
|
|
37
|
+
| { type: "words"; min?: number; max?: number }
|
|
38
|
+
| { type: "frontmatter"; field: string; minChars?: number; maxChars?: number }
|
|
39
|
+
| { type: "command"; run: string; timeoutSec?: number };
|
|
40
|
+
|
|
41
|
+
export interface Criterion {
|
|
42
|
+
id: string;
|
|
43
|
+
question: string; // phrased so that "yes" = pass
|
|
44
|
+
level: Level;
|
|
45
|
+
grader: "check" | "judge" | "human";
|
|
46
|
+
check?: Check; // grader: check
|
|
47
|
+
evidence?: Evidence[]; // grader: judge (default: ["output"])
|
|
48
|
+
isolate?: boolean; // grader: judge — its own call instead of the measure's batch
|
|
49
|
+
derived?: string; // grader: judge — a script under the task's grader/ folder that makes an image from the output; the judge sees it (README, "Derived evidence")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Measure {
|
|
53
|
+
id: string;
|
|
54
|
+
label: string;
|
|
55
|
+
weight?: number; // default 1
|
|
56
|
+
criteria: Criterion[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface Task {
|
|
60
|
+
id: string;
|
|
61
|
+
version?: number; // a readable label only, in neither hash; a task file can leave it out
|
|
62
|
+
title: string;
|
|
63
|
+
description: string;
|
|
64
|
+
prompt: string;
|
|
65
|
+
inputs: string; // copied into the trial workspace
|
|
66
|
+
output: string; // path the agent must write, relative to the workspace
|
|
67
|
+
references?: string; // graders only
|
|
68
|
+
project?: string; // files the agent would find in the real project (copied into the workspace root, not shown to graders)
|
|
69
|
+
sources?: string; // graders only: saved copies of the pages the notes link to, for fact checks
|
|
70
|
+
heldOut?: string; // the real original this task was built from; an agent output that copies it is invalid
|
|
71
|
+
context?: string; // one paragraph a judge reads before the criteria
|
|
72
|
+
gates: Criterion[];
|
|
73
|
+
measures: Measure[];
|
|
74
|
+
dir: string; // absolute task directory (set by the loader)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface Suite {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
tasks: string[];
|
|
82
|
+
weights?: Record<string, number>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- Results -----------------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
export interface Usage {
|
|
88
|
+
inputTokens: number;
|
|
89
|
+
outputTokens: number;
|
|
90
|
+
cacheReadTokens: number;
|
|
91
|
+
cacheWriteTokens: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface AgentResult {
|
|
95
|
+
ok: boolean;
|
|
96
|
+
error?: string;
|
|
97
|
+
usage: Usage;
|
|
98
|
+
costUsd: number | null;
|
|
99
|
+
costSource: "reported" | "estimated" | "unknown";
|
|
100
|
+
turns?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface Vote { pass: boolean; reason: string }
|
|
104
|
+
|
|
105
|
+
export interface Verdict {
|
|
106
|
+
score: number | null; // 0–1 (judge: share of votes passed); null = error
|
|
107
|
+
votes: Vote[];
|
|
108
|
+
error?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface Grades {
|
|
112
|
+
verdicts: Record<string, Verdict>; // by criterion id, gates included
|
|
113
|
+
gatesPassed: boolean;
|
|
114
|
+
measures: Record<string, number>; // 0–100 per measure
|
|
115
|
+
score: number; // 0–100 task score
|
|
116
|
+
errors: string[]; // criterion ids that errored (→ trial invalid)
|
|
117
|
+
judgeCostUsd: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type TrialStatus = "queued" | "running" | "grading" | "done" | "invalid" | "failed";
|
|
121
|
+
|
|
122
|
+
export interface Trial {
|
|
123
|
+
id: string; // the trial id
|
|
124
|
+
experimentId: string; // the batch id (the name stays because tests/score.test.ts sets it)
|
|
125
|
+
taskId: string;
|
|
126
|
+
configId: string;
|
|
127
|
+
index: number;
|
|
128
|
+
status: TrialStatus;
|
|
129
|
+
startedAt?: string;
|
|
130
|
+
endedAt?: string;
|
|
131
|
+
durationMs?: number; // agent wall-clock only
|
|
132
|
+
result?: AgentResult;
|
|
133
|
+
grades?: Grades;
|
|
134
|
+
error?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The input summarize() in score.ts takes: the tasks, suites and configs of one view (view.ts builds it).
|
|
139
|
+
* It is also the shape of the old `experiment.json` that migrate.ts imports. Nothing writes it any more.
|
|
140
|
+
* The name stays because score.ts and tests/score.test.ts use it, and both stay as they are.
|
|
141
|
+
*/
|
|
142
|
+
export interface Experiment {
|
|
143
|
+
schema: 2;
|
|
144
|
+
id: string;
|
|
145
|
+
suiteIds: string[];
|
|
146
|
+
tasks: Record<string, number | null>; // task id → version at run time (null: the task file has none)
|
|
147
|
+
configIds: string[];
|
|
148
|
+
trialsPerConfig: number;
|
|
149
|
+
judge: JudgeConfig & { votes: number };
|
|
150
|
+
createdAt: string;
|
|
151
|
+
status: "running" | "done";
|
|
152
|
+
configs: Record<string, AgentConfig>; // snapshot at run time
|
|
153
|
+
suites: Record<string, Suite>; // snapshot at run time
|
|
154
|
+
userSetup?: { files: Record<string, string> }; // the user-level setup trials ran with (path → hash)
|
|
155
|
+
blockedTools?: Partial<Record<AgentHarness, string[]>>; // outside-service tools blocked in trials
|
|
156
|
+
gradedAt?: string; // when the current grading started; createdAt before any regrade
|
|
157
|
+
taskDefs?: Record<string, TaskSnapshot>; // each task's criteria as the current grading used them
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A task's definition as one grading setup used it, so an earlier grading setup shows its own criteria. */
|
|
161
|
+
export type TaskSnapshot = Pick<Task, "id" | "title" | "description" | "version" | "gates" | "measures">;
|
|
162
|
+
|
|
163
|
+
// ---- Summaries (what the dashboard and `static-fire show` read) -------------------------------------
|
|
164
|
+
|
|
165
|
+
export interface Stat { mean: number | null; ci: number | null; n: number }
|
|
166
|
+
|
|
167
|
+
export interface TaskSummary {
|
|
168
|
+
score: Stat;
|
|
169
|
+
measures: Record<string, number>; // mean 0–100 per measure
|
|
170
|
+
criteria: Record<string, number>; // mean pass rate 0–1 per criterion
|
|
171
|
+
costUsd: number | null;
|
|
172
|
+
costSource: AgentResult["costSource"];
|
|
173
|
+
durationMs: number | null;
|
|
174
|
+
valid: number; invalid: number; failed: number; total: number;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ConfigSummary {
|
|
178
|
+
configId: string;
|
|
179
|
+
label: string;
|
|
180
|
+
harness: Harness;
|
|
181
|
+
model: string;
|
|
182
|
+
family: string;
|
|
183
|
+
effort?: string;
|
|
184
|
+
profile?: string;
|
|
185
|
+
tasks: Record<string, TaskSummary>;
|
|
186
|
+
suites: Record<string, Stat>; // Suite Index per suite
|
|
187
|
+
index: Stat; // Workflow Index
|
|
188
|
+
costUsd: number | null; // mean cost per task over all tasks
|
|
189
|
+
durationMs: number | null; // mean time per task
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ExperimentSummary {
|
|
193
|
+
configs: ConfigSummary[];
|
|
194
|
+
deadCriteria: Record<string, string[]>; // task id → criterion ids every config passed or failed
|
|
195
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The query of GET /api/view, shared by the server and the dashboard (no Node imports here).
|
|
2
|
+
// One table of parameters: a filter added to the view is one more field in ViewQuery and one more entry in
|
|
3
|
+
// SINGLE (one value) or CONFIG_FACETS (repeated, several values), and in PARAMS for its place in the URL. Both
|
|
4
|
+
// the route and the dashboard's loading code pick it up. This file is the one home of the config facet list.
|
|
5
|
+
import type { ViewFilters } from "./view";
|
|
6
|
+
|
|
7
|
+
/** What the chart is about: the Workflow Index over every suite, one suite, or one task. */
|
|
8
|
+
export type Scope = { kind: "index" } | { kind: "suite"; id: string } | { kind: "task"; id: string };
|
|
9
|
+
|
|
10
|
+
/** The view a page asks for. Every field is optional; an empty object is the default view. */
|
|
11
|
+
export interface ViewQuery {
|
|
12
|
+
scope?: Scope;
|
|
13
|
+
judge?: string;
|
|
14
|
+
grading?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Config facets: the picked values of each. A trial matches a facet when its value is any of them.
|
|
17
|
+
* `none` selects configs without an effort or without a Profile.
|
|
18
|
+
*/
|
|
19
|
+
harness?: string[];
|
|
20
|
+
model?: string[];
|
|
21
|
+
effort?: string[];
|
|
22
|
+
profile?: string[];
|
|
23
|
+
since?: string;
|
|
24
|
+
until?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Scope in a URL: absent or `index`, `suite:<id>`, `task:<id>`. */
|
|
28
|
+
export const scopeParam = (s: Scope): string => (s.kind === "index" ? "index" : `${s.kind}:${s.id}`);
|
|
29
|
+
|
|
30
|
+
function parseScope(v: string): Scope {
|
|
31
|
+
if (v === "index") return { kind: "index" };
|
|
32
|
+
const m = /^(suite|task):(.+)$/.exec(v);
|
|
33
|
+
if (!m) throw new Error(`scope ${v} is not index, suite:<id> or task:<id>`);
|
|
34
|
+
return { kind: m[1] as "suite" | "task", id: m[2]! };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parameters that take one value each. A view has one judge, one grading version and one date range. */
|
|
38
|
+
type Single = "judge" | "grading" | "since" | "until";
|
|
39
|
+
const SINGLE: Single[] = ["judge", "grading", "since", "until"];
|
|
40
|
+
/** The config facets, each a field of the trial row: the parameter repeats for each picked value (`?model=opus&model=haiku`). */
|
|
41
|
+
export const CONFIG_FACETS = ["harness", "model", "effort", "profile"] as const;
|
|
42
|
+
export type ConfigFacet = (typeof CONFIG_FACETS)[number];
|
|
43
|
+
/** Every parameter, in the order a URL the page writes lists them. */
|
|
44
|
+
const PARAMS: readonly ("scope" | Single | ConfigFacet)[] = ["scope", "judge", "grading", "harness", "model", "effort", "profile", "since", "until"];
|
|
45
|
+
const KNOWN = new Set<string>(["scope", ...SINGLE, ...CONFIG_FACETS]);
|
|
46
|
+
const isConfig = (k: string): k is ConfigFacet => (CONFIG_FACETS as readonly string[]).includes(k);
|
|
47
|
+
|
|
48
|
+
/** The values in one stable order, each once, with empty ones dropped: a set of picks has one URL. */
|
|
49
|
+
export const valueSet = (values: Iterable<string>): string[] => [...new Set(values)].filter(Boolean).sort();
|
|
50
|
+
|
|
51
|
+
/** Reads a query string. Throws on an unknown or repeated parameter or a bad scope, so a filter is never silently ignored. */
|
|
52
|
+
export function parseViewQuery(params: URLSearchParams): ViewQuery {
|
|
53
|
+
const unknown = [...new Set([...params.keys()].filter((k) => !KNOWN.has(k)))];
|
|
54
|
+
if (unknown.length) throw new Error(`unknown view parameter(s): ${unknown.join(", ")} (known: ${PARAMS.join(", ")})`);
|
|
55
|
+
const repeated = (["scope", ...SINGLE] as string[]).filter((k) => params.getAll(k).length > 1);
|
|
56
|
+
if (repeated.length) throw new Error(`view parameter(s) ${repeated.join(", ")} can take only one value (repeat only ${CONFIG_FACETS.join(", ")})`);
|
|
57
|
+
const q: ViewQuery = {};
|
|
58
|
+
const scope = params.get("scope");
|
|
59
|
+
if (scope) q.scope = parseScope(scope);
|
|
60
|
+
for (const k of SINGLE) { const v = params.get(k); if (v) q[k] = v; }
|
|
61
|
+
for (const k of CONFIG_FACETS) { const v = valueSet(params.getAll(k)); if (v.length) q[k] = v; }
|
|
62
|
+
return q;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The query string for `q`: only the fields that are set; the index scope is the default, so it is left out. */
|
|
66
|
+
export function viewQueryParams(q: ViewQuery): URLSearchParams {
|
|
67
|
+
const p = new URLSearchParams();
|
|
68
|
+
if (q.scope && q.scope.kind !== "index") p.set("scope", scopeParam(q.scope));
|
|
69
|
+
for (const k of PARAMS) {
|
|
70
|
+
if (k === "scope") continue;
|
|
71
|
+
if (isConfig(k)) for (const v of valueSet(q[k] ?? [])) p.append(k, v);
|
|
72
|
+
else if (q[k]) p.set(k, q[k]!);
|
|
73
|
+
}
|
|
74
|
+
return p;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The view function's filters for `q`: a suite scope is that suite, a task scope that task. */
|
|
78
|
+
export function toViewFilters(q: ViewQuery): ViewFilters {
|
|
79
|
+
const f: ViewFilters = {};
|
|
80
|
+
if (q.scope?.kind === "suite") f.suites = [q.scope.id];
|
|
81
|
+
if (q.scope?.kind === "task") f.tasks = [q.scope.id];
|
|
82
|
+
for (const k of SINGLE) if (q[k]) f[k] = q[k];
|
|
83
|
+
for (const k of CONFIG_FACETS) { const v = valueSet(q[k] ?? []); if (v.length) f[k] = v; }
|
|
84
|
+
return f;
|
|
85
|
+
}
|