@mutmutco/cli 3.47.0 → 3.48.1
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 +85 -19
- 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"]));
|
|
@@ -14468,6 +14506,24 @@ async function deriveHotfixVersion(deps) {
|
|
|
14468
14506
|
function hotfixBranch(tag) {
|
|
14469
14507
|
return `hotfix/${tag}`;
|
|
14470
14508
|
}
|
|
14509
|
+
async function restoreAfterFailedPort(deps, opts) {
|
|
14510
|
+
const { startBranch, branch, created } = opts;
|
|
14511
|
+
if (!startBranch || startBranch === "HEAD" || startBranch === branch) {
|
|
14512
|
+
return `Local state left as-is (could not determine the branch to return to).`;
|
|
14513
|
+
}
|
|
14514
|
+
try {
|
|
14515
|
+
await deps.run("git", ["checkout", startBranch]);
|
|
14516
|
+
} catch {
|
|
14517
|
+
return `Could NOT return to ${startBranch} \u2014 the checkout is still on ${branch} (origin/main content); switch back before editing anything.`;
|
|
14518
|
+
}
|
|
14519
|
+
if (!created) return `Returned to ${startBranch}; the pre-existing local ${branch} was left in place.`;
|
|
14520
|
+
try {
|
|
14521
|
+
await deps.run("git", ["branch", "-D", branch]);
|
|
14522
|
+
return `Returned to ${startBranch} and removed the local ${branch}.`;
|
|
14523
|
+
} catch {
|
|
14524
|
+
return `Returned to ${startBranch}; the local ${branch} could not be removed \u2014 delete it by hand.`;
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14471
14527
|
async function resolveHotfixDeployModel(deps, ctx) {
|
|
14472
14528
|
const load = await loadProjectMeta(deps, ctx);
|
|
14473
14529
|
const meta = requireProjectMetaForTrain(load, ctx.repo);
|
|
@@ -14538,7 +14594,9 @@ async function runHotfixStart(deps, options) {
|
|
|
14538
14594
|
const ctx = await buildTrainApplyContext(deps);
|
|
14539
14595
|
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
14540
14596
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
14541
|
-
if (porcelainHasBlockingChanges(status))
|
|
14597
|
+
if (porcelainHasBlockingChanges(status)) {
|
|
14598
|
+
throw new Error(`working tree must be clean before hotfix start${describeBlockingPaths(status)}`);
|
|
14599
|
+
}
|
|
14542
14600
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
14543
14601
|
const { tag, version } = await deriveHotfixVersion(deps);
|
|
14544
14602
|
const branch = hotfixBranch(tag);
|
|
@@ -14564,12 +14622,17 @@ async function runHotfixStart(deps, options) {
|
|
|
14564
14622
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
14565
14623
|
notes.push(`branch ${branch} already on origin \u2014 reused (cherry-pick/bump assumed present; PR step resumes)`);
|
|
14566
14624
|
} else {
|
|
14625
|
+
const startBranch = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
14626
|
+
const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
|
|
14567
14627
|
await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
|
|
14568
14628
|
try {
|
|
14569
14629
|
await deps.run("git", ["cherry-pick", "-x", sha]);
|
|
14570
14630
|
} catch (e) {
|
|
14571
14631
|
await deps.run("git", ["cherry-pick", "--abort"]);
|
|
14572
|
-
|
|
14632
|
+
const recovery = await restoreAfterFailedPort(deps, { startBranch, branch, created: !preexistingLocal });
|
|
14633
|
+
throw new Error(
|
|
14634
|
+
`cherry-pick of ${label} onto ${branch} conflicted \u2014 aborted; nothing was pushed. Land the conflict resolution on development through a normal PR, then rerun \`mmi-cli hotfix start --from <that PR's merge sha>\` \u2014 never hand-resolve the port onto main. ${recovery} (${e.message ?? e})`
|
|
14635
|
+
);
|
|
14573
14636
|
}
|
|
14574
14637
|
notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
|
|
14575
14638
|
if (deployModel === "hub-serverless") {
|
|
@@ -14651,7 +14714,9 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
14651
14714
|
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
14652
14715
|
const { tag, version } = normalizeHotfixVersion(versionInput);
|
|
14653
14716
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
14654
|
-
if (porcelainHasBlockingChanges(status))
|
|
14717
|
+
if (porcelainHasBlockingChanges(status)) {
|
|
14718
|
+
throw new Error(`working tree must be clean before hotfix release${describeBlockingPaths(status)}`);
|
|
14719
|
+
}
|
|
14655
14720
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
14656
14721
|
const pr2 = await findHotfixPr(deps, ctx, tag);
|
|
14657
14722
|
if (!pr2) throw new Error(`no hotfix PR found for ${tag} (head ${hotfixBranch(tag)}, base main) \u2014 run mmi-cli hotfix start first`);
|
|
@@ -21810,10 +21875,11 @@ async function fetchNpmReleasedVersion() {
|
|
|
21810
21875
|
}
|
|
21811
21876
|
}
|
|
21812
21877
|
var NPM_INSTALL_TIMEOUT_MS = 12e4;
|
|
21813
|
-
async function npmSelfUpdateCli() {
|
|
21878
|
+
async function npmSelfUpdateCli(target) {
|
|
21879
|
+
const command = cliUpdateCommand(target);
|
|
21814
21880
|
try {
|
|
21815
|
-
await runHostBin("npm", ["install", "-g",
|
|
21816
|
-
return { ok: true, detail:
|
|
21881
|
+
await runHostBin("npm", ["install", "-g", `@mutmutco/cli@${target ?? "latest"}`], { timeout: NPM_INSTALL_TIMEOUT_MS });
|
|
21882
|
+
return { ok: true, detail: `${command} exited 0` };
|
|
21817
21883
|
} catch (e) {
|
|
21818
21884
|
return { ok: false, detail: e.message.trim().slice(0, 200).replace(/\s+/g, " ") };
|
|
21819
21885
|
}
|
|
@@ -23318,7 +23384,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23318
23384
|
const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
|
|
23319
23385
|
const cliReport = buildVersionLagReport(cliInput);
|
|
23320
23386
|
if (apply && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
|
|
23321
|
-
const heal = await deps.updateCli();
|
|
23387
|
+
const heal = await deps.updateCli(cliReport.releasedVersion);
|
|
23322
23388
|
const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
|
|
23323
23389
|
checks.push(heal.ok ? {
|
|
23324
23390
|
ok: true,
|
|
@@ -23328,7 +23394,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23328
23394
|
} : {
|
|
23329
23395
|
ok: false,
|
|
23330
23396
|
label: `mmi-cli ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion}`,
|
|
23331
|
-
fix: `self-update failed (${heal.detail}) \u2014 run
|
|
23397
|
+
fix: `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
|
|
23332
23398
|
verbose: healEvidence
|
|
23333
23399
|
});
|
|
23334
23400
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.48.1",
|
|
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",
|