@gaunt-sloth/batch 2.0.0-alpha.19 → 2.0.0-alpha.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.
Files changed (39) hide show
  1. package/README.md +140 -0
  2. package/dist/bin.d.ts +13 -0
  3. package/dist/bin.js +28 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/deterministicChecks.d.ts +39 -7
  6. package/dist/deterministicChecks.js +120 -11
  7. package/dist/deterministicChecks.js.map +1 -1
  8. package/dist/evalOutput.d.ts +17 -4
  9. package/dist/evalOutput.js +40 -5
  10. package/dist/evalOutput.js.map +1 -1
  11. package/dist/evalRunner.d.ts +68 -15
  12. package/dist/evalRunner.js +309 -55
  13. package/dist/evalRunner.js.map +1 -1
  14. package/dist/evalSuite.d.ts +32 -11
  15. package/dist/evalSuite.js +487 -42
  16. package/dist/evalSuite.js.map +1 -1
  17. package/dist/evalTypes.d.ts +187 -15
  18. package/dist/index.d.ts +5 -1
  19. package/dist/index.js +6 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/pipelineCli.d.ts +102 -0
  22. package/dist/pipelineCli.js +348 -0
  23. package/dist/pipelineCli.js.map +1 -0
  24. package/dist/reporters/drive.d.ts +14 -0
  25. package/dist/reporters/drive.js +49 -0
  26. package/dist/reporters/drive.js.map +1 -0
  27. package/dist/reporters/registry.d.ts +11 -0
  28. package/dist/reporters/registry.js +26 -0
  29. package/dist/reporters/registry.js.map +1 -0
  30. package/dist/reporters/reporterTypes.d.ts +36 -0
  31. package/dist/reporters/reporterTypes.js +2 -0
  32. package/dist/reporters/reporterTypes.js.map +1 -0
  33. package/dist/reporters/textReporter.d.ts +10 -0
  34. package/dist/reporters/textReporter.js +50 -0
  35. package/dist/reporters/textReporter.js.map +1 -0
  36. package/dist/toolChecks.d.ts +21 -0
  37. package/dist/toolChecks.js +38 -0
  38. package/dist/toolChecks.js.map +1 -0
  39. package/package.json +6 -3
@@ -0,0 +1,348 @@
1
+ /**
2
+ * @module pipelineCli
3
+ *
4
+ * BATCH-9 — the standalone `gth-batch` pipeline runner. A thin entry point that runs the BATCH-1
5
+ * matrix runtime ({@link buildMatrix} + {@link runBatchMatrix}) directly from a shell pipeline,
6
+ * without pulling in the whole `gaunt-sloth` app. It takes a prompt-executable script + `--over`
7
+ * data (inline JSON/YAML, or piped on stdin), runs the matrix, and streams the **same structured
8
+ * per-cell `CellResult`** that `gth batch` writes — one JSON object per line (JSONL) on stdout.
9
+ *
10
+ * Relationship to `gth batch` (packages/app/src/commands/batchCommand.ts): the two share the exact
11
+ * matrix runtime and the exact per-cell run wiring. The production per-cell adapter
12
+ * (`buildProductionRunCell`) lives in the app; this file mirrors it — its own `createResolvers()`,
13
+ * a lean agent factory, `runSingleShot` in `exec` mode, and `cleanupTools()` on every path — so the
14
+ * standalone bin needs only `@gaunt-sloth/core` + `@gaunt-sloth/agent` (both already dependencies),
15
+ * never the app. The only intentional differences: over-data is inline/stdin (not a file path, which
16
+ * the pipeline shell already handles) and output is JSONL on stdout (not a directory of files).
17
+ *
18
+ * stdout discipline: the run itself is noisy (the runtime's `display()`/`ProgressIndicator`/token
19
+ * streaming all target `process.stdout`). The bin entry ({@link file://./bin.ts}) redirects
20
+ * `process.stdout.write` to stderr for the duration and this module writes the machine JSONL
21
+ * straight to fd 1 (`fs.writeSync`), so stdout stays a clean data channel — the same "protocol
22
+ * channel" discipline `packages/app/cli.js` uses for ACP.
23
+ */
24
+ import { writeSync } from 'node:fs';
25
+ import { readFileSync } from 'node:fs';
26
+ import { resolve } from 'node:path';
27
+ import { parse as parseYaml } from 'yaml';
28
+ import { initConfig as realInitConfig } from '@gaunt-sloth/core/config.js';
29
+ import { buildSystemMessages, readExecPrompt, wrapContent, } from '@gaunt-sloth/core/utils/llmUtils.js';
30
+ import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
31
+ import { buildMatrix } from '#src/matrix.js';
32
+ import { buildBatchSummary, runBatchMatrix } from '#src/BatchRunner.js';
33
+ const USAGE = 'Usage: gth-batch <script> [--over <json|yaml>] [--models a,b,c] [-j <n>] [--retry <n>]\n' +
34
+ ' <script> path to the .md prompt-executable to run over the matrix\n' +
35
+ ' --over <data> inline JSON/YAML array of row objects (or pipe it on stdin)\n' +
36
+ ' --models a,b,c comma-separated model axis (omit to use the configured model)\n' +
37
+ ' -j, --concurrency <n> max in-flight cells\n' +
38
+ ' --retry <n> retry a failed cell up to n times (default 0)';
39
+ /** Parse `--models a,b,c` into a trimmed, non-empty list; `undefined` when empty/absent. */
40
+ export function parseModels(models) {
41
+ const list = models
42
+ .split(',')
43
+ .map((m) => m.trim())
44
+ .filter((m) => m.length > 0);
45
+ return list.length > 0 ? list : undefined;
46
+ }
47
+ function parseIntArg(raw, flag) {
48
+ const n = Number.parseInt(raw, 10);
49
+ if (!Number.isFinite(n)) {
50
+ throw new Error(`${flag} expects an integer, got "${raw}"`);
51
+ }
52
+ return n;
53
+ }
54
+ /**
55
+ * Parse `gth-batch`'s argv (already sliced past `node <script>`). Pure and dependency-free so the
56
+ * arg surface is trivially unit-testable. Supports `--flag value` and `--flag=value`; throws a
57
+ * clear `Error` (with usage) on an unknown option, a missing value, or a missing `<script>`.
58
+ */
59
+ export function parseArgs(argv) {
60
+ let script;
61
+ let over;
62
+ let models;
63
+ let concurrency;
64
+ let retry;
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const arg = argv[i];
67
+ // Normalize `--flag=value` into a name + inline value.
68
+ let name = arg;
69
+ let inlineValue;
70
+ if (arg.startsWith('--') && arg.includes('=')) {
71
+ const eq = arg.indexOf('=');
72
+ name = arg.slice(0, eq);
73
+ inlineValue = arg.slice(eq + 1);
74
+ }
75
+ const takeValue = () => {
76
+ if (inlineValue !== undefined)
77
+ return inlineValue;
78
+ const v = argv[++i];
79
+ if (v === undefined)
80
+ throw new Error(`Missing value for ${name}\n\n${USAGE}`);
81
+ return v;
82
+ };
83
+ if (name === '--over') {
84
+ over = takeValue();
85
+ }
86
+ else if (name === '--models') {
87
+ models = parseModels(takeValue());
88
+ }
89
+ else if (name === '-j' || name === '--concurrency') {
90
+ concurrency = parseIntArg(takeValue(), name);
91
+ }
92
+ else if (name === '--retry') {
93
+ retry = parseIntArg(takeValue(), name);
94
+ }
95
+ else if (name === '-h' || name === '--help') {
96
+ throw new Error(USAGE);
97
+ }
98
+ else if (name.startsWith('-')) {
99
+ throw new Error(`Unknown option: ${arg}\n\n${USAGE}`);
100
+ }
101
+ else if (script === undefined) {
102
+ script = arg;
103
+ }
104
+ else {
105
+ throw new Error(`Unexpected extra argument: ${arg}\n\n${USAGE}`);
106
+ }
107
+ }
108
+ if (script === undefined) {
109
+ throw new Error(`Missing required <script> argument.\n\n${USAGE}`);
110
+ }
111
+ return { script, over, models, concurrency, retry };
112
+ }
113
+ /** Coerce a parsed row object's values to strings, mirroring `parseOver.ts`'s CSV/JSONL semantics
114
+ * (every field becomes a string that `{{field}}` interpolation binds). */
115
+ function stringifyRowValues(record) {
116
+ const row = {};
117
+ for (const [key, value] of Object.entries(record)) {
118
+ row[key] = typeof value === 'string' ? value : JSON.stringify(value);
119
+ }
120
+ return row;
121
+ }
122
+ /**
123
+ * Parse inline/stdin `--over` data — a JSON or YAML **array of row objects** — into
124
+ * {@link MatrixRow}s. (The app's `parseOverFile` picks CSV vs JSONL by file extension; a shell
125
+ * pipeline has no filename, so this bin standardizes on one self-describing format: JSON, which is
126
+ * also valid YAML.) Throws a descriptive `Error` on malformed input — the one thing that should
127
+ * make the bin exit non-zero.
128
+ */
129
+ export function parseOverData(text) {
130
+ let parsed;
131
+ try {
132
+ parsed = parseYaml(text);
133
+ }
134
+ catch (error) {
135
+ throw new Error(`--over data is not valid JSON/YAML: ${error instanceof Error ? error.message : String(error)}`);
136
+ }
137
+ if (!Array.isArray(parsed)) {
138
+ throw new Error('--over data must be a JSON/YAML array of row objects');
139
+ }
140
+ if (parsed.length === 0) {
141
+ throw new Error('--over data is an empty array (no rows)');
142
+ }
143
+ return parsed.map((row, i) => {
144
+ if (row === null || typeof row !== 'object' || Array.isArray(row)) {
145
+ throw new Error(`--over row ${i} must be an object, got ${JSON.stringify(row)}`);
146
+ }
147
+ return stringifyRowValues(row);
148
+ });
149
+ }
150
+ /**
151
+ * Resolve the input axis: inline `--over` if present, otherwise piped stdin, otherwise `undefined`
152
+ * (no input axis → a single cell over the script's own content, exactly like `exec`).
153
+ */
154
+ export async function resolveRows(args, readStdin) {
155
+ if (args.over !== undefined) {
156
+ return parseOverData(args.over);
157
+ }
158
+ const piped = (await readStdin()).trim();
159
+ if (piped.length > 0) {
160
+ return parseOverData(piped);
161
+ }
162
+ return undefined;
163
+ }
164
+ /** Build the exec-mode system preamble the same way `gth batch` does (app's `getExecSystemPrompt`),
165
+ * flattened to a string, using only `@gaunt-sloth/core` prompt builders. */
166
+ export function getExecPreamble(config) {
167
+ const [systemMessage] = buildSystemMessages(config, readExecPrompt(config));
168
+ const content = systemMessage?.content;
169
+ if (typeof content === 'string') {
170
+ return content;
171
+ }
172
+ if (Array.isArray(content)) {
173
+ return content
174
+ .map((item) => (typeof item === 'string' ? item : 'text' in item ? item.text : ''))
175
+ .join('\n');
176
+ }
177
+ return '';
178
+ }
179
+ /**
180
+ * Build a per-`model` {@link GthConfig} cache — the same idiom as `createCellConfigResolver`
181
+ * (batchCommand.ts) / `createModelConfigResolver` (runWorkflow.ts): `undefined` model → the base
182
+ * config; a named model → one cached `initConfig({ ...overrides, model })` call (cached by Promise
183
+ * so concurrent cells for the same not-yet-resolved model share one in-flight build).
184
+ */
185
+ function createModelConfigResolver(baseConfig, overrides, initConfig) {
186
+ const configForModel = new Map();
187
+ return (model) => {
188
+ if (!model)
189
+ return Promise.resolve(baseConfig);
190
+ let cached = configForModel.get(model);
191
+ if (!cached) {
192
+ cached = initConfig({ ...overrides, model });
193
+ configForModel.set(model, cached);
194
+ }
195
+ return cached;
196
+ };
197
+ }
198
+ /**
199
+ * The production per-cell adapter, mirroring `buildProductionRunCell` (batchCommand.ts) exactly:
200
+ * a fresh `createResolvers()` per cell, a lean agent factory, `runSingleShot` in `exec` mode, and
201
+ * `cleanupTools()` on every path. Kept here (not imported from the app) so the standalone bin
202
+ * depends only on core + agent.
203
+ */
204
+ async function buildProductionRunCell(baseConfig, preamble, overrides, initConfig) {
205
+ const { runSingleShot } = await import('@gaunt-sloth/core/runtime/singleShot.js');
206
+ const { createResolvers } = await import('@gaunt-sloth/agent/resolvers.js');
207
+ const { resolveAgentFactory } = await import('@gaunt-sloth/agent/core/resolveAgentFactory.js');
208
+ const resolveCellModelConfig = createModelConfigResolver(baseConfig, overrides, initConfig);
209
+ return async (cell) => {
210
+ const modelConfig = await resolveCellModelConfig(cell.model);
211
+ const cellConfig = {
212
+ ...modelConfig,
213
+ canInterruptInferenceWithEsc: false,
214
+ writeOutputToFile: false,
215
+ // Non-interactive pipeline: don't stream tokens to the (redirected) stdout; the answer is
216
+ // captured in the cell's JSON record instead.
217
+ streamOutput: false,
218
+ };
219
+ const content = wrapContent(cell.content, 'script', 'prompt-executable script', true);
220
+ const resolvers = createResolvers();
221
+ try {
222
+ const { ok, answer, tokensInput, tokensOutput, tools } = await runSingleShot(`BATCH-${cell.id}`, preamble, content, cellConfig, resolvers, 'exec', resolveAgentFactory(cellConfig, 'lean'));
223
+ return { ok, answer, tokensInput, tokensOutput, tools };
224
+ }
225
+ catch (error) {
226
+ // runSingleShot is documented to not throw for a normal LLM/tool failure; this guards the
227
+ // rare genuinely-unexpected exception so one bad cell never takes the whole batch down.
228
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
229
+ }
230
+ finally {
231
+ try {
232
+ await resolvers.cleanupTools?.();
233
+ }
234
+ catch (cleanupError) {
235
+ displayWarning(`Failed to clean up tools for cell ${cell.id}: ` +
236
+ `${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
237
+ }
238
+ }
239
+ };
240
+ }
241
+ /**
242
+ * Run the matrix and stream one JSON `CellResult` per line (JSONL) to `write`. Returns the
243
+ * aggregate {@link BatchSummary}. Each emitted line is the exact object `gth batch` writes to
244
+ * `<id>.json` (compact rather than pretty-printed) — the "same structured per-cell output".
245
+ */
246
+ export async function runMatrixToStream(scriptContent, models, rows, options) {
247
+ const cells = buildMatrix(scriptContent, models, rows);
248
+ const results = await runBatchMatrix(cells, {
249
+ runCell: options.runCell,
250
+ concurrency: options.concurrency,
251
+ retry: options.retry,
252
+ });
253
+ for (const result of results) {
254
+ options.write(`${JSON.stringify(result)}\n`);
255
+ }
256
+ return buildBatchSummary(results);
257
+ }
258
+ async function readStdinToEnd() {
259
+ const stdin = process.stdin;
260
+ if (stdin.isTTY)
261
+ return '';
262
+ const chunks = [];
263
+ for await (const chunk of stdin) {
264
+ chunks.push(Buffer.from(chunk));
265
+ }
266
+ return Buffer.concat(chunks).toString('utf8');
267
+ }
268
+ function readScriptFile(scriptPath) {
269
+ try {
270
+ return readFileSync(resolve(scriptPath), 'utf8');
271
+ }
272
+ catch (error) {
273
+ throw new Error(`Cannot read script "${scriptPath}": ${error instanceof Error ? error.message : String(error)}`);
274
+ }
275
+ }
276
+ /** A broken-pipe error — a downstream reader (`head`, `jq 'first'`, a quit pager) closed the pipe. */
277
+ function isEpipe(error) {
278
+ return !!error && typeof error === 'object' && error.code === 'EPIPE';
279
+ }
280
+ /**
281
+ * The standalone `gth-batch` entry. Parses argv, resolves the script + input axis, wires the
282
+ * per-cell runtime, runs the matrix, and streams JSONL cell records via `deps.write`.
283
+ *
284
+ * Exit-code contract (identical to `gth batch`): resolves to `0` iff the cells *ran* — a per-cell
285
+ * failure is recorded in that cell's JSON, never reflected in the exit code. A harness-level error
286
+ * (bad args, unreadable script, malformed `--over`, config failure) resolves to `1`. A downstream
287
+ * reader closing the pipe early (`EPIPE` on the stdout channel — e.g. `| head`) is normal pipeline
288
+ * usage and also resolves to `0`: the partial output already written is correct.
289
+ *
290
+ * @returns the process exit code (0 or 1).
291
+ */
292
+ export async function runBatchCli(argv, deps = {}) {
293
+ // Default stdout sink: once a downstream reader closes the pipe (EPIPE), stop writing and treat
294
+ // it as a clean stop rather than letting the broken-pipe error bubble up as a harness failure.
295
+ let stdoutClosed = false;
296
+ const write = deps.write ??
297
+ ((chunk) => {
298
+ if (stdoutClosed)
299
+ return;
300
+ try {
301
+ writeSync(1, chunk);
302
+ }
303
+ catch (error) {
304
+ if (isEpipe(error)) {
305
+ stdoutClosed = true;
306
+ return;
307
+ }
308
+ throw error;
309
+ }
310
+ });
311
+ const logError = deps.logError ?? ((chunk) => void process.stderr.write(chunk));
312
+ try {
313
+ const args = parseArgs(argv);
314
+ const readScript = deps.readScript ?? readScriptFile;
315
+ const readStdin = deps.readStdin ?? readStdinToEnd;
316
+ const initConfig = deps.initConfig ?? realInitConfig;
317
+ const scriptContent = readScript(args.script);
318
+ const rows = await resolveRows(args, readStdin);
319
+ let runCell = deps.runCell;
320
+ if (!runCell) {
321
+ const overrides = {};
322
+ const baseConfig = await initConfig(overrides);
323
+ const preamble = getExecPreamble(baseConfig);
324
+ runCell = await buildProductionRunCell(baseConfig, preamble, overrides, initConfig);
325
+ }
326
+ const summary = await runMatrixToStream(scriptContent, args.models, rows, {
327
+ concurrency: args.concurrency,
328
+ retry: args.retry,
329
+ runCell,
330
+ write,
331
+ });
332
+ logError(`gth-batch: ${summary.passed}/${summary.total} cell(s) ok` +
333
+ (summary.failed > 0 ? `, ${summary.failed} failed` : '') +
334
+ '\n');
335
+ return 0;
336
+ }
337
+ catch (error) {
338
+ // A broken pipe on the stdout data channel (a downstream reader closed early) is a clean stop,
339
+ // not a harness error: exit 0 and stay silent. Genuine harness errors (bad args, unreadable
340
+ // script, malformed --over, config failure) carry no EPIPE code and still exit non-zero.
341
+ if (isEpipe(error)) {
342
+ return 0;
343
+ }
344
+ logError(`gth-batch: ${error instanceof Error ? error.message : String(error)}\n`);
345
+ return 1;
346
+ }
347
+ }
348
+ //# sourceMappingURL=pipelineCli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipelineCli.js","sourceRoot":"","sources":["../src/pipelineCli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { EvalSuiteSummary } from '#src/evalTypes.js';
2
+ import type { EvalRunContext, NamedReporter } from '#src/reporters/reporterTypes.js';
3
+ /**
4
+ * Drive reporters over one completed run's results, in lifecycle order:
5
+ * onSuiteStart → onCellResult per case (summary.cases order) → onSuiteEnd. A reporter hook that
6
+ * throws is caught and surfaced via displayWarning — a reporter must NEVER change the eval exit code
7
+ * or abort the run (the exit code is classifyEvalExit's job alone). Never throws.
8
+ *
9
+ * The lifecycle is phase-grouped across reporters (every reporter's onSuiteStart, then every
10
+ * reporter's onCellResult per case, then every reporter's onSuiteEnd) so a shared surface like the
11
+ * console is never interleaved out of order. With the single built-in `text` reporter this
12
+ * reproduces the former `printSummary` output byte-for-byte.
13
+ */
14
+ export declare function driveReporters(reporters: NamedReporter[], summary: EvalSuiteSummary, ctx: EvalRunContext): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
2
+ /** Run one reporter hook, swallowing any error (thrown OR a rejected Promise, the JUnit reporter's
3
+ * real failure mode) into a warning. A reporter must NEVER change the eval exit code or abort the
4
+ * run (the exit code is `classifyEvalExit`'s job alone), so a failing hook is contained here and the
5
+ * run continues. The warning names WHICH reporter's WHICH hook failed — ambiguous once multiple
6
+ * reporters (text + junit + custom) are real. */
7
+ async function runHookQuietly(reporterName, hook, run) {
8
+ try {
9
+ await run();
10
+ }
11
+ catch (error) {
12
+ displayWarning(`eval reporter "${reporterName}" ${hook} hook failed: ` +
13
+ (error instanceof Error ? error.message : String(error)));
14
+ }
15
+ }
16
+ /**
17
+ * Drive reporters over one completed run's results, in lifecycle order:
18
+ * onSuiteStart → onCellResult per case (summary.cases order) → onSuiteEnd. A reporter hook that
19
+ * throws is caught and surfaced via displayWarning — a reporter must NEVER change the eval exit code
20
+ * or abort the run (the exit code is classifyEvalExit's job alone). Never throws.
21
+ *
22
+ * The lifecycle is phase-grouped across reporters (every reporter's onSuiteStart, then every
23
+ * reporter's onCellResult per case, then every reporter's onSuiteEnd) so a shared surface like the
24
+ * console is never interleaved out of order. With the single built-in `text` reporter this
25
+ * reproduces the former `printSummary` output byte-for-byte.
26
+ */
27
+ export async function driveReporters(reporters, summary, ctx) {
28
+ for (const { name, reporter } of reporters) {
29
+ const onSuiteStart = reporter.onSuiteStart;
30
+ if (onSuiteStart) {
31
+ await runHookQuietly(name, 'onSuiteStart', () => onSuiteStart.call(reporter, ctx));
32
+ }
33
+ }
34
+ for (const result of summary.cases) {
35
+ for (const { name, reporter } of reporters) {
36
+ const onCellResult = reporter.onCellResult;
37
+ if (onCellResult) {
38
+ await runHookQuietly(name, 'onCellResult', () => onCellResult.call(reporter, result, ctx));
39
+ }
40
+ }
41
+ }
42
+ for (const { name, reporter } of reporters) {
43
+ const onSuiteEnd = reporter.onSuiteEnd;
44
+ if (onSuiteEnd) {
45
+ await runHookQuietly(name, 'onSuiteEnd', () => onSuiteEnd.call(reporter, summary, ctx));
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=drive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drive.js","sourceRoot":"","sources":["../../src/reporters/drive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAIzE;;;;iDAIiD;AACjD,KAAK,UAAU,cAAc,CAC3B,YAAoB,EACpB,IAAY,EACZ,GAA+B;IAE/B,IAAI,CAAC;QACH,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,cAAc,CACZ,kBAAkB,YAAY,KAAK,IAAI,gBAAgB;YACrD,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC3D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAA0B,EAC1B,OAAyB,EACzB,GAAmB;IAEnB,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3C,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;QAC3C,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACnC,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3C,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;YAC3C,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;YAC7F,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3C,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACvC,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { EvalReporterFactory, NamedReporter } from '#src/reporters/reporterTypes.js';
2
+ /** Resolve reporter names to named instances ({@link NamedReporter}). `custom` (A2 — the bundled
3
+ * JUnit reporter and any config-declared user reporters) overlays/extends the built-ins through this
4
+ * ONE path; a `custom` key colliding with a built-in name wins (config beats built-in). A name found
5
+ * in neither throws with the available list (the command maps that to exit 2). Reporters are
6
+ * instantiated per call (fresh state per run). Each is paired with the name it was selected under so
7
+ * {@link driveReporters} can name a failing reporter in its contained-error warning. */
8
+ export declare function resolveReporters(names: string[], custom?: Record<string, EvalReporterFactory>): NamedReporter[];
9
+ /** The names `resolveReporters` accepts, given the same `custom` map — used to build the error
10
+ * message on an unknown name (and, in A2, to list reporters in help/errors). */
11
+ export declare function availableReporterNames(custom?: Record<string, EvalReporterFactory>): string[];
@@ -0,0 +1,26 @@
1
+ import { createTextReporter } from '#src/reporters/textReporter.js';
2
+ /** The built-in reporters, keyed by the name a user (later, A2) selects with `--reporter`. A1 ships
3
+ * only `text` (the former `printSummary`); A2 adds a JUnit reporter and a custom-reporter map. */
4
+ const BUILTIN_REPORTERS = { text: createTextReporter };
5
+ /** Resolve reporter names to named instances ({@link NamedReporter}). `custom` (A2 — the bundled
6
+ * JUnit reporter and any config-declared user reporters) overlays/extends the built-ins through this
7
+ * ONE path; a `custom` key colliding with a built-in name wins (config beats built-in). A name found
8
+ * in neither throws with the available list (the command maps that to exit 2). Reporters are
9
+ * instantiated per call (fresh state per run). Each is paired with the name it was selected under so
10
+ * {@link driveReporters} can name a failing reporter in its contained-error warning. */
11
+ export function resolveReporters(names, custom = {}) {
12
+ const registry = { ...BUILTIN_REPORTERS, ...custom };
13
+ return names.map((name) => {
14
+ const factory = registry[name];
15
+ if (!factory) {
16
+ throw new Error(`unknown reporter "${name}". Available reporters: ${availableReporterNames(custom).join(', ')}`);
17
+ }
18
+ return { name, reporter: factory() };
19
+ });
20
+ }
21
+ /** The names `resolveReporters` accepts, given the same `custom` map — used to build the error
22
+ * message on an unknown name (and, in A2, to list reporters in help/errors). */
23
+ export function availableReporterNames(custom = {}) {
24
+ return Object.keys({ ...BUILTIN_REPORTERS, ...custom });
25
+ }
26
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/reporters/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE;kGACkG;AAClG,MAAM,iBAAiB,GAAwC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;AAE5F;;;;;wFAKwF;AACxF,MAAM,UAAU,gBAAgB,CAC9B,KAAe,EACf,MAAM,GAAwC,EAAE;IAEhD,MAAM,QAAQ,GAAwC,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1F,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,qBAAqB,IAAI,2BAA2B,sBAAsB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;gFACgF;AAChF,MAAM,UAAU,sBAAsB,CAAC,MAAM,GAAwC,EAAE;IACrF,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { EvalCaseResult, EvalSuiteSummary } from '#src/evalTypes.js';
2
+ /** Immutable per-run context handed to every reporter hook. Carries what a reporter needs beyond the
3
+ * result objects themselves. `suitePath`/`outputDir` are already known to the command; passing them
4
+ * now (unused by the text reporter's console output except outputDir) sets up A2 (JUnit
5
+ * <testsuite name>) and B (per-suite output dirs) without another interface change. */
6
+ export interface EvalRunContext {
7
+ /** The suite file path as given on the CLI. */
8
+ suitePath: string;
9
+ /** The directory this run's structured output (results.json etc.) is written to. */
10
+ outputDir: string;
11
+ /** The BATCH-10-Task-2 self-describing judge line, when a separate judge profile is in effect. */
12
+ judgeNotice?: {
13
+ profile: string;
14
+ model?: string;
15
+ };
16
+ }
17
+ /** A reporter renders an eval run. Every hook is optional — a reporter implements only what it needs
18
+ * (the text reporter uses all three; a future file reporter may use only onSuiteEnd). Hooks are
19
+ * driven in lifecycle order by {@link driveReporters}. A reporter MUST NOT be able to fail the run:
20
+ * the driver catches any hook error and surfaces it as a warning (see driveReporters). */
21
+ export interface EvalReporter {
22
+ onSuiteStart?(ctx: EvalRunContext): void | Promise<void>;
23
+ onCellResult?(result: EvalCaseResult, ctx: EvalRunContext): void | Promise<void>;
24
+ onSuiteEnd?(summary: EvalSuiteSummary, ctx: EvalRunContext): void | Promise<void>;
25
+ }
26
+ /** Reporters are created per run (they may accumulate state, e.g. a JUnit doc), so the registry
27
+ * stores factories, not instances. */
28
+ export type EvalReporterFactory = () => EvalReporter;
29
+ /** A resolved reporter paired with the name it was selected under. {@link resolveReporters} returns
30
+ * these (not bare {@link EvalReporter}s) so {@link driveReporters} can name WHICH reporter's hook
31
+ * failed in the contained-error warning — with multiple reporters real (A2: text + junit + custom),
32
+ * a bare "onCellResult hook failed" is ambiguous. */
33
+ export interface NamedReporter {
34
+ name: string;
35
+ reporter: EvalReporter;
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reporterTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporterTypes.js","sourceRoot":"","sources":["../../src/reporters/reporterTypes.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { EvalReporter } from '#src/reporters/reporterTypes.js';
2
+ /**
3
+ * The built-in default reporter: the human-readable, `review`-flavored summary — one PASS/FAIL line
4
+ * per cell (failures carry their reasons), then a suite-total line. Doesn't replicate `review`'s
5
+ * exact `REVIEW RATING` block format, just its spirit (a scannable verdict, not a wall of JSON).
6
+ *
7
+ * A1: this is a byte-for-byte port of `evalCommand.ts`'s former `printSummary` onto the reporter
8
+ * lifecycle — same lines, same order, same `display`/`displayWarning`/`displaySuccess` channels.
9
+ */
10
+ export declare function createTextReporter(): EvalReporter;
@@ -0,0 +1,50 @@
1
+ import { display, displaySuccess, displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
2
+ /**
3
+ * The built-in default reporter: the human-readable, `review`-flavored summary — one PASS/FAIL line
4
+ * per cell (failures carry their reasons), then a suite-total line. Doesn't replicate `review`'s
5
+ * exact `REVIEW RATING` block format, just its spirit (a scannable verdict, not a wall of JSON).
6
+ *
7
+ * A1: this is a byte-for-byte port of `evalCommand.ts`'s former `printSummary` onto the reporter
8
+ * lifecycle — same lines, same order, same `display`/`displayWarning`/`displaySuccess` channels.
9
+ */
10
+ export function createTextReporter() {
11
+ return {
12
+ onSuiteStart(ctx) {
13
+ // BATCH-10 Task 2: when a separate judge profile is in effect, lead with a single
14
+ // self-describing line so a captured run records which model graded it (reproducibility).
15
+ // Emitted only for a separate judge — the default SUT-as-judge run prints exactly as before.
16
+ if (ctx.judgeNotice) {
17
+ display(`Judge: profile "${ctx.judgeNotice.profile}"` +
18
+ (ctx.judgeNotice.model ? ` (model: ${ctx.judgeNotice.model})` : ''));
19
+ }
20
+ },
21
+ onCellResult(result) {
22
+ // BATCH-12: an identity-matrix cell tags its line with the identity it ran under, so the two
23
+ // rows a case produces per (case × identity) are distinguishable. A no-identities cell prints
24
+ // exactly as before (just the case id).
25
+ const label = result.identity ? `${result.id} [${result.identity}]` : result.id;
26
+ if (result.verdict === 'PASS') {
27
+ display(`PASS ${label}`);
28
+ }
29
+ else {
30
+ displayWarning(`FAIL ${label} — ${result.reasons.join('; ') || 'no reason recorded'}`);
31
+ }
32
+ },
33
+ onSuiteEnd(summary, ctx) {
34
+ // M1: X/Y counts CELLS in a matrix run (one per case × identity) — e.g. `2/2` for 1 case × 2
35
+ // identities — so a bare "case(s)" would misreport the denominator. Use an identity-aware
36
+ // noun: "case(s)" for a no-identities run (unchanged), "cell(s)" once any cell carries an
37
+ // identity.
38
+ const isMatrix = summary.cases.some((caseResult) => caseResult.identity !== undefined);
39
+ const noun = isMatrix ? 'cell(s)' : 'case(s)';
40
+ const verdictLine = `EVAL RESULT: ${summary.passed}/${summary.total} ${noun} passed`;
41
+ if (summary.failed === 0) {
42
+ displaySuccess(`${verdictLine}. Results written to ${ctx.outputDir}`);
43
+ }
44
+ else {
45
+ displayWarning(`${verdictLine}, ${summary.failed} failed. Results written to ${ctx.outputDir}`);
46
+ }
47
+ },
48
+ };
49
+ }
50
+ //# sourceMappingURL=textReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textReporter.js","sourceRoot":"","sources":["../../src/reporters/textReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAIlG;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,YAAY,CAAC,GAAmB;YAC9B,kFAAkF;YAClF,0FAA0F;YAC1F,6FAA6F;YAC7F,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACpB,OAAO,CACL,mBAAmB,GAAG,CAAC,WAAW,CAAC,OAAO,GAAG;oBAC3C,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CACtE,CAAC;YACJ,CAAC;QACH,CAAC;QAED,YAAY,CAAC,MAAsB;YACjC,6FAA6F;YAC7F,8FAA8F;YAC9F,wCAAwC;YACxC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YAChF,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBAC9B,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,cAAc,CAAC,SAAS,KAAK,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;YAC1F,CAAC;QACH,CAAC;QAED,UAAU,CAAC,OAAyB,EAAE,GAAmB;YACvD,6FAA6F;YAC7F,0FAA0F;YAC1F,0FAA0F;YAC1F,YAAY;YACZ,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC;YACvF,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;YAC9C,MAAM,WAAW,GAAG,gBAAgB,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,SAAS,CAAC;YACrF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,cAAc,CAAC,GAAG,WAAW,wBAAwB,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;YACxE,CAAC;iBAAM,CAAC;gBACN,cAAc,CACZ,GAAG,WAAW,KAAK,OAAO,CAAC,MAAM,+BAA+B,GAAG,CAAC,SAAS,EAAE,CAChF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { EvalExpectation } from '#src/evalTypes.js';
2
+ /**
3
+ * BATCH-10 tool-trace assertions — grade a case against the tool *names* it actually invoked
4
+ * (`cellResult.tools`), rather than grepping the answer text. This "kills the false-positive class
5
+ * structurally": an MCP-only case can prove the server was called instead of a substring that a
6
+ * hallucinated answer could also contain.
7
+ *
8
+ * Kept separate from `runDeterministicChecks` (which reads the *answer*) on purpose — these read the
9
+ * tool trace — so neither signature is muddied. Patterns reuse GS2-61's `toolNameMatchesPattern`
10
+ * (`@gaunt-sloth/core`), the same glob/exact matcher `allowedTools` uses, so `mcp__unimarket__*`
11
+ * behaves identically here.
12
+ *
13
+ * - `mustCall` — for **each** pattern, at least one called tool must match it, else
14
+ * `did not call "<pattern>"`.
15
+ * - `mustNotCall` — **no** called tool may match any forbidden pattern; each offending tool is
16
+ * reported once as `called forbidden tool "<tool>" (matched "<pattern>")`.
17
+ *
18
+ * BATCH-12: grades one {@link EvalExpectation} block's tool-trace assertions (a flat case's single
19
+ * block or a matrix case's identity-scoped block) — same field names, same behavior as before.
20
+ */
21
+ export declare function runToolCallChecks(tools: string[], expectation: Pick<EvalExpectation, 'mustCall' | 'mustNotCall'>): string[];