@mutmutco/cli 3.46.0 → 3.48.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 +64 -18
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -4547,12 +4547,37 @@ function isAgentScratchPath(path2) {
4547
4547
  const normalized = path2.replace(/\\/g, "/").trim();
4548
4548
  return /^tmp_[^/]+$/.test(normalized);
4549
4549
  }
4550
+ var PUSH_WALL_AGENT_CONFIG_DIRS = [".claude/", ".cursor/rules/", ".codex/", ".agents/"];
4551
+ function isUncommittableAgentConfigPath(path2) {
4552
+ const normalized = path2.replace(/\\/g, "/").trim();
4553
+ return PUSH_WALL_AGENT_CONFIG_DIRS.some((dir) => normalized === dir || normalized === dir.slice(0, -1) || normalized.startsWith(dir) || normalized.includes(`/${dir}`));
4554
+ }
4555
+ function splitPorcelainLine(line) {
4556
+ return { status: line.slice(0, 2), path: line.slice(3).split(" -> ")[0]?.trim() ?? "" };
4557
+ }
4558
+ function isExemptPorcelainLine(status, path2) {
4559
+ if (isAgentScratchPath(path2)) return true;
4560
+ return status === "??" && isUncommittableAgentConfigPath(path2);
4561
+ }
4562
+ function porcelainBlockingPaths(porcelain) {
4563
+ const blocking = [];
4564
+ for (const line of porcelain.split("\n")) {
4565
+ if (!line.trim()) continue;
4566
+ const { status, path: path2 } = splitPorcelainLine(line);
4567
+ if (path2 !== "" && !isExemptPorcelainLine(status, path2)) blocking.push(path2);
4568
+ }
4569
+ return blocking;
4570
+ }
4550
4571
  function porcelainHasBlockingChanges(porcelain) {
4551
- return porcelain.split("\n").some((line) => {
4552
- if (!line.trim()) return false;
4553
- const path2 = line.slice(3).split(" -> ")[0]?.trim() ?? "";
4554
- return path2 !== "" && !isAgentScratchPath(path2);
4555
- });
4572
+ return porcelainBlockingPaths(porcelain).length > 0;
4573
+ }
4574
+ var BLOCKING_PATH_PREVIEW_LIMIT = 10;
4575
+ function describeBlockingPaths(porcelain) {
4576
+ const paths = porcelainBlockingPaths(porcelain);
4577
+ if (paths.length === 0) return "";
4578
+ const shown = paths.slice(0, BLOCKING_PATH_PREVIEW_LIMIT);
4579
+ const rest = paths.length - shown.length;
4580
+ return ` \u2014 blocking: ${shown.join(", ")}${rest > 0 ? `, \u2026 and ${rest} more` : ""}`;
4556
4581
  }
4557
4582
 
4558
4583
  // src/local-train-sync.ts
@@ -9403,6 +9428,12 @@ var MANAGED_GITIGNORE_LINES = [
9403
9428
  "docs/superpowers/",
9404
9429
  ".playwright-mcp/",
9405
9430
  ".claude/worktrees/",
9431
+ // #3425: local agent config the org push wall (`mmi-no-agent-files-org`) forbids committing, at any
9432
+ // depth — leaving it untracked is the ONLY legal state, so it is pure `git status` noise. Only the two
9433
+ // dirs no repo has cause to track: `.claude/` is excluded because MMI-Hub tracks `.claude/settings.json`,
9434
+ // and `.cursor/rules/` because a repo may want its rules tracked despite the wall.
9435
+ ".codex/",
9436
+ ".agents/",
9406
9437
  // #2321 doctrine: the canonical worktree home is the SIBLING `../mmi-worktrees/<branch>` (outside the tree,
9407
9438
  // via `mmi-cli worktree create`), so a repo-local `.worktrees/` is NOT un-ignored org-wide — `mmi-cli
9408
9439
  // doctor` flags one explicitly instead (buildRepoLocalWorktreeCheck) rather than baking the fallback path
@@ -12042,7 +12073,12 @@ function consolidateCommandNamespaces(program3) {
12042
12073
 
12043
12074
  // src/version-lag.ts
12044
12075
  var VERSION_LABEL = "installed plugin/adapter cache freshness";
12045
- var VERSION_FIX = "update the MMI plugin via /plugin; standalone npm CLI: npm install -g @mutmutco/cli@latest";
12076
+ function cliUpdateCommand(target) {
12077
+ return `npm install -g @mutmutco/cli@${target ?? "latest"}`;
12078
+ }
12079
+ function versionFix(target) {
12080
+ return `update the MMI plugin via /plugin; standalone npm CLI: ${cliUpdateCommand(target)}`;
12081
+ }
12046
12082
  function parseVersion(v) {
12047
12083
  const [coreRaw, prereleaseRaw = ""] = v.replace(/^v/, "").split("-", 2);
12048
12084
  const core = coreRaw.split(".").slice(0, 3).map((part) => {
@@ -12084,7 +12120,7 @@ function buildVersionLagReport(input) {
12084
12120
  return {
12085
12121
  ok: false,
12086
12122
  label: VERSION_LABEL,
12087
- fix: VERSION_FIX,
12123
+ fix: versionFix(input.releasedVersion),
12088
12124
  currentVersion: input.currentVersion,
12089
12125
  repoVersion: input.repoVersion,
12090
12126
  releasedVersion: input.releasedVersion,
@@ -12095,7 +12131,7 @@ function buildVersionLagReport(input) {
12095
12131
  return {
12096
12132
  ok: false,
12097
12133
  label: VERSION_LABEL,
12098
- fix: VERSION_FIX,
12134
+ fix: versionFix(),
12099
12135
  currentVersion: input.currentVersion,
12100
12136
  repoVersion: input.repoVersion,
12101
12137
  releasedVersion: input.releasedVersion,
@@ -12105,7 +12141,7 @@ function buildVersionLagReport(input) {
12105
12141
  return {
12106
12142
  ok: true,
12107
12143
  label: VERSION_LABEL,
12108
- fix: VERSION_FIX,
12144
+ fix: versionFix(),
12109
12145
  currentVersion: input.currentVersion,
12110
12146
  repoVersion: input.repoVersion,
12111
12147
  releasedVersion: input.releasedVersion
@@ -12119,7 +12155,7 @@ function parseNpmVersion(stdout) {
12119
12155
  return /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(v) ? v : void 0;
12120
12156
  }
12121
12157
  function staleTrainCliMessage(report, commandName) {
12122
- return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`npm install -g @mutmutco/cli@latest\` to update to ${report.releasedVersion} (a standalone CLI shadows a plugin-provided mmi-cli on PATH and needs no Claude restart), then rerun ${commandName} so it uses the current train path. This gate runs before any train mutation, so this invocation changed nothing; any partial train state (local merge, pushed tag) is from an earlier run and the rerun resumes it safely`;
12158
+ return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`${cliUpdateCommand(report.releasedVersion)}\` to update to ${report.releasedVersion} (a standalone CLI shadows a plugin-provided mmi-cli on PATH and needs no Claude restart), then rerun ${commandName} so it uses the current train path. This gate runs before any train mutation, so this invocation changed nothing; any partial train state (local merge, pushed tag) is from an earlier run and the rerun resumes it safely`;
12123
12159
  }
12124
12160
  function versionAutoUpdateAction(report, releasedSource) {
12125
12161
  if (report.ok || report.staleAgainst !== "released") return "none";
@@ -12774,7 +12810,9 @@ function commandAuthorityLabel(owner) {
12774
12810
  }
12775
12811
  async function requireCleanTree(deps) {
12776
12812
  const status = await deps.run("git", ["status", "--porcelain"]);
12777
- if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before train apply");
12813
+ if (porcelainHasBlockingChanges(status)) {
12814
+ throw new Error(`working tree must be clean before train apply${describeBlockingPaths(status)}`);
12815
+ }
12778
12816
  }
12779
12817
  async function requireBranch(deps, branch) {
12780
12818
  const current = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
@@ -14538,7 +14576,9 @@ async function runHotfixStart(deps, options) {
14538
14576
  const ctx = await buildTrainApplyContext(deps);
14539
14577
  const deployModel = await resolveHotfixDeployModel(deps, ctx);
14540
14578
  const status = await deps.run("git", ["status", "--porcelain"]);
14541
- if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before hotfix start");
14579
+ if (porcelainHasBlockingChanges(status)) {
14580
+ throw new Error(`working tree must be clean before hotfix start${describeBlockingPaths(status)}`);
14581
+ }
14542
14582
  await deps.run("git", ["fetch", "origin", "--tags"]);
14543
14583
  const { tag, version } = await deriveHotfixVersion(deps);
14544
14584
  const branch = hotfixBranch(tag);
@@ -14651,7 +14691,9 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
14651
14691
  const deployModel = await resolveHotfixDeployModel(deps, ctx);
14652
14692
  const { tag, version } = normalizeHotfixVersion(versionInput);
14653
14693
  const status = await deps.run("git", ["status", "--porcelain"]);
14654
- if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before hotfix release");
14694
+ if (porcelainHasBlockingChanges(status)) {
14695
+ throw new Error(`working tree must be clean before hotfix release${describeBlockingPaths(status)}`);
14696
+ }
14655
14697
  await deps.run("git", ["fetch", "origin", "--tags"]);
14656
14698
  const pr2 = await findHotfixPr(deps, ctx, tag);
14657
14699
  if (!pr2) throw new Error(`no hotfix PR found for ${tag} (head ${hotfixBranch(tag)}, base main) \u2014 run mmi-cli hotfix start first`);
@@ -14788,6 +14830,9 @@ function deriveHotfixState(f) {
14788
14830
  if (!f.branchExists && !f.pr && !f.tagPushed && !f.releaseExists) {
14789
14831
  return { state: "not-started", next: `mmi-cli hotfix start --from <pr#|sha> (would mint ${f.tag})` };
14790
14832
  }
14833
+ if (!f.pr && !f.branchExists && f.tagPushed && f.releaseExists) {
14834
+ return { state: "complete", next: "nothing \u2014 that version is a completed release, not a hotfix in flight" };
14835
+ }
14791
14836
  if (!f.pr) {
14792
14837
  return { state: "branch-pushed (no PR)", next: `mmi-cli hotfix start --from <pr#|sha> (resumes at the PR step for ${f.tag})` };
14793
14838
  }
@@ -21807,10 +21852,11 @@ async function fetchNpmReleasedVersion() {
21807
21852
  }
21808
21853
  }
21809
21854
  var NPM_INSTALL_TIMEOUT_MS = 12e4;
21810
- async function npmSelfUpdateCli() {
21855
+ async function npmSelfUpdateCli(target) {
21856
+ const command = cliUpdateCommand(target);
21811
21857
  try {
21812
- await runHostBin("npm", ["install", "-g", "@mutmutco/cli@latest"], { timeout: NPM_INSTALL_TIMEOUT_MS });
21813
- return { ok: true, detail: "npm install -g @mutmutco/cli@latest exited 0" };
21858
+ await runHostBin("npm", ["install", "-g", `@mutmutco/cli@${target ?? "latest"}`], { timeout: NPM_INSTALL_TIMEOUT_MS });
21859
+ return { ok: true, detail: `${command} exited 0` };
21814
21860
  } catch (e) {
21815
21861
  return { ok: false, detail: e.message.trim().slice(0, 200).replace(/\s+/g, " ") };
21816
21862
  }
@@ -23315,7 +23361,7 @@ async function runDoctorClean(opts, io, deps) {
23315
23361
  const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
23316
23362
  const cliReport = buildVersionLagReport(cliInput);
23317
23363
  if (apply && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
23318
- const heal = await deps.updateCli();
23364
+ const heal = await deps.updateCli(cliReport.releasedVersion);
23319
23365
  const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
23320
23366
  checks.push(heal.ok ? {
23321
23367
  ok: true,
@@ -23325,7 +23371,7 @@ async function runDoctorClean(opts, io, deps) {
23325
23371
  } : {
23326
23372
  ok: false,
23327
23373
  label: `mmi-cli ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion}`,
23328
- fix: `self-update failed (${heal.detail}) \u2014 run \`npm install -g @mutmutco/cli@latest\``,
23374
+ fix: `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
23329
23375
  verbose: healEvidence
23330
23376
  });
23331
23377
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.46.0",
3
+ "version": "3.48.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",