@gaunt-sloth/batch 2.0.0-beta.1 → 2.0.0-beta.11

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.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * One advertised tool, as the batch layer carries it — a structural mirror of core's
3
+ * `GthAdvertisedTool`, kept local for the same reason `ToolResultRecord` (`#src/types.js`) is: this
4
+ * package's outcome shapes stay independent of the runner/LLM types.
5
+ */
6
+ export interface AdvertisedToolRecord {
7
+ /** The registered tool name, exactly as the model would call it. */
8
+ name: string;
9
+ /**
10
+ * The `mcpServers` key that explains the name, resolved against the configured keys at capture.
11
+ * Absent for a tool outside the MCP namespace (a built-in or a user-configured one); the empty
12
+ * string for an MCP-namespaced name no configured key explains.
13
+ */
14
+ server?: string;
15
+ }
16
+ /**
17
+ * One run's advertised-tool inventory, as captured by the agent at `init` — a structural mirror of
18
+ * core's `GthAdvertisedTools`.
19
+ */
20
+ export interface AdvertisedToolInventory {
21
+ /** Every named tool the agent loaded, BEFORE any `allowedTools` narrowing. */
22
+ tools: AdvertisedToolRecord[];
23
+ /** The subset an `allowedTools` allow-list removed. Empty when no allow-list is configured. */
24
+ filteredOut: AdvertisedToolRecord[];
25
+ /** How many loaded tools carry no name — counted in neither half of the fraction. */
26
+ unnamed: number;
27
+ }
28
+ /**
29
+ * A suite's `tool_coverage:` declaration: which tools are deliberately not covered, and what would
30
+ * make the run fail.
31
+ */
32
+ export interface ToolCoverageSpec {
33
+ /**
34
+ * Globs (the `must_call` matcher) whose matching advertised tools LEAVE the denominator.
35
+ *
36
+ * Load-bearing, not a nicety: a read-only suite must not be shamed for never calling the mutating
37
+ * tools, and without waivers any real server's coverage starts red and is then ignored — which is
38
+ * the same failure as a metric nobody reads. A deliberate decision not to cover something belongs
39
+ * in the suite, where it is reviewable, rather than in a comment.
40
+ */
41
+ waive: string[];
42
+ /**
43
+ * Optional floor: the minimum percentage (0-100) of the post-waiver denominator that must have
44
+ * been exercised. Breaching it is a product signal, graded like a declared metric gate.
45
+ */
46
+ min?: number;
47
+ /**
48
+ * Globs that must EACH match at least one exercised tool, whatever the percentage says. A floor
49
+ * answers "is enough of the surface covered"; this answers "was this specific tool reached", which
50
+ * a percentage can always satisfy by covering something else.
51
+ */
52
+ require: string[];
53
+ }
54
+ /** One bucket of the per-server breakdown. */
55
+ export interface ToolCoverageServerReport {
56
+ /**
57
+ * Which kind of bucket this is — explicit rather than encoded in {@link server}, so a JSON reader
58
+ * never has to tell "no server" from "a server named empty", and so a configured key cannot
59
+ * collide with a label this file made up.
60
+ */
61
+ kind: 'mcp' | 'builtin' | 'unresolved';
62
+ /** The `mcpServers` key, for a `mcp` bucket only. */
63
+ server?: string;
64
+ /** Exercised tool names in this bucket (post-waiver). */
65
+ covered: string[];
66
+ /** Advertised-but-never-called names in this bucket (post-waiver). */
67
+ uncovered: string[];
68
+ }
69
+ /**
70
+ * The coverage block written to `results.json` and rendered on the console.
71
+ *
72
+ * Name LISTS rather than counts, deliberately: a directory run's aggregate is a set union across
73
+ * suites (the same 41-tool server advertised to every suite must be counted once, not once per
74
+ * suite), and counts cannot be unioned. `n/N` is `covered.length` over `covered.length +
75
+ * uncovered.length`.
76
+ */
77
+ export interface ToolCoverageReport {
78
+ /** Advertised AND exercised, after waivers — the numerator. */
79
+ covered: string[];
80
+ /** Advertised, not waived, never called — the names the report exists to print. */
81
+ uncovered: string[];
82
+ /**
83
+ * Advertised names a `waive:` glob removed from the denominator, reported BESIDE the fraction.
84
+ *
85
+ * A waived tool leaves the denominator, so a suite waiving 38 of 41 reports 100% while covering
86
+ * three. Printing the waived count next to the number is what stops that reading as full coverage
87
+ * — the blind-denominator shape a metric facility cannot afford, because its whole value is being
88
+ * trusted.
89
+ */
90
+ waived: string[];
91
+ /** Advertised names an `allowedTools` allow-list removed before the agent bound them. */
92
+ filteredOut: string[];
93
+ /** Nameless provider-native tools, counted in neither half (there is no name to count). */
94
+ unnamed: number;
95
+ /** Per-server breakdown — a single percentage hides which server is uncovered. */
96
+ byServer: ToolCoverageServerReport[];
97
+ /** Everything that makes the figure mean less than it appears to. Never silently dropped. */
98
+ warnings: string[];
99
+ /**
100
+ * Breached `min` / unmet `require:` entries. Non-empty forces the run's exit code to 1 through the
101
+ * same contract a breached metric gate uses — a product signal, never a harness one.
102
+ */
103
+ gateFailures: string[];
104
+ }
105
+ /**
106
+ * The waived share at which the fraction stops describing the surface and starts describing the
107
+ * exemptions. Half is the point where the tools NOT being measured outnumber the tools that are, so
108
+ * a reader who takes the percentage at face value is wrong more often than right.
109
+ *
110
+ * A threshold rather than a hard failure because a high waiver share is legitimate — a read-only
111
+ * suite against a mostly-mutating server is exactly that — and failing it would push authors to
112
+ * delete the waivers, which is how the denominator goes blind in the first place.
113
+ */
114
+ export declare const WAIVER_SHARE_WARN_THRESHOLD = 0.5;
115
+ /** Inputs to {@link computeToolCoverage}. */
116
+ export interface ToolCoverageInput {
117
+ /**
118
+ * One entry per cell that reported an inventory. **Empty means no cell reported one**, which is
119
+ * not the same as a cell reporting an empty inventory: the first cannot supply a denominator at
120
+ * all (an external target, or a run whose SUT never initialised) and yields no report; the second
121
+ * is a real denominator of zero and does.
122
+ */
123
+ inventories: readonly AdvertisedToolInventory[];
124
+ /** Every tool name any cell invoked, in any order, deduplicated or not. */
125
+ exercised: readonly string[];
126
+ /** The suite's `tool_coverage:` declaration, when it made one. */
127
+ spec?: ToolCoverageSpec;
128
+ }
129
+ /**
130
+ * Compute the coverage report, or `undefined` when no inventory was observed at all.
131
+ *
132
+ * `undefined` is the honest answer for a target that cannot supply a denominator, and is why this
133
+ * returns a value rather than a zeroed report: **`0/0` printed as a coverage figure is the "metric
134
+ * nobody can trust" failure in its purest form** — it looks like a measurement, it is green, and it
135
+ * measured nothing. A caller that declared gates on a target that cannot be measured is rejected at
136
+ * parse time instead (`evalSuite.ts`), so a silent pass is unreachable from both ends.
137
+ */
138
+ export declare function computeToolCoverage(input: ToolCoverageInput): ToolCoverageReport | undefined;
139
+ /**
140
+ * Union several suites' reports into the run-level figure, or `undefined` when none was produced.
141
+ *
142
+ * **A set union, never a sum.** A directory run points every suite at the same agent, so the same 41
143
+ * advertised tools appear in each report; summing would report 123 tools and make one suite's
144
+ * coverage of a tool count three times. Coverage is a directory-level figure precisely because one
145
+ * suite covering 3 tools is fine if its sibling covers the other 38 — which only reads correctly
146
+ * when a tool covered anywhere is covered once.
147
+ *
148
+ * **Carries no warnings and no gate failures, deliberately.** A gate is declared in a suite and
149
+ * graded against that suite (the `metrics:` precedent), and the run's exit is the OR of those. If a
150
+ * declared floor were re-applied to the aggregate, the same suite would pass when run alone and fail
151
+ * when run as part of a directory, with nothing in either output saying the threshold had moved.
152
+ * This is reporting only.
153
+ */
154
+ export declare function aggregateToolCoverage(reports: readonly ToolCoverageReport[]): ToolCoverageReport | undefined;
@@ -0,0 +1,304 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * BATCH-32 — **tool coverage**: which of the tools the agent actually advertised to the model a
4
+ * suite exercised, and which it never touched.
5
+ *
6
+ * The numerator has existed since GS2-16 (`runStats.tools`, per cell, already in every
7
+ * `<case>.json`). What was missing is the DENOMINATOR: a run that calls 3 of 41 advertised tools
8
+ * prints the same all-green summary as one that calls all 41, so "every cell passed" reads as
9
+ * reassurance it has not earned, and coverage decays silently as the server grows a 42nd tool
10
+ * nobody wrote a case for.
11
+ *
12
+ * Pure and dependency-free on purpose (the `classificationReport.ts` precedent): every input is
13
+ * handed in, so the whole facility is unit-testable without an agent, an MCP server or a model.
14
+ *
15
+ * ## The denominator is the FULL advertised list, and that is the design decision here
16
+ *
17
+ * The list the agent binds is narrowed by `allowedTools` before it is bound. Taking the denominator
18
+ * from the narrowed list would let any suite reach 100% by narrowing the allow-list to the tools it
19
+ * already calls — the tools nobody exercises would leave the bottom of the fraction instead of being
20
+ * named. So the denominator is the pre-filter inventory, and the removed tools are reported as their
21
+ * own {@link ToolCoverageReport.filteredOut} category.
22
+ *
23
+ * ## What "covered" means, stated in the output rather than assumed
24
+ *
25
+ * A tool counts as covered when it was **called**. A call whose result came back an error still
26
+ * counts: the tool was reached, which is what a coverage figure measures. Grading what a tool
27
+ * RETURNED is what `must_error` / `tool_result_json_path` assertions are for, and they are a
28
+ * different question asked per case.
29
+ */
30
+ import { toolNameMatchesPattern } from '@gaunt-sloth/core/utils/toolMatching.js';
31
+ /**
32
+ * The waived share at which the fraction stops describing the surface and starts describing the
33
+ * exemptions. Half is the point where the tools NOT being measured outnumber the tools that are, so
34
+ * a reader who takes the percentage at face value is wrong more often than right.
35
+ *
36
+ * A threshold rather than a hard failure because a high waiver share is legitimate — a read-only
37
+ * suite against a mostly-mutating server is exactly that — and failing it would push authors to
38
+ * delete the waivers, which is how the denominator goes blind in the first place.
39
+ */
40
+ export const WAIVER_SHARE_WARN_THRESHOLD = 0.5;
41
+ /** Percentage of `total` that `part` represents, to one decimal place. `total` of 0 yields 0. */
42
+ function percent(part, total) {
43
+ if (total === 0)
44
+ return 0;
45
+ return Math.round((part / total) * 1000) / 10;
46
+ }
47
+ /** Bucket key for the per-server breakdown — one stable string per bucket identity. */
48
+ function bucketKeyFor(record) {
49
+ if (record.server === undefined)
50
+ return 'builtin';
51
+ if (record.server === '')
52
+ return 'unresolved';
53
+ return `mcp:${record.server}`;
54
+ }
55
+ /**
56
+ * Fold the per-cell inventories into one, and say so when they disagreed.
57
+ *
58
+ * **The union is the denominator, not the first cell's list.** Each cell resolves its own tools (a
59
+ * fresh MCP client per cell), so a cell whose server failed to connect advertises a shorter list. A
60
+ * denominator taken from that cell would quietly shrink, and coverage would IMPROVE because a server
61
+ * broke — the most misleading direction a coverage number can move. The union keeps the tool in the
62
+ * denominator, where it shows up as uncovered.
63
+ *
64
+ * Disagreement is still a warning, because a varying inventory means the cells did not all see the
65
+ * same surface and no single fraction describes the run exactly.
66
+ */
67
+ function unionInventories(inventories) {
68
+ const tools = new Map();
69
+ const filteredOut = new Map();
70
+ let unnamed = 0;
71
+ let disagreed = false;
72
+ let firstSignature;
73
+ for (const inventory of inventories) {
74
+ for (const record of inventory.tools) {
75
+ if (!tools.has(record.name))
76
+ tools.set(record.name, record);
77
+ }
78
+ for (const record of inventory.filteredOut) {
79
+ if (!filteredOut.has(record.name))
80
+ filteredOut.set(record.name, record);
81
+ }
82
+ // The same session's inventory repeated per cell must not multiply, so take the largest count
83
+ // rather than the sum — `unnamed` describes one agent's surface, like every other field here.
84
+ unnamed = Math.max(unnamed, inventory.unnamed);
85
+ // Compared as JSON rather than as a joined string: any separator character that could itself
86
+ // occur in a tool name would make two different inventories compare equal.
87
+ const signature = JSON.stringify(inventory.tools.map((record) => record.name).sort());
88
+ if (firstSignature === undefined)
89
+ firstSignature = signature;
90
+ else if (signature !== firstSignature)
91
+ disagreed = true;
92
+ }
93
+ return {
94
+ tools: [...tools.values()],
95
+ filteredOut: [...filteredOut.values()],
96
+ unnamed,
97
+ disagreed,
98
+ };
99
+ }
100
+ /**
101
+ * Compute the coverage report, or `undefined` when no inventory was observed at all.
102
+ *
103
+ * `undefined` is the honest answer for a target that cannot supply a denominator, and is why this
104
+ * returns a value rather than a zeroed report: **`0/0` printed as a coverage figure is the "metric
105
+ * nobody can trust" failure in its purest form** — it looks like a measurement, it is green, and it
106
+ * measured nothing. A caller that declared gates on a target that cannot be measured is rejected at
107
+ * parse time instead (`evalSuite.ts`), so a silent pass is unreachable from both ends.
108
+ */
109
+ export function computeToolCoverage(input) {
110
+ if (input.inventories.length === 0)
111
+ return undefined;
112
+ const { tools, filteredOut, unnamed, disagreed } = unionInventories(input.inventories);
113
+ const spec = input.spec;
114
+ const waivePatterns = spec?.waive ?? [];
115
+ const exercised = new Set(input.exercised);
116
+ const warnings = [];
117
+ const gateFailures = [];
118
+ const waived = [];
119
+ const counted = [];
120
+ for (const record of tools) {
121
+ if (waivePatterns.some((pattern) => toolNameMatchesPattern(record.name, pattern))) {
122
+ waived.push(record);
123
+ }
124
+ else {
125
+ counted.push(record);
126
+ }
127
+ }
128
+ const covered = counted.filter((record) => exercised.has(record.name));
129
+ const uncovered = counted.filter((record) => !exercised.has(record.name));
130
+ const total = counted.length;
131
+ // Per-server buckets over the counted (post-waiver) tools — the same population the headline
132
+ // fraction describes, so the buckets sum to it.
133
+ const buckets = new Map();
134
+ for (const record of counted) {
135
+ const key = bucketKeyFor(record);
136
+ let bucket = buckets.get(key);
137
+ if (!bucket) {
138
+ bucket =
139
+ record.server === undefined
140
+ ? { kind: 'builtin', covered: [], uncovered: [] }
141
+ : record.server === ''
142
+ ? { kind: 'unresolved', covered: [], uncovered: [] }
143
+ : { kind: 'mcp', server: record.server, covered: [], uncovered: [] };
144
+ buckets.set(key, bucket);
145
+ }
146
+ if (exercised.has(record.name))
147
+ bucket.covered.push(record.name);
148
+ else
149
+ bucket.uncovered.push(record.name);
150
+ }
151
+ if (disagreed) {
152
+ warnings.push('the cells did not all advertise the same tools — the denominator is their union, so a ' +
153
+ 'tool missing from some cells still counts (check for an MCP server that failed to connect)');
154
+ }
155
+ // A waiver that matches nothing is dead config, and the likely cause is the dangerous one: the
156
+ // tool was renamed, so it is back in the denominator under its new name while the author believes
157
+ // it is still waived. Cheap to say, and it is the decay this whole feature exists to catch.
158
+ for (const pattern of waivePatterns) {
159
+ if (!tools.some((record) => toolNameMatchesPattern(record.name, pattern))) {
160
+ warnings.push(`waive "${pattern}" matched no advertised tool — stale waiver, or a typo`);
161
+ }
162
+ }
163
+ if (waived.length > 0 && waived.length / tools.length >= WAIVER_SHARE_WARN_THRESHOLD) {
164
+ warnings.push(`${waived.length} of ${tools.length} advertised tool(s) are waived ` +
165
+ `(${percent(waived.length, tools.length)}%) — the figure describes the exemptions more ` +
166
+ 'than the surface');
167
+ }
168
+ if (filteredOut.length > 0) {
169
+ warnings.push(`${filteredOut.length} advertised tool(s) were removed by allowedTools and never reached ` +
170
+ 'the model — they are in the denominator, so they can only ever read as uncovered');
171
+ }
172
+ // Exercised names the inventory never listed. Not folded into the numerator (that would let a
173
+ // fraction exceed 1 and would credit coverage of a tool nobody advertised), and not dropped
174
+ // either — a name that ran but was never advertised means the two halves are measuring different
175
+ // populations, which is exactly the kind of quiet mismatch this report must surface.
176
+ const advertisedNames = new Set(tools.map((record) => record.name));
177
+ const unexpected = [...exercised].filter((name) => !advertisedNames.has(name)).sort();
178
+ if (unexpected.length > 0) {
179
+ warnings.push(`${unexpected.length} exercised tool(s) were not in the advertised inventory ` +
180
+ `(${unexpected.join(', ')}) — they are not counted in the numerator`);
181
+ }
182
+ if (spec) {
183
+ // Deliberately asymmetric with `waive`, which warns when a pattern matches nothing. A `require`
184
+ // pattern that matches nothing — the mistyped or stale entry — lands in the first branch and
185
+ // fails the gate, which already stops the run and names the pattern, so a stale-config warning
186
+ // beside it would only be quieter duplication. The warning below is the narrower case the gate
187
+ // cannot state: the pattern WAS satisfied by an exercised tool, but no advertised tool matches
188
+ // it, so the run passes while the two halves disagree about what exists.
189
+ for (const pattern of spec.require) {
190
+ if (![...exercised].some((name) => toolNameMatchesPattern(name, pattern))) {
191
+ gateFailures.push(`require "${pattern}": no case exercised a tool matching it`);
192
+ }
193
+ else if (!tools.some((record) => toolNameMatchesPattern(record.name, pattern))) {
194
+ warnings.push(`require "${pattern}" matched no ADVERTISED tool — it was satisfied by a tool the ` +
195
+ 'inventory does not list');
196
+ }
197
+ }
198
+ if (spec.min !== undefined) {
199
+ if (total === 0) {
200
+ // A floor over an empty denominator cannot be met, and must not be treated as met. This is
201
+ // the case where every tool went missing (an MCP server that never connected, or a waiver
202
+ // list that swallowed the whole surface) — the one run where a vacuous pass would be most
203
+ // misleading, because it is indistinguishable from perfect coverage.
204
+ gateFailures.push(`min ${spec.min}%: no tools remain in the denominator — ` +
205
+ (tools.length === 0
206
+ ? 'the agent advertised none'
207
+ : `all ${tools.length} advertised tool(s) are waived`));
208
+ }
209
+ else if (percent(covered.length, total) < spec.min) {
210
+ gateFailures.push(`min ${spec.min}%: covered ${covered.length}/${total} ` +
211
+ `(${percent(covered.length, total)}%)`);
212
+ }
213
+ }
214
+ }
215
+ return {
216
+ covered: covered.map((record) => record.name),
217
+ uncovered: uncovered.map((record) => record.name),
218
+ waived: waived.map((record) => record.name),
219
+ filteredOut: filteredOut.map((record) => record.name),
220
+ unnamed,
221
+ byServer: [...buckets.values()],
222
+ warnings,
223
+ gateFailures,
224
+ };
225
+ }
226
+ /**
227
+ * Union several suites' reports into the run-level figure, or `undefined` when none was produced.
228
+ *
229
+ * **A set union, never a sum.** A directory run points every suite at the same agent, so the same 41
230
+ * advertised tools appear in each report; summing would report 123 tools and make one suite's
231
+ * coverage of a tool count three times. Coverage is a directory-level figure precisely because one
232
+ * suite covering 3 tools is fine if its sibling covers the other 38 — which only reads correctly
233
+ * when a tool covered anywhere is covered once.
234
+ *
235
+ * **Carries no warnings and no gate failures, deliberately.** A gate is declared in a suite and
236
+ * graded against that suite (the `metrics:` precedent), and the run's exit is the OR of those. If a
237
+ * declared floor were re-applied to the aggregate, the same suite would pass when run alone and fail
238
+ * when run as part of a directory, with nothing in either output saying the threshold had moved.
239
+ * This is reporting only.
240
+ */
241
+ export function aggregateToolCoverage(reports) {
242
+ if (reports.length === 0)
243
+ return undefined;
244
+ const covered = new Set();
245
+ const uncovered = new Set();
246
+ const waived = new Set();
247
+ const filteredOut = new Set();
248
+ const buckets = new Map();
249
+ let unnamed = 0;
250
+ for (const report of reports) {
251
+ for (const name of report.covered)
252
+ covered.add(name);
253
+ for (const name of report.uncovered)
254
+ uncovered.add(name);
255
+ for (const name of report.waived)
256
+ waived.add(name);
257
+ for (const name of report.filteredOut)
258
+ filteredOut.add(name);
259
+ unnamed = Math.max(unnamed, report.unnamed);
260
+ for (const bucket of report.byServer) {
261
+ const key = bucket.kind === 'mcp' ? `mcp:${bucket.server}` : bucket.kind;
262
+ let merged = buckets.get(key);
263
+ if (!merged) {
264
+ merged = {
265
+ kind: bucket.kind,
266
+ ...(bucket.server !== undefined ? { server: bucket.server } : {}),
267
+ covered: [],
268
+ uncovered: [],
269
+ };
270
+ buckets.set(key, merged);
271
+ }
272
+ merged.covered.push(...bucket.covered);
273
+ merged.uncovered.push(...bucket.uncovered);
274
+ }
275
+ }
276
+ // A tool covered by ANY suite is covered: that is what makes the aggregate the interesting
277
+ // number. Without this, a tool one suite exercised would still be listed as uncovered because a
278
+ // sibling suite never touched it.
279
+ for (const name of covered)
280
+ uncovered.delete(name);
281
+ // Likewise a tool that is waived in one suite but counted in another stays counted — a waiver is
282
+ // one suite's statement about its own scope, and the run as a whole did measure that tool.
283
+ for (const name of [...covered, ...uncovered])
284
+ waived.delete(name);
285
+ return {
286
+ covered: [...covered],
287
+ uncovered: [...uncovered],
288
+ waived: [...waived],
289
+ filteredOut: [...filteredOut],
290
+ unnamed,
291
+ byServer: [...buckets.values()].map((bucket) => {
292
+ const bucketCovered = [...new Set(bucket.covered)];
293
+ const coveredSet = new Set(bucketCovered);
294
+ return {
295
+ ...bucket,
296
+ covered: bucketCovered,
297
+ uncovered: [...new Set(bucket.uncovered)].filter((name) => !coveredSet.has(name)),
298
+ };
299
+ }),
300
+ warnings: [],
301
+ gateFailures: [],
302
+ };
303
+ }
304
+ //# sourceMappingURL=toolCoverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCoverage.js","sourceRoot":"","sources":["../src/toolCoverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * BATCH-32 — render a {@link ToolCoverageReport} as plain lines.
4
+ *
5
+ * A pure string builder, separate from the reporter that prints it (the `classificationRender.ts`
6
+ * precedent): the console rendering is unit-testable without mocking `consoleUtils`, and another
7
+ * reporter can emit the same block without reimplementing the layout.
8
+ *
9
+ * Total over the report — no "…and 12 more" anywhere. The uncovered list IS the output; a renderer
10
+ * that truncates it turns the one actionable part of the block into a teaser, and a truncated list
11
+ * reads as a complete one.
12
+ */
13
+ import type { ToolCoverageReport } from '#src/toolCoverage.js';
14
+ /** Options for {@link renderToolCoverage}. */
15
+ export interface RenderToolCoverageOptions {
16
+ /**
17
+ * Prefix the headline with this word — `'TOTAL'` for the run-level aggregate that follows
18
+ * `EVAL TOTAL:`, so the two numbers on screen are never mistaken for each other.
19
+ */
20
+ scope?: string;
21
+ }
22
+ /**
23
+ * Render the whole block: the fraction, what qualifies it, the uncovered names, the per-server
24
+ * breakdown, any warnings, and any gate failures.
25
+ *
26
+ * The waived count sits on the headline rather than below it. A suite waiving 38 of 41 reports
27
+ * 100%, and a reader who sees only the fraction has been told the opposite of the truth — so the
28
+ * qualifier has to arrive in the same glance as the number it qualifies, not in a detail line that
29
+ * scrolls or gets skimmed.
30
+ */
31
+ export declare function renderToolCoverage(report: ToolCoverageReport, options?: RenderToolCoverageOptions): string[];
@@ -0,0 +1,71 @@
1
+ /** The headline label, shared by the per-suite line and the run-level aggregate. */
2
+ const HEADLINE = 'TOOL COVERAGE';
3
+ /** How many tools the report's fraction is over (covered + uncovered). */
4
+ function denominatorOf(report) {
5
+ return report.covered.length + report.uncovered.length;
6
+ }
7
+ /** A bucket's human label: the server key, or what the bucket holds instead of one. */
8
+ function serverLabel(bucket) {
9
+ if (bucket.kind === 'mcp')
10
+ return bucket.server ?? '';
11
+ if (bucket.kind === 'builtin')
12
+ return 'built-in / config tools';
13
+ return 'unattributed MCP tools';
14
+ }
15
+ /**
16
+ * Render the whole block: the fraction, what qualifies it, the uncovered names, the per-server
17
+ * breakdown, any warnings, and any gate failures.
18
+ *
19
+ * The waived count sits on the headline rather than below it. A suite waiving 38 of 41 reports
20
+ * 100%, and a reader who sees only the fraction has been told the opposite of the truth — so the
21
+ * qualifier has to arrive in the same glance as the number it qualifies, not in a detail line that
22
+ * scrolls or gets skimmed.
23
+ */
24
+ export function renderToolCoverage(report, options = {}) {
25
+ const lines = [];
26
+ const total = denominatorOf(report);
27
+ const headline = options.scope ? `${HEADLINE} ${options.scope}` : HEADLINE;
28
+ const qualifiers = [];
29
+ if (report.waived.length > 0)
30
+ qualifiers.push(`${report.waived.length} waived`);
31
+ if (report.filteredOut.length > 0) {
32
+ qualifiers.push(`${report.filteredOut.length} filtered out by allowedTools`);
33
+ }
34
+ if (report.unnamed > 0)
35
+ qualifiers.push(`${report.unnamed} unnamed (not counted)`);
36
+ lines.push(`${headline}: ${report.covered.length}/${total} tools exercised` +
37
+ (qualifiers.length > 0 ? ` — ${qualifiers.join(', ')}` : ''));
38
+ // Coverage counts a CALL. Said in the output rather than left for the reader to assume, because
39
+ // the alternative reading (the tool was called AND returned something usable) is the one a person
40
+ // reaching for a coverage number is likely to want.
41
+ lines.push(' a tool counts as covered once a case CALLED it, error result or not');
42
+ if (report.uncovered.length > 0) {
43
+ lines.push(` uncovered: ${[...report.uncovered].sort().join(', ')}`);
44
+ }
45
+ if (report.waived.length > 0) {
46
+ lines.push(` waived: ${[...report.waived].sort().join(', ')}`);
47
+ }
48
+ if (report.filteredOut.length > 0) {
49
+ lines.push(` filtered out by allowedTools: ${[...report.filteredOut].sort().join(', ')}`);
50
+ }
51
+ // Per-server only once more than one MCP server is configured — with a single server the
52
+ // breakdown restates the headline, and printing it twice is noise rather than information. The
53
+ // report still carries every bucket in `results.json`, where a reader is not paying for lines.
54
+ const mcpBuckets = report.byServer.filter((bucket) => bucket.kind === 'mcp');
55
+ if (mcpBuckets.length > 1) {
56
+ lines.push(' by server:');
57
+ for (const bucket of report.byServer) {
58
+ const bucketTotal = bucket.covered.length + bucket.uncovered.length;
59
+ lines.push(` ${serverLabel(bucket)}: ${bucket.covered.length}/${bucketTotal}` +
60
+ (bucket.uncovered.length > 0
61
+ ? ` — uncovered: ${[...bucket.uncovered].sort().join(', ')}`
62
+ : ''));
63
+ }
64
+ }
65
+ for (const warning of report.warnings)
66
+ lines.push(` ! ${warning}`);
67
+ for (const failure of report.gateFailures)
68
+ lines.push(`TOOL COVERAGE GATE FAILED — ${failure}`);
69
+ return lines;
70
+ }
71
+ //# sourceMappingURL=toolCoverageRender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCoverageRender.js","sourceRoot":"","sources":["../src/toolCoverageRender.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * `packages/app/src/commands/batchCommand.ts`, which is what keeps {@link RunCellFn} injectable —
8
8
  * unit tests here fake it, the CLI wires the real one.
9
9
  */
10
+ import type { AdvertisedToolInventory } from '#src/toolCoverage.js';
10
11
  /** One row/record parsed from `--over <path.csv|path.jsonl>`. Values are always strings for csv. */
11
12
  export type MatrixRow = Record<string, string>;
12
13
  /**
@@ -68,6 +69,13 @@ export interface CellRunOutcome {
68
69
  * {@link tools} but un-deduped and carrying each result's error status + payload. Only the
69
70
  * in-process `gth-agent` runner can populate this (external adk-agent/ag-ui targets never do). */
70
71
  toolResults?: ToolResultRecord[];
72
+ /**
73
+ * BATCH-32 — what the agent ADVERTISED to the model for this cell: the tool-coverage denominator,
74
+ * to {@link tools}'s numerator. Only the in-process `gth-agent` runner can populate it (an
75
+ * external adk-agent/ag-ui target never advertises its inventory over the wire), and a test fake
76
+ * leaves it unset — which is why absence means "no denominator available", never "no tools".
77
+ */
78
+ advertisedTools?: AdvertisedToolInventory;
71
79
  /** A human-readable failure reason, set when `ok` is `false`. */
72
80
  error?: string;
73
81
  }
package/dist/types.js CHANGED
@@ -1,12 +1,3 @@
1
- /**
2
- * @packageDocumentation
3
- * BATCH-1 — the shapes shared across matrix construction, the concurrency/retry runner, and the
4
- * structured output writer. Deliberately independent of any LLM/runner types (`GthConfig`,
5
- * `runSingleShot`, …): this package only knows about *cells* and *outcomes*. The production
6
- * adapter that turns a cell into an actual `runSingleShot` call lives in
7
- * `packages/app/src/commands/batchCommand.ts`, which is what keeps {@link RunCellFn} injectable —
8
- * unit tests here fake it, the CLI wires the real one.
9
- */
10
1
  /**
11
2
  * BATCH-24 — the default **cell** concurrency cap for `gth batch` / `gth eval` when
12
3
  * `-j/--concurrency` is not supplied. Serial by default, for every provider (deliberately NOT
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkHH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkIA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaunt-sloth/batch",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.11",
4
4
  "description": "Batch matrix runtime for Gaunt Sloth (BATCH-1): run a prompt-executable over a matrix of models and/or content-bound inputs",
5
5
  "license": "MIT",
6
6
  "author": "Andrew Kondratev",
@@ -34,11 +34,11 @@
34
34
  "#src/*.js": "./dist/*.js"
35
35
  },
36
36
  "dependencies": {
37
- "@langchain/core": "^1.2.5",
38
- "yaml": "^2.9.0",
39
- "zod": "^4.4.3",
40
- "@gaunt-sloth/agent": "2.0.0-beta.1",
41
- "@gaunt-sloth/core": "2.0.0-beta.1"
37
+ "@langchain/core": "^1.2.11",
38
+ "yaml": "^2.9.1",
39
+ "zod": "^4.6.3",
40
+ "@gaunt-sloth/agent": "2.0.0-beta.11",
41
+ "@gaunt-sloth/core": "2.0.0-beta.11"
42
42
  },
43
43
  "files": [
44
44
  "./dist/*"