@garuda-agent/garuda 0.18.0 → 0.18.1

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 (61) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +3 -2
  3. package/dist/{acpCommand-IUZPLPLI.js → acpCommand-JAYNMIMP.js} +17 -14
  4. package/dist/advise-HDKJGNBL.js +195 -0
  5. package/dist/brief-RWKESOPT.js +91 -0
  6. package/dist/buildCheck-HB46NWLX.js +19 -0
  7. package/dist/buildInfo-K73FFI5S.js +8 -0
  8. package/dist/chunk-2OYTNNUJ.js +216 -0
  9. package/dist/{chunk-OQ26W5L4.js → chunk-34WBDQBL.js} +134 -77
  10. package/dist/chunk-4QRTTRVK.js +88 -0
  11. package/dist/{chunk-NHWHG3SW.js → chunk-5UDYQ5MK.js} +354 -59
  12. package/dist/chunk-CWVXOSAA.js +894 -0
  13. package/dist/{chunk-FARS4R67.js → chunk-DQSCCE77.js} +18 -1
  14. package/dist/chunk-DUYLVGPY.js +821 -0
  15. package/dist/{chunk-Q4P3W657.js → chunk-GKU62MHQ.js} +10 -1124
  16. package/dist/chunk-H7LIBGZT.js +9 -0
  17. package/dist/{chunk-FOPBOB7W.js → chunk-IKPGHM54.js} +6 -4
  18. package/dist/chunk-IYGJWX65.js +9 -0
  19. package/dist/{chunk-65NUEOI7.js → chunk-JMH5W5DG.js} +1 -44
  20. package/dist/chunk-L55VUSX3.js +54 -0
  21. package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
  22. package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
  23. package/dist/chunk-NH72AELP.js +48 -0
  24. package/dist/{chunk-UKIE4AAM.js → chunk-NLS7GMF7.js} +11 -105
  25. package/dist/{chunk-2FIV2VZP.js → chunk-QLBCM2YQ.js} +2 -2
  26. package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
  27. package/dist/{chunk-4BVCYCQT.js → chunk-R7RCW4BQ.js} +9 -7
  28. package/dist/chunk-TLA2PDGM.js +156 -0
  29. package/dist/{chunk-5B5HDTUR.js → chunk-TWQ7V2L6.js} +2 -2
  30. package/dist/chunk-ULAJPMY7.js +107 -0
  31. package/dist/chunk-VISKX36R.js +306 -0
  32. package/dist/{chunk-55CFEEIN.js → chunk-WK5NE6RY.js} +2 -2
  33. package/dist/chunk-Y62J7PTD.js +90 -0
  34. package/dist/chunk-YAQ6QLKB.js +40 -0
  35. package/dist/{chunk-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
  36. package/dist/cli/index.js +252 -61
  37. package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
  38. package/dist/{evalCommand-SKJQ6CW2.js → evalCommand-BLJJ4G76.js} +43 -33
  39. package/dist/{evalTune-CZRCPSZ3.js → evalTune-W53KAD4V.js} +25 -18
  40. package/dist/{export-SJ3DUP6W.js → export-E677HIQS.js} +27 -4
  41. package/dist/exportHtml-BMV73YRO.js +21 -0
  42. package/dist/{inkChat-WYYAFZXD.js → inkChat-EVLYDC3K.js} +258 -42
  43. package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
  44. package/dist/{jobCommand-2ODQLHB3.js → jobCommand-ILOTYOX3.js} +8 -7
  45. package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
  46. package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
  47. package/dist/{manager-TCYTA5XU.js → manager-L25IUXHW.js} +3 -3
  48. package/dist/{nightCommand-WJU6VZA6.js → nightCommand-XPWT6CQZ.js} +8 -7
  49. package/dist/receiptView-YFDHV253.js +19 -0
  50. package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
  51. package/dist/{run-PTMRQCIU.js → run-W4TK52WT.js} +5 -5
  52. package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
  53. package/dist/{setupCommand-YMF6I3MD.js → setupCommand-OSOMFHDQ.js} +9 -8
  54. package/dist/tour-CEVMAC5S.js +18 -0
  55. package/dist/tune-VX2WQTHS.js +52 -0
  56. package/dist/uiState-YFJQ43D7.js +16 -0
  57. package/package.json +1 -1
  58. package/dist/chunk-4JSUUDFQ.js +0 -118
  59. package/dist/chunk-C57GHLKP.js +0 -9
  60. package/dist/chunk-DOCDCQVM.js +0 -484
  61. package/dist/tune-YVI2KWQF.js +0 -31
@@ -0,0 +1,821 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_SEED,
4
+ TOOLCHAIN_ENV,
5
+ applyTests,
6
+ byTask,
7
+ consistency,
8
+ costPerPass,
9
+ isProtectedPath,
10
+ isolatedTaskCopy,
11
+ mcpUse,
12
+ mcpUseText,
13
+ passInterval,
14
+ repoTaskReadDenials,
15
+ roughNote,
16
+ spread,
17
+ stepsSd,
18
+ unexpectedChanges,
19
+ writeFiles
20
+ } from "./chunk-CWVXOSAA.js";
21
+ import {
22
+ BUILD_COMMIT
23
+ } from "./chunk-H7LIBGZT.js";
24
+ import {
25
+ garudaCommit
26
+ } from "./chunk-YAQ6QLKB.js";
27
+ import {
28
+ AutoApprover
29
+ } from "./chunk-NXJ4XLB5.js";
30
+ import {
31
+ DEFAULT_MAX_STEPS,
32
+ Runtime
33
+ } from "./chunk-5UDYQ5MK.js";
34
+ import {
35
+ FileSessionStore,
36
+ isCostIncomplete
37
+ } from "./chunk-NLS7GMF7.js";
38
+ import {
39
+ totalTokens
40
+ } from "./chunk-7KBOIPUT.js";
41
+ import {
42
+ VERSION
43
+ } from "./chunk-IYGJWX65.js";
44
+ import {
45
+ git
46
+ } from "./chunk-QXTHEFKP.js";
47
+ import {
48
+ DEFAULT_ENV_ALLOWLIST,
49
+ HostExecutor,
50
+ parseSettings
51
+ } from "./chunk-LMKGSYJC.js";
52
+ import {
53
+ pathMatches
54
+ } from "./chunk-3NJNVZTK.js";
55
+
56
+ // src/evals/runner.ts
57
+ import { constants as fsConstants, mkdirSync, realpathSync } from "fs";
58
+ import { copyFile, mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
59
+ import { homedir, tmpdir as tmpdir2 } from "os";
60
+ import { join as join3 } from "path";
61
+
62
+ // src/evals/agents/claudeCode.ts
63
+ import { mkdtemp, rm, writeFile } from "fs/promises";
64
+ import { tmpdir } from "os";
65
+ import { join } from "path";
66
+ var MAX_OUTPUT_BYTES = 5e6;
67
+ function claudeCommand(agent, prompt, maxTurns) {
68
+ return [
69
+ quote(agent.command),
70
+ "-p",
71
+ quote(prompt),
72
+ "--output-format",
73
+ "json",
74
+ "--model",
75
+ quote(agent.model),
76
+ "--max-turns",
77
+ String(maxTurns),
78
+ "--permission-mode",
79
+ "bypassPermissions"
80
+ ].join(" ");
81
+ }
82
+ function parseClaudeResult(stdout) {
83
+ const line = stdout.trim().split("\n").reverse().find((l) => l.trim().startsWith("{"));
84
+ let data;
85
+ try {
86
+ data = JSON.parse(line ?? "");
87
+ } catch {
88
+ return {
89
+ stopReason: "error",
90
+ steps: 0,
91
+ tokens: 0,
92
+ cacheReadTokens: 0,
93
+ costUsd: void 0,
94
+ reason: `Claude Code gave no JSON result: ${stdout.trim().slice(0, 300) || "no output"}`
95
+ };
96
+ }
97
+ const usage = data.usage ?? {};
98
+ const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
99
+ const cacheRead = n(usage.cache_read_input_tokens);
100
+ const tokens = n(usage.input_tokens) + n(usage.output_tokens) + cacheRead + n(usage.cache_creation_input_tokens);
101
+ const costUsd = typeof data.total_cost_usd === "number" ? data.total_cost_usd : void 0;
102
+ const steps = n(data.num_turns);
103
+ const subtype = typeof data.subtype === "string" ? data.subtype : "";
104
+ const base = { steps, tokens, cacheReadTokens: cacheRead, costUsd };
105
+ if (subtype === "error_max_turns") return { ...base, stopReason: "max_steps" };
106
+ if (subtype === "success" && data.is_error !== true) return { ...base, stopReason: "done" };
107
+ const text = typeof data.result === "string" ? data.result : subtype || "unknown error";
108
+ return { ...base, stopReason: "error", reason: `Claude Code failed: ${text.slice(0, 300)}` };
109
+ }
110
+ async function runClaudeCode(executor, policy, agent, prompt, maxTurns, signal) {
111
+ const config = await mkdtemp(join(tmpdir(), "garuda-claude-config-"));
112
+ try {
113
+ await writeFile(
114
+ join(config, ".claude.json"),
115
+ `${JSON.stringify({ hasCompletedOnboarding: true })}
116
+ `
117
+ );
118
+ const result = await executor.run(
119
+ claudeCommand(agent, prompt, maxTurns),
120
+ { ...policy, maxOutputBytes: MAX_OUTPUT_BYTES },
121
+ {
122
+ signal,
123
+ env: {
124
+ ...agent.env,
125
+ CLAUDE_CONFIG_DIR: config,
126
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
127
+ DISABLE_AUTOUPDATER: "1"
128
+ }
129
+ }
130
+ );
131
+ const outcome = result.timedOut || result.aborted ? {
132
+ stopReason: "timeout",
133
+ steps: 0,
134
+ tokens: 0,
135
+ cacheReadTokens: 0,
136
+ costUsd: void 0,
137
+ reason: "Claude Code took longer than the task's time limit."
138
+ } : parseClaudeResult(result.stdout.text);
139
+ if (outcome.stopReason === "error" && result.stderr.text.trim() !== "" && outcome.reason !== void 0) {
140
+ outcome.reason += `
141
+ ${result.stderr.text.trim().split("\n").slice(-3).join("\n")}`;
142
+ }
143
+ return { result, outcome };
144
+ } finally {
145
+ await rm(config, { recursive: true, force: true });
146
+ }
147
+ }
148
+ function quote(text) {
149
+ return `'${text.replace(/'/g, "'\\''")}'`;
150
+ }
151
+
152
+ // src/evals/changes.ts
153
+ import { createHash } from "crypto";
154
+ import { lstat, readdir, readFile, readlink } from "fs/promises";
155
+ import { join as join2 } from "path";
156
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
157
+ ".git",
158
+ ".garuda",
159
+ // Claude Code's own project folder (the T11b baseline), as .garuda is Garuda's.
160
+ ".claude",
161
+ "node_modules",
162
+ "target",
163
+ "build",
164
+ "dist",
165
+ "__pycache__",
166
+ ".pytest_cache"
167
+ ]);
168
+ var IGNORED_FILE = /\.(class|pyc)$/;
169
+ var MAX_TRACKED_FILES = 5e4;
170
+ async function treeState(root) {
171
+ const out = /* @__PURE__ */ new Map();
172
+ const walk = async (dir, prefix) => {
173
+ let names;
174
+ try {
175
+ names = await readdir(dir);
176
+ } catch {
177
+ return;
178
+ }
179
+ for (const name of names.sort()) {
180
+ if (out.size >= MAX_TRACKED_FILES) return;
181
+ const path = prefix === "" ? name : `${prefix}/${name}`;
182
+ const full = join2(dir, name);
183
+ const info = await lstat(full).catch(() => void 0);
184
+ if (info === void 0) continue;
185
+ if (info.isDirectory()) {
186
+ if (!IGNORED_DIRS.has(name)) await walk(full, path);
187
+ } else if (info.isSymbolicLink()) {
188
+ out.set(path, `link:${await readlink(full).catch(() => "")}`);
189
+ } else if (info.isFile() && !IGNORED_FILE.test(name)) {
190
+ const hash = createHash("sha256");
191
+ hash.update(await readFile(full).catch(() => Buffer.alloc(0)));
192
+ out.set(path, hash.digest("hex"));
193
+ }
194
+ }
195
+ };
196
+ await walk(root, "");
197
+ return out;
198
+ }
199
+ function changedPaths(before, after) {
200
+ const out = /* @__PURE__ */ new Set();
201
+ for (const [path, hash] of after) if (before.get(path) !== hash) out.add(path);
202
+ for (const path of before.keys()) if (!after.has(path)) out.add(path);
203
+ return [...out].sort();
204
+ }
205
+ function unexpectedPaths(changed, expected, allowChanges = []) {
206
+ const known = new Set(expected);
207
+ return changed.filter(
208
+ (path) => !known.has(path) && !allowChanges.some((glob) => pathMatches(glob, path, false))
209
+ );
210
+ }
211
+
212
+ // src/evals/runner.ts
213
+ var EVAL_DENY_RULES = [
214
+ "bash(rm -rf*)",
215
+ "bash(sudo*)",
216
+ "bash(git push*)",
217
+ "bash(curl*)",
218
+ "bash(wget*)"
219
+ ];
220
+ var REPO_CHECK_TIMEOUT_MS = 5 * 6e4;
221
+ var EVAL_TASK_TIMEOUT_MS = 10 * 6e4;
222
+ var MCP_PREPARE_TIMEOUT_MS = 12e4;
223
+ async function runEvalTask(task, options) {
224
+ const started = Date.now();
225
+ const host = new HostExecutor();
226
+ let root;
227
+ if (task.repo === void 0) {
228
+ root = realpathSync(await mkdtemp2(join3(tmpdir2(), `garuda-eval-${task.id}-`)));
229
+ await writeFiles(root, task.files);
230
+ } else {
231
+ root = await isolatedTaskCopy(host, task.repo.root, task.repo.base);
232
+ await applyTests(root, task.repo.tests);
233
+ }
234
+ if (options.format !== void 0) await formatProject(root, options.format.biome);
235
+ const protect = task.protect ?? Object.keys(task.files).filter(isProtectedPath);
236
+ const before = await snapshot(root, protect);
237
+ const settings = parseSettings({
238
+ // With an allowlist, a host outside it is denied: nobody answers questions in an eval.
239
+ permissions: {
240
+ deny: options.network === void 0 ? EVAL_DENY_RULES : [...EVAL_DENY_RULES, "network"]
241
+ },
242
+ ...options.network === void 0 ? {} : { network: { allow: options.network } },
243
+ // Evals must not depend on the internet.
244
+ web: { enabled: false },
245
+ // 0.18.1: a repo task's commands cannot read the project's own folder or the rest of the
246
+ // home folder: the checkout, and copies of it, hold the commit that the task asks for.
247
+ ...task.repo === void 0 ? {} : { sandbox: { denyRead: repoTaskReadDenials(task.repo.root, options.home ?? homedir()) } },
248
+ ...options.executor === void 0 ? {} : { executor: options.executor },
249
+ ...options.codeIndex === void 0 ? {} : { codeIndex: options.codeIndex },
250
+ ...options.maxSteps === void 0 ? {} : { limits: { maxSteps: options.maxSteps } },
251
+ ...options.subagents === void 0 ? {} : { subagents: { enabled: options.subagents } },
252
+ ...options.todo === void 0 ? {} : { todo: { enabled: options.todo } },
253
+ ...options.format === void 0 ? {} : {
254
+ formatters: {
255
+ enabled: options.format.on,
256
+ commands: {
257
+ biome: {
258
+ extensions: ["js", "jsx", "mjs", "cjs", "ts", "tsx", "json"],
259
+ command: [options.format.biome, "format", "--write", "$FILE"]
260
+ }
261
+ }
262
+ }
263
+ },
264
+ ...options.keepThinking === void 0 ? {} : { thinking: { keepBlocks: options.keepThinking } }
265
+ });
266
+ const store = new FileSessionStore(root);
267
+ let mcpCalls = 0;
268
+ const tools = [];
269
+ const createRuntime = (prepare2) => Runtime.create({
270
+ root,
271
+ modelId: options.modelId,
272
+ model: async () => options.model(),
273
+ ...options.modelInfo === void 0 ? {} : { modelInfo: options.modelInfo },
274
+ ...options.maxTokens === void 0 ? {} : { maxTokens: options.maxTokens },
275
+ ...options.subagentModel === void 0 ? {} : { subagentModel: options.subagentModel },
276
+ approver: new AutoApprover("once"),
277
+ store,
278
+ settings,
279
+ // The user's own MCP servers must not change eval results; `--mcp` allows one by name.
280
+ mcp: options.mcp === void 0 || prepare2 ? false : { home: options.mcp.home, only: options.mcp.server },
281
+ hooks: false,
282
+ ...options.addendum === void 0 ? {} : { addendum: options.addendum },
283
+ ...options.policy === void 0 ? {} : { policy: options.policy },
284
+ commands: false,
285
+ // The list comes from the eval command line: the user typed it.
286
+ ...options.network === void 0 ? {} : { network: { approved: true } },
287
+ ...options.lsp === void 0 ? {} : { lsp: { enabled: options.lsp } },
288
+ onEvent: (event) => {
289
+ if (event.type === "tool_call") {
290
+ tools.push(event.call.name);
291
+ if (event.call.name.startsWith("mcp__")) mcpCalls++;
292
+ }
293
+ options.onEvent?.(task.id, event);
294
+ }
295
+ });
296
+ let prepareFailure;
297
+ const prepare = options.mcp?.prepare;
298
+ if (prepare !== void 0) {
299
+ const prep = await createRuntime(true);
300
+ try {
301
+ const prepared = await runCheck(
302
+ root,
303
+ prepare,
304
+ MCP_PREPARE_TIMEOUT_MS,
305
+ prep.executor.isolation === "none" ? void 0 : { executor: prep.executor, policy: prep.execPolicy(MCP_PREPARE_TIMEOUT_MS) }
306
+ );
307
+ if (!prepared.ok) prepareFailure = `${MCP_PREPARE_FAILED}
308
+ ${prepared.output}`;
309
+ } finally {
310
+ await prep.close();
311
+ prep.executor.shutdown();
312
+ }
313
+ }
314
+ const runtime = await createRuntime(false);
315
+ const result = {
316
+ id: task.id,
317
+ title: task.title,
318
+ passed: false,
319
+ stopReason: "error",
320
+ steps: 0,
321
+ tokens: 0,
322
+ costUsd: void 0,
323
+ durationMs: 0
324
+ };
325
+ if (prepareFailure !== void 0) result.reason = prepareFailure;
326
+ const start = await treeState(root);
327
+ const limitMs = options.taskTimeoutMs ?? EVAL_TASK_TIMEOUT_MS;
328
+ const timeout = AbortSignal.timeout(limitMs);
329
+ try {
330
+ if (result.reason !== void 0) {
331
+ } else if (options.agent !== void 0) {
332
+ await runtime.prepareCommands(timeout);
333
+ const { result: exec, outcome } = await runClaudeCode(
334
+ runtime.executor,
335
+ runtime.execPolicy(limitMs),
336
+ options.agent,
337
+ task.prompt,
338
+ options.maxSteps ?? DEFAULT_MAX_STEPS,
339
+ timeout
340
+ );
341
+ result.stopReason = outcome.stopReason;
342
+ result.steps = outcome.steps;
343
+ result.tokens = outcome.tokens;
344
+ result.cacheReadTokens = outcome.cacheReadTokens;
345
+ result.costUsd = outcome.costUsd;
346
+ if (outcome.reason !== void 0) result.reason = outcome.reason;
347
+ if (options.outDir !== void 0) {
348
+ mkdirSync(options.outDir, { recursive: true });
349
+ result.sessionFile = await saveUnique(
350
+ options.outDir,
351
+ task.id,
352
+ ".claude.json",
353
+ (path) => writeFile2(path, exec.stdout.text, { flag: "wx" })
354
+ );
355
+ }
356
+ } else {
357
+ const run = await runtime.runTurn(task.prompt, timeout);
358
+ result.stopReason = run.stopReason;
359
+ result.steps = run.steps;
360
+ }
361
+ } catch (error) {
362
+ runtime.recordStop("error");
363
+ if (timeout.aborted) {
364
+ result.stopReason = "timeout";
365
+ result.reason = `The task took longer than ${limitMs / 6e4} minutes.`;
366
+ } else {
367
+ result.reason = `The run failed: ${error.message}`;
368
+ }
369
+ } finally {
370
+ if (tools.length > 0) result.tools = tools;
371
+ if (options.mcp !== void 0) {
372
+ result.mcpCalls = mcpCalls;
373
+ const status = runtime.mcpStatus().find((m) => m.name === options.mcp?.server);
374
+ if (result.reason === void 0 && status !== void 0 && status.state === "failed") {
375
+ result.stopReason = "error";
376
+ result.reason = `${MCP_NOT_STARTED} "${options.mcp.server}": ${status.message ?? "no reason given"}`;
377
+ }
378
+ }
379
+ await runtime.close();
380
+ runtime.executor.shutdown();
381
+ }
382
+ const session = runtime.session;
383
+ if (session !== void 0 && options.agent === void 0) {
384
+ result.tokens = totalTokens(session.usage);
385
+ if (session.unreportedTokens > 0) result.unreportedTokens = session.unreportedTokens;
386
+ result.costUsd = session.costUsd;
387
+ if (isCostIncomplete(session)) result.costIncomplete = true;
388
+ result.cacheReadTokens = session.usage.cacheReadTokens;
389
+ if (options.outDir !== void 0) {
390
+ mkdirSync(options.outDir, { recursive: true });
391
+ const from = store.path(session.id);
392
+ result.sessionFile = await saveUnique(
393
+ options.outDir,
394
+ task.id,
395
+ ".jsonl",
396
+ (path) => copyFile(from, path, fsConstants.COPYFILE_EXCL)
397
+ );
398
+ }
399
+ }
400
+ result.changed = changedPaths(start, await treeState(root));
401
+ const expected = await expectedFiles(task, host);
402
+ if (expected !== void 0) {
403
+ result.unexpected = unexpectedPaths(result.changed, expected, task.allowChanges);
404
+ }
405
+ if (result.reason === void 0) {
406
+ const changed = await changedFiles(root, before);
407
+ const checkMs = task.repo === void 0 ? 12e4 : REPO_CHECK_TIMEOUT_MS;
408
+ const check = await runCheck(
409
+ root,
410
+ task.check,
411
+ checkMs,
412
+ runtime.executor.isolation === "none" ? void 0 : { executor: runtime.executor, policy: runtime.execPolicy(checkMs) }
413
+ );
414
+ if (changed.length > 0) result.reason = `${PROTECTED_FILES_CHANGED} ${changed.join(", ")}.`;
415
+ else if (!check.ok) result.reason = `The check failed:
416
+ ${check.output}`;
417
+ else result.passed = true;
418
+ }
419
+ result.durationMs = Date.now() - started;
420
+ if (!options.keep) {
421
+ await rm2(root, { recursive: true, force: true });
422
+ }
423
+ return result;
424
+ }
425
+ async function expectedFiles(task, host) {
426
+ if (task.repo === void 0) return Object.keys(task.solution);
427
+ if (task.repo.sha === void 0) return void 0;
428
+ const r = await git(
429
+ host,
430
+ task.repo.root,
431
+ ["diff", "--name-only", task.repo.base, task.repo.sha],
432
+ {
433
+ check: false
434
+ }
435
+ );
436
+ if (r.exitCode !== 0) return void 0;
437
+ const tests = new Set(task.repo.tests.map((t) => t.path));
438
+ return r.stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !tests.has(line));
439
+ }
440
+ function isAuthFailure(result) {
441
+ if (!isErrorRun(result) || result.reason === void 0) return false;
442
+ return /\b40[13]\b|authentication_error|permission_error|invalid x-api-key|invalid api key/i.test(
443
+ result.reason
444
+ );
445
+ }
446
+ function isConnectionFailure(result) {
447
+ if (!isErrorRun(result) || result.reason === void 0) return false;
448
+ return /ECONNREFUSED|ENOTFOUND|EAI_AGAIN|connection refused|self[- ]signed certificate|unable to get local issuer certificate|certificate has expired/i.test(
449
+ result.reason
450
+ );
451
+ }
452
+ async function saveUnique(dir, id, ext, write) {
453
+ for (let n = 1; ; n++) {
454
+ const path = join3(dir, `${id}${n === 1 ? "" : `-${n}`}${ext}`);
455
+ try {
456
+ await write(path);
457
+ return path;
458
+ } catch (error) {
459
+ if (error.code !== "EEXIST") throw error;
460
+ }
461
+ }
462
+ }
463
+ var PROTECTED_FILES_CHANGED = "The agent changed protected files:";
464
+ var MCP_PREPARE_FAILED = "The MCP prepare command failed:";
465
+ var MCP_NOT_STARTED = "The MCP server did not start:";
466
+ function isMcpFailure(result) {
467
+ return result.reason !== void 0 && (result.reason.startsWith(MCP_PREPARE_FAILED) || result.reason.startsWith(MCP_NOT_STARTED));
468
+ }
469
+ function isSetupFailure(result) {
470
+ return isAuthFailure(result) || isConnectionFailure(result) || isMcpFailure(result);
471
+ }
472
+ var FAILURE_KINDS = [
473
+ "check",
474
+ "protected-files",
475
+ "step-limit",
476
+ "token-limit",
477
+ "repeated-calls",
478
+ "refusal",
479
+ "timeout",
480
+ "error",
481
+ "setup"
482
+ ];
483
+ function failureKind(result) {
484
+ if (result.passed) return void 0;
485
+ if (isErrorRun(result)) return isSetupFailure(result) ? "setup" : "error";
486
+ switch (result.stopReason) {
487
+ case "timeout":
488
+ return "timeout";
489
+ case "max_steps":
490
+ return "step-limit";
491
+ case "token_budget":
492
+ case "max_tokens":
493
+ return "token-limit";
494
+ case "repeated_calls":
495
+ return "repeated-calls";
496
+ case "refusal":
497
+ return "refusal";
498
+ }
499
+ if (result.reason?.startsWith(PROTECTED_FILES_CHANGED) === true) return "protected-files";
500
+ return "check";
501
+ }
502
+ function failureSummary(results) {
503
+ const runs = [];
504
+ for (const r of results) {
505
+ const kind = failureKind(r);
506
+ if (kind === void 0) continue;
507
+ runs.push({
508
+ id: r.id,
509
+ attempt: r.attempt ?? 1,
510
+ kind,
511
+ steps: r.steps,
512
+ tokens: r.tokens,
513
+ durationMs: r.durationMs
514
+ });
515
+ }
516
+ const byKind = {};
517
+ for (const kind of FAILURE_KINDS) {
518
+ const n = runs.filter((f) => f.kind === kind).length;
519
+ if (n > 0) byKind[kind] = n;
520
+ }
521
+ return { total: runs.length, byKind, runs };
522
+ }
523
+ function failureLine(results) {
524
+ const s = failureSummary(results);
525
+ if (s.total === 0) return void 0;
526
+ const parts = FAILURE_KINDS.flatMap((kind) => {
527
+ const runs = s.runs.filter((f) => f.kind === kind);
528
+ if (runs.length === 0) return [];
529
+ const named = runs.slice(0, 3).map((f) => `${f.id} run ${f.attempt}`);
530
+ if (runs.length > 3) named.push(`${runs.length - 3} more`);
531
+ return [`${kind} ${runs.length} (${named.join(", ")})`];
532
+ });
533
+ return `Failures by kind: ${parts.join(" \xB7 ")}`;
534
+ }
535
+ async function runEvals(tasks, options, onResult, stop) {
536
+ const results = new Array(tasks.length);
537
+ const seen = /* @__PURE__ */ new Map();
538
+ const attempts = tasks.map((t) => {
539
+ const n = (seen.get(t.id) ?? 0) + 1;
540
+ seen.set(t.id, n);
541
+ return n;
542
+ });
543
+ let next = 0;
544
+ let stopped = false;
545
+ const worker = async () => {
546
+ for (let i = next++; i < tasks.length && !stopped; i = next++) {
547
+ const result = await runEvalTask(tasks[i], options);
548
+ result.attempt = attempts[i];
549
+ results[i] = result;
550
+ onResult?.(result);
551
+ if (stop?.(result)) stopped = true;
552
+ }
553
+ };
554
+ const width = Math.max(1, Math.min(options.parallel ?? 1, tasks.length));
555
+ await Promise.all(Array.from({ length: width }, worker));
556
+ return results.filter((r) => r !== void 0);
557
+ }
558
+ async function runCheck(root, command, timeoutMs = 12e4, sandboxed) {
559
+ const envAllowlist = [...DEFAULT_ENV_ALLOWLIST, ...TOOLCHAIN_ENV];
560
+ const r = sandboxed === void 0 ? await new HostExecutor().run(command, {
561
+ root,
562
+ sandbox: false,
563
+ writePaths: [root],
564
+ denyWritePaths: [],
565
+ denyReadPaths: [],
566
+ network: false,
567
+ envAllowlist,
568
+ timeoutMs,
569
+ maxOutputBytes: 4e3
570
+ }) : await sandboxed.executor.run(command, {
571
+ ...sandboxed.policy,
572
+ envAllowlist: [.../* @__PURE__ */ new Set([...sandboxed.policy.envAllowlist, ...TOOLCHAIN_ENV])],
573
+ timeoutMs,
574
+ maxOutputBytes: 4e3
575
+ });
576
+ const text = `${r.stdout.text}${r.stderr.text}`.trim();
577
+ const output = text !== "" ? text : `(no output; ${r.timedOut ? "timed out" : `exit code ${r.exitCode}`})`;
578
+ return { ok: r.exitCode === 0 && !r.timedOut, output };
579
+ }
580
+ async function snapshot(root, paths) {
581
+ const out = /* @__PURE__ */ new Map();
582
+ for (const path of paths) out.set(path, await readFile2(join3(root, path), "utf8").catch(() => ""));
583
+ return out;
584
+ }
585
+ async function changedFiles(root, before) {
586
+ const after = await snapshot(root, [...before.keys()]);
587
+ return [...before.keys()].filter((path) => before.get(path) !== after.get(path));
588
+ }
589
+ function isErrorRun(result) {
590
+ return result.stopReason === "error";
591
+ }
592
+ function summarize(results) {
593
+ const attempted = results.length;
594
+ const errors = results.filter(isErrorRun).length;
595
+ const scored = attempted - errors;
596
+ const passed = results.filter((r) => r.passed).length;
597
+ return {
598
+ attempted,
599
+ errors,
600
+ scored,
601
+ passed,
602
+ ...scored === 0 ? {} : { scoredRate: passed / scored },
603
+ ...attempted === 0 ? {} : { attemptedRate: passed / attempted }
604
+ };
605
+ }
606
+ var percent = (rate) => rate === void 0 ? "\u2013" : `${Math.round(rate * 100)}%`;
607
+ function formatReport(results, options = {}) {
608
+ const width = idWidth(results);
609
+ const rows = results.map(
610
+ (r) => [
611
+ r.passed ? "PASS" : isErrorRun(r) ? "ERR " : "FAIL",
612
+ r.id.padEnd(width),
613
+ `${String(r.steps).padStart(3)} steps`,
614
+ (r.unreportedTokens ?? 0) > 0 ? `tok ? (${r.unreportedTokens} reserved)` : `${(r.tokens / 1e3).toFixed(1).padStart(7)}k tok`,
615
+ r.costUsd === void 0 ? " cost ?" : `${r.costIncomplete === true ? "\u2265" : ""}$${r.costUsd.toFixed(4).padStart(7)}`,
616
+ `${(r.durationMs / 1e3).toFixed(0).padStart(4)} s`,
617
+ r.stopReason
618
+ ].join(" ")
619
+ );
620
+ const summary = summarize(results);
621
+ const known = `$${results.reduce((s, r) => s + (r.costUsd ?? 0), 0).toFixed(4)}`;
622
+ const cost = !results.every((r) => r.costUsd !== void 0) ? "unknown" : results.some((r) => r.costIncomplete === true) ? `\u2265 ${known} (incomplete)` : known;
623
+ const steps = results.reduce((s, r) => s + r.steps, 0);
624
+ const counted = `${summary.passed}/${summary.scored} scored runs passed (${percent(summary.scoredRate)}) \xB7 ${summary.passed}/${summary.attempted} of all attempted runs (${percent(summary.attemptedRate)})` + (summary.errors > 0 ? ` \xB7 ${summary.errors} error run(s)` : "");
625
+ const tokens = results.reduce((s, r) => s + r.tokens, 0);
626
+ const cached = results.reduce((s, r) => s + (r.cacheReadTokens ?? 0), 0);
627
+ const share = results.some((r) => (r.unreportedTokens ?? 0) > 0) ? " \xB7 cache share unknown" : tokens === 0 ? "" : ` \xB7 ${Math.round(cached / tokens * 100)}% of tokens from the cache`;
628
+ const lines = [
629
+ ...rows,
630
+ "",
631
+ `${counted} \xB7 ${steps} steps \xB7 cost ${cost}${share}`,
632
+ ...evidenceLines(results, options.seed ?? DEFAULT_SEED)
633
+ ];
634
+ const ids = [...new Set(results.map((r) => r.id))];
635
+ if (ids.length < results.length) lines.push("", "Mean per task:", ...meanRows(results, ids));
636
+ return lines.join("\n");
637
+ }
638
+ function evidenceLines(results, seed) {
639
+ const out = [];
640
+ if (results.length > 0 && results.every(isErrorRun)) {
641
+ return [
642
+ "No scored run: every run failed with an error (the reasons are above), so this run gives no evidence about the agent."
643
+ ];
644
+ }
645
+ const interval = passInterval(results, { seed });
646
+ if (interval !== void 0 && interval.tasks >= 2) {
647
+ out.push(
648
+ `Pass rate over all attempts: ${percent(interval.rate)} (95% interval ${percent(interval.low)}\u2013${percent(interval.high)}; cluster bootstrap over ${interval.tasks} tasks, ${interval.draws} draws, seed ${interval.seed})`
649
+ );
650
+ if (interval.low === interval.high) {
651
+ out.push(
652
+ " Every draw gave the same rate, so the interval shows no uncertainty: more tasks are needed to measure it."
653
+ );
654
+ }
655
+ if (interval.tasks < 10) out.push(` ${roughNote(interval.tasks)}`);
656
+ }
657
+ const c = consistency(results);
658
+ if (c.repeated > 0) {
659
+ const none = c.nonePassed.length === 0 ? "" : ` (${c.nonePassed.join(", ")})`;
660
+ const errors = c.onlyErrors.length === 0 ? "" : `; ${c.onlyErrors.length} had only error runs (${c.onlyErrors.join(", ")})`;
661
+ out.push(
662
+ `Consistency: ${c.allPassed.length} of ${c.repeated} repeated tasks passed every run; ${c.nonePassed.length} passed none${none}${errors}`
663
+ );
664
+ }
665
+ const s = spread(results);
666
+ if (s !== void 0) {
667
+ const k = (n) => `${(n / 1e3).toFixed(1)}k`;
668
+ const sec = (ms) => `${Math.round(ms / 1e3)} s`;
669
+ out.push(
670
+ `Spread (${s.runs} scored runs): steps median ${s.steps.median.toFixed(1)}, p95 ${s.steps.p95.toFixed(1)} \xB7 tokens median ${k(s.tokens.median)}, p95 ${k(s.tokens.p95)} \xB7 time median ${sec(s.durationMs.median)}, p95 ${sec(s.durationMs.p95)}`
671
+ );
672
+ }
673
+ const cost = costPerPass(results);
674
+ out.push(
675
+ cost.kind === "unknown" ? "Cost per accepted change: unknown (a run's cost is unknown)" : cost.kind === "none" ? "Cost per accepted change: no passes" : `Cost per accepted change: ${cost.minimum ? "\u2265 " : ""}$${cost.usd.toFixed(4)} (all attempts ${cost.minimum ? "\u2265 " : ""}$${cost.totalUsd.toFixed(4)} / ${cost.passes} pass${cost.passes === 1 ? "" : "es"})`
676
+ );
677
+ const u = unexpectedChanges(results);
678
+ if (u.measured > 0) {
679
+ out.push(
680
+ `Unexpected changes: ${u.runs} of ${u.measured} runs, ${u.files} file${u.files === 1 ? "" : "s"}`
681
+ );
682
+ const shown = results.filter((r) => (r.unexpected?.length ?? 0) > 0).slice(0, 5);
683
+ for (const r of shown) {
684
+ out.push(
685
+ ` ${r.id}${r.attempt === void 0 ? "" : ` (run ${r.attempt})`}: ${(r.unexpected ?? []).join(", ")}`
686
+ );
687
+ }
688
+ if (u.runs > shown.length) out.push(` \u2026 ${u.runs - shown.length} more in report.json`);
689
+ }
690
+ const mcp = mcpUse(results);
691
+ if (mcp !== void 0) out.push(`MCP tool calls: ${mcpUseText(mcp)}`);
692
+ const failures = failureLine(results);
693
+ if (failures !== void 0) out.push(failures);
694
+ return out;
695
+ }
696
+ function idWidth(results) {
697
+ return Math.max(18, ...results.map((r) => r.id.length));
698
+ }
699
+ function meanRows(results, ids) {
700
+ const width = idWidth(results);
701
+ return ids.map((id) => {
702
+ const all = results.filter((r) => r.id === id);
703
+ const runs = all.filter((r) => !isErrorRun(r));
704
+ const errors = all.length - runs.length;
705
+ const passesAll = all.filter((r) => r.passed).length;
706
+ const note = errors > 0 ? ` (${passesAll}/${all.length} of all attempts; ${errors} error run(s))` : "";
707
+ if (runs.length === 0) return `${"0/0".padEnd(4)} ${id.padEnd(width)}${note}`;
708
+ const mean = (f) => runs.reduce((s, r) => s + f(r), 0) / runs.length;
709
+ const passes = runs.filter((r) => r.passed).length;
710
+ return [
711
+ `${passes}/${runs.length}`.padEnd(4),
712
+ id.padEnd(width),
713
+ `${mean((r) => r.steps).toFixed(1).padStart(5)} steps`,
714
+ runs.some((r) => (r.unreportedTokens ?? 0) > 0) ? " tok ?" : `${(mean((r) => r.tokens) / 1e3).toFixed(1).padStart(7)}k tok`,
715
+ runs.every((r) => r.costUsd !== void 0) ? `${runs.some((r) => r.costIncomplete === true) ? "\u2265" : ""}$${mean((r) => r.costUsd ?? 0).toFixed(4).padStart(7)}` : " cost ?",
716
+ ...stepsSd(runs) === void 0 ? [] : [`sd ${stepsSd(runs).toFixed(1)} steps`]
717
+ ].join(" ") + note;
718
+ });
719
+ }
720
+ var EVAL_BIOME_CONFIG = {
721
+ formatter: { indentStyle: "space", indentWidth: 2, lineWidth: 100 },
722
+ linter: { enabled: false },
723
+ assist: { enabled: false }
724
+ };
725
+ async function formatProject(root, biome) {
726
+ await writeFile2(join3(root, "biome.json"), `${JSON.stringify(EVAL_BIOME_CONFIG, null, 2)}
727
+ `);
728
+ const result = await new HostExecutor().run(`${shellWord(biome)} format --write .`, {
729
+ root,
730
+ sandbox: false,
731
+ writePaths: [root],
732
+ denyWritePaths: [],
733
+ denyReadPaths: [],
734
+ network: false,
735
+ envAllowlist: [...DEFAULT_ENV_ALLOWLIST],
736
+ timeoutMs: 6e4,
737
+ maxOutputBytes: 2e4
738
+ });
739
+ if (result.exitCode !== 0) {
740
+ throw new Error(
741
+ `Biome could not format the eval project: ${result.stderr.text.trim().split("\n")[0] ?? ""}`
742
+ );
743
+ }
744
+ }
745
+ function shellWord(text) {
746
+ return /^[A-Za-z0-9_./:@=+-]+$/.test(text) ? text : `'${text.replace(/'/g, "'\\''")}'`;
747
+ }
748
+
749
+ // src/evals/report.ts
750
+ import { createHash as createHash2 } from "crypto";
751
+ import { arch, platform, release } from "os";
752
+ var REPORT_SCHEMA = 2;
753
+ function taskHash(task) {
754
+ const sorted = (record) => Object.fromEntries(Object.entries(record).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
755
+ const data = {
756
+ prompt: task.prompt,
757
+ files: sorted(task.files),
758
+ check: task.check,
759
+ protect: task.protect ?? null,
760
+ solution: sorted(task.solution),
761
+ allowChanges: task.allowChanges ?? null,
762
+ repo: task.repo === void 0 ? null : { base: task.repo.base, sha: task.repo.sha ?? null, tests: task.repo.tests }
763
+ };
764
+ return createHash2("sha256").update(JSON.stringify(data)).digest("hex");
765
+ }
766
+ function buildFields(build, checkout) {
767
+ const commit = build ?? checkout;
768
+ return {
769
+ version: VERSION,
770
+ ...commit === void 0 ? {} : { commit },
771
+ ...build !== void 0 && checkout !== void 0 && checkout !== build ? { checkout } : {}
772
+ };
773
+ }
774
+ async function reportV2(meta, tasks, results) {
775
+ return {
776
+ schema: REPORT_SCHEMA,
777
+ garuda: buildFields(BUILD_COMMIT, await garudaCommit()),
778
+ spec: meta.model,
779
+ agent: meta.agent ?? { name: "garuda" },
780
+ provider: meta.provider,
781
+ // Evals set no effort: each model's default applies.
782
+ effort: "model default",
783
+ isolation: meta.isolation,
784
+ os: { platform: platform(), release: release(), arch: arch(), node: process.versions.node },
785
+ seed: meta.seed,
786
+ shuffle: meta.shuffle,
787
+ split: meta.split ?? "dev",
788
+ addendum: meta.addendum === void 0 ? null : {
789
+ file: meta.addendum.file,
790
+ chars: meta.addendum.text.length,
791
+ sha256: createHash2("sha256").update(meta.addendum.text).digest("hex")
792
+ },
793
+ mcp: meta.mcp === void 0 ? null : {
794
+ server: meta.mcp.server,
795
+ ...meta.mcp.prepare === void 0 ? {} : { prepare: meta.mcp.prepare }
796
+ },
797
+ order: results.map((r) => ({ id: r.id, attempt: r.attempt ?? 1 })),
798
+ tasks: Object.fromEntries(tasks.map((t) => [t.id, taskHash(t)])),
799
+ evidence: {
800
+ interval: passInterval(results, { seed: meta.seed }) ?? null,
801
+ consistency: consistency(results),
802
+ spread: spread(results) ?? null,
803
+ stepsSd: Object.fromEntries(byTask(results).map((t) => [t.id, stepsSd(t.results) ?? null])),
804
+ costPerAcceptedChange: costPerPass(results),
805
+ unexpected: unexpectedChanges(results),
806
+ failures: failureSummary(results)
807
+ }
808
+ };
809
+ }
810
+
811
+ export {
812
+ isAuthFailure,
813
+ isMcpFailure,
814
+ isSetupFailure,
815
+ runEvals,
816
+ summarize,
817
+ formatReport,
818
+ taskHash,
819
+ reportV2
820
+ };
821
+ //# sourceMappingURL=chunk-DUYLVGPY.js.map