@odla-ai/cli 0.27.2 → 0.27.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/dist/index.cjs CHANGED
@@ -100,6 +100,7 @@ var import_node_process7 = __toESM(require("process"), 1);
100
100
 
101
101
  // src/token.ts
102
102
  var import_db = require("@odla-ai/db");
103
+ var import_node_crypto = require("crypto");
103
104
  var import_node_process4 = __toESM(require("process"), 1);
104
105
 
105
106
  // src/handshake-approval.ts
@@ -454,6 +455,7 @@ async function freshHandshake(ctx, waitMs) {
454
455
  endpoint: ctx.cfg.platformUrl,
455
456
  email: ctx.email,
456
457
  label: `${ctx.cfg.app.id} provisioner`,
458
+ agentHandle: projectAgentHandle(ctx.cfg.app.id),
457
459
  projectIds: [ctx.cfg.app.id],
458
460
  fetch: ctx.doFetch,
459
461
  waitMs,
@@ -489,6 +491,12 @@ async function freshHandshake(ctx, waitMs) {
489
491
  stopReminder?.();
490
492
  }
491
493
  }
494
+ function projectAgentHandle(appId) {
495
+ const candidate = /^[a-z]/.test(appId) ? appId : `app-${appId}`;
496
+ if (candidate.length <= 32) return candidate;
497
+ const digest = (0, import_node_crypto.createHash)("sha256").update(appId).digest("hex").slice(0, 8);
498
+ return `${candidate.slice(0, 23).replace(/-+$/, "")}-${digest}`;
499
+ }
492
500
  function stillPending(pending, email) {
493
501
  return new import_db.OdlaError(
494
502
  "handshake_pending",
@@ -2701,8 +2709,8 @@ async function calendarCalendars(options) {
2701
2709
  async function calendarConnect(options) {
2702
2710
  const { cfg, ctx, out } = await lifecycleContext(options);
2703
2711
  productionConsent(ctx.env, options.yes, "connect calendar");
2704
- const page = calendarBookingPageUrl(cfg, ctx.env);
2705
- const applied = page === void 0 ? await readCalendarStatus(ctx) : await applyCalendarSettings(ctx, page);
2712
+ const page2 = calendarBookingPageUrl(cfg, ctx.env);
2713
+ const applied = page2 === void 0 ? await readCalendarStatus(ctx) : await applyCalendarSettings(ctx, page2);
2706
2714
  const connectOptions = connectionOptions(options, out);
2707
2715
  return await continueConnectedCalendar(ctx, applied, connectOptions) ?? connectWithContext(ctx, connectOptions);
2708
2716
  }
@@ -2951,12 +2959,12 @@ var import_apps3 = require("@odla-ai/apps");
2951
2959
  var import_node_fs10 = require("fs");
2952
2960
 
2953
2961
  // src/config-reconcile-digest.ts
2954
- var import_node_crypto = require("crypto");
2962
+ var import_node_crypto2 = require("crypto");
2955
2963
  function canonicalJson(value2) {
2956
2964
  return JSON.stringify(canonicalValue(value2));
2957
2965
  }
2958
2966
  function configDigest(value2) {
2959
- return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2967
+ return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2960
2968
  }
2961
2969
  function canonicalValue(value2) {
2962
2970
  if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
@@ -4529,9 +4537,10 @@ For work that creates an odla app or adds odla services, read and follow
4529
4537
  \`.agents/skills/odla-migrate/SKILL.md\`. For production telemetry triage, use
4530
4538
  \`.agents/skills/odla-o11y-debug/SKILL.md\`.
4531
4539
 
4532
- Track the work in odla's PM as you go \u2014 record decisions when you make them, file
4533
- bugs when you notice them, and move tasks when the work moves
4534
- (\`npx @odla-ai/cli pm task list\`). The conventions and the full command set are
4540
+ Track the work in odla's PM as you go. Before project-mutating work, run
4541
+ \`npx @odla-ai/cli pm next --app <appId>\`, confirm alignment to an open goal,
4542
+ and atomically claim a refined Ready task. Record decisions when you make them
4543
+ and file bugs when you notice them. The conventions and the full command set are
4535
4544
  in \`.agents/skills/odla/references/pm.md\`.
4536
4545
 
4537
4546
  The setup runbooks and their references are installed in this repository, pinned
@@ -5755,7 +5764,7 @@ function dependenciesOf(values, influence = "data") {
5755
5764
  return [...unique3.values()];
5756
5765
  }
5757
5766
 
5758
- // ../camel/dist/chunk-S7EVNA2U.js
5767
+ // ../camel/dist/chunk-4DQ6BIHP.js
5759
5768
  var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
5760
5769
  var authenticCamelValues = /* @__PURE__ */ new WeakSet();
5761
5770
  function isCamelValue(value2) {
@@ -5767,13 +5776,13 @@ function isSafe(value2) {
5767
5776
  function isUnsafe(value2) {
5768
5777
  return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
5769
5778
  }
5770
- function createSafeInternal(value2, safeBasis, metadata2) {
5779
+ function createSafeInternal(value2, safeBasis, metadata2, extra) {
5771
5780
  return createValue(value2, {
5772
5781
  schemaVersion: 1,
5773
5782
  promptSafety: "safe",
5774
5783
  safeBasis,
5775
5784
  ...copyMetadata(metadata2)
5776
- });
5785
+ }, extra);
5777
5786
  }
5778
5787
  function createUnsafeInternal(value2, metadata2) {
5779
5788
  return createValue(value2, {
@@ -5782,8 +5791,8 @@ function createUnsafeInternal(value2, metadata2) {
5782
5791
  ...copyMetadata(metadata2)
5783
5792
  });
5784
5793
  }
5785
- function createValue(value2, label) {
5786
- const result = { value: value2, label: Object.freeze(label) };
5794
+ function createValue(value2, label, extra) {
5795
+ const result = { value: value2, label: Object.freeze(label), ...extra };
5787
5796
  Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
5788
5797
  authenticCamelValues.add(result);
5789
5798
  return Object.freeze(result);
@@ -6044,7 +6053,7 @@ var import_path8 = require("path");
6044
6053
  var import_promises10 = require("fs/promises");
6045
6054
  var import_path9 = require("path");
6046
6055
 
6047
- // ../camel/dist/chunk-LAXU2AVK.js
6056
+ // ../camel/dist/chunk-4EIRFS3A.js
6048
6057
  function conversionPolicyDigest(policy) {
6049
6058
  return sha256Hex(canonicalJson2(policy));
6050
6059
  }
@@ -6132,10 +6141,18 @@ async function convert(source, policy, value2, counts) {
6132
6141
  const count = counts.get(countKey) ?? 0;
6133
6142
  if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
6134
6143
  counts.set(countKey, count + 1);
6144
+ const conversionRecordId = await sha256Hex(canonicalJson2({ conversionId: policy.conversionId, digest: policy.digest, source: sourceKey, ordinal: count }));
6145
+ const datumId = await sha256Hex(canonicalJson2({ conversionRecordId, value: value2 }));
6135
6146
  return createSafeInternal(value2, "atomic_conversion", {
6136
6147
  readers: source.label.readers,
6137
6148
  provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
6138
6149
  dependencies: dependenciesOf([source])
6150
+ }, {
6151
+ datumId,
6152
+ kind: policy.output.kind,
6153
+ conversionId: policy.conversionId,
6154
+ conversionDigest: policy.digest,
6155
+ conversionRecordId
6139
6156
  });
6140
6157
  }
6141
6158
  function validatePolicyShape(policy) {
@@ -6200,7 +6217,7 @@ function missingPolicy() {
6200
6217
  throw new CamelError("conversion_rejected", "Conversion policy is not registered.");
6201
6218
  }
6202
6219
 
6203
- // ../camel/dist/chunk-P2WQIAG6.js
6220
+ // ../camel/dist/chunk-VEAUXH4F.js
6204
6221
  function createCamelIngress(constants2 = []) {
6205
6222
  const byId = /* @__PURE__ */ new Map();
6206
6223
  for (const item of constants2) {
@@ -8190,7 +8207,7 @@ async function defaultReadOrigin(cwd) {
8190
8207
 
8191
8208
  // src/code-local-source.ts
8192
8209
  var import_node_child_process5 = require("child_process");
8193
- var import_node_crypto2 = require("crypto");
8210
+ var import_node_crypto3 = require("crypto");
8194
8211
  var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
8195
8212
  async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
8196
8213
  const headCommitSha = await readHead(cwd);
@@ -8241,12 +8258,12 @@ async function readGitHead(cwd) {
8241
8258
  return value2;
8242
8259
  }
8243
8260
  function digestText(value2) {
8244
- return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(value2).digest("hex")}`;
8261
+ return `sha256:${(0, import_node_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
8245
8262
  }
8246
8263
 
8247
8264
  // src/code-images.ts
8248
8265
  var import_node_child_process6 = require("child_process");
8249
- var import_node_crypto3 = require("crypto");
8266
+ var import_node_crypto4 = require("crypto");
8250
8267
  var import_promises11 = require("fs/promises");
8251
8268
  var import_node_os3 = require("os");
8252
8269
  var import_node_path14 = require("path");
@@ -8332,7 +8349,7 @@ async function embeddedPiImageName() {
8332
8349
  const bundle = await (0, import_promises11.readFile)(embeddedPiAssetPath()).catch(() => {
8333
8350
  throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
8334
8351
  });
8335
- return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
8352
+ return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto4.createHash)("sha256").update(bundle).digest("hex")}`;
8336
8353
  }
8337
8354
  async function buildEmbeddedPiImage(engine, image, run) {
8338
8355
  const context = await (0, import_promises11.mkdtemp)((0, import_node_path14.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
@@ -8760,16 +8777,22 @@ Usage:
8760
8777
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
8761
8778
  odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
8762
8779
  odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8763
- odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8780
+ odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8764
8781
  odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8765
8782
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8766
8783
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
8767
- odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
8784
+ odla-ai pm task add --app <id> --title <t> [--column <backlog|ready|doing|review|done>] [--goal <id>|--alignment-decision <id>] [--assignee <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
8785
+ odla-ai pm next --app <id> [--json]
8786
+ odla-ai pm watch --app <id> [--cursor <cursor>] [--entity goal|task|decision|bug] [--action created|updated|deleted|comment.created|comment.updated] [--state <state>] [--by <principalId>] [--self <principalId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
8787
+ odla-ai pm task ready <id> --expected-revision <n> [--goal <id>|--alignment-decision <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--mutation-id <id>] [--json]
8788
+ odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
8789
+ odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
8768
8790
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
8769
8791
  odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
8770
8792
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
8793
+ odla-ai pm <goal|task|decision|bug> ref <id> [--json]
8771
8794
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
8772
- odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
8795
+ odla-ai pm task set <id> [--title <t>|--column <backlog|ready|doing|review|done>|--rank <n>|--goal <id>|--no-goal|--alignment-decision <id>|--no-alignment-decision|--execution <human|agent|either>|--assignee <id>|--no-assignee|--description <text>|--body <text>|--acceptance <text>|--no-acceptance|--due <epoch-ms>|--no-due|--expected-revision <n>] [--mutation-id <id>] [--json]
8773
8796
  odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
8774
8797
  odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
8775
8798
  odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
@@ -9088,11 +9111,11 @@ async function discussList(ctx, parsed) {
9088
9111
  if (value2) query.set(param, value2);
9089
9112
  }
9090
9113
  const qs = query.toString();
9091
- const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
9092
- emit(ctx, page, () => {
9093
- ctx.out.log(`topics \u2014 ${page.topics.length} of ${page.total}`);
9114
+ const page2 = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
9115
+ emit(ctx, page2, () => {
9116
+ ctx.out.log(`topics \u2014 ${page2.topics.length} of ${page2.total}`);
9094
9117
  ctx.out.log("id state app replies subject");
9095
- for (const topic of page.topics) {
9118
+ for (const topic of page2.topics) {
9096
9119
  ctx.out.log(
9097
9120
  `${topic.id} ${state(topic)} ${topic.appId ?? ""} ${topic.replyCount} ${topic.subject}`
9098
9121
  );
@@ -9107,11 +9130,11 @@ async function discussRead(ctx, id, parsed) {
9107
9130
  limit: requestedLimit ?? "200",
9108
9131
  offset: requestedOffset ?? "0"
9109
9132
  });
9110
- const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
9133
+ const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
9111
9134
  emit(
9112
9135
  ctx,
9113
- page,
9114
- () => renderDiscussRead(ctx, page.topic, page.posts, page)
9136
+ page2,
9137
+ () => renderDiscussRead(ctx, page2.topic, page2.posts, page2)
9115
9138
  );
9116
9139
  return;
9117
9140
  }
@@ -9124,20 +9147,20 @@ async function discussRead(ctx, id, parsed) {
9124
9147
  let topic = null;
9125
9148
  let offset = 0;
9126
9149
  for (; ; ) {
9127
- const page = await request(
9150
+ const page2 = await request(
9128
9151
  ctx,
9129
9152
  "GET",
9130
9153
  `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
9131
9154
  );
9132
- topic = page.topic;
9133
- for (const post of page.posts) posts.set(post.id, post);
9134
- mergeDiscussPrincipals(projection, page);
9155
+ topic = page2.topic;
9156
+ for (const post of page2.posts) posts.set(post.id, post);
9157
+ mergeDiscussPrincipals(projection, page2);
9135
9158
  if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
9136
- if (!page.page?.hasMore) break;
9137
- if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
9159
+ if (!page2.page?.hasMore) break;
9160
+ if (page2.page.nextOffset === null || page2.page.nextOffset <= offset) {
9138
9161
  throw new Error("discuss read failed: registry returned a non-advancing post page");
9139
9162
  }
9140
- offset = page.page.nextOffset;
9163
+ offset = page2.page.nextOffset;
9141
9164
  }
9142
9165
  const ordered = [...posts.values()].sort(
9143
9166
  (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
@@ -9305,9 +9328,9 @@ async function discussWatch(ctx, topicId, parsed) {
9305
9328
  let firstSuccess = true;
9306
9329
  let consecutiveFailures = 0;
9307
9330
  for (; ; ) {
9308
- let page;
9331
+ let page2;
9309
9332
  try {
9310
- page = await getWatchPage(ctx, requestPath(topicId, app, cursor));
9333
+ page2 = await getWatchPage(ctx, requestPath(topicId, app, cursor));
9311
9334
  consecutiveFailures = 0;
9312
9335
  } catch (error) {
9313
9336
  if (!(error instanceof WatchRequestError) || !error.retryable) {
@@ -9343,25 +9366,25 @@ async function discussWatch(ctx, topicId, parsed) {
9343
9366
  await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
9344
9367
  continue;
9345
9368
  }
9346
- cursor = page.cursor;
9347
- const baseline = firstSuccess && page.events.length === 0;
9369
+ cursor = page2.cursor;
9370
+ const baseline = firstSuccess && page2.events.length === 0;
9348
9371
  if (baseline) {
9349
9372
  jsonl(ctx, parsed, {
9350
9373
  type: "checkpoint",
9351
- streamId: page.streamId,
9374
+ streamId: page2.streamId,
9352
9375
  cursor,
9353
- serverTime: page.serverTime
9376
+ serverTime: page2.serverTime
9354
9377
  });
9355
9378
  }
9356
9379
  firstSuccess = false;
9357
- const matching = page.events.filter((event) => {
9380
+ const matching = page2.events.filter((event) => {
9358
9381
  if (topicId && (event.type !== "message" || event.action !== "created")) return false;
9359
9382
  return (!by || event.actor.id === by) && (!self || event.actor.id !== self);
9360
9383
  });
9361
9384
  for (const event of matching) {
9362
9385
  jsonl(ctx, parsed, {
9363
9386
  type: "event",
9364
- streamId: page.streamId,
9387
+ streamId: page2.streamId,
9365
9388
  eventId: event.id,
9366
9389
  cursor: event.cursor,
9367
9390
  event
@@ -9370,9 +9393,9 @@ async function discussWatch(ctx, topicId, parsed) {
9370
9393
  if (matching.length > 0) {
9371
9394
  jsonl(ctx, parsed, {
9372
9395
  type: "checkpoint",
9373
- streamId: page.streamId,
9396
+ streamId: page2.streamId,
9374
9397
  cursor,
9375
- serverTime: page.serverTime
9398
+ serverTime: page2.serverTime
9376
9399
  });
9377
9400
  const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
9378
9401
  const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
@@ -9380,28 +9403,28 @@ async function discussWatch(ctx, topicId, parsed) {
9380
9403
  found: true,
9381
9404
  cursor,
9382
9405
  events: matching,
9383
- ...page.authors ? { authors: page.authors } : {},
9384
- ...page.principals ? { principals: page.principals } : {},
9406
+ ...page2.authors ? { authors: page2.authors } : {},
9407
+ ...page2.principals ? { principals: page2.principals } : {},
9385
9408
  ...posts && posts.length > 0 ? { posts } : {},
9386
9409
  ...topics && topics.length > 0 ? { topics } : {}
9387
9410
  });
9388
9411
  }
9389
- if (page.events.length > 0) {
9412
+ if (page2.events.length > 0) {
9390
9413
  jsonl(ctx, parsed, {
9391
9414
  type: "checkpoint",
9392
- streamId: page.streamId,
9415
+ streamId: page2.streamId,
9393
9416
  cursor,
9394
- serverTime: page.serverTime
9417
+ serverTime: page2.serverTime
9395
9418
  });
9396
9419
  } else if (!baseline) {
9397
9420
  jsonl(ctx, parsed, {
9398
9421
  type: "heartbeat",
9399
- streamId: page.streamId,
9422
+ streamId: page2.streamId,
9400
9423
  cursor,
9401
- serverTime: page.serverTime
9424
+ serverTime: page2.serverTime
9402
9425
  });
9403
9426
  }
9404
- if (page.hasMore) continue;
9427
+ if (page2.hasMore) continue;
9405
9428
  if (deadline !== void 0 && now() >= deadline) {
9406
9429
  return report2(ctx, parsed, { found: false, cursor });
9407
9430
  }
@@ -9520,7 +9543,13 @@ async function discussCommand(parsed, deps = {}) {
9520
9543
  }
9521
9544
  }
9522
9545
 
9523
- // src/pm-actions.ts
9546
+ // src/pm-action-core.ts
9547
+ var DONE = {
9548
+ goal: { status: "met", currentPct: 100 },
9549
+ task: { column: "done" },
9550
+ decision: { status: "accepted" },
9551
+ bug: { status: "fixed" }
9552
+ };
9524
9553
  var writeMutationId2 = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
9525
9554
  var FIELD_MAP = {
9526
9555
  title: { key: "title" },
@@ -9536,22 +9565,27 @@ var FIELD_MAP = {
9536
9565
  body: { key: "body" },
9537
9566
  target: { key: "targetPct", num: true },
9538
9567
  description: { key: "description" },
9539
- desc: { key: "description" }
9540
- };
9541
- var DONE = {
9542
- goal: { status: "met", currentPct: 100 },
9543
- task: { column: "done" },
9544
- decision: { status: "accepted" },
9545
- bug: { status: "fixed" }
9568
+ desc: { key: "description" },
9569
+ acceptance: { key: "acceptanceCriteria" },
9570
+ "alignment-decision": { key: "alignmentDecisionId" },
9571
+ execution: { key: "executionMode" },
9572
+ "expected-revision": { key: "expectedRevision", num: true }
9546
9573
  };
9547
9574
  async function pmRequest(ctx, method, path, body) {
9548
- const res = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
9575
+ const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
9549
9576
  method,
9550
- headers: { authorization: `Bearer ${ctx.token}`, "content-type": "application/json" },
9577
+ headers: {
9578
+ authorization: `Bearer ${ctx.token}`,
9579
+ "content-type": "application/json"
9580
+ },
9551
9581
  body: body === void 0 ? void 0 : JSON.stringify(body)
9552
9582
  });
9553
- const data = await res.json().catch(() => ({}));
9554
- if (!res.ok) throw new Error(`pm ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
9583
+ const data = await response2.json().catch(() => ({}));
9584
+ if (!response2.ok) {
9585
+ throw new Error(
9586
+ `pm ${method} ${path} failed: ${data.error ?? `registry returned ${response2.status}`}`
9587
+ );
9588
+ }
9555
9589
  return data;
9556
9590
  }
9557
9591
  function collectFields(parsed, allowClear) {
@@ -9574,20 +9608,32 @@ function collectEntityFields(entity, parsed, allowClear) {
9574
9608
  if (fields.description === void 0) fields.description = fields.body;
9575
9609
  delete fields.body;
9576
9610
  }
9611
+ if (entity === "task" && fields.column === "ready") fields.column = "todo";
9577
9612
  return fields;
9578
9613
  }
9579
- function statusCol(entity, r) {
9580
- if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
9581
- if (entity === "task") return String(r.column ?? "");
9582
- return String(r.status ?? "");
9614
+ function statusCol(entity, record10) {
9615
+ if (entity === "bug") return `${record10.status ?? ""}/${record10.severity ?? ""}`;
9616
+ if (entity === "task") {
9617
+ const state2 = record10.column === "todo" ? "ready" : String(record10.column ?? "");
9618
+ return record10.revision ? `${state2}; r${record10.revision}` : state2;
9619
+ }
9620
+ return String(record10.status ?? "");
9583
9621
  }
9584
- function printRecord(ctx, entity, r) {
9585
- ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
9622
+ function referenceMarkup(entity, record10) {
9623
+ const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
9624
+ return `@[${label}](pm:${entity}/${record10.id})`;
9625
+ }
9626
+ function printRecord(ctx, entity, record10) {
9627
+ ctx.out.log(
9628
+ `${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${record10.title ?? ""}`
9629
+ );
9586
9630
  }
9587
9631
  function emit2(ctx, value2, human) {
9588
9632
  if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
9589
9633
  else human();
9590
9634
  }
9635
+
9636
+ // src/pm-actions.ts
9591
9637
  async function pmList(ctx, entity, parsed) {
9592
9638
  const q = new URLSearchParams();
9593
9639
  const filters = {
@@ -9601,7 +9647,8 @@ async function pmList(ctx, entity, parsed) {
9601
9647
  const app = stringOpt(parsed.options.app) ?? ctx.appId;
9602
9648
  if (app) q.set("app", app);
9603
9649
  for (const [flag, param] of Object.entries(filters)) {
9604
- const v = stringOpt(parsed.options[flag]);
9650
+ const raw = stringOpt(parsed.options[flag]);
9651
+ const v = entity === "task" && flag === "column" && raw === "ready" ? "todo" : raw;
9605
9652
  if (v) q.set(param, v);
9606
9653
  }
9607
9654
  for (const opt of ["q", "limit", "offset"]) {
@@ -9609,11 +9656,11 @@ async function pmList(ctx, entity, parsed) {
9609
9656
  if (v) q.set(opt, v);
9610
9657
  }
9611
9658
  const qs = q.toString();
9612
- const page = await pmRequest(ctx, "GET", `/${entity}${qs ? `?${qs}` : ""}`);
9613
- emit2(ctx, page, () => {
9614
- ctx.out.log(`${entity} \u2014 ${page.records.length} of ${page.total}`);
9659
+ const page2 = await pmRequest(ctx, "GET", `/${entity}${qs ? `?${qs}` : ""}`);
9660
+ emit2(ctx, page2, () => {
9661
+ ctx.out.log(`${entity} \u2014 ${page2.records.length} of ${page2.total}`);
9615
9662
  ctx.out.log("id state app title");
9616
- for (const r of page.records) printRecord(ctx, entity, r);
9663
+ for (const r of page2.records) printRecord(ctx, entity, r);
9617
9664
  });
9618
9665
  }
9619
9666
  async function pmAdd(ctx, entity, parsed) {
@@ -9634,6 +9681,17 @@ async function pmGet(ctx, entity, id) {
9634
9681
  const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
9635
9682
  emit2(ctx, record10, () => printRecord(ctx, entity, record10));
9636
9683
  }
9684
+ async function pmReference(ctx, entity, id) {
9685
+ const { record: record10 } = await pmRequest(
9686
+ ctx,
9687
+ "GET",
9688
+ `/${entity}/${encodeURIComponent(id)}`
9689
+ );
9690
+ const markup = referenceMarkup(entity, record10);
9691
+ emit2(ctx, { kind: `pm:${entity}`, id: record10.id, label: record10.title ?? "", markup }, () => {
9692
+ ctx.out.log(markup);
9693
+ });
9694
+ }
9637
9695
  async function pmSet(ctx, entity, id, parsed) {
9638
9696
  const patch2 = collectEntityFields(entity, parsed, true);
9639
9697
  if (Object.keys(patch2).length === 0)
@@ -9654,6 +9712,36 @@ async function pmDone(ctx, entity, id, parsed) {
9654
9712
  });
9655
9713
  emit2(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
9656
9714
  }
9715
+ async function pmTaskLifecycle(ctx, id, action2, parsed) {
9716
+ const rawRevision = stringOpt(parsed.options["expected-revision"]);
9717
+ const expectedRevision = Number(rawRevision);
9718
+ if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
9719
+ throw new Error(`pm task ${action2} needs --expected-revision <n>`);
9720
+ }
9721
+ const mutationId = writeMutationId2(parsed);
9722
+ const res = action2 === "ready" ? await pmRequest(
9723
+ ctx,
9724
+ "PATCH",
9725
+ `/task/${encodeURIComponent(id)}`,
9726
+ {
9727
+ patch: {
9728
+ ...collectEntityFields("task", parsed, true),
9729
+ column: "todo",
9730
+ expectedRevision
9731
+ },
9732
+ mutationId
9733
+ }
9734
+ ) : await pmRequest(
9735
+ ctx,
9736
+ "POST",
9737
+ `/task/${encodeURIComponent(id)}/${action2}`,
9738
+ { expectedRevision, mutationId }
9739
+ );
9740
+ emit2(ctx, res, () => {
9741
+ const state2 = res.record ? statusCol("task", res.record) : action2;
9742
+ ctx.out.log(`task ${id} \u2192 ${state2}`);
9743
+ });
9744
+ }
9657
9745
  async function allRecords(ctx, entity, appId) {
9658
9746
  const records = [];
9659
9747
  for (; ; ) {
@@ -9662,11 +9750,48 @@ async function allRecords(ctx, entity, appId) {
9662
9750
  limit: "100",
9663
9751
  offset: String(records.length)
9664
9752
  });
9665
- const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
9666
- records.push(...page.records);
9667
- if (records.length >= page.total || page.records.length === 0) return records;
9753
+ const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
9754
+ records.push(...page2.records);
9755
+ if (records.length >= page2.total || page2.records.length === 0) return records;
9668
9756
  }
9669
9757
  }
9758
+ async function pmNext(ctx, parsed) {
9759
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
9760
+ if (!appId) throw new Error("pm next needs --app <appId>");
9761
+ const [goals, tasks] = await Promise.all([
9762
+ allRecords(ctx, "goal", appId),
9763
+ allRecords(ctx, "task", appId)
9764
+ ]);
9765
+ const result = {
9766
+ appId,
9767
+ openGoals: goals.filter((record10) => record10.status === "open"),
9768
+ doing: tasks.filter((record10) => record10.column === "doing"),
9769
+ ready: tasks.filter((record10) => record10.column === "todo")
9770
+ };
9771
+ emit2(ctx, result, () => {
9772
+ ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
9773
+ for (const [label, records] of [
9774
+ ["doing", result.doing],
9775
+ ["ready", result.ready],
9776
+ ["open goals", result.openGoals]
9777
+ ]) {
9778
+ ctx.out.log(`${label}:`);
9779
+ if (!records.length) ctx.out.log("- (none)");
9780
+ else for (const record10 of records) printRecord(
9781
+ ctx,
9782
+ label === "open goals" ? "goal" : "task",
9783
+ record10
9784
+ );
9785
+ }
9786
+ if (!result.openGoals.length) {
9787
+ ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
9788
+ } else if (!result.ready.length) {
9789
+ ctx.out.log("next: refine a linked Backlog task and mark it Ready");
9790
+ } else {
9791
+ ctx.out.log("next: review a Ready task, then claim it with its revision");
9792
+ }
9793
+ });
9794
+ }
9670
9795
  async function pmHandoff(ctx, parsed) {
9671
9796
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
9672
9797
  if (!appId) throw new Error("pm handoff needs --app <appId>");
@@ -9706,6 +9831,12 @@ async function pmHandoff(ctx, parsed) {
9706
9831
  }
9707
9832
  });
9708
9833
  }
9834
+ async function pmRemove(ctx, entity, id) {
9835
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
9836
+ ctx.out.log(`deleted ${entity} ${id}`);
9837
+ }
9838
+
9839
+ // src/pm-comments.ts
9709
9840
  async function pmComment(ctx, entity, id, parsed) {
9710
9841
  const body = stringOpt(parsed.options.body);
9711
9842
  if (!body) throw new Error('pm comment needs --body "..."');
@@ -9716,19 +9847,218 @@ async function pmComment(ctx, entity, id, parsed) {
9716
9847
  ctx.out.log(`commented on ${entity} ${id}`);
9717
9848
  }
9718
9849
  async function pmComments(ctx, entity, id) {
9719
- const { messages } = await pmRequest(
9720
- ctx,
9721
- "GET",
9722
- `/${entity}/${encodeURIComponent(id)}/comments`
9723
- );
9850
+ const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}/comments`);
9724
9851
  emit2(ctx, messages, () => {
9725
9852
  if (messages.length === 0) ctx.out.log("(no comments)");
9726
- else for (const m of messages) ctx.out.log(`[${m.authorId ?? "?"}] ${m.body ?? ""}`);
9853
+ else for (const message2 of messages) {
9854
+ ctx.out.log(
9855
+ `[${message2.authorId ?? "?"}] ${message2.markup ?? message2.body ?? ""}`
9856
+ );
9857
+ }
9727
9858
  });
9728
9859
  }
9729
- async function pmRemove(ctx, entity, id) {
9730
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
9731
- ctx.out.log(`deleted ${entity} ${id}`);
9860
+
9861
+ // src/pm-watch-types.ts
9862
+ var PmWatchCheckpointError = class extends Error {
9863
+ constructor(cursor, streamId) {
9864
+ super("PM work cursor requires a new checkpoint");
9865
+ this.cursor = cursor;
9866
+ this.streamId = streamId;
9867
+ this.name = "PmWatchCheckpointError";
9868
+ }
9869
+ cursor;
9870
+ streamId;
9871
+ code = "checkpoint_required";
9872
+ };
9873
+ var PmWatchRequestError = class extends Error {
9874
+ constructor(message2, retryable, status) {
9875
+ super(message2);
9876
+ this.retryable = retryable;
9877
+ this.status = status;
9878
+ this.name = "PmWatchRequestError";
9879
+ }
9880
+ retryable;
9881
+ status;
9882
+ };
9883
+
9884
+ // src/pm-watch.ts
9885
+ var DEFAULT_INTERVAL_MS2 = 15e3;
9886
+ var MAX_CONSECUTIVE_FAILURES2 = 5;
9887
+ var MAX_BACKOFF_MS2 = 3e4;
9888
+ function positiveNumber(parsed, flag, fallback) {
9889
+ const raw = stringOpt(parsed.options[flag]);
9890
+ const value2 = raw == null ? NaN : Number(raw);
9891
+ return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
9892
+ }
9893
+ function jsonl2(ctx, parsed, value2) {
9894
+ if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
9895
+ }
9896
+ async function page(ctx, appId, cursor) {
9897
+ const params = new URLSearchParams({ app: appId });
9898
+ if (cursor) params.set("cursor", cursor);
9899
+ let response2;
9900
+ try {
9901
+ response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm/work/watch?${params}`, {
9902
+ headers: { authorization: `Bearer ${ctx.token}` }
9903
+ });
9904
+ } catch (error) {
9905
+ throw new PmWatchRequestError(
9906
+ `pm watch request failed: ${error instanceof Error ? error.message : String(error)}`,
9907
+ true
9908
+ );
9909
+ }
9910
+ const data = await response2.json().catch(() => ({}));
9911
+ if (response2.status === 409 && data.code === "checkpoint_required") {
9912
+ throw new PmWatchCheckpointError(data.cursor, data.streamId);
9913
+ }
9914
+ if (!response2.ok) {
9915
+ throw new PmWatchRequestError(
9916
+ `pm watch failed: ${data.error ?? `registry returned ${response2.status}`}`,
9917
+ response2.status === 429 || response2.status >= 500,
9918
+ response2.status
9919
+ );
9920
+ }
9921
+ return data;
9922
+ }
9923
+ function recordState(record10) {
9924
+ if (record10.column) return record10.column === "todo" ? "ready" : record10.column;
9925
+ return String(record10.status ?? "");
9926
+ }
9927
+ function eventRecord(event) {
9928
+ return event.payload.payload;
9929
+ }
9930
+ function eventLabel(event) {
9931
+ const record10 = eventRecord(event);
9932
+ if (record10) return String(record10.title ?? event.payload.entityId);
9933
+ const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
9934
+ return body || event.payload.entityId;
9935
+ }
9936
+ function report3(ctx, parsed, result) {
9937
+ if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
9938
+ else if (parsed.options.jsonl !== true && result.found) {
9939
+ for (const event of result.events ?? []) {
9940
+ const record10 = eventRecord(event);
9941
+ const state2 = record10 ? recordState(record10) : "comment";
9942
+ ctx.out.log(
9943
+ `${event.id} ${event.type} ${state2}${record10?.revision ? `; r${record10.revision}` : ""} ${eventLabel(event)}`
9944
+ );
9945
+ }
9946
+ }
9947
+ return result;
9948
+ }
9949
+ async function pmWatch(ctx, parsed) {
9950
+ if (ctx.json && parsed.options.jsonl === true) {
9951
+ throw new Error("--json and --jsonl cannot be combined");
9952
+ }
9953
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
9954
+ if (!appId) throw new Error("pm watch needs --app <appId>");
9955
+ const sleep = ctx.sleep ?? ((ms) => new Promise((resolve13) => setTimeout(resolve13, ms)));
9956
+ const now = ctx.now ?? Date.now;
9957
+ const intervalMs = (positiveNumber(parsed, "interval", DEFAULT_INTERVAL_MS2 / 1e3) ?? DEFAULT_INTERVAL_MS2 / 1e3) * 1e3;
9958
+ const timeoutSeconds = positiveNumber(parsed, "timeout");
9959
+ const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
9960
+ const entity = stringOpt(parsed.options.entity);
9961
+ const action2 = stringOpt(parsed.options.action);
9962
+ const wantedState = stringOpt(parsed.options.state)?.toLowerCase();
9963
+ const by = stringOpt(parsed.options.by);
9964
+ const self = stringOpt(parsed.options.self);
9965
+ let cursor = stringOpt(parsed.options.cursor);
9966
+ let firstSuccess = true;
9967
+ let consecutiveFailures = 0;
9968
+ for (; ; ) {
9969
+ let current;
9970
+ try {
9971
+ current = await page(ctx, appId, cursor);
9972
+ consecutiveFailures = 0;
9973
+ } catch (error) {
9974
+ if (error instanceof PmWatchCheckpointError) {
9975
+ jsonl2(ctx, parsed, {
9976
+ type: "status",
9977
+ state: "checkpoint_required",
9978
+ retryable: false,
9979
+ ...error.cursor ? { cursor: error.cursor } : {},
9980
+ ...error.streamId ? { streamId: error.streamId } : {}
9981
+ });
9982
+ throw error;
9983
+ }
9984
+ if (!(error instanceof PmWatchRequestError) || !error.retryable) throw error;
9985
+ consecutiveFailures++;
9986
+ jsonl2(ctx, parsed, {
9987
+ type: "status",
9988
+ state: "degraded",
9989
+ retryable: true,
9990
+ attempt: consecutiveFailures,
9991
+ ...cursor ? { cursor } : {},
9992
+ ...error.status ? { status: error.status } : {}
9993
+ });
9994
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) throw error;
9995
+ if (deadline !== void 0 && now() >= deadline) {
9996
+ return report3(ctx, parsed, { found: false, cursor: cursor ?? "" });
9997
+ }
9998
+ const backoff = Math.min(
9999
+ MAX_BACKOFF_MS2,
10000
+ Math.min(intervalMs, 1e3) * 2 ** (consecutiveFailures - 1)
10001
+ );
10002
+ await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
10003
+ continue;
10004
+ }
10005
+ cursor = current.cursor;
10006
+ const baseline = firstSuccess && current.events.length === 0;
10007
+ if (baseline) {
10008
+ jsonl2(ctx, parsed, {
10009
+ type: "checkpoint",
10010
+ streamId: current.streamId,
10011
+ cursor,
10012
+ serverTime: current.serverTime
10013
+ });
10014
+ }
10015
+ firstSuccess = false;
10016
+ const matching = current.events.filter((event) => {
10017
+ const record10 = eventRecord(event);
10018
+ const state2 = record10 ? recordState(record10).toLowerCase() : "";
10019
+ return (!entity || event.payload.entityKind === entity) && (!action2 || event.payload.action === action2) && (!wantedState || state2 === wantedState || wantedState === "todo" && state2 === "ready") && (!by || event.actor.id === by) && (!self || event.actor.id !== self);
10020
+ });
10021
+ for (const event of matching) {
10022
+ jsonl2(ctx, parsed, {
10023
+ type: "event",
10024
+ streamId: current.streamId,
10025
+ eventId: event.id,
10026
+ cursor: event.cursor,
10027
+ event
10028
+ });
10029
+ }
10030
+ if (matching.length > 0) {
10031
+ jsonl2(ctx, parsed, {
10032
+ type: "checkpoint",
10033
+ streamId: current.streamId,
10034
+ cursor,
10035
+ serverTime: current.serverTime
10036
+ });
10037
+ return report3(ctx, parsed, { found: true, cursor, events: matching });
10038
+ }
10039
+ if (current.events.length > 0) {
10040
+ jsonl2(ctx, parsed, {
10041
+ type: "checkpoint",
10042
+ streamId: current.streamId,
10043
+ cursor,
10044
+ serverTime: current.serverTime
10045
+ });
10046
+ } else if (!baseline) {
10047
+ jsonl2(ctx, parsed, {
10048
+ type: "heartbeat",
10049
+ streamId: current.streamId,
10050
+ cursor,
10051
+ serverTime: current.serverTime
10052
+ });
10053
+ }
10054
+ if (current.hasMore) continue;
10055
+ if (deadline !== void 0 && now() >= deadline) {
10056
+ return report3(ctx, parsed, { found: false, cursor });
10057
+ }
10058
+ await sleep(
10059
+ deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()))
10060
+ );
10061
+ }
9732
10062
  }
9733
10063
 
9734
10064
  // src/pm-command.ts
@@ -9749,7 +10079,11 @@ var ACTION_OPTIONS = {
9749
10079
  done: ["mutation-id"],
9750
10080
  comment: ["body", "mutation-id"],
9751
10081
  comments: [],
9752
- rm: []
10082
+ rm: [],
10083
+ ready: ["goal", "alignment-decision", "execution", "description", "desc", "body", "acceptance", "expected-revision", "mutation-id"],
10084
+ claim: ["expected-revision", "mutation-id"],
10085
+ release: ["expected-revision", "mutation-id"],
10086
+ ref: []
9753
10087
  };
9754
10088
  var ENTITY_OPTIONS = {
9755
10089
  goal: {
@@ -9760,8 +10094,8 @@ var ENTITY_OPTIONS = {
9760
10094
  },
9761
10095
  task: {
9762
10096
  list: ["column", "goal", "assignee"],
9763
- add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
9764
- set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
10097
+ add: ["column", "goal", "alignment-decision", "execution", "assignee", "due", "description", "desc", "body", "acceptance"],
10098
+ set: ["title", "column", "rank", "goal", "alignment-decision", "execution", "assignee", "due", "description", "desc", "body", "acceptance", "expected-revision"],
9765
10099
  done: []
9766
10100
  },
9767
10101
  decision: {
@@ -9812,11 +10146,33 @@ async function buildContext2(parsed, deps) {
9812
10146
  json: parsed.options.json === true,
9813
10147
  // Preserve PM's existing cross-project default when a config is present;
9814
10148
  // only an explicit remote-agent environment or profile selects an app.
9815
- appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
10149
+ appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0,
10150
+ ...deps.sleep ? { sleep: deps.sleep } : {},
10151
+ ...deps.now ? { now: deps.now } : {}
9816
10152
  };
9817
10153
  }
9818
10154
  async function pmCommand(parsed, deps = {}) {
9819
10155
  const word = parsed.positionals[1] ?? "";
10156
+ if (word === "next") {
10157
+ assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
10158
+ return pmNext(await buildContext2(parsed, deps), parsed);
10159
+ }
10160
+ if (word === "watch") {
10161
+ assertArgs(parsed, [
10162
+ ...COMMON_OPTIONS,
10163
+ "app",
10164
+ "cursor",
10165
+ "interval",
10166
+ "timeout",
10167
+ "jsonl",
10168
+ "entity",
10169
+ "action",
10170
+ "state",
10171
+ "by",
10172
+ "self"
10173
+ ], 2);
10174
+ return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
10175
+ }
9820
10176
  if (word === "handoff") {
9821
10177
  assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
9822
10178
  return pmHandoff(await buildContext2(parsed, deps), parsed);
@@ -9827,6 +10183,9 @@ async function pmCommand(parsed, deps = {}) {
9827
10183
  const action2 = canonicalAction(requestedAction);
9828
10184
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
9829
10185
  assertArgs(parsed, allowedOptions(entity, action2), 4);
10186
+ if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
10187
+ throw new Error(`pm ${action2} is only valid for tasks`);
10188
+ }
9830
10189
  const ctx = await buildContext2(parsed, deps);
9831
10190
  const id = parsed.positionals[3];
9832
10191
  switch (action2) {
@@ -9846,6 +10205,12 @@ async function pmCommand(parsed, deps = {}) {
9846
10205
  return pmComments(ctx, entity, requireId2(id, action2));
9847
10206
  case "rm":
9848
10207
  return pmRemove(ctx, entity, requireId2(id, action2));
10208
+ case "ref":
10209
+ return pmReference(ctx, entity, requireId2(id, action2));
10210
+ case "ready":
10211
+ case "claim":
10212
+ case "release":
10213
+ return pmTaskLifecycle(ctx, requireId2(id, action2), action2, parsed);
9849
10214
  }
9850
10215
  }
9851
10216
 
@@ -10735,12 +11100,23 @@ var PM_ACTIONS = {
10735
11100
  done: {},
10736
11101
  comment: {},
10737
11102
  comments: {},
11103
+ ref: {},
10738
11104
  rm: {},
10739
11105
  delete: {}
10740
11106
  };
10741
- var PM_ENTITIES = Object.fromEntries(
10742
- ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
10743
- );
11107
+ var PM_TASK_ACTIONS = {
11108
+ ...PM_ACTIONS,
11109
+ ready: {},
11110
+ claim: {},
11111
+ release: {}
11112
+ };
11113
+ var PM_ENTITIES = {
11114
+ ...Object.fromEntries(
11115
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
11116
+ ),
11117
+ task: PM_TASK_ACTIONS,
11118
+ kanban: PM_TASK_ACTIONS
11119
+ };
10744
11120
  var COMMAND_SURFACE = {
10745
11121
  agent: { jobs: {}, retry: {} },
10746
11122
  admin: {
@@ -10793,7 +11169,9 @@ var COMMAND_SURFACE = {
10793
11169
  },
10794
11170
  pm: {
10795
11171
  ...PM_ENTITIES,
10796
- handoff: {}
11172
+ handoff: {},
11173
+ next: {},
11174
+ watch: {}
10797
11175
  },
10798
11176
  provision: {},
10799
11177
  runbook: {
@@ -10966,12 +11344,12 @@ async function call(ctx, method, path, body) {
10966
11344
  throw new Error(message2);
10967
11345
  }
10968
11346
  async function bySlug(ctx, slug) {
10969
- const page = await call(
11347
+ const page2 = await call(
10970
11348
  ctx,
10971
11349
  "GET",
10972
11350
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
10973
11351
  );
10974
- const found = page.records[0];
11352
+ const found = page2.records[0];
10975
11353
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
10976
11354
  return found;
10977
11355
  }
@@ -10985,11 +11363,11 @@ async function runbookList(ctx, all, query) {
10985
11363
  const params = new URLSearchParams();
10986
11364
  if (!all) params.set("app", ctx.appId);
10987
11365
  if (query) params.set("q", query);
10988
- const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
10989
- if (ctx.json) return ctx.out.log(JSON.stringify(page, null, 2));
10990
- if (!page.records.length) return ctx.out.log("(no runbooks)");
11366
+ const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11367
+ if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
11368
+ if (!page2.records.length) return ctx.out.log("(no runbooks)");
10991
11369
  ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
10992
- for (const r of page.records)
11370
+ for (const r of page2.records)
10993
11371
  ctx.out.log(
10994
11372
  [r.slug, r.status, `v${r.version}`, r.appId === PLATFORM_SCOPE ? "platform" : r.appId, stamp(r.updatedAt), r.title].join(" ")
10995
11373
  );
@@ -11044,12 +11422,12 @@ async function runbookVisibility(ctx, slug, visibility) {
11044
11422
  }
11045
11423
  async function runbookHistory(ctx, slug) {
11046
11424
  const runbook = await bySlug(ctx, slug);
11047
- const page = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
11048
- if (ctx.json) return ctx.out.log(JSON.stringify(page, null, 2));
11425
+ const page2 = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
11426
+ if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
11049
11427
  ctx.out.log(`${slug} is at v${runbook.version}`);
11050
- if (!page.records.length) return ctx.out.log("(no earlier versions)");
11428
+ if (!page2.records.length) return ctx.out.log("(no earlier versions)");
11051
11429
  ctx.out.log(["V", "WHEN", "BY", "NOTE"].join(" "));
11052
- for (const r of page.records)
11430
+ for (const r of page2.records)
11053
11431
  ctx.out.log([`v${r.version}`, stamp(r.createdAt), r.ownerEmail ?? "", r.note ?? ""].join(" "));
11054
11432
  }
11055
11433
  async function runbookRevert(ctx, slug, version) {
@@ -11124,12 +11502,12 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
11124
11502
  );
11125
11503
  }
11126
11504
  async function upsert(ctx, r, visibility) {
11127
- const page = await call(
11505
+ const page2 = await call(
11128
11506
  ctx,
11129
11507
  "GET",
11130
11508
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(r.slug)}&limit=1`
11131
11509
  );
11132
- const found = page.records[0];
11510
+ const found = page2.records[0];
11133
11511
  if (!found) {
11134
11512
  await call(ctx, "POST", "/runbook", {
11135
11513
  appId: ctx.appId,
@@ -11385,7 +11763,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
11385
11763
  return out;
11386
11764
  }
11387
11765
  var editHint = (slug, appId) => `odla-ai runbook edit ${slug}${appId === PLATFORM_SCOPE ? "" : ` --app ${appId}`} --note "<what changed>"`;
11388
- function report3(ctx, impacts) {
11766
+ function report4(ctx, impacts) {
11389
11767
  const covered = impacts.filter((i) => i.runbooks.length);
11390
11768
  ctx.out.log(
11391
11769
  `${impacts.length} changed surface${impacts.length === 1 ? "" : "s"}; ${covered.length} covered by a runbook. Reread each one and fix any step this change made wrong.`
@@ -11423,7 +11801,7 @@ async function runbookImpact(ctx, options, deps = {}) {
11423
11801
  }
11424
11802
  const impacts = await assessImpact(ctx, surfaces, options.all, options.limit ?? 4);
11425
11803
  if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
11426
- report3(ctx, impacts);
11804
+ report4(ctx, impacts);
11427
11805
  }
11428
11806
 
11429
11807
  // src/runbook-lint.ts
@@ -11461,17 +11839,17 @@ function lintRunbook(runbook, installed) {
11461
11839
  async function runbookLint(ctx, all) {
11462
11840
  const params = new URLSearchParams();
11463
11841
  if (!all) params.set("app", ctx.appId);
11464
- const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11842
+ const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11465
11843
  const installed = { "@odla-ai/cli": cliVersion() };
11466
- const findings = page.records.flatMap((runbook) => lintRunbook(runbook, installed));
11467
- if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page.records.length, findings }, null, 2));
11468
- if (!page.records.length) return ctx.out.log("(no runbooks in scope)");
11844
+ const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
11845
+ if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
11846
+ if (!page2.records.length) return ctx.out.log("(no runbooks in scope)");
11469
11847
  if (!findings.length) {
11470
11848
  return ctx.out.log(
11471
- `${page.records.length} runbook${page.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
11849
+ `${page2.records.length} runbook${page2.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
11472
11850
  );
11473
11851
  }
11474
- ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page.records.length} runbooks:`);
11852
+ ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page2.records.length} runbooks:`);
11475
11853
  for (const finding of findings) {
11476
11854
  const scope = finding.appId === PLATFORM_SCOPE ? "" : ` --app ${finding.appId}`;
11477
11855
  ctx.out.log("");
@@ -11534,12 +11912,12 @@ async function runbookAsk(ctx, question, all) {
11534
11912
  ctx.out.log(JSDOC_POINTER);
11535
11913
  }
11536
11914
  async function runbookComment(ctx, slug, body) {
11537
- const page = await call(
11915
+ const page2 = await call(
11538
11916
  ctx,
11539
11917
  "GET",
11540
11918
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
11541
11919
  );
11542
- const found = page.records[0];
11920
+ const found = page2.records[0];
11543
11921
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11544
11922
  await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
11545
11923
  ctx.out.log(`commented on ${slug} (v${found.version})`);
@@ -11591,12 +11969,12 @@ var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
11591
11969
 
11592
11970
  // src/runbook-edit-flow.ts
11593
11971
  async function editRunbook(ctx, slug, deps = {}) {
11594
- const page = await call(
11972
+ const page2 = await call(
11595
11973
  ctx,
11596
11974
  "GET",
11597
11975
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
11598
11976
  );
11599
- const found = page.records[0];
11977
+ const found = page2.records[0];
11600
11978
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11601
11979
  ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
11602
11980
  const body = await editText(found.body, slug, deps);
@@ -12017,31 +12395,31 @@ function printHostedJob(out, job, platform, appId) {
12017
12395
  url.searchParams.set("job", job.jobId);
12018
12396
  out.log(` Studio: ${url.toString()}`);
12019
12397
  }
12020
- function printHostedReport(out, report4) {
12021
- out.log(`security report ${report4.jobId}: ${report4.repository}@${report4.revision}`);
12022
- out.log(` coverage: ${report4.coverageStatus} cells=${report4.metrics.coverageCells} shallow=${report4.metrics.shallowCells} blocked=${report4.metrics.blockedCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
12023
- out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates} rejected=${report4.metrics.rejected}`);
12024
- out.log(` discovery: ${report4.provenance.discovery?.provider ?? "unknown"}/${report4.provenance.discovery?.model ?? "unknown"}`);
12025
- out.log(` validation: ${report4.provenance.validation?.provider ?? "unknown"}/${report4.provenance.validation?.model ?? "unknown"} independent=${String(report4.provenance.independentValidation)}`);
12026
- for (const finding of report4.findings) {
12398
+ function printHostedReport(out, report5) {
12399
+ out.log(`security report ${report5.jobId}: ${report5.repository}@${report5.revision}`);
12400
+ out.log(` coverage: ${report5.coverageStatus} cells=${report5.metrics.coverageCells} shallow=${report5.metrics.shallowCells} blocked=${report5.metrics.blockedCells} unscheduled=${report5.metrics.unscheduledCells} budget_exhausted=${report5.metrics.budgetExhaustedCells}`);
12401
+ out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates} rejected=${report5.metrics.rejected}`);
12402
+ out.log(` discovery: ${report5.provenance.discovery?.provider ?? "unknown"}/${report5.provenance.discovery?.model ?? "unknown"}`);
12403
+ out.log(` validation: ${report5.provenance.validation?.provider ?? "unknown"}/${report5.provenance.validation?.model ?? "unknown"} independent=${String(report5.provenance.independentValidation)}`);
12404
+ for (const finding of report5.findings) {
12027
12405
  const location = finding.locations[0];
12028
12406
  out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
12029
12407
  }
12030
- for (const limitation of report4.limitations) out.log(` limitation: ${limitation}`);
12408
+ for (const limitation of report5.limitations) out.log(` limitation: ${limitation}`);
12031
12409
  }
12032
- function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
12410
+ function enforceHostedReportGate(report5, parsed, out, emitSuccess) {
12033
12411
  const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
12034
12412
  const candidateValue = parsed.options["fail-on-candidates"];
12035
12413
  const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
12036
12414
  const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
12037
- const confirmed = report4.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
12038
- const leads = failOnCandidates ? report4.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
12039
- const incomplete = report4.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
12415
+ const confirmed = report5.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
12416
+ const leads = failOnCandidates ? report5.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
12417
+ const incomplete = report5.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
12040
12418
  if (confirmed.length || leads.length || incomplete) {
12041
- throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report4.coverageStatus}` : ""}`);
12419
+ throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report5.coverageStatus}` : ""}`);
12042
12420
  }
12043
12421
  if (emitSuccess) {
12044
- out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
12422
+ out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report5.coverageStatus}. This is not proof that the application is secure.`);
12045
12423
  }
12046
12424
  }
12047
12425
  function printHostedSecurityPlanRoute(out, label, route2) {
@@ -12124,17 +12502,17 @@ async function runHostedSecurity(options) {
12124
12502
  allowNetwork: false
12125
12503
  }
12126
12504
  });
12127
- const report4 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
12128
- await (0, import_node3.writeSecurityArtifacts)(output, report4);
12129
- const reportDigest = await (0, import_security.securityFingerprint)(report4);
12505
+ const report5 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
12506
+ await (0, import_node3.writeSecurityArtifacts)(output, report5);
12507
+ const reportDigest = await (0, import_security.securityFingerprint)(report5);
12130
12508
  await hosted.complete({
12131
12509
  reportDigest,
12132
- coverageStatus: report4.coverageStatus,
12133
- confirmed: report4.metrics.confirmed,
12134
- candidates: report4.metrics.candidates
12510
+ coverageStatus: report5.coverageStatus,
12511
+ confirmed: report5.metrics.confirmed,
12512
+ candidates: report5.metrics.candidates
12135
12513
  }, { signal: options.signal });
12136
- printSummary(options.stdout ?? console, appId, env, hosted.run, report4, output);
12137
- return Object.freeze({ report: report4, run: hosted.run, output });
12514
+ printSummary(options.stdout ?? console, appId, env, hosted.run, report5, output);
12515
+ return Object.freeze({ report: report5, run: hosted.run, output });
12138
12516
  }
12139
12517
  function selectEnv(requested, declared, configPath, rootDir) {
12140
12518
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
@@ -12160,14 +12538,14 @@ function profileFor(name, maxHuntTasks) {
12160
12538
  if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
12161
12539
  return { ...profile, maxHuntTasks };
12162
12540
  }
12163
- function printSummary(out, appId, env, run, report4, output) {
12164
- const complete = report4.coverage.filter((cell) => cell.state === "complete").length;
12541
+ function printSummary(out, appId, env, run, report5, output) {
12542
+ const complete = report5.coverage.filter((cell) => cell.state === "complete").length;
12165
12543
  out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
12166
12544
  out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
12167
12545
  out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
12168
- out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
12169
- if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
12170
- out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
12546
+ out.log(` coverage: ${report5.coverageStatus} ${complete}/${report5.coverage.length} blocked=${report5.metrics.blockedCells} shallow=${report5.metrics.shallowCells} unscheduled=${report5.metrics.unscheduledCells} budget_exhausted=${report5.metrics.budgetExhaustedCells}`);
12547
+ if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
12548
+ out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
12171
12549
  out.log(` report: ${(0, import_node_path19.resolve)(output, "REPORT.md")}`);
12172
12550
  }
12173
12551
  function formatBudget(usage) {
@@ -12414,13 +12792,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
12414
12792
  }
12415
12793
  throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
12416
12794
  }
12417
- const report4 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
12795
+ const report5 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
12418
12796
  if (parsed.options.json === true) {
12419
- context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report4 }, null, 2));
12797
+ context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report5 }, null, 2));
12420
12798
  } else {
12421
- printHostedReport(context.stdout, report4);
12799
+ printHostedReport(context.stdout, report5);
12422
12800
  }
12423
- enforceHostedReportGate(report4, parsed, context.stdout, parsed.options.json !== true);
12801
+ enforceHostedReportGate(report5, parsed, context.stdout, parsed.options.json !== true);
12424
12802
  }
12425
12803
  async function runLocalSecurityCommand(parsed, dependencies) {
12426
12804
  if (parsed.options.source === true) {
@@ -12487,13 +12865,13 @@ async function runLocalSecurityCommand(parsed, dependencies) {
12487
12865
  });
12488
12866
  enforceLocalGate(result.report, parsed);
12489
12867
  }
12490
- function enforceLocalGate(report4, parsed) {
12868
+ function enforceLocalGate(report5, parsed) {
12491
12869
  const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
12492
12870
  const candidateValue = parsed.options["fail-on-candidates"];
12493
12871
  const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
12494
- const confirmed = (0, import_security2.findingsAtOrAbove)(report4, failOn);
12495
- const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(report4, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
12496
- const incomplete = report4.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
12872
+ const confirmed = (0, import_security2.findingsAtOrAbove)(report5, failOn);
12873
+ const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(report5, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
12874
+ const incomplete = report5.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
12497
12875
  if (confirmed.length || leads.length || incomplete) {
12498
12876
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
12499
12877
  }
@@ -12532,9 +12910,9 @@ async function securityCommand(parsed, dependencies) {
12532
12910
  assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
12533
12911
  const jobId = requiredSecurityPositional(parsed, 2, "job id");
12534
12912
  const context = await hostedSecurityContext(parsed, dependencies);
12535
- const report4 = await getHostedSecurityReport({ ...context, jobId });
12536
- if (parsed.options.json === true) context.stdout.log(JSON.stringify(report4, null, 2));
12537
- else printHostedReport(context.stdout, report4);
12913
+ const report5 = await getHostedSecurityReport({ ...context, jobId });
12914
+ if (parsed.options.json === true) context.stdout.log(JSON.stringify(report5, null, 2));
12915
+ else printHostedReport(context.stdout, report5);
12538
12916
  return;
12539
12917
  }
12540
12918
  if (sub !== "run") {