@odla-ai/cli 0.27.3 → 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.
@@ -232,6 +232,7 @@ function handshakeUrl(platformUrl, userCode) {
232
232
 
233
233
  // src/token.ts
234
234
  import { collectToken, OdlaError, requestToken } from "@odla-ai/db";
235
+ import { createHash } from "crypto";
235
236
  import process5 from "process";
236
237
 
237
238
  // src/handshake-state.ts
@@ -360,6 +361,7 @@ async function freshHandshake(ctx, waitMs) {
360
361
  endpoint: ctx.cfg.platformUrl,
361
362
  email: ctx.email,
362
363
  label: `${ctx.cfg.app.id} provisioner`,
364
+ agentHandle: projectAgentHandle(ctx.cfg.app.id),
363
365
  projectIds: [ctx.cfg.app.id],
364
366
  fetch: ctx.doFetch,
365
367
  waitMs,
@@ -395,6 +397,12 @@ async function freshHandshake(ctx, waitMs) {
395
397
  stopReminder?.();
396
398
  }
397
399
  }
400
+ function projectAgentHandle(appId) {
401
+ const candidate = /^[a-z]/.test(appId) ? appId : `app-${appId}`;
402
+ if (candidate.length <= 32) return candidate;
403
+ const digest = createHash("sha256").update(appId).digest("hex").slice(0, 8);
404
+ return `${candidate.slice(0, 23).replace(/-+$/, "")}-${digest}`;
405
+ }
398
406
  function stillPending(pending, email) {
399
407
  return new OdlaError(
400
408
  "handshake_pending",
@@ -1482,8 +1490,8 @@ async function calendarCalendars(options) {
1482
1490
  async function calendarConnect(options) {
1483
1491
  const { cfg, ctx, out } = await lifecycleContext(options);
1484
1492
  productionConsent(ctx.env, options.yes, "connect calendar");
1485
- const page = calendarBookingPageUrl(cfg, ctx.env);
1486
- const applied = page === void 0 ? await readCalendarStatus(ctx) : await applyCalendarSettings(ctx, page);
1493
+ const page2 = calendarBookingPageUrl(cfg, ctx.env);
1494
+ const applied = page2 === void 0 ? await readCalendarStatus(ctx) : await applyCalendarSettings(ctx, page2);
1487
1495
  const connectOptions = connectionOptions(options, out);
1488
1496
  return await continueConnectedCalendar(ctx, applied, connectOptions) ?? connectWithContext(ctx, connectOptions);
1489
1497
  }
@@ -1830,12 +1838,12 @@ import {
1830
1838
  import { readFileSync as readFileSync4 } from "fs";
1831
1839
 
1832
1840
  // src/config-reconcile-digest.ts
1833
- import { createHash } from "crypto";
1841
+ import { createHash as createHash2 } from "crypto";
1834
1842
  function canonicalJson(value2) {
1835
1843
  return JSON.stringify(canonicalValue(value2));
1836
1844
  }
1837
1845
  function configDigest(value2) {
1838
- return `sha256:${createHash("sha256").update(canonicalJson(value2)).digest("hex")}`;
1846
+ return `sha256:${createHash2("sha256").update(canonicalJson(value2)).digest("hex")}`;
1839
1847
  }
1840
1848
  function canonicalValue(value2) {
1841
1849
  if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
@@ -3294,9 +3302,10 @@ For work that creates an odla app or adds odla services, read and follow
3294
3302
  \`.agents/skills/odla-migrate/SKILL.md\`. For production telemetry triage, use
3295
3303
  \`.agents/skills/odla-o11y-debug/SKILL.md\`.
3296
3304
 
3297
- Track the work in odla's PM as you go \u2014 record decisions when you make them, file
3298
- bugs when you notice them, and move tasks when the work moves
3299
- (\`npx @odla-ai/cli pm task list\`). The conventions and the full command set are
3305
+ Track the work in odla's PM as you go. Before project-mutating work, run
3306
+ \`npx @odla-ai/cli pm next --app <appId>\`, confirm alignment to an open goal,
3307
+ and atomically claim a refined Ready task. Record decisions when you make them
3308
+ and file bugs when you notice them. The conventions and the full command set are
3300
3309
  in \`.agents/skills/odla/references/pm.md\`.
3301
3310
 
3302
3311
  The setup runbooks and their references are installed in this repository, pinned
@@ -3929,7 +3938,7 @@ var CODE_BUILD_RECIPES = Object.freeze([{
3929
3938
 
3930
3939
  // src/code-images.ts
3931
3940
  import { spawn as spawn3 } from "child_process";
3932
- import { createHash as createHash2 } from "crypto";
3941
+ import { createHash as createHash3 } from "crypto";
3933
3942
  import { copyFile, mkdtemp, readFile, rm, writeFile } from "fs/promises";
3934
3943
  import { tmpdir } from "os";
3935
3944
  import { join as join8 } from "path";
@@ -3988,7 +3997,7 @@ async function embeddedPiImageName() {
3988
3997
  const bundle = await readFile(embeddedPiAssetPath()).catch(() => {
3989
3998
  throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
3990
3999
  });
3991
- return `odla-ai/pi-agent:embedded-sha256-${createHash2("sha256").update(bundle).digest("hex")}`;
4000
+ return `odla-ai/pi-agent:embedded-sha256-${createHash3("sha256").update(bundle).digest("hex")}`;
3992
4001
  }
3993
4002
  async function buildEmbeddedPiImage(engine, image, run) {
3994
4003
  const context = await mkdtemp(join8(tmpdir(), "odla-code-pi-"));
@@ -4611,7 +4620,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
4611
4620
  }
4612
4621
 
4613
4622
  // ../harness/dist/chunk-GMVZ4LZH.js
4614
- import { createHash as createHash3 } from "crypto";
4623
+ import { createHash as createHash4 } from "crypto";
4615
4624
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
4616
4625
  import { relative as relative4, resolve as resolve7 } from "path";
4617
4626
 
@@ -4675,7 +4684,7 @@ function dependenciesOf(values, influence = "data") {
4675
4684
  return [...unique3.values()];
4676
4685
  }
4677
4686
 
4678
- // ../camel/dist/chunk-S7EVNA2U.js
4687
+ // ../camel/dist/chunk-4DQ6BIHP.js
4679
4688
  var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
4680
4689
  var authenticCamelValues = /* @__PURE__ */ new WeakSet();
4681
4690
  function isCamelValue(value2) {
@@ -4687,13 +4696,13 @@ function isSafe(value2) {
4687
4696
  function isUnsafe(value2) {
4688
4697
  return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
4689
4698
  }
4690
- function createSafeInternal(value2, safeBasis, metadata2) {
4699
+ function createSafeInternal(value2, safeBasis, metadata2, extra) {
4691
4700
  return createValue(value2, {
4692
4701
  schemaVersion: 1,
4693
4702
  promptSafety: "safe",
4694
4703
  safeBasis,
4695
4704
  ...copyMetadata(metadata2)
4696
- });
4705
+ }, extra);
4697
4706
  }
4698
4707
  function createUnsafeInternal(value2, metadata2) {
4699
4708
  return createValue(value2, {
@@ -4702,8 +4711,8 @@ function createUnsafeInternal(value2, metadata2) {
4702
4711
  ...copyMetadata(metadata2)
4703
4712
  });
4704
4713
  }
4705
- function createValue(value2, label) {
4706
- const result = { value: value2, label: Object.freeze(label) };
4714
+ function createValue(value2, label, extra) {
4715
+ const result = { value: value2, label: Object.freeze(label), ...extra };
4707
4716
  Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
4708
4717
  authenticCamelValues.add(result);
4709
4718
  return Object.freeze(result);
@@ -4964,7 +4973,7 @@ import { dirname as dirname6, join as join23, resolve as resolve32, sep as sep23
4964
4973
  import { readFile as readFile22, readdir as readdir22, stat as stat2 } from "fs/promises";
4965
4974
  import { relative as relative22, resolve as resolve42 } from "path";
4966
4975
 
4967
- // ../camel/dist/chunk-LAXU2AVK.js
4976
+ // ../camel/dist/chunk-4EIRFS3A.js
4968
4977
  function conversionPolicyDigest(policy) {
4969
4978
  return sha256Hex(canonicalJson2(policy));
4970
4979
  }
@@ -5052,10 +5061,18 @@ async function convert(source, policy, value2, counts) {
5052
5061
  const count = counts.get(countKey) ?? 0;
5053
5062
  if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
5054
5063
  counts.set(countKey, count + 1);
5064
+ const conversionRecordId = await sha256Hex(canonicalJson2({ conversionId: policy.conversionId, digest: policy.digest, source: sourceKey, ordinal: count }));
5065
+ const datumId = await sha256Hex(canonicalJson2({ conversionRecordId, value: value2 }));
5055
5066
  return createSafeInternal(value2, "atomic_conversion", {
5056
5067
  readers: source.label.readers,
5057
5068
  provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
5058
5069
  dependencies: dependenciesOf([source])
5070
+ }, {
5071
+ datumId,
5072
+ kind: policy.output.kind,
5073
+ conversionId: policy.conversionId,
5074
+ conversionDigest: policy.digest,
5075
+ conversionRecordId
5059
5076
  });
5060
5077
  }
5061
5078
  function validatePolicyShape(policy) {
@@ -5120,7 +5137,7 @@ function missingPolicy() {
5120
5137
  throw new CamelError("conversion_rejected", "Conversion policy is not registered.");
5121
5138
  }
5122
5139
 
5123
- // ../camel/dist/chunk-P2WQIAG6.js
5140
+ // ../camel/dist/chunk-VEAUXH4F.js
5124
5141
  function createCamelIngress(constants2 = []) {
5125
5142
  const byId = /* @__PURE__ */ new Map();
5126
5143
  for (const item of constants2) {
@@ -5257,7 +5274,7 @@ async function digestStagedWorkspace(root, limits) {
5257
5274
  }
5258
5275
  };
5259
5276
  await walk(resolve7(root));
5260
- const hash = createHash3("sha256");
5277
+ const hash = createHash4("sha256");
5261
5278
  let bytes = 0;
5262
5279
  for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
5263
5280
  const content2 = await readFile2(file.target);
@@ -6896,7 +6913,7 @@ var CodePiRuntimeEngine = class {
6896
6913
 
6897
6914
  // src/code-local-source.ts
6898
6915
  import { execFile as execFile3 } from "child_process";
6899
- import { createHash as createHash4 } from "crypto";
6916
+ import { createHash as createHash5 } from "crypto";
6900
6917
  var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
6901
6918
  async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
6902
6919
  const headCommitSha = await readHead(cwd);
@@ -6947,7 +6964,7 @@ async function readGitHead(cwd) {
6947
6964
  return value2;
6948
6965
  }
6949
6966
  function digestText(value2) {
6950
- return `sha256:${createHash4("sha256").update(value2).digest("hex")}`;
6967
+ return `sha256:${createHash5("sha256").update(value2).digest("hex")}`;
6951
6968
  }
6952
6969
 
6953
6970
  // src/code-connect.ts
@@ -7492,12 +7509,23 @@ var PM_ACTIONS = {
7492
7509
  done: {},
7493
7510
  comment: {},
7494
7511
  comments: {},
7512
+ ref: {},
7495
7513
  rm: {},
7496
7514
  delete: {}
7497
7515
  };
7498
- var PM_ENTITIES = Object.fromEntries(
7499
- ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
7500
- );
7516
+ var PM_TASK_ACTIONS = {
7517
+ ...PM_ACTIONS,
7518
+ ready: {},
7519
+ claim: {},
7520
+ release: {}
7521
+ };
7522
+ var PM_ENTITIES = {
7523
+ ...Object.fromEntries(
7524
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
7525
+ ),
7526
+ task: PM_TASK_ACTIONS,
7527
+ kanban: PM_TASK_ACTIONS
7528
+ };
7501
7529
  var COMMAND_SURFACE = {
7502
7530
  agent: { jobs: {}, retry: {} },
7503
7531
  admin: {
@@ -7550,7 +7578,9 @@ var COMMAND_SURFACE = {
7550
7578
  },
7551
7579
  pm: {
7552
7580
  ...PM_ENTITIES,
7553
- handoff: {}
7581
+ handoff: {},
7582
+ next: {},
7583
+ watch: {}
7554
7584
  },
7555
7585
  provision: {},
7556
7586
  runbook: {
@@ -7694,17 +7724,17 @@ async function runHostedSecurity(options) {
7694
7724
  allowNetwork: false
7695
7725
  }
7696
7726
  });
7697
- const report4 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
7698
- await writeSecurityArtifacts(output, report4);
7699
- const reportDigest = await securityFingerprint(report4);
7727
+ const report5 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
7728
+ await writeSecurityArtifacts(output, report5);
7729
+ const reportDigest = await securityFingerprint(report5);
7700
7730
  await hosted.complete({
7701
7731
  reportDigest,
7702
- coverageStatus: report4.coverageStatus,
7703
- confirmed: report4.metrics.confirmed,
7704
- candidates: report4.metrics.candidates
7732
+ coverageStatus: report5.coverageStatus,
7733
+ confirmed: report5.metrics.confirmed,
7734
+ candidates: report5.metrics.candidates
7705
7735
  }, { signal: options.signal });
7706
- printSummary(options.stdout ?? console, appId, env, hosted.run, report4, output);
7707
- return Object.freeze({ report: report4, run: hosted.run, output });
7736
+ printSummary(options.stdout ?? console, appId, env, hosted.run, report5, output);
7737
+ return Object.freeze({ report: report5, run: hosted.run, output });
7708
7738
  }
7709
7739
  function selectEnv(requested, declared, configPath, rootDir) {
7710
7740
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
@@ -7730,14 +7760,14 @@ function profileFor(name, maxHuntTasks) {
7730
7760
  if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
7731
7761
  return { ...profile, maxHuntTasks };
7732
7762
  }
7733
- function printSummary(out, appId, env, run, report4, output) {
7734
- const complete = report4.coverage.filter((cell) => cell.state === "complete").length;
7763
+ function printSummary(out, appId, env, run, report5, output) {
7764
+ const complete = report5.coverage.filter((cell) => cell.state === "complete").length;
7735
7765
  out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
7736
7766
  out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
7737
7767
  out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
7738
- 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}`);
7739
- if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
7740
- out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
7768
+ 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}`);
7769
+ if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
7770
+ out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
7741
7771
  out.log(` report: ${resolve9(output, "REPORT.md")}`);
7742
7772
  }
7743
7773
  function formatBudget(usage) {
@@ -9454,16 +9484,22 @@ Usage:
9454
9484
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
9455
9485
  odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
9456
9486
  odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
9457
- odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
9487
+ odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
9458
9488
  odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
9459
9489
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
9460
9490
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
9461
- 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]
9491
+ 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]
9492
+ odla-ai pm next --app <id> [--json]
9493
+ 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]
9494
+ 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]
9495
+ odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
9496
+ odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
9462
9497
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
9463
9498
  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]
9464
9499
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
9500
+ odla-ai pm <goal|task|decision|bug> ref <id> [--json]
9465
9501
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
9466
- 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]
9502
+ 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]
9467
9503
  odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
9468
9504
  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]
9469
9505
  odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
@@ -9782,11 +9818,11 @@ async function discussList(ctx, parsed) {
9782
9818
  if (value2) query.set(param, value2);
9783
9819
  }
9784
9820
  const qs = query.toString();
9785
- const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
9786
- emit(ctx, page, () => {
9787
- ctx.out.log(`topics \u2014 ${page.topics.length} of ${page.total}`);
9821
+ const page2 = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
9822
+ emit(ctx, page2, () => {
9823
+ ctx.out.log(`topics \u2014 ${page2.topics.length} of ${page2.total}`);
9788
9824
  ctx.out.log("id state app replies subject");
9789
- for (const topic of page.topics) {
9825
+ for (const topic of page2.topics) {
9790
9826
  ctx.out.log(
9791
9827
  `${topic.id} ${state(topic)} ${topic.appId ?? ""} ${topic.replyCount} ${topic.subject}`
9792
9828
  );
@@ -9801,11 +9837,11 @@ async function discussRead(ctx, id, parsed) {
9801
9837
  limit: requestedLimit ?? "200",
9802
9838
  offset: requestedOffset ?? "0"
9803
9839
  });
9804
- const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
9840
+ const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
9805
9841
  emit(
9806
9842
  ctx,
9807
- page,
9808
- () => renderDiscussRead(ctx, page.topic, page.posts, page)
9843
+ page2,
9844
+ () => renderDiscussRead(ctx, page2.topic, page2.posts, page2)
9809
9845
  );
9810
9846
  return;
9811
9847
  }
@@ -9818,20 +9854,20 @@ async function discussRead(ctx, id, parsed) {
9818
9854
  let topic = null;
9819
9855
  let offset = 0;
9820
9856
  for (; ; ) {
9821
- const page = await request(
9857
+ const page2 = await request(
9822
9858
  ctx,
9823
9859
  "GET",
9824
9860
  `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
9825
9861
  );
9826
- topic = page.topic;
9827
- for (const post of page.posts) posts.set(post.id, post);
9828
- mergeDiscussPrincipals(projection, page);
9862
+ topic = page2.topic;
9863
+ for (const post of page2.posts) posts.set(post.id, post);
9864
+ mergeDiscussPrincipals(projection, page2);
9829
9865
  if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
9830
- if (!page.page?.hasMore) break;
9831
- if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
9866
+ if (!page2.page?.hasMore) break;
9867
+ if (page2.page.nextOffset === null || page2.page.nextOffset <= offset) {
9832
9868
  throw new Error("discuss read failed: registry returned a non-advancing post page");
9833
9869
  }
9834
- offset = page.page.nextOffset;
9870
+ offset = page2.page.nextOffset;
9835
9871
  }
9836
9872
  const ordered = [...posts.values()].sort(
9837
9873
  (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
@@ -9999,9 +10035,9 @@ async function discussWatch(ctx, topicId, parsed) {
9999
10035
  let firstSuccess = true;
10000
10036
  let consecutiveFailures = 0;
10001
10037
  for (; ; ) {
10002
- let page;
10038
+ let page2;
10003
10039
  try {
10004
- page = await getWatchPage(ctx, requestPath(topicId, app, cursor));
10040
+ page2 = await getWatchPage(ctx, requestPath(topicId, app, cursor));
10005
10041
  consecutiveFailures = 0;
10006
10042
  } catch (error) {
10007
10043
  if (!(error instanceof WatchRequestError) || !error.retryable) {
@@ -10037,25 +10073,25 @@ async function discussWatch(ctx, topicId, parsed) {
10037
10073
  await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
10038
10074
  continue;
10039
10075
  }
10040
- cursor = page.cursor;
10041
- const baseline = firstSuccess && page.events.length === 0;
10076
+ cursor = page2.cursor;
10077
+ const baseline = firstSuccess && page2.events.length === 0;
10042
10078
  if (baseline) {
10043
10079
  jsonl(ctx, parsed, {
10044
10080
  type: "checkpoint",
10045
- streamId: page.streamId,
10081
+ streamId: page2.streamId,
10046
10082
  cursor,
10047
- serverTime: page.serverTime
10083
+ serverTime: page2.serverTime
10048
10084
  });
10049
10085
  }
10050
10086
  firstSuccess = false;
10051
- const matching = page.events.filter((event) => {
10087
+ const matching = page2.events.filter((event) => {
10052
10088
  if (topicId && (event.type !== "message" || event.action !== "created")) return false;
10053
10089
  return (!by || event.actor.id === by) && (!self || event.actor.id !== self);
10054
10090
  });
10055
10091
  for (const event of matching) {
10056
10092
  jsonl(ctx, parsed, {
10057
10093
  type: "event",
10058
- streamId: page.streamId,
10094
+ streamId: page2.streamId,
10059
10095
  eventId: event.id,
10060
10096
  cursor: event.cursor,
10061
10097
  event
@@ -10064,9 +10100,9 @@ async function discussWatch(ctx, topicId, parsed) {
10064
10100
  if (matching.length > 0) {
10065
10101
  jsonl(ctx, parsed, {
10066
10102
  type: "checkpoint",
10067
- streamId: page.streamId,
10103
+ streamId: page2.streamId,
10068
10104
  cursor,
10069
- serverTime: page.serverTime
10105
+ serverTime: page2.serverTime
10070
10106
  });
10071
10107
  const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
10072
10108
  const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
@@ -10074,28 +10110,28 @@ async function discussWatch(ctx, topicId, parsed) {
10074
10110
  found: true,
10075
10111
  cursor,
10076
10112
  events: matching,
10077
- ...page.authors ? { authors: page.authors } : {},
10078
- ...page.principals ? { principals: page.principals } : {},
10113
+ ...page2.authors ? { authors: page2.authors } : {},
10114
+ ...page2.principals ? { principals: page2.principals } : {},
10079
10115
  ...posts && posts.length > 0 ? { posts } : {},
10080
10116
  ...topics && topics.length > 0 ? { topics } : {}
10081
10117
  });
10082
10118
  }
10083
- if (page.events.length > 0) {
10119
+ if (page2.events.length > 0) {
10084
10120
  jsonl(ctx, parsed, {
10085
10121
  type: "checkpoint",
10086
- streamId: page.streamId,
10122
+ streamId: page2.streamId,
10087
10123
  cursor,
10088
- serverTime: page.serverTime
10124
+ serverTime: page2.serverTime
10089
10125
  });
10090
10126
  } else if (!baseline) {
10091
10127
  jsonl(ctx, parsed, {
10092
10128
  type: "heartbeat",
10093
- streamId: page.streamId,
10129
+ streamId: page2.streamId,
10094
10130
  cursor,
10095
- serverTime: page.serverTime
10131
+ serverTime: page2.serverTime
10096
10132
  });
10097
10133
  }
10098
- if (page.hasMore) continue;
10134
+ if (page2.hasMore) continue;
10099
10135
  if (deadline !== void 0 && now() >= deadline) {
10100
10136
  return report2(ctx, parsed, { found: false, cursor });
10101
10137
  }
@@ -10214,7 +10250,13 @@ async function discussCommand(parsed, deps = {}) {
10214
10250
  }
10215
10251
  }
10216
10252
 
10217
- // src/pm-actions.ts
10253
+ // src/pm-action-core.ts
10254
+ var DONE = {
10255
+ goal: { status: "met", currentPct: 100 },
10256
+ task: { column: "done" },
10257
+ decision: { status: "accepted" },
10258
+ bug: { status: "fixed" }
10259
+ };
10218
10260
  var writeMutationId2 = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
10219
10261
  var FIELD_MAP = {
10220
10262
  title: { key: "title" },
@@ -10230,22 +10272,27 @@ var FIELD_MAP = {
10230
10272
  body: { key: "body" },
10231
10273
  target: { key: "targetPct", num: true },
10232
10274
  description: { key: "description" },
10233
- desc: { key: "description" }
10234
- };
10235
- var DONE = {
10236
- goal: { status: "met", currentPct: 100 },
10237
- task: { column: "done" },
10238
- decision: { status: "accepted" },
10239
- bug: { status: "fixed" }
10275
+ desc: { key: "description" },
10276
+ acceptance: { key: "acceptanceCriteria" },
10277
+ "alignment-decision": { key: "alignmentDecisionId" },
10278
+ execution: { key: "executionMode" },
10279
+ "expected-revision": { key: "expectedRevision", num: true }
10240
10280
  };
10241
10281
  async function pmRequest(ctx, method, path, body) {
10242
- const res = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
10282
+ const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
10243
10283
  method,
10244
- headers: { authorization: `Bearer ${ctx.token}`, "content-type": "application/json" },
10284
+ headers: {
10285
+ authorization: `Bearer ${ctx.token}`,
10286
+ "content-type": "application/json"
10287
+ },
10245
10288
  body: body === void 0 ? void 0 : JSON.stringify(body)
10246
10289
  });
10247
- const data = await res.json().catch(() => ({}));
10248
- if (!res.ok) throw new Error(`pm ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
10290
+ const data = await response2.json().catch(() => ({}));
10291
+ if (!response2.ok) {
10292
+ throw new Error(
10293
+ `pm ${method} ${path} failed: ${data.error ?? `registry returned ${response2.status}`}`
10294
+ );
10295
+ }
10249
10296
  return data;
10250
10297
  }
10251
10298
  function collectFields(parsed, allowClear) {
@@ -10268,20 +10315,32 @@ function collectEntityFields(entity, parsed, allowClear) {
10268
10315
  if (fields.description === void 0) fields.description = fields.body;
10269
10316
  delete fields.body;
10270
10317
  }
10318
+ if (entity === "task" && fields.column === "ready") fields.column = "todo";
10271
10319
  return fields;
10272
10320
  }
10273
- function statusCol(entity, r) {
10274
- if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
10275
- if (entity === "task") return String(r.column ?? "");
10276
- return String(r.status ?? "");
10321
+ function statusCol(entity, record10) {
10322
+ if (entity === "bug") return `${record10.status ?? ""}/${record10.severity ?? ""}`;
10323
+ if (entity === "task") {
10324
+ const state2 = record10.column === "todo" ? "ready" : String(record10.column ?? "");
10325
+ return record10.revision ? `${state2}; r${record10.revision}` : state2;
10326
+ }
10327
+ return String(record10.status ?? "");
10328
+ }
10329
+ function referenceMarkup(entity, record10) {
10330
+ const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
10331
+ return `@[${label}](pm:${entity}/${record10.id})`;
10277
10332
  }
10278
- function printRecord(ctx, entity, r) {
10279
- ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
10333
+ function printRecord(ctx, entity, record10) {
10334
+ ctx.out.log(
10335
+ `${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${record10.title ?? ""}`
10336
+ );
10280
10337
  }
10281
10338
  function emit2(ctx, value2, human) {
10282
10339
  if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
10283
10340
  else human();
10284
10341
  }
10342
+
10343
+ // src/pm-actions.ts
10285
10344
  async function pmList(ctx, entity, parsed) {
10286
10345
  const q = new URLSearchParams();
10287
10346
  const filters = {
@@ -10295,7 +10354,8 @@ async function pmList(ctx, entity, parsed) {
10295
10354
  const app = stringOpt(parsed.options.app) ?? ctx.appId;
10296
10355
  if (app) q.set("app", app);
10297
10356
  for (const [flag, param] of Object.entries(filters)) {
10298
- const v = stringOpt(parsed.options[flag]);
10357
+ const raw = stringOpt(parsed.options[flag]);
10358
+ const v = entity === "task" && flag === "column" && raw === "ready" ? "todo" : raw;
10299
10359
  if (v) q.set(param, v);
10300
10360
  }
10301
10361
  for (const opt of ["q", "limit", "offset"]) {
@@ -10303,11 +10363,11 @@ async function pmList(ctx, entity, parsed) {
10303
10363
  if (v) q.set(opt, v);
10304
10364
  }
10305
10365
  const qs = q.toString();
10306
- const page = await pmRequest(ctx, "GET", `/${entity}${qs ? `?${qs}` : ""}`);
10307
- emit2(ctx, page, () => {
10308
- ctx.out.log(`${entity} \u2014 ${page.records.length} of ${page.total}`);
10366
+ const page2 = await pmRequest(ctx, "GET", `/${entity}${qs ? `?${qs}` : ""}`);
10367
+ emit2(ctx, page2, () => {
10368
+ ctx.out.log(`${entity} \u2014 ${page2.records.length} of ${page2.total}`);
10309
10369
  ctx.out.log("id state app title");
10310
- for (const r of page.records) printRecord(ctx, entity, r);
10370
+ for (const r of page2.records) printRecord(ctx, entity, r);
10311
10371
  });
10312
10372
  }
10313
10373
  async function pmAdd(ctx, entity, parsed) {
@@ -10328,6 +10388,17 @@ async function pmGet(ctx, entity, id) {
10328
10388
  const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
10329
10389
  emit2(ctx, record10, () => printRecord(ctx, entity, record10));
10330
10390
  }
10391
+ async function pmReference(ctx, entity, id) {
10392
+ const { record: record10 } = await pmRequest(
10393
+ ctx,
10394
+ "GET",
10395
+ `/${entity}/${encodeURIComponent(id)}`
10396
+ );
10397
+ const markup = referenceMarkup(entity, record10);
10398
+ emit2(ctx, { kind: `pm:${entity}`, id: record10.id, label: record10.title ?? "", markup }, () => {
10399
+ ctx.out.log(markup);
10400
+ });
10401
+ }
10331
10402
  async function pmSet(ctx, entity, id, parsed) {
10332
10403
  const patch2 = collectEntityFields(entity, parsed, true);
10333
10404
  if (Object.keys(patch2).length === 0)
@@ -10348,6 +10419,36 @@ async function pmDone(ctx, entity, id, parsed) {
10348
10419
  });
10349
10420
  emit2(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
10350
10421
  }
10422
+ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10423
+ const rawRevision = stringOpt(parsed.options["expected-revision"]);
10424
+ const expectedRevision = Number(rawRevision);
10425
+ if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
10426
+ throw new Error(`pm task ${action2} needs --expected-revision <n>`);
10427
+ }
10428
+ const mutationId = writeMutationId2(parsed);
10429
+ const res = action2 === "ready" ? await pmRequest(
10430
+ ctx,
10431
+ "PATCH",
10432
+ `/task/${encodeURIComponent(id)}`,
10433
+ {
10434
+ patch: {
10435
+ ...collectEntityFields("task", parsed, true),
10436
+ column: "todo",
10437
+ expectedRevision
10438
+ },
10439
+ mutationId
10440
+ }
10441
+ ) : await pmRequest(
10442
+ ctx,
10443
+ "POST",
10444
+ `/task/${encodeURIComponent(id)}/${action2}`,
10445
+ { expectedRevision, mutationId }
10446
+ );
10447
+ emit2(ctx, res, () => {
10448
+ const state2 = res.record ? statusCol("task", res.record) : action2;
10449
+ ctx.out.log(`task ${id} \u2192 ${state2}`);
10450
+ });
10451
+ }
10351
10452
  async function allRecords(ctx, entity, appId) {
10352
10453
  const records = [];
10353
10454
  for (; ; ) {
@@ -10356,11 +10457,48 @@ async function allRecords(ctx, entity, appId) {
10356
10457
  limit: "100",
10357
10458
  offset: String(records.length)
10358
10459
  });
10359
- const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
10360
- records.push(...page.records);
10361
- if (records.length >= page.total || page.records.length === 0) return records;
10460
+ const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
10461
+ records.push(...page2.records);
10462
+ if (records.length >= page2.total || page2.records.length === 0) return records;
10362
10463
  }
10363
10464
  }
10465
+ async function pmNext(ctx, parsed) {
10466
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
10467
+ if (!appId) throw new Error("pm next needs --app <appId>");
10468
+ const [goals, tasks] = await Promise.all([
10469
+ allRecords(ctx, "goal", appId),
10470
+ allRecords(ctx, "task", appId)
10471
+ ]);
10472
+ const result = {
10473
+ appId,
10474
+ openGoals: goals.filter((record10) => record10.status === "open"),
10475
+ doing: tasks.filter((record10) => record10.column === "doing"),
10476
+ ready: tasks.filter((record10) => record10.column === "todo")
10477
+ };
10478
+ emit2(ctx, result, () => {
10479
+ ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
10480
+ for (const [label, records] of [
10481
+ ["doing", result.doing],
10482
+ ["ready", result.ready],
10483
+ ["open goals", result.openGoals]
10484
+ ]) {
10485
+ ctx.out.log(`${label}:`);
10486
+ if (!records.length) ctx.out.log("- (none)");
10487
+ else for (const record10 of records) printRecord(
10488
+ ctx,
10489
+ label === "open goals" ? "goal" : "task",
10490
+ record10
10491
+ );
10492
+ }
10493
+ if (!result.openGoals.length) {
10494
+ ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
10495
+ } else if (!result.ready.length) {
10496
+ ctx.out.log("next: refine a linked Backlog task and mark it Ready");
10497
+ } else {
10498
+ ctx.out.log("next: review a Ready task, then claim it with its revision");
10499
+ }
10500
+ });
10501
+ }
10364
10502
  async function pmHandoff(ctx, parsed) {
10365
10503
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
10366
10504
  if (!appId) throw new Error("pm handoff needs --app <appId>");
@@ -10400,6 +10538,12 @@ async function pmHandoff(ctx, parsed) {
10400
10538
  }
10401
10539
  });
10402
10540
  }
10541
+ async function pmRemove(ctx, entity, id) {
10542
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
10543
+ ctx.out.log(`deleted ${entity} ${id}`);
10544
+ }
10545
+
10546
+ // src/pm-comments.ts
10403
10547
  async function pmComment(ctx, entity, id, parsed) {
10404
10548
  const body = stringOpt(parsed.options.body);
10405
10549
  if (!body) throw new Error('pm comment needs --body "..."');
@@ -10410,19 +10554,218 @@ async function pmComment(ctx, entity, id, parsed) {
10410
10554
  ctx.out.log(`commented on ${entity} ${id}`);
10411
10555
  }
10412
10556
  async function pmComments(ctx, entity, id) {
10413
- const { messages } = await pmRequest(
10414
- ctx,
10415
- "GET",
10416
- `/${entity}/${encodeURIComponent(id)}/comments`
10417
- );
10557
+ const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}/comments`);
10418
10558
  emit2(ctx, messages, () => {
10419
10559
  if (messages.length === 0) ctx.out.log("(no comments)");
10420
- else for (const m of messages) ctx.out.log(`[${m.authorId ?? "?"}] ${m.body ?? ""}`);
10560
+ else for (const message2 of messages) {
10561
+ ctx.out.log(
10562
+ `[${message2.authorId ?? "?"}] ${message2.markup ?? message2.body ?? ""}`
10563
+ );
10564
+ }
10421
10565
  });
10422
10566
  }
10423
- async function pmRemove(ctx, entity, id) {
10424
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
10425
- ctx.out.log(`deleted ${entity} ${id}`);
10567
+
10568
+ // src/pm-watch-types.ts
10569
+ var PmWatchCheckpointError = class extends Error {
10570
+ constructor(cursor, streamId) {
10571
+ super("PM work cursor requires a new checkpoint");
10572
+ this.cursor = cursor;
10573
+ this.streamId = streamId;
10574
+ this.name = "PmWatchCheckpointError";
10575
+ }
10576
+ cursor;
10577
+ streamId;
10578
+ code = "checkpoint_required";
10579
+ };
10580
+ var PmWatchRequestError = class extends Error {
10581
+ constructor(message2, retryable, status) {
10582
+ super(message2);
10583
+ this.retryable = retryable;
10584
+ this.status = status;
10585
+ this.name = "PmWatchRequestError";
10586
+ }
10587
+ retryable;
10588
+ status;
10589
+ };
10590
+
10591
+ // src/pm-watch.ts
10592
+ var DEFAULT_INTERVAL_MS2 = 15e3;
10593
+ var MAX_CONSECUTIVE_FAILURES2 = 5;
10594
+ var MAX_BACKOFF_MS2 = 3e4;
10595
+ function positiveNumber(parsed, flag, fallback) {
10596
+ const raw = stringOpt(parsed.options[flag]);
10597
+ const value2 = raw == null ? NaN : Number(raw);
10598
+ return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
10599
+ }
10600
+ function jsonl2(ctx, parsed, value2) {
10601
+ if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
10602
+ }
10603
+ async function page(ctx, appId, cursor) {
10604
+ const params = new URLSearchParams({ app: appId });
10605
+ if (cursor) params.set("cursor", cursor);
10606
+ let response2;
10607
+ try {
10608
+ response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm/work/watch?${params}`, {
10609
+ headers: { authorization: `Bearer ${ctx.token}` }
10610
+ });
10611
+ } catch (error) {
10612
+ throw new PmWatchRequestError(
10613
+ `pm watch request failed: ${error instanceof Error ? error.message : String(error)}`,
10614
+ true
10615
+ );
10616
+ }
10617
+ const data = await response2.json().catch(() => ({}));
10618
+ if (response2.status === 409 && data.code === "checkpoint_required") {
10619
+ throw new PmWatchCheckpointError(data.cursor, data.streamId);
10620
+ }
10621
+ if (!response2.ok) {
10622
+ throw new PmWatchRequestError(
10623
+ `pm watch failed: ${data.error ?? `registry returned ${response2.status}`}`,
10624
+ response2.status === 429 || response2.status >= 500,
10625
+ response2.status
10626
+ );
10627
+ }
10628
+ return data;
10629
+ }
10630
+ function recordState(record10) {
10631
+ if (record10.column) return record10.column === "todo" ? "ready" : record10.column;
10632
+ return String(record10.status ?? "");
10633
+ }
10634
+ function eventRecord(event) {
10635
+ return event.payload.payload;
10636
+ }
10637
+ function eventLabel(event) {
10638
+ const record10 = eventRecord(event);
10639
+ if (record10) return String(record10.title ?? event.payload.entityId);
10640
+ const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
10641
+ return body || event.payload.entityId;
10642
+ }
10643
+ function report3(ctx, parsed, result) {
10644
+ if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
10645
+ else if (parsed.options.jsonl !== true && result.found) {
10646
+ for (const event of result.events ?? []) {
10647
+ const record10 = eventRecord(event);
10648
+ const state2 = record10 ? recordState(record10) : "comment";
10649
+ ctx.out.log(
10650
+ `${event.id} ${event.type} ${state2}${record10?.revision ? `; r${record10.revision}` : ""} ${eventLabel(event)}`
10651
+ );
10652
+ }
10653
+ }
10654
+ return result;
10655
+ }
10656
+ async function pmWatch(ctx, parsed) {
10657
+ if (ctx.json && parsed.options.jsonl === true) {
10658
+ throw new Error("--json and --jsonl cannot be combined");
10659
+ }
10660
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
10661
+ if (!appId) throw new Error("pm watch needs --app <appId>");
10662
+ const sleep = ctx.sleep ?? ((ms) => new Promise((resolve13) => setTimeout(resolve13, ms)));
10663
+ const now = ctx.now ?? Date.now;
10664
+ const intervalMs = (positiveNumber(parsed, "interval", DEFAULT_INTERVAL_MS2 / 1e3) ?? DEFAULT_INTERVAL_MS2 / 1e3) * 1e3;
10665
+ const timeoutSeconds = positiveNumber(parsed, "timeout");
10666
+ const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
10667
+ const entity = stringOpt(parsed.options.entity);
10668
+ const action2 = stringOpt(parsed.options.action);
10669
+ const wantedState = stringOpt(parsed.options.state)?.toLowerCase();
10670
+ const by = stringOpt(parsed.options.by);
10671
+ const self = stringOpt(parsed.options.self);
10672
+ let cursor = stringOpt(parsed.options.cursor);
10673
+ let firstSuccess = true;
10674
+ let consecutiveFailures = 0;
10675
+ for (; ; ) {
10676
+ let current;
10677
+ try {
10678
+ current = await page(ctx, appId, cursor);
10679
+ consecutiveFailures = 0;
10680
+ } catch (error) {
10681
+ if (error instanceof PmWatchCheckpointError) {
10682
+ jsonl2(ctx, parsed, {
10683
+ type: "status",
10684
+ state: "checkpoint_required",
10685
+ retryable: false,
10686
+ ...error.cursor ? { cursor: error.cursor } : {},
10687
+ ...error.streamId ? { streamId: error.streamId } : {}
10688
+ });
10689
+ throw error;
10690
+ }
10691
+ if (!(error instanceof PmWatchRequestError) || !error.retryable) throw error;
10692
+ consecutiveFailures++;
10693
+ jsonl2(ctx, parsed, {
10694
+ type: "status",
10695
+ state: "degraded",
10696
+ retryable: true,
10697
+ attempt: consecutiveFailures,
10698
+ ...cursor ? { cursor } : {},
10699
+ ...error.status ? { status: error.status } : {}
10700
+ });
10701
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) throw error;
10702
+ if (deadline !== void 0 && now() >= deadline) {
10703
+ return report3(ctx, parsed, { found: false, cursor: cursor ?? "" });
10704
+ }
10705
+ const backoff = Math.min(
10706
+ MAX_BACKOFF_MS2,
10707
+ Math.min(intervalMs, 1e3) * 2 ** (consecutiveFailures - 1)
10708
+ );
10709
+ await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
10710
+ continue;
10711
+ }
10712
+ cursor = current.cursor;
10713
+ const baseline = firstSuccess && current.events.length === 0;
10714
+ if (baseline) {
10715
+ jsonl2(ctx, parsed, {
10716
+ type: "checkpoint",
10717
+ streamId: current.streamId,
10718
+ cursor,
10719
+ serverTime: current.serverTime
10720
+ });
10721
+ }
10722
+ firstSuccess = false;
10723
+ const matching = current.events.filter((event) => {
10724
+ const record10 = eventRecord(event);
10725
+ const state2 = record10 ? recordState(record10).toLowerCase() : "";
10726
+ 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);
10727
+ });
10728
+ for (const event of matching) {
10729
+ jsonl2(ctx, parsed, {
10730
+ type: "event",
10731
+ streamId: current.streamId,
10732
+ eventId: event.id,
10733
+ cursor: event.cursor,
10734
+ event
10735
+ });
10736
+ }
10737
+ if (matching.length > 0) {
10738
+ jsonl2(ctx, parsed, {
10739
+ type: "checkpoint",
10740
+ streamId: current.streamId,
10741
+ cursor,
10742
+ serverTime: current.serverTime
10743
+ });
10744
+ return report3(ctx, parsed, { found: true, cursor, events: matching });
10745
+ }
10746
+ if (current.events.length > 0) {
10747
+ jsonl2(ctx, parsed, {
10748
+ type: "checkpoint",
10749
+ streamId: current.streamId,
10750
+ cursor,
10751
+ serverTime: current.serverTime
10752
+ });
10753
+ } else if (!baseline) {
10754
+ jsonl2(ctx, parsed, {
10755
+ type: "heartbeat",
10756
+ streamId: current.streamId,
10757
+ cursor,
10758
+ serverTime: current.serverTime
10759
+ });
10760
+ }
10761
+ if (current.hasMore) continue;
10762
+ if (deadline !== void 0 && now() >= deadline) {
10763
+ return report3(ctx, parsed, { found: false, cursor });
10764
+ }
10765
+ await sleep(
10766
+ deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()))
10767
+ );
10768
+ }
10426
10769
  }
10427
10770
 
10428
10771
  // src/pm-command.ts
@@ -10443,7 +10786,11 @@ var ACTION_OPTIONS = {
10443
10786
  done: ["mutation-id"],
10444
10787
  comment: ["body", "mutation-id"],
10445
10788
  comments: [],
10446
- rm: []
10789
+ rm: [],
10790
+ ready: ["goal", "alignment-decision", "execution", "description", "desc", "body", "acceptance", "expected-revision", "mutation-id"],
10791
+ claim: ["expected-revision", "mutation-id"],
10792
+ release: ["expected-revision", "mutation-id"],
10793
+ ref: []
10447
10794
  };
10448
10795
  var ENTITY_OPTIONS = {
10449
10796
  goal: {
@@ -10454,8 +10801,8 @@ var ENTITY_OPTIONS = {
10454
10801
  },
10455
10802
  task: {
10456
10803
  list: ["column", "goal", "assignee"],
10457
- add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
10458
- set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
10804
+ add: ["column", "goal", "alignment-decision", "execution", "assignee", "due", "description", "desc", "body", "acceptance"],
10805
+ set: ["title", "column", "rank", "goal", "alignment-decision", "execution", "assignee", "due", "description", "desc", "body", "acceptance", "expected-revision"],
10459
10806
  done: []
10460
10807
  },
10461
10808
  decision: {
@@ -10506,11 +10853,33 @@ async function buildContext2(parsed, deps) {
10506
10853
  json: parsed.options.json === true,
10507
10854
  // Preserve PM's existing cross-project default when a config is present;
10508
10855
  // only an explicit remote-agent environment or profile selects an app.
10509
- appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
10856
+ appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0,
10857
+ ...deps.sleep ? { sleep: deps.sleep } : {},
10858
+ ...deps.now ? { now: deps.now } : {}
10510
10859
  };
10511
10860
  }
10512
10861
  async function pmCommand(parsed, deps = {}) {
10513
10862
  const word = parsed.positionals[1] ?? "";
10863
+ if (word === "next") {
10864
+ assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
10865
+ return pmNext(await buildContext2(parsed, deps), parsed);
10866
+ }
10867
+ if (word === "watch") {
10868
+ assertArgs(parsed, [
10869
+ ...COMMON_OPTIONS,
10870
+ "app",
10871
+ "cursor",
10872
+ "interval",
10873
+ "timeout",
10874
+ "jsonl",
10875
+ "entity",
10876
+ "action",
10877
+ "state",
10878
+ "by",
10879
+ "self"
10880
+ ], 2);
10881
+ return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
10882
+ }
10514
10883
  if (word === "handoff") {
10515
10884
  assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
10516
10885
  return pmHandoff(await buildContext2(parsed, deps), parsed);
@@ -10521,6 +10890,9 @@ async function pmCommand(parsed, deps = {}) {
10521
10890
  const action2 = canonicalAction(requestedAction);
10522
10891
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
10523
10892
  assertArgs(parsed, allowedOptions(entity, action2), 4);
10893
+ if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
10894
+ throw new Error(`pm ${action2} is only valid for tasks`);
10895
+ }
10524
10896
  const ctx = await buildContext2(parsed, deps);
10525
10897
  const id = parsed.positionals[3];
10526
10898
  switch (action2) {
@@ -10540,6 +10912,12 @@ async function pmCommand(parsed, deps = {}) {
10540
10912
  return pmComments(ctx, entity, requireId2(id, action2));
10541
10913
  case "rm":
10542
10914
  return pmRemove(ctx, entity, requireId2(id, action2));
10915
+ case "ref":
10916
+ return pmReference(ctx, entity, requireId2(id, action2));
10917
+ case "ready":
10918
+ case "claim":
10919
+ case "release":
10920
+ return pmTaskLifecycle(ctx, requireId2(id, action2), action2, parsed);
10543
10921
  }
10544
10922
  }
10545
10923
 
@@ -11154,12 +11532,12 @@ async function call(ctx, method, path, body) {
11154
11532
  throw new Error(message2);
11155
11533
  }
11156
11534
  async function bySlug(ctx, slug) {
11157
- const page = await call(
11535
+ const page2 = await call(
11158
11536
  ctx,
11159
11537
  "GET",
11160
11538
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
11161
11539
  );
11162
- const found = page.records[0];
11540
+ const found = page2.records[0];
11163
11541
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11164
11542
  return found;
11165
11543
  }
@@ -11173,11 +11551,11 @@ async function runbookList(ctx, all, query) {
11173
11551
  const params = new URLSearchParams();
11174
11552
  if (!all) params.set("app", ctx.appId);
11175
11553
  if (query) params.set("q", query);
11176
- const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11177
- if (ctx.json) return ctx.out.log(JSON.stringify(page, null, 2));
11178
- if (!page.records.length) return ctx.out.log("(no runbooks)");
11554
+ const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11555
+ if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
11556
+ if (!page2.records.length) return ctx.out.log("(no runbooks)");
11179
11557
  ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
11180
- for (const r of page.records)
11558
+ for (const r of page2.records)
11181
11559
  ctx.out.log(
11182
11560
  [r.slug, r.status, `v${r.version}`, r.appId === PLATFORM_SCOPE ? "platform" : r.appId, stamp(r.updatedAt), r.title].join(" ")
11183
11561
  );
@@ -11232,12 +11610,12 @@ async function runbookVisibility(ctx, slug, visibility) {
11232
11610
  }
11233
11611
  async function runbookHistory(ctx, slug) {
11234
11612
  const runbook = await bySlug(ctx, slug);
11235
- const page = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
11236
- if (ctx.json) return ctx.out.log(JSON.stringify(page, null, 2));
11613
+ const page2 = await call(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
11614
+ if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
11237
11615
  ctx.out.log(`${slug} is at v${runbook.version}`);
11238
- if (!page.records.length) return ctx.out.log("(no earlier versions)");
11616
+ if (!page2.records.length) return ctx.out.log("(no earlier versions)");
11239
11617
  ctx.out.log(["V", "WHEN", "BY", "NOTE"].join(" "));
11240
- for (const r of page.records)
11618
+ for (const r of page2.records)
11241
11619
  ctx.out.log([`v${r.version}`, stamp(r.createdAt), r.ownerEmail ?? "", r.note ?? ""].join(" "));
11242
11620
  }
11243
11621
  async function runbookRevert(ctx, slug, version) {
@@ -11312,12 +11690,12 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
11312
11690
  );
11313
11691
  }
11314
11692
  async function upsert(ctx, r, visibility) {
11315
- const page = await call(
11693
+ const page2 = await call(
11316
11694
  ctx,
11317
11695
  "GET",
11318
11696
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(r.slug)}&limit=1`
11319
11697
  );
11320
- const found = page.records[0];
11698
+ const found = page2.records[0];
11321
11699
  if (!found) {
11322
11700
  await call(ctx, "POST", "/runbook", {
11323
11701
  appId: ctx.appId,
@@ -11573,7 +11951,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
11573
11951
  return out;
11574
11952
  }
11575
11953
  var editHint = (slug, appId) => `odla-ai runbook edit ${slug}${appId === PLATFORM_SCOPE ? "" : ` --app ${appId}`} --note "<what changed>"`;
11576
- function report3(ctx, impacts) {
11954
+ function report4(ctx, impacts) {
11577
11955
  const covered = impacts.filter((i) => i.runbooks.length);
11578
11956
  ctx.out.log(
11579
11957
  `${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.`
@@ -11611,7 +11989,7 @@ async function runbookImpact(ctx, options, deps = {}) {
11611
11989
  }
11612
11990
  const impacts = await assessImpact(ctx, surfaces, options.all, options.limit ?? 4);
11613
11991
  if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
11614
- report3(ctx, impacts);
11992
+ report4(ctx, impacts);
11615
11993
  }
11616
11994
 
11617
11995
  // src/runbook-lint.ts
@@ -11649,17 +12027,17 @@ function lintRunbook(runbook, installed) {
11649
12027
  async function runbookLint(ctx, all) {
11650
12028
  const params = new URLSearchParams();
11651
12029
  if (!all) params.set("app", ctx.appId);
11652
- const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
12030
+ const page2 = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
11653
12031
  const installed = { "@odla-ai/cli": cliVersion() };
11654
- const findings = page.records.flatMap((runbook) => lintRunbook(runbook, installed));
11655
- if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page.records.length, findings }, null, 2));
11656
- if (!page.records.length) return ctx.out.log("(no runbooks in scope)");
12032
+ const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
12033
+ if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
12034
+ if (!page2.records.length) return ctx.out.log("(no runbooks in scope)");
11657
12035
  if (!findings.length) {
11658
12036
  return ctx.out.log(
11659
- `${page.records.length} runbook${page.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
12037
+ `${page2.records.length} runbook${page2.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
11660
12038
  );
11661
12039
  }
11662
- ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page.records.length} runbooks:`);
12040
+ ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page2.records.length} runbooks:`);
11663
12041
  for (const finding of findings) {
11664
12042
  const scope = finding.appId === PLATFORM_SCOPE ? "" : ` --app ${finding.appId}`;
11665
12043
  ctx.out.log("");
@@ -11722,12 +12100,12 @@ async function runbookAsk(ctx, question, all) {
11722
12100
  ctx.out.log(JSDOC_POINTER);
11723
12101
  }
11724
12102
  async function runbookComment(ctx, slug, body) {
11725
- const page = await call(
12103
+ const page2 = await call(
11726
12104
  ctx,
11727
12105
  "GET",
11728
12106
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
11729
12107
  );
11730
- const found = page.records[0];
12108
+ const found = page2.records[0];
11731
12109
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11732
12110
  await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
11733
12111
  ctx.out.log(`commented on ${slug} (v${found.version})`);
@@ -11779,12 +12157,12 @@ var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
11779
12157
 
11780
12158
  // src/runbook-edit-flow.ts
11781
12159
  async function editRunbook(ctx, slug, deps = {}) {
11782
- const page = await call(
12160
+ const page2 = await call(
11783
12161
  ctx,
11784
12162
  "GET",
11785
12163
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
11786
12164
  );
11787
- const found = page.records[0];
12165
+ const found = page2.records[0];
11788
12166
  if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
11789
12167
  ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
11790
12168
  const body = await editText(found.body, slug, deps);
@@ -12205,31 +12583,31 @@ function printHostedJob(out, job, platform, appId) {
12205
12583
  url.searchParams.set("job", job.jobId);
12206
12584
  out.log(` Studio: ${url.toString()}`);
12207
12585
  }
12208
- function printHostedReport(out, report4) {
12209
- out.log(`security report ${report4.jobId}: ${report4.repository}@${report4.revision}`);
12210
- 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}`);
12211
- out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates} rejected=${report4.metrics.rejected}`);
12212
- out.log(` discovery: ${report4.provenance.discovery?.provider ?? "unknown"}/${report4.provenance.discovery?.model ?? "unknown"}`);
12213
- out.log(` validation: ${report4.provenance.validation?.provider ?? "unknown"}/${report4.provenance.validation?.model ?? "unknown"} independent=${String(report4.provenance.independentValidation)}`);
12214
- for (const finding of report4.findings) {
12586
+ function printHostedReport(out, report5) {
12587
+ out.log(`security report ${report5.jobId}: ${report5.repository}@${report5.revision}`);
12588
+ 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}`);
12589
+ out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates} rejected=${report5.metrics.rejected}`);
12590
+ out.log(` discovery: ${report5.provenance.discovery?.provider ?? "unknown"}/${report5.provenance.discovery?.model ?? "unknown"}`);
12591
+ out.log(` validation: ${report5.provenance.validation?.provider ?? "unknown"}/${report5.provenance.validation?.model ?? "unknown"} independent=${String(report5.provenance.independentValidation)}`);
12592
+ for (const finding of report5.findings) {
12215
12593
  const location = finding.locations[0];
12216
12594
  out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
12217
12595
  }
12218
- for (const limitation of report4.limitations) out.log(` limitation: ${limitation}`);
12596
+ for (const limitation of report5.limitations) out.log(` limitation: ${limitation}`);
12219
12597
  }
12220
- function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
12598
+ function enforceHostedReportGate(report5, parsed, out, emitSuccess) {
12221
12599
  const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
12222
12600
  const candidateValue = parsed.options["fail-on-candidates"];
12223
12601
  const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
12224
12602
  const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
12225
- const confirmed = report4.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
12226
- const leads = failOnCandidates ? report4.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
12227
- const incomplete = report4.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
12603
+ const confirmed = report5.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
12604
+ const leads = failOnCandidates ? report5.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
12605
+ const incomplete = report5.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
12228
12606
  if (confirmed.length || leads.length || incomplete) {
12229
- throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report4.coverageStatus}` : ""}`);
12607
+ throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report5.coverageStatus}` : ""}`);
12230
12608
  }
12231
12609
  if (emitSuccess) {
12232
- out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
12610
+ out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report5.coverageStatus}. This is not proof that the application is secure.`);
12233
12611
  }
12234
12612
  }
12235
12613
  function printHostedSecurityPlanRoute(out, label, route2) {
@@ -12333,13 +12711,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
12333
12711
  }
12334
12712
  throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
12335
12713
  }
12336
- const report4 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
12714
+ const report5 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
12337
12715
  if (parsed.options.json === true) {
12338
- context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report4 }, null, 2));
12716
+ context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report5 }, null, 2));
12339
12717
  } else {
12340
- printHostedReport(context.stdout, report4);
12718
+ printHostedReport(context.stdout, report5);
12341
12719
  }
12342
- enforceHostedReportGate(report4, parsed, context.stdout, parsed.options.json !== true);
12720
+ enforceHostedReportGate(report5, parsed, context.stdout, parsed.options.json !== true);
12343
12721
  }
12344
12722
  async function runLocalSecurityCommand(parsed, dependencies) {
12345
12723
  if (parsed.options.source === true) {
@@ -12406,13 +12784,13 @@ async function runLocalSecurityCommand(parsed, dependencies) {
12406
12784
  });
12407
12785
  enforceLocalGate(result.report, parsed);
12408
12786
  }
12409
- function enforceLocalGate(report4, parsed) {
12787
+ function enforceLocalGate(report5, parsed) {
12410
12788
  const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
12411
12789
  const candidateValue = parsed.options["fail-on-candidates"];
12412
12790
  const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
12413
- const confirmed = findingsAtOrAbove(report4, failOn);
12414
- const leads = failOnCandidates ? findingsAtOrAbove(report4, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
12415
- const incomplete = report4.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
12791
+ const confirmed = findingsAtOrAbove(report5, failOn);
12792
+ const leads = failOnCandidates ? findingsAtOrAbove(report5, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
12793
+ const incomplete = report5.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
12416
12794
  if (confirmed.length || leads.length || incomplete) {
12417
12795
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
12418
12796
  }
@@ -12451,9 +12829,9 @@ async function securityCommand(parsed, dependencies) {
12451
12829
  assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
12452
12830
  const jobId = requiredSecurityPositional(parsed, 2, "job id");
12453
12831
  const context = await hostedSecurityContext(parsed, dependencies);
12454
- const report4 = await getHostedSecurityReport({ ...context, jobId });
12455
- if (parsed.options.json === true) context.stdout.log(JSON.stringify(report4, null, 2));
12456
- else printHostedReport(context.stdout, report4);
12832
+ const report5 = await getHostedSecurityReport({ ...context, jobId });
12833
+ if (parsed.options.json === true) context.stdout.log(JSON.stringify(report5, null, 2));
12834
+ else printHostedReport(context.stdout, report5);
12457
12835
  return;
12458
12836
  }
12459
12837
  if (sub !== "run") {
@@ -12746,4 +13124,4 @@ export {
12746
13124
  exitCodeFor,
12747
13125
  runCli
12748
13126
  };
12749
- //# sourceMappingURL=chunk-JWEBGIBR.js.map
13127
+ //# sourceMappingURL=chunk-GENTB3VO.js.map