@gaunt-sloth/batch 2.0.0-alpha.24 → 2.0.0-alpha.26

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 (52) hide show
  1. package/README.md +8 -6
  2. package/dist/BatchRunner.d.ts +20 -0
  3. package/dist/BatchRunner.js +28 -2
  4. package/dist/BatchRunner.js.map +1 -1
  5. package/dist/blindExport.d.ts +88 -0
  6. package/dist/blindExport.js +129 -0
  7. package/dist/blindExport.js.map +1 -0
  8. package/dist/classification.d.ts +52 -0
  9. package/dist/classification.js +140 -0
  10. package/dist/classification.js.map +1 -0
  11. package/dist/classificationRender.d.ts +24 -0
  12. package/dist/classificationRender.js +96 -0
  13. package/dist/classificationRender.js.map +1 -0
  14. package/dist/classificationReport.d.ts +11 -0
  15. package/dist/classificationReport.js +60 -0
  16. package/dist/classificationReport.js.map +1 -0
  17. package/dist/classificationTypes.d.ts +311 -0
  18. package/dist/classificationTypes.js +40 -0
  19. package/dist/classificationTypes.js.map +1 -0
  20. package/dist/evalCompare.d.ts +108 -0
  21. package/dist/evalCompare.js +246 -0
  22. package/dist/evalCompare.js.map +1 -0
  23. package/dist/evalRunner.d.ts +34 -3
  24. package/dist/evalRunner.js +259 -9
  25. package/dist/evalRunner.js.map +1 -1
  26. package/dist/evalSuite.d.ts +12 -2
  27. package/dist/evalSuite.js +534 -8
  28. package/dist/evalSuite.js.map +1 -1
  29. package/dist/evalTypes.d.ts +358 -5
  30. package/dist/evalTypes.js +98 -0
  31. package/dist/evalTypes.js.map +1 -1
  32. package/dist/index.d.ts +15 -1
  33. package/dist/index.js +14 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/metrics.d.ts +50 -0
  36. package/dist/metrics.js +433 -0
  37. package/dist/metrics.js.map +1 -0
  38. package/dist/pipelineCli.js +1 -1
  39. package/dist/pipelineCli.js.map +1 -1
  40. package/dist/raterTarget.d.ts +94 -0
  41. package/dist/raterTarget.js +328 -0
  42. package/dist/raterTarget.js.map +1 -0
  43. package/dist/reporters/reporterTypes.d.ts +9 -0
  44. package/dist/reporters/textReporter.js +21 -0
  45. package/dist/reporters/textReporter.js.map +1 -1
  46. package/dist/types.d.ts +14 -2
  47. package/dist/types.js +14 -2
  48. package/dist/types.js.map +1 -1
  49. package/dist/workflow/runWorkflow.d.ts +1 -1
  50. package/dist/workflow/runWorkflow.js +2 -2
  51. package/dist/workflow/runWorkflow.js.map +1 -1
  52. package/package.json +3 -3
@@ -0,0 +1,246 @@
1
+ import { formatTally } from '#src/metrics.js';
2
+ /**
3
+ * Expand a {@link EvalSweep} into its cartesian product of cells, in declared axis order.
4
+ *
5
+ * A later axis wins on a conflicting key, and that is documented rather than defended against: two
6
+ * axes that set the same config key are describing the same knob twice, which the author should see
7
+ * in the cell name.
8
+ */
9
+ export function expandSweep(sweep) {
10
+ let cells = [{ parts: [] }];
11
+ for (const axis of sweep.axes) {
12
+ const next = [];
13
+ for (const cell of cells) {
14
+ for (const value of axis.values) {
15
+ next.push({ parts: [...cell.parts, { axis: axis.name, value }] });
16
+ }
17
+ }
18
+ cells = next;
19
+ }
20
+ return cells.map((cell) => {
21
+ let model;
22
+ let config = {};
23
+ for (const part of cell.parts) {
24
+ if (part.value.model !== undefined)
25
+ model = part.value.model;
26
+ if (part.value.config)
27
+ config = deepMerge(config, part.value.config);
28
+ }
29
+ return {
30
+ name: cell.parts.map((part) => `${part.axis}=${part.value.name}`).join(' · '),
31
+ dirName: cell.parts.map((part) => `${part.axis}-${part.value.name}`).join('__'),
32
+ model,
33
+ config,
34
+ };
35
+ });
36
+ }
37
+ /**
38
+ * Deep-merge plain data (the sweep's `config:` overrides) into a target.
39
+ *
40
+ * Objects merge recursively; arrays and scalars REPLACE. Replacing an array is the right default
41
+ * for config: an override that appended to `mcpServers` or `allowedTools` would silently keep the
42
+ * base value the author meant to displace, which is the harder bug to see.
43
+ *
44
+ * Prototype-polluting keys are skipped — a suite file is only semi-trusted input.
45
+ */
46
+ export function deepMerge(base, overrides) {
47
+ const out = { ...base };
48
+ for (const [key, value] of Object.entries(overrides)) {
49
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype')
50
+ continue;
51
+ const existing = out[key];
52
+ if (isPlainObject(existing) && isPlainObject(value)) {
53
+ out[key] = deepMerge(existing, value);
54
+ }
55
+ else {
56
+ out[key] = value;
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ function isPlainObject(value) {
62
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
63
+ }
64
+ /**
65
+ * Render the cross-cell comparison table.
66
+ *
67
+ * Rows are the metrics the suite declares (plus pass rate), columns are the sweep cells. A metric
68
+ * that a cell could not compute renders `n/a`, never a blank and never a zero — the same rule the
69
+ * metric engine follows, for the same reason.
70
+ *
71
+ * Per-tag sub-scores get their own rows under each metric, because the whole point of running a
72
+ * sweep is to see which setting moved which family, and a blended per-cell number cannot show that.
73
+ */
74
+ export function renderComparison(columns) {
75
+ if (columns.length === 0)
76
+ return [];
77
+ const lines = ['', `COMPARISON across ${columns.length} cell(s)`];
78
+ const nameWidth = Math.max(...columns.map((column) => column.name.length), 'metric'.length, 24);
79
+ const columnWidth = Math.max(...columns.map((column) => column.name.length), 16) + 2;
80
+ const header = columns.map((column) => column.name.padStart(columnWidth)).join('');
81
+ lines.push(` ${'metric'.padEnd(nameWidth)}${header}`);
82
+ const row = (label, values) => ` ${label.padEnd(nameWidth)}${values.map((value) => value.padStart(columnWidth)).join('')}`;
83
+ lines.push(row('pass rate', columns.map((column) => column.summary.total === 0
84
+ ? 'n/a (0 cases)'
85
+ : `${column.summary.passed}/${column.summary.total}`)));
86
+ lines.push(row('classified', columns.map((column) => {
87
+ const coverage = column.summary.classification?.coverage;
88
+ return coverage ? `${coverage.scored}/${coverage.total}` : 'n/a';
89
+ })));
90
+ // Metric rows, in the order the FIRST cell declares them, then any metric only later cells have
91
+ // (which would mean the cells ran different suites — worth seeing rather than hiding).
92
+ const metricNames = [];
93
+ for (const column of columns) {
94
+ for (const metric of column.summary.classification?.metrics ?? []) {
95
+ if (!metricNames.includes(metric.name))
96
+ metricNames.push(metric.name);
97
+ }
98
+ }
99
+ const tags = [];
100
+ for (const column of columns) {
101
+ for (const tag of column.summary.classification?.tags ?? []) {
102
+ if (!tags.includes(tag))
103
+ tags.push(tag);
104
+ }
105
+ }
106
+ for (const name of metricNames) {
107
+ lines.push('');
108
+ lines.push(row(name, columns.map((column) => cellMetricValue(column, name, undefined))));
109
+ for (const tag of tags) {
110
+ lines.push(row(` · ${tag}`, columns.map((column) => cellMetricValue(column, name, tag))));
111
+ }
112
+ // A gate breach must be visible in the comparison itself, not only in the per-cell report.
113
+ const breached = columns.filter((column) => (column.summary.classification?.gateFailures ?? []).includes(name));
114
+ if (breached.length > 0) {
115
+ lines.push(` ${''.padEnd(nameWidth)}GATE FAILED in: ${breached.map((c) => c.name).join(', ')}`);
116
+ }
117
+ }
118
+ return lines;
119
+ }
120
+ /** One cell's value for a metric (overall, or for one tag). `n/a` when the cell has no such metric
121
+ * — never blank, never 0. */
122
+ function cellMetricValue(column, metricName, tag) {
123
+ const metric = column.summary.classification?.metrics.find((m) => m.name === metricName);
124
+ if (!metric)
125
+ return 'n/a';
126
+ const tally = tag === undefined ? metric.overall : metric.byTag[tag];
127
+ if (!tally)
128
+ return 'n/a';
129
+ return formatTally(tally);
130
+ }
131
+ /** The key a cell is diffed on across runs: id plus identity, since a matrix cell's id alone is
132
+ * ambiguous. */
133
+ function diffKey(result) {
134
+ return result.identity === undefined ? result.id : `${result.id}__${result.identity}`;
135
+ }
136
+ /**
137
+ * Diff two runs of the same suite, so a rating-prompt edit produces a signal rather than a vibe.
138
+ *
139
+ * Three separate lists, because they answer different questions: verdict regressions are what a CI
140
+ * gate reads, verdict fixes are what a change claims to have done, and RECLASSIFICATIONS are what
141
+ * a prompt edit actually moved — a case can keep its verdict while the label underneath it changes,
142
+ * and that is exactly the drift a pass-rate comparison cannot see.
143
+ */
144
+ export function diffRuns(before, after) {
145
+ const beforeByKey = new Map(before.cases.map((result) => [diffKey(result), result]));
146
+ const afterByKey = new Map(after.cases.map((result) => [diffKey(result), result]));
147
+ const regressed = [];
148
+ const fixed = [];
149
+ const reclassified = [];
150
+ let compared = 0;
151
+ for (const [key, afterCase] of afterByKey) {
152
+ const beforeCase = beforeByKey.get(key);
153
+ if (!beforeCase)
154
+ continue;
155
+ compared += 1;
156
+ if (beforeCase.verdict === 'PASS' && afterCase.verdict === 'FAIL') {
157
+ regressed.push({ id: key, before: 'PASS', after: 'FAIL' });
158
+ }
159
+ else if (beforeCase.verdict === 'FAIL' && afterCase.verdict === 'PASS') {
160
+ fixed.push({ id: key, before: 'FAIL', after: 'PASS' });
161
+ }
162
+ const beforeClass = describeClassification(beforeCase.classification);
163
+ const afterClass = describeClassification(afterCase.classification);
164
+ if (beforeClass !== afterClass && (beforeClass !== '-' || afterClass !== '-')) {
165
+ reclassified.push({ id: key, before: beforeClass, after: afterClass });
166
+ }
167
+ }
168
+ const onlyInBefore = [...beforeByKey.keys()].filter((key) => !afterByKey.has(key));
169
+ const onlyInAfter = [...afterByKey.keys()].filter((key) => !beforeByKey.has(key));
170
+ const metricDeltas = [];
171
+ for (const afterMetric of after.classification?.metrics ?? []) {
172
+ const beforeMetric = before.classification?.metrics.find((m) => m.name === afterMetric.name);
173
+ if (!beforeMetric)
174
+ continue;
175
+ const beforeValue = beforeMetric.overall.value;
176
+ const afterValue = afterMetric.overall.value;
177
+ metricDeltas.push({
178
+ name: afterMetric.name,
179
+ before: beforeValue,
180
+ after: afterValue,
181
+ delta: beforeValue === null || afterValue === null ? null : afterValue - beforeValue,
182
+ });
183
+ }
184
+ const warnings = [];
185
+ if (onlyInBefore.length > 0 || onlyInAfter.length > 0) {
186
+ warnings.push(`the two runs do not cover the same cases: ${onlyInBefore.length} only in the baseline, ` +
187
+ `${onlyInAfter.length} only in this run. The comparison covers ${compared} case(s); a ` +
188
+ 'case that disappeared cannot regress, so "no regressions" here is not "nothing broke".');
189
+ }
190
+ return {
191
+ compared,
192
+ regressed,
193
+ fixed,
194
+ reclassified,
195
+ metricDeltas,
196
+ onlyInBefore,
197
+ onlyInAfter,
198
+ warnings,
199
+ };
200
+ }
201
+ function describeClassification(classification) {
202
+ if (!classification)
203
+ return '-';
204
+ const label = classification.actualLabel ?? '-';
205
+ const action = classification.actualAction;
206
+ return action === undefined ? label : `${label}/${action}`;
207
+ }
208
+ /** Render a {@link RunDiff} as plain lines. */
209
+ export function renderRunDiff(diff) {
210
+ const lines = ['', 'RUN-OVER-RUN DIFF'];
211
+ lines.push(` compared: ${diff.compared} case(s)`);
212
+ for (const warning of diff.warnings)
213
+ lines.push(` ! ${warning}`);
214
+ const list = (title, entries) => {
215
+ if (entries.length === 0)
216
+ return;
217
+ lines.push(` ${title} (${entries.length}):`);
218
+ for (const entry of entries)
219
+ lines.push(` ${entry.id}: ${entry.before} → ${entry.after}`);
220
+ };
221
+ list('REGRESSED', diff.regressed);
222
+ list('fixed', diff.fixed);
223
+ list('reclassified', diff.reclassified);
224
+ if (diff.metricDeltas.length > 0) {
225
+ lines.push(' metric deltas:');
226
+ for (const delta of diff.metricDeltas) {
227
+ const format = (value) => value === null ? 'n/a' : `${(value * 100).toFixed(1)}%`;
228
+ const change = delta.delta === null
229
+ ? 'n/a'
230
+ : `${delta.delta >= 0 ? '+' : ''}${(delta.delta * 100).toFixed(1)}pp`;
231
+ lines.push(` ${delta.name}: ${format(delta.before)} → ${format(delta.after)} (${change})`);
232
+ }
233
+ }
234
+ if (diff.regressed.length === 0 &&
235
+ diff.fixed.length === 0 &&
236
+ diff.reclassified.length === 0 &&
237
+ diff.metricDeltas.every((delta) => delta.delta === 0)) {
238
+ lines.push(' no change.');
239
+ }
240
+ return lines;
241
+ }
242
+ /** Does this suite declare a sweep? Small helper so the command reads declaratively. */
243
+ export function suiteSweep(suite) {
244
+ return suite.sweep;
245
+ }
246
+ //# sourceMappingURL=evalCompare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evalCompare.js","sourceRoot":"","sources":["../src/evalCompare.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { RunCellFn } from '#src/types.js';
2
- import type { EvalSuite, EvalSuiteSummary, JudgeFn, RunConversationFn } from '#src/evalTypes.js';
2
+ import type { EvalCaseResult, EvalSuite, EvalSuiteSummary, JudgeFn, RunClassifyFn, RunConversationFn } from '#src/evalTypes.js';
3
+ import type { ClassifiedCell } from '#src/classificationTypes.js';
3
4
  /** Options for {@link runEvalSuite}. */
4
5
  export interface RunEvalSuiteOptions {
5
6
  /**
@@ -35,9 +36,24 @@ export interface RunEvalSuiteOptions {
35
36
  * reason (the runner degrades safely rather than throwing). The judge is orthogonal to identity:
36
37
  * it grades each cell's answer regardless of which identity produced it. */
37
38
  judge?: JudgeFn;
38
- /** Max in-flight cells — reuses BATCH-1's `runBatchMatrix` pool (`DEFAULT_CONCURRENCY` when
39
- * omitted); no second concurrency mechanism is introduced for eval. */
39
+ /** Max in-flight cells — reuses BATCH-1's `runBatchMatrix` pool (`DEFAULT_CELL_CONCURRENCY`,
40
+ * i.e. serial, when omitted); no second concurrency mechanism is introduced for eval. */
40
41
  concurrency?: number;
42
+ /**
43
+ * BATCH-25 Half B SEAM — an injected CLASSIFICATION target. When provided (and the suite declares
44
+ * a `classification:` block), every case is dispatched through it INSTEAD of the agent runners:
45
+ * the target returns the label/action per round directly, plus the model-call count that makes
46
+ * `model_free` enforceable.
47
+ *
48
+ * Half B's `rater` target supplies it (`buildRaterClassifier`, #src/raterTarget.js) by driving
49
+ * the approvals rating prompt + decision mapping at a declared rung. REQUIRED for a `rater`
50
+ * suite — the guard at the top of {@link runEvalSuite} rejects the run without it rather than
51
+ * letting the cases fall through to the agent.
52
+ *
53
+ * When absent, a classifier suite reads its label/action out of the ordinary agent answer via the
54
+ * suite's declared extractors, which is what makes the facility usable on its own today.
55
+ */
56
+ classify?: RunClassifyFn;
41
57
  }
42
58
  /**
43
59
  * Run every (case × identity) cell of the suite through the SUT ({@link RunCellFn}, pooled via
@@ -56,6 +72,15 @@ export interface RunEvalSuiteOptions {
56
72
  * single-turn unit keeps the proven `runCell` + {@link gradeUnit} path byte-for-byte.
57
73
  */
58
74
  export declare function runEvalSuite(suite: EvalSuite, options: RunEvalSuiteOptions): Promise<EvalSuiteSummary>;
75
+ /**
76
+ * BATCH-25 — reduce the graded results to the shape the matrices and metrics read.
77
+ *
78
+ * `scored` is the load-bearing field: a cell whose SUT never ran has NO place in any matrix cell or
79
+ * any metric denominator, and is instead counted as `excluded` and said out loud. Silently treating
80
+ * it as a wrong answer would inflate the error rate; silently dropping it would inflate coverage.
81
+ * Both are how a classifier report comes to claim more than it measured.
82
+ */
83
+ export declare function toClassifiedCells(results: EvalCaseResult[]): ClassifiedCell[];
59
84
  /** The three-way process exit code for a completed `gth eval` run. See {@link classifyEvalExit}. */
60
85
  export type EvalExitCode = 0 | 1 | 2;
61
86
  /**
@@ -74,5 +99,11 @@ export type EvalExitCode = 0 | 1 | 2;
74
99
  * Classification is anchored on `sutOk`, not the verdict: a cell that ran (`sutOk === true`) but
75
100
  * whose judge errored (or whose answer failed a check) is a real result → exit `1`, never `2`. A
76
101
  * *mix* of `sutOk:false` and `sutOk:true` cells therefore yields `1`.
102
+ *
103
+ * BATCH-25 adds ONE further way to reach `1`: a breached `gate: fail` metric threshold. That is a
104
+ * product signal of exactly the same kind — and it is the one that a per-case pass/fail sweep cannot
105
+ * express, because a classifier corpus can be entirely within per-case tolerance while its
106
+ * false-approve rate is unacceptable. Per-case verdicts answer "did each case behave"; a gated
107
+ * metric answers "is the aggregate shippable", and only the second gates a release.
77
108
  */
78
109
  export declare function classifyEvalExit(summary: EvalSuiteSummary): EvalExitCode;