agent-trials 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prince Jain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,118 @@
1
- # Temporary Holding Version
1
+ # agent-trials
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Interview your coding agent before you ship with it.
4
+
5
+ `agent-trials` runs the same task suite through every coding agent CLI you have installed and prints a scoreboard: who passed, how long they took, what they spent. Each task gets a temporary working copy. Commands still run on your host.
6
+
7
+ ![demo](demo.png)
8
+
9
+ ## why
10
+
11
+ Public benchmarks score agents on tasks someone else picked. Your repo is not that benchmark. The question that matters before you let an agent loose on a codebase: does it fix YOUR bugs, in YOUR stack, at a sane price? Write the tasks once, run every candidate, keep the receipts.
12
+
13
+ ## execution safety
14
+
15
+ A temporary working copy is not a security sandbox. Agents and suite commands run with your host permissions and environment; built-in Claude and Gemini commands disable approval prompts. Repository dotfiles and secrets may be copied. Use only trusted suites in a disposable VM with no sensitive files or credentials. Do not run this on a workstation containing private data.
16
+
17
+ ## install
18
+
19
+ ```sh
20
+ npx agent-trials init # writes trials.json in your repo
21
+ ```
22
+
23
+ Works straight from GitHub too: `npx github:princejain756/agent-trials`.
24
+
25
+ ## use
26
+
27
+ ```sh
28
+ agent-trials init # starter suite
29
+ agent-trials doctor # which agent CLIs are installed
30
+ agent-trials run # run the suite, print the board, write TRIALS.md
31
+ ```
32
+
33
+ ## trials.json
34
+
35
+ ```json
36
+ {
37
+ "verify": "npm test",
38
+ "verifyPaths": ["verify"],
39
+ "timeoutSec": 600,
40
+ "tasks": [
41
+ {
42
+ "id": "fix-cache",
43
+ "prompt": "The cache in src/cache.js never expires entries. Add a ttl option. Keep the API.",
44
+ "verify": "node checks/cache-ttl.mjs"
45
+ }
46
+ ],
47
+ "agents": {
48
+ "sonnet": { "run": ["claude", "-p", "{prompt}", "--dangerously-skip-permissions", "--model", "sonnet"] }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Each cell (agent x task) works like this:
54
+
55
+ 1. your repo, minus `node_modules` and `.git`, is copied to a temporary working copy
56
+ 2. optional `setup` command runs once (`npm ci` and friends)
57
+ 3. the agent runs headless on the task prompt, using the selected CLI flags, timeout-guarded
58
+ 4. checker paths are restored from your repo, so the agent cannot rewrite its own test
59
+ 5. your `verify` command runs in the working copy; exit 0 means pass
60
+
61
+ Step 4 restores `verifyPaths` from the suite, or a top-level `verify/` dir when `verifyPaths` is not set. A verify command that reads mutable state outside those paths can still be gamed; put your checkers in `verify/` or list them.
62
+
63
+ Builtin agents: `claude`, `codex`, `gemini`. Custom agents are one argv array away: anything installed, any flags, `{prompt}` gets replaced with the task prompt.
64
+
65
+ ## a real run
66
+
67
+ `examples/tinylib` is a 3-function library with planted bugs. Same 3 tasks, 3 model variants of the same agent CLI (`claude --model`):
68
+
69
+ ```
70
+ agent-trials · 3 tasks · 3 agents
71
+
72
+ opus sonnet haiku
73
+ fix-clamp pass 141.3s pass 44.2s pass 60.9s
74
+ divide-guard fail 96.1s pass 72.1s pass 118.6s
75
+ slug-test pass 253.4s fail 124.4s pass 50s
76
+
77
+ score 2/3 2/3 3/3
78
+ median 141.3s 72.1s 60.9s
79
+ cost $1.11 $1.10 $2.06
80
+
81
+ winner: haiku (3/3, median 60.9s)
82
+ ```
83
+
84
+ Read this board with caveats. `opus`, `sonnet`, and `haiku` are requested CLI aliases; that run predates model-identity capture (`--json` now records `modelUsed` per cell when the agent CLI reports it). One run of three toy tasks is a smoke test, not a comparative benchmark. Costs are CLI-reported, not invoice costs.
85
+
86
+ What the board caught:
87
+
88
+ - haiku went 3/3 with the fastest median while spending the most: $2.06. Pass rate and price did not move together.
89
+ - in one earlier run, the agent asked a clarifying question ("should -0 count as zero?") into an empty terminal, then exited 0 having changed nothing. One observed example, not a general claim. This showed why exit code alone cannot be the verdict; the verify command is what counts.
90
+ - sonnet failed slug-test.
91
+
92
+ Full results with per-cell costs: [LEADERBOARD.md](LEADERBOARD.md)
93
+
94
+ ## limits, honestly
95
+
96
+ - pass/fail is your verify command's exit code. The tool cannot judge taste, only outcomes.
97
+ - runs spend your agent quota and your auth; costs are reported when the agent's JSON output includes them (claude does)
98
+ - agent CLIs must be installed and logged in
99
+ - macOS/Linux; the working copy and verify assume `sh`. Windows untested
100
+ - scoring is binary per task. No partial credit for "almost fixed it"
101
+
102
+ ## flags
103
+
104
+ | flag | default | what it does |
105
+ |---|---|---|
106
+ | `--suite <file>` | `trials.json` | suite file to run |
107
+ | `--agents a,b` | all known | restrict the agent list |
108
+ | `--root <dir>` | `.` | the repo being tested |
109
+ | `--jobs <n>` | `2` | parallel agent x task cells |
110
+ | `--out <file>` | `TRIALS.md` | markdown report path, `none` skips |
111
+ | `--json <file>` | - | raw per-cell results |
112
+ | `--keep` | off | keep working copies under /tmp for debugging |
113
+
114
+ ## why the suite format is the point
115
+
116
+ Tasks are plain JSON and `verify` is plain shell, so a suite doubles as a smoke-test pack for humans. Commit `trials.json` next to the code it describes. When you swap agents or bump model versions, rerun and diff the board.
117
+
118
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { agentInstalled, BUILTIN_AGENTS, loadSuite, renderBoard, renderMarkdown, resolveAgents, runSuite, writeMarkdown, verdictOf, } from "./trials.js";
5
+ const VERSION = "1.0.1";
6
+ const HELP = `agent-trials ${VERSION}
7
+ Interview your coding agent before you ship with it.
8
+
9
+ usage:
10
+ agent-trials init write a starter trials.json
11
+ agent-trials doctor check which agent CLIs are installed
12
+ agent-trials run run the suite and print the scoreboard
13
+
14
+ run flags:
15
+ --suite <file> suite file (default: trials.json)
16
+ --agents a,b only these agents (default: all known + custom)
17
+ --root <dir> repo to run on (default: cwd)
18
+ --jobs <n> parallel cells (default: 2)
19
+ --out <file> markdown report path (default: TRIALS.md, "none" skips)
20
+ --json <file> also write raw results JSON
21
+ --keep keep working copies for debugging
22
+ `;
23
+ const STARTER_SUITE = {
24
+ name: "my repo trials",
25
+ setup: "",
26
+ verify: "npm test",
27
+ timeoutSec: 600,
28
+ tasks: [
29
+ {
30
+ id: "example-task",
31
+ prompt: "Describe what this repo does in one sentence, then make the smallest possible improvement to the README.",
32
+ verify: "git diff --quiet || true",
33
+ },
34
+ ],
35
+ agents: {},
36
+ };
37
+ function parseArgs(argv) {
38
+ const out = {};
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ if (a.startsWith("--")) {
42
+ const key = a.slice(2);
43
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
44
+ out[key] = argv[++i];
45
+ }
46
+ else {
47
+ out[key] = true;
48
+ }
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+ function fail(msg) {
54
+ console.error(`agent-trials: ${msg}`);
55
+ process.exit(1);
56
+ }
57
+ async function main() {
58
+ const [cmd, ...rest] = process.argv.slice(2);
59
+ const flags = parseArgs(rest);
60
+ if (!cmd || flags.help || cmd === "help") {
61
+ console.log(HELP);
62
+ return;
63
+ }
64
+ if (cmd === "--version" || cmd === "-v" || flags.version) {
65
+ console.log(VERSION);
66
+ return;
67
+ }
68
+ if (cmd === "init") {
69
+ const file = typeof flags.suite === "string" ? flags.suite : "trials.json";
70
+ if (existsSync(file) && !flags.force) {
71
+ fail(`${file} already exists (use --force to overwrite)`);
72
+ }
73
+ writeFileSync(file, JSON.stringify(STARTER_SUITE, null, 2) + "\n");
74
+ console.log(`wrote ${file}\n\nedit tasks[] with real work for your repo, then:\n agent-trials run`);
75
+ return;
76
+ }
77
+ if (cmd === "doctor") {
78
+ const suiteFile = typeof flags.suite === "string" ? flags.suite : "trials.json";
79
+ const suite = existsSync(suiteFile)
80
+ ? loadSuite(suiteFile)
81
+ : { tasks: [] };
82
+ const agents = resolveAgents(typeof flags.agents === "string" ? flags.agents.split(",") : undefined, suite);
83
+ console.log("agent-trials doctor\n");
84
+ for (const [name, spec] of Object.entries(agents)) {
85
+ const bin = spec.argv[0];
86
+ const ok = agentInstalled(bin);
87
+ const src = BUILTIN_AGENTS[name] ? "builtin" : "custom";
88
+ console.log(`${ok ? "ok " : "miss"} ${name.padEnd(10)} ${bin}${ok ? "" : " (not on PATH)"} [${src}]`);
89
+ }
90
+ console.log("\nmissing agents are skipped at run time only if you pass --agents");
91
+ return;
92
+ }
93
+ if (cmd === "run") {
94
+ const suiteFile = typeof flags.suite === "string" ? flags.suite : "trials.json";
95
+ if (!existsSync(suiteFile)) {
96
+ fail(`no ${suiteFile}. run \`agent-trials init\` or pass --suite <file>`);
97
+ }
98
+ const suite = loadSuite(suiteFile);
99
+ const root = path.resolve(typeof flags.root === "string" ? flags.root : ".");
100
+ const agentNames = typeof flags.agents === "string" ? flags.agents.split(",") : undefined;
101
+ let agents = resolveAgents(agentNames, suite);
102
+ const missing = Object.entries(agents)
103
+ .filter(([, s]) => !agentInstalled(s.argv[0]))
104
+ .map(([n]) => n);
105
+ if (missing.length && !agentNames) {
106
+ for (const n of missing)
107
+ delete agents[n];
108
+ console.log(`skipping missing agents: ${missing.join(", ")}`);
109
+ }
110
+ const names = Object.keys(agents);
111
+ if (names.length === 0)
112
+ fail("no agents available. install claude/codex/gemini or define a custom agent");
113
+ const jobs = Math.max(1, Number(flags.jobs ?? 2) || 2);
114
+ const keep = Boolean(flags.keep);
115
+ const started = Date.now();
116
+ const results = await runSuite({
117
+ root,
118
+ suite,
119
+ agents,
120
+ jobs,
121
+ keep,
122
+ onCell: (c) => {
123
+ const verdict = verdictOf(c);
124
+ console.log(` [${verdict}] ${c.agent} · ${c.task} · ${c.sec}s`);
125
+ },
126
+ });
127
+ console.log("");
128
+ const board = renderBoard(suite, results, names);
129
+ console.log(board);
130
+ console.log(`\ntotal wall time ${Math.round((Date.now() - started) / 1000)}s`);
131
+ const out = flags.out === undefined ? "TRIALS.md" : flags.out;
132
+ if (out !== "none" && typeof out === "string") {
133
+ writeMarkdown(out, renderMarkdown(suite, results, names));
134
+ console.log(`wrote ${out}`);
135
+ }
136
+ if (typeof flags.json === "string") {
137
+ writeFileSync(flags.json, JSON.stringify(results, null, 2) + "\n");
138
+ console.log(`wrote ${flags.json}`);
139
+ }
140
+ return;
141
+ }
142
+ fail(`unknown command "${cmd}". try agent-trials help`);
143
+ }
144
+ main().catch((err) => fail(err instanceof Error ? err.message : String(err)));
package/dist/trials.js ADDED
@@ -0,0 +1,291 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ export const BUILTIN_AGENTS = {
6
+ claude: {
7
+ argv: ["claude", "-p", "{prompt}", "--dangerously-skip-permissions", "--output-format", "json"],
8
+ resultJson: true,
9
+ },
10
+ codex: { argv: ["codex", "exec", "--full-auto", "{prompt}"] },
11
+ gemini: { argv: ["gemini", "-p", "{prompt}", "--yolo", "-o", "json"], resultJson: true },
12
+ };
13
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".agent-trials"]);
14
+ export function loadSuite(file) {
15
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
16
+ if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
17
+ throw new Error(`${file}: needs a non-empty "tasks" array`);
18
+ }
19
+ for (const t of raw.tasks) {
20
+ if (!t.id || !t.prompt)
21
+ throw new Error(`${file}: every task needs "id" and "prompt"`);
22
+ if (!t.verify && !raw.verify)
23
+ throw new Error(`${file}: task "${t.id}" has no verify and suite has no default`);
24
+ }
25
+ if (raw.verifyPaths !== undefined && !Array.isArray(raw.verifyPaths)) {
26
+ throw new Error(`${file}: "verifyPaths" must be an array of repo-relative paths`);
27
+ }
28
+ return raw;
29
+ }
30
+ export function resolveAgents(names, suite) {
31
+ const custom = suite.agents ?? {};
32
+ const all = { ...BUILTIN_AGENTS };
33
+ for (const [name, def] of Object.entries(custom)) {
34
+ if (!Array.isArray(def.run) || def.run.length === 0) {
35
+ throw new Error(`agent "${name}": "run" must be an argv array, e.g. ["sh","./agent.sh","{prompt}"]`);
36
+ }
37
+ all[name] = { argv: def.run, resultJson: def.resultJson === true };
38
+ }
39
+ if (!names || names.length === 0)
40
+ return all;
41
+ const picked = {};
42
+ for (const n of names) {
43
+ if (!all[n])
44
+ throw new Error(`unknown agent "${n}". known: ${Object.keys(all).join(", ")}`);
45
+ picked[n] = all[n];
46
+ }
47
+ return picked;
48
+ }
49
+ export function copyWorkdir(root, dest) {
50
+ cpSync(root, dest, {
51
+ recursive: true,
52
+ filter: (src) => {
53
+ const rel = path.relative(root, src);
54
+ if (!rel)
55
+ return true;
56
+ const first = rel.split(path.sep)[0];
57
+ return !SKIP_DIRS.has(first);
58
+ },
59
+ });
60
+ }
61
+ function runCmd(argv, cwd, timeoutMs) {
62
+ return new Promise((resolve) => {
63
+ const child = spawn(argv[0], argv.slice(1), { cwd, env: process.env });
64
+ let stdout = "";
65
+ let stderr = "";
66
+ let timedOut = false;
67
+ const cap = (s, chunk) => (s.length < 512_000 ? s + chunk.toString() : s);
68
+ child.stdout.on("data", (c) => (stdout = cap(stdout, c)));
69
+ child.stderr.on("data", (c) => (stderr = cap(stderr, c)));
70
+ const killer = setTimeout(() => {
71
+ timedOut = true;
72
+ child.kill("SIGTERM");
73
+ setTimeout(() => child.kill("SIGKILL"), 5000).unref();
74
+ }, timeoutMs);
75
+ child.on("error", (err) => {
76
+ clearTimeout(killer);
77
+ resolve({ code: null, stdout, stderr: String(err), timedOut });
78
+ });
79
+ child.on("close", (code) => {
80
+ clearTimeout(killer);
81
+ resolve({ code, stdout, stderr, timedOut });
82
+ });
83
+ });
84
+ }
85
+ function parseAgentJson(stdout) {
86
+ try {
87
+ const j = JSON.parse(stdout.trim());
88
+ const cost = j.total_cost_usd ?? j.cost_usd ?? j.stats?.total_cost_usd;
89
+ const model = j.model ?? Object.keys(j.modelUsage ?? {})[0];
90
+ const out = {};
91
+ if (typeof cost === "number")
92
+ out.costUsd = cost;
93
+ if (typeof model === "string" && model)
94
+ out.modelUsed = model;
95
+ return out;
96
+ }
97
+ catch {
98
+ return {};
99
+ }
100
+ }
101
+ // The checker files are restored from the source repo after the agent runs,
102
+ // so an agent cannot rewrite its own test. The suite marks them with
103
+ // verifyPaths; a top-level verify/ dir is the convention fallback.
104
+ function protectedPaths(root, suite) {
105
+ const rels = suite.verifyPaths ?? (existsSync(path.join(root, "verify")) ? ["verify"] : []);
106
+ return rels.filter((rel) => !path.isAbsolute(rel) &&
107
+ !rel.split(/[\\/]+/).includes("..") &&
108
+ !SKIP_DIRS.has(rel.split(/[\\/]+/)[0]) &&
109
+ existsSync(path.join(root, rel)));
110
+ }
111
+ export async function runSuite(opts) {
112
+ const { root, suite, agents, jobs, keep, onCell } = opts;
113
+ const timeoutMs = (suite.timeoutSec ?? 600) * 1000;
114
+ const cells = [];
115
+ for (const [agent, spec] of Object.entries(agents)) {
116
+ for (const task of suite.tasks)
117
+ cells.push({ agent, spec, task });
118
+ }
119
+ const results = [];
120
+ let idx = 0;
121
+ async function worker() {
122
+ while (idx < cells.length) {
123
+ const cell = cells[idx++];
124
+ const { agent, spec, task } = cell;
125
+ const sandbox = mkdtempSync(path.join(tmpdir(), `agent-trials-${task.id}-`));
126
+ const t0 = Date.now();
127
+ const out = {
128
+ agent,
129
+ task: task.id,
130
+ pass: false,
131
+ agentExit: null,
132
+ verifyExit: null,
133
+ sec: 0,
134
+ };
135
+ try {
136
+ copyWorkdir(root, sandbox);
137
+ if (suite.setup) {
138
+ const s = await runCmd(["sh", "-c", suite.setup], sandbox, timeoutMs);
139
+ if (s.code !== 0)
140
+ throw new Error(`setup failed (${s.code}): ${s.stderr.slice(-200)}`);
141
+ }
142
+ const argv = spec.argv.map((a) => (a === "{prompt}" ? task.prompt : a));
143
+ if (!spec.argv.includes("{prompt}"))
144
+ argv.push(task.prompt);
145
+ const agentRes = await runCmd(argv, sandbox, timeoutMs);
146
+ out.agentExit = agentRes.code;
147
+ if (spec.resultJson)
148
+ Object.assign(out, parseAgentJson(agentRes.stdout));
149
+ if (agentRes.code === null) {
150
+ out.error = agentRes.timedOut ? "timeout" : `spawn failed: ${agentRes.stderr.slice(-160)}`;
151
+ }
152
+ const verifyCmd = task.verify ?? suite.verify;
153
+ const restored = protectedPaths(root, suite);
154
+ for (const rel of restored) {
155
+ cpSync(path.join(root, rel), path.join(sandbox, rel), { recursive: true });
156
+ }
157
+ if (restored.length)
158
+ out.verifyRestored = restored;
159
+ const v = await runCmd(["sh", "-c", verifyCmd], sandbox, timeoutMs);
160
+ out.verifyExit = v.code;
161
+ out.pass = out.agentExit === 0 && v.code === 0;
162
+ }
163
+ catch (err) {
164
+ out.error = err instanceof Error ? err.message : String(err);
165
+ }
166
+ finally {
167
+ out.sec = Math.round((Date.now() - t0) / 100) / 10;
168
+ if (!keep)
169
+ rmSync(sandbox, { recursive: true, force: true });
170
+ results.push(out);
171
+ onCell?.(out);
172
+ }
173
+ }
174
+ }
175
+ await Promise.all(Array.from({ length: Math.max(1, jobs) }, worker));
176
+ return results;
177
+ }
178
+ export function median(nums) {
179
+ if (nums.length === 0)
180
+ return 0;
181
+ const s = [...nums].sort((a, b) => a - b);
182
+ const mid = Math.floor(s.length / 2);
183
+ return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
184
+ }
185
+ export function summarize(results, agentNames) {
186
+ return agentNames.map((agent) => {
187
+ const cells = results.filter((r) => r.agent === agent);
188
+ const passes = cells.filter((r) => r.pass).length;
189
+ const costs = cells.map((r) => r.costUsd).filter((c) => typeof c === "number");
190
+ return {
191
+ agent,
192
+ passes,
193
+ total: cells.length,
194
+ medianSec: median(cells.map((r) => r.sec)),
195
+ totalCost: costs.length ? costs.reduce((a, b) => a + b, 0) : undefined,
196
+ };
197
+ });
198
+ }
199
+ export function verdictOf(c) {
200
+ if (c.error || c.agentExit !== 0)
201
+ return "err";
202
+ return c.pass ? "pass" : "fail";
203
+ }
204
+ export function renderBoard(suite, results, agentNames) {
205
+ const taskIds = suite.tasks.map((t) => t.id);
206
+ const find = (a, t) => results.find((r) => r.agent === a && r.task === t);
207
+ const colW = agentNames.map((a) => Math.max(a.length, ...taskIds.map((t) => {
208
+ const c = find(a, t);
209
+ return c ? `${verdictOf(c)} ${c.sec}s`.length : 1;
210
+ })) + 2);
211
+ const taskW = Math.max(...taskIds.map((t) => t.length), "median".length) + 2;
212
+ const lines = [];
213
+ lines.push(`agent-trials · ${taskIds.length} task${taskIds.length === 1 ? "" : "s"} · ${agentNames.length} agent${agentNames.length === 1 ? "" : "s"}`);
214
+ lines.push("");
215
+ lines.push("".padEnd(taskW) + agentNames.map((a, i) => a.padStart(colW[i])).join(""));
216
+ for (const tid of taskIds) {
217
+ const row = agentNames
218
+ .map((a, i) => {
219
+ const c = find(a, tid);
220
+ const label = c ? `${verdictOf(c)} ${c.sec}s` : "-";
221
+ return label.padStart(colW[i]);
222
+ })
223
+ .join("");
224
+ lines.push(tid.padEnd(taskW) + row);
225
+ }
226
+ lines.push("");
227
+ const sums = summarize(results, agentNames);
228
+ lines.push("score".padEnd(taskW) +
229
+ sums.map((s, i) => `${s.passes}/${s.total}`.padStart(colW[i])).join(""));
230
+ lines.push("median".padEnd(taskW) +
231
+ sums.map((s, i) => `${s.medianSec}s`.padStart(colW[i])).join(""));
232
+ if (sums.some((s) => s.totalCost !== undefined)) {
233
+ lines.push("cost".padEnd(taskW) +
234
+ sums
235
+ .map((s) => (s.totalCost === undefined ? "-" : `$${s.totalCost.toFixed(2)}`))
236
+ .map((x, i) => x.padStart(colW[i]))
237
+ .join(""));
238
+ }
239
+ const best = [...sums].sort((a, b) => b.passes - a.passes || a.medianSec - b.medianSec)[0];
240
+ if (best && sums.length > 1) {
241
+ lines.push("");
242
+ lines.push(`winner: ${best.agent} (${best.passes}/${best.total}, median ${best.medianSec}s)`);
243
+ }
244
+ return lines.join("\n");
245
+ }
246
+ export function renderMarkdown(suite, results, agentNames) {
247
+ const sums = summarize(results, agentNames);
248
+ const find = (a, t) => results.find((r) => r.agent === a && r.task === t);
249
+ const head = `| task | ${agentNames.join(" | ")} |`;
250
+ const sep = `|${"---|".repeat(agentNames.length + 1)}`;
251
+ const rows = suite.tasks.map((t) => {
252
+ const cells = agentNames
253
+ .map((a) => {
254
+ const c = find(a, t.id);
255
+ if (!c)
256
+ return "-";
257
+ const verdict = verdictOf(c);
258
+ const cost = c.costUsd !== undefined ? ` · $${c.costUsd.toFixed(2)}` : "";
259
+ return `${verdict} · ${c.sec}s${cost}`;
260
+ })
261
+ .join(" | ");
262
+ return `| ${t.id} | ${cells} |`;
263
+ });
264
+ const totals = `| **score** | ${sums.map((s) => `**${s.passes}/${s.total}**`).join(" | ")} |`;
265
+ const med = `| median | ${sums.map((s) => `${s.medianSec}s`).join(" | ")} |`;
266
+ const when = new Date().toISOString().slice(0, 10);
267
+ return [
268
+ `# agent-trials results`,
269
+ ``,
270
+ `${suite.name ?? "suite"} · run ${when} · ${suite.tasks.length} tasks × ${agentNames.length} agents`,
271
+ ``,
272
+ head,
273
+ sep,
274
+ ...rows,
275
+ totals,
276
+ med,
277
+ ``,
278
+ `Generated by [agent-trials](https://github.com/princejain756/agent-trials)`,
279
+ ].join("\n");
280
+ }
281
+ export function writeMarkdown(file, content) {
282
+ writeFileSync(file, content + "\n");
283
+ }
284
+ export function agentInstalled(argv0) {
285
+ const which = process.platform === "win32" ? "where" : "which";
286
+ const res = spawnSync(which, [argv0], { stdio: "ignore" });
287
+ return res.status === 0;
288
+ }
289
+ export function suiteFileExists(file) {
290
+ return existsSync(file);
291
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "agent-trials",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "Interview your coding agent before you ship with it. Run the same tasks through every agent CLI you have installed and get a scoreboard.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-trials": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepare": "npm run build",
15
+ "test": "node test/run.mjs"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "keywords": [
21
+ "ai-agents",
22
+ "evals",
23
+ "benchmark",
24
+ "claude-code",
25
+ "codex",
26
+ "gemini-cli",
27
+ "coding-agents",
28
+ "llm"
29
+ ],
30
+ "author": "Prince Jain",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/princejain756/agent-trials.git"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^26.6.5",
38
+ "typescript": "^7.0.2"
39
+ }
40
+ }