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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/dist/cli.js +36 -41
  3. 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.7). See `plan.md`.
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, goal, rubric, check results, paths). Nothing is uploaded.
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. The `goal` and an optional `rubric:` list are handed over, not graded: whoever drives firegoat (usually your coding agent) judges them with its own process from `npx firegoat eval --json` / `run.json`, the transcript and the workdir. firegoat records no verdict beyond the checks.
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.7.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, a goal, checks
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 goal + rubric yourself, from its run.json, transcript and
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
- goal: ... one sentence that is true when the problem is solved
7271
- rubric: [...] optional criteria for whoever grades the run (you)
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
- goal and rubric are not run by firegoat: they are handed to the caller to judge.
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
- goal: A script sends one event and prints its id.
7289
- rubric:
7290
- - uses the SDK's client, not raw HTTP
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, goal, rubric, checks [{run, code}], and paths:
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. The goal and
7325
- rubric are yours to judge, with your own process. session.jsonl is stream-json: the
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
- rubric.push(str(c.judge, `${cp}.judge`));
7514
- else
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
- goal: str(t.goal, `${p}.goal`),
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. The goal and rubric are for the caller to judge, from each run's session (stream-json transcript) and workdir.";
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
- goal: test.goal,
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; the goal is for the grader");
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(`goal and rubric are not graded by firegoat; per run: ${join6(runDir, "<test>", "<n>", "run.json")}`);
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
- goal: `cli-output.txt exists and holds real output from running \`${p.bin}\`, not an install log or an error.`,
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
- goal: `A program in the workdir uses ${p.name}, runs without errors, and its output is saved in output.txt.`,
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
- goal: `The ${p.name} quickstart example runs without errors and its output is saved in output.txt.`,
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.", " goal: >-", block(t.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");
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
  `)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firegoat",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/team2027/firegoat-cli.git"