@gaunt-sloth/batch 2.0.0-alpha.19 → 2.0.0-alpha.20

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,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,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[];
@@ -0,0 +1,38 @@
1
+ import { toolNameMatchesPattern } from '@gaunt-sloth/core/utils/toolMatching.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 function runToolCallChecks(tools, expectation) {
22
+ const failures = [];
23
+ for (const pattern of expectation.mustCall) {
24
+ if (!tools.some((tool) => toolNameMatchesPattern(tool, pattern))) {
25
+ failures.push(`did not call "${pattern}"`);
26
+ }
27
+ }
28
+ // De-duplicate: a tool called N times (traces realistically repeat names) is one violation, not
29
+ // N identical failure lines. `Set` preserves first-seen order.
30
+ for (const tool of new Set(tools)) {
31
+ const matched = expectation.mustNotCall.find((pattern) => toolNameMatchesPattern(tool, pattern));
32
+ if (matched !== undefined) {
33
+ failures.push(`called forbidden tool "${tool}" (matched "${matched}")`);
34
+ }
35
+ }
36
+ return failures;
37
+ }
38
+ //# sourceMappingURL=toolChecks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolChecks.js","sourceRoot":"","sources":["../src/toolChecks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAIjF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAe,EACf,WAA8D;IAE9D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,OAAO,GAAG,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,gGAAgG;IAChG,+DAA+D;IAC/D,KAAK,MAAM,IAAI,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACvD,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CACtC,CAAC;QACF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,0BAA0B,IAAI,eAAe,OAAO,IAAI,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaunt-sloth/batch",
3
- "version": "2.0.0-alpha.19",
3
+ "version": "2.0.0-alpha.20",
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",
@@ -23,6 +23,9 @@
23
23
  "type": "module",
24
24
  "main": "dist/index.js",
25
25
  "types": "dist/index.d.ts",
26
+ "bin": {
27
+ "gth-batch": "dist/bin.js"
28
+ },
26
29
  "exports": {
27
30
  ".": "./dist/index.js",
28
31
  "./*.js": "./dist/*.js"
@@ -34,8 +37,8 @@
34
37
  "@langchain/core": "^1.2.3",
35
38
  "yaml": "^2.9.0",
36
39
  "zod": "^4.4.3",
37
- "@gaunt-sloth/agent": "2.0.0-alpha.19",
38
- "@gaunt-sloth/core": "2.0.0-alpha.19"
40
+ "@gaunt-sloth/agent": "2.0.0-alpha.20",
41
+ "@gaunt-sloth/core": "2.0.0-alpha.20"
39
42
  },
40
43
  "files": [
41
44
  "./dist/*"