@mutmutco/cli 4.5.33 → 4.5.35
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 +73 -20
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5485,7 +5485,7 @@ function isSnapshotShape(body) {
|
|
|
5485
5485
|
const staleValid = b?.stale === void 0 || typeof b.stale === "object" && b.stale !== null && Number.isFinite(b.stale.ageMs) && b.stale.ageMs >= 0;
|
|
5486
5486
|
const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && (rate === void 0 || Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string");
|
|
5487
5487
|
return Boolean(
|
|
5488
|
-
b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && githubValid && staleValid
|
|
5488
|
+
b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && (b.eventCursor === void 0 || Number.isInteger(b.eventCursor) && b.eventCursor >= 0) && githubValid && staleValid
|
|
5489
5489
|
);
|
|
5490
5490
|
}
|
|
5491
5491
|
async function fetchHubBoardSnapshot(request, deps) {
|
|
@@ -12541,6 +12541,7 @@ async function readBoard(options, deps = {}) {
|
|
|
12541
12541
|
const { collected, snapshot } = await collectBoardPreferringSnapshot(cfg, options, deps);
|
|
12542
12542
|
let writable;
|
|
12543
12543
|
let pullRequests;
|
|
12544
|
+
let eventCursor;
|
|
12544
12545
|
let github;
|
|
12545
12546
|
if (snapshot) {
|
|
12546
12547
|
for (const unreadable of snapshot.unreadableRepos) {
|
|
@@ -12552,6 +12553,7 @@ async function readBoard(options, deps = {}) {
|
|
|
12552
12553
|
unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
|
|
12553
12554
|
};
|
|
12554
12555
|
pullRequests = snapshot.pullRequests;
|
|
12556
|
+
eventCursor = snapshot.eventCursor;
|
|
12555
12557
|
github = snapshot.github && { ...snapshot.github, ...snapshot.quotaResetAt ? { quotaResetAt: snapshot.quotaResetAt } : {} };
|
|
12556
12558
|
} else {
|
|
12557
12559
|
const probed = await resolveWritableReposForClaimables(collected.items, client);
|
|
@@ -12569,6 +12571,7 @@ async function readBoard(options, deps = {}) {
|
|
|
12569
12571
|
partial: collected.partial,
|
|
12570
12572
|
source: "live",
|
|
12571
12573
|
...pullRequests ? { pullRequests } : {},
|
|
12574
|
+
...eventCursor !== void 0 ? { eventCursor } : {},
|
|
12572
12575
|
...github ? { github } : {}
|
|
12573
12576
|
};
|
|
12574
12577
|
if (options.includeBundleDetails || options.includeAllBodies) {
|
|
@@ -16837,10 +16840,10 @@ var rollout_plan_default = {
|
|
|
16837
16840
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
16838
16841
|
},
|
|
16839
16842
|
baseline: {
|
|
16840
|
-
version: "4.5.
|
|
16841
|
-
tag: "v4.5.
|
|
16842
|
-
commit: "
|
|
16843
|
-
npm: "@mutmutco/cli@4.5.
|
|
16843
|
+
version: "4.5.35",
|
|
16844
|
+
tag: "v4.5.35",
|
|
16845
|
+
commit: "f10f5c65382f",
|
|
16846
|
+
npm: "@mutmutco/cli@4.5.35"
|
|
16844
16847
|
},
|
|
16845
16848
|
exitCriterion: "fleet-n-of-n",
|
|
16846
16849
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16857,14 +16860,14 @@ var rollout_plan_default = {
|
|
|
16857
16860
|
repo: "mutmutco/mmi-hub",
|
|
16858
16861
|
role: "canary",
|
|
16859
16862
|
schedule: "train",
|
|
16860
|
-
v3Target: "v4.5.
|
|
16863
|
+
v3Target: "v4.5.35"
|
|
16861
16864
|
}
|
|
16862
16865
|
],
|
|
16863
16866
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
16864
16867
|
rollback: {
|
|
16865
16868
|
independent: true,
|
|
16866
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16867
|
-
v3Target: "v4.5.
|
|
16869
|
+
mechanism: "npm dist-tag latest -> 4.5.35 and redeploy the Hub Lambda from tag v4.5.35 (f10f5c65382f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16870
|
+
v3Target: "v4.5.35 (@mutmutco/cli@4.5.35, tag commit f10f5c65382f \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16868
16871
|
}
|
|
16869
16872
|
},
|
|
16870
16873
|
{
|
|
@@ -23544,7 +23547,8 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
23544
23547
|
`release --supersede-deploy: origin reports ${prior.tag} at ${liveSha.slice(0, 12) || "no tag"} but the ledger anchors ${prior.tagSha.slice(0, 12)} \u2014 an absent or divergent release is NOT supersedeable (release --abort --apply is the unpublished candidate's own exit); nothing was written (#6553)`
|
|
23545
23548
|
);
|
|
23546
23549
|
}
|
|
23547
|
-
const
|
|
23550
|
+
const failed = prior.phases.deploy.state === "failed";
|
|
23551
|
+
const note = failed ? `superseded: content-caused deploy failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6553)` : `superseded: ${prior.tag} deploy outcome uncorrelatable from the release SHA (no failure recorded) \u2014 closed on the owner's evidence (master-approved closure, #6553, #7327)`;
|
|
23548
23552
|
await fence(() => atomicWriteJson(path2, {
|
|
23549
23553
|
...prior,
|
|
23550
23554
|
phases: {
|
|
@@ -23558,7 +23562,7 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
23558
23562
|
repo: prior.repo,
|
|
23559
23563
|
tag: prior.tag,
|
|
23560
23564
|
tagSha: prior.tagSha,
|
|
23561
|
-
note: `closed the ${prior.tag} deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix`
|
|
23565
|
+
note: failed ? `closed the ${prior.tag} deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix` : `closed the ${prior.tag} uncorrelatable deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm`
|
|
23562
23566
|
};
|
|
23563
23567
|
});
|
|
23564
23568
|
}
|
|
@@ -25929,6 +25933,14 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
25929
25933
|
}
|
|
25930
25934
|
if (persisted?.phases.deploy.superseded === true) phaseInputs.deploy = persisted.phases.deploy;
|
|
25931
25935
|
if (persisted?.phases.publish.superseded === true) phaseInputs.publish = persisted.phases.publish;
|
|
25936
|
+
for (const leg of ["deploy", "publish"]) {
|
|
25937
|
+
const kept = persisted?.phases[leg];
|
|
25938
|
+
if (kept?.state === "complete" && kept.runId != null && phaseInputs[leg].state === "pending") phaseInputs[leg] = kept;
|
|
25939
|
+
}
|
|
25940
|
+
if (!companion && phaseInputs.deploy.state === "complete" && (phaseInputs.publish.state === "complete" || phaseInputs.publish.state === "skipped")) {
|
|
25941
|
+
dispatch2.deployStatus = "success";
|
|
25942
|
+
dispatch2.note = `historical ${deployModel} deploy/publish status: success (re-verified by the recorded run)`;
|
|
25943
|
+
}
|
|
25932
25944
|
if (persisted) {
|
|
25933
25945
|
if (persisted.phases.deploy.superseded !== true) {
|
|
25934
25946
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
|
|
@@ -34301,12 +34313,13 @@ var import_node_crypto11 = require("node:crypto");
|
|
|
34301
34313
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
34302
34314
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
34303
34315
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
34316
|
+
var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
|
|
34304
34317
|
function suitePins(record) {
|
|
34305
|
-
return record.members.map((m) =>
|
|
34318
|
+
return record.members.map((m) => m.carried ? { module: m.id, rc: m.rcPin, main: m.mainPin } : {
|
|
34306
34319
|
module: m.id,
|
|
34307
34320
|
rc: `https://${m.id === "katip" ? m.rcHost ?? m.host : m.host}/${m.id === "katip" ? "" : "rc/"}module.js?v=${m.tag}`,
|
|
34308
34321
|
main: `https://${m.host}/module.js?v=${suiteReleaseTag(m.tag)}`
|
|
34309
|
-
})
|
|
34322
|
+
});
|
|
34310
34323
|
}
|
|
34311
34324
|
function suiteReleaseTag(rc) {
|
|
34312
34325
|
if (!RC_TAG.test(rc)) throw new Error(`invalid rc tag ${rc}`);
|
|
@@ -34326,7 +34339,7 @@ function readSuiteCut(path2) {
|
|
|
34326
34339
|
} catch {
|
|
34327
34340
|
throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
|
|
34328
34341
|
}
|
|
34329
|
-
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent"].includes(record.announcement) || !record.leadReleased) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string")))
|
|
34342
|
+
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent"].includes(record.announcement) || !record.leadReleased) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
34330
34343
|
throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
|
|
34331
34344
|
return record;
|
|
34332
34345
|
}
|
|
@@ -34340,6 +34353,27 @@ function writeSuiteCut(path2, record) {
|
|
|
34340
34353
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
34341
34354
|
}
|
|
34342
34355
|
}
|
|
34356
|
+
function isPin(url) {
|
|
34357
|
+
return typeof url === "string" && url.startsWith("https://");
|
|
34358
|
+
}
|
|
34359
|
+
function pinVersion(url) {
|
|
34360
|
+
try {
|
|
34361
|
+
return url ? new URL(url).searchParams.get("v") ?? "" : "";
|
|
34362
|
+
} catch {
|
|
34363
|
+
return "";
|
|
34364
|
+
}
|
|
34365
|
+
}
|
|
34366
|
+
async function carriedPins(deps, member, bundles) {
|
|
34367
|
+
const main = bundles.main;
|
|
34368
|
+
if (!main || !RELEASE_TAG.test(pinVersion(main))) throw new Error(`${member.repo}: nothing new since rc, but its MM-Work main pin ${main ?? "(none)"} has no ?v=vX.Y.Z release; pin it before the suite cut`);
|
|
34369
|
+
for (const url of [bundles.rc, main]) {
|
|
34370
|
+
const tag = pinVersion(url);
|
|
34371
|
+
if (!RC_TAG.test(tag) && !RELEASE_TAG.test(tag)) continue;
|
|
34372
|
+
const sha = await deps.tagSha(member.repo, tag);
|
|
34373
|
+
if (sha && await deps.contains(member.repo, member.development, sha)) return { rcPin: url, mainPin: main };
|
|
34374
|
+
}
|
|
34375
|
+
throw new Error(`${member.repo}: nothing new since rc, but no pinned MM-Work bundle carries development ${member.development.slice(0, 12)}; pin it before the suite cut`);
|
|
34376
|
+
}
|
|
34343
34377
|
async function suiteRcand(deps, opts) {
|
|
34344
34378
|
if (!opts.ownerGo) throw new Error("suite rcand requires --owner-go (rc uses live data); nothing was written");
|
|
34345
34379
|
const members = suiteMembers(await deps.registry());
|
|
@@ -34352,8 +34386,6 @@ async function suiteRcand(deps, opts) {
|
|
|
34352
34386
|
if (opts.dryRun) return { plan };
|
|
34353
34387
|
let record = readSuiteCut(deps.recordPath);
|
|
34354
34388
|
if (record?.announcement === "sent" && record.leadReleased) {
|
|
34355
|
-
const heads = await Promise.all(modules.map(async (m) => [await deps.head(m.repo, "development"), await deps.head(m.repo, "rc")]));
|
|
34356
|
-
if (heads.some(([dev, rc]) => dev === rc)) throw new Error("a member has no new development commits since rc; no new suite cut started");
|
|
34357
34389
|
const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
|
|
34358
34390
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
34359
34391
|
(0, import_node_fs29.renameSync)(deps.recordPath, archive);
|
|
@@ -34378,9 +34410,22 @@ async function suiteRcand(deps, opts) {
|
|
|
34378
34410
|
if (!record) throw new Error("suite cut record unavailable after initialization");
|
|
34379
34411
|
if (record.members.length !== modules.length || modules.some((m) => !record.members.some((r) => r.repo === m.repo && r.id === m.id)))
|
|
34380
34412
|
throw new Error("suite roster changed since cut began; refuse to omit or add a member");
|
|
34413
|
+
const quiet = [];
|
|
34414
|
+
for (const member of record.members) {
|
|
34415
|
+
if (!member.tag && !member.carried && await deps.contains(member.repo, member.development, member.previousRc)) quiet.push(member);
|
|
34416
|
+
}
|
|
34417
|
+
if (record.members.every((m) => m.carried || quiet.includes(m))) {
|
|
34418
|
+
(0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
|
|
34419
|
+
throw new Error("nothing to promote: no MM-Work module has new development commits since its rc; no suite cut started");
|
|
34420
|
+
}
|
|
34421
|
+
for (const member of quiet) {
|
|
34422
|
+
Object.assign(member, { carried: true }, await carriedPins(deps, member, modules.find((m) => m.repo === member.repo).bundles));
|
|
34423
|
+
writeSuiteCut(deps.recordPath, record);
|
|
34424
|
+
}
|
|
34381
34425
|
for (const member of record.members) {
|
|
34382
34426
|
if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
|
|
34383
34427
|
throw new Error(`${member.repo}: development moved after the cut snapshot; refuse to add untested code`);
|
|
34428
|
+
if (member.carried) continue;
|
|
34384
34429
|
if (!member.tag) {
|
|
34385
34430
|
const cut = await deps.rcand(member.repo, member.previousRc, member.rcWasAbsent);
|
|
34386
34431
|
if (!RC_TAG.test(cut.tag) || !SHA2.test(cut.sha) || cut.sha.toLowerCase() === member.previousRc.toLowerCase()) throw new Error(`${member.repo}: rcand did not return a new immutable rc tag/SHA`);
|
|
@@ -34431,6 +34476,11 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
34431
34476
|
if (members.length !== record.members.length || members.some((m) => !record.members.some((r) => r.repo === m.repo && r.id === m.id))) throw new Error("suite roster changed after the cut; release refused");
|
|
34432
34477
|
await deps.verifyPins(suitePins(record));
|
|
34433
34478
|
for (const member of record.members) {
|
|
34479
|
+
if (member.carried) {
|
|
34480
|
+
const live2 = await deps.backend({ repo: member.repo, id: member.id, bundles: {} });
|
|
34481
|
+
if (live2.version !== member.backend) throw new Error(`${member.repo}: live backend changed after the cut (${member.backend} \u2192 ${live2.version}); release refused`);
|
|
34482
|
+
continue;
|
|
34483
|
+
}
|
|
34434
34484
|
const [sha, live, rcHead] = await Promise.all([deps.tagSha(member.repo, member.tag), deps.backend({ repo: member.repo, id: member.id, bundles: {} }), deps.head(member.repo, "rc")]);
|
|
34435
34485
|
if (!sha || sha.toLowerCase() !== member.sha?.toLowerCase() || !member.released && rcHead?.toLowerCase() !== member.sha?.toLowerCase()) throw new Error(`${member.repo}: rc tag/branch ${member.tag} changed after the cut; release refused`);
|
|
34436
34486
|
const katipReleaseSha = member.id === "katip" ? await deps.tagSha(member.repo, suiteReleaseTag(member.tag)) : void 0;
|
|
@@ -34440,7 +34490,7 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
34440
34490
|
}
|
|
34441
34491
|
if ((await deps.tagSha(SUITE_REPO, record.leadRcTag))?.toLowerCase() !== record.leadRcSha?.toLowerCase()) throw new Error("MM-Work rc tag changed after the cut; release refused");
|
|
34442
34492
|
for (const member of record.members) {
|
|
34443
|
-
if (member.released) continue;
|
|
34493
|
+
if (member.released || member.carried) continue;
|
|
34444
34494
|
await deps.release(member.repo, suiteReleaseTag(member.tag), member.sha);
|
|
34445
34495
|
if (member.id === "katip") {
|
|
34446
34496
|
const expected = await deps.tagSha(member.repo, suiteReleaseTag(member.tag));
|
|
@@ -37585,8 +37635,11 @@ function readRepoVersion() {
|
|
|
37585
37635
|
}
|
|
37586
37636
|
function registerTrainCommands(program3, trainDeps) {
|
|
37587
37637
|
const train = program3.command("train").description("release-train observability \u2014 track position, version lag, and the shared preflight-and-heal doctor (#2688, #6067)");
|
|
37588
|
-
train.command("doctor").description("one shared preflight-and-heal verdict for the release, rcand and hotfix lanes; read-only unless --heal (#6067)").addOption(new Option("--lane <lane>", "train lane to check (defaults from the release track: direct \u2192 release)").choices([...TRAIN_LANES])).option("--dev", "judge the `release --dev` lane (development -> main, skipping rc): the lane starts from development and is read on development's workflows (#6318)").option("--heal", "repair safe local reconstructible state (scratch gitignore, churn, stray local tags, ff-only branch lag, finished scratch branches, stale alignment ledger leg); it never clears a ledger-pending, ledger-failed or branch-mismatch blocker (#6404, #6401, #6399) \u2014 those need `mmi-cli devops release --resume` (or, with fresh approval, `--abort --apply`) and `git checkout <start branch>`, so exit 1 after --heal is the gate working, not a failed heal").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").option("--json", "machine-readable output").option("--out <path>", "write the verdict to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802/#6563); pair with --json for a machine-readable file (e.g. `--json --out .jerv/tmp/train-doctor.json`); the file is written even when the verdict is NOT READY").addHelpText("after", "\nExit codes:\n 0 ready \u2014 no blocker and origin verified\n 1 a blocker remains, or origin could not be verified (never green on an unread origin)\n\nEvery finding carries code, severity (blocker|warning|healed|info), source (local|origin), remedy and a\ndocs/Guides/train-troubleshooting.md#<code> anchor. `release --apply` and `rcand --apply` run this at step 0 with --heal.\n").action(async (o) => {
|
|
37638
|
+
train.command("doctor").description("one shared preflight-and-heal verdict for the release, rcand and hotfix lanes; read-only unless --heal (#6067)").addOption(new Option("--lane <lane>", "train lane to check (defaults from the release track: direct \u2192 release)").choices([...TRAIN_LANES])).option("--dev", "judge the `release --dev` lane (development -> main, skipping rc): the lane starts from development and is read on development's workflows (#6318)").option("--heal", "repair safe local reconstructible state (scratch gitignore, churn, stray local tags, ff-only branch lag, finished scratch branches, stale alignment ledger leg); it never clears a ledger-pending, ledger-failed or branch-mismatch blocker (#6404, #6401, #6399) \u2014 those need `mmi-cli devops release --resume` (or, with fresh approval, `--abort --apply`) and `git checkout <start branch>`, so exit 1 after --heal is the gate working, not a failed heal").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").option("--suite-solo", "judge an owner-approved solo release of one suite module, as `release --suite-solo --owner-go` does (requires --owner-go; never on rcand, #7327)").option("--owner-go", "explicit owner approval for the suite-solo lane (requires --suite-solo)").option("--json", "machine-readable output").option("--out <path>", "write the verdict to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802/#6563); pair with --json for a machine-readable file (e.g. `--json --out .jerv/tmp/train-doctor.json`); the file is written even when the verdict is NOT READY").addHelpText("after", "\nExit codes:\n 0 ready \u2014 no blocker and origin verified\n 1 a blocker remains, or origin could not be verified (never green on an unread origin)\n\nEvery finding carries code, severity (blocker|warning|healed|info), source (local|origin), remedy and a\ndocs/Guides/train-troubleshooting.md#<code> anchor. `release --apply` and `rcand --apply` run this at step 0 with --heal.\n").action(async (o) => {
|
|
37589
37639
|
try {
|
|
37640
|
+
if (o.suiteSolo !== o.ownerGo || o.lane === "rcand" && o.suiteSolo) {
|
|
37641
|
+
return failGraceful("train doctor: a suite-solo verdict needs BOTH --suite-solo --owner-go, and never on the rcand lane");
|
|
37642
|
+
}
|
|
37590
37643
|
if (o.dev && o.lane && o.lane !== "release") {
|
|
37591
37644
|
return failGraceful(`train doctor: --dev applies only to the release lane \u2014 it names the development -> main variant that skips rc, which the ${o.lane} lane does not have`);
|
|
37592
37645
|
}
|
|
@@ -37607,7 +37660,7 @@ function registerTrainCommands(program3, trainDeps) {
|
|
|
37607
37660
|
}
|
|
37608
37661
|
}
|
|
37609
37662
|
};
|
|
37610
|
-
const verdict = await runTrainDoctor({ lane: o.lane, dev: o.dev === true, heal: o.heal === true, train: deps, repo: o.repo, cwd: root, argv: INVOKED_ARGV });
|
|
37663
|
+
const verdict = await runTrainDoctor({ lane: o.lane, dev: o.dev === true, heal: o.heal === true, train: deps, repo: o.repo, cwd: root, argv: INVOKED_ARGV, suiteSolo: o.suiteSolo === true });
|
|
37611
37664
|
if (verdict.reexecCode !== void 0) return process.exit(verdict.reexecCode);
|
|
37612
37665
|
const output = o.json ? JSON.stringify(verdict, null, 2) : formatTrainDoctor(verdict);
|
|
37613
37666
|
if (!o.out) console.log(output);
|
|
@@ -44456,7 +44509,7 @@ function registerSuiteCommands(program3) {
|
|
|
44456
44509
|
console.log(o.json ? JSON.stringify(result, null, 2) : [
|
|
44457
44510
|
`suite rcand ${o.dryRun ? "dry-run" : "receipt"}:`,
|
|
44458
44511
|
...result.plan.map((line) => ` - ${line}`),
|
|
44459
|
-
...result.record?.members.map((m) => ` - ${m.repo}: ${m.tag} (${m.sha?.slice(0, 12)}) ${m.published ? "served" : "pending"}`) ?? [],
|
|
44512
|
+
...result.record?.members.map((m) => m.carried ? ` - ${m.repo}: carried (nothing new) at ${m.mainPin}` : ` - ${m.repo}: ${m.tag} (${m.sha?.slice(0, 12)}) ${m.published ? "served" : "pending"}`) ?? [],
|
|
44460
44513
|
...result.record ? [` - pin PR ${result.record.pinPr}: merged; MM-Work ${result.record.leadRcTag}`] : []
|
|
44461
44514
|
].join("\n"));
|
|
44462
44515
|
} catch (e) {
|
|
@@ -44479,7 +44532,7 @@ function registerSuiteCommands(program3) {
|
|
|
44479
44532
|
}
|
|
44480
44533
|
console.log(o.json ? JSON.stringify(result, null, 2) : [
|
|
44481
44534
|
`suite release: ${result.leadRcTag} \u2192 main`,
|
|
44482
|
-
...result.members.map((m) => ` - ${m.repo}: ${m.tag} \u2192 main ${m.released ? "released" : "pending"}`),
|
|
44535
|
+
...result.members.map((m) => m.carried ? ` - ${m.repo}: carried, untouched` : ` - ${m.repo}: ${m.tag} \u2192 main ${m.released ? "released" : "pending"}`),
|
|
44483
44536
|
` - MM-Work: ${result.leadReleased ? "released" : "pending"}; announcement: ${result.announcement}`
|
|
44484
44537
|
].join("\n"));
|
|
44485
44538
|
} catch (e) {
|
package/package.json
CHANGED