@mutmutco/cli 4.3.60 → 4.3.62
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 +4 -0
- package/dist/main.cjs +174 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,4 +85,8 @@ node cli/dist/index.cjs --version
|
|
|
85
85
|
node cli/dist/index.cjs doctor --json
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
When reproducing or verifying CLI behavior from a checkout, always run the checkout's built
|
|
89
|
+
`node cli/dist/index.cjs` — the globally installed `mmi-cli` can lag or diverge from this source
|
|
90
|
+
(#6629), and a reproduction against the global install misreports the fix you are validating.
|
|
91
|
+
|
|
88
92
|
The Claude Code plugin also ships its own `bin/mmi-cli` launcher, so plugin-enabled Bash sessions can use the bundled command even without a global npm install. Codex and other non-Claude surfaces use the standalone npm package or this repo-local fallback.
|
package/dist/main.cjs
CHANGED
|
@@ -12070,12 +12070,15 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
|
|
|
12070
12070
|
resume
|
|
12071
12071
|
};
|
|
12072
12072
|
}
|
|
12073
|
-
function
|
|
12073
|
+
function laneContestParts(contest) {
|
|
12074
12074
|
const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? "an unreadable lane (its claim-marker comments could not be read, so no holder can be named)" : "another lane";
|
|
12075
12075
|
const seen = contest.live.join("; ");
|
|
12076
12076
|
const unreadable = contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : "";
|
|
12077
|
-
|
|
12078
|
-
|
|
12077
|
+
return { holder, why: [seen, unreadable].filter(Boolean).join("; ") };
|
|
12078
|
+
}
|
|
12079
|
+
function laneContestMessage(ref, contest, verb) {
|
|
12080
|
+
const { holder, why } = laneContestParts(contest);
|
|
12081
|
+
return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); re-run with --force to take it anyway` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
|
|
12079
12082
|
}
|
|
12080
12083
|
function ghError(e) {
|
|
12081
12084
|
const err = e;
|
|
@@ -12633,9 +12636,19 @@ async function unclaimBoardIssue(options, deps = {}) {
|
|
|
12633
12636
|
throw boardNotFoundError(`${selector.repo}#${selector.number}`, { owner: cfg.projectOwner, number: cfg.projectNumber });
|
|
12634
12637
|
}
|
|
12635
12638
|
const toStatus = options.toStatus ?? "Todo";
|
|
12636
|
-
if (
|
|
12639
|
+
if (item.contentType === "Issue") {
|
|
12637
12640
|
const contest = await checkLaneContest(client, item);
|
|
12638
|
-
if (contest.contested)
|
|
12641
|
+
if (contest.contested) {
|
|
12642
|
+
if (!options.force) {
|
|
12643
|
+
throw new Error(withGatedWriteChainHint(laneContestMessage(item.ref, contest, "unclaim")));
|
|
12644
|
+
}
|
|
12645
|
+
if (!options.holderDead) {
|
|
12646
|
+
const { holder, why } = laneContestParts(contest);
|
|
12647
|
+
throw new Error(withGatedWriteChainHint(
|
|
12648
|
+
`refusing to unclaim ${item.ref} with --force: ${holder} still shows live evidence of active work (${why}); --force no longer overrides a live lane alone \u2014 re-run with --holder-dead once you have evidence the holder's session is dead, or wait for its lane to go stale (#6625)`
|
|
12649
|
+
));
|
|
12650
|
+
}
|
|
12651
|
+
}
|
|
12639
12652
|
}
|
|
12640
12653
|
try {
|
|
12641
12654
|
await client.rest("DELETE", `repos/${item.repository}/issues/${item.number}/assignees`, {
|
|
@@ -15788,10 +15801,10 @@ var rollout_plan_default = {
|
|
|
15788
15801
|
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)."
|
|
15789
15802
|
},
|
|
15790
15803
|
baseline: {
|
|
15791
|
-
version: "4.3.
|
|
15792
|
-
tag: "v4.3.
|
|
15793
|
-
commit: "
|
|
15794
|
-
npm: "@mutmutco/cli@4.3.
|
|
15804
|
+
version: "4.3.62",
|
|
15805
|
+
tag: "v4.3.62",
|
|
15806
|
+
commit: "d664b30d09d7",
|
|
15807
|
+
npm: "@mutmutco/cli@4.3.62"
|
|
15795
15808
|
},
|
|
15796
15809
|
exitCriterion: "fleet-n-of-n",
|
|
15797
15810
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15808,14 +15821,14 @@ var rollout_plan_default = {
|
|
|
15808
15821
|
repo: "mutmutco/mmi-hub",
|
|
15809
15822
|
role: "canary",
|
|
15810
15823
|
schedule: "train",
|
|
15811
|
-
v3Target: "v4.3.
|
|
15824
|
+
v3Target: "v4.3.62"
|
|
15812
15825
|
}
|
|
15813
15826
|
],
|
|
15814
15827
|
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.",
|
|
15815
15828
|
rollback: {
|
|
15816
15829
|
independent: true,
|
|
15817
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15818
|
-
v3Target: "v4.3.
|
|
15830
|
+
mechanism: "npm dist-tag latest -> 4.3.62 and redeploy the Hub Lambda from tag v4.3.62 (d664b30d09d7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15831
|
+
v3Target: "v4.3.62 (@mutmutco/cli@4.3.62, tag commit d664b30d09d7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15819
15832
|
}
|
|
15820
15833
|
},
|
|
15821
15834
|
{
|
|
@@ -21451,6 +21464,69 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
21451
21464
|
};
|
|
21452
21465
|
});
|
|
21453
21466
|
}
|
|
21467
|
+
function supersedeablePublishLegShape(rec) {
|
|
21468
|
+
return rec.state === "failed" || rec.state === "pending" && rec.runId == null;
|
|
21469
|
+
}
|
|
21470
|
+
async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
21471
|
+
const path2 = releaseLedgerPath(cwd);
|
|
21472
|
+
return withFileLock(`${path2}.lock`, { label: "release ledger" }, async () => {
|
|
21473
|
+
let prior;
|
|
21474
|
+
try {
|
|
21475
|
+
prior = parseReleaseLedger((0, import_node_fs21.readFileSync)(path2, "utf8"));
|
|
21476
|
+
} catch (e) {
|
|
21477
|
+
throw new ReleaseLedgerError(
|
|
21478
|
+
"unreadable",
|
|
21479
|
+
`release --supersede-publish: the active release ledger at ${path2} could not be read (${e instanceof Error ? e.message : String(e)}) \u2014 resolve it by hand before any train; nothing was written (#6638)`
|
|
21480
|
+
);
|
|
21481
|
+
}
|
|
21482
|
+
if (prior.phases.publish.superseded === true) {
|
|
21483
|
+
throw new Error(`release --supersede-publish: the ${prior.tag} publish leg is already superseded \u2014 nothing to do; the next train runs normally`);
|
|
21484
|
+
}
|
|
21485
|
+
if (!supersedeablePublishLegShape(prior.phases.publish)) {
|
|
21486
|
+
throw new Error(
|
|
21487
|
+
`release --supersede-publish: the ${prior.tag} publish leg is ${prior.phases.publish.state}` + (prior.phases.publish.runId != null ? ` with run ${prior.phases.publish.runId} \u2014 a run-backed leg is resolved by watching/reverifying its run, never superseded` : "") + "; the supersession closes only a FAILED publish leg, or a PENDING one no run correlates (the uncorrelatable historical shape); nothing was written (#6638)"
|
|
21488
|
+
);
|
|
21489
|
+
}
|
|
21490
|
+
const phases = { ...prior.phases };
|
|
21491
|
+
if (phases.alignment.state === "pending") {
|
|
21492
|
+
const closed = await alignmentLegClosedLive(deps, prior);
|
|
21493
|
+
if (closed) phases.alignment = closed;
|
|
21494
|
+
}
|
|
21495
|
+
const unresolved = ["promotion", "ordinaryCi", "githubRelease", "deploy", "alignment"].filter((phase) => phases[phase].state === "pending" || phases[phase].state === "failed");
|
|
21496
|
+
if (unresolved.length > 0) {
|
|
21497
|
+
throw new Error(
|
|
21498
|
+
`release --supersede-publish: ${prior.tag} has unresolved legs besides publish (${unresolved.map((p) => `${p}=${phases[p].state}`).join(", ")}) \u2014 the supersession closes only the publish leg of an otherwise-complete release; resolve those first, nothing was written (#6638)`
|
|
21499
|
+
);
|
|
21500
|
+
}
|
|
21501
|
+
let liveSha = "";
|
|
21502
|
+
try {
|
|
21503
|
+
liveSha = (await deps.run("git", ["ls-remote", "origin", `refs/tags/${prior.tag}`])).trim().split(/\s+/)[0] ?? "";
|
|
21504
|
+
} catch (e) {
|
|
21505
|
+
throw new Error(`release --supersede-publish: could not prove ${prior.tag} live on origin (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to close the leg on an unproven state; retry when origin answers (#6638)`);
|
|
21506
|
+
}
|
|
21507
|
+
if (liveSha.toLowerCase() !== prior.tagSha.toLowerCase()) {
|
|
21508
|
+
throw new Error(
|
|
21509
|
+
`release --supersede-publish: 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 (#6638)`
|
|
21510
|
+
);
|
|
21511
|
+
}
|
|
21512
|
+
const note = `superseded: content-caused publish failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6638)`;
|
|
21513
|
+
atomicWriteJson(path2, {
|
|
21514
|
+
...prior,
|
|
21515
|
+
phases: {
|
|
21516
|
+
...phases,
|
|
21517
|
+
publish: { state: "skipped", sha: prior.tagSha, superseded: true, note, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
21518
|
+
},
|
|
21519
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21520
|
+
});
|
|
21521
|
+
return {
|
|
21522
|
+
command: "release-supersede-publish",
|
|
21523
|
+
repo: prior.repo,
|
|
21524
|
+
tag: prior.tag,
|
|
21525
|
+
tagSha: prior.tagSha,
|
|
21526
|
+
note: `closed the ${prior.tag} publish 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`
|
|
21527
|
+
};
|
|
21528
|
+
});
|
|
21529
|
+
}
|
|
21454
21530
|
async function reverifyPriorRunLegs(deps, path2, targetTag) {
|
|
21455
21531
|
let prior;
|
|
21456
21532
|
try {
|
|
@@ -23243,7 +23319,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
23243
23319
|
if (persisted.phases.deploy.superseded !== true) {
|
|
23244
23320
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
|
|
23245
23321
|
}
|
|
23246
|
-
|
|
23322
|
+
if (persisted.phases.publish.superseded !== true) {
|
|
23323
|
+
await recordPhase(ledger, deps, anchors, "publish", phaseEntry(phaseInputs.publish), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
|
|
23324
|
+
}
|
|
23247
23325
|
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: the GitHub Release exists at the immutable tag SHA" }), { strict: true, landed: "the promotion (verified live)" });
|
|
23248
23326
|
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: origin/main contains the immutable tag SHA" }), { strict: true, landed: "nothing beyond the already-public release" });
|
|
23249
23327
|
}
|
|
@@ -23524,6 +23602,17 @@ async function runReleaseSupersedeDeploy(deps, options = {}) {
|
|
|
23524
23602
|
throw new Error(message2.includes("nothing was written") || message2.includes("nothing to do") ? message2 : `${message2}; nothing was written`);
|
|
23525
23603
|
}
|
|
23526
23604
|
}
|
|
23605
|
+
async function runReleaseSupersedePublish(deps, options = {}) {
|
|
23606
|
+
if (!options.approved) {
|
|
23607
|
+
throw new Error("release --supersede-publish requires --apply after explicit master approval; nothing was written");
|
|
23608
|
+
}
|
|
23609
|
+
try {
|
|
23610
|
+
return await supersedeContentCausedPublishLeg(deps, process.cwd());
|
|
23611
|
+
} catch (e) {
|
|
23612
|
+
const message2 = e instanceof Error ? e.message : String(e);
|
|
23613
|
+
throw new Error(message2.includes("nothing was written") || message2.includes("nothing to do") ? message2 : `${message2}; nothing was written`);
|
|
23614
|
+
}
|
|
23615
|
+
}
|
|
23527
23616
|
async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
23528
23617
|
if (!options.approved) {
|
|
23529
23618
|
throw new Error("release --retry-publish requires --apply after explicit approval; nothing was written");
|
|
@@ -26318,7 +26407,7 @@ function registerBoardCommands(program3) {
|
|
|
26318
26407
|
return failGraceful(`board set-priority failed: ${e.message}`);
|
|
26319
26408
|
}
|
|
26320
26409
|
});
|
|
26321
|
-
board.command("unclaim <issue>").description("clear the assignee and reset the board Status (defaults to Todo) \u2014 the inverse of claim").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--to-status <status>", `set Status after unclaim (default Todo; one of ${BOARD_STATUSES.join(", ")})`).option("--force", "release
|
|
26410
|
+
board.command("unclaim <issue>").description("clear the assignee and reset the board Status (defaults to Todo) \u2014 the inverse of claim").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--to-status <status>", `set Status after unclaim (default Todo; one of ${BOARD_STATUSES.join(", ")})`).option("--force", "release a claim whose lane has gone stale or shows no live evidence (#3727); a lane that still shows live evidence also needs --holder-dead (#6625)").option("--holder-dead", "acknowledge the holding lane is known dead \u2014 required with --force when the holder still shows live evidence (#6625)").option("--allow-partial", "return success JSON if the assignee clear or status move fails").action(async (issueRef, o) => {
|
|
26322
26411
|
const toStatus = o.toStatus ? resolveBoardStatus(o.toStatus) : void 0;
|
|
26323
26412
|
try {
|
|
26324
26413
|
const result = await unclaimBoardIssue({
|
|
@@ -26327,6 +26416,7 @@ function registerBoardCommands(program3) {
|
|
|
26327
26416
|
repo: o.repo,
|
|
26328
26417
|
toStatus,
|
|
26329
26418
|
force: o.force,
|
|
26419
|
+
holderDead: o.holderDead,
|
|
26330
26420
|
allowPartial: o.allowPartial
|
|
26331
26421
|
});
|
|
26332
26422
|
if (o.json) return console.log(JSON.stringify(result));
|
|
@@ -33190,19 +33280,23 @@ function findCommandInManifest(manifest, commandPath3) {
|
|
|
33190
33280
|
return visit(manifest.tree);
|
|
33191
33281
|
}
|
|
33192
33282
|
function registerExplainCommand(program3) {
|
|
33193
|
-
program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').addOption(new Option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").choices(Object.keys(LOOP_PLAYBOOKS))).option("--json", "machine-readable focused command, route, or loop detail").option("--recursive", "include full schemas for immediate children (default: compact child index)").option("--full", "alias for --recursive").action((commandArgs, opts) => {
|
|
33283
|
+
program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').addOption(new Option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").choices(Object.keys(LOOP_PLAYBOOKS))).option("--json", "machine-readable focused command, route, or loop detail").option("--out <path>", "write the output to a UTF-8 file instead of stdout (#5802 byte contract)").option("--recursive", "include full schemas for immediate children (default: compact child index)").option("--full", "alias for --recursive").action((commandArgs, opts) => {
|
|
33284
|
+
const emit = (output) => {
|
|
33285
|
+
if (!opts.out) console.log(output);
|
|
33286
|
+
else console.log(`Wrote explain output to ${opts.out} (UTF-8, ${writeUtf8Receipt(opts.out, output)} bytes)`);
|
|
33287
|
+
};
|
|
33194
33288
|
if (opts.loop) {
|
|
33195
33289
|
if (!LOOP_PLAYBOOKS[opts.loop]) {
|
|
33196
33290
|
const valid = Object.keys(LOOP_PLAYBOOKS).join(", ");
|
|
33197
33291
|
fail(`explain --loop: unknown loop "${opts.loop}"; expected one of: ${valid}`, { code: ERROR_CODES.ERR_BAD_ENUM, expected: Object.keys(LOOP_PLAYBOOKS) });
|
|
33198
33292
|
return;
|
|
33199
33293
|
}
|
|
33200
|
-
|
|
33294
|
+
emit(opts.json ? JSON.stringify({ schema_version: 1, scope: "loop", name: opts.loop, playbook: LOOP_PLAYBOOKS[opts.loop] }, null, 2) : formatExplainLoop(LOOP_PLAYBOOKS[opts.loop]));
|
|
33201
33295
|
return;
|
|
33202
33296
|
}
|
|
33203
33297
|
const manifest = buildCommandManifest(program3);
|
|
33204
33298
|
if (!commandArgs.length) {
|
|
33205
|
-
|
|
33299
|
+
emit(opts.json ? JSON.stringify(compactRouteManifest(manifest), null, 2) : formatManifestHuman(manifest));
|
|
33206
33300
|
return;
|
|
33207
33301
|
}
|
|
33208
33302
|
const commandPath3 = commandArgs.join(" ");
|
|
@@ -33225,7 +33319,7 @@ function registerExplainCommand(program3) {
|
|
|
33225
33319
|
return;
|
|
33226
33320
|
}
|
|
33227
33321
|
const recursive = Boolean(opts.recursive || opts.full);
|
|
33228
|
-
|
|
33322
|
+
emit(opts.json ? JSON.stringify(explainCommandManifest(manifest, command, { recursive }), null, 2) : command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
|
|
33229
33323
|
});
|
|
33230
33324
|
}
|
|
33231
33325
|
|
|
@@ -40325,6 +40419,19 @@ function registerCollaborationCommands(program3) {
|
|
|
40325
40419
|
delete rest.number;
|
|
40326
40420
|
return rest;
|
|
40327
40421
|
}
|
|
40422
|
+
async function resolveTransferredRef(repo, number) {
|
|
40423
|
+
try {
|
|
40424
|
+
const { stdout } = await execFileP("gh", ["api", `repos/${repo}/issues/${number}`], { timeout: GIT_TIMEOUT_MS });
|
|
40425
|
+
const issue2 = JSON.parse(stdout);
|
|
40426
|
+
const match = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)$/.exec(issue2.html_url ?? "");
|
|
40427
|
+
if (!match || typeof issue2.number !== "number") return void 0;
|
|
40428
|
+
const [currentRepo, currentNumber] = [match[1], Number(match[2])];
|
|
40429
|
+
if (currentRepo.toLowerCase() === repo.toLowerCase() && currentNumber === number) return void 0;
|
|
40430
|
+
return { repo: currentRepo, number: currentNumber, url: issue2.html_url };
|
|
40431
|
+
} catch {
|
|
40432
|
+
return void 0;
|
|
40433
|
+
}
|
|
40434
|
+
}
|
|
40328
40435
|
issue.command("view <ref>").aliases(["show", "get", "read"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show`, `get` and `read` alias view (#5483/#5706/#6078)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). `parent` is synthetic: selectable here, composed by the wrapper via a REST parent read, never sent to gh (#5970). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (ref, o) => {
|
|
40329
40436
|
let parsed;
|
|
40330
40437
|
try {
|
|
@@ -40338,19 +40445,33 @@ function registerCollaborationCommands(program3) {
|
|
|
40338
40445
|
const fields = normalizeIssueViewJsonFields(o.json);
|
|
40339
40446
|
const gh = resolveIssueViewGhFields(fields);
|
|
40340
40447
|
const emit = (data, parent) => console.log(JSON.stringify({ ...gh.numberIsTransport ? withoutTransportNumber(data) : data, ...parent }));
|
|
40341
|
-
|
|
40448
|
+
const readIssue = async (targetRepo2, targetNumber) => {
|
|
40342
40449
|
if (o.comments || o.context) {
|
|
40343
40450
|
const boardProjectId = o.context ? await resolveBoardProjectId(o.repo) : void 0;
|
|
40344
|
-
const data2 = await runIssueViewContext(queryDeps(), { number:
|
|
40345
|
-
|
|
40346
|
-
return;
|
|
40451
|
+
const data2 = await runIssueViewContext(queryDeps(), { number: targetNumber, repo: targetRepo2, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: gh.ghFields, boardProjectId });
|
|
40452
|
+
return { data: data2, parent: await readParentField(targetNumber, targetRepo2) };
|
|
40347
40453
|
}
|
|
40348
|
-
const data = await ghJson(["issue", "view", String(
|
|
40349
|
-
|
|
40454
|
+
const data = await ghJson(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh.ghFields]);
|
|
40455
|
+
return { data, parent: await readParentField(targetNumber, targetRepo2) };
|
|
40456
|
+
};
|
|
40457
|
+
try {
|
|
40458
|
+
const { data, parent } = await readIssue(repo, n);
|
|
40459
|
+
emit(data, parent);
|
|
40350
40460
|
} catch (e) {
|
|
40351
40461
|
const err = e;
|
|
40352
40462
|
const raw = (err.stderr || err.message || String(e)).trim();
|
|
40353
40463
|
const argvNote = o.json ? ` (received --json argv: ${JSON.stringify(o.json)}, normalized to "${fields}")` : "";
|
|
40464
|
+
const moved = /Could not resolve to an issue/i.test(raw) ? await resolveTransferredRef(repo, n) : void 0;
|
|
40465
|
+
if (moved) {
|
|
40466
|
+
try {
|
|
40467
|
+
const { data, parent } = await readIssue(moved.repo, moved.number);
|
|
40468
|
+
emit({ ...data, transferred: { from: `${repo}#${n}`, to: `${moved.repo}#${moved.number}`, url: moved.url } }, parent);
|
|
40469
|
+
return;
|
|
40470
|
+
} catch (reRead) {
|
|
40471
|
+
const reErr = reRead;
|
|
40472
|
+
return fail(`issue view: ${repo}#${n} was transferred to ${moved.repo}#${moved.number} (${moved.url}), but re-reading it there failed: ${(reErr.stderr || reErr.message || String(reRead)).trim()}`);
|
|
40473
|
+
}
|
|
40474
|
+
}
|
|
40354
40475
|
return fail(`issue view: ${raw}${argvNote}`);
|
|
40355
40476
|
}
|
|
40356
40477
|
});
|
|
@@ -44202,6 +44323,9 @@ function renderReleaseAbort(r) {
|
|
|
44202
44323
|
function renderReleaseSupersedeDeploy(r) {
|
|
44203
44324
|
return `mmi-cli devops release --supersede-deploy --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
44204
44325
|
}
|
|
44326
|
+
function renderReleaseSupersedePublish(r) {
|
|
44327
|
+
return `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
44328
|
+
}
|
|
44205
44329
|
function renderReleasePublishRetry(r) {
|
|
44206
44330
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
44207
44331
|
}
|
|
@@ -44427,6 +44551,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44427
44551
|
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
|
|
44428
44552
|
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
|
|
44429
44553
|
{ flags: "--supersede-deploy", description: "with --apply, master-approved closure of a content-caused failed deploy leg on a PUBLISHED release whose immutable tag can never deploy green \u2014 the next patch train then carries the fix (#6553)" },
|
|
44554
|
+
{ flags: "--supersede-publish", description: "with --apply, master-approved closure of a content-caused failed publish leg on a fully PROMOTED release whose immutable tag can never publish green \u2014 the next patch train then carries the fix (#6638)" },
|
|
44430
44555
|
{ flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
|
|
44431
44556
|
];
|
|
44432
44557
|
for (const f of RELEASE_ONLY_FLAGS) {
|
|
@@ -44457,6 +44582,9 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44457
44582
|
if (o.supersedeDeploy && commandName !== "release") {
|
|
44458
44583
|
return fail(`${commandName}: --supersede-deploy applies only to release \u2014 it closes a content-caused failed deploy leg on a published release's ledger. Run: mmi-cli devops release --supersede-deploy --apply`);
|
|
44459
44584
|
}
|
|
44585
|
+
if (o.supersedePublish && commandName !== "release") {
|
|
44586
|
+
return fail(`${commandName}: --supersede-publish applies only to release \u2014 it closes a content-caused failed publish leg on a promoted release's ledger. Run: mmi-cli devops release --supersede-publish --apply`);
|
|
44587
|
+
}
|
|
44460
44588
|
if (o.retryPublish && commandName !== "release") {
|
|
44461
44589
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
44462
44590
|
}
|
|
@@ -44504,6 +44632,9 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44504
44632
|
return failGraceful(`release --abort: ${e.message}`);
|
|
44505
44633
|
}
|
|
44506
44634
|
}
|
|
44635
|
+
if (o.supersedeDeploy && o.supersedePublish) {
|
|
44636
|
+
return fail("release: --supersede-deploy and --supersede-publish are mutually exclusive \u2014 close the one leg that failed content-caused; the other leg has its own resolution paths");
|
|
44637
|
+
}
|
|
44507
44638
|
if (o.supersedeDeploy) {
|
|
44508
44639
|
if (o.resume) return fail("release: --supersede-deploy and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-derives the very leg it closes");
|
|
44509
44640
|
if (o.abort) return fail("release: --supersede-deploy and --abort are mutually exclusive \u2014 the closure is for a PUBLISHED release, abort only ever deletes a proven unpublished candidate");
|
|
@@ -44523,6 +44654,25 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44523
44654
|
return failGraceful(`release --supersede-deploy: ${e.message}`);
|
|
44524
44655
|
}
|
|
44525
44656
|
}
|
|
44657
|
+
if (o.supersedePublish) {
|
|
44658
|
+
if (o.resume) return fail("release: --supersede-publish and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-reads the very failed run it closes");
|
|
44659
|
+
if (o.abort) return fail("release: --supersede-publish and --abort are mutually exclusive \u2014 the closure is for a PROMOTED release, abort only ever deletes a proven unpublished candidate");
|
|
44660
|
+
if (o.retryPublish) return fail("release: --supersede-publish cannot be combined with --retry-publish \u2014 a content-caused failure on the immutable tag cannot be retried green");
|
|
44661
|
+
if (!o.apply) return fail("release: --supersede-publish requires --apply after explicit master approval; nothing was written");
|
|
44662
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
44663
|
+
return fail("release: --supersede-publish accepts only --apply, --repo, --json and --out; it closes a ledger leg and dispatches nothing");
|
|
44664
|
+
}
|
|
44665
|
+
if (o.repo) {
|
|
44666
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), "mmi-cli devops release --supersede-publish --apply");
|
|
44667
|
+
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
44668
|
+
}
|
|
44669
|
+
try {
|
|
44670
|
+
const result = await runReleaseSupersedePublish(trainApplyDeps(), { approved: true });
|
|
44671
|
+
return emitTrainResult("release --supersede-publish", o.json ? JSON.stringify(result, null, 2) : renderReleaseSupersedePublish(result), o.out);
|
|
44672
|
+
} catch (e) {
|
|
44673
|
+
return failGraceful(`release --supersede-publish: ${e.message}`);
|
|
44674
|
+
}
|
|
44675
|
+
}
|
|
44526
44676
|
if (o.resume) {
|
|
44527
44677
|
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
|
|
44528
44678
|
try {
|
package/package.json
CHANGED