@plune-ai/cli 0.10.0 → 0.11.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 CHANGED
@@ -158,6 +158,14 @@ job closes the run instead of the explicit `finish`.
158
158
  `plune run exec` sets it for you — anything reporting inside it, this command included, joins
159
159
  without closing.
160
160
 
161
+ ### What the run is called
162
+
163
+ A run nobody named is called after the directory, the minute it started and where it ran —
164
+ `plune · 2026-09-15 15:26 · ci` — so a row in the list reads without being opened. The clock is the
165
+ reporting machine's own; `ci` comes from the `CI` variable every hosted runner sets. To call it
166
+ something else, set `PLUNE_RUN_TITLE`; `PLUNE_ENV` and `PLUNE_LABELS` mark where it ran and how, and
167
+ those stay marks rather than becoming part of the name.
168
+
161
169
  ## Optional: keep a history
162
170
 
163
171
  Everything above works with no account, no network, and no token — that does not change. `run`,
package/dist/cli.cjs CHANGED
@@ -2210,7 +2210,7 @@ function createClient(opts) {
2210
2210
  const wait = opts.wait ?? sleep;
2211
2211
  const base = opts.apiUrl.replace(/\/+$/, "");
2212
2212
  const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
2213
- async function send(method, path12, body) {
2213
+ async function send(method, path13, body) {
2214
2214
  let last = {
2215
2215
  kind: "unavailable",
2216
2216
  status: null,
@@ -2219,7 +2219,7 @@ function createClient(opts) {
2219
2219
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
2220
2220
  let res;
2221
2221
  try {
2222
- res = await doFetch(`${base}${path12}`, {
2222
+ res = await doFetch(`${base}${path13}`, {
2223
2223
  method,
2224
2224
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
2225
2225
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -2247,8 +2247,8 @@ function createClient(opts) {
2247
2247
  return { ok: false, ...last };
2248
2248
  }
2249
2249
  return {
2250
- post: (path12, body) => send("POST", path12, body),
2251
- del: (path12) => send("DELETE", path12)
2250
+ post: (path13, body) => send("POST", path13, body),
2251
+ del: (path13) => send("DELETE", path13)
2252
2252
  };
2253
2253
  }
2254
2254
  var RETRY_DELAYS_MS, sleep;
@@ -2313,18 +2313,25 @@ function readEnv(env = process.env) {
2313
2313
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
2314
2314
  };
2315
2315
  }
2316
- var UNSUPPORTED_VARS;
2316
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
2317
+ const two = (n) => String(n).padStart(2, "0");
2318
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
2319
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
2320
+ return `${path6.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
2321
+ }
2322
+ var path6, UNSUPPORTED_VARS;
2317
2323
  var init_env2 = __esm({
2318
2324
  "src/reporter-core/env.ts"() {
2319
2325
  "use strict";
2320
2326
  init_cjs_shims();
2327
+ path6 = __toESM(require("path"), 1);
2321
2328
  UNSUPPORTED_VARS = ["PLUNE_GROUP"];
2322
2329
  }
2323
2330
  });
2324
2331
 
2325
2332
  // src/reporter-core/fallback.ts
2326
2333
  function appendBatch(file, batch) {
2327
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
2334
+ fs6.mkdirSync(path7.dirname(file), { recursive: true });
2328
2335
  const line = {
2329
2336
  ts: (/* @__PURE__ */ new Date()).toISOString(),
2330
2337
  runId: batch.runId,
@@ -2334,14 +2341,14 @@ function appendBatch(file, batch) {
2334
2341
  fs6.appendFileSync(file, `${JSON.stringify(line)}
2335
2342
  `, "utf8");
2336
2343
  }
2337
- var fs6, path6, DEFAULT_FALLBACK_PATH;
2344
+ var fs6, path7, DEFAULT_FALLBACK_PATH;
2338
2345
  var init_fallback = __esm({
2339
2346
  "src/reporter-core/fallback.ts"() {
2340
2347
  "use strict";
2341
2348
  init_cjs_shims();
2342
2349
  fs6 = __toESM(require("fs"), 1);
2343
- path6 = __toESM(require("path"), 1);
2344
- DEFAULT_FALLBACK_PATH = path6.join(".plune", "pending-results.jsonl");
2350
+ path7 = __toESM(require("path"), 1);
2351
+ DEFAULT_FALLBACK_PATH = path7.join(".plune", "pending-results.jsonl");
2345
2352
  }
2346
2353
  });
2347
2354
 
@@ -2468,6 +2475,10 @@ async function startRun(passed, declared) {
2468
2475
  ...externalKey !== null ? { externalKey } : {},
2469
2476
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
2470
2477
  ...configuration !== void 0 ? { configuration } : {},
2478
+ // Named even when nobody named it — the list has to read without opening a row. Only what
2479
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
2480
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
2481
+ title: defaultRunTitle(),
2471
2482
  ...described
2472
2483
  });
2473
2484
  if (start.ok) {
@@ -2689,7 +2700,9 @@ async function handleRunStart(options = {}) {
2689
2700
  const out = await client.post("/v1/runs", {
2690
2701
  schemaVersion: 2,
2691
2702
  kind: options.kind ?? "automated",
2692
- externalKey
2703
+ externalKey,
2704
+ // The same name the reporter would give it; `PLUNE_RUN_TITLE` replaces it, as everywhere.
2705
+ title: env.config.title ?? defaultRunTitle()
2693
2706
  });
2694
2707
  if (!out.ok) throw new RunCommandError(`Could not start the run \u2014 ${out.detail || out.kind}.`);
2695
2708
  const result = {
@@ -3351,7 +3364,7 @@ function isRunResult(v) {
3351
3364
  }
3352
3365
  function handleReport(opts = {}) {
3353
3366
  const cwd = opts.cwd ?? process.cwd();
3354
- const file = path7.join(cwd, ".plune", "last-run.json");
3367
+ const file = path8.join(cwd, ".plune", "last-run.json");
3355
3368
  let raw;
3356
3369
  try {
3357
3370
  raw = fs9.readFileSync(file, "utf8");
@@ -3369,13 +3382,13 @@ function handleReport(opts = {}) {
3369
3382
  }
3370
3383
  return parsed;
3371
3384
  }
3372
- var fs9, path7, ReportNotFoundError;
3385
+ var fs9, path8, ReportNotFoundError;
3373
3386
  var init_report = __esm({
3374
3387
  "src/cli/commands/report.ts"() {
3375
3388
  "use strict";
3376
3389
  init_cjs_shims();
3377
3390
  fs9 = __toESM(require("fs"), 1);
3378
- path7 = __toESM(require("path"), 1);
3391
+ path8 = __toESM(require("path"), 1);
3379
3392
  ReportNotFoundError = class extends Error {
3380
3393
  code = "NO_SAVED_RUN";
3381
3394
  constructor(message) {
@@ -3599,7 +3612,7 @@ async function runInitWizard(cwd) {
3599
3612
  process.exit(1);
3600
3613
  return;
3601
3614
  }
3602
- const outputPath = path8.join(cwd, "plune.yaml");
3615
+ const outputPath = path9.join(cwd, "plune.yaml");
3603
3616
  if (fs11.existsSync(outputPath)) {
3604
3617
  const overwrite = await clack.confirm({
3605
3618
  message: "plune.yaml already exists. Overwrite?"
@@ -3643,14 +3656,14 @@ async function runInitWizard(cwd) {
3643
3656
  spin.stop("plune.yaml created!");
3644
3657
  clack.outro("plune.yaml is ready.");
3645
3658
  }
3646
- var clack, fs11, path8, import_yaml2;
3659
+ var clack, fs11, path9, import_yaml2;
3647
3660
  var init_wizard = __esm({
3648
3661
  "src/config/init/wizard.ts"() {
3649
3662
  "use strict";
3650
3663
  init_cjs_shims();
3651
3664
  clack = __toESM(require("@clack/prompts"), 1);
3652
3665
  fs11 = __toESM(require("fs"), 1);
3653
- path8 = __toESM(require("path"), 1);
3666
+ path9 = __toESM(require("path"), 1);
3654
3667
  import_yaml2 = require("yaml");
3655
3668
  init_errors();
3656
3669
  }
@@ -3658,21 +3671,21 @@ var init_wizard = __esm({
3658
3671
 
3659
3672
  // src/cli/templates/index.ts
3660
3673
  function writeTemplateFile(cwd, rel, content, force) {
3661
- const target = path9.join(cwd, rel);
3674
+ const target = path10.join(cwd, rel);
3662
3675
  if (!force && fs12.existsSync(target)) {
3663
3676
  return "skipped";
3664
3677
  }
3665
- fs12.mkdirSync(path9.dirname(target), { recursive: true });
3678
+ fs12.mkdirSync(path10.dirname(target), { recursive: true });
3666
3679
  fs12.writeFileSync(target, content, "utf8");
3667
3680
  return "written";
3668
3681
  }
3669
- var fs12, path9, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
3682
+ var fs12, path10, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
3670
3683
  var init_templates = __esm({
3671
3684
  "src/cli/templates/index.ts"() {
3672
3685
  "use strict";
3673
3686
  init_cjs_shims();
3674
3687
  fs12 = __toESM(require("fs"), 1);
3675
- path9 = __toESM(require("path"), 1);
3688
+ path10 = __toESM(require("path"), 1);
3676
3689
  ENV_EXAMPLE_TPL = `# Copy this file to .env and replace the placeholders with your real keys.
3677
3690
  # .env is gitignored; .env.example is safe to commit.
3678
3691
  # Plune reads whichever key matches your configured provider.
@@ -3928,7 +3941,7 @@ __export(sync_exports, {
3928
3941
  reportSyncFailure: () => reportSyncFailure
3929
3942
  });
3930
3943
  function readRun(cwd, file) {
3931
- const target = file !== void 0 ? path10.resolve(cwd, file) : path10.join(cwd, ".plune", "last-run.json");
3944
+ const target = file !== void 0 ? path11.resolve(cwd, file) : path11.join(cwd, ".plune", "last-run.json");
3932
3945
  let raw;
3933
3946
  try {
3934
3947
  raw = fs13.readFileSync(target, "utf8");
@@ -3995,13 +4008,13 @@ function reportSyncFailure(err, write) {
3995
4008
  }
3996
4009
  return null;
3997
4010
  }
3998
- var fs13, path10, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
4011
+ var fs13, path11, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
3999
4012
  var init_sync = __esm({
4000
4013
  "src/cli/commands/sync.ts"() {
4001
4014
  "use strict";
4002
4015
  init_cjs_shims();
4003
4016
  fs13 = __toESM(require("fs"), 1);
4004
- path10 = __toESM(require("path"), 1);
4017
+ path11 = __toESM(require("path"), 1);
4005
4018
  init_run_result_schema();
4006
4019
  init_api_url();
4007
4020
  init_credentials();
@@ -4051,9 +4064,9 @@ async function findLatestCairnRun(runsDir = "runs") {
4051
4064
  let newest;
4052
4065
  for (const entry of entries) {
4053
4066
  if (!entry.isDirectory()) continue;
4054
- const dir = path11.join(runsDir, entry.name);
4067
+ const dir = path12.join(runsDir, entry.name);
4055
4068
  try {
4056
- const report = await fs14.lstat(path11.join(dir, "report.json"));
4069
+ const report = await fs14.lstat(path12.join(dir, "report.json"));
4057
4070
  if (!report.isFile()) continue;
4058
4071
  if (newest === void 0 || report.mtimeMs > newest.at) {
4059
4072
  newest = { dir, at: report.mtimeMs };
@@ -4072,7 +4085,7 @@ async function readJson(file) {
4072
4085
  }
4073
4086
  }
4074
4087
  async function defaultReadEvidence(dir) {
4075
- const raw = await readJson(path11.join(dir, "api-evidence.json"));
4088
+ const raw = await readJson(path12.join(dir, "api-evidence.json"));
4076
4089
  if (!Array.isArray(raw)) return void 0;
4077
4090
  return raw.map((r) => {
4078
4091
  const rec = asRecord(r);
@@ -4114,7 +4127,7 @@ function casesFromApi(report, verdicts) {
4114
4127
  });
4115
4128
  }
4116
4129
  async function readCairnRun(dir, deps = {}) {
4117
- const raw = await readJson(path11.join(dir, "report.json"));
4130
+ const raw = await readJson(path12.join(dir, "report.json"));
4118
4131
  if (raw === void 0) throw new CairnRunNotFoundError(dir);
4119
4132
  const report = asRecord(raw);
4120
4133
  if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
@@ -4144,20 +4157,20 @@ async function readCairnRun(dir, deps = {}) {
4144
4157
  return {
4145
4158
  schemaVersion: INGEST_SCHEMA_VERSION,
4146
4159
  run: {
4147
- runId: str(report["runId"], path11.basename(dir)),
4160
+ runId: str(report["runId"], path12.basename(dir)),
4148
4161
  mode,
4149
4162
  target: str(report["url"], "unknown")
4150
4163
  },
4151
4164
  cases
4152
4165
  };
4153
4166
  }
4154
- var fs14, path11, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4167
+ var fs14, path12, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4155
4168
  var init_cairn_artifact = __esm({
4156
4169
  "src/cli/commands/cairn-artifact.ts"() {
4157
4170
  "use strict";
4158
4171
  init_cjs_shims();
4159
4172
  fs14 = __toESM(require("fs/promises"), 1);
4160
- path11 = __toESM(require("path"), 1);
4173
+ path12 = __toESM(require("path"), 1);
4161
4174
  SUPPORTED_ARTIFACT_VERSION = 1;
4162
4175
  INGEST_SCHEMA_VERSION = 1;
4163
4176
  NoCairnRunsError = class extends Error {
@@ -4613,10 +4626,10 @@ function createProgram() {
4613
4626
  const { handleLogin: handleLogin2, reportLoginFailure: reportLoginFailure2 } = await Promise.resolve().then(() => (init_login(), login_exports));
4614
4627
  try {
4615
4628
  const token = options.token ?? await readTokenFromStdin();
4616
- const { path: path12, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
4629
+ const { path: path13, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
4617
4630
  process.stdout.write(
4618
- verified ? `Logged in. Token checked against the API and saved to ${path12}
4619
- ` : `Token saved to ${path12} WITHOUT being checked (--skip-verify).
4631
+ verified ? `Logged in. Token checked against the API and saved to ${path13}
4632
+ ` : `Token saved to ${path13} WITHOUT being checked (--skip-verify).
4620
4633
  `
4621
4634
  );
4622
4635
  } catch (err) {
@@ -4633,9 +4646,9 @@ function createProgram() {
4633
4646
  const verbose = command.optsWithGlobals().verbose === true;
4634
4647
  const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
4635
4648
  try {
4636
- const { removed, path: path12 } = handleLogout2();
4649
+ const { removed, path: path13 } = handleLogout2();
4637
4650
  process.stdout.write(
4638
- removed ? `Logged out. Removed ${path12}
4651
+ removed ? `Logged out. Removed ${path13}
4639
4652
  ` : "Not logged in \u2014 nothing to remove.\n"
4640
4653
  );
4641
4654
  } catch (err) {
@@ -97,7 +97,7 @@ function createClient(opts) {
97
97
  const wait = opts.wait ?? sleep;
98
98
  const base = opts.apiUrl.replace(/\/+$/, "");
99
99
  const scrub = (text) => opts.token === "" ? text : text.split(opts.token).join("***");
100
- async function send(method, path2, body) {
100
+ async function send(method, path3, body) {
101
101
  let last = {
102
102
  kind: "unavailable",
103
103
  status: null,
@@ -106,7 +106,7 @@ function createClient(opts) {
106
106
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
107
107
  let res;
108
108
  try {
109
- res = await doFetch(`${base}${path2}`, {
109
+ res = await doFetch(`${base}${path3}`, {
110
110
  method,
111
111
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
112
112
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -134,12 +134,13 @@ function createClient(opts) {
134
134
  return { ok: false, ...last };
135
135
  }
136
136
  return {
137
- post: (path2, body) => send("POST", path2, body),
138
- del: (path2) => send("DELETE", path2)
137
+ post: (path3, body) => send("POST", path3, body),
138
+ del: (path3) => send("DELETE", path3)
139
139
  };
140
140
  }
141
141
 
142
142
  // src/reporter-core/env.ts
143
+ var path = __toESM(require("path"), 1);
143
144
  var UNSUPPORTED_VARS = ["PLUNE_GROUP"];
144
145
  function value(env, name) {
145
146
  const raw = env[name]?.trim();
@@ -192,13 +193,19 @@ function readEnv(env = process.env) {
192
193
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
193
194
  };
194
195
  }
196
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
197
+ const two = (n) => String(n).padStart(2, "0");
198
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
199
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
200
+ return `${path.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
201
+ }
195
202
 
196
203
  // src/reporter-core/fallback.ts
197
204
  var fs = __toESM(require("fs"), 1);
198
- var path = __toESM(require("path"), 1);
199
- var DEFAULT_FALLBACK_PATH = path.join(".plune", "pending-results.jsonl");
205
+ var path2 = __toESM(require("path"), 1);
206
+ var DEFAULT_FALLBACK_PATH = path2.join(".plune", "pending-results.jsonl");
200
207
  function appendBatch(file, batch) {
201
- fs.mkdirSync(path.dirname(file), { recursive: true });
208
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
202
209
  const line = {
203
210
  ts: (/* @__PURE__ */ new Date()).toISOString(),
204
211
  runId: batch.runId,
@@ -339,6 +346,10 @@ async function startRun(passed, declared) {
339
346
  ...externalKey !== null ? { externalKey } : {},
340
347
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
341
348
  ...configuration !== void 0 ? { configuration } : {},
349
+ // Named even when nobody named it — the list has to read without opening a row. Only what
350
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
351
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
352
+ title: defaultRunTitle(),
342
353
  ...described
343
354
  });
344
355
  if (start.ok) {
@@ -54,7 +54,7 @@ function createClient(opts) {
54
54
  const wait = opts.wait ?? sleep;
55
55
  const base = opts.apiUrl.replace(/\/+$/, "");
56
56
  const scrub = (text) => opts.token === "" ? text : text.split(opts.token).join("***");
57
- async function send(method, path2, body) {
57
+ async function send(method, path3, body) {
58
58
  let last = {
59
59
  kind: "unavailable",
60
60
  status: null,
@@ -63,7 +63,7 @@ function createClient(opts) {
63
63
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
64
64
  let res;
65
65
  try {
66
- res = await doFetch(`${base}${path2}`, {
66
+ res = await doFetch(`${base}${path3}`, {
67
67
  method,
68
68
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
69
69
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -91,12 +91,13 @@ function createClient(opts) {
91
91
  return { ok: false, ...last };
92
92
  }
93
93
  return {
94
- post: (path2, body) => send("POST", path2, body),
95
- del: (path2) => send("DELETE", path2)
94
+ post: (path3, body) => send("POST", path3, body),
95
+ del: (path3) => send("DELETE", path3)
96
96
  };
97
97
  }
98
98
 
99
99
  // src/reporter-core/env.ts
100
+ import * as path from "path";
100
101
  var UNSUPPORTED_VARS = ["PLUNE_GROUP"];
101
102
  function value(env, name) {
102
103
  const raw = env[name]?.trim();
@@ -149,13 +150,19 @@ function readEnv(env = process.env) {
149
150
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
150
151
  };
151
152
  }
153
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
154
+ const two = (n) => String(n).padStart(2, "0");
155
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
156
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
157
+ return `${path.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
158
+ }
152
159
 
153
160
  // src/reporter-core/fallback.ts
154
161
  import * as fs from "fs";
155
- import * as path from "path";
156
- var DEFAULT_FALLBACK_PATH = path.join(".plune", "pending-results.jsonl");
162
+ import * as path2 from "path";
163
+ var DEFAULT_FALLBACK_PATH = path2.join(".plune", "pending-results.jsonl");
157
164
  function appendBatch(file, batch) {
158
- fs.mkdirSync(path.dirname(file), { recursive: true });
165
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
159
166
  const line = {
160
167
  ts: (/* @__PURE__ */ new Date()).toISOString(),
161
168
  runId: batch.runId,
@@ -296,6 +303,10 @@ async function startRun(passed, declared) {
296
303
  ...externalKey !== null ? { externalKey } : {},
297
304
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
298
305
  ...configuration !== void 0 ? { configuration } : {},
306
+ // Named even when nobody named it — the list has to read without opening a row. Only what
307
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
308
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
309
+ title: defaultRunTitle(),
299
310
  ...described
300
311
  });
301
312
  if (start.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
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",