@plune-ai/cli 0.34.0 → 0.34.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/README.md CHANGED
@@ -117,7 +117,7 @@ the last resort, used only when `PLUNE_TOKEN` is set nowhere else. Nothing write
117
117
  | ------- | ------- |
118
118
  | `plune run` | Run the suite. Flags: `--dry-run` (price the run from the model's rates — no provider key, no network), `--only <id\|tag>` (repeatable), `--bail`, `--no-cache`, `--cache-strict`, `--cache-max-age <duration>`, `--concurrency <n>`, `--format console\|json\|markdown`, `-o, --output <file>`. |
119
119
  | `plune report` | Re-render the most recent run. Flags: `--format`, `-o`. |
120
- | `plune diff <baseline> <current>` | Compare two `plune run --format json` outputs and report pass→fail regressions. Flags: `--fail-on-regression`, `--format`, `-o`. |
120
+ | `plune diff <baseline> <current>` | Compare two `plune run --format json` outputs and report pass→fail regressions. Flags: `--fail-on-regression` (exit `1` on a regression, `3` when none but an eval was not measured), `--format`, `-o`. |
121
121
  | `plune init` | Scaffold `plune.yaml`, a sample dataset, and `.env.example`. Flags: `--yes` (non-interactive), `--force`. |
122
122
  | `plune login` | Save a [Plune platform](https://plune.ai/platform) API token so `sync` and `ingest` can reach it. The token is **checked against the API before it is saved**, so a wrong one fails here rather than two commands later. Get one at `https://beta.plune.ai` → Account → API tokens. Flags: `--token <token>` (omit to paste it or pipe it via stdin), `--skip-verify` (save without checking, for offline setup). In CI, skip this step: every platform command reads `PLUNE_TOKEN` from the environment first, and the saved login only when it is not set. |
123
123
  | `plune logout` | Remove the saved token. |
package/dist/cli.cjs CHANGED
@@ -2010,7 +2010,8 @@ async function runOrchestration(config, options, deps) {
2010
2010
  limit,
2011
2011
  (row) => runRow({
2012
2012
  template,
2013
- assertions: ev.assertions,
2013
+ // `defaults.assertions` apply to every eval, ahead of its own (#2415).
2014
+ assertions: [...config.defaults?.assertions ?? [], ...ev.assertions],
2014
2015
  row,
2015
2016
  providerConfig,
2016
2017
  provider,
@@ -2887,13 +2888,13 @@ var init_fallback = __esm({
2887
2888
  function failureOf(attempt, home = (0, import_node_os2.homedir)()) {
2888
2889
  const error = chosen(attempt);
2889
2890
  const line = error === void 0 ? void 0 : firstLine(machineless(error.text, attempt.repoRoot, home));
2890
- const headline = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
2891
+ const headline2 = line !== void 0 && jsonBytes(line) <= HEADLINE_LIMIT ? line : void 0;
2891
2892
  const steps = chainOf(attempt.steps);
2892
2893
  const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
2893
2894
  const artifacts = attempt.attachments.filter((a) => a.path !== void 0 && !a.name.startsWith("_") && a.contentType !== METADATA_TYPE).map(({ name, contentType }) => ({ name: fileNameOf(name), ...contentType ? { contentType } : {} })).filter((a) => a.name !== "");
2894
2895
  const ciUrl = webLink(attempt.buildHref);
2895
2896
  const failure = {
2896
- ...headline !== void 0 ? { headline } : {},
2897
+ ...headline2 !== void 0 ? { headline: headline2 } : {},
2897
2898
  ...steps.length > 0 ? { steps } : {},
2898
2899
  ...location !== void 0 ? { location } : {},
2899
2900
  ...artifacts.length > 0 ? { artifacts } : {},
@@ -6241,7 +6242,8 @@ function classify2(baseline, current) {
6241
6242
  if (baseline === "absent") return current === "passed" ? "new-pass" : "new-fail";
6242
6243
  if (current === "absent") return "removed";
6243
6244
  if (baseline === "passed") return current === "passed" ? "stable-pass" : "regression";
6244
- return current === "passed" ? "improvement" : "pre-existing-fail";
6245
+ if (current === "passed") return "improvement";
6246
+ return baseline === "errored" ? "unmeasured" : "pre-existing-fail";
6245
6247
  }
6246
6248
  function diffRuns(baseline, current) {
6247
6249
  const baseMap = new Map(baseline.evals.map((e) => [e.id, evalStatus(e)]));
@@ -6262,6 +6264,7 @@ function diffRuns(baseline, current) {
6262
6264
  stablePasses: count2("stable-pass"),
6263
6265
  errored: count2("errored"),
6264
6266
  removed: count2("removed"),
6267
+ unmeasured: count2("errored") + count2("unmeasured"),
6265
6268
  hasRegression: count2("regression") > 0
6266
6269
  };
6267
6270
  return { evals, summary };
@@ -6332,6 +6335,11 @@ __export(diff_exports2, {
6332
6335
  renderDiff: () => renderDiff,
6333
6336
  renderDiffMarkdown: () => renderDiffMarkdown
6334
6337
  });
6338
+ function headline(d) {
6339
+ if (d.summary.hasRegression) return { mark: "\u274C", text: `${d.summary.regressions} regression(s)` };
6340
+ if (d.summary.unmeasured > 0) return { mark: "\u26A0\uFE0F", text: `${d.summary.unmeasured} eval(s) not measured` };
6341
+ return null;
6342
+ }
6335
6343
  function summaryLine(d) {
6336
6344
  const s = d.summary;
6337
6345
  return [
@@ -6341,14 +6349,14 @@ function summaryLine(d) {
6341
6349
  `${s.preExistingFails} pre-existing-fail`,
6342
6350
  `${s.errored} errored`,
6343
6351
  `${s.removed} removed`,
6352
+ `${s.unmeasured} not measured`,
6344
6353
  `${s.stablePasses} stable`
6345
6354
  ].join(" \xB7 ");
6346
6355
  }
6347
6356
  function renderDiffMarkdown(d) {
6348
6357
  const out = [STICKY_MARKER, "## Plune eval diff", ""];
6349
- out.push(
6350
- d.summary.hasRegression ? `### \u274C ${d.summary.regressions} regression(s)` : "### \u2705 No regressions"
6351
- );
6358
+ const head = headline(d);
6359
+ out.push(head ? `### ${head.mark} ${head.text}` : "### \u2705 No regressions");
6352
6360
  out.push("");
6353
6361
  const notable = d.evals.filter((e) => e.status !== "stable-pass");
6354
6362
  if (notable.length > 0) {
@@ -6363,9 +6371,8 @@ function renderDiffMarkdown(d) {
6363
6371
  return out.join("\n");
6364
6372
  }
6365
6373
  function renderDiffConsole(d) {
6366
- const out = [
6367
- d.summary.hasRegression ? `Plune eval diff: ${d.summary.regressions} regression(s)` : "Plune eval diff: no regressions"
6368
- ];
6374
+ const head = headline(d);
6375
+ const out = [`Plune eval diff: ${head ? head.text : "no regressions"}`];
6369
6376
  for (const e of d.evals) {
6370
6377
  if (e.status === "stable-pass") continue;
6371
6378
  out.push(` ${e.id}: ${e.baseline} -> ${e.current} [${e.status}]`);
@@ -6395,6 +6402,7 @@ var init_diff3 = __esm({
6395
6402
  "new-pass": "\u{1F7E2} new pass",
6396
6403
  "stable-pass": "\u2705 stable",
6397
6404
  errored: "\u26A0\uFE0F errored",
6405
+ unmeasured: "\u26A0\uFE0F not measured (baseline errored)",
6398
6406
  removed: "\u2796 removed"
6399
6407
  };
6400
6408
  }
@@ -9014,7 +9022,11 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
9014
9022
  failUnexpected(err, globals.verbose === true);
9015
9023
  }
9016
9024
  });
9017
- program.command("diff").description("Compare two `plune run --format json` outputs and report regressions").argument("<baseline>", "Path to the baseline run JSON (e.g. from main)").argument("<current>", "Path to the current run JSON (e.g. from the PR)").option("--format <fmt>", "console | json | markdown", "console").option("--fail-on-regression", "Exit 1 when a pass\u2192fail regression is detected", false).option("-o, --output <path>", "Write the diff to a file instead of stdout").action(
9025
+ program.command("diff").description("Compare two `plune run --format json` outputs and report regressions").argument("<baseline>", "Path to the baseline run JSON (e.g. from main)").argument("<current>", "Path to the current run JSON (e.g. from the PR)").option("--format <fmt>", "console | json | markdown", "console").option(
9026
+ "--fail-on-regression",
9027
+ "Exit 1 when a pass\u2192fail regression is detected, 3 when none is but an eval was not measured",
9028
+ false
9029
+ ).option("-o, --output <path>", "Write the diff to a file instead of stdout").action(
9018
9030
  async (baseline, current, options, command) => {
9019
9031
  const verbose = command.optsWithGlobals().verbose === true;
9020
9032
  const [{ handleDiff: handleDiff2, DiffInputError: DiffInputError2 }, { renderDiff: renderDiff2 }] = await Promise.all([
@@ -9024,7 +9036,9 @@ ${missed.map((m) => ` - ${m}`).join("\n")}
9024
9036
  try {
9025
9037
  const diff = handleDiff2({ baselinePath: baseline, currentPath: current });
9026
9038
  writeReport(renderDiff2(diff, toFormat(options.format)), options.output);
9027
- process.exitCode = options.failOnRegression && diff.summary.hasRegression ? 1 : 0;
9039
+ process.exitCode = 0;
9040
+ if (options.failOnRegression && diff.summary.hasRegression) process.exitCode = 1;
9041
+ else if (options.failOnRegression && diff.summary.unmeasured > 0) process.exitCode = 3;
9028
9042
  } catch (err) {
9029
9043
  if (err instanceof DiffInputError2) {
9030
9044
  process.stderr.write(err.message + "\n");
package/dist/index.cjs CHANGED
@@ -1647,7 +1647,8 @@ async function runOrchestration(config, options, deps) {
1647
1647
  limit,
1648
1648
  (row) => runRow({
1649
1649
  template,
1650
- assertions: ev.assertions,
1650
+ // `defaults.assertions` apply to every eval, ahead of its own (#2415).
1651
+ assertions: [...config.defaults?.assertions ?? [], ...ev.assertions],
1651
1652
  row,
1652
1653
  providerConfig,
1653
1654
  provider,
package/dist/index.js CHANGED
@@ -1587,7 +1587,8 @@ async function runOrchestration(config, options, deps) {
1587
1587
  limit,
1588
1588
  (row) => runRow({
1589
1589
  template,
1590
- assertions: ev.assertions,
1590
+ // `defaults.assertions` apply to every eval, ahead of its own (#2415).
1591
+ assertions: [...config.defaults?.assertions ?? [], ...ev.assertions],
1591
1592
  row,
1592
1593
  providerConfig,
1593
1594
  provider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.34.0",
3
+ "version": "0.34.1",
4
4
  "description": "AI-powered assertion testing for LLM apps — write assertions, run them against any provider, get a pass/fail report (local, CI, or diff).",
5
5
  "keywords": [
6
6
  "ai",