@mutmutco/cli 3.15.0 → 3.16.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/README.md +1 -1
- package/dist/main.cjs +57 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The command-line engine for MMI Future org tooling. It delivers the org-managed `.gitignore` block, reads and claims GitHub Project work, and exposes the model-agnostic commands used by the MMI plugin and non-Claude agents. (Personal agent guides — `AGENTS.md` / `CLAUDE.md` — are developer-owned and gitignored; the CLI does not deliver, overwrite, or fan them out — the whole-spine fanout is retired.)
|
|
4
4
|
|
|
5
|
-
This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MMI-Hub) and its version matches the MMI Hub Claude Code
|
|
5
|
+
This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MMI-Hub) and its version matches the MMI Hub Claude Code plugin distribution version (Claude-only since #2741; the release train bumps both in lockstep).
|
|
6
6
|
|
|
7
7
|
The CLI carries the org **Hub endpoint** intrinsically (override with the `MMI_HUB_URL` env var), so a product repo needs **no committed control-plane config** to reach the Hub — board coords, deploy coordinates, OAuth, and the secrets layout are all discovered from the Hub registry at runtime.
|
|
8
8
|
|
package/dist/main.cjs
CHANGED
|
@@ -6299,30 +6299,54 @@ async function registerDeferredWorktree(store, entry) {
|
|
|
6299
6299
|
return { entries, newlyRegistered: true };
|
|
6300
6300
|
}
|
|
6301
6301
|
async function sweepDeferredWorktrees(store, deps) {
|
|
6302
|
-
if (!store) return { removed: [], stillDeferred: [] };
|
|
6302
|
+
if (!store) return { removed: [], stillDeferred: [], skipped: [] };
|
|
6303
6303
|
const entries = await store.read();
|
|
6304
|
-
if (!entries.length) return { removed: [], stillDeferred: [] };
|
|
6304
|
+
if (!entries.length) return { removed: [], stillDeferred: [], skipped: [] };
|
|
6305
|
+
let live;
|
|
6306
|
+
try {
|
|
6307
|
+
live = parseWorktreePorcelain(await deps.git(["worktree", "list", "--porcelain"]));
|
|
6308
|
+
} catch {
|
|
6309
|
+
live = [];
|
|
6310
|
+
}
|
|
6305
6311
|
const removed = [];
|
|
6306
6312
|
const stillDeferred = [];
|
|
6313
|
+
const skipped = [];
|
|
6307
6314
|
for (const entry of entries) {
|
|
6315
|
+
const current = live.find((w) => samePath(w.path, entry.path));
|
|
6316
|
+
if (current && current.branch !== entry.branch) {
|
|
6317
|
+
skipped.push(entry);
|
|
6318
|
+
continue;
|
|
6319
|
+
}
|
|
6320
|
+
if (current) {
|
|
6321
|
+
const dirty = (await deps.git(["-C", entry.path, "status", "--porcelain"]).catch(() => "dirty") || "").trim().length > 0;
|
|
6322
|
+
if (dirty) {
|
|
6323
|
+
stillDeferred.push(entry);
|
|
6324
|
+
continue;
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6308
6327
|
const outcome = await removeWorktreeWithRecovery(entry.path, deps);
|
|
6309
6328
|
if (outcome.status === "removed") removed.push(entry.path);
|
|
6310
6329
|
else stillDeferred.push(entry);
|
|
6311
6330
|
}
|
|
6312
6331
|
await store.write(stillDeferred);
|
|
6313
|
-
return { removed, stillDeferred };
|
|
6332
|
+
return { removed, stillDeferred, skipped };
|
|
6314
6333
|
}
|
|
6315
6334
|
async function sweepDeferredWorktreesWithRetry(store, deps, opts = {}) {
|
|
6316
6335
|
const attempts = opts.attempts ?? 4;
|
|
6317
6336
|
const backoff = opts.backoffMs ?? [500, 2e3, 5e3, 1e4];
|
|
6318
6337
|
const sleep = opts.sleep ?? defaultSleep;
|
|
6319
|
-
|
|
6338
|
+
const removed = [];
|
|
6339
|
+
const skipped = [];
|
|
6340
|
+
let stillDeferred = [];
|
|
6320
6341
|
for (let i = 0; i < attempts; i++) {
|
|
6321
|
-
|
|
6322
|
-
|
|
6342
|
+
const result = await sweepDeferredWorktrees(store, deps);
|
|
6343
|
+
removed.push(...result.removed);
|
|
6344
|
+
skipped.push(...result.skipped);
|
|
6345
|
+
stillDeferred = result.stillDeferred;
|
|
6346
|
+
if (!stillDeferred.length) break;
|
|
6323
6347
|
if (i < attempts - 1) await sleep(backoff[Math.min(i, backoff.length - 1)]);
|
|
6324
6348
|
}
|
|
6325
|
-
return
|
|
6349
|
+
return { removed, stillDeferred, skipped };
|
|
6326
6350
|
}
|
|
6327
6351
|
var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
6328
6352
|
async function removeWorktreeWithRecovery(wtPath, deps) {
|
|
@@ -19137,6 +19161,18 @@ function checkClaudePlugin(probe) {
|
|
|
19137
19161
|
}
|
|
19138
19162
|
return { ok: true, label: installed ? `Claude plugin ${installed}` : "Claude plugin" };
|
|
19139
19163
|
}
|
|
19164
|
+
function checkTrainSync(result) {
|
|
19165
|
+
const problems = result.branches.filter((b) => b.status === "skipped" && (b.reason === "diverged" || b.reason === "ff-failed"));
|
|
19166
|
+
if (problems.length) {
|
|
19167
|
+
return { ok: false, label: "train branches", fix: problems.map((b) => b.note).join("; ") };
|
|
19168
|
+
}
|
|
19169
|
+
const moved = result.branches.filter((b) => b.status === "synced");
|
|
19170
|
+
return {
|
|
19171
|
+
ok: true,
|
|
19172
|
+
label: "train branches",
|
|
19173
|
+
detail: moved.length ? `fast-forwarded ${moved.map((b) => b.branch).join(", ")}` : void 0
|
|
19174
|
+
};
|
|
19175
|
+
}
|
|
19140
19176
|
function planGitignore(current) {
|
|
19141
19177
|
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
19142
19178
|
return changed ? { ok: false, content } : { ok: true };
|
|
@@ -19175,6 +19211,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
19175
19211
|
checks.push(plugin);
|
|
19176
19212
|
if (!plugin.ok) restartPending = true;
|
|
19177
19213
|
if (isOrgRepo && !opts.fast && !opts.banner) {
|
|
19214
|
+
try {
|
|
19215
|
+
checks.push(checkTrainSync(await deps.syncTrain()));
|
|
19216
|
+
} catch (e) {
|
|
19217
|
+
checks.push({ ok: false, label: "train branches", fix: `sync failed \u2014 ${e instanceof Error ? e.message : String(e)}` });
|
|
19218
|
+
}
|
|
19178
19219
|
const repoRoot2 = await deps.repoRoot();
|
|
19179
19220
|
try {
|
|
19180
19221
|
const plan = await deps.gcPlan("origin", 200);
|
|
@@ -19438,6 +19479,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19438
19479
|
}
|
|
19439
19480
|
return result;
|
|
19440
19481
|
}
|
|
19482
|
+
var execFileGitRun = async (file, args) => (await execFileP2(file, args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
19441
19483
|
function mmiDoctorDeps() {
|
|
19442
19484
|
return {
|
|
19443
19485
|
githubLogin,
|
|
@@ -19452,7 +19494,10 @@ function mmiDoctorDeps() {
|
|
|
19452
19494
|
repoRoot,
|
|
19453
19495
|
gcPlan,
|
|
19454
19496
|
applyGcPlan,
|
|
19455
|
-
executeScratchGc: (root, o) => executeScratchGc(root, { apply: o.apply })
|
|
19497
|
+
executeScratchGc: (root, o) => executeScratchGc(root, { apply: o.apply }),
|
|
19498
|
+
// #2759: same fetch+ff-only sync SessionStart runs, wired here too so an on-demand `mmi-cli doctor`
|
|
19499
|
+
// self-heals a checkout a long-running session left stale.
|
|
19500
|
+
syncTrain: () => syncLocalTrainBranches(execFileGitRun)
|
|
19456
19501
|
};
|
|
19457
19502
|
}
|
|
19458
19503
|
async function requireFreshTrainCli(commandName) {
|
|
@@ -19611,10 +19656,11 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
19611
19656
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout)
|
|
19612
19657
|
);
|
|
19613
19658
|
if (o.json) return console.log(JSON.stringify(result));
|
|
19614
|
-
if (!o.quiet || result.removed.length || result.stillDeferred.length) {
|
|
19659
|
+
if (!o.quiet || result.removed.length || result.stillDeferred.length || result.skipped.length) {
|
|
19615
19660
|
if (result.removed.length) console.log(`gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
|
|
19616
19661
|
if (result.stillDeferred.length) console.log(`gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
|
|
19617
|
-
if (
|
|
19662
|
+
if (result.skipped.length) console.log(`gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
|
|
19663
|
+
if (!result.removed.length && !result.stillDeferred.length && !result.skipped.length) console.log("gc sweep-deferred: nothing queued");
|
|
19618
19664
|
}
|
|
19619
19665
|
} catch (e) {
|
|
19620
19666
|
fail(`gc sweep-deferred: ${e.message}`);
|
|
@@ -22567,8 +22613,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
22567
22613
|
// release pushed to origin (incl. the deferred main -> development alignment merge). Bounded git,
|
|
22568
22614
|
// fail-soft, silent unless a branch moved.
|
|
22569
22615
|
syncTrain: async (io) => {
|
|
22570
|
-
const
|
|
22571
|
-
const result = await syncLocalTrainBranches(gitRun, { warn: (m) => io.err(`[mmi-hook] train sync: ${m}`) });
|
|
22616
|
+
const result = await syncLocalTrainBranches(execFileGitRun, { warn: (m) => io.err(`[mmi-hook] train sync: ${m}`) });
|
|
22572
22617
|
const line = localTrainSyncBannerLine(result);
|
|
22573
22618
|
if (line) io.log(line);
|
|
22574
22619
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.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",
|