@mutmutco/cli 4.2.1 → 4.2.3

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.
Files changed (2) hide show
  1. package/dist/main.cjs +181 -55
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ var program = new Command();
3416
3416
  // src/index.ts
3417
3417
  var import_promises8 = require("node:fs/promises");
3418
3418
  var import_node_fs43 = require("node:fs");
3419
- var import_node_child_process19 = require("node:child_process");
3419
+ var import_node_child_process20 = require("node:child_process");
3420
3420
 
3421
3421
  // src/cli-shared.ts
3422
3422
  var import_node_child_process3 = require("node:child_process");
@@ -4522,10 +4522,29 @@ function targetFlagValueFromArgv(flag, argv) {
4522
4522
  if (!next || next.startsWith("-")) return void 0;
4523
4523
  return next;
4524
4524
  }
4525
+ function survivingFlagArgs(argv, rejectedFlag, ownFlags) {
4526
+ const out = [];
4527
+ for (let i = 0; i < argv.length; i += 1) {
4528
+ const token = argv[i];
4529
+ if (!token.startsWith("--")) continue;
4530
+ const [name, inlineValue] = token.includes("=") ? [token.slice(0, token.indexOf("=")), token.slice(token.indexOf("=") + 1)] : [token, void 0];
4531
+ const values = [];
4532
+ if (inlineValue === void 0) {
4533
+ while (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("-")) {
4534
+ values.push(argv[i + 1]);
4535
+ i += 1;
4536
+ }
4537
+ }
4538
+ if (name === rejectedFlag || !ownFlags.includes(name)) continue;
4539
+ out.push(inlineValue !== void 0 ? token : name, ...values);
4540
+ }
4541
+ return out;
4542
+ }
4525
4543
  function formatPositionalTarget(commandPath3, argName, opts = {}) {
4526
4544
  const concrete = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
4527
- if (concrete) return `mmi-cli ${commandPath3} ${concrete}`;
4528
- return `mmi-cli ${commandPath3} <${argName}>`;
4545
+ const target = concrete ?? `<${argName}>`;
4546
+ const kept = opts.flag && opts.argv && opts.ownFlags ? survivingFlagArgs(opts.argv, opts.flag, opts.ownFlags) : [];
4547
+ return [`mmi-cli ${commandPath3}`, target, ...kept].join(" ");
4529
4548
  }
4530
4549
  var FLAG_SYNONYMS = /* @__PURE__ */ new Map([["--outcome", "--reason"]]);
4531
4550
  function synonymFlagFor(flag, ownFlags) {
@@ -4545,6 +4564,27 @@ var GUIDES = /* @__PURE__ */ new Map([
4545
4564
  expected: ["learning report", "oracle issue create", "oracle issue list"],
4546
4565
  did_you_mean: "learning report"
4547
4566
  }
4567
+ ],
4568
+ // #5821: `list` is the verb `oracle issue list` and `devops pr list` teach, so callers reach for
4569
+ // `oracle board list` next. Distance ranks the OTHER houses' `list` leaves above the board's own
4570
+ // reader, so the receipt pointed at `devops pr list` — a different house, for a board query.
4571
+ [
4572
+ "oracle board:list",
4573
+ {
4574
+ hint: "no `oracle board list` \u2014 the bounded board query is `oracle board read` (add `--json` for the machine-readable partition)",
4575
+ expected: ["oracle board read"],
4576
+ did_you_mean: "oracle board read"
4577
+ }
4578
+ ],
4579
+ // #5823: `label` names a capability, not a command. The mutation door is `oracle issue edit`
4580
+ // (`--add-label` / `--remove-label`); distance cannot bridge noun→verb, so name it here.
4581
+ [
4582
+ "oracle issue:label",
4583
+ {
4584
+ hint: "no `oracle issue label` \u2014 labels are edited through `oracle issue edit --add-label <name>` / `--remove-label <name>`",
4585
+ expected: ["oracle issue edit"],
4586
+ did_you_mean: "oracle issue edit"
4587
+ }
4548
4588
  ]
4549
4589
  ]);
4550
4590
  function unknownCommandDomainGuide(parentPath, token) {
@@ -11762,10 +11802,10 @@ var rollout_plan_default = {
11762
11802
  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)."
11763
11803
  },
11764
11804
  baseline: {
11765
- version: "4.2.1",
11766
- tag: "v4.2.1",
11767
- commit: "66e67b6905a1",
11768
- npm: "@mutmutco/cli@4.2.1"
11805
+ version: "4.2.3",
11806
+ tag: "v4.2.3",
11807
+ commit: "a54078f2ac92",
11808
+ npm: "@mutmutco/cli@4.2.3"
11769
11809
  },
11770
11810
  exitCriterion: "fleet-n-of-n",
11771
11811
  hubOnlyShortcut: "forbidden",
@@ -11782,14 +11822,14 @@ var rollout_plan_default = {
11782
11822
  repo: "mutmutco/mmi-hub",
11783
11823
  role: "canary",
11784
11824
  schedule: "train",
11785
- v3Target: "v4.2.1"
11825
+ v3Target: "v4.2.3"
11786
11826
  }
11787
11827
  ],
11788
11828
  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.",
11789
11829
  rollback: {
11790
11830
  independent: true,
11791
- mechanism: "npm dist-tag latest -> 4.2.1 and redeploy the Hub Lambda from tag v4.2.1 (66e67b6905a1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
11792
- v3Target: "v4.2.1 (@mutmutco/cli@4.2.1, tag commit 66e67b6905a1 \u2014 last known-good release carrying the repo-index v4-only contract)"
11831
+ mechanism: "npm dist-tag latest -> 4.2.3 and redeploy the Hub Lambda from tag v4.2.3 (a54078f2ac92); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
11832
+ v3Target: "v4.2.3 (@mutmutco/cli@4.2.3, tag commit a54078f2ac92 \u2014 last known-good release carrying the repo-index v4-only contract)"
11793
11833
  }
11794
11834
  },
11795
11835
  {
@@ -23508,8 +23548,11 @@ var defaultDeps = {
23508
23548
  },
23509
23549
  // Re-exec the SAME command line in the fresh CLI with inherited stdio (spawn, not execFile, so
23510
23550
  // output streams live) and hand its exit code back — the stale parent exits with it (#5762).
23511
- reexec: () => new Promise((resolve6) => {
23512
- const args = process.argv.slice(2);
23551
+ // #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
23552
+ // the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
23553
+ // instead of `devops release …`) and the converged child refused with exit 2.
23554
+ reexec: (argv) => new Promise((resolve6) => {
23555
+ const args = [...argv];
23513
23556
  const child2 = isWin2 ? (0, import_node_child_process10.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process10.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
23514
23557
  child2.on("exit", (code) => resolve6(code ?? 1));
23515
23558
  child2.on("error", () => resolve6(1));
@@ -23531,7 +23574,7 @@ async function selfConvergeTrainCli(input) {
23531
23574
  error: `self-converge did not take effect: mmi-cli on PATH still resolves to ${installed ?? "unknown"}, expected >= ${input.releasedVersion}`
23532
23575
  };
23533
23576
  }
23534
- const reexecCode = await deps.reexec();
23577
+ const reexecCode = await deps.reexec(input.argv);
23535
23578
  return { kind: "converged", reexecCode };
23536
23579
  } catch (e) {
23537
23580
  return { kind: "failed", error: e instanceof Error ? e.message : String(e) };
@@ -23988,6 +24031,11 @@ function renderSweep(r) {
23988
24031
 
23989
24032
  // src/hotfix-apply.ts
23990
24033
  var HOTFIX_RELEASE_WORKFLOWS = ["deploy.yml", "publish.yml"];
24034
+ function hotfixStatusWorkflows(deployModel) {
24035
+ if (deployModel === "registry-publish") return ["publish.yml"];
24036
+ if (deployModel === void 0 || deployModel === "hub-serverless" || deployModel === "tenant-container" || deployModel === "solo-container") return HOTFIX_RELEASE_WORKFLOWS;
24037
+ return [];
24038
+ }
23991
24039
  var HOTFIX_RUN_FIND_ATTEMPTS = 10;
23992
24040
  var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
23993
24041
  var HOTFIX_VERIFY_ATTEMPTS = 5;
@@ -24623,29 +24671,35 @@ async function runHotfixStatus(deps, versionInput) {
24623
24671
  const work = (async () => {
24624
24672
  const ctx = await buildTrainApplyContext(deps);
24625
24673
  await deps.run("git", ["fetch", "origin", "--tags"]);
24674
+ let workflows = HOTFIX_RELEASE_WORKFLOWS;
24675
+ let warnings = [];
24676
+ try {
24677
+ workflows = hotfixStatusWorkflows(await resolveHotfixDeployModel(deps, ctx));
24678
+ } catch (e) {
24679
+ warnings = [`deploy model unverified (${e.message ?? e}) \u2014 probed the default deploy.yml+publish.yml run list, so an unproven run may hold the verdict at unverified`];
24680
+ }
24626
24681
  let tag;
24627
24682
  let version;
24628
- let warnings = [];
24629
24683
  if (versionInput) {
24630
24684
  ({ tag, version } = normalizeHotfixVersion(versionInput));
24631
24685
  } else {
24632
24686
  const latest = await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--merged", "origin/main", "--sort=-v:refname"]).then((out) => out.split("\n").map((s) => s.trim()).find((t) => /^v\d+\.\d+\.\d+$/.test(t)));
24633
24687
  if (latest) {
24634
- const latestFacts = await gatherHotfixFacts(deps, ctx, latest, latest.slice(1));
24688
+ const latestFacts = await gatherHotfixFacts(deps, ctx, latest, latest.slice(1), workflows);
24635
24689
  const latestDerived = deriveHotfixState(latestFacts);
24636
24690
  if (latestDerived.state !== "complete") {
24637
24691
  return { ...ctx, command: "hotfix-status", ...latestFacts, ...latestDerived, warnings };
24638
24692
  }
24639
24693
  }
24640
- const found = await findInFlightHotfixVersion(deps, ctx, latest);
24641
- warnings = supersededHotfixWarnings(found.superseded, latest);
24694
+ const found = await findInFlightHotfixVersion(deps, ctx, latest, workflows);
24695
+ warnings = [...warnings, ...supersededHotfixWarnings(found.superseded, latest)];
24642
24696
  if (found.inFlight) {
24643
24697
  ({ tag, version } = found.inFlight);
24644
24698
  } else {
24645
24699
  ({ tag, version } = await deriveHotfixVersion(deps));
24646
24700
  }
24647
24701
  }
24648
- const facts = await gatherHotfixFacts(deps, ctx, tag, version);
24702
+ const facts = await gatherHotfixFacts(deps, ctx, tag, version, workflows);
24649
24703
  return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
24650
24704
  })();
24651
24705
  let timer;
@@ -24664,7 +24718,7 @@ async function runHotfixStatus(deps, versionInput) {
24664
24718
  if (timer) clearTimeout(timer);
24665
24719
  }
24666
24720
  }
24667
- async function gatherHotfixFacts(deps, ctx, tag, version) {
24721
+ async function gatherHotfixFacts(deps, ctx, tag, version, workflows = HOTFIX_RELEASE_WORKFLOWS) {
24668
24722
  const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
24669
24723
  const pr2 = await findHotfixPr(deps, ctx, tag);
24670
24724
  const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
@@ -24673,7 +24727,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
24673
24727
  const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
24674
24728
  const runs = [];
24675
24729
  if (releaseExists && tagSha) {
24676
- for (const workflow of HOTFIX_RELEASE_WORKFLOWS) {
24730
+ for (const workflow of workflows) {
24677
24731
  try {
24678
24732
  const out = await deps.run("gh", [
24679
24733
  "run",
@@ -24729,7 +24783,7 @@ function supersededHotfixWarnings(superseded, latestMainTag) {
24729
24783
  if (superseded.length === 0) return [];
24730
24784
  return [`skipped superseded hotfix marker(s) ${superseded.join(", ")} \u2014 merged to main but never released and at/below the latest released tag ${latestMainTag ?? "(none)"}; later releases already absorbed them (#976). Not actionable \u2014 run mmi-cli devops hotfix start for a new fix.`];
24731
24785
  }
24732
- async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
24786
+ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = HOTFIX_RELEASE_WORKFLOWS) {
24733
24787
  const tags = /* @__PURE__ */ new Set();
24734
24788
  const out = await deps.run("gh", [
24735
24789
  "pr",
@@ -24759,13 +24813,13 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
24759
24813
  const fresh = latestMainTag ? sorted.filter((t) => compareHotfixVersions(t, latestMainTag) > 0) : sorted;
24760
24814
  for (const tag of fresh) {
24761
24815
  const version = tag.slice(1);
24762
- const facts = await gatherHotfixFacts(deps, ctx, tag, version);
24816
+ const facts = await gatherHotfixFacts(deps, ctx, tag, version, workflows);
24763
24817
  if (deriveHotfixState(facts).state !== "complete") return { inFlight: { tag, version }, superseded: [] };
24764
24818
  }
24765
24819
  const stale = latestMainTag ? sorted.filter((t) => compareHotfixVersions(t, latestMainTag) <= 0) : [];
24766
24820
  const superseded = [];
24767
24821
  for (const tag of stale) {
24768
- const facts = await gatherHotfixFacts(deps, ctx, tag, tag.slice(1));
24822
+ const facts = await gatherHotfixFacts(deps, ctx, tag, tag.slice(1), workflows);
24769
24823
  if (deriveHotfixState(facts).state !== "complete") superseded.push(tag);
24770
24824
  }
24771
24825
  return { inFlight: null, superseded };
@@ -32376,7 +32430,53 @@ async function fetchRestCorePool(gh = defaultGhApi) {
32376
32430
  }
32377
32431
 
32378
32432
  // src/pr-create-docs-check.ts
32433
+ var import_node_child_process18 = require("node:child_process");
32379
32434
  var GIT_TIMEOUT_MS2 = 15e3;
32435
+ function catFileBatch(root, ref, paths) {
32436
+ if (paths.length === 0) return Promise.resolve([]);
32437
+ return new Promise((resolve6) => {
32438
+ const chunks = [];
32439
+ let settled = false;
32440
+ const child2 = (0, import_node_child_process18.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
32441
+ const finish = () => {
32442
+ if (settled) return;
32443
+ settled = true;
32444
+ clearTimeout(timer);
32445
+ resolve6(parseCatFileBatch(Buffer.concat(chunks), paths.length));
32446
+ };
32447
+ const timer = setTimeout(() => {
32448
+ child2.kill("SIGKILL");
32449
+ finish();
32450
+ }, GIT_TIMEOUT_MS2);
32451
+ timer.unref?.();
32452
+ child2.stdout.on("data", (c) => chunks.push(c));
32453
+ child2.on("error", finish);
32454
+ child2.stdin.on("error", () => {
32455
+ });
32456
+ child2.on("close", finish);
32457
+ child2.stdin.end(paths.map((path2) => `${ref}:${path2}
32458
+ `).join(""));
32459
+ });
32460
+ }
32461
+ function parseCatFileBatch(out, expected) {
32462
+ const results = [];
32463
+ let at = 0;
32464
+ while (results.length < expected) {
32465
+ const eol = out.indexOf(10, at);
32466
+ if (eol === -1) break;
32467
+ const header = out.toString("utf8", at, eol);
32468
+ at = eol + 1;
32469
+ if (header.endsWith(" missing")) {
32470
+ results.push(void 0);
32471
+ continue;
32472
+ }
32473
+ const size = Number(header.slice(header.lastIndexOf(" ") + 1));
32474
+ if (!Number.isInteger(size) || size < 0 || at + size > out.length) break;
32475
+ results.push(out.toString("utf8", at, at + size));
32476
+ at = at + size + 1;
32477
+ }
32478
+ return results;
32479
+ }
32380
32480
  function createPrCreateDocsIndexDeps() {
32381
32481
  return {
32382
32482
  worktreeRoot: async () => {
@@ -32405,20 +32505,13 @@ function createPrCreateDocsIndexDeps() {
32405
32505
  },
32406
32506
  listDocsAtRef: async (root, ref) => {
32407
32507
  try {
32408
- const { stdout } = await execFileP2("git", ["-C", root, "ls-tree", "-r", "--name-only", ref, "--", "docs"], { timeout: GIT_TIMEOUT_MS2 });
32409
- return stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
32508
+ const { stdout } = await execFileP2("git", ["-C", root, "ls-tree", "-r", "-z", "--name-only", ref, "--", "docs"], { timeout: GIT_TIMEOUT_MS2 });
32509
+ return stdout.split("\0").filter(Boolean);
32410
32510
  } catch {
32411
32511
  return [];
32412
32512
  }
32413
32513
  },
32414
- readAtRef: async (root, ref, path2) => {
32415
- try {
32416
- const { stdout } = await execFileP2("git", ["-C", root, "show", `${ref}:${path2}`], { timeout: GIT_TIMEOUT_MS2 });
32417
- return stdout;
32418
- } catch {
32419
- return void 0;
32420
- }
32421
- }
32514
+ readManyAtRef: (root, ref, paths) => catFileBatch(root, ref, paths)
32422
32515
  };
32423
32516
  }
32424
32517
  async function checkDocsIndexAtHead(opts, deps) {
@@ -32433,10 +32526,10 @@ async function checkDocsIndexAtHead(opts, deps) {
32433
32526
  const paths = await deps.listDocsAtRef(root, ref);
32434
32527
  if (!paths.includes(DOCS_INDEX_PATH)) return void 0;
32435
32528
  const relDocs = paths.filter((p) => p.startsWith("docs/") && p.endsWith(".md")).map((p) => p.slice("docs/".length)).filter(isRoutableDocsPath);
32436
- const [indexContent, docContents] = await Promise.all([
32437
- deps.readAtRef(root, ref, DOCS_INDEX_PATH),
32438
- Promise.all(relDocs.map((rel) => deps.readAtRef(root, ref, `docs/${rel}`)))
32439
- ]);
32529
+ const contents = await deps.readManyAtRef(root, ref, [DOCS_INDEX_PATH, ...relDocs.map((rel) => `docs/${rel}`)]);
32530
+ const [indexContent, ...docContents] = contents;
32531
+ if (contents.length !== relDocs.length + 1) return void 0;
32532
+ if (indexContent === void 0 || docContents.some((c) => c === void 0)) return void 0;
32440
32533
  const contentByPath = new Map(relDocs.map((rel, i) => [rel, docContents[i] ?? ""]));
32441
32534
  const atRefDeps = {
32442
32535
  listDocs: () => relDocs,
@@ -32452,7 +32545,7 @@ async function checkDocsIndexAtHead(opts, deps) {
32452
32545
  }
32453
32546
  return {
32454
32547
  ok: false,
32455
- detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there`,
32548
+ detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there (checked ${ref} over ${relDocs.length} indexed doc${relDocs.length === 1 ? "" : "s"})`,
32456
32549
  fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`"
32457
32550
  };
32458
32551
  }
@@ -33907,12 +34000,18 @@ function catalogCommandPaths(manifest) {
33907
34000
  function nearestExplainTargets(query, paths, limit = 5) {
33908
34001
  const parentPath = query.includes(" ") ? query.slice(0, query.lastIndexOf(" ")) : void 0;
33909
34002
  const leaf = query.includes(" ") ? query.slice(query.lastIndexOf(" ") + 1) : query;
33910
- const guide = unknownCommandDomainGuide(parentPath, leaf);
33911
- if (guide) return guide.expected.slice(0, limit);
33912
34003
  const all = [...paths];
33913
34004
  const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
34005
+ const siblings = parentPath ? all.filter((p) => p !== query && p.startsWith(`${parentPath} `) && !p.slice(parentPath.length + 1).includes(" ")).sort() : [];
34006
+ const withSiblings = (primary) => {
34007
+ const out2 = [...primary];
34008
+ for (const sibling of siblings) if (!out2.includes(sibling)) out2.push(sibling);
34009
+ return out2.slice(0, limit);
34010
+ };
34011
+ const guide = unknownCommandDomainGuide(parentPath, leaf);
34012
+ if (guide) return withSiblings(guide.expected);
33914
34013
  const exactLeaf = all.filter((p) => p !== query && leafOf(p) === leaf).sort();
33915
- if (exactLeaf.length) return exactLeaf.slice(0, limit);
34014
+ if (exactLeaf.length) return withSiblings(exactLeaf);
33916
34015
  const out = [];
33917
34016
  const nearPath = didYouMean(query, all);
33918
34017
  if (nearPath) out.push(nearPath);
@@ -33923,7 +34022,7 @@ function nearestExplainTargets(query, paths, limit = 5) {
33923
34022
  if (out.length >= limit) break;
33924
34023
  }
33925
34024
  }
33926
- return out.slice(0, limit);
34025
+ return withSiblings(out);
33927
34026
  }
33928
34027
  function explainCommandManifest(manifest, command, opts = {}) {
33929
34028
  const base = {
@@ -36588,6 +36687,9 @@ function diagnoseSurface(evidence) {
36588
36687
  if (evidence.manifest === "invalid") return { ...base, state: "corrupt" };
36589
36688
  if (!evidence.releasedVersion) return { ...base, state: "freshness-unknown" };
36590
36689
  if (evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0) {
36690
+ if (evidence.stagedVersion && evidence.stagedVersion === evidence.releasedVersion) {
36691
+ return { ...base, state: "staged" };
36692
+ }
36591
36693
  return { ...base, state: "stale" };
36592
36694
  }
36593
36695
  return { ...base, state: "clean" };
@@ -36598,14 +36700,16 @@ function reloadInstruction(descriptor) {
36598
36700
  return `${verb} ${descriptor.displayName}`;
36599
36701
  }
36600
36702
  function planSurfaceRepair(diagnosis) {
36601
- if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
36703
+ if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "staged" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
36602
36704
  const descriptor = diagnosis.descriptor;
36603
36705
  if (descriptor.repairOwner === "mmi-hub") {
36604
36706
  return {
36605
36707
  supported: false,
36606
36708
  owner: "mmi-hub",
36607
36709
  changes: [],
36608
- instruction: `run \`mmi-hub update\` to converge the MMI-managed Hermes plugin, then ${reloadInstruction(descriptor)}`
36710
+ // #5833: name the SURFACE this row is about — the literal "Hermes plugin" sent every other
36711
+ // mmi-hub-owned surface (Claude Code included) chasing a different host that was already current.
36712
+ instruction: `run \`mmi-hub update\` to converge the MMI-managed ${descriptor.displayName} plugin, then ${reloadInstruction(descriptor)}`
36609
36713
  };
36610
36714
  }
36611
36715
  if (descriptor.repairOwner !== "mmi-cli") {
@@ -36632,6 +36736,8 @@ function buildSurfaceDoctorCheck(diagnosis) {
36632
36736
  skipped: "skipped \u2014 host not active",
36633
36737
  clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
36634
36738
  "pending-reload": `pending ${descriptor.token === "hermes" ? "fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
36739
+ // #5833: the update already staged the released tree; the wording mirrors `mmi-hub status`.
36740
+ staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${descriptor.token === "hermes" ? "start a fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} ${descriptor.displayName} to converge`,
36635
36741
  "freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
36636
36742
  stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
36637
36743
  missing: "missing \u2014 no install record",
@@ -36643,14 +36749,14 @@ function buildSurfaceDoctorCheck(diagnosis) {
36643
36749
  id: `${descriptor.token}-plugin`,
36644
36750
  surface: descriptor.token,
36645
36751
  state,
36646
- ok: state === "clean" || state === "skipped" || state === "pending-reload",
36752
+ ok: state === "clean" || state === "skipped" || state === "pending-reload" || state === "staged",
36647
36753
  ...state === "freshness-unknown" ? { reportOnly: true } : {},
36648
- // Nothing is broken, so this is not a ✗ — but it is the one OK row an operator must act on, and the
36754
+ // Nothing is broken, so this is not a ✗ — but these are the OK rows an operator must act on, and the
36649
36755
  // short default lane and the SessionStart banner both filter to failures unless a row says otherwise.
36650
- ...state === "pending-reload" ? { warn: true } : {},
36756
+ ...state === "pending-reload" || state === "staged" ? { warn: true } : {},
36651
36757
  label: `${descriptor.displayName} plugin`,
36652
36758
  detail: detailByState[state],
36653
- ...plan ? { fix: plan.instruction } : state === "pending-reload" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
36759
+ ...plan ? { fix: plan.instruction } : state === "pending-reload" || state === "staged" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
36654
36760
  verbose: [
36655
36761
  // The three numbers the legacy builder used to print. They belong here now that this is the only
36656
36762
  // plugin row, and a report that names a state without naming the versions behind it is not evidence.
@@ -37758,7 +37864,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
37758
37864
  var import_node_fs42 = require("node:fs");
37759
37865
  var import_node_os18 = require("node:os");
37760
37866
  var import_node_path39 = require("node:path");
37761
- var import_node_child_process18 = require("node:child_process");
37867
+ var import_node_child_process19 = require("node:child_process");
37762
37868
  var import_node_util8 = require("node:util");
37763
37869
 
37764
37870
  // src/discard-sink.ts
@@ -37767,13 +37873,13 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
37767
37873
  }
37768
37874
 
37769
37875
  // src/doctor-io.ts
37770
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
37876
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
37771
37877
  function execFileCapture(file, args, opts = {}) {
37772
37878
  const sink = nodeDiscardSinkPath();
37773
37879
  const inFd = (0, import_node_fs42.openSync)(sink, "r");
37774
37880
  const errFd = (0, import_node_fs42.openSync)(sink, "w");
37775
37881
  try {
37776
- return (0, import_node_child_process18.execFileSync)(file, args, {
37882
+ return (0, import_node_child_process19.execFileSync)(file, args, {
37777
37883
  ...opts,
37778
37884
  encoding: "utf8",
37779
37885
  stdio: [inFd, "pipe", errFd]
@@ -37784,6 +37890,18 @@ function execFileCapture(file, args, opts = {}) {
37784
37890
  }
37785
37891
  }
37786
37892
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
37893
+ function stagedClaudePluginVersion() {
37894
+ try {
37895
+ const stagingRoot = (0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "staging");
37896
+ const record = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(stagingRoot, "launch-target.json"), "utf8"));
37897
+ if (typeof record.target !== "string" || typeof record.incoming !== "string") return void 0;
37898
+ const manifest = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(record.incoming, ".claude-plugin", "plugin.json"), "utf8"));
37899
+ if (typeof manifest.version !== "string") return void 0;
37900
+ return manifest.version === record.target ? record.target : void 0;
37901
+ } catch {
37902
+ return void 0;
37903
+ }
37904
+ }
37787
37905
  function installedClaudePluginVersion() {
37788
37906
  try {
37789
37907
  const file = JSON.parse(
@@ -38042,6 +38160,7 @@ function mmiDoctorDeps(opts = {}) {
38042
38160
  const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
38043
38161
  const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
38044
38162
  const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
38163
+ const stagedVersion = token === "claude" ? stagedClaudePluginVersion() : void 0;
38045
38164
  surfaceEvidence = {
38046
38165
  descriptor,
38047
38166
  // Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
@@ -38053,7 +38172,8 @@ function mmiDoctorDeps(opts = {}) {
38053
38172
  manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
38054
38173
  guardState: buildPluginGuardDecision(snapshot).state,
38055
38174
  ...kimi?.receipt ? { receipt: kimi.receipt } : {},
38056
- ...installedVersion ? { installedVersion } : {}
38175
+ ...installedVersion ? { installedVersion } : {},
38176
+ ...stagedVersion ? { stagedVersion } : {}
38057
38177
  };
38058
38178
  return surfaceEvidence;
38059
38179
  };
@@ -38280,7 +38400,9 @@ async function requireFreshTrainCli(commandName) {
38280
38400
  const converge = await selfConvergeTrainCli({
38281
38401
  currentVersion: resolveClientVersion(),
38282
38402
  releasedVersion: report.releasedVersion,
38283
- commandName
38403
+ commandName,
38404
+ // The typed, house-prefixed argv — never the spliced process.argv (#5813).
38405
+ argv: INVOKED_ARGV
38284
38406
  });
38285
38407
  if (converge.kind === "failed") {
38286
38408
  throw new Error(`${commandName}: ${converge.error}. ${staleTrainCliMessage(report, commandName)}`);
@@ -38375,7 +38497,10 @@ function positionalTargetForm(cmd, opts = {}) {
38375
38497
  const args = cmd.registeredArguments ?? [];
38376
38498
  const first = args[0];
38377
38499
  if (!first) return void 0;
38378
- return formatPositionalTarget(canonicalPathFor(commandPath2(cmd)) ?? commandPath2(cmd), first.name(), opts);
38500
+ return formatPositionalTarget(canonicalPathFor(commandPath2(cmd)) ?? commandPath2(cmd), first.name(), {
38501
+ ...opts,
38502
+ ownFlags: commandOwnLongFlags(cmd)
38503
+ });
38379
38504
  }
38380
38505
  function unknownCommandCandidates(parent, allPaths) {
38381
38506
  if (!parent) return [...allPaths];
@@ -38470,6 +38595,7 @@ function envelopeAwareWriteErr(str) {
38470
38595
  if (unknownFlagJsonHandled && argvWantsJson2()) return;
38471
38596
  process.stderr.write(str);
38472
38597
  }
38598
+ var INVOKED_ARGV = process.argv.slice(2);
38473
38599
  var program2 = new Command();
38474
38600
  program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
38475
38601
  program2.addHelpText(
@@ -38696,7 +38822,7 @@ function scheduleRelatedDiscovery(o) {
38696
38822
  try {
38697
38823
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
38698
38824
  if (o.repo) args.push("--repo", o.repo);
38699
- spawnDetachedSelf(args, { spawn: import_node_child_process19.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38825
+ spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38700
38826
  } catch {
38701
38827
  }
38702
38828
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",