@garuda-agent/garuda 0.17.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.
- package/CHANGELOG.md +243 -0
- package/README.md +3 -2
- package/SECURITY.md +1 -1
- package/dist/{acpCommand-7EY3NHPG.js → acpCommand-JAYNMIMP.js} +19 -16
- package/dist/advise-HDKJGNBL.js +195 -0
- package/dist/brief-RWKESOPT.js +91 -0
- package/dist/buildCheck-HB46NWLX.js +19 -0
- package/dist/buildInfo-K73FFI5S.js +8 -0
- package/dist/chunk-2OYTNNUJ.js +216 -0
- package/dist/{chunk-Z66BWTMD.js → chunk-34WBDQBL.js} +135 -78
- package/dist/chunk-4QRTTRVK.js +88 -0
- package/dist/{chunk-2DYMJMRQ.js → chunk-5UDYQ5MK.js} +405 -70
- package/dist/{chunk-J4346IDH.js → chunk-7KBOIPUT.js} +2 -1
- package/dist/chunk-CWVXOSAA.js +894 -0
- package/dist/{chunk-FARS4R67.js → chunk-DQSCCE77.js} +18 -1
- package/dist/chunk-DUYLVGPY.js +821 -0
- package/dist/chunk-GKU62MHQ.js +5258 -0
- package/dist/chunk-H7LIBGZT.js +9 -0
- package/dist/chunk-IKPGHM54.js +334 -0
- package/dist/chunk-IYGJWX65.js +9 -0
- package/dist/{chunk-U65DQZDB.js → chunk-JMH5W5DG.js} +2 -45
- package/dist/chunk-L55VUSX3.js +54 -0
- package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
- package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
- package/dist/chunk-NH72AELP.js +48 -0
- package/dist/{chunk-735BKABA.js → chunk-NLS7GMF7.js} +12 -106
- package/dist/{chunk-ZAE25JM2.js → chunk-QANRZW7W.js} +8 -1
- package/dist/{chunk-JRVWH27M.js → chunk-QLBCM2YQ.js} +7 -3
- package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
- package/dist/{chunk-TJIWD7R7.js → chunk-R7RCW4BQ.js} +10 -8
- package/dist/chunk-TLA2PDGM.js +156 -0
- package/dist/{chunk-5B5HDTUR.js → chunk-TWQ7V2L6.js} +2 -2
- package/dist/chunk-ULAJPMY7.js +107 -0
- package/dist/chunk-VISKX36R.js +306 -0
- package/dist/{chunk-55CFEEIN.js → chunk-WK5NE6RY.js} +2 -2
- package/dist/chunk-Y62J7PTD.js +90 -0
- package/dist/chunk-YAQ6QLKB.js +40 -0
- package/dist/{chunk-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
- package/dist/cli/index.js +289 -65
- package/dist/config-WV2H64TF.js +23 -0
- package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
- package/dist/evalCommand-BLJJ4G76.js +696 -0
- package/dist/evalTune-W53KAD4V.js +302 -0
- package/dist/{export-SJ3DUP6W.js → export-E677HIQS.js} +27 -4
- package/dist/exportHtml-BMV73YRO.js +21 -0
- package/dist/{inkChat-FNUBK43U.js → inkChat-EVLYDC3K.js} +263 -47
- package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
- package/dist/{jobCommand-FE6XN3YD.js → jobCommand-ILOTYOX3.js} +9 -8
- package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
- package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
- package/dist/{manager-73MPYFTP.js → manager-L25IUXHW.js} +11 -11
- package/dist/{nightCommand-OODJNWDT.js → nightCommand-XPWT6CQZ.js} +9 -8
- package/dist/receiptView-YFDHV253.js +19 -0
- package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
- package/dist/{run-ML6GSPN4.js → run-W4TK52WT.js} +6 -6
- package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
- package/dist/{setupCommand-GBQIVYJC.js → setupCommand-OSOMFHDQ.js} +10 -9
- package/dist/tour-CEVMAC5S.js +18 -0
- package/dist/tune-VX2WQTHS.js +52 -0
- package/dist/uiState-YFJQ43D7.js +16 -0
- package/package.json +1 -1
- package/dist/chunk-4JSUUDFQ.js +0 -118
- package/dist/chunk-BSK5T2ZG.js +0 -9
- package/dist/evalCommand-YVQAWUZE.js +0 -2833
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
EVAL_SUITES,
|
|
4
|
+
HELD_OUT_TASKS,
|
|
5
|
+
chooseTasks,
|
|
6
|
+
requiredToolchains
|
|
7
|
+
} from "./chunk-GKU62MHQ.js";
|
|
8
|
+
import {
|
|
9
|
+
formatReport,
|
|
10
|
+
isAuthFailure,
|
|
11
|
+
isMcpFailure,
|
|
12
|
+
isSetupFailure,
|
|
13
|
+
reportV2,
|
|
14
|
+
runEvals,
|
|
15
|
+
summarize
|
|
16
|
+
} from "./chunk-DUYLVGPY.js";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_COMMITS,
|
|
19
|
+
DEFAULT_SEED,
|
|
20
|
+
TOOLCHAINS,
|
|
21
|
+
buildRepoSuite,
|
|
22
|
+
checkToolchains,
|
|
23
|
+
compareReports,
|
|
24
|
+
formatComparison,
|
|
25
|
+
loadRepoSuite,
|
|
26
|
+
loadReport,
|
|
27
|
+
prepareToolchain,
|
|
28
|
+
repoEvalTasks,
|
|
29
|
+
saveRepoSuite,
|
|
30
|
+
shuffled,
|
|
31
|
+
toolchainId
|
|
32
|
+
} from "./chunk-CWVXOSAA.js";
|
|
33
|
+
import {
|
|
34
|
+
BUILD_COMMIT
|
|
35
|
+
} from "./chunk-H7LIBGZT.js";
|
|
36
|
+
import {
|
|
37
|
+
garudaCommit,
|
|
38
|
+
staleBuildNote
|
|
39
|
+
} from "./chunk-YAQ6QLKB.js";
|
|
40
|
+
import "./chunk-NXJ4XLB5.js";
|
|
41
|
+
import {
|
|
42
|
+
CODE_INDEX_MODES,
|
|
43
|
+
DEFAULT_CODE_INDEX_MODE,
|
|
44
|
+
detectProfiles
|
|
45
|
+
} from "./chunk-5UDYQ5MK.js";
|
|
46
|
+
import "./chunk-G2AX7MGS.js";
|
|
47
|
+
import "./chunk-ULAJPMY7.js";
|
|
48
|
+
import "./chunk-JMH5W5DG.js";
|
|
49
|
+
import {
|
|
50
|
+
NO_MODEL,
|
|
51
|
+
chooseModel,
|
|
52
|
+
keepProviderKeys,
|
|
53
|
+
loadModelsConfig,
|
|
54
|
+
providerKeyValue,
|
|
55
|
+
resolveModel
|
|
56
|
+
} from "./chunk-QLBCM2YQ.js";
|
|
57
|
+
import "./chunk-NH72AELP.js";
|
|
58
|
+
import "./chunk-4QRTTRVK.js";
|
|
59
|
+
import "./chunk-A2QAIRQL.js";
|
|
60
|
+
import {
|
|
61
|
+
newSessionId
|
|
62
|
+
} from "./chunk-NLS7GMF7.js";
|
|
63
|
+
import "./chunk-7KBOIPUT.js";
|
|
64
|
+
import "./chunk-B25VL4RU.js";
|
|
65
|
+
import "./chunk-MNE7HHX2.js";
|
|
66
|
+
import "./chunk-IYGJWX65.js";
|
|
67
|
+
import "./chunk-5DEWP3YX.js";
|
|
68
|
+
import "./chunk-PZS3V6P3.js";
|
|
69
|
+
import "./chunk-QANRZW7W.js";
|
|
70
|
+
import "./chunk-ATW7BNIG.js";
|
|
71
|
+
import "./chunk-WK5NE6RY.js";
|
|
72
|
+
import {
|
|
73
|
+
detectTestCommand
|
|
74
|
+
} from "./chunk-AOGFYUKG.js";
|
|
75
|
+
import {
|
|
76
|
+
git
|
|
77
|
+
} from "./chunk-QXTHEFKP.js";
|
|
78
|
+
import {
|
|
79
|
+
EXECUTOR_NAMES,
|
|
80
|
+
HostExecutor,
|
|
81
|
+
createExecutor,
|
|
82
|
+
expandAllowlist
|
|
83
|
+
} from "./chunk-LMKGSYJC.js";
|
|
84
|
+
import "./chunk-BHZ6G65V.js";
|
|
85
|
+
import "./chunk-P6XWWIEK.js";
|
|
86
|
+
import "./chunk-UIRJOQQS.js";
|
|
87
|
+
import "./chunk-4LFJFDCF.js";
|
|
88
|
+
import "./chunk-3NJNVZTK.js";
|
|
89
|
+
|
|
90
|
+
// src/cli/evalCommand.ts
|
|
91
|
+
import { existsSync, mkdirSync, realpathSync, writeFileSync } from "fs";
|
|
92
|
+
import { createRequire } from "module";
|
|
93
|
+
import { homedir } from "os";
|
|
94
|
+
import { delimiter, dirname, join } from "path";
|
|
95
|
+
var BATCH_TASK_TIMEOUT_MS = 12 * 60 * 6e4;
|
|
96
|
+
async function runEvalCommand(options) {
|
|
97
|
+
if (options.fromGit) return buildFromGit(options);
|
|
98
|
+
let repoTasks;
|
|
99
|
+
if (options.list || options.suite === "repo") {
|
|
100
|
+
const loaded = await repoSuite();
|
|
101
|
+
if (typeof loaded === "string") {
|
|
102
|
+
if (options.suite === "repo") {
|
|
103
|
+
process.stderr.write(`${loaded}
|
|
104
|
+
`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
} else repoTasks = repoEvalTasks(loaded);
|
|
108
|
+
}
|
|
109
|
+
if (options.list) {
|
|
110
|
+
const suites = [
|
|
111
|
+
...Object.entries(EVAL_SUITES),
|
|
112
|
+
...repoTasks === void 0 ? [] : [["repo", repoTasks]]
|
|
113
|
+
];
|
|
114
|
+
for (const [suite2, tasks2] of suites) {
|
|
115
|
+
const needs2 = requiredToolchains(tasks2).map((id) => TOOLCHAINS[id].title);
|
|
116
|
+
process.stdout.write(`${suite2}:${needs2.length > 0 ? ` (needs ${needs2.join(", ")})` : ""}
|
|
117
|
+
`);
|
|
118
|
+
for (const task of tasks2) process.stdout.write(` ${task.id.padEnd(32)} ${task.title}
|
|
119
|
+
`);
|
|
120
|
+
}
|
|
121
|
+
const needs = requiredToolchains(HELD_OUT_TASKS).map((id) => TOOLCHAINS[id].title);
|
|
122
|
+
process.stdout.write(
|
|
123
|
+
`
|
|
124
|
+
held-out split (runs only with --split heldout or all; needs ${needs.join(", ")} for some):
|
|
125
|
+
`
|
|
126
|
+
);
|
|
127
|
+
for (const task of HELD_OUT_TASKS) {
|
|
128
|
+
process.stdout.write(` ${task.id.padEnd(32)} ${task.title}
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (options.prepare !== void 0) return prepare(options.prepare);
|
|
134
|
+
const models = await loadModelsConfig();
|
|
135
|
+
if (models.problem !== void 0) {
|
|
136
|
+
process.stderr.write(`${models.problem}
|
|
137
|
+
`);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
const modelId = chooseModel(models.config, options.model);
|
|
141
|
+
if (!modelId) {
|
|
142
|
+
process.stderr.write(`${NO_MODEL}
|
|
143
|
+
`);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
let resolved;
|
|
147
|
+
try {
|
|
148
|
+
resolved = resolveModel(modelId, models.config);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
process.stderr.write(`${error.message}
|
|
151
|
+
`);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
for (const note of resolved.notes) process.stderr.write(`${note}
|
|
155
|
+
`);
|
|
156
|
+
const index = options.index ?? DEFAULT_CODE_INDEX_MODE;
|
|
157
|
+
if (!CODE_INDEX_MODES.includes(index)) {
|
|
158
|
+
process.stderr.write(
|
|
159
|
+
`Unknown index mode ${options.index}. Use: ${CODE_INDEX_MODES.join(", ")}.
|
|
160
|
+
`
|
|
161
|
+
);
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
if (options.executor !== void 0 && !EXECUTOR_NAMES.includes(options.executor)) {
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`Unknown executor ${options.executor}. Use: ${EXECUTOR_NAMES.join(", ")}.
|
|
167
|
+
`
|
|
168
|
+
);
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
const subagentsMode = options.subagents ?? "off";
|
|
172
|
+
if (subagentsMode !== "on" && subagentsMode !== "off") {
|
|
173
|
+
process.stderr.write(`Unknown subagents mode ${options.subagents}. Use: on, off.
|
|
174
|
+
`);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
const todoMode = options.todo ?? "off";
|
|
178
|
+
if (todoMode !== "on" && todoMode !== "off") {
|
|
179
|
+
process.stderr.write(`Unknown todo mode ${options.todo}. Use: on, off.
|
|
180
|
+
`);
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
const formatMode = options.format ?? "off";
|
|
184
|
+
if (formatMode !== "on" && formatMode !== "off") {
|
|
185
|
+
process.stderr.write(`Unknown format mode ${options.format}. Use: on, off.
|
|
186
|
+
`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
let biome;
|
|
190
|
+
if (options.format !== void 0) {
|
|
191
|
+
biome = garudaBiome();
|
|
192
|
+
if (biome === void 0) {
|
|
193
|
+
process.stderr.write(
|
|
194
|
+
"--format needs Biome in Garuda's node_modules (a source checkout after pnpm install).\n"
|
|
195
|
+
);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
if (options.suite === "java" || options.suite === "python" || options.suite === "all" || options.suite === "repo" || options.split !== void 0 && options.split !== "dev") {
|
|
199
|
+
process.stderr.write("--format works with the JS suites: basic and hard.\n");
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const thinkingMode = options.keepThinking ?? "on";
|
|
204
|
+
if (thinkingMode !== "on" && thinkingMode !== "off") {
|
|
205
|
+
process.stderr.write(`Unknown keep-thinking mode ${options.keepThinking}. Use: on, off.
|
|
206
|
+
`);
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
const lspMode = options.lsp ?? "off";
|
|
210
|
+
if (lspMode !== "on" && lspMode !== "off") {
|
|
211
|
+
process.stderr.write(`Unknown lsp mode ${options.lsp}. Use: on, off.
|
|
212
|
+
`);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
let network;
|
|
216
|
+
if (options.network !== void 0) {
|
|
217
|
+
network = options.network.flatMap((e) => e.split(",")).filter((e) => e.trim() !== "");
|
|
218
|
+
const { problems } = expandAllowlist(network);
|
|
219
|
+
if (problems.length > 0) {
|
|
220
|
+
process.stderr.write(`--network: ${problems.join(" ")}
|
|
221
|
+
`);
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
if (network.length === 0) network = void 0;
|
|
225
|
+
}
|
|
226
|
+
const batchMode = options.batch ?? "off";
|
|
227
|
+
if (batchMode !== "on" && batchMode !== "off") {
|
|
228
|
+
process.stderr.write(`Unknown batch mode ${options.batch}. Use: on, off.
|
|
229
|
+
`);
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
const createBatch = resolved.createBatch;
|
|
233
|
+
if (batchMode === "on" && createBatch === void 0) {
|
|
234
|
+
process.stderr.write(`--batch on needs an Anthropic model; ${resolved.spec} is not one.
|
|
235
|
+
`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
let sub;
|
|
239
|
+
if (options.subagentModel) {
|
|
240
|
+
try {
|
|
241
|
+
sub = resolveModel(options.subagentModel, models.config);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
process.stderr.write(`${error.message}
|
|
244
|
+
`);
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const { assertModelAllowedByPolicy, loadTeamPolicy } = await import("./policy-ZJ7RMOFN.js");
|
|
249
|
+
let team;
|
|
250
|
+
try {
|
|
251
|
+
team = await loadTeamPolicy();
|
|
252
|
+
assertModelAllowedByPolicy(team?.policy, resolved.spec);
|
|
253
|
+
if (sub !== void 0) assertModelAllowedByPolicy(team?.policy, sub.spec);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
process.stderr.write(`Team policy: ${error.message}
|
|
256
|
+
`);
|
|
257
|
+
return 1;
|
|
258
|
+
}
|
|
259
|
+
const credentialsWarning = await keepProviderKeys([
|
|
260
|
+
resolved.def.apiKeyEnv,
|
|
261
|
+
sub?.def.apiKeyEnv,
|
|
262
|
+
"ANTHROPIC_API_KEY"
|
|
263
|
+
]);
|
|
264
|
+
if (credentialsWarning !== void 0) process.stderr.write(`${credentialsWarning}
|
|
265
|
+
`);
|
|
266
|
+
const agentName = options.agent ?? "garuda";
|
|
267
|
+
let baseline;
|
|
268
|
+
if (agentName === "claude-code") {
|
|
269
|
+
const made = await claudeCodeAgent(options, resolved);
|
|
270
|
+
if (typeof made === "string") {
|
|
271
|
+
process.stderr.write(`--agent claude-code: ${made}
|
|
272
|
+
`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
baseline = made;
|
|
276
|
+
network = ["api.anthropic.com"];
|
|
277
|
+
} else if (agentName !== "garuda") {
|
|
278
|
+
process.stderr.write(`Unknown agent ${options.agent}. Use: garuda, claude-code.
|
|
279
|
+
`);
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const split = options.split ?? "dev";
|
|
283
|
+
const suite = options.suite ?? (split === "dev" ? "basic" : split === "all" ? "all" : "heldout");
|
|
284
|
+
const choice = chooseTasks({
|
|
285
|
+
suite: options.suite,
|
|
286
|
+
split,
|
|
287
|
+
task: options.task,
|
|
288
|
+
repoTasks
|
|
289
|
+
});
|
|
290
|
+
if ("problem" in choice) {
|
|
291
|
+
process.stderr.write(`${choice.problem}
|
|
292
|
+
`);
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
const chosen = choice.tasks;
|
|
296
|
+
const repeat = Math.max(1, options.repeat ?? 1);
|
|
297
|
+
const seed = options.seed ?? DEFAULT_SEED;
|
|
298
|
+
if (!Number.isInteger(seed) || seed < 0) {
|
|
299
|
+
process.stderr.write("--seed must be a whole number, 0 or more.\n");
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
const listed = chosen.flatMap((t) => Array.from({ length: repeat }, () => t));
|
|
303
|
+
const tasks = options.shuffle ? shuffled(listed, seed) : listed;
|
|
304
|
+
if (chosen.length === 0) {
|
|
305
|
+
process.stderr.write("No task matches. Use --list to see the task ids.\n");
|
|
306
|
+
return 1;
|
|
307
|
+
}
|
|
308
|
+
const missing = (await checkToolchains(requiredToolchains(chosen))).filter((s) => !s.ok);
|
|
309
|
+
if (missing.length > 0) {
|
|
310
|
+
for (const status of missing) {
|
|
311
|
+
const detail = status.output.split("\n").filter((line) => line.trim() !== "").slice(0, 2).join("\n ");
|
|
312
|
+
process.stderr.write(
|
|
313
|
+
`${TOOLCHAINS[status.id].hint}
|
|
314
|
+
${detail === "" ? "" : ` ${detail}
|
|
315
|
+
`}`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
const mcp = await chooseMcp(options, homedir());
|
|
321
|
+
if (typeof mcp === "string") {
|
|
322
|
+
process.stderr.write(`${mcp}
|
|
323
|
+
`);
|
|
324
|
+
return 1;
|
|
325
|
+
}
|
|
326
|
+
let addendum;
|
|
327
|
+
if (options.addendum !== void 0) {
|
|
328
|
+
const { readAddendum } = await import("./tune-VX2WQTHS.js");
|
|
329
|
+
const read = await readAddendum(options.addendum);
|
|
330
|
+
if ("problem" in read) {
|
|
331
|
+
process.stderr.write(`${read.problem}
|
|
332
|
+
`);
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
addendum = read.text;
|
|
336
|
+
}
|
|
337
|
+
const { executor, notice } = createExecutor(options.executor ?? "auto");
|
|
338
|
+
if (notice !== void 0) process.stderr.write(`${notice}
|
|
339
|
+
`);
|
|
340
|
+
if (baseline !== void 0 && executor.isolation === "none") {
|
|
341
|
+
process.stderr.write(
|
|
342
|
+
"--agent claude-code needs the OS sandbox: Claude Code runs with every tool allowed, so Garuda runs it only inside the sandbox.\n"
|
|
343
|
+
);
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
if (lspMode === "on") {
|
|
347
|
+
if (executor.isolation === "none") {
|
|
348
|
+
process.stderr.write("--lsp on needs the OS sandbox: language servers run only there.\n");
|
|
349
|
+
return 1;
|
|
350
|
+
}
|
|
351
|
+
const { discoverServer, LSP_LANGUAGES } = await import("./servers-6YDGWOZ6.js");
|
|
352
|
+
const found = LSP_LANGUAGES.map((l) => ({ l, s: discoverServer(l, { root: process.cwd() }) }));
|
|
353
|
+
if (found.every((f) => f.s === void 0)) {
|
|
354
|
+
process.stderr.write(
|
|
355
|
+
"--lsp on: no language server found. Run: garuda lsp install typescript\n"
|
|
356
|
+
);
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
for (const { l, s } of found) {
|
|
360
|
+
process.stderr.write(
|
|
361
|
+
`LSP ${l}: ${s === void 0 ? "none" : `${s.spec.name} (${s.path})`}
|
|
362
|
+
`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const where = executor.isolation === "none" ? "commands run on this machine with no sandbox" : `commands and the checks run in the ${executor.name} sandbox`;
|
|
367
|
+
const parallel = Math.max(
|
|
368
|
+
1,
|
|
369
|
+
options.parallel ?? (batchMode === "on" ? Math.min(tasks.length, 20) : 1)
|
|
370
|
+
);
|
|
371
|
+
const outDir = options.outDir ?? join(process.cwd(), ".garuda", "evals", newSessionId());
|
|
372
|
+
mkdirSync(outDir, { recursive: true });
|
|
373
|
+
const stale = staleBuildNote(BUILD_COMMIT, await garudaCommit());
|
|
374
|
+
if (stale !== void 0) process.stderr.write(`Warning: ${stale}
|
|
375
|
+
|
|
376
|
+
`);
|
|
377
|
+
process.stderr.write(
|
|
378
|
+
`${baseline === void 0 ? "" : `Baseline: Claude Code ${baseline.version ?? ""} (claude -p, every tool allowed, in the sandbox, network only to api.anthropic.com).
|
|
379
|
+
`}Running ${chosen.length} task(s)${repeat > 1 ? ` \xD7 ${repeat}` : ""}${options.shuffle ? ` in a shuffled order (seed ${seed})` : ""} (suite ${options.task === void 0 ? suite : "custom"}, code index ${index}, subagents ${subagentsMode}${sub === void 0 ? "" : ` on ${sub.spec}`}, todo ${todoMode}, lsp ${lspMode}, keep-thinking ${thinkingMode}, format ${formatMode}, network ${network === void 0 ? "off" : network.join(",")}, mcp ${mcp === void 0 ? "off" : `${mcp.server}${mcp.prepare === void 0 ? "" : `, prepared with \`${mcp.prepare}\``}`}, batch ${batchMode}${addendum === void 0 ? "" : `, addendum ${options.addendum} (${addendum.length} characters)`}${parallel > 1 ? `, ${parallel} at a time` : ""}) with ${modelId}. Garuda approves every call except its deny rules;
|
|
380
|
+
${where}, in scratch folders.
|
|
381
|
+
|
|
382
|
+
`
|
|
383
|
+
);
|
|
384
|
+
if (batchMode === "on") {
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
"Batch API: each model call waits for its batch (minutes, sometimes more) at half the token price.\n\n"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const started = Date.now();
|
|
390
|
+
const results = await runEvals(
|
|
391
|
+
tasks,
|
|
392
|
+
{
|
|
393
|
+
modelId: resolved.spec,
|
|
394
|
+
...team === void 0 ? {} : { policy: team.policy },
|
|
395
|
+
model: () => batchMode === "on" && createBatch !== void 0 ? createBatch() : resolved.create(),
|
|
396
|
+
// Batch responses carry their own price factor (half): the price stays the model's.
|
|
397
|
+
modelInfo: resolved.info,
|
|
398
|
+
parallel,
|
|
399
|
+
...batchMode === "on" ? { taskTimeoutMs: BATCH_TASK_TIMEOUT_MS } : {},
|
|
400
|
+
...resolved.maxTokens === void 0 ? {} : { maxTokens: resolved.maxTokens },
|
|
401
|
+
outDir,
|
|
402
|
+
...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
|
|
403
|
+
codeIndex: index,
|
|
404
|
+
subagents: subagentsMode === "on",
|
|
405
|
+
todo: todoMode === "on",
|
|
406
|
+
lsp: lspMode === "on",
|
|
407
|
+
keepThinking: thinkingMode === "on",
|
|
408
|
+
...network === void 0 ? {} : { network },
|
|
409
|
+
...baseline === void 0 ? {} : { agent: baseline },
|
|
410
|
+
...mcp === void 0 ? {} : { mcp },
|
|
411
|
+
...addendum === void 0 ? {} : { addendum },
|
|
412
|
+
...biome === void 0 ? {} : { format: { on: formatMode === "on", biome } },
|
|
413
|
+
...sub === void 0 ? {} : { subagentModel: { spec: sub.spec, model: () => sub.create(), info: sub.info } },
|
|
414
|
+
executor: executor.name === "host" ? "host" : "os",
|
|
415
|
+
...options.keep ? { keep: true } : {},
|
|
416
|
+
onEvent: (taskId, event) => {
|
|
417
|
+
if (event.type === "tool_call") process.stderr.write(` [${taskId}] ${event.call.name}
|
|
418
|
+
`);
|
|
419
|
+
if (event.type === "notice") process.stderr.write(` [${taskId}] ${event.text}
|
|
420
|
+
`);
|
|
421
|
+
if (event.type === "model_retry")
|
|
422
|
+
process.stderr.write(
|
|
423
|
+
` [${taskId}] retry ${event.attempt}/${event.maxRetries}: ${event.reason}
|
|
424
|
+
`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
(r) => process.stderr.write(
|
|
429
|
+
`${r.passed ? "\u2713" : "\u2717"} ${r.id}: ${r.passed ? "pass" : "fail"} (${r.steps} steps)${r.reason ? `
|
|
430
|
+
${r.reason.split("\n").slice(0, 3).join("\n ")}` : ""}
|
|
431
|
+
`
|
|
432
|
+
),
|
|
433
|
+
// A refused key or an unreachable API fails every run: stop at the first one (0.17).
|
|
434
|
+
isSetupFailure
|
|
435
|
+
);
|
|
436
|
+
const wallMs = Date.now() - started;
|
|
437
|
+
if (results.length < tasks.length) {
|
|
438
|
+
const last = results[results.length - 1];
|
|
439
|
+
process.stderr.write(
|
|
440
|
+
last !== void 0 && isAuthFailure(last) ? `
|
|
441
|
+
Stopped after ${results.length} of ${tasks.length} run(s): the provider refused the API key or the access. Check ANTHROPIC_API_KEY (it wins over the stored key) or run \`garuda setup\`.
|
|
442
|
+
` : last !== void 0 && isMcpFailure(last) ? `
|
|
443
|
+
Stopped after ${results.length} of ${tasks.length} run(s): the --mcp server or its prepare command failed (see the reason above), so every other run would fail the same way.
|
|
444
|
+
` : `
|
|
445
|
+
Stopped after ${results.length} of ${tasks.length} run(s): the API could not be reached (see the reason above), so every other run would fail the same way.
|
|
446
|
+
`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const report = `${formatReport(results, { seed })}
|
|
450
|
+
Wall time: ${Math.round(wallMs / 1e3)} s${parallel > 1 ? ` (${parallel} tasks at a time)` : ""}.`;
|
|
451
|
+
writeFileSync(
|
|
452
|
+
join(outDir, "report.json"),
|
|
453
|
+
`${JSON.stringify({ ...await reportV2({ model: resolved.spec, provider: resolved.provider, executor: executor.name, isolation: executor.isolation, seed, shuffle: options.shuffle === true, split, ...mcp === void 0 ? {} : { mcp }, ...addendum === void 0 ? {} : { addendum: { file: options.addendum ?? "", text: addendum } }, agent: baseline === void 0 ? { name: "garuda" } : { name: "claude-code", ...baseline.version === void 0 ? {} : { version: baseline.version } } }, chosen, results), model: modelId, suite, repeat, codeIndex: index, subagents: subagentsMode, subagentModel: sub?.spec, todo: todoMode, lsp: lspMode, keepThinking: thinkingMode, format: formatMode, network: network ?? "off", batch: batchMode, parallel, wallMs, executor: executor.name, summary: summarize(results), results }, null, 2)}
|
|
454
|
+
`
|
|
455
|
+
);
|
|
456
|
+
process.stdout.write(`
|
|
457
|
+
${report}
|
|
458
|
+
|
|
459
|
+
Session files and report.json: ${outDir}
|
|
460
|
+
`);
|
|
461
|
+
return results.every((r) => r.passed) ? 0 : 2;
|
|
462
|
+
}
|
|
463
|
+
async function repoSuite() {
|
|
464
|
+
const root = await gitRoot(process.cwd());
|
|
465
|
+
if (root === void 0) return "-s repo needs a git repository: run it in your project.";
|
|
466
|
+
let suite;
|
|
467
|
+
try {
|
|
468
|
+
suite = await loadRepoSuite(root);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
return error.message;
|
|
471
|
+
}
|
|
472
|
+
if (suite === void 0) {
|
|
473
|
+
return `No repo suite in ${root}. Build it first: garuda eval --from-git`;
|
|
474
|
+
}
|
|
475
|
+
if (suite.tasks.length === 0) {
|
|
476
|
+
return `The repo suite has no tasks. Build it again with more commits: garuda eval --from-git --commits 500`;
|
|
477
|
+
}
|
|
478
|
+
return { ...suite, root };
|
|
479
|
+
}
|
|
480
|
+
async function gitRoot(cwd) {
|
|
481
|
+
const r = await git(new HostExecutor(), cwd, ["rev-parse", "--show-toplevel"], { check: false });
|
|
482
|
+
return r.exitCode === 0 ? realpathSync(r.stdout.trim()) : void 0;
|
|
483
|
+
}
|
|
484
|
+
async function buildFromGit(options) {
|
|
485
|
+
const root = await gitRoot(process.cwd());
|
|
486
|
+
if (root === void 0) {
|
|
487
|
+
process.stderr.write("--from-git needs a git repository: run it in your project.\n");
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
const testCommand = options.testCommand ?? detectTestCommand(root, detectProfiles(root));
|
|
491
|
+
if (testCommand === void 0) {
|
|
492
|
+
process.stderr.write(
|
|
493
|
+
'No test command found. Give one: garuda eval --from-git --test-command "npm test"\n'
|
|
494
|
+
);
|
|
495
|
+
return 1;
|
|
496
|
+
}
|
|
497
|
+
for (const [name, value] of [
|
|
498
|
+
["--commits", options.commits],
|
|
499
|
+
["--max-tasks", options.maxTasks]
|
|
500
|
+
]) {
|
|
501
|
+
if (value !== void 0 && !(Number.isInteger(value) && value > 0)) {
|
|
502
|
+
process.stderr.write(`${name} must be a whole number above 0.
|
|
503
|
+
`);
|
|
504
|
+
return 1;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
process.stderr.write(
|
|
508
|
+
`Building the repo suite from ${options.commits ?? DEFAULT_COMMITS} recent commits${options.since === void 0 ? "" : ` since ${options.since}`} in ${root}.
|
|
509
|
+
Each candidate runs its own test files with \`${testCommand}\` twice on this machine (no sandbox), in a git worktree; your checkout does not change.
|
|
510
|
+
|
|
511
|
+
`
|
|
512
|
+
);
|
|
513
|
+
const { suite, checked, skipped, failure } = await buildRepoSuite(new HostExecutor(), root, {
|
|
514
|
+
testCommand,
|
|
515
|
+
...options.commits === void 0 ? {} : { commits: options.commits },
|
|
516
|
+
...options.since === void 0 ? {} : { since: options.since },
|
|
517
|
+
...options.maxTasks === void 0 ? {} : { maxTasks: options.maxTasks },
|
|
518
|
+
onProgress: (line) => process.stderr.write(` ${line}
|
|
519
|
+
`)
|
|
520
|
+
});
|
|
521
|
+
const file = await saveRepoSuite(suite);
|
|
522
|
+
const reasons = Object.entries(skipped).sort((a, b) => b[1] - a[1]).map(([why, n]) => ` ${String(n).padStart(4)} ${why}`);
|
|
523
|
+
process.stdout.write(
|
|
524
|
+
`${[
|
|
525
|
+
`
|
|
526
|
+
${suite.tasks.length} task(s) kept; ${checked} candidate(s) checked with the tests.`,
|
|
527
|
+
...reasons.length === 0 ? [] : ["Skipped:", ...reasons],
|
|
528
|
+
...failure === void 0 ? [] : [
|
|
529
|
+
`The first failure at a commit (${failure.sha.slice(0, 7)}: \`${failure.command}\`):`,
|
|
530
|
+
...failure.tail.split("\n").map((line) => ` ${line}`),
|
|
531
|
+
'If every commit fails like this, the tests do not run in a worktree on this machine; give a working command with --test-command ("{files}" takes the test files).'
|
|
532
|
+
],
|
|
533
|
+
`Suite: ${file}`,
|
|
534
|
+
...suite.tasks.length === 0 ? ["No task: try more commits (--commits 500) or an older --since."] : ["Run it: garuda eval -s repo -m <model> (add --list to see the tasks)"]
|
|
535
|
+
].join("\n")}
|
|
536
|
+
`
|
|
537
|
+
);
|
|
538
|
+
return suite.tasks.length === 0 ? 2 : 0;
|
|
539
|
+
}
|
|
540
|
+
async function prepare(name) {
|
|
541
|
+
const id = toolchainId(name);
|
|
542
|
+
if (id === void 0) {
|
|
543
|
+
process.stderr.write(`Unknown toolchain ${name}. Use: java, python.
|
|
544
|
+
`);
|
|
545
|
+
return 1;
|
|
546
|
+
}
|
|
547
|
+
const toolchain = TOOLCHAINS[id];
|
|
548
|
+
if (toolchain.prepare !== void 0) {
|
|
549
|
+
process.stderr.write(
|
|
550
|
+
`Preparing ${toolchain.title}: running \`${toolchain.prepare}\` in a scratch project (downloads with network)\u2026
|
|
551
|
+
`
|
|
552
|
+
);
|
|
553
|
+
const r = await prepareToolchain(id);
|
|
554
|
+
if (!r.ok) {
|
|
555
|
+
process.stderr.write(`It failed:
|
|
556
|
+
${r.output}
|
|
557
|
+
`);
|
|
558
|
+
return 1;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const [status] = await checkToolchains([id]);
|
|
562
|
+
if (status?.ok) {
|
|
563
|
+
process.stderr.write(`${toolchain.title}: ready.
|
|
564
|
+
`);
|
|
565
|
+
return 0;
|
|
566
|
+
}
|
|
567
|
+
process.stderr.write(`${toolchain.hint}
|
|
568
|
+
${status?.output ?? ""}
|
|
569
|
+
`);
|
|
570
|
+
return 1;
|
|
571
|
+
}
|
|
572
|
+
function garudaBiome() {
|
|
573
|
+
try {
|
|
574
|
+
const pkg = createRequire(import.meta.url).resolve("@biomejs/biome/package.json");
|
|
575
|
+
const bin = join(dirname(pkg), "bin", "biome");
|
|
576
|
+
return existsSync(bin) ? bin : void 0;
|
|
577
|
+
} catch {
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function runEvalCompare(pathA, pathB, options = {}) {
|
|
582
|
+
const seed = options.seed ?? DEFAULT_SEED;
|
|
583
|
+
if (!Number.isInteger(seed) || seed < 0) {
|
|
584
|
+
process.stderr.write("--seed must be a whole number, 0 or more.\n");
|
|
585
|
+
return 1;
|
|
586
|
+
}
|
|
587
|
+
let a;
|
|
588
|
+
let b;
|
|
589
|
+
try {
|
|
590
|
+
[a, b] = await Promise.all([loadReport(pathA), loadReport(pathB)]);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
process.stderr.write(`garuda eval compare: ${error.message}
|
|
593
|
+
`);
|
|
594
|
+
return 1;
|
|
595
|
+
}
|
|
596
|
+
const result = compareReports(a, b, { seed });
|
|
597
|
+
if ("problems" in result) {
|
|
598
|
+
process.stderr.write(
|
|
599
|
+
`garuda eval compare: these reports cannot be compared.
|
|
600
|
+
${result.problems.map((p) => ` ${p}`).join("\n")}
|
|
601
|
+
`
|
|
602
|
+
);
|
|
603
|
+
return 1;
|
|
604
|
+
}
|
|
605
|
+
process.stdout.write(`${formatComparison(a, b, result.comparison)}
|
|
606
|
+
`);
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
async function claudeCodeAgent(options, resolved) {
|
|
610
|
+
if (resolved.provider !== "anthropic") {
|
|
611
|
+
return `the baseline needs a Claude model; ${resolved.spec} is not one.`;
|
|
612
|
+
}
|
|
613
|
+
const garudaOnly = [
|
|
614
|
+
["--index", options.index],
|
|
615
|
+
["--subagents", options.subagents],
|
|
616
|
+
["--subagent-model", options.subagentModel],
|
|
617
|
+
["--todo", options.todo],
|
|
618
|
+
["--lsp", options.lsp],
|
|
619
|
+
["--keep-thinking", options.keepThinking],
|
|
620
|
+
["--format", options.format],
|
|
621
|
+
["--batch", options.batch],
|
|
622
|
+
["--network", options.network],
|
|
623
|
+
["--mcp", options.mcp],
|
|
624
|
+
["--mcp-prepare", options.mcpPrepare],
|
|
625
|
+
["--addendum", options.addendum]
|
|
626
|
+
];
|
|
627
|
+
const set = garudaOnly.filter(([, value]) => value !== void 0).map(([name]) => name);
|
|
628
|
+
if (set.length > 0) {
|
|
629
|
+
return `${set.join(", ")} change${set.length === 1 ? "s" : ""} Garuda only; leave ${set.length === 1 ? "it" : "them"} out of the baseline run.`;
|
|
630
|
+
}
|
|
631
|
+
const key = providerKeyValue("ANTHROPIC_API_KEY");
|
|
632
|
+
if (key === void 0 || key === "") {
|
|
633
|
+
return "set ANTHROPIC_API_KEY (or store a key with `garuda setup`). A Claude subscription login is not used: the sandbox keeps the Keychain closed.";
|
|
634
|
+
}
|
|
635
|
+
const command = onPath("claude");
|
|
636
|
+
if (command === void 0) {
|
|
637
|
+
return "the `claude` program is not on PATH. Install Claude Code: npm install -g @anthropic-ai/claude-code";
|
|
638
|
+
}
|
|
639
|
+
const probe = await new HostExecutor().run(`'${command.replace(/'/g, "'\\''")}' --version`, {
|
|
640
|
+
root: process.cwd(),
|
|
641
|
+
sandbox: false,
|
|
642
|
+
writePaths: [],
|
|
643
|
+
denyWritePaths: [],
|
|
644
|
+
denyReadPaths: [],
|
|
645
|
+
network: false,
|
|
646
|
+
envAllowlist: ["PATH", "HOME"],
|
|
647
|
+
timeoutMs: 2e4,
|
|
648
|
+
maxOutputBytes: 2e3
|
|
649
|
+
});
|
|
650
|
+
const version = probe.exitCode === 0 ? probe.stdout.text.trim().split("\n")[0] : void 0;
|
|
651
|
+
return {
|
|
652
|
+
name: "claude-code",
|
|
653
|
+
command,
|
|
654
|
+
model: resolved.model,
|
|
655
|
+
...version === void 0 || version === "" ? {} : { version },
|
|
656
|
+
env: { ANTHROPIC_API_KEY: key }
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function onPath(program) {
|
|
660
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
661
|
+
const path = join(dir, program);
|
|
662
|
+
if (dir !== "" && existsSync(path)) return path;
|
|
663
|
+
}
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
async function chooseMcp(options, home) {
|
|
667
|
+
if (options.mcp === void 0) {
|
|
668
|
+
return options.mcpPrepare === void 0 ? void 0 : "--mcp-prepare needs --mcp <server>.";
|
|
669
|
+
}
|
|
670
|
+
const { loadMcpConfig } = await import("./config-WV2H64TF.js");
|
|
671
|
+
const { servers, problems } = await loadMcpConfig({ home, root: home });
|
|
672
|
+
const own = servers.filter((s) => s.source === "user");
|
|
673
|
+
const server = own.find((s) => s.name === options.mcp);
|
|
674
|
+
if (server === void 0) {
|
|
675
|
+
const names = own.map((s) => s.name);
|
|
676
|
+
return `--mcp ${options.mcp}: no server of that name in ~/.garuda/mcp.json${names.length === 0 ? "" : ` (yours: ${names.join(", ")})`}.${problems.length === 0 ? "" : `
|
|
677
|
+
${problems.join("\n")}`}`;
|
|
678
|
+
}
|
|
679
|
+
if (!server.def.enabled) {
|
|
680
|
+
return `--mcp ${options.mcp}: the server is disabled ("enabled": false) in ~/.garuda/mcp.json.`;
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
server: server.name,
|
|
684
|
+
home,
|
|
685
|
+
...options.mcpPrepare === void 0 ? {} : { prepare: options.mcpPrepare }
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
export {
|
|
689
|
+
BATCH_TASK_TIMEOUT_MS,
|
|
690
|
+
chooseMcp,
|
|
691
|
+
claudeCodeAgent,
|
|
692
|
+
repoSuite,
|
|
693
|
+
runEvalCommand,
|
|
694
|
+
runEvalCompare
|
|
695
|
+
};
|
|
696
|
+
//# sourceMappingURL=evalCommand-BLJJ4G76.js.map
|