@mutmutco/cli 4.3.35 → 4.3.37

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 +117 -20
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -7648,7 +7648,7 @@ function whereLabel(v) {
7648
7648
  function formatFindHits(intent, hits) {
7649
7649
  if (!hits.length) return `no catalog match for "${intent}". Browse with \`mmi-cli vault secrets catalog\`, or run \`mmi-cli vault secrets doctor\` for drift.`;
7650
7650
  const blocks = hits.map((h) => [`${h.key} (${whereLabel(h)}) \u2014 ${h.purpose}`, ` ${h.command}`].join("\n"));
7651
- return [`${hits.length} match(es) for "${intent}" (names + meaning only \u2014 consume keyless with \`secrets use\`):`, "", ...blocks].join("\n");
7651
+ return [`${hits.length} match(es) for "${intent}" (names + meaning only \u2014 consume keyless with \`mmi-cli vault secrets use\`):`, "", ...blocks].join("\n");
7652
7652
  }
7653
7653
  function formatCatalog(r) {
7654
7654
  const lines2 = [`catalog \u2014 ${r.count} secret(s) across ${r.groups.length} group(s):`, ""];
@@ -15617,10 +15617,10 @@ var rollout_plan_default = {
15617
15617
  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)."
15618
15618
  },
15619
15619
  baseline: {
15620
- version: "4.3.35",
15621
- tag: "v4.3.35",
15622
- commit: "837f809456ec",
15623
- npm: "@mutmutco/cli@4.3.35"
15620
+ version: "4.3.37",
15621
+ tag: "v4.3.37",
15622
+ commit: "37821457da10",
15623
+ npm: "@mutmutco/cli@4.3.37"
15624
15624
  },
15625
15625
  exitCriterion: "fleet-n-of-n",
15626
15626
  hubOnlyShortcut: "forbidden",
@@ -15637,14 +15637,14 @@ var rollout_plan_default = {
15637
15637
  repo: "mutmutco/mmi-hub",
15638
15638
  role: "canary",
15639
15639
  schedule: "train",
15640
- v3Target: "v4.3.35"
15640
+ v3Target: "v4.3.37"
15641
15641
  }
15642
15642
  ],
15643
15643
  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.",
15644
15644
  rollback: {
15645
15645
  independent: true,
15646
- mechanism: "npm dist-tag latest -> 4.3.35 and redeploy the Hub Lambda from tag v4.3.35 (837f809456ec); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15647
- v3Target: "v4.3.35 (@mutmutco/cli@4.3.35, tag commit 837f809456ec \u2014 last known-good release carrying the repo-index v4-only contract)"
15646
+ mechanism: "npm dist-tag latest -> 4.3.37 and redeploy the Hub Lambda from tag v4.3.37 (37821457da10); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15647
+ v3Target: "v4.3.37 (@mutmutco/cli@4.3.37, tag commit 37821457da10 \u2014 last known-good release carrying the repo-index v4-only contract)"
15648
15648
  }
15649
15649
  },
15650
15650
  {
@@ -18112,6 +18112,50 @@ async function enqueueAlignmentAutoMerge(deps, ctx, prNumber, prUrl, openedNote)
18112
18112
  }
18113
18113
  return { ...base, note: manual };
18114
18114
  }
18115
+ var ALIGNMENT_LAND_POLL_MS = 15e3;
18116
+ var ALIGNMENT_LAND_TIMEOUT_MS = 20 * 6e4;
18117
+ async function awaitAlignmentLanded(deps, ctx, leg, base, opts = {}) {
18118
+ if (leg.status !== "pr-pending" || !leg.autoMergeEnqueued || leg.prNumber === void 0) return leg;
18119
+ const sleep2 = resolveSleep(deps);
18120
+ const now = deps.now ?? Date.now;
18121
+ const startedAt = now();
18122
+ const timeoutMs = opts.timeoutMs ?? ALIGNMENT_LAND_TIMEOUT_MS;
18123
+ const n = leg.prNumber;
18124
+ deps.warn?.(`watching alignment PR #${n} (${ctx.repo}) until it lands on ${base}`);
18125
+ for (; ; ) {
18126
+ let view;
18127
+ try {
18128
+ view = JSON.parse(await deps.run("gh", ["pr", "view", String(n), "--repo", ctx.repo, "--json", "state,mergeCommit"]));
18129
+ } catch (e) {
18130
+ deps.warn?.(`alignment PR #${n}: read failed (${describeReadError(e)}) \u2014 retrying`);
18131
+ }
18132
+ if (view?.state === "MERGED") {
18133
+ const oid = typeof view.mergeCommit?.oid === "string" ? view.mergeCommit.oid : void 0;
18134
+ let parents;
18135
+ if (oid) {
18136
+ try {
18137
+ parents = Number(clean2(await deps.run("gh", ["api", `repos/${ctx.repo}/commits/${oid}`, "--jq", ".parents | length"])));
18138
+ } catch {
18139
+ parents = void 0;
18140
+ }
18141
+ }
18142
+ if (parents === 1) {
18143
+ const squashNote = `alignment PR #${n} landed as a SQUASH (${oid?.slice(0, 12)} has one parent), not the true merge the Merge floor requires \u2014 re-align ${base} by true merge before the next train`;
18144
+ deps.warn?.(squashNote);
18145
+ return { ...leg, note: `${leg.note} \u2014 ${squashNote}` };
18146
+ }
18147
+ return { status: "aligned", alignment: "already-merged", prNumber: n, ...leg.prUrl ? { prUrl: leg.prUrl } : {}, note: `alignment PR #${n} landed on ${base} by true merge (watched)` };
18148
+ }
18149
+ if (view?.state === "CLOSED") {
18150
+ return { ...leg, note: `${leg.note} \u2014 alignment PR #${n} was CLOSED without merging; rerun the roll-forward` };
18151
+ }
18152
+ if (now() - startedAt >= timeoutMs) {
18153
+ deps.warn?.(`alignment PR #${n} still open after ${Math.round(timeoutMs / 6e4)}m \u2014 leaving the alignment leg pending; fold it later with mmi-cli devops release --resume --watch`);
18154
+ return leg;
18155
+ }
18156
+ await sleep2(ALIGNMENT_LAND_POLL_MS);
18157
+ }
18158
+ }
18115
18159
  async function alignRcForward(deps, ctx, tag) {
18116
18160
  await runGitRemoteRead(deps, ["fetch", "origin", "main", "rc"]);
18117
18161
  const required = await discoverRequiredCheckContexts(deps, ctx, "rc");
@@ -21625,6 +21669,17 @@ async function runTrainDoctor(input) {
21625
21669
  } catch (e) {
21626
21670
  add({ code: "contexts-not-tag-addressable", severity: "blocker", source: "local", title: `required ${stage} check(s) cannot materialize on a tag push (#5428)`, remedy: message(e) });
21627
21671
  }
21672
+ if (startBranch) {
21673
+ const tip = await git3(["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${startBranch}`]);
21674
+ const checkRuns = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/check-runs`, "--jq", TRAIN_CHECK_RUNS_JQ]));
21675
+ const statuses = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/status`, "--jq", TRAIN_COMMIT_STATUS_JQ]));
21676
+ const observed = required.filter((c) => checkRuns.some((r) => r.name === c) || statuses.some((s) => s.context === c));
21677
+ const red = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "failed");
21678
+ const inFlight = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "pending");
21679
+ const at = `${startBranch}@${tip.slice(0, 12)}`;
21680
+ if (red.length) add({ code: "required-check-red", severity: "blocker", source: "origin", title: `required ${stage} check(s) failed on the lane's start branch tip: ${red.join(", ")} at ${at}`, remedy: `--heal never clears this one \u2014 a red trunk needs a fix, not a heal: land the failure's fix on ${startBranch} through a CI-gated PR, then rerun once its run is green (#6461)` });
21681
+ else if (inFlight.length) add({ code: "required-check-red", severity: "warning", source: "origin", title: `required ${stage} check(s) still in flight on the lane's start branch tip: ${inFlight.join(", ")} at ${at}`, remedy: `wait for the run to conclude and rerun \u2014 the tip's verdict is not known yet, and the train would tag before it is` });
21682
+ }
21628
21683
  }
21629
21684
  } catch (e) {
21630
21685
  unverified(`required status checks on ${stage}`, e);
@@ -22473,7 +22528,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
22473
22528
  await recordPhase(ledger, deps, anchors, "alignment", phaseEntry({ state: "skipped", sha, note: "rcand aligns no branch \u2014 alignment rides the release" }), { ...opts, landed });
22474
22529
  return dispatch;
22475
22530
  }
22476
- async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger) {
22531
+ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
22477
22532
  const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
22478
22533
  const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
22479
22534
  try {
@@ -22578,6 +22633,7 @@ ${recovery.note}`), recoveryInput);
22578
22633
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
22579
22634
  await recordPhase(ledger, deps, ledgerAnchors, "githubRelease", phaseEntry({ state: "complete", sha: releaseSha, runUrl: releaseUrl, note: `verified GitHub Release ${tag} against ${releaseSha.slice(0, 12)}` }), { landed: "the immutable tag, the green required-check wall, and the origin/main fast-forward" });
22580
22635
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
22636
+ let alignment = alignEarly ? await alignEarly() : void 0;
22581
22637
  const autoRunSince = (deps.now ?? Date.now)();
22582
22638
  const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
22583
22639
  const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
@@ -22622,7 +22678,13 @@ ${recovery.note}`), recoveryInput);
22622
22678
  });
22623
22679
  let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
22624
22680
  if (publishSkipNote) dispatch = { ...dispatch, note: `${dispatch.note}; tenant-publish.yml skipped (${publishSkipNote})` };
22625
- return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors };
22681
+ if (watch && alignment) {
22682
+ alignment = {
22683
+ dev: await awaitAlignmentLanded(deps, ctx, alignment.dev, "development"),
22684
+ ...alignment.rc ? { rc: await awaitAlignmentLanded(deps, ctx, alignment.rc, "rc") } : {}
22685
+ };
22686
+ }
22687
+ return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors, ...alignment ? { alignment } : {} };
22626
22688
  }
22627
22689
  function resumeStateOf(status) {
22628
22690
  return status === "complete" ? "aligned" : status;
@@ -23010,6 +23072,8 @@ ${recovery.note}`), recoveryInput);
23010
23072
  steps.push("verified the Release published against the tagged commit");
23011
23073
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, runUrl: releaseUrl, note: "verified the Release published against the tagged commit" }), { strict: ledgerMode === "strict", landed: "the immutable tag, the re-proven wall, and the origin/main push" });
23012
23074
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
23075
+ let devRollForward = await rollDevelopmentForward(deps, ctx, tag);
23076
+ let rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
23013
23077
  const autoRunSince = (deps.now ?? Date.now)();
23014
23078
  const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
23015
23079
  const recoveredDeployDispatch = await recoverFailedDispatchPhase(
@@ -23045,9 +23109,11 @@ ${recovery.note}`), recoveryInput);
23045
23109
  landed: "the immutable tag, the re-proven wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
23046
23110
  });
23047
23111
  const dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
23048
- const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
23112
+ if (watch) {
23113
+ devRollForward = await awaitAlignmentLanded(deps, ctx, devRollForward, "development");
23114
+ if (rcAlignment) rcAlignment = await awaitAlignmentLanded(deps, ctx, rcAlignment, "rc");
23115
+ }
23049
23116
  steps.push(`development roll-forward: ${devRollForward.status}`);
23050
- const rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
23051
23117
  if (rcAlignment) steps.push(`rc alignment: ${rcAlignment.status}`);
23052
23118
  const followUp = deriveTrainFollowUpStatus({
23053
23119
  projectInfo: "ok",
@@ -23388,9 +23454,25 @@ ${spent.note}`), rcandRecovery(tag2));
23388
23454
  const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
23389
23455
  return { versionFold: versionFold3, releaseSha: releaseSha3 };
23390
23456
  });
23391
- const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2, ledgerAnchors: ledgerAnchors2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2, branchHints, ledger);
23392
- const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
23393
- await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
23457
+ const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2, ledgerAnchors: ledgerAnchors2, alignment: alignment2 } = await completeMainRelease(
23458
+ deps,
23459
+ ctx,
23460
+ meta,
23461
+ deployModel2,
23462
+ watch,
23463
+ options,
23464
+ tag2,
23465
+ releaseSha2,
23466
+ "development",
23467
+ preFold2,
23468
+ tagProbe2,
23469
+ branchHints,
23470
+ ledger,
23471
+ // #6468: alignment PRs open before the publish wait; rc aligns only on the --dev lane of a repo that has one.
23472
+ async () => ({ dev: await rollDevelopmentForward(deps, ctx, tag2), ...!directTrack && hasRcBranch ? { rc: await alignRcForward(deps, ctx, tag2) } : {} })
23473
+ );
23474
+ const devRollForward2 = alignment2?.dev ?? await rollDevelopmentForward(deps, ctx, tag2);
23475
+ await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, alignment2?.rc)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
23394
23476
  const ledgerReport2 = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
23395
23477
  if (directTrack) {
23396
23478
  return {
@@ -23419,8 +23501,7 @@ ${spent.note}`), rcandRecovery(tag2));
23419
23501
  };
23420
23502
  }
23421
23503
  const retirement2 = hasRcBranch ? await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease) : { status: "not-applicable", note: "no origin/rc branch \u2014 rc runtime retirement skipped" };
23422
- const rcAlignment2 = hasRcBranch ? await alignRcForward(deps, ctx, tag2) : void 0;
23423
- await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
23504
+ const rcAlignment2 = alignment2?.rc;
23424
23505
  const fullLedgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
23425
23506
  const environments2 = await buildEnvironments(deps, ctx, deployModel2, d2.deployStatus, retirement2);
23426
23507
  return {
@@ -23482,10 +23563,26 @@ ${spent.note}`), rcandRecovery(tag2));
23482
23563
  const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
23483
23564
  return { versionFold: versionFold2, releaseSha: releaseSha2 };
23484
23565
  });
23485
- const { checks, releaseUrl, announceNote, dispatch: d, ledgerAnchors } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe, branchHints, ledger);
23566
+ const { checks, releaseUrl, announceNote, dispatch: d, ledgerAnchors, alignment } = await completeMainRelease(
23567
+ deps,
23568
+ ctx,
23569
+ meta,
23570
+ deployModel,
23571
+ watch,
23572
+ options,
23573
+ tag,
23574
+ releaseSha,
23575
+ "rc",
23576
+ preFold,
23577
+ tagProbe,
23578
+ branchHints,
23579
+ ledger,
23580
+ // #6468: both alignment PRs open before the publish wait.
23581
+ async () => ({ dev: await rollDevelopmentForward(deps, ctx, tag), rc: await alignRcForward(deps, ctx, tag) })
23582
+ );
23486
23583
  const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
23487
- const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
23488
- const rcAlignment = await alignRcForward(deps, ctx, tag);
23584
+ const devRollForward = alignment?.dev ?? await rollDevelopmentForward(deps, ctx, tag);
23585
+ const rcAlignment = alignment?.rc ?? await alignRcForward(deps, ctx, tag);
23489
23586
  await recordPhase(ledger, deps, ledgerAnchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward, rcAlignment)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
23490
23587
  const ledgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
23491
23588
  const environments = await buildEnvironments(deps, ctx, deployModel, d.deployStatus, retirement);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.35",
3
+ "version": "4.3.37",
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",