@mutmutco/cli 3.37.0 → 3.38.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.
Files changed (2) hide show
  1. package/dist/main.cjs +63 -12
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -11236,6 +11236,17 @@ function buildIssueArgs({ type, title, body, priority, repo, labels }) {
11236
11236
  for (const label of labels ?? []) args.push("--label", label);
11237
11237
  return args;
11238
11238
  }
11239
+ async function ensureLabelsExist(labels, repo, deps = {}) {
11240
+ const run = deps.run ?? execFileP2;
11241
+ for (const label of new Set(labels)) {
11242
+ const args = ["label", "create", label, "--color", "ededed"];
11243
+ if (repo) args.push("--repo", repo);
11244
+ try {
11245
+ await run("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
11246
+ } catch {
11247
+ }
11248
+ }
11249
+ }
11239
11250
  async function bodyArgsViaFile(args, deps = {}) {
11240
11251
  const i = args.indexOf("--body");
11241
11252
  if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
@@ -11961,6 +11972,10 @@ function parseNpmVersion(stdout) {
11961
11972
  function staleTrainCliMessage(report, commandName) {
11962
11973
  return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`npm install -g @mutmutco/cli@latest\` to update to ${report.releasedVersion} (a standalone CLI shadows a plugin-provided mmi-cli on PATH and needs no Claude restart), then rerun ${commandName} so it uses the current train path. This gate runs before any train mutation, so this invocation changed nothing; any partial train state (local merge, pushed tag) is from an earlier run and the rerun resumes it safely`;
11963
11974
  }
11975
+ function versionAutoUpdateAction(report, releasedSource) {
11976
+ if (report.ok || report.staleAgainst !== "released") return "none";
11977
+ return releasedSource === "gh" ? "npm-unreachable" : "npm";
11978
+ }
11964
11979
 
11965
11980
  // src/plugin-cache-prune.ts
11966
11981
  var PLUGIN_CACHE_KEEP = 2;
@@ -20721,6 +20736,7 @@ function validateBatchSpecs(specs) {
20721
20736
  return { ok: errors.length === 0, errors, validated };
20722
20737
  }
20723
20738
  async function createIssuesBatch(specs, options, deps = {}) {
20739
+ const ensureLabels = deps.ensureLabels ?? ensureLabelsExist;
20724
20740
  const client = deps.client ?? defaultGitHubClient();
20725
20741
  const validation = validateBatchSpecs(specs);
20726
20742
  if (!validation.ok) {
@@ -20733,6 +20749,14 @@ ${lines}`);
20733
20749
  throw new Error("could not resolve repo \u2014 pass --repo owner/repo or set repo per row");
20734
20750
  }
20735
20751
  const rowRepo = (spec) => spec.repo ?? defaultRepo;
20752
+ const labelsByRepo = /* @__PURE__ */ new Map();
20753
+ for (const { spec } of validation.validated) {
20754
+ if (!spec.labels?.length) continue;
20755
+ const bucket = labelsByRepo.get(rowRepo(spec)) ?? /* @__PURE__ */ new Set();
20756
+ for (const label of spec.labels) bucket.add(label);
20757
+ labelsByRepo.set(rowRepo(spec), bucket);
20758
+ }
20759
+ for (const [repo, labels] of labelsByRepo) await ensureLabels([...labels], repo);
20736
20760
  const created = [];
20737
20761
  const failures = [];
20738
20762
  for (const entry of validation.validated) {
@@ -21140,6 +21164,15 @@ async function fetchNpmReleasedVersion() {
21140
21164
  return void 0;
21141
21165
  }
21142
21166
  }
21167
+ var NPM_INSTALL_TIMEOUT_MS = 12e4;
21168
+ async function npmSelfUpdateCli() {
21169
+ try {
21170
+ await runHostBin("npm", ["install", "-g", "@mutmutco/cli@latest"], { timeout: NPM_INSTALL_TIMEOUT_MS });
21171
+ return { ok: true, detail: "npm install -g @mutmutco/cli@latest exited 0" };
21172
+ } catch (e) {
21173
+ return { ok: false, detail: e.message.trim().slice(0, 200).replace(/\s+/g, " ") };
21174
+ }
21175
+ }
21143
21176
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
21144
21177
  const homeDir = surface === "codex" ? ".codex" : ".claude";
21145
21178
  const installed = readInstalledPlugins(surface);
@@ -22567,7 +22600,25 @@ async function runDoctorClean(opts, io, deps) {
22567
22600
  const plugin = checkClaudePlugin({ installed, released, guardState: deps.pluginGuardState(isOrgRepo) });
22568
22601
  checks.push(plugin);
22569
22602
  if (!plugin.ok) restartPending = true;
22570
- checks.push(checkCliVersion({ currentVersion: deps.currentCliVersion(), releasedVersion: released }));
22603
+ const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
22604
+ const cliReport = buildVersionLagReport(cliInput);
22605
+ if (apply && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
22606
+ const heal = await deps.updateCli();
22607
+ const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
22608
+ checks.push(heal.ok ? {
22609
+ ok: true,
22610
+ label: `mmi-cli ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion}`,
22611
+ detail: "self-updated via npm install -g; the next mmi-cli invocation runs the new version",
22612
+ verbose: healEvidence
22613
+ } : {
22614
+ ok: false,
22615
+ label: `mmi-cli ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion}`,
22616
+ fix: `self-update failed (${heal.detail}) \u2014 run \`npm install -g @mutmutco/cli@latest\``,
22617
+ verbose: healEvidence
22618
+ });
22619
+ } else {
22620
+ checks.push(checkCliVersion(cliInput));
22621
+ }
22571
22622
  checks.push(checkPluginCache(deps.pluginCache()));
22572
22623
  if (!opts.fast && !opts.banner && !opts.preflight) {
22573
22624
  const probe = await deps.schedulesNotebook().catch((e) => ({
@@ -22773,6 +22824,8 @@ function mmiDoctorDeps() {
22773
22824
  installedPluginVersion: installedClaudePluginVersion,
22774
22825
  pluginGuardState: claudePluginGuardState,
22775
22826
  releasedVersion: fetchNpmReleasedVersion,
22827
+ // #3272: the --apply self-heal for a stale running CLI — npm global, shadows any plugin shim (#2879).
22828
+ updateCli: npmSelfUpdateCli,
22776
22829
  currentCliVersion: resolveClientVersion,
22777
22830
  readGitignore,
22778
22831
  writeGitignore,
@@ -23858,6 +23911,7 @@ withExamples(mutating(
23858
23911
  let title;
23859
23912
  let issueType;
23860
23913
  let extraLabels = [];
23914
+ let targetRepo2;
23861
23915
  try {
23862
23916
  issueType = resolveCreateType(o.type, "issue create");
23863
23917
  title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
@@ -23865,33 +23919,30 @@ withExamples(mutating(
23865
23919
  if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
23866
23920
  priority = resolveCreatePriority(o.priority, "issue create");
23867
23921
  extraLabels = [...o.label ?? []];
23922
+ targetRepo2 = await resolveRepo(o.repo);
23923
+ if (!targetRepo2) {
23924
+ return fail("issue create: could not resolve the target repo \u2014 run inside a git checkout or pass --repo <owner/repo>");
23925
+ }
23868
23926
  args = buildIssueArgs({
23869
23927
  type: issueType,
23870
23928
  title,
23871
23929
  body,
23872
23930
  priority,
23873
- repo: o.repo,
23931
+ repo: targetRepo2,
23874
23932
  labels: extraLabels.length ? extraLabels : void 0
23875
23933
  });
23876
23934
  if (o.parent !== void 0) parseIssueRef(o.parent);
23877
23935
  } catch (e) {
23878
23936
  return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
23879
23937
  }
23880
- for (const label of extraLabels) {
23881
- const la = ["label", "create", label, "--color", "ededed"];
23882
- if (o.repo) la.push("--repo", o.repo);
23883
- try {
23884
- await execFileP2("gh", la, { timeout: GH_MUTATION_TIMEOUT_MS });
23885
- } catch {
23886
- }
23887
- }
23938
+ await ensureLabelsExist(extraLabels, targetRepo2);
23888
23939
  const created = await ghCreate(args);
23889
- const { projectItemId, onBoard } = await attachToProject(created.number, o.repo, priority);
23940
+ const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo2, priority);
23890
23941
  let parent;
23891
23942
  let parentLinkError;
23892
23943
  if (o.parent !== void 0) {
23893
23944
  try {
23894
- parent = await linkSubIssue(ghRunner2, o.parent, created.url, o.repo);
23945
+ parent = await linkSubIssue(ghRunner2, o.parent, created.url, targetRepo2);
23895
23946
  } catch (e) {
23896
23947
  const err = e;
23897
23948
  parentLinkError = (err.stderr || err.message || String(e)).trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.37.0",
3
+ "version": "3.38.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",