firegoat 0.7.0 → 0.8.0
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/README.md +4 -4
- package/dist/cli.js +36 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Local-first AX evals: how well coding agents use your SDK, CLI, API and docs.
|
|
4
4
|
|
|
5
|
-
Status: early (v0.
|
|
5
|
+
Status: early (v0.8). See `plan.md`.
|
|
6
6
|
|
|
7
7
|
## Use
|
|
8
8
|
|
|
@@ -61,11 +61,11 @@ A probe checks the sandbox before every run; if it does not hold, the run is not
|
|
|
61
61
|
|
|
62
62
|
Off macOS there is no sandbox yet: run inside a throwaway container and pass `--host`.
|
|
63
63
|
|
|
64
|
-
`eval` saves each run's transcript to `.firegoat/runs/<runId>/<test>/<n>/session.jsonl`, runs the test's `checks` in the agent's folder, and writes `run.json` per run plus `summary.json` (prompt,
|
|
64
|
+
`eval` saves each run's transcript to `.firegoat/runs/<runId>/<test>/<n>/session.jsonl`, runs the test's `checks` in the agent's folder, and writes `run.json` per run plus `summary.json` (prompt, end_goal, check results, paths). Nothing is uploaded.
|
|
65
65
|
|
|
66
|
-
### Grading: your agent judges the goal
|
|
66
|
+
### Grading: your agent judges the end goal
|
|
67
67
|
|
|
68
|
-
firegoat runs only the script checks; they set the exit code.
|
|
68
|
+
firegoat runs only the script checks; they set the exit code. Each test's `end_goal` is handed over, not graded: whoever drives firegoat (usually your coding agent) judges it with its own process from `npx firegoat eval --json` / `run.json`, the transcript and the workdir. firegoat records no verdict beyond the checks.
|
|
69
69
|
|
|
70
70
|
Every command has `--help` written for an agent; start with `npx firegoat --help`. `firegoat.yaml` is looked up in the current directory, then parents up to the git root.
|
|
71
71
|
|
package/dist/cli.js
CHANGED
|
@@ -6947,7 +6947,7 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
6947
6947
|
// package.json
|
|
6948
6948
|
var package_default = {
|
|
6949
6949
|
name: "firegoat",
|
|
6950
|
-
version: "0.
|
|
6950
|
+
version: "0.8.0",
|
|
6951
6951
|
repository: {
|
|
6952
6952
|
type: "git",
|
|
6953
6953
|
url: "git+https://github.com/team2027/firegoat-cli.git"
|
|
@@ -7221,10 +7221,10 @@ sandboxed folder; firegoat records the transcript and runs your script checks.
|
|
|
7221
7221
|
|
|
7222
7222
|
Typical flow (for an agent driving firegoat on behalf of a human):
|
|
7223
7223
|
1. npx firegoat init --yes write firegoat.yaml (read: npx firegoat init --help)
|
|
7224
|
-
2. edit firegoat.yaml real user problems as prompts,
|
|
7224
|
+
2. edit firegoat.yaml real user problems as prompts, an end_goal, checks
|
|
7225
7225
|
3. ask the human to run once: npx firegoat claude login (opens a browser)
|
|
7226
7226
|
4. npx firegoat eval --json run the tests; JSON summary on stdout
|
|
7227
|
-
5. judge each run's
|
|
7227
|
+
5. judge each run's end_goal yourself, from its run.json, transcript and
|
|
7228
7228
|
workdir: firegoat only runs the script checks
|
|
7229
7229
|
|
|
7230
7230
|
Commands:
|
|
@@ -7267,15 +7267,15 @@ firegoat.yaml reference:
|
|
|
7267
7267
|
tests:
|
|
7268
7268
|
- name: quickstart letters, digits, '.', '_', '-'; unique
|
|
7269
7269
|
prompt: ... what a user would ask the agent; one concrete outcome
|
|
7270
|
-
|
|
7271
|
-
|
|
7270
|
+
end_goal: ... what is true when the problem is solved; not graded by
|
|
7271
|
+
firegoat, handed to you (goal: is a deprecated alias)
|
|
7272
7272
|
checks: optional shell commands, run in the workdir after the
|
|
7273
7273
|
- run: ... agent; exit 0 = pass; they set eval's exit code
|
|
7274
7274
|
timeout: 600 optional, seconds
|
|
7275
7275
|
|
|
7276
7276
|
The agent's folder has no repo files. Checks get $FIREGOAT_WORKDIR,
|
|
7277
7277
|
$FIREGOAT_SESSION (stream-json transcript), $FIREGOAT_OUT and the vars.
|
|
7278
|
-
|
|
7278
|
+
end_goal is not graded by firegoat: it is handed to the caller to judge.
|
|
7279
7279
|
|
|
7280
7280
|
Example:
|
|
7281
7281
|
setup: npm pack --pack-destination "$FIREGOAT_OUT"
|
|
@@ -7285,12 +7285,13 @@ Example:
|
|
|
7285
7285
|
prompt: >-
|
|
7286
7286
|
Using the acme SDK (already installed from the build under test), send a test
|
|
7287
7287
|
event and print the event id. Credentials are in ACME_API_KEY. Keep it minimal.
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
- does not install acme from the registry
|
|
7288
|
+
end_goal: >-
|
|
7289
|
+
A script sends one event with the SDK's client (not raw HTTP) and prints its
|
|
7290
|
+
id, without installing acme from the registry.
|
|
7292
7291
|
checks:
|
|
7293
7292
|
- run: grep -qE 'evt_[A-Za-z0-9]+' "$FIREGOAT_SESSION"
|
|
7293
|
+
- run: |
|
|
7294
|
+
! grep -Eq 'npm (install|i) [^"]*acme' "$FIREGOAT_SESSION"
|
|
7294
7295
|
|
|
7295
7296
|
Next: npx firegoat claude login (a human, once), then npx firegoat eval.
|
|
7296
7297
|
|
|
@@ -7316,13 +7317,13 @@ command to run.
|
|
|
7316
7317
|
Output: .firegoat/runs/<runId>/
|
|
7317
7318
|
summary.json every run, plus totals and a grading hint
|
|
7318
7319
|
<test>/<n>/run.json one run: test, n, status and reason (from the checks),
|
|
7319
|
-
prompt,
|
|
7320
|
+
prompt, end_goal, checks [{run, code}], and paths:
|
|
7320
7321
|
dir, workdir, session, stderr, checksLog
|
|
7321
7322
|
<test>/<n>/session.jsonl the agent's transcript, stream-json (one event per line)
|
|
7322
7323
|
setup.txt, <test>/<n>/workdir_setup.txt, checks.txt logs
|
|
7323
7324
|
|
|
7324
|
-
Grading: firegoat runs only the script checks; they set the exit code.
|
|
7325
|
-
|
|
7325
|
+
Grading: firegoat runs only the script checks; they set the exit code. end_goal is
|
|
7326
|
+
yours to judge, with your own process. session.jsonl is stream-json: the
|
|
7326
7327
|
last line has type "result" with the agent's final message; tool calls are
|
|
7327
7328
|
"tool_use" items in "assistant" messages; tool output is in "user" messages.
|
|
7328
7329
|
|
|
@@ -7464,7 +7465,7 @@ function posInt(v, path, fallback) {
|
|
|
7464
7465
|
fail(path, "must be a positive integer");
|
|
7465
7466
|
return v;
|
|
7466
7467
|
}
|
|
7467
|
-
function parseConfig(text) {
|
|
7468
|
+
function parseConfig(text, warn = console.error) {
|
|
7468
7469
|
let raw;
|
|
7469
7470
|
try {
|
|
7470
7471
|
raw = $parse(text);
|
|
@@ -7504,21 +7505,25 @@ function parseConfig(text) {
|
|
|
7504
7505
|
if (!Array.isArray(checksRaw))
|
|
7505
7506
|
fail(`${p}.checks`, "must be a list");
|
|
7506
7507
|
const checks = [];
|
|
7507
|
-
const rubric = rubricOf(t.rubric, `${p}.rubric`);
|
|
7508
7508
|
checksRaw.forEach((c, j) => {
|
|
7509
7509
|
const cp = `${p}.checks[${j}]`;
|
|
7510
7510
|
if (!isRecord(c))
|
|
7511
7511
|
fail(cp, "must be a mapping");
|
|
7512
7512
|
if ("judge" in c)
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
checks.push({ run: str(c.run, `${cp}.run`) });
|
|
7513
|
+
fail(`${cp}.judge`, "plain-language checks were removed; put the criteria in end_goal (your agent judges it) or write a run: check");
|
|
7514
|
+
checks.push({ run: str(c.run, `${cp}.run`) });
|
|
7516
7515
|
});
|
|
7516
|
+
if (t.rubric !== undefined)
|
|
7517
|
+
fail(`${p}.rubric`, "was removed in firegoat 0.8.0; put the criteria in end_goal");
|
|
7518
|
+
if (t.goal !== undefined && t.end_goal !== undefined)
|
|
7519
|
+
fail(`${p}.goal`, "use end_goal only (goal is its deprecated name)");
|
|
7520
|
+
if (t.goal !== undefined)
|
|
7521
|
+
warn(`${CONFIG_FILE}: ${p}.goal is deprecated; rename it to end_goal`);
|
|
7522
|
+
const endGoalKey = t.goal !== undefined ? "goal" : "end_goal";
|
|
7517
7523
|
return {
|
|
7518
7524
|
name,
|
|
7519
7525
|
prompt: str(t.prompt, `${p}.prompt`),
|
|
7520
|
-
|
|
7521
|
-
rubric,
|
|
7526
|
+
end_goal: str(t[endGoalKey], `${p}.${endGoalKey}`),
|
|
7522
7527
|
checks,
|
|
7523
7528
|
timeout: posInt(t.timeout, `${p}.timeout`, DEFAULT_TIMEOUT)
|
|
7524
7529
|
};
|
|
@@ -7533,15 +7538,6 @@ function parseConfig(text) {
|
|
|
7533
7538
|
};
|
|
7534
7539
|
}
|
|
7535
7540
|
var NAME = /^[A-Za-z0-9._-]+$/;
|
|
7536
|
-
function rubricOf(raw, path) {
|
|
7537
|
-
if (raw === undefined)
|
|
7538
|
-
return [];
|
|
7539
|
-
if (typeof raw === "string")
|
|
7540
|
-
return [str(raw, path)];
|
|
7541
|
-
if (!Array.isArray(raw))
|
|
7542
|
-
fail(path, "must be a string or a list of strings");
|
|
7543
|
-
return raw.map((r, i) => str(r, `${path}[${i}]`));
|
|
7544
|
-
}
|
|
7545
7541
|
function commands(raw, path) {
|
|
7546
7542
|
if (raw === undefined)
|
|
7547
7543
|
return [];
|
|
@@ -7579,11 +7575,11 @@ function render(text, vars) {
|
|
|
7579
7575
|
return v;
|
|
7580
7576
|
});
|
|
7581
7577
|
}
|
|
7582
|
-
function loadConfig(dir) {
|
|
7578
|
+
function loadConfig(dir, warn) {
|
|
7583
7579
|
const file = `${dir}/${CONFIG_FILE}`;
|
|
7584
7580
|
if (!existsSync3(file))
|
|
7585
7581
|
throw new ConfigError(`${CONFIG_FILE} not found in ${dir}. Run: npx firegoat init`);
|
|
7586
|
-
return parseConfig(readFileSync2(file, "utf8"));
|
|
7582
|
+
return parseConfig(readFileSync2(file, "utf8"), warn);
|
|
7587
7583
|
}
|
|
7588
7584
|
function findConfigDir(cwd) {
|
|
7589
7585
|
let dir = cwd;
|
|
@@ -7945,7 +7941,7 @@ function resolveBin(bin) {
|
|
|
7945
7941
|
|
|
7946
7942
|
// src/eval.ts
|
|
7947
7943
|
var CHECK_TIMEOUT_MS = 120000;
|
|
7948
|
-
var GRADING_HINT = "firegoat ran only the script checks.
|
|
7944
|
+
var GRADING_HINT = "firegoat ran only the script checks. Each run's end_goal is for the caller to judge, from its session (stream-json transcript) and workdir.";
|
|
7949
7945
|
function blankRun(test, n, dir, prompt) {
|
|
7950
7946
|
return {
|
|
7951
7947
|
test: test.name,
|
|
@@ -7953,8 +7949,7 @@ function blankRun(test, n, dir, prompt) {
|
|
|
7953
7949
|
status: "error",
|
|
7954
7950
|
reason: "",
|
|
7955
7951
|
prompt,
|
|
7956
|
-
|
|
7957
|
-
rubric: test.rubric,
|
|
7952
|
+
end_goal: test.end_goal,
|
|
7958
7953
|
checks: [],
|
|
7959
7954
|
dir,
|
|
7960
7955
|
workdir: null,
|
|
@@ -8106,7 +8101,7 @@ async function runOne(opts) {
|
|
|
8106
8101
|
if (!outcome.ok)
|
|
8107
8102
|
return done("error", `not graded: ${outcome.reason}`);
|
|
8108
8103
|
if (test.checks.length === 0)
|
|
8109
|
-
return done("pass", "no script checks;
|
|
8104
|
+
return done("pass", "no script checks; end_goal is for the caller to judge");
|
|
8110
8105
|
const log = createWriteStream3(summary.checksLog);
|
|
8111
8106
|
const env = {
|
|
8112
8107
|
...process.env,
|
|
@@ -8156,7 +8151,7 @@ async function evalCommand(args, cwd) {
|
|
|
8156
8151
|
const say = opts.json ? console.error : console.log;
|
|
8157
8152
|
const warn = console.error;
|
|
8158
8153
|
const root = findConfigDir(cwd);
|
|
8159
|
-
const config = loadConfig(root);
|
|
8154
|
+
const config = loadConfig(root, warn);
|
|
8160
8155
|
let tests = config.tests;
|
|
8161
8156
|
if (opts.names.length > 0) {
|
|
8162
8157
|
const known = new Set(tests.map((t) => t.name));
|
|
@@ -8292,7 +8287,7 @@ async function evalCommand(args, cwd) {
|
|
|
8292
8287
|
say(`
|
|
8293
8288
|
${pass} passed, ${fail} failed, ${error} not graded (${results.length} runs)`);
|
|
8294
8289
|
say(`runs: ${runDir}`);
|
|
8295
|
-
say(`
|
|
8290
|
+
say(`end_goal is not graded by firegoat; per run: ${join6(runDir, "<test>", "<n>", "run.json")}`);
|
|
8296
8291
|
if (opts.json)
|
|
8297
8292
|
console.log(JSON.stringify(doc, null, 2));
|
|
8298
8293
|
return error > 0 ? EXIT.notGraded : fail > 0 ? EXIT.fail : EXIT.pass;
|
|
@@ -8422,7 +8417,7 @@ function templates(p) {
|
|
|
8422
8417
|
out.push({
|
|
8423
8418
|
name: "cli-first-run",
|
|
8424
8419
|
prompt: `I just heard about the \`${p.bin}\` command-line tool from ${p.name} and want to see it actually do something useful. Use it for one basic real task and save what it prints to cli-output.txt. Keep it minimal. Use only ${docs} as a guide.`,
|
|
8425
|
-
|
|
8420
|
+
end_goal: `cli-output.txt exists and holds real output from running \`${p.bin}\`, not an install log or an error.`,
|
|
8426
8421
|
checks: [
|
|
8427
8422
|
"test -s cli-output.txt",
|
|
8428
8423
|
"! grep -qiE 'command not found|not recognized|ENOENT' cli-output.txt"
|
|
@@ -8433,7 +8428,7 @@ function templates(p) {
|
|
|
8433
8428
|
out.push({
|
|
8434
8429
|
name: "quickstart",
|
|
8435
8430
|
prompt: `I want to use ${p.name} in a new ${LANG[p.ecosystem]} project. Get the smallest working example going: a program that uses ${p.name} to do one real thing, then run it and save what it prints to output.txt. Keep it minimal. Use only ${docs} as a guide.`,
|
|
8436
|
-
|
|
8431
|
+
end_goal: `A program in the workdir uses ${p.name}, runs without errors, and its output is saved in output.txt.`,
|
|
8437
8432
|
checks: [
|
|
8438
8433
|
"test -s output.txt",
|
|
8439
8434
|
`grep -rqsF ${GREP_EXCLUDES} -- ${sq(importName(p))} .`
|
|
@@ -8444,7 +8439,7 @@ function templates(p) {
|
|
|
8444
8439
|
out.push({
|
|
8445
8440
|
name: "quickstart",
|
|
8446
8441
|
prompt: `I want to try ${p.name}. Follow its quickstart in a new project, get the smallest example working, then run it and save what it prints to output.txt. Keep it minimal. Use only ${docs} as a guide.`,
|
|
8447
|
-
|
|
8442
|
+
end_goal: `The ${p.name} quickstart example runs without errors and its output is saved in output.txt.`,
|
|
8448
8443
|
checks: ["test -s output.txt"]
|
|
8449
8444
|
});
|
|
8450
8445
|
}
|
|
@@ -8493,7 +8488,7 @@ function renderYaml(p, tests) {
|
|
|
8493
8488
|
"tests:"
|
|
8494
8489
|
];
|
|
8495
8490
|
for (const t of tests) {
|
|
8496
|
-
lines.push(` - name: ${t.name} # letters, digits, '.', '_' and '-'`, " # What a user would ask the agent. Edit this.", " prompt: >-", block(t.prompt, " "), " # One sentence that is true when the user's problem is solved.", "
|
|
8491
|
+
lines.push(` - name: ${t.name} # letters, digits, '.', '_' and '-'`, " # What a user would ask the agent. Edit this.", " prompt: >-", block(t.prompt, " "), " # One sentence that is true when the user's problem is solved. Not graded by", " # firegoat: it goes to your agent (eval --json, run.json) to judge.", " end_goal: >-", block(t.end_goal, " "), " # Shell commands run in the agent's workdir afterwards; exit 0 = pass.", " checks:", ...t.checks.map((c) => ` - run: ${JSON.stringify(c)}`), " # Seconds before the agent run is stopped.", " timeout: 600");
|
|
8497
8492
|
}
|
|
8498
8493
|
return `${lines.join(`
|
|
8499
8494
|
`)}
|