@mutmutco/cli 4.3.41 → 4.3.43

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 +82 -33
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -13692,8 +13692,10 @@ function walk(dir, root, out) {
13692
13692
  return out;
13693
13693
  }
13694
13694
  function defaultListDocs(root) {
13695
- const docsDir = (0, import_node_path12.join)(root, "docs");
13696
- const docs = ((0, import_node_fs14.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
13695
+ const docs = ["docs", "skills/org-guide", "skills/mmi-help"].flatMap((path2) => {
13696
+ const directory = (0, import_node_path12.join)(root, path2);
13697
+ return (0, import_node_fs14.existsSync)(directory) ? walk(directory, root, []) : [];
13698
+ }).filter(
13697
13699
  (rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
13698
13700
  );
13699
13701
  return [...ROOT_DOCS.filter((rel) => (0, import_node_fs14.existsSync)((0, import_node_path12.join)(root, rel))), ...docs];
@@ -15708,10 +15710,10 @@ var rollout_plan_default = {
15708
15710
  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)."
15709
15711
  },
15710
15712
  baseline: {
15711
- version: "4.3.41",
15712
- tag: "v4.3.41",
15713
- commit: "02cc438cfbf4",
15714
- npm: "@mutmutco/cli@4.3.41"
15713
+ version: "4.3.43",
15714
+ tag: "v4.3.43",
15715
+ commit: "120c58230cfc",
15716
+ npm: "@mutmutco/cli@4.3.43"
15715
15717
  },
15716
15718
  exitCriterion: "fleet-n-of-n",
15717
15719
  hubOnlyShortcut: "forbidden",
@@ -15728,14 +15730,14 @@ var rollout_plan_default = {
15728
15730
  repo: "mutmutco/mmi-hub",
15729
15731
  role: "canary",
15730
15732
  schedule: "train",
15731
- v3Target: "v4.3.41"
15733
+ v3Target: "v4.3.43"
15732
15734
  }
15733
15735
  ],
15734
15736
  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.",
15735
15737
  rollback: {
15736
15738
  independent: true,
15737
- mechanism: "npm dist-tag latest -> 4.3.41 and redeploy the Hub Lambda from tag v4.3.41 (02cc438cfbf4); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15738
- v3Target: "v4.3.41 (@mutmutco/cli@4.3.41, tag commit 02cc438cfbf4 \u2014 last known-good release carrying the repo-index v4-only contract)"
15739
+ mechanism: "npm dist-tag latest -> 4.3.43 and redeploy the Hub Lambda from tag v4.3.43 (120c58230cfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15740
+ v3Target: "v4.3.43 (@mutmutco/cli@4.3.43, tag commit 120c58230cfc \u2014 last known-good release carrying the repo-index v4-only contract)"
15739
15741
  }
15740
15742
  },
15741
15743
  {
@@ -31054,7 +31056,16 @@ async function collectOnboardStatus(opts = {}) {
31054
31056
  readKnown: () => readFileSyncSafe((0, import_node_path28.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs30.readFileSync),
31055
31057
  readSettings: () => readFileSyncSafe((0, import_node_path28.join)(home, ".claude", "settings.json"), import_node_fs30.readFileSync)
31056
31058
  });
31057
- return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
31059
+ return {
31060
+ guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
31061
+ track,
31062
+ board,
31063
+ registry: registry2,
31064
+ secrets,
31065
+ plugin,
31066
+ doors: opts.doors ?? [],
31067
+ nextCommand
31068
+ };
31058
31069
  }
31059
31070
  function registerDiscoveryCommands(program3) {
31060
31071
  program3.command("status").description("unified repo snapshot: branch, worktrees, open PRs (yours), claimed board items, stage state").option("--json", "machine-readable output").action(async (o) => {
@@ -31104,6 +31115,7 @@ function registerDiscoveryCommands(program3) {
31104
31115
  if (o.json) {
31105
31116
  console.log(JSON.stringify(report, null, 2));
31106
31117
  } else {
31118
+ console.log(`Guide: ${report.guide}`);
31107
31119
  console.log(`Track: ${report.track}`);
31108
31120
  console.log(`Board: ${report.board.ok ? "\u2713" : "\u2717"} ${report.board.detail}`);
31109
31121
  console.log(`Registry: ${report.registry.ok ? "\u2713" : "\u2717"} ${report.registry.detail}`);
@@ -42221,7 +42233,7 @@ function registerDeveloperCommands(program3) {
42221
42233
  await failGraceful(e.message);
42222
42234
  }
42223
42235
  });
42224
- docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by \u2014 -->` comment across docs/** + README.md + architecture.md must resolve, or exit 1 (#3339)").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).action(async (o) => {
42236
+ docs.command("refs").description("deterministic doc reference gate: repo paths, markdown links, mmi-cli command refs, and pins across docs, org-guide, mmi-help, README and architecture must resolve, or exit 1").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).action(async (o) => {
42225
42237
  try {
42226
42238
  if (o.repo) {
42227
42239
  const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli oracle docs refs${o.json ? " --json" : ""}`, "docs refs");
@@ -42345,7 +42357,7 @@ var SSM_REGION = "eu-central-1";
42345
42357
  var SSM_TOKEN_PARAM = "/mmi-future/_org/slack/SLACK_BOT_TOKEN";
42346
42358
  var SSM_CHANNEL_PARAM = "/mmi-future/_org/slack/SLACK_ALERTS_CHANNEL";
42347
42359
  var SLACK_TIMEOUT_MS = 1e4;
42348
- async function ssmParameter(deps, name, decrypt) {
42360
+ async function readSsmParameter(deps, name, decrypt) {
42349
42361
  const args = [
42350
42362
  "ssm",
42351
42363
  "get-parameter",
@@ -42363,23 +42375,30 @@ async function ssmParameter(deps, name, decrypt) {
42363
42375
  if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
42364
42376
  return value;
42365
42377
  }
42366
- async function postToAlertsChannel(deps, text) {
42367
- const token = await ssmParameter(deps, SSM_TOKEN_PARAM, true);
42368
- const channel = await ssmParameter(deps, SSM_CHANNEL_PARAM, false);
42378
+ async function postToChannel(deps, channel, text) {
42379
+ const resolved = channel.trim();
42380
+ if (!resolved || resolved === "None") throw new Error("no Slack channel resolved");
42381
+ const token = await readSsmParameter(deps, SSM_TOKEN_PARAM, true);
42369
42382
  const fetchImpl = deps.fetchImpl ?? fetch;
42370
42383
  const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
42371
42384
  method: "POST",
42372
42385
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
42373
- body: JSON.stringify({ channel, text, unfurl_links: false }),
42386
+ body: JSON.stringify({ channel: resolved, text, unfurl_links: false }),
42374
42387
  signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
42375
42388
  });
42376
42389
  const json = await res.json().catch(() => ({}));
42377
42390
  if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
42391
+ return { status: "posted", note: "posted" };
42392
+ }
42393
+ async function postToAlertsChannel(deps, text) {
42394
+ const channel = await readSsmParameter(deps, SSM_CHANNEL_PARAM, false);
42395
+ await postToChannel(deps, channel, text);
42378
42396
  return { status: "posted", note: "posted to the alerts channel" };
42379
42397
  }
42380
42398
 
42381
42399
  // src/release-announce.ts
42382
42400
  var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
42401
+ var RELEASE_CHANNEL_KEY = "SLACK_RELEASE_CHANNEL";
42383
42402
  var MAX_BULLETS = 6;
42384
42403
  var MAX_SUMMARY_LINES = 8;
42385
42404
  function neutralizeHubProductBrands(text) {
@@ -42387,10 +42406,17 @@ function neutralizeHubProductBrands(text) {
42387
42406
  s = s.replace(/\bdesign-system\s+design-system\b/gi, "design-system");
42388
42407
  return s.replace(/\s{2,}/g, " ").trim();
42389
42408
  }
42409
+ function releaseChannelParam(repo) {
42410
+ return `/mmi-future/${slugOf(repo)}/${RELEASE_CHANNEL_KEY}`;
42411
+ }
42412
+ function isAbsentParameter(e) {
42413
+ const message2 = e instanceof Error ? e.message : String(e);
42414
+ return /ParameterNotFound/.test(message2) || /is empty$/.test(message2);
42415
+ }
42390
42416
  function escapeMrkdwn(text) {
42391
42417
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42392
42418
  }
42393
- function bulletToLine(raw) {
42419
+ function bulletToLine(raw, neutralize) {
42394
42420
  const m = /^[*-]\s+(.*)$/.exec(raw.trim());
42395
42421
  if (!m) return null;
42396
42422
  let text = m[1].trim();
@@ -42405,9 +42431,9 @@ function bulletToLine(raw) {
42405
42431
  text = text.slice(0, tail.index).trim();
42406
42432
  }
42407
42433
  if (!text) return null;
42408
- return `\u2022 ${escapeMrkdwn(neutralizeHubProductBrands(text))}${link}`;
42434
+ return `\u2022 ${escapeMrkdwn(neutralize ? neutralizeHubProductBrands(text) : text)}${link}`;
42409
42435
  }
42410
- function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS) {
42436
+ function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS, neutralize = true) {
42411
42437
  const lines2 = [];
42412
42438
  let bullets = 0;
42413
42439
  let dropped = 0;
@@ -42425,7 +42451,7 @@ function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS) {
42425
42451
  continue;
42426
42452
  }
42427
42453
  if (/new contributors/i.test(section)) continue;
42428
- const line = bulletToLine(raw);
42454
+ const line = bulletToLine(raw, neutralize);
42429
42455
  if (!line) continue;
42430
42456
  if (bullets >= maxBullets) {
42431
42457
  dropped += 1;
@@ -42451,21 +42477,39 @@ function formatAnnouncement(args) {
42451
42477
  `<${args.releaseUrl}|Release notes>`
42452
42478
  ].join("\n");
42453
42479
  }
42454
- function summaryFileLines(content) {
42455
- return content.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, MAX_SUMMARY_LINES).map((l) => escapeMrkdwn(neutralizeHubProductBrands(l.replace(/^[•*-]\s*/, "")))).filter(Boolean).map((l) => `\u2022 ${l}`);
42480
+ function summaryFileLines(content, neutralize = true) {
42481
+ return content.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, MAX_SUMMARY_LINES).map((l) => {
42482
+ const bare = l.replace(/^[•*-]\s*/, "");
42483
+ return escapeMrkdwn(neutralize ? neutralizeHubProductBrands(bare) : bare);
42484
+ }).filter(Boolean).map((l) => `\u2022 ${l}`);
42456
42485
  }
42457
- async function announceRelease(deps, args) {
42458
- if (args.repo !== ANNOUNCE_REPO) {
42459
- return { status: "skipped", note: `announce is Hub-only \u2014 ${args.repo} skipped` };
42486
+ async function resolveAnnounceTarget(deps, repo) {
42487
+ if (repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase()) return { kind: "alerts" };
42488
+ const param = releaseChannelParam(repo);
42489
+ try {
42490
+ const channel = await readSsmParameter(deps, param, false);
42491
+ return { kind: "channel", channel };
42492
+ } catch (e) {
42493
+ if (isAbsentParameter(e)) return { kind: "none", param };
42494
+ throw e;
42460
42495
  }
42496
+ }
42497
+ function missingChannelNote(repo) {
42498
+ const slug = slugOf(repo);
42499
+ return `no ${RELEASE_CHANNEL_KEY} declared for ${slug} \u2014 declare it with \`mmi-cli vault secrets declare ${RELEASE_CHANNEL_KEY} --group slack --consumers agent --purpose \u2026\` then \`mmi-cli vault secrets set ${RELEASE_CHANNEL_KEY}\``;
42500
+ }
42501
+ async function announceRelease(deps, args) {
42502
+ const neutralize = args.repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
42461
42503
  try {
42504
+ const target = await resolveAnnounceTarget(deps, args.repo);
42505
+ if (target.kind === "none") return { status: "skipped", note: missingChannelNote(args.repo) };
42462
42506
  const viewRaw = await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body,url"]);
42463
42507
  const view = JSON.parse(viewRaw);
42464
42508
  const releaseUrl = view.url ?? `https://github.com/${args.repo}/releases/tag/${args.tag}`;
42465
42509
  let lines2 = [];
42466
42510
  if (args.summaryFile) {
42467
42511
  if (!deps.readFile) throw new Error("summary file given but deps.readFile is missing");
42468
- lines2 = summaryFileLines(await deps.readFile(args.summaryFile));
42512
+ lines2 = summaryFileLines(await deps.readFile(args.summaryFile), neutralize);
42469
42513
  if (deps.removeFile) {
42470
42514
  try {
42471
42515
  await deps.removeFile(args.summaryFile);
@@ -42473,11 +42517,15 @@ async function announceRelease(deps, args) {
42473
42517
  }
42474
42518
  }
42475
42519
  }
42476
- if (lines2.length === 0) lines2 = curateReleaseNotes(view.body ?? "");
42520
+ if (lines2.length === 0) lines2 = curateReleaseNotes(view.body ?? "", MAX_BULLETS, neutralize);
42477
42521
  if (lines2.length === 0) lines2 = ["\u2022 maintenance release \u2014 see the notes for details"];
42478
42522
  const text = formatAnnouncement({ repo: args.repo, tag: args.tag, lines: lines2, releaseUrl });
42479
- await postToAlertsChannel(deps, text);
42480
- return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
42523
+ if (target.kind === "alerts") {
42524
+ await postToAlertsChannel(deps, text);
42525
+ return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
42526
+ }
42527
+ await postToChannel(deps, target.channel, text);
42528
+ return { status: "announced", note: `announced ${args.tag} to the project release channel` };
42481
42529
  } catch (e) {
42482
42530
  return { status: "failed", note: `announce failed (release unaffected): ${e.message}` };
42483
42531
  }
@@ -43892,7 +43940,8 @@ function trainApplyDeps() {
43892
43940
  // train lands the back-merge itself instead of leaving a manual step. Same enqueue path `pr land` uses,
43893
43941
  // but method `--merge` (NOT squash): the misalignment guard needs the merge parentage to survive.
43894
43942
  mergeAuto: (prNumber, repo) => ghMergeAutoEnqueue(prNumber, repo, "--merge"),
43895
- // Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
43943
+ // Slack release announcement (#883/#6517): the org alerts channel for the Hub, the project's own
43944
+ // SLACK_RELEASE_CHANNEL for a product repo — best-effort inside announceRelease itself.
43896
43945
  announce: (args) => announceRelease({
43897
43946
  run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
43898
43947
  readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
@@ -44156,7 +44205,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44156
44205
  for (const commandName of ["rcand", "release"]) {
44157
44206
  const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44158
44207
  const RELEASE_ONLY_FLAGS = [
44159
- { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
44208
+ { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901), optional for a product repo announcing to its project release channel (#6517)" },
44160
44209
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
44161
44210
  { 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)" },
44162
44211
  { 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)" },
@@ -44182,7 +44231,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44182
44231
  return fail(`${commandName}: --dev applies only to release \u2014 it ships development -> main skipping rc, which rcand cannot do. Run: mmi-cli devops release --dev`);
44183
44232
  }
44184
44233
  if (o.announceSummaryFile && commandName !== "release") {
44185
- return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no Hub Slack announcement. Run: mmi-cli devops release --announce-summary-file <path>`);
44234
+ return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no release announcement. Run: mmi-cli devops release --announce-summary-file <path>`);
44186
44235
  }
44187
44236
  if (o.abort && commandName !== "release") {
44188
44237
  return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished release tag. Run: mmi-cli devops release --abort --apply`);
@@ -44434,7 +44483,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44434
44483
  console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli devops hotfix: dry-run plan", steps));
44435
44484
  });
44436
44485
  hotfixCmd.command("start").description("cherry-pick one or more merged development PRs (or SHAs) onto hotfix/vX.Y.Z from origin/main, bump the distribution, open the main-base PR").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR number(s) or commit SHA(s) to cherry-pick, in pick order \u2014 one hotfix cycle carries as many fixes as you name (#4411)").option("--json", "machine-readable output").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (o) => runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from }), o, renderHotfixStart));
44437
- hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line Hub Slack summary; required for a NEW MMI-Hub hotfix Release (#883/#3901/#6068)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (version, o) => runHotfixSub("release", async () => {
44486
+ hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line release Slack summary; required for a NEW MMI-Hub hotfix Release (#883/#3901/#6068), optional for a product repo announcing to its project release channel (#6517)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (version, o) => runHotfixSub("release", async () => {
44438
44487
  const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [] });
44439
44488
  const postReleaseJervDoctor = await runPostReleaseJervDoctor(result.repo);
44440
44489
  const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.41",
3
+ "version": "4.3.43",
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",