@mutmutco/cli 4.1.10 → 4.1.12

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 +89 -14
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -13152,10 +13152,10 @@ var rollout_plan_default = {
13152
13152
  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)."
13153
13153
  },
13154
13154
  baseline: {
13155
- version: "4.1.10",
13156
- tag: "v4.1.10",
13157
- commit: "2a913c6e917e",
13158
- npm: "@mutmutco/cli@4.1.10"
13155
+ version: "4.1.12",
13156
+ tag: "v4.1.12",
13157
+ commit: "306701c103b7",
13158
+ npm: "@mutmutco/cli@4.1.12"
13159
13159
  },
13160
13160
  exitCriterion: "fleet-n-of-n",
13161
13161
  hubOnlyShortcut: "forbidden",
@@ -13172,14 +13172,14 @@ var rollout_plan_default = {
13172
13172
  repo: "mutmutco/mmi-hub",
13173
13173
  role: "canary",
13174
13174
  schedule: "train",
13175
- v3Target: "v4.1.10"
13175
+ v3Target: "v4.1.12"
13176
13176
  }
13177
13177
  ],
13178
13178
  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.",
13179
13179
  rollback: {
13180
13180
  independent: true,
13181
- mechanism: "npm dist-tag latest -> 4.1.10 and redeploy the Hub Lambda from tag v4.1.10 (2a913c6e917e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
- v3Target: "v4.1.10 (@mutmutco/cli@4.1.10, tag commit 2a913c6e917e \u2014 last known-good release carrying the repo-index v4-only contract)"
13181
+ mechanism: "npm dist-tag latest -> 4.1.12 and redeploy the Hub Lambda from tag v4.1.12 (306701c103b7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
+ v3Target: "v4.1.12 (@mutmutco/cli@4.1.12, tag commit 306701c103b7 \u2014 last known-good release carrying the repo-index v4-only contract)"
13183
13183
  }
13184
13184
  },
13185
13185
  {
@@ -17338,6 +17338,9 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
17338
17338
  let tagPush;
17339
17339
  try {
17340
17340
  await assertComputedReleaseTagStillNext(deps, tag);
17341
+ if (deployModel === "hub-serverless") {
17342
+ await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
17343
+ }
17341
17344
  tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
17342
17345
  } catch (e) {
17343
17346
  throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
@@ -24450,6 +24453,9 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
24450
24453
  }
24451
24454
  const required = await discoverRequiredCheckContexts(deps, ctx, "main");
24452
24455
  assertTagAddressableRequiredContexts(deps, required, ctx.repo);
24456
+ if (deployModel === "hub-serverless") {
24457
+ await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", version, "--release-commit", mergedSha]);
24458
+ }
24453
24459
  const tagPush = await ensureTagPushed(deps, tag, mergedSha);
24454
24460
  const tagPushSince = Date.now();
24455
24461
  const tagNote = tagPush.note;
@@ -33658,6 +33664,27 @@ function selectSafeWorktreeCwd(worktrees, targetPath, options) {
33658
33664
  const exists = options?.pathExists ?? (() => true);
33659
33665
  return worktrees.find((w) => !samePath(w.path, targetPath) && exists(w.path))?.path;
33660
33666
  }
33667
+ function formatGitCommandError(e) {
33668
+ if (!(e instanceof Error)) return String(e);
33669
+ const err = e;
33670
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
33671
+ const stdout = typeof err.stdout === "string" ? err.stdout.trim() : "";
33672
+ const message = err.message.trim();
33673
+ if (stderr) return stderr;
33674
+ if (message && !/^Command failed:/i.test(message)) return message;
33675
+ if (stdout) return stdout;
33676
+ return message || "git command failed";
33677
+ }
33678
+ function isWorktreePathRegistered(worktrees, wtPath) {
33679
+ return worktrees.some((w) => samePath(w.path, wtPath));
33680
+ }
33681
+ function isNotAWorkingTreeMessage(error) {
33682
+ return /is not a working tree/i.test(error);
33683
+ }
33684
+ function formatWorktreeRemovalFailureDetail(options) {
33685
+ const detail = options.removeError.trim() || "git worktree remove exited nonzero";
33686
+ return `${detail}; registered=${options.registered ? "yes" : "no"}; pathExists=${options.pathExists ? "yes" : "no"}; path=${options.wtPath}`;
33687
+ }
33661
33688
  async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
33662
33689
  try {
33663
33690
  const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
@@ -33685,14 +33712,52 @@ async function teardownWorktreeStage(worktreePath) {
33685
33712
  return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33686
33713
  }
33687
33714
  }
33688
- async function removeWorktree(wtPath, git3) {
33689
- try {
33690
- await git3(["worktree", "remove", "--force", wtPath]);
33715
+ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExists) {
33716
+ const attemptRemove = async () => {
33717
+ try {
33718
+ await git3(["worktree", "remove", "--force", wtPath]);
33719
+ return void 0;
33720
+ } catch (e) {
33721
+ return formatGitCommandError(e);
33722
+ }
33723
+ };
33724
+ const firstError = await attemptRemove();
33725
+ if (!firstError) {
33691
33726
  await git3(["worktree", "prune"]).catch(() => "");
33692
33727
  return { status: "removed" };
33693
- } catch (e) {
33694
- return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33695
33728
  }
33729
+ let worktrees = await listWorktrees().catch(() => []);
33730
+ if (!isWorktreePathRegistered(worktrees, wtPath)) {
33731
+ await git3(["worktree", "prune"]).catch(() => "");
33732
+ return { status: "removed", reason: "reconciled-unregistered" };
33733
+ }
33734
+ const retryError = await attemptRemove();
33735
+ if (!retryError) {
33736
+ await git3(["worktree", "prune"]).catch(() => "");
33737
+ return { status: "removed", reason: "reconciled-after-retry" };
33738
+ }
33739
+ if (isNotAWorkingTreeMessage(retryError) || isNotAWorkingTreeMessage(firstError)) {
33740
+ worktrees = await listWorktrees().catch(() => worktrees);
33741
+ if (!isWorktreePathRegistered(worktrees, wtPath)) {
33742
+ await git3(["worktree", "prune"]).catch(() => "");
33743
+ return { status: "removed", reason: "reconciled-not-a-working-tree" };
33744
+ }
33745
+ }
33746
+ worktrees = await listWorktrees().catch(() => worktrees);
33747
+ const registered = isWorktreePathRegistered(worktrees, wtPath);
33748
+ if (!registered) {
33749
+ await git3(["worktree", "prune"]).catch(() => "");
33750
+ return { status: "removed", reason: "reconciled-unregistered" };
33751
+ }
33752
+ return {
33753
+ status: "failed",
33754
+ error: formatWorktreeRemovalFailureDetail({
33755
+ removeError: retryError || firstError,
33756
+ registered,
33757
+ pathExists: pathExists(wtPath),
33758
+ wtPath
33759
+ })
33760
+ };
33696
33761
  }
33697
33762
  async function cleanupPrMergeLocalBranch(branch, options) {
33698
33763
  const report = { branch };
@@ -33797,7 +33862,12 @@ async function cleanupPrMergeLocalBranch(branch, options) {
33797
33862
  report.localBranch = { name: branch, status: "not-attempted", reason: "archive-failed" };
33798
33863
  return report;
33799
33864
  }
33800
- const removal = await removeWorktree(wtPath, git3);
33865
+ const removal = await removeWorktreeWithReconcile(
33866
+ wtPath,
33867
+ git3,
33868
+ async () => parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"])),
33869
+ pathExists
33870
+ );
33801
33871
  if (removal.status === "failed") {
33802
33872
  report.worktree = {
33803
33873
  path: wtPath,
@@ -33813,6 +33883,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
33813
33883
  report.worktree = {
33814
33884
  path: wtPath,
33815
33885
  status: "removed",
33886
+ ...removal.reason ? { reason: removal.reason } : {},
33816
33887
  artifactsArchive,
33817
33888
  stageTeardown,
33818
33889
  tmpEvidenceCount: tmpEvidence.length
@@ -33831,7 +33902,11 @@ function renderPrMergeCleanupLines(cleanup) {
33831
33902
  if (!wt?.path) return [];
33832
33903
  const lines = [];
33833
33904
  if (wt.status === "removed") {
33834
- lines.push(`pr merge: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
33905
+ if (wt.reason?.startsWith("reconciled")) {
33906
+ lines.push(`pr merge: worktree ${wt.path} remove reported failure but git no longer registers it \u2014 deleted local branch`);
33907
+ } else {
33908
+ lines.push(`pr merge: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
33909
+ }
33835
33910
  } else if (wt.status === "refused") {
33836
33911
  lines.push(`pr merge: worktree ${wt.path} kept \u2014 ${wt.reason ?? "refused"}${wt.error ? `: ${wt.error}` : ""}`);
33837
33912
  if (wt.tmpEvidenceCount) lines.push(`pr merge: ${wt.tmpEvidenceCount} tmp path(s) newer than branch base would have been destroyed`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.1.10",
3
+ "version": "4.1.12",
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",