@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.
- package/dist/main.cjs +64 -18
- 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
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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 \`
|
|
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))
|
|
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))
|
|
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))
|
|
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",
|
|
21813
|
-
return { ok: true, detail:
|
|
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
|
|
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.
|
|
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",
|