@plune-ai/cli 0.34.2 → 0.34.4

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
@@ -70,8 +70,10 @@ evals:
70
70
  value: "Paris"
71
71
  ```
72
72
 
73
- Datasets are JSONL, one row per line, shaped `{ "vars": { ... }, "expected"?: "..." }`. The
74
- provider API key is read from the environment based on `provider.type`:
73
+ Datasets are JSONL, one row per line, shaped `{ "vars": { ... }, "expected"?: "..." }`. A line that
74
+ is not valid JSON or not a row (no `vars`, or `vars` that is not an object) stops the run before
75
+ any model call with `<file>:<line>` and what is wrong, exit code `2`, like any other configuration
76
+ error. The provider API key is read from the environment based on `provider.type`:
75
77
 
76
78
  | Provider | `provider.type` | Environment variable |
77
79
  | ---------- | --------------- | -------------------- |
package/dist/cli.cjs CHANGED
@@ -2096,7 +2096,31 @@ function loadDataset(ref, baseDir) {
2096
2096
  return ref.examples;
2097
2097
  }
2098
2098
  const content = fs4.readFileSync(path4.resolve(baseDir, ref), "utf8");
2099
- return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
2099
+ const rows = [];
2100
+ content.split(/\r?\n/).forEach((line, i) => {
2101
+ if (line.trim().length === 0) return;
2102
+ let row;
2103
+ try {
2104
+ row = JSON.parse(line.trim());
2105
+ } catch (err) {
2106
+ throw new RunConfigError(`${ref}:${i + 1}: not valid JSON (${err.message})`);
2107
+ }
2108
+ const vars = isObject(row) ? row["vars"] : void 0;
2109
+ if (!isObject(vars)) {
2110
+ const why = isObject(row) && "vars" in row ? `vars: Invalid input: expected record, received ${typeName(vars)}` : 'this row has no "vars"';
2111
+ throw new RunConfigError(
2112
+ `${ref}:${i + 1}: a dataset row must be { "vars": { ... } }; ${why}`
2113
+ );
2114
+ }
2115
+ rows.push(row);
2116
+ });
2117
+ return rows;
2118
+ }
2119
+ function isObject(value2) {
2120
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
2121
+ }
2122
+ function typeName(value2) {
2123
+ return value2 === null ? "null" : Array.isArray(value2) ? "array" : typeof value2;
2100
2124
  }
2101
2125
  var fs4, path4;
2102
2126
  var init_dataset = __esm({
@@ -2105,6 +2129,7 @@ var init_dataset = __esm({
2105
2129
  init_cjs_shims();
2106
2130
  fs4 = __toESM(require("fs"), 1);
2107
2131
  path4 = __toESM(require("path"), 1);
2132
+ init_run();
2108
2133
  }
2109
2134
  });
2110
2135
 
@@ -5662,21 +5687,21 @@ var init_playwright_json = __esm({
5662
5687
  });
5663
5688
 
5664
5689
  // src/importers/k6.ts
5665
- function isObject(value2) {
5690
+ function isObject2(value2) {
5666
5691
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
5667
5692
  }
5668
5693
  function isK6Summary(parsed) {
5669
- if (!isObject(parsed) || !isObject(parsed["metrics"])) return false;
5670
- if (isObject(parsed["root_group"])) return true;
5671
- return Object.values(parsed["metrics"]).some((m) => isObject(m) && (typeof m["type"] === "string" || isObject(m["thresholds"])));
5694
+ if (!isObject2(parsed) || !isObject2(parsed["metrics"])) return false;
5695
+ if (isObject2(parsed["root_group"])) return true;
5696
+ return Object.values(parsed["metrics"]).some((m) => isObject2(m) && (typeof m["type"] === "string" || isObject2(m["thresholds"])));
5672
5697
  }
5673
5698
  function passedOf(verdict) {
5674
- if (isObject(verdict) && typeof verdict["ok"] === "boolean") return verdict["ok"];
5699
+ if (isObject2(verdict) && typeof verdict["ok"] === "boolean") return verdict["ok"];
5675
5700
  if (typeof verdict === "boolean") return !verdict;
5676
5701
  return void 0;
5677
5702
  }
5678
5703
  function measuredOf(metric) {
5679
- const values = isObject(metric.values) ? metric.values : metric;
5704
+ const values = isObject2(metric.values) ? metric.values : metric;
5680
5705
  const unit = metric.contains === "time" ? " ms" : "";
5681
5706
  return Object.entries(values).filter(([name, v]) => name !== "thresholds" && typeof v === "number").map(([name, v]) => `${name}=${Math.round(v * 100) / 100}${unit}`).join(", ");
5682
5707
  }
@@ -5687,13 +5712,13 @@ function readK6(source, file) {
5687
5712
  } catch (err) {
5688
5713
  throw new JsonReportError(file, `not JSON \u2014 ${printable(err instanceof Error ? err.message : String(err))}`);
5689
5714
  }
5690
- if (!isObject(parsed) || !isObject(parsed["metrics"])) {
5715
+ if (!isObject2(parsed) || !isObject2(parsed["metrics"])) {
5691
5716
  throw new JsonReportError(file, 'no "metrics" object \u2014 is this a k6 summary (k6 run --summary-export, or the data handleSummary receives)?');
5692
5717
  }
5693
5718
  const specRef = path13.basename(file);
5694
5719
  const out = [];
5695
5720
  for (const [name, raw] of Object.entries(parsed["metrics"])) {
5696
- if (!isObject(raw) || !isObject(raw["thresholds"])) continue;
5721
+ if (!isObject2(raw) || !isObject2(raw["thresholds"])) continue;
5697
5722
  const metric = raw;
5698
5723
  const measured = measuredOf(metric);
5699
5724
  for (const [expression, verdict] of Object.entries(metric.thresholds)) {
@@ -5734,11 +5759,11 @@ var init_k6 = __esm({
5734
5759
  });
5735
5760
 
5736
5761
  // src/importers/lighthouse.ts
5737
- function isObject2(value2) {
5762
+ function isObject3(value2) {
5738
5763
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
5739
5764
  }
5740
5765
  function isLighthouseReport(parsed) {
5741
- return isObject2(parsed) && typeof parsed["lighthouseVersion"] === "string" && isObject2(parsed["categories"]);
5766
+ return isObject3(parsed) && typeof parsed["lighthouseVersion"] === "string" && isObject3(parsed["categories"]);
5742
5767
  }
5743
5768
  function idOf(key, category) {
5744
5769
  return typeof category.id === "string" && category.id !== "" ? category.id : key;
@@ -5747,7 +5772,7 @@ function executionOf2(report) {
5747
5772
  const started = typeof report["fetchTime"] === "string" ? new Date(report["fetchTime"]) : void 0;
5748
5773
  if (started === void 0 || Number.isNaN(started.getTime())) return void 0;
5749
5774
  const timing = report["timing"];
5750
- const total = isObject2(timing) && typeof timing["total"] === "number" && timing["total"] >= 0 ? Math.round(timing["total"]) : 0;
5775
+ const total = isObject3(timing) && typeof timing["total"] === "number" && timing["total"] >= 0 ? Math.round(timing["total"]) : 0;
5751
5776
  return {
5752
5777
  startedAt: started.toISOString(),
5753
5778
  finishedAt: new Date(started.getTime() + total).toISOString(),
@@ -5757,7 +5782,7 @@ function executionOf2(report) {
5757
5782
  }
5758
5783
  function runtimeErrorOf(report) {
5759
5784
  const err = report["runtimeError"];
5760
- if (!isObject2(err)) return void 0;
5785
+ if (!isObject3(err)) return void 0;
5761
5786
  const code = typeof err["code"] === "string" ? err["code"] : void 0;
5762
5787
  if (code === "NO_ERROR") return void 0;
5763
5788
  const message = typeof err["message"] === "string" ? err["message"] : void 0;
@@ -5778,11 +5803,11 @@ function readLighthouse(source, file, minScores = {}) {
5778
5803
  const categories = report["categories"];
5779
5804
  const url = ["requestedUrl", "finalDisplayedUrl", "finalUrl"].map((k) => report[k]).find((u) => typeof u === "string" && u !== "");
5780
5805
  const settings = report["configSettings"];
5781
- const formFactor = isObject2(settings) && typeof settings["formFactor"] === "string" && settings["formFactor"] !== "" ? settings["formFactor"] : void 0;
5806
+ const formFactor = isObject3(settings) && typeof settings["formFactor"] === "string" && settings["formFactor"] !== "" ? settings["formFactor"] : void 0;
5782
5807
  const runtimeError = runtimeErrorOf(report);
5783
5808
  const execution = executionOf2(report);
5784
5809
  const specRef = path14.basename(file);
5785
- const known = Object.entries(categories).filter(([, raw]) => isObject2(raw)).map(([key, raw]) => idOf(key, raw));
5810
+ const known = Object.entries(categories).filter(([, raw]) => isObject3(raw)).map(([key, raw]) => idOf(key, raw));
5786
5811
  if (known.length > 0) {
5787
5812
  for (const [id2, score] of Object.entries(minScores)) {
5788
5813
  if (!known.includes(id2)) {
@@ -5809,7 +5834,7 @@ function readLighthouse(source, file, minScores = {}) {
5809
5834
  });
5810
5835
  };
5811
5836
  for (const [key, raw] of Object.entries(categories)) {
5812
- if (!isObject2(raw)) continue;
5837
+ if (!isObject3(raw)) continue;
5813
5838
  const category = raw;
5814
5839
  const id2 = idOf(key, category);
5815
5840
  const name = typeof category.title === "string" && category.title !== "" ? category.title : id2;
@@ -6235,15 +6260,16 @@ function evalStatus(e) {
6235
6260
  const statuses = e.rows.map(rowStatus);
6236
6261
  if (statuses.includes("failed")) return "failed";
6237
6262
  if (statuses.includes("errored")) return "errored";
6238
- return "passed";
6263
+ return statuses.includes("passed") ? "passed" : "unjudged";
6239
6264
  }
6240
6265
  function classify2(baseline, current) {
6241
6266
  if (current === "errored") return "errored";
6267
+ if (current === "unjudged") return "unmeasured";
6242
6268
  if (baseline === "absent") return current === "passed" ? "new-pass" : "new-fail";
6243
6269
  if (current === "absent") return "removed";
6244
6270
  if (baseline === "passed") return current === "passed" ? "stable-pass" : "regression";
6245
6271
  if (current === "passed") return "improvement";
6246
- return baseline === "errored" ? "unmeasured" : "pre-existing-fail";
6272
+ return baseline === "failed" ? "pre-existing-fail" : "unmeasured";
6247
6273
  }
6248
6274
  function diffRuns(baseline, current) {
6249
6275
  const baseMap = new Map(baseline.evals.map((e) => [e.id, evalStatus(e)]));
@@ -6402,7 +6428,7 @@ var init_diff3 = __esm({
6402
6428
  "new-pass": "\u{1F7E2} new pass",
6403
6429
  "stable-pass": "\u2705 stable",
6404
6430
  errored: "\u26A0\uFE0F errored",
6405
- unmeasured: "\u26A0\uFE0F not measured (baseline errored)",
6431
+ unmeasured: "\u26A0\uFE0F not measured",
6406
6432
  removed: "\u2796 removed"
6407
6433
  };
6408
6434
  }
@@ -6983,7 +7009,7 @@ var init_contract = __esm({
6983
7009
  define({
6984
7010
  name: "plune_review_decide",
6985
7011
  title: "Decide queue entries",
6986
- description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it.",
7012
+ description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it. An entry skipped as policy_refused was refused by the review policy of the project (for example, the person who proposed it cannot approve it): do not retry it \u2014 it needs another reviewer; its detail says why.",
6987
7013
  input: import_zod5.z.strictObject({
6988
7014
  itemIds: import_zod5.z.array(import_zod5.z.string().min(1)).min(1).max(BATCH_MAX2).refine((a) => new Set(a).size === a.length, { message: "an id is named once" }),
6989
7015
  verdict: import_zod5.z.enum(["approved", "rejected"]),
package/dist/index.cjs CHANGED
@@ -1686,7 +1686,31 @@ function loadDataset(ref, baseDir) {
1686
1686
  return ref.examples;
1687
1687
  }
1688
1688
  const content = fs4.readFileSync(path3.resolve(baseDir, ref), "utf8");
1689
- return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
1689
+ const rows = [];
1690
+ content.split(/\r?\n/).forEach((line, i) => {
1691
+ if (line.trim().length === 0) return;
1692
+ let row;
1693
+ try {
1694
+ row = JSON.parse(line.trim());
1695
+ } catch (err) {
1696
+ throw new RunConfigError(`${ref}:${i + 1}: not valid JSON (${err.message})`);
1697
+ }
1698
+ const vars = isObject(row) ? row["vars"] : void 0;
1699
+ if (!isObject(vars)) {
1700
+ const why = isObject(row) && "vars" in row ? `vars: Invalid input: expected record, received ${typeName(vars)}` : 'this row has no "vars"';
1701
+ throw new RunConfigError(
1702
+ `${ref}:${i + 1}: a dataset row must be { "vars": { ... } }; ${why}`
1703
+ );
1704
+ }
1705
+ rows.push(row);
1706
+ });
1707
+ return rows;
1708
+ }
1709
+ function isObject(value) {
1710
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1711
+ }
1712
+ function typeName(value) {
1713
+ return value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
1690
1714
  }
1691
1715
 
1692
1716
  // src/cli/commands/run.ts
package/dist/index.js CHANGED
@@ -1626,7 +1626,31 @@ function loadDataset(ref, baseDir) {
1626
1626
  return ref.examples;
1627
1627
  }
1628
1628
  const content = fs4.readFileSync(path3.resolve(baseDir, ref), "utf8");
1629
- return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
1629
+ const rows = [];
1630
+ content.split(/\r?\n/).forEach((line, i) => {
1631
+ if (line.trim().length === 0) return;
1632
+ let row;
1633
+ try {
1634
+ row = JSON.parse(line.trim());
1635
+ } catch (err) {
1636
+ throw new RunConfigError(`${ref}:${i + 1}: not valid JSON (${err.message})`);
1637
+ }
1638
+ const vars = isObject(row) ? row["vars"] : void 0;
1639
+ if (!isObject(vars)) {
1640
+ const why = isObject(row) && "vars" in row ? `vars: Invalid input: expected record, received ${typeName(vars)}` : 'this row has no "vars"';
1641
+ throw new RunConfigError(
1642
+ `${ref}:${i + 1}: a dataset row must be { "vars": { ... } }; ${why}`
1643
+ );
1644
+ }
1645
+ rows.push(row);
1646
+ });
1647
+ return rows;
1648
+ }
1649
+ function isObject(value) {
1650
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1651
+ }
1652
+ function typeName(value) {
1653
+ return value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
1630
1654
  }
1631
1655
 
1632
1656
  // src/cli/commands/run.ts
@@ -298,7 +298,7 @@ var PLUNE_TOOLS = [
298
298
  define({
299
299
  name: "plune_review_decide",
300
300
  title: "Decide queue entries",
301
- description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it.",
301
+ description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it. An entry skipped as policy_refused was refused by the review policy of the project (for example, the person who proposed it cannot approve it): do not retry it \u2014 it needs another reviewer; its detail says why.",
302
302
  input: import_zod2.z.strictObject({
303
303
  itemIds: import_zod2.z.array(import_zod2.z.string().min(1)).min(1).max(BATCH_MAX).refine((a) => new Set(a).size === a.length, { message: "an id is named once" }),
304
304
  verdict: import_zod2.z.enum(["approved", "rejected"]),
package/dist/mcp-tools.js CHANGED
@@ -262,7 +262,7 @@ var PLUNE_TOOLS = [
262
262
  define({
263
263
  name: "plune_review_decide",
264
264
  title: "Decide queue entries",
265
- description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it.",
265
+ description: "Approve or reject up to 100 review queue entries in one decision. The first call changes nothing and returns a preview with a confirm value; repeat the same call with confirm to apply it. An entry skipped as policy_refused was refused by the review policy of the project (for example, the person who proposed it cannot approve it): do not retry it \u2014 it needs another reviewer; its detail says why.",
266
266
  input: z2.strictObject({
267
267
  itemIds: z2.array(z2.string().min(1)).min(1).max(BATCH_MAX).refine((a) => new Set(a).size === a.length, { message: "an id is named once" }),
268
268
  verdict: z2.enum(["approved", "rejected"]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.34.2",
3
+ "version": "0.34.4",
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",