@mutmutco/cli 4.5.62 → 4.5.64

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.
Files changed (2) hide show
  1. package/dist/main.cjs +480 -125
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -587,7 +587,147 @@ var init_github_client = __esm({
587
587
  }
588
588
  });
589
589
 
590
+ // src/github-quota.ts
591
+ function hubQuotaResetWaitMs(resetAt, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS, jitterMs = 0) {
592
+ if (typeof resetAt !== "string" || !resetAt) return void 0;
593
+ const resetMs = Date.parse(resetAt);
594
+ if (!Number.isFinite(resetMs)) return void 0;
595
+ const wait = rateLimitWaitMs(resetMs / 1e3, nowMs, capMs);
596
+ if (wait === void 0) return void 0;
597
+ return wait + Math.max(0, jitterMs);
598
+ }
599
+ function isRateLimitedError(e) {
600
+ return e instanceof RateLimitedError;
601
+ }
602
+ function isRateLimitText(text) {
603
+ return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
604
+ }
605
+ function isRateLimitRefusal(e) {
606
+ if (e instanceof GitHubApiError && e.rateLimited) return true;
607
+ if (e instanceof GitHubApiError && e.status === 403) {
608
+ return /rate limit|abuse detection|secondary rate/i.test(e.message);
609
+ }
610
+ if (e instanceof RateLimitedError) return true;
611
+ const text = e instanceof Error ? `${e.message}
612
+ ${e.stderr ?? ""}` : String(e ?? "");
613
+ return isRateLimitText(text);
614
+ }
615
+ function resetEpochFromError(e) {
616
+ if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
617
+ if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
618
+ return e.receipt.resetEpochSeconds;
619
+ }
620
+ return void 0;
621
+ }
622
+ function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
623
+ if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
624
+ const wait = resetEpochSeconds * 1e3 - nowMs;
625
+ if (wait <= 0) return 0;
626
+ if (wait > capMs) return void 0;
627
+ return wait;
628
+ }
629
+ function rateLimitedReceipt(opts) {
630
+ const resetEpochSeconds = opts.resetEpochSeconds;
631
+ const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
632
+ const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
633
+ const context = opts.context ?? "GitHub API";
634
+ const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
635
+ return {
636
+ status: "rate_limited",
637
+ ...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
638
+ ...resetAt ? { resetAt } : {},
639
+ message: opts.message ?? `${context}: ${note}`,
640
+ retryCommand: opts.retryCommand,
641
+ ...remaining ? { remaining } : {}
642
+ };
643
+ }
644
+ async function runWithRateLimitBackoff(operation, opts) {
645
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
646
+ const now = opts.now ?? Date.now;
647
+ const log = opts.log ?? ((message2) => console.warn(message2));
648
+ const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
649
+ const tryOnce = async () => {
650
+ try {
651
+ return await operation();
652
+ } catch (e) {
653
+ if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
654
+ try {
655
+ return await opts.fallback();
656
+ } catch (e2) {
657
+ if (isRateLimitRefusal(e2)) throw e2;
658
+ throw e2;
659
+ }
660
+ }
661
+ };
662
+ try {
663
+ return await tryOnce();
664
+ } catch (e) {
665
+ if (!isRateLimitRefusal(e)) throw e;
666
+ const reset = resetEpochFromError(e);
667
+ const waitMs = rateLimitWaitMs(reset, now(), capMs);
668
+ if (waitMs === void 0) {
669
+ throw new RateLimitedError(rateLimitedReceipt({
670
+ resetEpochSeconds: reset,
671
+ retryCommand: opts.retryCommand,
672
+ context: opts.context,
673
+ nowMs: now()
674
+ }));
675
+ }
676
+ if (waitMs > 0) {
677
+ log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
678
+ await sleep2(waitMs);
679
+ }
680
+ try {
681
+ return await tryOnce();
682
+ } catch (e2) {
683
+ if (!isRateLimitRefusal(e2)) throw e2;
684
+ throw new RateLimitedError(rateLimitedReceipt({
685
+ resetEpochSeconds: resetEpochFromError(e2) ?? reset,
686
+ retryCommand: opts.retryCommand,
687
+ context: opts.context,
688
+ nowMs: now()
689
+ }));
690
+ }
691
+ }
692
+ }
693
+ var RATE_LIMIT_WAIT_CAP_MS, RateLimitedError;
694
+ var init_github_quota = __esm({
695
+ "src/github-quota.ts"() {
696
+ "use strict";
697
+ init_github_client();
698
+ RATE_LIMIT_WAIT_CAP_MS = 12e4;
699
+ RateLimitedError = class extends Error {
700
+ receipt;
701
+ constructor(receipt) {
702
+ super(receipt.message);
703
+ this.name = "RateLimitedError";
704
+ this.receipt = receipt;
705
+ }
706
+ };
707
+ }
708
+ });
709
+
590
710
  // src/fetch-retry.ts
711
+ async function hubQuota503(res, opts) {
712
+ if (res.status !== 503) return "pass";
713
+ let body;
714
+ try {
715
+ body = await res.clone().json();
716
+ } catch {
717
+ return "pass";
718
+ }
719
+ const code = body?.code;
720
+ if (code !== "github-rate-limited") return "pass";
721
+ const q = opts.hubQuotaWait;
722
+ const resetAt = body?.resetAt;
723
+ const wait = hubQuotaResetWaitMs(
724
+ typeof resetAt === "string" ? resetAt : void 0,
725
+ q?.now?.() ?? Date.now(),
726
+ q?.capMs ?? RATE_LIMIT_WAIT_CAP_MS,
727
+ (q?.jitterMs ?? (() => Math.floor(Math.random() * HUB_QUOTA_JITTER_MAX_MS)))()
728
+ );
729
+ return wait === void 0 ? "fail-fast" : { waitMs: wait };
730
+ }
591
731
  async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
592
732
  const attempts = opts.attempts ?? 3;
593
733
  const baseDelayMs = opts.baseDelayMs ?? 250;
@@ -599,9 +739,17 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
599
739
  const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
600
740
  try {
601
741
  const res = await fetchImpl(url, attemptInit);
602
- if (!isLast && retryOn(res)) {
603
- await sleep2(baseDelayMs * 2 ** i);
604
- continue;
742
+ if (!isLast) {
743
+ const quota = await hubQuota503(res, opts);
744
+ if (typeof quota === "object") {
745
+ await sleep2(quota.waitMs);
746
+ continue;
747
+ }
748
+ if (quota === "fail-fast") return res;
749
+ if (retryOn(res)) {
750
+ await sleep2(baseDelayMs * 2 ** i);
751
+ continue;
752
+ }
605
753
  }
606
754
  return res;
607
755
  } catch (e) {
@@ -612,9 +760,12 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
612
760
  }
613
761
  throw lastErr;
614
762
  }
763
+ var HUB_QUOTA_JITTER_MAX_MS;
615
764
  var init_fetch_retry = __esm({
616
765
  "src/fetch-retry.ts"() {
617
766
  "use strict";
767
+ init_github_quota();
768
+ HUB_QUOTA_JITTER_MAX_MS = 5e3;
618
769
  }
619
770
  });
620
771
 
@@ -5568,114 +5719,12 @@ async function fetchHubBoardSnapshot(request, deps) {
5568
5719
  return { state: "ok", snapshot: body };
5569
5720
  }
5570
5721
 
5571
- // src/github-quota.ts
5572
- init_github_client();
5573
- var RATE_LIMIT_WAIT_CAP_MS = 12e4;
5574
- var RateLimitedError = class extends Error {
5575
- receipt;
5576
- constructor(receipt) {
5577
- super(receipt.message);
5578
- this.name = "RateLimitedError";
5579
- this.receipt = receipt;
5580
- }
5581
- };
5582
- function isRateLimitedError(e) {
5583
- return e instanceof RateLimitedError;
5584
- }
5585
- function isRateLimitText(text) {
5586
- return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
5587
- }
5588
- function isRateLimitRefusal(e) {
5589
- if (e instanceof GitHubApiError && e.rateLimited) return true;
5590
- if (e instanceof GitHubApiError && e.status === 403) {
5591
- return /rate limit|abuse detection|secondary rate/i.test(e.message);
5592
- }
5593
- if (e instanceof RateLimitedError) return true;
5594
- const text = e instanceof Error ? `${e.message}
5595
- ${e.stderr ?? ""}` : String(e ?? "");
5596
- return isRateLimitText(text);
5597
- }
5598
- function resetEpochFromError(e) {
5599
- if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
5600
- if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
5601
- return e.receipt.resetEpochSeconds;
5602
- }
5603
- return void 0;
5604
- }
5605
- function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
5606
- if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
5607
- const wait = resetEpochSeconds * 1e3 - nowMs;
5608
- if (wait <= 0) return 0;
5609
- if (wait > capMs) return void 0;
5610
- return wait;
5611
- }
5612
- function rateLimitedReceipt(opts) {
5613
- const resetEpochSeconds = opts.resetEpochSeconds;
5614
- const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
5615
- const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
5616
- const context = opts.context ?? "GitHub API";
5617
- const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
5618
- return {
5619
- status: "rate_limited",
5620
- ...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
5621
- ...resetAt ? { resetAt } : {},
5622
- message: opts.message ?? `${context}: ${note}`,
5623
- retryCommand: opts.retryCommand,
5624
- ...remaining ? { remaining } : {}
5625
- };
5626
- }
5627
- async function runWithRateLimitBackoff(operation, opts) {
5628
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
5629
- const now = opts.now ?? Date.now;
5630
- const log = opts.log ?? ((message2) => console.warn(message2));
5631
- const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
5632
- const tryOnce = async () => {
5633
- try {
5634
- return await operation();
5635
- } catch (e) {
5636
- if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
5637
- try {
5638
- return await opts.fallback();
5639
- } catch (e2) {
5640
- if (isRateLimitRefusal(e2)) throw e2;
5641
- throw e2;
5642
- }
5643
- }
5644
- };
5645
- try {
5646
- return await tryOnce();
5647
- } catch (e) {
5648
- if (!isRateLimitRefusal(e)) throw e;
5649
- const reset = resetEpochFromError(e);
5650
- const waitMs = rateLimitWaitMs(reset, now(), capMs);
5651
- if (waitMs === void 0) {
5652
- throw new RateLimitedError(rateLimitedReceipt({
5653
- resetEpochSeconds: reset,
5654
- retryCommand: opts.retryCommand,
5655
- context: opts.context,
5656
- nowMs: now()
5657
- }));
5658
- }
5659
- if (waitMs > 0) {
5660
- log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
5661
- await sleep2(waitMs);
5662
- }
5663
- try {
5664
- return await tryOnce();
5665
- } catch (e2) {
5666
- if (!isRateLimitRefusal(e2)) throw e2;
5667
- throw new RateLimitedError(rateLimitedReceipt({
5668
- resetEpochSeconds: resetEpochFromError(e2) ?? reset,
5669
- retryCommand: opts.retryCommand,
5670
- context: opts.context,
5671
- nowMs: now()
5672
- }));
5673
- }
5674
- }
5675
- }
5722
+ // src/board-read.ts
5723
+ init_github_quota();
5676
5724
 
5677
5725
  // src/claim-marker-client.ts
5678
5726
  init_github_client();
5727
+ init_github_quota();
5679
5728
 
5680
5729
  // src/pr-checks-rest.ts
5681
5730
  init_cli_shared();
@@ -6519,6 +6568,7 @@ function withAppClaimMarkerRead(client, appClient) {
6519
6568
  }
6520
6569
 
6521
6570
  // src/board-dependency.ts
6571
+ init_github_quota();
6522
6572
  var DEPENDS_ON_LINE = /^\s*[-*]?\s*\*\*Depends on:\*\*\s*(.+)$/im;
6523
6573
  var ISSUE_REF = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)#(\d+)/g;
6524
6574
  var DEPENDENCY_FETCH_CONCURRENCY = 8;
@@ -7209,6 +7259,9 @@ function jobNeeds(job) {
7209
7259
  function unquote(s) {
7210
7260
  return s.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
7211
7261
  }
7262
+ function contextMatchesJobId(jobId, context) {
7263
+ return context === jobId || context.startsWith(`${jobId} / `);
7264
+ }
7212
7265
  function unblockingGateJobs(workflows, required) {
7213
7266
  const unblocking = /* @__PURE__ */ new Set();
7214
7267
  for (const wf of workflows) {
@@ -7216,7 +7269,7 @@ function unblockingGateJobs(workflows, required) {
7216
7269
  const jobs = parseWorkflowJobs(wf.body);
7217
7270
  const needs = new Map(jobs.map((job) => [job.id, jobNeeds(job)]));
7218
7271
  const covered = /* @__PURE__ */ new Set();
7219
- const stack = jobs.map((job) => job.id).filter((id) => required.has(id));
7272
+ const stack = jobs.map((job) => job.id).filter((id) => [...required].some((context) => contextMatchesJobId(id, context)));
7220
7273
  while (stack.length) {
7221
7274
  const id = stack.pop();
7222
7275
  if (covered.has(id)) continue;
@@ -8489,6 +8542,22 @@ function shouldWithdrawSurfaceFlag(result) {
8489
8542
 
8490
8543
  // src/secrets-transport.ts
8491
8544
  init_client_version();
8545
+
8546
+ // ../shared/quota-attribution.ts
8547
+ var ATTRIBUTION_MAX = 120;
8548
+ function sanitizeAttribution(value, max = ATTRIBUTION_MAX) {
8549
+ if (typeof value !== "string" || !Number.isInteger(max) || max < 1) return void 0;
8550
+ const cleaned = value.replace(/[^A-Za-z0-9 ._/@:-]/g, "").replace(/\s+/g, " ").trim().slice(0, max);
8551
+ return cleaned.length > 0 ? cleaned : void 0;
8552
+ }
8553
+ function attributionFields(attr) {
8554
+ if (!attr) return {};
8555
+ const command = sanitizeAttribution(attr.command);
8556
+ const via = sanitizeAttribution(attr.via);
8557
+ return { ...command ? { for: command } : {}, ...via ? { via } : {} };
8558
+ }
8559
+
8560
+ // src/secrets-transport.ts
8492
8561
  var OWNER = "mutmutco";
8493
8562
  var SSM_ROOT = "/mmi-future";
8494
8563
  var PROJECT_TIER_SEGMENT = "dev";
@@ -8664,7 +8733,7 @@ async function fetchSecretValue(deps, key, opts, diagnose = false) {
8664
8733
  const res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/get`, {
8665
8734
  method: "POST",
8666
8735
  headers: await deps.headers({ "content-type": "application/json" }),
8667
- body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
8736
+ body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {}, ...attributionFields(opts.attribution) })
8668
8737
  });
8669
8738
  if (!res.ok) {
8670
8739
  if (diagnose) {
@@ -9158,6 +9227,7 @@ function isPriorityFieldConfigured(cfg) {
9158
9227
  // src/gh-create.ts
9159
9228
  init_cli_shared();
9160
9229
  init_github_client();
9230
+ init_github_quota();
9161
9231
  var ISSUE_TYPES = ["bug", "feature", "task"];
9162
9232
  var GH_MUTATION_TIMEOUT_MS = 12e4;
9163
9233
  function timeoutKillNote(err, timeoutMs) {
@@ -13467,9 +13537,11 @@ function ghError(e) {
13467
13537
 
13468
13538
  // src/board-claim-move.ts
13469
13539
  init_github_client();
13540
+ init_github_quota();
13470
13541
 
13471
13542
  // src/sub-issue.ts
13472
13543
  init_error_codes();
13544
+ init_github_quota();
13473
13545
  function buildResolveIdArgs(ref) {
13474
13546
  const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
13475
13547
  if (ref.repo) args.push("--repo", ref.repo);
@@ -17156,10 +17228,10 @@ var rollout_plan_default = {
17156
17228
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
17157
17229
  },
17158
17230
  baseline: {
17159
- version: "4.5.62",
17160
- tag: "v4.5.62",
17161
- commit: "5328997bb80a",
17162
- npm: "@mutmutco/cli@4.5.62"
17231
+ version: "4.5.64",
17232
+ tag: "v4.5.64",
17233
+ commit: "f201a5b12b79",
17234
+ npm: "@mutmutco/cli@4.5.64"
17163
17235
  },
17164
17236
  exitCriterion: "fleet-n-of-n",
17165
17237
  hubOnlyShortcut: "forbidden",
@@ -17176,14 +17248,14 @@ var rollout_plan_default = {
17176
17248
  repo: "mutmutco/mmi-hub",
17177
17249
  role: "canary",
17178
17250
  schedule: "train",
17179
- v3Target: "v4.5.62"
17251
+ v3Target: "v4.5.64"
17180
17252
  }
17181
17253
  ],
17182
17254
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
17183
17255
  rollback: {
17184
17256
  independent: true,
17185
- mechanism: "npm dist-tag latest -> 4.5.62 and redeploy the Hub Lambda from tag v4.5.62 (5328997bb80a); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17186
- v3Target: "v4.5.62 (@mutmutco/cli@4.5.62, tag commit 5328997bb80a \u2014 last known-good release carrying the repo-index v4-only contract)"
17257
+ mechanism: "npm dist-tag latest -> 4.5.64 and redeploy the Hub Lambda from tag v4.5.64 (f201a5b12b79); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17258
+ v3Target: "v4.5.64 (@mutmutco/cli@4.5.64, tag commit f201a5b12b79 \u2014 last known-good release carrying the repo-index v4-only contract)"
17187
17259
  }
17188
17260
  },
17189
17261
  {
@@ -27966,7 +28038,7 @@ async function auditRepoCi(repo, callerDeps) {
27966
28038
  if (hasRequiredChecks) {
27967
28039
  const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
27968
28040
  const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
27969
- const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.includes(job)));
28041
+ const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.some((context) => contextMatchesJobId(job, context))));
27970
28042
  checks.push({
27971
28043
  ok: unblocking.length === 0,
27972
28044
  label: UNBLOCKING_GATE_JOBS_LABEL,
@@ -28051,7 +28123,7 @@ async function auditRepoCi(repo, callerDeps) {
28051
28123
  const body = await fetchFileContent(deps, repo, baseBranch, path2);
28052
28124
  if (body == null) continue;
28053
28125
  if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
28054
- if (!/^\s*concurrency:/m.test(body) || !/cancel-in-progress:\s*true/.test(body)) {
28126
+ if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*==\s*(['"])pull_request\1\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
28055
28127
  if (NO_CANCEL_WAIVER.test(body)) waived.push(path2);
28056
28128
  else missing.push(path2);
28057
28129
  }
@@ -29667,6 +29739,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
29667
29739
  init_cli_shared();
29668
29740
 
29669
29741
  // src/attach-to-project.ts
29742
+ init_github_quota();
29670
29743
  async function attachViaHubApp(cfg, contentNodeId) {
29671
29744
  const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
29672
29745
  return addIssueToProjectViaHub2(cfg, contentNodeId);
@@ -29827,6 +29900,7 @@ async function waitBoardEvents(since, waitS, deps) {
29827
29900
  }
29828
29901
 
29829
29902
  // src/board-commands.ts
29903
+ init_github_quota();
29830
29904
  function refuseRateLimited(e, json) {
29831
29905
  if (!isRateLimitedError(e)) return false;
29832
29906
  if (json) console.log(JSON.stringify(e.receipt));
@@ -35608,6 +35682,16 @@ async function suiteRcand(deps, opts) {
35608
35682
  ];
35609
35683
  if (opts.dryRun) return { plan };
35610
35684
  let record = readSuiteCut(deps.recordPath);
35685
+ if (record?.leadReleased && record.announcement === "pending") {
35686
+ const reason = await deps.announceSkipReason(record);
35687
+ if (!reason) throw new Error(`the finished cut ${record.leadRcTag} has an unverified announcement (pending); verify whether it was posted, then finish it with suite release --owner-go before a new cut (never post twice)`);
35688
+ record.announcement = "skipped";
35689
+ record.announcementNote = reason;
35690
+ writeSuiteCut(deps.recordPath, record);
35691
+ }
35692
+ if (record?.leadReleased && !record.announcement) {
35693
+ throw new Error(`the cut ${record.leadRcTag} already released the lead but never announced; finish it with suite release --owner-go before a new cut`);
35694
+ }
35611
35695
  if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
35612
35696
  const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
35613
35697
  if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
@@ -35649,6 +35733,17 @@ async function suiteRcand(deps, opts) {
35649
35733
  writeSuiteCut(deps.recordPath, record);
35650
35734
  continue;
35651
35735
  }
35736
+ const onHead = [];
35737
+ for (const tag of (await deps.rcTagsAt(member.repo, member.previousRc)).filter((t) => RC_TAG.test(t))) {
35738
+ if (!await deps.tagSha(member.repo, suiteReleaseTag(tag))) onHead.push(tag);
35739
+ }
35740
+ if (onHead.length > 1) throw new Error(`${member.repo}: several unreleased rc tags sit on its rc head (${onHead.join(", ")}); pin the one to reuse before the suite cut`);
35741
+ if (onHead.length === 1) {
35742
+ member.tag = onHead[0];
35743
+ member.sha = member.previousRc;
35744
+ writeSuiteCut(deps.recordPath, record);
35745
+ continue;
35746
+ }
35652
35747
  quiet.push(member);
35653
35748
  }
35654
35749
  if (record.members.every((m) => m.carried || quiet.includes(m))) {
@@ -35811,6 +35906,194 @@ Each member's live release must serve <data>/main/module.js at its main pin path
35811
35906
  return record;
35812
35907
  }
35813
35908
 
35909
+ // src/suite-rc-data.ts
35910
+ var RC_DATA_VAULT_REPO = "mutmutco/MM-Work";
35911
+ var RC_DATA_VERCEL_PROJECT = "prj_2Q1UcZ6tscIxHr4lPEhSD3jU1ZGm";
35912
+ var RC_DATA_VERCEL_TEAM = "team_ntTdWEQ896Eh20RT5Ri5XJDB";
35913
+ var RC_DATA_ALIAS = "rc.mutmut.work";
35914
+ var RC_DATA_GIT_BRANCH = "rc";
35915
+ var RC_DATA_BOUND_KEYS = [
35916
+ { envKey: "DATABASE_URL", liveVaultKey: "main/DATABASE_URL", rcVaultKey: "rc/DATABASE_URL" },
35917
+ { envKey: "MIGRATION_DATABASE_URL", liveVaultKey: "main/MIGRATION_DATABASE_URL", rcVaultKey: "rc/MIGRATION_DATABASE_URL" },
35918
+ { envKey: "MMW_DATA_KEY", liveVaultKey: "main/MMW_DATA_KEY", rcVaultKey: "rc/MMW_DATA_KEY" },
35919
+ { envKey: "GOOGLE_CLIENT_ID", liveVaultKey: "GOOGLE_CLIENT_ID", rcVaultKey: null },
35920
+ { envKey: "GOOGLE_CLIENT_SECRET", liveVaultKey: "GOOGLE_CLIENT_SECRET", rcVaultKey: null }
35921
+ ];
35922
+ var POLL_ATTEMPTS = 60;
35923
+ var POLL_INTERVAL_MS = 5e3;
35924
+ function withTeam(path2) {
35925
+ const url = new URL(`https://api.vercel.com${path2}`);
35926
+ url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
35927
+ return url.toString();
35928
+ }
35929
+ function envCollectionUrl2() {
35930
+ return withTeam(`/v10/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env`);
35931
+ }
35932
+ function envItemUrl2(id) {
35933
+ return withTeam(`/v9/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env/${encodeURIComponent(id)}`);
35934
+ }
35935
+ function aliasUrl() {
35936
+ return withTeam(`/v4/aliases/${RC_DATA_ALIAS}`);
35937
+ }
35938
+ function deploymentUrl(id) {
35939
+ return withTeam(`/v13/deployments/${encodeURIComponent(id)}`);
35940
+ }
35941
+ function redeployUrl() {
35942
+ const url = new URL("https://api.vercel.com/v13/deployments");
35943
+ url.searchParams.set("forceNew", "1");
35944
+ url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
35945
+ return url.toString();
35946
+ }
35947
+ function isRcBranchEnv(row, key) {
35948
+ if (row.key !== key) return false;
35949
+ if (row.gitBranch !== RC_DATA_GIT_BRANCH) return false;
35950
+ const target = Array.isArray(row.target) ? row.target.filter((item) => typeof item === "string") : [];
35951
+ return target.includes("preview");
35952
+ }
35953
+ function envsOf2(body) {
35954
+ if (Array.isArray(body)) return body;
35955
+ if (body && typeof body === "object" && Array.isArray(body.envs)) {
35956
+ return body.envs;
35957
+ }
35958
+ return null;
35959
+ }
35960
+ function scrub(reason, secrets) {
35961
+ let out = reason.replace(/\s+/g, " ").trim().slice(0, 300);
35962
+ for (const secret of secrets) {
35963
+ if (secret) out = out.split(secret).join("[redacted]");
35964
+ }
35965
+ return out || "unknown error";
35966
+ }
35967
+ async function readJson(deps, label, url, secrets, init) {
35968
+ const token = secrets[0];
35969
+ const headers = { ...init?.headers ?? {}, ...token ? { Authorization: `Bearer ${token}` } : {} };
35970
+ const method = init?.method ?? "GET";
35971
+ let res;
35972
+ try {
35973
+ res = await deps.fetch(url, { ...init, method, headers });
35974
+ } catch (e) {
35975
+ throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
35976
+ }
35977
+ let raw = "";
35978
+ try {
35979
+ raw = await res.text();
35980
+ } catch (e) {
35981
+ throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
35982
+ }
35983
+ if (!res.ok) throw new Error(`${label} failed: HTTP ${res.status}${raw ? `: ${scrub(raw, secrets)}` : ""}`);
35984
+ if (!raw.trim()) return {};
35985
+ try {
35986
+ return JSON.parse(raw);
35987
+ } catch {
35988
+ throw new Error(`${label} failed: response was not JSON`);
35989
+ }
35990
+ }
35991
+ function deploymentReadyState(body) {
35992
+ if (body && typeof body === "object") {
35993
+ const row = body;
35994
+ if (typeof row.status === "string") return row.status;
35995
+ if (typeof row.readyState === "string") return row.readyState;
35996
+ }
35997
+ return null;
35998
+ }
35999
+ function deploymentName(body) {
36000
+ if (body && typeof body === "object" && typeof body.name === "string") {
36001
+ return body.name;
36002
+ }
36003
+ return null;
36004
+ }
36005
+ function aliasDeploymentId(body) {
36006
+ if (body && typeof body === "object" && typeof body.deploymentId === "string") {
36007
+ return body.deploymentId;
36008
+ }
36009
+ return null;
36010
+ }
36011
+ async function runRcDataSwitch(deps, opts) {
36012
+ if (opts.to !== "live" && opts.to !== "rc") {
36013
+ throw new Error(`--to must be live or rc (got ${String(opts.to ?? "nothing")}); nothing was written`);
36014
+ }
36015
+ if (opts.apply && !opts.ownerGo) {
36016
+ throw new Error("--apply requires --owner-go (it touches the live data key); nothing was written");
36017
+ }
36018
+ const secrets = [];
36019
+ const token = await deps.readToken();
36020
+ secrets.push(token);
36021
+ if (!token) throw new Error("no _org/vercel/API_TOKEN; nothing was written");
36022
+ const listed = await readJson(deps, "GET envs", envCollectionUrl2(), secrets, { method: "GET" });
36023
+ const rows = envsOf2(listed);
36024
+ if (!rows) throw new Error("env list returned an unexpected shape; nothing was written");
36025
+ const plan = RC_DATA_BOUND_KEYS.map((bound) => {
36026
+ const source = opts.to === "live" ? bound.liveVaultKey : bound.rcVaultKey;
36027
+ const existing = rows.find((row) => isRcBranchEnv(row, bound.envKey) && typeof row.id === "string" && row.id.length > 0);
36028
+ const entryId = existing && typeof existing.id === "string" ? existing.id : null;
36029
+ if (source === null) return { action: existing ? "delete" : "noop", key: bound.envKey, entryId, source };
36030
+ return { action: entryId ? "patch" : "create", key: bound.envKey, entryId, source };
36031
+ });
36032
+ if (!opts.apply) {
36033
+ return { command: "suite rc-data", direction: opts.to, dryRun: true, plan, changed: [] };
36034
+ }
36035
+ const values = /* @__PURE__ */ new Map();
36036
+ for (const step of plan) {
36037
+ if (step.action !== "patch" && step.action !== "create") continue;
36038
+ const value = await deps.readVault(step.source);
36039
+ secrets.push(value);
36040
+ if (!value) throw new Error(`vault ${step.source} is missing or unreadable; nothing was written`);
36041
+ values.set(step.key, value);
36042
+ }
36043
+ const changed = [];
36044
+ for (const step of plan) {
36045
+ try {
36046
+ if (step.action === "patch") {
36047
+ await readJson(deps, `PATCH ${step.key}`, envItemUrl2(step.entryId), secrets, {
36048
+ method: "PATCH",
36049
+ headers: { "Content-Type": "application/json" },
36050
+ body: JSON.stringify({ value: values.get(step.key), type: "sensitive" })
36051
+ });
36052
+ changed.push(step.key);
36053
+ } else if (step.action === "create") {
36054
+ await readJson(deps, `POST ${step.key}`, envCollectionUrl2(), secrets, {
36055
+ method: "POST",
36056
+ headers: { "Content-Type": "application/json" },
36057
+ body: JSON.stringify({ key: step.key, value: values.get(step.key), type: "sensitive", target: ["preview"], gitBranch: RC_DATA_GIT_BRANCH })
36058
+ });
36059
+ changed.push(step.key);
36060
+ } else if (step.action === "delete") {
36061
+ await readJson(deps, `DELETE ${step.key}`, envItemUrl2(step.entryId), secrets, { method: "DELETE" });
36062
+ changed.push(step.key);
36063
+ }
36064
+ } catch (e) {
36065
+ const changedLabel = changed.length ? changed.join(", ") : "none";
36066
+ throw new Error(`${e.message}; changed: ${changedLabel}; no redeploy`);
36067
+ }
36068
+ }
36069
+ const aliasBefore = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
36070
+ if (!aliasBefore) throw new Error(`alias ${RC_DATA_ALIAS} has no deployment; envs changed: ${changed.join(", ")}; no redeploy`);
36071
+ const name = deploymentName(await readJson(deps, "GET deployment", deploymentUrl(aliasBefore), secrets, { method: "GET" }));
36072
+ if (!name) throw new Error(`deployment ${aliasBefore} has no name; envs changed: ${changed.join(", ")}; no redeploy`);
36073
+ const created = await readJson(deps, "POST redeploy", redeployUrl(), secrets, {
36074
+ method: "POST",
36075
+ headers: { "Content-Type": "application/json" },
36076
+ body: JSON.stringify({ name, deploymentId: aliasBefore, meta: { redeployReason: `MMI-Hub#7588 rc-data switch to ${opts.to}` } })
36077
+ });
36078
+ const newId = aliasDeploymentId({ deploymentId: created.id });
36079
+ if (!newId) throw new Error(`redeploy answered without an id; envs changed: ${changed.join(", ")}`);
36080
+ let state = null;
36081
+ for (let attempt = 0; attempt < POLL_ATTEMPTS; attempt += 1) {
36082
+ state = deploymentReadyState(await readJson(deps, "GET deployment", deploymentUrl(newId), secrets, { method: "GET" }));
36083
+ if (state === "READY") break;
36084
+ if (state === "ERROR" || state === "CANCELED" || state === "FAILED") {
36085
+ throw new Error(`redeploy ${newId} ended ${state}; envs changed: ${changed.join(", ")}`);
36086
+ }
36087
+ await deps.sleep(POLL_INTERVAL_MS);
36088
+ }
36089
+ if (state !== "READY") throw new Error(`redeploy ${newId} never reached READY (last: ${state ?? "unknown"}); envs changed: ${changed.join(", ")}`);
36090
+ const aliasAfter = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
36091
+ if (aliasAfter !== newId) {
36092
+ throw new Error(`alias ${RC_DATA_ALIAS} still points at ${aliasAfter ?? "nothing"} (expected ${newId}); envs changed: ${changed.join(", ")}`);
36093
+ }
36094
+ return { command: "suite rc-data", direction: opts.to, dryRun: false, plan, changed, deploymentId: newId, alias: RC_DATA_ALIAS };
36095
+ }
36096
+
35814
36097
  // src/suite-train-io.ts
35815
36098
  var import_node_fs43 = require("node:fs");
35816
36099
  var import_node_path43 = require("node:path");
@@ -39400,6 +39683,7 @@ function applyChecklistCheck(body, query, checked) {
39400
39683
 
39401
39684
  // src/command-register-collaboration.ts
39402
39685
  init_github_client();
39686
+ init_github_quota();
39403
39687
 
39404
39688
  // src/issue-commands.ts
39405
39689
  var import_node_fs34 = require("node:fs");
@@ -40340,6 +40624,7 @@ var import_node_path35 = require("node:path");
40340
40624
 
40341
40625
  // src/cross-repo-filing-issue.ts
40342
40626
  init_github_client();
40627
+ init_github_quota();
40343
40628
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
40344
40629
  return `mmi-cli devops pr merge ${prNumber} --repo ${prRepo}`;
40345
40630
  }
@@ -40442,6 +40727,7 @@ ${pr.body ?? ""}`;
40442
40727
  }
40443
40728
 
40444
40729
  // src/post-merge-recon.ts
40730
+ init_github_quota();
40445
40731
  var POST_MERGE_RECON_SCHEMA_VERSION = 1;
40446
40732
  function postMergeReconRetryCommand(repo, pr) {
40447
40733
  return crossRepoFilingRetryCommand(repo, Number(pr));
@@ -41244,6 +41530,7 @@ async function checkDocsIndexAtHead(opts, deps) {
41244
41530
 
41245
41531
  // src/pr-create-claim-guard.ts
41246
41532
  init_github_client();
41533
+ init_github_quota();
41247
41534
  var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
41248
41535
  function withRateLimitRetry(client, seams = {}) {
41249
41536
  const throttledPaths = /* @__PURE__ */ new Set();
@@ -44768,6 +45055,16 @@ var GIT_COMMIT_TIMEOUT_MS = 6e4;
44768
45055
  function trainGitArgs(args) {
44769
45056
  return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
44770
45057
  }
45058
+ async function announceSummaryLineCountRefusal(path2) {
45059
+ let content;
45060
+ try {
45061
+ content = await (0, import_promises9.readFile)(path2, "utf8");
45062
+ } catch (e) {
45063
+ return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
45064
+ }
45065
+ const lines2 = summaryFileLines(content);
45066
+ return lines2.length < 3 || lines2.length > 6 ? `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing` : null;
45067
+ }
44771
45068
  async function announceSummaryRefusal(path2, isHub = false) {
44772
45069
  let lines2;
44773
45070
  try {
@@ -45221,6 +45518,10 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
45221
45518
  const retryDeploy = new Option("--retry-deploy", "re-dispatch the FAILED rc deploy of the promoted candidate in the active rcand ledger \u2014 same tag and SHA, never re-cut or re-pushed (#7053)");
45222
45519
  trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
45223
45520
  trainCmd.option("--repo <owner/repo>", "dry-run plan for a target repo without relying on the current checkout; --apply still uses the current checkout").action(async (o) => {
45521
+ if (commandName === "release" && o.announceSummaryFile) {
45522
+ const refusal = await announceSummaryLineCountRefusal(o.announceSummaryFile);
45523
+ if (refusal) return fail(refusal);
45524
+ }
45224
45525
  try {
45225
45526
  await requireFreshTrainCli(commandName);
45226
45527
  } catch (e) {
@@ -45693,12 +45994,18 @@ async function withEnv(name, value, fn) {
45693
45994
  }
45694
45995
  }
45695
45996
  async function prepareRcCheckout(git3, repo) {
45696
- if ((await git3(["status", "--porcelain"])).trim()) throw new Error(`${repo}: checkout has uncommitted changes; refuse to switch it to rc for release`);
45997
+ await prepareLaneCheckout(git3, repo, "rc", "release");
45998
+ }
45999
+ async function prepareDevelopmentCheckout(git3, repo) {
46000
+ await prepareLaneCheckout(git3, repo, "development", "rcand");
46001
+ }
46002
+ async function prepareLaneCheckout(git3, repo, branch, lane) {
46003
+ if ((await git3(["status", "--porcelain"])).trim()) throw new Error(`${repo}: checkout has uncommitted changes; refuse to switch it to ${branch} for ${lane}`);
45697
46004
  await git3(["fetch", "origin", "--tags"]);
45698
- await git3(["switch", "rc"]);
45699
- await git3(["merge", "--ff-only", "origin/rc"]);
45700
- const [head, origin] = (await git3(["rev-parse", "HEAD", "origin/rc"])).trim().split(/\s+/);
45701
- if (!head || head !== origin) throw new Error(`${repo}: local rc ${head?.slice(0, 12)} is not origin/rc ${origin?.slice(0, 12)}; stop`);
46005
+ await git3(["switch", branch]);
46006
+ await git3(["merge", "--ff-only", `origin/${branch}`]);
46007
+ const [head, origin] = (await git3(["rev-parse", "HEAD", `origin/${branch}`])).trim().split(/\s+/);
46008
+ if (!head || head !== origin) throw new Error(`${repo}: local ${branch} ${head?.slice(0, 12)} is not origin/${branch} ${origin?.slice(0, 12)}; stop`);
45702
46009
  }
45703
46010
  function rcRunCauseFromLog(log) {
45704
46011
  const causes = [...new Set([...log.matchAll(/##\[error\](?!Process completed with exit code)([^\r\n]+)/g)].map((m) => m[1].trim()).filter(Boolean))];
@@ -45775,6 +46082,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
45775
46082
  }
45776
46083
  };
45777
46084
  const runRcand = async (repo, previousRc, rcWasAbsent) => inTrain(repo, async (deps) => {
46085
+ if (repo !== SUITE_REPO) await prepareDevelopmentCheckout((args) => deps.run("git", args), repo);
45778
46086
  await deps.run("git", ["fetch", "origin", "--tags"]);
45779
46087
  let rcNow;
45780
46088
  try {
@@ -45924,6 +46232,12 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
45924
46232
  }
45925
46233
  },
45926
46234
  tagSha,
46235
+ rcTagsAt: async (repo, sha) => {
46236
+ if (!/^[0-9a-f]{40}$/i.test(sha)) return [];
46237
+ const target = await checkout(repo);
46238
+ await exec("git", ["fetch", "--quiet", "origin", "--tags"], target, WATCH_TIMEOUT);
46239
+ return (await exec("git", ["tag", "--points-at", sha, "--list", "v*-rc.*"], target)).split("\n").map((t) => t.trim()).filter(Boolean);
46240
+ },
45927
46241
  contains: async (repo, ancestor, descendant) => {
45928
46242
  const target = await checkout(repo);
45929
46243
  await exec("git", ["fetch", "origin", "--tags"], target);
@@ -45987,6 +46301,17 @@ function parseMemberSummaryFiles(entries) {
45987
46301
  }
45988
46302
  return memberSummaryFiles;
45989
46303
  }
46304
+ function suiteRcDataProdDeps() {
46305
+ return {
46306
+ fetch: async (url, init) => {
46307
+ const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15e3) });
46308
+ return { ok: res.ok, status: res.status, text: () => res.text() };
46309
+ },
46310
+ readToken: readOrgVercelApiToken,
46311
+ readVault: async (vaultKey) => fetchSecretValue(makeSecretsDeps(await loadConfig()), vaultKey, { repo: RC_DATA_VAULT_REPO }),
46312
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
46313
+ };
46314
+ }
45990
46315
  function registerSuiteCommands(program3) {
45991
46316
  const suite = program3.command("suite").description("MM-Work suite status, candidate cut and release (#7262/#7263)");
45992
46317
  const leadRoot = async () => {
@@ -46059,6 +46384,31 @@ function registerSuiteCommands(program3) {
46059
46384
  return fail(`suite release: ${e.message}`);
46060
46385
  }
46061
46386
  });
46387
+ suite.command("rc-data").description("switch the rc Preview envs between the live and rc data sets as one bound switch (#7588)").option("--to <direction>", "switch source: live or rc").option("--apply", "write the switch (default is a dry-run plan)").option("--owner-go", "explicit approval: touches the live data key").option("--json", "structured receipt").action(async (o) => {
46388
+ try {
46389
+ if (o.to !== "live" && o.to !== "rc") throw new Error(`--to must be live or rc (got ${o.to ?? "nothing"}); nothing was written`);
46390
+ const result = await runRcDataSwitch(suiteRcDataProdDeps(), { to: o.to, apply: o.apply === true, ownerGo: o.ownerGo === true });
46391
+ if (o.json) {
46392
+ console.log(JSON.stringify(result, null, 2));
46393
+ return;
46394
+ }
46395
+ console.log([
46396
+ `suite rc-data ${result.dryRun ? "dry-run" : "receipt"} (--to ${result.direction}):`,
46397
+ ...result.plan.map((step) => {
46398
+ if (step.action === "patch") return ` - patch ${step.key} rc entry ${step.entryId} <- vault ${step.source}`;
46399
+ if (step.action === "create") return ` - create ${step.key} rc preview entry <- vault ${step.source}`;
46400
+ if (step.action === "delete") return ` - delete ${step.key} rc entry ${step.entryId} (generic Preview applies again)`;
46401
+ return ` - keep ${step.key} (no rc entry; generic Preview already applies)`;
46402
+ }),
46403
+ ...result.dryRun ? [" rerun with --apply --owner-go to write, then redeploy rc.mutmut.work"] : [
46404
+ ` - changed: ${result.changed.join(", ") || "none"}`,
46405
+ ` - redeployed ${result.deploymentId}; alias ${result.alias} verified`
46406
+ ]
46407
+ ].join("\n"));
46408
+ } catch (e) {
46409
+ return fail(`suite rc-data: ${e.message}`);
46410
+ }
46411
+ });
46062
46412
  suite.command("status").description("compare served versions with development, rc and main branch heads across MM-Work modules").option("--suite <name>", "suite name (mm-work)", "mm-work").option("--json", "structured output").action(async (o) => {
46063
46413
  if (o.suite !== "mm-work") return fail("suite status: only mm-work is supported");
46064
46414
  const github = defaultGitHubClient();
@@ -51534,9 +51884,13 @@ program2.addHelpText(
51534
51884
  \`mmi-cli commands\` shows the agentic map; \`mmi-cli <house> --help\` lists one house.
51535
51885
  `
51536
51886
  );
51887
+ var appActorCommand = "poll";
51537
51888
  function appActorDeps() {
51538
51889
  return {
51539
- fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
51890
+ fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, {
51891
+ repo: APP_VAULT_REPO,
51892
+ attribution: { command: appActorCommand, via: formatSessionIdentity(describeSessionIdentity()) }
51893
+ }),
51540
51894
  fetch: (url, init) => fetch(url, init)
51541
51895
  };
51542
51896
  }
@@ -51544,6 +51898,7 @@ setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
51544
51898
  var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
51545
51899
  registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
51546
51900
  const commandPath3 = commandPathOf(actionCommand);
51901
+ appActorCommand = commandPath3;
51547
51902
  emitCliCallTelemetry(commandPath3);
51548
51903
  try {
51549
51904
  await activateAppActor(commandPath3, process.env, () => mintInstallationToken(appActorDeps()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.62",
3
+ "version": "4.5.64",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",