@mutmutco/claude-plugin 4.5.134 → 4.5.135

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mmi",
3
3
  "displayName": "MMI",
4
- "version": "4.5.134",
4
+ "version": "4.5.135",
5
5
  "mmiCompat": "4.x",
6
6
  "description": "MMI workflow skills and org gates delivery.",
7
7
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -18274,10 +18274,10 @@ var rollout_plan_default = {
18274
18274
  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)."
18275
18275
  },
18276
18276
  baseline: {
18277
- version: "4.5.134",
18278
- tag: "v4.5.134",
18279
- commit: "f976ea4f8325",
18280
- npm: "@mutmutco/cli@4.5.134"
18277
+ version: "4.5.135",
18278
+ tag: "v4.5.135",
18279
+ commit: "2ae28f97dd36",
18280
+ npm: "@mutmutco/cli@4.5.135"
18281
18281
  },
18282
18282
  exitCriterion: "fleet-n-of-n",
18283
18283
  hubOnlyShortcut: "forbidden",
@@ -18294,14 +18294,14 @@ var rollout_plan_default = {
18294
18294
  repo: "mutmutco/mmi-hub",
18295
18295
  role: "canary",
18296
18296
  schedule: "train",
18297
- v3Target: "v4.5.134"
18297
+ v3Target: "v4.5.135"
18298
18298
  }
18299
18299
  ],
18300
18300
  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.",
18301
18301
  rollback: {
18302
18302
  independent: true,
18303
- mechanism: "npm dist-tag latest -> 4.5.134 and redeploy the Hub Lambda from tag v4.5.134 (f976ea4f8325); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18304
- v3Target: "v4.5.134 (@mutmutco/cli@4.5.134, tag commit f976ea4f8325 \u2014 last known-good release carrying the repo-index v4-only contract)"
18303
+ mechanism: "npm dist-tag latest -> 4.5.135 and redeploy the Hub Lambda from tag v4.5.135 (2ae28f97dd36); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18304
+ v3Target: "v4.5.135 (@mutmutco/cli@4.5.135, tag commit 2ae28f97dd36 \u2014 last known-good release carrying the repo-index v4-only contract)"
18305
18305
  }
18306
18306
  },
18307
18307
  {
@@ -26417,36 +26417,75 @@ async function collectReleaseCredits(deps, repo, body) {
26417
26417
  return [];
26418
26418
  }
26419
26419
  }
26420
- function buildPlainWordsSection(lines2, big, heading = PLAIN_WORDS_HEADING, credits = []) {
26421
- const [title, ...notes] = lines2;
26422
- const body = [heading, title ?? "", ...notes.slice(0, 5).map((n) => `- ${n}`)];
26420
+ var UPDATE_SECTIONS = {
26421
+ en: { fixed: "Fixed", new: "New" },
26422
+ tr: { fixed: "D\xFCzeldi", new: "Yeni" }
26423
+ };
26424
+ var UPDATE_LINE_MAX = 120;
26425
+ var UPDATE_SECTION_MAX = 8;
26426
+ var UPDATE_LINE = /^[^:\s][^:]{0,39}: \S.*\.$/u;
26427
+ var UPDATE_FORBIDDEN = /#\d|\bv?\d+\.\d+\.\d+|^(?:feat|fix|chore|refactor|docs|test|ci|build|perf|style)\b/;
26428
+ function parseUpdateNotes(content, lang = "en") {
26429
+ const words = UPDATE_SECTIONS[lang];
26430
+ const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
26431
+ const notes = { fixed: [], new: [] };
26432
+ let section;
26433
+ const seen = /* @__PURE__ */ new Set();
26434
+ for (const line of lines2) {
26435
+ const key = line === words.fixed ? "fixed" : line === words.new ? "new" : void 0;
26436
+ if (key) {
26437
+ if (seen.has(key) || key === "fixed" && seen.has("new")) throw new Error(`${lang} summary: "${words.fixed}" then "${words.new}", each at most once \u2014 see ${SUMMARY_GUIDE}`);
26438
+ seen.add(key);
26439
+ section = key;
26440
+ continue;
26441
+ }
26442
+ if (!section) throw new Error(`${lang} summary: start with "${words.fixed}" or "${words.new}" on its own line \u2014 see ${SUMMARY_GUIDE}`);
26443
+ if (line.length > UPDATE_LINE_MAX || !UPDATE_LINE.test(line) || UPDATE_FORBIDDEN.test(line)) {
26444
+ throw new Error(`${lang} summary line must read "<Area>: <sentence>." in at most ${UPDATE_LINE_MAX} characters, with no issue numbers, versions or commit prefixes: "${line}" \u2014 see ${SUMMARY_GUIDE}`);
26445
+ }
26446
+ notes[section].push(line);
26447
+ }
26448
+ for (const key of seen) {
26449
+ const n = notes[key].length;
26450
+ if (n === 0 || n > UPDATE_SECTION_MAX) throw new Error(`${lang} summary: each section needs 1\u2013${UPDATE_SECTION_MAX} lines (found ${n}) \u2014 see ${SUMMARY_GUIDE}`);
26451
+ }
26452
+ if (!seen.size) throw new Error(`${lang} summary needs a "${words.fixed}" or "${words.new}" section \u2014 see ${SUMMARY_GUIDE}`);
26453
+ return notes;
26454
+ }
26455
+ function buildPlainWordsSection(notes, big, lang = "en", credits = []) {
26456
+ const words = UPDATE_SECTIONS[lang];
26457
+ const body = [lang === "en" ? PLAIN_WORDS_HEADING : PLAIN_WORDS_HEADING_TR];
26458
+ for (const key of ["fixed", "new"]) {
26459
+ if (notes[key].length) body.push(`### ${words[key]}`, ...notes[key].map((n) => `- ${n}`));
26460
+ }
26423
26461
  if (big) body.push(PLAIN_WORDS_MARKER);
26424
26462
  const ids = [...new Set(credits.filter((id) => REPORT_ID.test(id)))].sort().slice(0, 20);
26425
26463
  if (ids.length) body.push(`<!-- mmi:credits ${ids.join(",")} -->`);
26426
26464
  return body.join("\n");
26427
26465
  }
26428
26466
  var SUMMARY_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing";
26429
- function validatePlainWordsSummary(content) {
26430
- const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
26431
- if (lines2[0]?.startsWith("No changes to this module")) throw new Error(`requires a real MM-Work title, not the no-change placeholder \u2014 see ${SUMMARY_GUIDE}`);
26432
- if (lines2.length < 3 || lines2.length > 6) throw new Error(`requires 3\u20136 curated non-empty lines (title + 2\u20135 notes; found ${lines2.length}) \u2014 see ${SUMMARY_GUIDE}`);
26433
- }
26434
- function validateSuiteReleaseSummary(path2) {
26435
- if (!path2) throw new Error(`requires --announce-summary-file <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
26436
- let content;
26437
- try {
26438
- content = (0, import_node_fs25.readFileSync)(path2, "utf8");
26439
- } catch (e) {
26440
- throw new Error(`could not read summary file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26467
+ var SUMMARY_FLAG = { en: "--announce-summary-file", tr: "--announce-summary-file-tr" };
26468
+ function validateSuiteReleaseSummary(path2, pathTr) {
26469
+ const files = [["en", path2], ["tr", pathTr]];
26470
+ for (const [lang, file] of files) {
26471
+ if (!file) throw new Error(`requires ${SUMMARY_FLAG[lang]} <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
26472
+ }
26473
+ for (const [lang, file] of files) {
26474
+ let content;
26475
+ try {
26476
+ content = (0, import_node_fs25.readFileSync)(file, "utf8");
26477
+ } catch (e) {
26478
+ throw new Error(`could not read summary file ${file}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26479
+ }
26480
+ parseUpdateNotes(content, lang);
26441
26481
  }
26442
- validatePlainWordsSummary(content);
26443
26482
  }
26444
- async function validateReleaseSummaries(releases, files, releaseExists) {
26483
+ async function validateReleaseSummaries(releases, files, filesTr, releaseExists) {
26445
26484
  const refusals = [];
26446
26485
  for (const { repo, tag } of releases) {
26447
26486
  if (await releaseExists(repo, tag)) continue;
26448
26487
  try {
26449
- validateSuiteReleaseSummary(files[repo]);
26488
+ validateSuiteReleaseSummary(files[repo], filesTr[repo]);
26450
26489
  } catch (e) {
26451
26490
  refusals.push(`${repo}: ${e.message}`);
26452
26491
  }
@@ -26454,15 +26493,20 @@ async function validateReleaseSummaries(releases, files, releaseExists) {
26454
26493
  if (refusals.length) throw new Error(`suite release summaries refused; no member was released:
26455
26494
  ${refusals.join("\n")}`);
26456
26495
  }
26457
- async function requireSuiteReleaseSummary(deps, path2) {
26458
- if (!path2) throw new Error(`a new mm-work suite Release requires --announce-summary-file <path> with a title + 2\u20135 notes \u2014 see ${SUMMARY_GUIDE}`);
26459
- let content;
26460
- try {
26461
- content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(path2);
26462
- } catch (e) {
26463
- throw new Error(`could not read --announce-summary-file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26496
+ async function requireSuiteReleaseSummary(deps, path2, pathTr) {
26497
+ const files = [["en", path2], ["tr", pathTr]];
26498
+ for (const [lang, file] of files) {
26499
+ if (!file) throw new Error(`a new mm-work suite Release requires ${SUMMARY_FLAG[lang]} <path> in the update-notes template \u2014 see ${SUMMARY_GUIDE}`);
26500
+ }
26501
+ for (const [lang, file] of files) {
26502
+ let content;
26503
+ try {
26504
+ content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(file);
26505
+ } catch (e) {
26506
+ throw new Error(`could not read ${SUMMARY_FLAG[lang]} ${file}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26507
+ }
26508
+ parseUpdateNotes(content, lang);
26464
26509
  }
26465
- validatePlainWordsSummary(content);
26466
26510
  }
26467
26511
  function upsertPlainWordsSection(body, section) {
26468
26512
  let rest = body;
@@ -26486,19 +26530,15 @@ function upsertPlainWordsSection(body, section) {
26486
26530
 
26487
26531
  ${trimmedRest}` : section;
26488
26532
  }
26489
- function plainWordsSummaryLines(content) {
26490
- return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 6);
26491
- }
26492
26533
  async function writePlainWordsIfSuiteMember(deps, args) {
26493
26534
  if (args.meta?.suite !== "mm-work") return;
26494
26535
  try {
26495
- if (!args.summaryLines?.length) return;
26496
- const lines2 = args.summaryLines;
26536
+ if (!args.summary) return;
26497
26537
  const body = clean2(await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body", "-q", ".body"]));
26498
26538
  const credits = await collectReleaseCredits(deps, args.repo, body);
26499
26539
  const section = [
26500
- buildPlainWordsSection(lines2, args.big === true, PLAIN_WORDS_HEADING, credits),
26501
- ...args.summaryLinesTr?.length ? [buildPlainWordsSection(args.summaryLinesTr, false, PLAIN_WORDS_HEADING_TR)] : []
26540
+ buildPlainWordsSection(args.summary, args.big === true, "en", credits),
26541
+ ...args.summaryTr ? [buildPlainWordsSection(args.summaryTr, false, "tr")] : []
26502
26542
  ].join("\n\n");
26503
26543
  await deps.run("gh", ["release", "edit", args.tag, "--repo", args.repo, "--notes", upsertPlainWordsSection(body, section)]);
26504
26544
  } catch (e) {
@@ -26507,9 +26547,10 @@ async function writePlainWordsIfSuiteMember(deps, args) {
26507
26547
  }
26508
26548
  async function writePlainWordsSection(deps, options, repo, meta, tag) {
26509
26549
  try {
26510
- const summaryLines = options.announceSummaryFile ? plainWordsSummaryLines(await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(options.announceSummaryFile)) : void 0;
26511
- const summaryLinesTr = options.announceSummaryFileTr ? plainWordsSummaryLines(await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(options.announceSummaryFileTr)) : void 0;
26512
- await writePlainWordsIfSuiteMember(deps, { repo, tag, meta, summaryLines, summaryLinesTr, big: options.big, suiteTag: options.suiteTag });
26550
+ const read = deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8"));
26551
+ const summary = options.announceSummaryFile ? parseUpdateNotes(await read(options.announceSummaryFile), "en") : void 0;
26552
+ const summaryTr = options.announceSummaryFileTr ? parseUpdateNotes(await read(options.announceSummaryFileTr), "tr") : void 0;
26553
+ await writePlainWordsIfSuiteMember(deps, { repo, tag, meta, summary, summaryTr, big: options.big, suiteTag: options.suiteTag });
26513
26554
  } catch {
26514
26555
  }
26515
26556
  }
@@ -27844,7 +27885,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
27844
27885
  const preMainPartial = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
27845
27886
  const promotedPartial = !preMainPartial && await isPromotedUnreleasedTag(deps, tag, tagSha, ctx.repo);
27846
27887
  const partialRelease = preMainPartial || promotedPartial;
27847
- if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
27888
+ if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
27848
27889
  if (!partialRelease) {
27849
27890
  try {
27850
27891
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
@@ -28720,7 +28761,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
28720
28761
  const branchHints = await loadReleaseTrackBranchHints(deps);
28721
28762
  const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
28722
28763
  assertSuiteAutoVersion(ctx.repo, meta, process.env, command === "release" && !directTrack);
28723
- if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
28764
+ if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
28724
28765
  const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
28725
28766
  if (command === "rcand") {
28726
28767
  if (directTrack) {
@@ -39055,7 +39096,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
39055
39096
  await assertConsumerBomLineage(deps, mergedSha, tag);
39056
39097
  }
39057
39098
  const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
39058
- if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
39099
+ if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
39059
39100
  if (!releaseExists && isHubControlRepo(ctx.repo)) {
39060
39101
  if (!options.announceSummaryFile) {
39061
39102
  throw new Error("a new MMI-Hub hotfix Release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same hotfix release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing");
@@ -47470,16 +47511,18 @@ async function announceSummaryLineCountRefusal(path2) {
47470
47511
  return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47471
47512
  }
47472
47513
  const lines2 = summaryFileLines(content);
47514
+ const first = content.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
47515
+ if (first && Object.values(UPDATE_SECTIONS).some((w) => first === w.fixed || first === w.new)) return null;
47473
47516
  return lines2.length < 3 || lines2.length > 6 ? `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing` : null;
47474
47517
  }
47475
- async function announceSummaryRefusal(path2, isHub = false) {
47518
+ async function announceSummaryRefusal(path2, isHub = false, isWorkSuite = false) {
47476
47519
  let lines2;
47477
47520
  try {
47478
47521
  lines2 = summaryFileLines(await (0, import_promises9.readFile)(path2, "utf8"));
47479
47522
  } catch (e) {
47480
47523
  return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47481
47524
  }
47482
- if (lines2.length < 3 || lines2.length > 6) {
47525
+ if (!isWorkSuite && (lines2.length < 3 || lines2.length > 6)) {
47483
47526
  return `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47484
47527
  }
47485
47528
  if (isHub) {
@@ -47951,6 +47994,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
47951
47994
  const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--suite-solo", "release one suite module alone, or cut the suite lead alone (requires --owner-go; a member rcand is always refused)").option("--owner-go", "explicit owner approval for a suite-solo backend release").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)");
47952
47995
  const RELEASE_ONLY_FLAGS = [
47953
47996
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
47997
+ { flags: "--announce-summary-file-tr <path>", description: "Turkish update-notes summary; required next to --announce-summary-file for a new mm-work suite Release (#8230)" },
47954
47998
  { 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)" },
47955
47999
  { 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)" },
47956
48000
  { 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)" },
@@ -48144,7 +48188,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48144
48188
  const raw = await withReleaseProgress(
48145
48189
  trainApplyDeps(),
48146
48190
  "Checking the existing release",
48147
- (deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, suiteSolo: o.suiteSolo, big: o.big })
48191
+ (deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, suiteSolo: o.suiteSolo, big: o.big })
48148
48192
  );
48149
48193
  const result = raw.dispatch?.workflowRuns ? { ...raw, dispatch: { ...raw.dispatch, workflowRuns: raw.dispatch.workflowRuns.map(workflowRunWithEvidence) } } : raw;
48150
48194
  const projectInfoSync = await withReleaseProgress(
@@ -48200,7 +48244,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48200
48244
  }
48201
48245
  if (isWorkSuite) {
48202
48246
  try {
48203
- await requireSuiteReleaseSummary({}, o.announceSummaryFile);
48247
+ await requireSuiteReleaseSummary({}, o.announceSummaryFile, o.announceSummaryFileTr);
48204
48248
  } catch (e) {
48205
48249
  return fail(`release: ${e.message}`);
48206
48250
  }
@@ -48211,14 +48255,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48211
48255
  isHub ? "release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing" : `release: ${slug} announces to its project release channel, so a new release requires --announce-summary-file <path> with 3-6 short plain-language lines in the project's releaseLanguage (${releaseLanguage2}); create the summary file, then rerun the same release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`
48212
48256
  );
48213
48257
  }
48214
- const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub);
48258
+ const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub, isWorkSuite);
48215
48259
  if (refusal) return fail(refusal);
48216
48260
  }
48217
48261
  }
48218
48262
  if (o.apply) {
48219
48263
  try {
48220
48264
  const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
48221
- const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev, suiteSolo: o.suiteSolo, big: o.big });
48265
+ const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, ack, dev: o.dev, suiteSolo: o.suiteSolo, big: o.big });
48222
48266
  const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
48223
48267
  const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
48224
48268
  let projectInfoSync;
@@ -48403,16 +48447,16 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48403
48447
  if (o.suiteSolo !== o.ownerGo) return fail("hotfix start: solo suite hotfix requires BOTH --suite-solo --owner-go");
48404
48448
  return runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from, suiteSolo: o.suiteSolo, ack: o.ack ? o.ack.split(",").map((s) => s.trim()).filter(Boolean) : void 0 }), o, renderHotfixStart);
48405
48449
  });
48406
- 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("--ack <shas>", "accepted for flag symmetry with hotfix start; hotfix release proves every carry by ancestry, so it is ignored here, never an error (#7338)").option("--retry-deploy", "re-run only the failed deploy and/or fold-port alignment legs of a published hotfix of this same tag (#6985)").option("--suite-solo", "release one suite module alone (requires --owner-go)").option("--owner-go", "explicit owner approval for a solo backend hotfix").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) => {
48450
+ 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("--announce-summary-file-tr <path>", "Turkish update-notes summary; required next to --announce-summary-file for a new mm-work suite hotfix Release (#8230)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--ack <shas>", "accepted for flag symmetry with hotfix start; hotfix release proves every carry by ancestry, so it is ignored here, never an error (#7338)").option("--retry-deploy", "re-run only the failed deploy and/or fold-port alignment legs of a published hotfix of this same tag (#6985)").option("--suite-solo", "release one suite module alone (requires --owner-go)").option("--owner-go", "explicit owner approval for a solo backend hotfix").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) => {
48407
48451
  if (o.suiteSolo !== o.ownerGo) return fail("hotfix release: solo suite release requires BOTH --suite-solo --owner-go");
48408
48452
  if (o.retryDeploy) {
48409
- if (o.announceSummaryFile || o.carries) {
48453
+ if (o.announceSummaryFile || o.announceSummaryFileTr || o.carries) {
48410
48454
  return fail("hotfix release: --retry-deploy accepts only --json and --out; it re-runs failed deploy/alignment legs of the published tag and does not recut");
48411
48455
  }
48412
48456
  return runHotfixSub("retry-deploy", () => runHotfixRetryDeploy(trainApplyDeps(), version), o, renderHotfixRetryDeploy);
48413
48457
  }
48414
48458
  return runHotfixSub("release", async () => {
48415
- const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [], suiteSolo: o.suiteSolo, ...o.ack ? { ack: o.ack.split(",").map((s) => s.trim()).filter(Boolean) } : {} });
48459
+ const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, carries: o.carries ? [o.carries] : [], suiteSolo: o.suiteSolo, ...o.ack ? { ack: o.ack.split(",").map((s) => s.trim()).filter(Boolean) } : {} });
48416
48460
  const suitePin = o.suiteSolo ? await pinCompletedMember(result) : void 0;
48417
48461
  const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
48418
48462
  return { ...result, projectInfoSync, ...o.suiteSolo ? { suiteSolo: true, suitePin } : {} };
@@ -48745,10 +48789,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
48745
48789
  };
48746
48790
  return {
48747
48791
  recordPath,
48748
- validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile),
48792
+ validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile, summaryFileTr),
48749
48793
  validateReleaseSummaries: (releases) => validateReleaseSummaries(
48750
48794
  releases,
48751
48795
  { ...memberSummaryFiles, [SUITE_REPO]: summaryFile },
48796
+ { ...memberSummaryFilesTr, [SUITE_REPO]: summaryFileTr },
48752
48797
  async (repo, tag) => {
48753
48798
  try {
48754
48799
  await gh(["release", "view", tag, "--repo", repo, "--json", "tagName"]);
@@ -48839,7 +48884,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
48839
48884
  },
48840
48885
  // #7738: a member hotfixes as suite-solo; the lead hotfixes under its suite-lead capability (inTrain sets it).
48841
48886
  hotfixStart: (repo, o) => inTrain(repo, async (deps) => {
48842
- await requireSuiteReleaseSummary(deps, summaryFile);
48887
+ await requireSuiteReleaseSummary(deps, summaryFile, summaryFileTr);
48843
48888
  const r = await runHotfixStart(deps, { from: o.from, ack: o.ack, suiteSolo: repo !== SUITE_REPO });
48844
48889
  return { tag: r.tag };
48845
48890
  }, repo !== SUITE_REPO),
@@ -49023,7 +49068,7 @@ function registerSuiteCommands(program3) {
49023
49068
  return fail(`suite abandon: ${e.message}`);
49024
49069
  }
49025
49070
  });
49026
- suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required title + 2\u20135 notes for each member being released; carried members need none", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "Turkish plain-words summary for MM-Work itself").option("--member-summary-file-tr <owner/repo=path>", "repeatable: a member's own Turkish plain-words summary", (val, prev = []) => [...prev, val], []).option("--revamp <owner/repo>", `repeatable: marks that repo's release an owner-approved revamp in the plain-words section \u2014 the only "big" signal; versions carry no meaning (#8224)`, (val, prev = []) => [...prev, val], []).option("--big", "alias for --revamp mutmutco/MM-Work (#7510)").option("--json", "structured receipt").action(async (o) => {
49071
+ suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required update-notes summary (Fixed/New) for each member being released; carried members need none (#8230)", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "required Turkish update-notes summary (D\xFCzeldi/Yeni) for MM-Work itself (#8230)").option("--member-summary-file-tr <owner/repo=path>", "repeatable: required Turkish update-notes summary for each member being released (#8230)", (val, prev = []) => [...prev, val], []).option("--revamp <owner/repo>", `repeatable: marks that repo's release an owner-approved revamp in the plain-words section \u2014 the only "big" signal; versions carry no meaning (#8224)`, (val, prev = []) => [...prev, val], []).option("--big", "alias for --revamp mutmutco/MM-Work (#7510)").option("--json", "structured receipt").action(async (o) => {
49027
49072
  try {
49028
49073
  if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
49029
49074
  const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
@@ -49046,7 +49091,7 @@ function registerSuiteCommands(program3) {
49046
49091
  return fail(`suite release: ${e.message}`);
49047
49092
  }
49048
49093
  });
49049
- suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required title + 2\u20135 notes for the member and lead hotfix Releases").option("--announce-summary-file-tr <path>", "optional Turkish plain-words notes").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
49094
+ suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required update-notes summary (Fixed/New) for the member and lead hotfix Releases (#8230)").option("--announce-summary-file-tr <path>", "required Turkish update-notes summary (D\xFCzeldi/Yeni) (#8230)").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
49050
49095
  try {
49051
49096
  if (!o.ownerGo && !o.dryRun) throw new Error("suite hotfix requires --owner-go (it ships to production); nothing was written. --dry-run prints the plan");
49052
49097
  const root = await leadRoot();
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.134",
3
+ "version": "4.5.135",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/claude-plugin",
3
- "version": "4.5.134",
3
+ "version": "4.5.135",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",
@@ -109,7 +109,7 @@ commit, so build and verify the lines from one pass over that range —
109
109
  subject, body and changed files — never from subjects, PR titles or memory; a line whose claim is not
110
110
  in that output does not go in the file. A Hub summary never mentions a Jerv-side project; the train
111
111
  refuses such a line.
112
- Every new MM-Work suite Release, solo and hotfix included, requires a real `--announce-summary-file` (title plus 2–5 notes, never `No changes to this module`), and a suite release requires a `--member-summary-file owner/repo=path` for every member it releases; optional `--announce-summary-file-tr` and repeatable `--member-summary-file-tr owner/repo=path` add Turkish plain words.
112
+ Every new MM-Work suite Release, solo and hotfix included, requires `--announce-summary-file` and `--announce-summary-file-tr` in the bilingual update-notes template of the [MM-Work GUIDE, "Update notes"](https://github.com/mutmutco/MM-Work/blob/development/packages/mmwork-ds/GUIDE.md) (`Fixed`/`Düzeldi`, then `New`/`Yeni`, with `<Area>: <sentence>.` lines). A suite release requires `--member-summary-file owner/repo=path` and `--member-summary-file-tr owner/repo=path` for every member it releases. Details: the release skill.
113
113
 
114
114
  Announcement delivery uses the developer's GitHub-authenticated Hub session. The Hub checks project
115
115
  authority, resolves the registered channel, and reads the MMI Future Slack key inside its vault boundary.
@@ -15,8 +15,8 @@ Full-track repos ship exactly what is on `rc` (never `development`): merge `rc
15
15
 
16
16
  In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
17
17
 
18
- Every new mm-work suite Release (including solo and hotfix releases) requires `--announce-summary-file <path>` with a real title and 2–5 notes (3–6 non-empty lines); missing files, out-of-range summaries and titles starting with `No changes to this module` refuse before tagging. Suite release also requires a valid `--member-summary-file <owner/repo=path>` for every member being released, before promoting any member; carried, held and already-released members need none, and existing Releases can resume without new files.
19
- Use `--member-summary-file <owner/repo=path>` for English member notes, `--announce-summary-file-tr <path>` for MM-Work's Turkish notes and repeatable `--member-summary-file-tr <owner/repo=path>` for Turkish member notes; Turkish files remain optional.
18
+ Every new mm-work suite Release (including solo and hotfix releases) requires both `--announce-summary-file <path>` (English) and `--announce-summary-file-tr <path>` (Turkish, "sen" form) in the update-notes template. The canonical wording lives in the [MM-Work GUIDE, "Update notes"](https://github.com/mutmutco/MM-Work/blob/development/packages/mmwork-ds/GUIDE.md); this repo links it and never copies it. Each file has the section word `Fixed` / `Düzeldi` (the problem the person saw, present tense) and/or `New` / `Yeni` (what you can now do) on its own line, Fixed first, each only when it has lines. Under them go 1–8 lines of `<Area>: <sentence>.`, each at most 120 characters, with no issue numbers, versions or commit prefixes. Off-template or missing files refuse before tagging. Do not write the head (app · date · time) or the thanks line: MM-Work renders both from the Release date and credits. Suite release also requires a valid `--member-summary-file <owner/repo=path>` and `--member-summary-file-tr <owner/repo=path>` for every member being released, before promoting any member. Carried, held and already-released members need none, and existing Releases can resume without new files.
19
+ The `/user-releases` feed serves template notes as `updateNotes: {en?, tr?: {fixed[], new[]}}`. Older title-and-notes bodies keep `plainWords` and get `updateNotes: null`.
20
20
 
21
21
  Read META and name the lane before describing or executing anything:
22
22
 
@@ -40,7 +40,7 @@ The JSON is on stdout alone; a stale CLI first prints its self-update notice on
40
40
 
41
41
  Run from the primary checkout on the lane's start branch (`rc`; `development` for direct-track and `--dev`). A Jerv-Hub release on Windows must run under Git Bash (`"C:/Program Files/Git/bin/bash.exe" -lc "…"`) — the train refuses `--apply` before tagging when it is not. Export the bump intent once from the bare argument — `MMI_BUMP_INTENT=minor|major|patch`, unset → `patch`; `MMI_RELEASE_VERSION=X.Y.Z` only for an exact target the tag math cannot derive.
42
42
 
43
- Two cases need a summary file. (1) **MMI-Hub** — always: write a fresh 3–6 line neutral summary to `f=$(mkdir -p .jerv/tmp && mktemp .jerv/tmp/release-summary.XXXXXX)` — sourced from the range `origin/main..origin/development`, rewritten in Hub-subsystem terms, never a product or brand name, and never a Jerv-side project (the train refuses such a line). Hub scope is only `mutmutco/MMI-Hub`: never a product's board, `ds-propagate.yml`, or a product's deploy state. **Build and verify every line from ONE command's output**, never from commit subjects or PR titles alone: `git log --stat --format='%h %s%n%b' origin/main..origin/development` gives each commit's subject, its full body (where a bundled PR records `Closes #N`) and the files it changed in a single pass. A line whose claim you cannot point to in that output does not go in the file, and every `#N` a Hub line cites must appear there: the train refuses `--apply` on a citation that ships in no commit of the cut. (2) **Any product repo whose META carries `releaseChannel`** (`mmi-cli oracle org project get {owner}/{repo} --json`) — required too: read `releaseLanguage` (default `en`) and write 3–6 lines IN THAT LANGUAGE, in plain words for the project's audience: what changed and why it matters to a user; no commit prefixes, no issue or PR numbers, no file names, no product-internal jargon. The train refuses `--apply` without the file, and raw release notes are never posted to a project channel. A repo with no `releaseChannel` announces nothing, but an mm-work suite repo still requires the summary for its GitHub Release.
43
+ Two cases need a summary file. (1) **MMI-Hub** — always: write a fresh 3–6 line neutral summary to `f=$(mkdir -p .jerv/tmp && mktemp .jerv/tmp/release-summary.XXXXXX)` — sourced from the range `origin/main..origin/development`, rewritten in Hub-subsystem terms, never a product or brand name, and never a Jerv-side project (the train refuses such a line). Hub scope is only `mutmutco/MMI-Hub`: never a product's board, `ds-propagate.yml`, or a product's deploy state. **Build and verify every line from ONE command's output**, never from commit subjects or PR titles alone: `git log --stat --format='%h %s%n%b' origin/main..origin/development` gives each commit's subject, its full body (where a bundled PR records `Closes #N`) and the files it changed in a single pass. A line whose claim you cannot point to in that output does not go in the file, and every `#N` a Hub line cites must appear there: the train refuses `--apply` on a citation that ships in no commit of the cut. (2) **Any product repo whose META carries `releaseChannel`** (`mmi-cli oracle org project get {owner}/{repo} --json`) — required too: read `releaseLanguage` (default `en`) and write 3–6 lines IN THAT LANGUAGE, in plain words for the project's audience: what changed and why it matters to a user; no commit prefixes, no issue or PR numbers, no file names, no product-internal jargon. The train refuses `--apply` without the file, and raw release notes are never posted to a project channel. A repo with no `releaseChannel` announces nothing, but an mm-work suite repo still requires the summary for its GitHub Release. An mm-work suite repo's summary follows the update-notes template below instead of the 3–6 line rule.
44
44
 
45
45
  ```bash
46
46
  mkdir -p .jerv/tmp