@mutmutco/cli 3.111.0 → 3.113.0

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 +52 -14
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -7686,14 +7686,27 @@ function validateDeadWorktreeDirCleanup(planned, inspected) {
7686
7686
  }
7687
7687
  return { ok: true };
7688
7688
  }
7689
- function validateDeadWorktreeDirContent(planned, inspected) {
7689
+ function describeLeftoverEntries(entries) {
7690
+ const shown = entries.slice(0, 8);
7691
+ const more = entries.length > shown.length ? `, +${entries.length - shown.length} more` : "";
7692
+ return `${shown.join(", ")}${more}`;
7693
+ }
7694
+ function validateDeadWorktreeDirContent(planned, inspected, options = {}) {
7690
7695
  if (!inspected.entries) return { ok: false, reason: inspected.error ?? "unable to inspect directory contents" };
7691
7696
  const entries = inspected.entries.filter(Boolean);
7692
7697
  if (planned.reason === "orphaned-folder") {
7693
- return entries.length === 0 ? { ok: true } : { ok: false, reason: "orphaned worktree directory still contains files" };
7698
+ if (entries.length === 0 || options.force) return { ok: true };
7699
+ return {
7700
+ ok: false,
7701
+ reason: `orphaned worktree directory still contains files: ${describeLeftoverEntries(entries)} \u2014 re-run with --force to remove anyway`
7702
+ };
7694
7703
  }
7695
7704
  if (planned.reason === "dangling-gitdir") {
7696
- return entries.length === 1 && entries[0] === ".git" ? { ok: true } : { ok: false, reason: "dangling worktree directory still contains files" };
7705
+ if (entries.length === 1 && entries[0] === ".git" || options.force) return { ok: true };
7706
+ return {
7707
+ ok: false,
7708
+ reason: `dangling worktree directory (git registration already severed) still contains files: ${describeLeftoverEntries(entries)} \u2014 re-run with --force to remove anyway`
7709
+ };
7697
7710
  }
7698
7711
  return { ok: false, reason: "unknown dead worktree cleanup kind" };
7699
7712
  }
@@ -13167,6 +13180,7 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
13167
13180
  if (!crons.length && !header.schedule) return null;
13168
13181
  const basename6 = workflowPath.split("/").pop() ?? workflowPath;
13169
13182
  const expectedId = `${repo}/${basename6.replace(/\.ya?ml$/, "")}`;
13183
+ if (STRAY_CRON_EXEMPT.has(expectedId)) return null;
13170
13184
  const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
13171
13185
  if (missing.length) {
13172
13186
  throw new Error(`schedules register: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
@@ -13252,7 +13266,10 @@ async function fetchTrainAuthority(repo, deps) {
13252
13266
  headers: { Authorization: `Bearer ${token}` }
13253
13267
  });
13254
13268
  if (res.status === 426) return { ok: false, error: upgradeRequiredError(res, await res.json().catch(() => null)) };
13255
- if (!res.ok) return { ok: false, error: `train-authority HTTP ${res.status}` };
13269
+ if (!res.ok) {
13270
+ const detail = (await res.json().catch(() => null))?.error;
13271
+ return { ok: false, error: detail ? `train-authority HTTP ${res.status}: ${detail}` : `train-authority HTTP ${res.status}` };
13272
+ }
13256
13273
  const body = await res.json();
13257
13274
  if (typeof body?.train !== "boolean" || !body.role) return { ok: false, error: "malformed train-authority response" };
13258
13275
  return { ok: true, authority: body };
@@ -15618,10 +15635,10 @@ var rollout_plan_default = {
15618
15635
  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)."
15619
15636
  },
15620
15637
  baseline: {
15621
- version: "3.111.0",
15622
- tag: "v3.111.0",
15623
- commit: "ef48be2432be",
15624
- npm: "@mutmutco/cli@3.111.0"
15638
+ version: "3.113.0",
15639
+ tag: "v3.113.0",
15640
+ commit: "1d1e521aa96f",
15641
+ npm: "@mutmutco/cli@3.113.0"
15625
15642
  },
15626
15643
  exitCriterion: "fleet-n-of-n",
15627
15644
  hubOnlyShortcut: "forbidden",
@@ -15638,14 +15655,14 @@ var rollout_plan_default = {
15638
15655
  repo: "mutmutco/mmi-hub",
15639
15656
  role: "canary",
15640
15657
  schedule: "train",
15641
- v3Target: "v3.111.0"
15658
+ v3Target: "v3.113.0"
15642
15659
  }
15643
15660
  ],
15644
15661
  rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
15645
15662
  rollback: {
15646
15663
  independent: true,
15647
- mechanism: "npm dist-tag latest -> 3.111.0 and redeploy the Hub Lambda from tag v3.111.0 (ef48be2432be); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15648
- v3Target: "v3.111.0 (@mutmutco/cli@3.111.0, tag commit ef48be2432be \u2014 the preserved latest-v3 distribution, D6b)"
15664
+ mechanism: "npm dist-tag latest -> 3.113.0 and redeploy the Hub Lambda from tag v3.113.0 (1d1e521aa96f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15665
+ v3Target: "v3.113.0 (@mutmutco/cli@3.113.0, tag commit 1d1e521aa96f \u2014 the preserved latest-v3 distribution, D6b)"
15649
15666
  }
15650
15667
  },
15651
15668
  {
@@ -32802,7 +32819,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
32802
32819
  result.failed.push(`${wt.path}: skipped dead-dir removal (${stillValid.reason})`);
32803
32820
  continue;
32804
32821
  }
32805
- const contentSafe = validateDeadWorktreeDirContent(wt, inspectDeadWorktreeDirContent(wt.path));
32822
+ const contentSafe = validateDeadWorktreeDirContent(wt, inspectDeadWorktreeDirContent(wt.path), { force: opts.force });
32806
32823
  if (!contentSafe.ok) {
32807
32824
  result.failed.push(`${wt.path}: skipped dead-dir removal (${contentSafe.reason})`);
32808
32825
  continue;
@@ -33780,6 +33797,17 @@ function landSteps(branch, hasWorktree, hasStage) {
33780
33797
  steps.push("prune worktree metadata");
33781
33798
  return steps;
33782
33799
  }
33800
+ function describeLandUnresolvableWorktree(inspection) {
33801
+ if (inspection.gitType === "missing") {
33802
+ return "this directory has no .git at all \u2014 its worktree registration is gone";
33803
+ }
33804
+ if (inspection.gitType === "file" && inspection.gitDirExists === false) {
33805
+ const match = /^gitdir:\s*(.+)\s*$/im.exec(inspection.gitFileContent ?? "");
33806
+ const gitdir = match?.[1]?.trim();
33807
+ return `this directory's .git points at ${gitdir ? `'${gitdir}'` : "its admin dir"}, which no longer exists \u2014 its worktree registration was severed`;
33808
+ }
33809
+ return void 0;
33810
+ }
33783
33811
  function orphanLandSteps(lostBranch, hasStage) {
33784
33812
  const steps = [];
33785
33813
  if (hasStage) steps.push("stop spawned dev stage");
@@ -34012,7 +34040,17 @@ function registerWorktreeCommands(program3) {
34012
34040
  if (o.apply && o.dryRun) return fail("worktree land: choose either --dry-run or --apply");
34013
34041
  const apply = Boolean(o.apply);
34014
34042
  try {
34015
- const wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
34043
+ let wtPath;
34044
+ try {
34045
+ wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
34046
+ } catch (e) {
34047
+ const reason = describeLandUnresolvableWorktree(inspectSiblingWorktreeDir(process.cwd(), ""));
34048
+ if (!reason) throw e;
34049
+ return fail(
34050
+ `worktree land: ${reason}. It cannot be landed from inside itself \u2014 cd to the primary checkout and run \`mmi-cli worktree gc --apply\` (add --force if this directory still holds leftover files) to clear it.`,
34051
+ { code: ERROR_CODES.ERR_BAD_ENUM }
34052
+ );
34053
+ }
34016
34054
  const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
34017
34055
  const symbolicBranch = (await execFileP2(
34018
34056
  "git",
@@ -39759,7 +39797,7 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
39759
39797
  process.exit(process.exitCode ?? 0);
39760
39798
  });
39761
39799
  });
39762
- gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to read PER BRANCH ? an effort bound, not a correctness one: merge state is resolved per branch, so no branch is ever skipped for being old (#4227)", "200").option("--force", "remove worktrees even when the ownership registry shows another session created or worked in them recently (#3580)").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471) ? descends into this repo container when present; ownership, dead-dir, and content guards still apply").action(async (o) => {
39800
+ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to read PER BRANCH ? an effort bound, not a correctness one: merge state is resolved per branch, so no branch is ever skipped for being old (#4227)", "200").option("--force", "remove worktrees even when the ownership registry shows another session created or worked in them recently (#3580), or when a dead worktree directory still holds leftover files (#4779)").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471) ? descends into this repo container when present; ownership, dead-dir, and content guards still apply").action(async (o) => {
39763
39801
  if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
39764
39802
  if (o.scratch) {
39765
39803
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.111.0",
3
+ "version": "3.113.0",
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",