@mutmutco/cli 2.23.0 → 2.24.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/README.md +5 -2
- package/dist/main.cjs +58 -9
- package/dist/saga.cjs +21 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,9 +29,12 @@ mmi-cli doctor --json
|
|
|
29
29
|
|
|
30
30
|
- `mmi-cli rules sync` delivers the org-owned `AGENTS.md`, `CLAUDE.md`, and Claude settings files.
|
|
31
31
|
- `mmi-cli docs sync` refreshes repo-owned `README.md` and `architecture.md` without clobbering dirty files.
|
|
32
|
-
- `mmi-cli saga note`, `saga show`, `saga health`, `saga session`, `saga capture`, and `saga head-update` write and inspect session continuity through a cached Hub session token.
|
|
32
|
+
- `mmi-cli saga note`, `saga show`, `saga health`, `saga session`, `saga capture`, and `saga head-update` write and inspect session continuity through a cached Hub session token. Saga writes are local-first: a transient server miss queues the note in a local pending file and a detached flush worker delivers it — a queued note is normal, not a failure.
|
|
33
33
|
- `mmi-cli kb get` and `kb list` read the MM KB source (`kb list [prefix]` lists document paths, optionally under a prefix).
|
|
34
|
-
- `mmi-cli northstar push|pull|list|delete|graduate` manages North Star, the per-user plan/SSOT store.
|
|
34
|
+
- `mmi-cli northstar push|pull|list|status|sync|delete|graduate` manages North Star, the per-user plan/SSOT store.
|
|
35
|
+
`northstar push` is async by default — it queues a durable background push and prints a "queued" line,
|
|
36
|
+
which is the expected success path. `northstar status` shows pending/conflicted pushes; `northstar sync`
|
|
37
|
+
(or `push --wait`) gives durable server confirmation. Never treat a queued push as failure.
|
|
35
38
|
`northstar graduate <slug> --merged-pr <url-or-number> --org-visible` marks a built-and-merged plan for
|
|
36
39
|
KB curation without echoing the plan body.
|
|
37
40
|
`mmi-cli plan` remains a compatibility alias.
|
package/dist/main.cjs
CHANGED
|
@@ -3582,6 +3582,20 @@ var FLUSH_LOCK_STALE_MS = 5 * 6e4;
|
|
|
3582
3582
|
function pendingPath(dir = ".mmi") {
|
|
3583
3583
|
return (0, import_node_path3.join)(dir, PENDING_FILE);
|
|
3584
3584
|
}
|
|
3585
|
+
var PENDING_TMP_STALE_MS = 6e4;
|
|
3586
|
+
function sweepStaleTmp(dir = ".mmi", now = Date.now()) {
|
|
3587
|
+
try {
|
|
3588
|
+
for (const name of (0, import_node_fs3.readdirSync)(dir)) {
|
|
3589
|
+
if (!name.startsWith(`${PENDING_FILE}.`) || !name.endsWith(".tmp")) continue;
|
|
3590
|
+
const path2 = (0, import_node_path3.join)(dir, name);
|
|
3591
|
+
try {
|
|
3592
|
+
if (now - (0, import_node_fs3.statSync)(path2).mtimeMs > PENDING_TMP_STALE_MS) (0, import_node_fs3.unlinkSync)(path2);
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
} catch {
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3585
3599
|
function flushLockPath(dir = ".mmi") {
|
|
3586
3600
|
return (0, import_node_path3.join)(dir, FLUSH_LOCK_FILE);
|
|
3587
3601
|
}
|
|
@@ -3625,15 +3639,20 @@ function readPending(dir = ".mmi") {
|
|
|
3625
3639
|
return out;
|
|
3626
3640
|
}
|
|
3627
3641
|
function writePending(entries, dir = ".mmi") {
|
|
3642
|
+
const tmp = `${pendingPath(dir)}.${process.pid}.tmp`;
|
|
3628
3643
|
try {
|
|
3629
3644
|
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
3645
|
+
sweepStaleTmp(dir);
|
|
3630
3646
|
const trimmed = entries.slice(-PENDING_MAX);
|
|
3631
3647
|
const body = trimmed.map((e) => JSON.stringify(e)).join("\n");
|
|
3632
|
-
const tmp = `${pendingPath(dir)}.${process.pid}.tmp`;
|
|
3633
3648
|
(0, import_node_fs3.writeFileSync)(tmp, trimmed.length ? `${body}
|
|
3634
3649
|
` : "", "utf8");
|
|
3635
3650
|
(0, import_node_fs3.renameSync)(tmp, pendingPath(dir));
|
|
3636
3651
|
} catch {
|
|
3652
|
+
try {
|
|
3653
|
+
(0, import_node_fs3.unlinkSync)(tmp);
|
|
3654
|
+
} catch {
|
|
3655
|
+
}
|
|
3637
3656
|
}
|
|
3638
3657
|
}
|
|
3639
3658
|
function enqueuePending(body, dir = ".mmi") {
|
|
@@ -3646,6 +3665,7 @@ function enqueuePending(body, dir = ".mmi") {
|
|
|
3646
3665
|
return;
|
|
3647
3666
|
}
|
|
3648
3667
|
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
3668
|
+
sweepStaleTmp(dir);
|
|
3649
3669
|
(0, import_node_fs3.appendFileSync)(pendingPath(dir), `${JSON.stringify({ id, body })}
|
|
3650
3670
|
`, "utf8");
|
|
3651
3671
|
} catch {
|
|
@@ -6423,7 +6443,8 @@ var MANAGED_GITIGNORE_LINES = [
|
|
|
6423
6443
|
".claude/worktrees/",
|
|
6424
6444
|
".mmi/.session",
|
|
6425
6445
|
".mmi/head-ts/",
|
|
6426
|
-
".mmi/saga-pending.jsonl",
|
|
6446
|
+
".mmi/saga-pending.jsonl*",
|
|
6447
|
+
".mmi/saga-flush.lock",
|
|
6427
6448
|
".aws-sam/",
|
|
6428
6449
|
"/*.png"
|
|
6429
6450
|
];
|
|
@@ -7579,10 +7600,37 @@ async function resolveRcResumeTag(deps, base, sha) {
|
|
|
7579
7600
|
const note = leftovers.length > 0 ? `resuming existing RC tag ${newest} already on ${sha.slice(0, 7)} (newest of ${sorted.length}); harmless leftover tag(s) on the same SHA: ${leftovers.join(", ")}` : `resuming existing RC tag ${newest} already on ${sha.slice(0, 7)} instead of minting a fresh rc`;
|
|
7580
7601
|
return { tag: newest, note };
|
|
7581
7602
|
}
|
|
7582
|
-
|
|
7603
|
+
var DISPATCH_ATTEMPTS = 3;
|
|
7604
|
+
var DISPATCH_RETRY_DELAY_MS = 2e3;
|
|
7605
|
+
function isTransientDispatchFailure(e) {
|
|
7606
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7607
|
+
return /timed? ?out|timeout|aborted|network|fetch failed|ECONNRESET|ECONNREFUSED|EAI_AGAIN/i.test(msg);
|
|
7608
|
+
}
|
|
7609
|
+
async function dispatchTenantDeployWithRetry(deps, input) {
|
|
7610
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
7611
|
+
for (let attempt = 1; ; attempt++) {
|
|
7612
|
+
try {
|
|
7613
|
+
await deps.dispatchTenantDeploy(input);
|
|
7614
|
+
return;
|
|
7615
|
+
} catch (e) {
|
|
7616
|
+
if (attempt >= DISPATCH_ATTEMPTS || !isTransientDispatchFailure(e)) throw e;
|
|
7617
|
+
await sleep(DISPATCH_RETRY_DELAY_MS * attempt);
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
async function dispatchDeploy(deps, ctx, stage2, ref, model, watch, autoRunSince, autoRunHeadSha, dispatchFailure = "throw") {
|
|
7583
7622
|
if (model === "tenant-container" || model === "solo-container") {
|
|
7584
7623
|
const since = (deps.now ?? Date.now)();
|
|
7585
|
-
|
|
7624
|
+
try {
|
|
7625
|
+
await dispatchTenantDeployWithRetry(deps, { repo: ctx.repo, slug: ctx.slug, ref, stage: stage2 });
|
|
7626
|
+
} catch (e) {
|
|
7627
|
+
if (dispatchFailure === "throw") throw e;
|
|
7628
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7629
|
+
return {
|
|
7630
|
+
note: `tenant-deploy dispatch FAILED: ${msg}. The promotion itself landed (merge/tag/Release pushed before the dispatch) \u2014 recover the deploy with \`mmi-cli tenant redeploy ${ctx.repo} ${stage2}\`; never re-tag.`,
|
|
7631
|
+
deployStatus: "failure"
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7586
7634
|
const { runId, runUrl } = await correlateTenantRun(deps, since);
|
|
7587
7635
|
const deployStatus = watch ? await watchTenantRun(deps, runId) : "pending";
|
|
7588
7636
|
return { note: `dispatched tenant-deploy.yml (slug=${ctx.slug}, ref=${ref}, stage=${stage2})`, runId, runUrl, deployStatus };
|
|
@@ -7716,7 +7764,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7716
7764
|
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
7717
7765
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
7718
7766
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
7719
|
-
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2);
|
|
7767
|
+
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
7720
7768
|
await deps.run("git", ["checkout", "development"]);
|
|
7721
7769
|
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
7722
7770
|
await deps.run("git", ["merge", "main", "--no-edit"]);
|
|
@@ -7787,7 +7835,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7787
7835
|
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
7788
7836
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
7789
7837
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
7790
|
-
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2);
|
|
7838
|
+
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
7791
7839
|
const retirement2 = await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease);
|
|
7792
7840
|
await deps.run("git", ["checkout", "development"]);
|
|
7793
7841
|
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
@@ -7867,7 +7915,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7867
7915
|
const releaseUrl = clean(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
7868
7916
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
7869
7917
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
7870
|
-
const d = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha);
|
|
7918
|
+
const d = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report");
|
|
7871
7919
|
const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
|
|
7872
7920
|
await deps.run("git", ["checkout", "development"]);
|
|
7873
7921
|
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
@@ -9478,6 +9526,7 @@ var PROJECTS_ENVELOPE_KEY = "projects";
|
|
|
9478
9526
|
// src/registry-client.ts
|
|
9479
9527
|
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
9480
9528
|
var WAITED_TENANT_CONTROL_TIMEOUT_MS = 13e3;
|
|
9529
|
+
var TENANT_DEPLOY_TIMEOUT_MS = 12e4;
|
|
9481
9530
|
var RETRY_ATTEMPTS = 3;
|
|
9482
9531
|
function retriedFetch(deps, url, init) {
|
|
9483
9532
|
const headers = { ...clientVersionHeaders(), ...init.headers };
|
|
@@ -9617,7 +9666,7 @@ async function tenantControl(payload, deps) {
|
|
|
9617
9666
|
return postJson("/tenant-control", payload, deps, "POST", { noRetry, timeoutMs });
|
|
9618
9667
|
}
|
|
9619
9668
|
async function tenantDeploy(payload, deps) {
|
|
9620
|
-
return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true });
|
|
9669
|
+
return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS });
|
|
9621
9670
|
}
|
|
9622
9671
|
|
|
9623
9672
|
// src/tenant-verify-secrets.ts
|
|
@@ -10514,7 +10563,7 @@ async function planPush(deps, slug, opts = {}) {
|
|
|
10514
10563
|
queue.push({ project: project2, slug, hash: hashContent(normalizeEol(raw)), attempts: 0, queuedAt: deps.now(), ...opts.force ? { force: true } : {} });
|
|
10515
10564
|
deps.writeQueueRaw(serializeQueue(queue));
|
|
10516
10565
|
deps.detachSync();
|
|
10517
|
-
deps.log(`queued ${slug} for background push (\`mmi-cli northstar status\` to check
|
|
10566
|
+
deps.log(`queued ${slug} for background push \u2014 expected, not a failure (\`mmi-cli northstar status\` to check; \`mmi-cli northstar sync\` or --wait for durable confirmation)`);
|
|
10518
10567
|
return true;
|
|
10519
10568
|
}
|
|
10520
10569
|
async function planPushNow(deps, slug, opts = {}) {
|
package/dist/saga.cjs
CHANGED
|
@@ -3681,6 +3681,20 @@ var FLUSH_LOCK_STALE_MS = 5 * 6e4;
|
|
|
3681
3681
|
function pendingPath(dir = ".mmi") {
|
|
3682
3682
|
return (0, import_node_path4.join)(dir, PENDING_FILE);
|
|
3683
3683
|
}
|
|
3684
|
+
var PENDING_TMP_STALE_MS = 6e4;
|
|
3685
|
+
function sweepStaleTmp(dir = ".mmi", now = Date.now()) {
|
|
3686
|
+
try {
|
|
3687
|
+
for (const name of (0, import_node_fs4.readdirSync)(dir)) {
|
|
3688
|
+
if (!name.startsWith(`${PENDING_FILE}.`) || !name.endsWith(".tmp")) continue;
|
|
3689
|
+
const path2 = (0, import_node_path4.join)(dir, name);
|
|
3690
|
+
try {
|
|
3691
|
+
if (now - (0, import_node_fs4.statSync)(path2).mtimeMs > PENDING_TMP_STALE_MS) (0, import_node_fs4.unlinkSync)(path2);
|
|
3692
|
+
} catch {
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3684
3698
|
function flushLockPath(dir = ".mmi") {
|
|
3685
3699
|
return (0, import_node_path4.join)(dir, FLUSH_LOCK_FILE);
|
|
3686
3700
|
}
|
|
@@ -3724,15 +3738,20 @@ function readPending(dir = ".mmi") {
|
|
|
3724
3738
|
return out;
|
|
3725
3739
|
}
|
|
3726
3740
|
function writePending(entries, dir = ".mmi") {
|
|
3741
|
+
const tmp = `${pendingPath(dir)}.${process.pid}.tmp`;
|
|
3727
3742
|
try {
|
|
3728
3743
|
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
3744
|
+
sweepStaleTmp(dir);
|
|
3729
3745
|
const trimmed = entries.slice(-PENDING_MAX);
|
|
3730
3746
|
const body = trimmed.map((e) => JSON.stringify(e)).join("\n");
|
|
3731
|
-
const tmp = `${pendingPath(dir)}.${process.pid}.tmp`;
|
|
3732
3747
|
(0, import_node_fs4.writeFileSync)(tmp, trimmed.length ? `${body}
|
|
3733
3748
|
` : "", "utf8");
|
|
3734
3749
|
(0, import_node_fs4.renameSync)(tmp, pendingPath(dir));
|
|
3735
3750
|
} catch {
|
|
3751
|
+
try {
|
|
3752
|
+
(0, import_node_fs4.unlinkSync)(tmp);
|
|
3753
|
+
} catch {
|
|
3754
|
+
}
|
|
3736
3755
|
}
|
|
3737
3756
|
}
|
|
3738
3757
|
function enqueuePending(body, dir = ".mmi") {
|
|
@@ -3745,6 +3764,7 @@ function enqueuePending(body, dir = ".mmi") {
|
|
|
3745
3764
|
return;
|
|
3746
3765
|
}
|
|
3747
3766
|
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
3767
|
+
sweepStaleTmp(dir);
|
|
3748
3768
|
(0, import_node_fs4.appendFileSync)(pendingPath(dir), `${JSON.stringify({ id, body })}
|
|
3749
3769
|
`, "utf8");
|
|
3750
3770
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|