@mutmutco/codex-plugin 4.5.133 → 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.
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.
|
|
18278
|
-
tag: "v4.5.
|
|
18279
|
-
commit: "
|
|
18280
|
-
npm: "@mutmutco/cli@4.5.
|
|
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.
|
|
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.
|
|
18304
|
-
v3Target: "v4.5.
|
|
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
|
{
|
|
@@ -26387,34 +26387,105 @@ Cut by ${formatSessionIdentity(cutBy)}
|
|
|
26387
26387
|
var PLAIN_WORDS_HEADING = "## In plain words";
|
|
26388
26388
|
var PLAIN_WORDS_HEADING_TR = "## Sade s\xF6zlerle";
|
|
26389
26389
|
var PLAIN_WORDS_MARKER = "<!-- mmi:big -->";
|
|
26390
|
-
|
|
26391
|
-
|
|
26392
|
-
|
|
26390
|
+
var REPORT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
26391
|
+
async function collectReleaseCredits(deps, repo, body) {
|
|
26392
|
+
try {
|
|
26393
|
+
const [owner, name] = repo.split("/");
|
|
26394
|
+
const prs = /* @__PURE__ */ new Set();
|
|
26395
|
+
let changes = false;
|
|
26396
|
+
for (const line of body.split(/\r?\n/)) {
|
|
26397
|
+
if (line.startsWith("## ")) changes = line === "## What's Changed";
|
|
26398
|
+
if (!changes) continue;
|
|
26399
|
+
const match = line.match(/\bin https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)\s*$/);
|
|
26400
|
+
if (match?.[1]?.toLowerCase() === repo.toLowerCase()) prs.add(Number(match[2]));
|
|
26401
|
+
}
|
|
26402
|
+
const ids = /* @__PURE__ */ new Set();
|
|
26403
|
+
for (const number of [...prs].sort((a, b) => a - b)) {
|
|
26404
|
+
const query = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100){nodes{body} pageInfo{hasNextPage}}}}}";
|
|
26405
|
+
const result = JSON.parse(clean2(await deps.run("gh", ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`, "-F", `number=${number}`])));
|
|
26406
|
+
const refs = result.data?.repository?.pullRequest?.closingIssuesReferences;
|
|
26407
|
+
if (result.errors?.length || !Array.isArray(refs?.nodes) || refs.pageInfo?.hasNextPage !== false) throw new Error(`closing issues unavailable or truncated for PR #${number}`);
|
|
26408
|
+
for (const issue of refs.nodes) {
|
|
26409
|
+
if (typeof issue?.body !== "string") throw new Error(`issue body unavailable for PR #${number}`);
|
|
26410
|
+
const footer = issue.body.trim().match(/(?:^|\n)---\r?\nReported by a person in MM-Work \((?:dev|rc|main)\), module `[^`\r\n]+`, report `([A-Za-z0-9_-]{1,64})`\. Filed by the Hub \(#7389\)\.$/);
|
|
26411
|
+
if (footer?.[1]) ids.add(footer[1]);
|
|
26412
|
+
}
|
|
26413
|
+
}
|
|
26414
|
+
return [...ids].sort().slice(0, 20);
|
|
26415
|
+
} catch (e) {
|
|
26416
|
+
(deps.warn ?? console.warn)(`release credits omitted for ${repo}: ${e.message}`);
|
|
26417
|
+
return [];
|
|
26418
|
+
}
|
|
26419
|
+
}
|
|
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
|
+
}
|
|
26393
26461
|
if (big) body.push(PLAIN_WORDS_MARKER);
|
|
26462
|
+
const ids = [...new Set(credits.filter((id) => REPORT_ID.test(id)))].sort().slice(0, 20);
|
|
26463
|
+
if (ids.length) body.push(`<!-- mmi:credits ${ids.join(",")} -->`);
|
|
26394
26464
|
return body.join("\n");
|
|
26395
26465
|
}
|
|
26396
26466
|
var SUMMARY_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing";
|
|
26397
|
-
|
|
26398
|
-
|
|
26399
|
-
|
|
26400
|
-
|
|
26401
|
-
}
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
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);
|
|
26409
26481
|
}
|
|
26410
|
-
validatePlainWordsSummary(content);
|
|
26411
26482
|
}
|
|
26412
|
-
async function validateReleaseSummaries(releases, files, releaseExists) {
|
|
26483
|
+
async function validateReleaseSummaries(releases, files, filesTr, releaseExists) {
|
|
26413
26484
|
const refusals = [];
|
|
26414
26485
|
for (const { repo, tag } of releases) {
|
|
26415
26486
|
if (await releaseExists(repo, tag)) continue;
|
|
26416
26487
|
try {
|
|
26417
|
-
validateSuiteReleaseSummary(files[repo]);
|
|
26488
|
+
validateSuiteReleaseSummary(files[repo], filesTr[repo]);
|
|
26418
26489
|
} catch (e) {
|
|
26419
26490
|
refusals.push(`${repo}: ${e.message}`);
|
|
26420
26491
|
}
|
|
@@ -26422,15 +26493,20 @@ async function validateReleaseSummaries(releases, files, releaseExists) {
|
|
|
26422
26493
|
if (refusals.length) throw new Error(`suite release summaries refused; no member was released:
|
|
26423
26494
|
${refusals.join("\n")}`);
|
|
26424
26495
|
}
|
|
26425
|
-
async function requireSuiteReleaseSummary(deps, path2) {
|
|
26426
|
-
|
|
26427
|
-
|
|
26428
|
-
|
|
26429
|
-
|
|
26430
|
-
|
|
26431
|
-
|
|
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);
|
|
26432
26509
|
}
|
|
26433
|
-
validatePlainWordsSummary(content);
|
|
26434
26510
|
}
|
|
26435
26511
|
function upsertPlainWordsSection(body, section) {
|
|
26436
26512
|
let rest = body;
|
|
@@ -26454,28 +26530,27 @@ function upsertPlainWordsSection(body, section) {
|
|
|
26454
26530
|
|
|
26455
26531
|
${trimmedRest}` : section;
|
|
26456
26532
|
}
|
|
26457
|
-
function plainWordsSummaryLines(content) {
|
|
26458
|
-
return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 6);
|
|
26459
|
-
}
|
|
26460
26533
|
async function writePlainWordsIfSuiteMember(deps, args) {
|
|
26461
26534
|
if (args.meta?.suite !== "mm-work") return;
|
|
26462
26535
|
try {
|
|
26463
|
-
if (!args.
|
|
26464
|
-
const
|
|
26536
|
+
if (!args.summary) return;
|
|
26537
|
+
const body = clean2(await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body", "-q", ".body"]));
|
|
26538
|
+
const credits = await collectReleaseCredits(deps, args.repo, body);
|
|
26465
26539
|
const section = [
|
|
26466
|
-
buildPlainWordsSection(
|
|
26467
|
-
...args.
|
|
26540
|
+
buildPlainWordsSection(args.summary, args.big === true, "en", credits),
|
|
26541
|
+
...args.summaryTr ? [buildPlainWordsSection(args.summaryTr, false, "tr")] : []
|
|
26468
26542
|
].join("\n\n");
|
|
26469
|
-
const body = clean2(await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body", "-q", ".body"]));
|
|
26470
26543
|
await deps.run("gh", ["release", "edit", args.tag, "--repo", args.repo, "--notes", upsertPlainWordsSection(body, section)]);
|
|
26471
|
-
} catch {
|
|
26544
|
+
} catch (e) {
|
|
26545
|
+
(deps.warn ?? console.warn)(`plain-words write failed for ${args.repo}@${args.tag}: ${e.message}`);
|
|
26472
26546
|
}
|
|
26473
26547
|
}
|
|
26474
26548
|
async function writePlainWordsSection(deps, options, repo, meta, tag) {
|
|
26475
26549
|
try {
|
|
26476
|
-
const
|
|
26477
|
-
const
|
|
26478
|
-
|
|
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 });
|
|
26479
26554
|
} catch {
|
|
26480
26555
|
}
|
|
26481
26556
|
}
|
|
@@ -27810,7 +27885,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27810
27885
|
const preMainPartial = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27811
27886
|
const promotedPartial = !preMainPartial && await isPromotedUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27812
27887
|
const partialRelease = preMainPartial || promotedPartial;
|
|
27813
|
-
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);
|
|
27814
27889
|
if (!partialRelease) {
|
|
27815
27890
|
try {
|
|
27816
27891
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
@@ -28686,7 +28761,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
28686
28761
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
28687
28762
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
28688
28763
|
assertSuiteAutoVersion(ctx.repo, meta, process.env, command === "release" && !directTrack);
|
|
28689
|
-
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);
|
|
28690
28765
|
const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
|
|
28691
28766
|
if (command === "rcand") {
|
|
28692
28767
|
if (directTrack) {
|
|
@@ -39021,7 +39096,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
|
|
|
39021
39096
|
await assertConsumerBomLineage(deps, mergedSha, tag);
|
|
39022
39097
|
}
|
|
39023
39098
|
const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
|
|
39024
|
-
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);
|
|
39025
39100
|
if (!releaseExists && isHubControlRepo(ctx.repo)) {
|
|
39026
39101
|
if (!options.announceSummaryFile) {
|
|
39027
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");
|
|
@@ -47436,16 +47511,18 @@ async function announceSummaryLineCountRefusal(path2) {
|
|
|
47436
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`;
|
|
47437
47512
|
}
|
|
47438
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;
|
|
47439
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;
|
|
47440
47517
|
}
|
|
47441
|
-
async function announceSummaryRefusal(path2, isHub = false) {
|
|
47518
|
+
async function announceSummaryRefusal(path2, isHub = false, isWorkSuite = false) {
|
|
47442
47519
|
let lines2;
|
|
47443
47520
|
try {
|
|
47444
47521
|
lines2 = summaryFileLines(await (0, import_promises9.readFile)(path2, "utf8"));
|
|
47445
47522
|
} catch (e) {
|
|
47446
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`;
|
|
47447
47524
|
}
|
|
47448
|
-
if (lines2.length < 3 || lines2.length > 6) {
|
|
47525
|
+
if (!isWorkSuite && (lines2.length < 3 || lines2.length > 6)) {
|
|
47449
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`;
|
|
47450
47527
|
}
|
|
47451
47528
|
if (isHub) {
|
|
@@ -47917,6 +47994,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
47917
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)");
|
|
47918
47995
|
const RELEASE_ONLY_FLAGS = [
|
|
47919
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)" },
|
|
47920
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)" },
|
|
47921
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)" },
|
|
47922
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)" },
|
|
@@ -48110,7 +48188,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
48110
48188
|
const raw = await withReleaseProgress(
|
|
48111
48189
|
trainApplyDeps(),
|
|
48112
48190
|
"Checking the existing release",
|
|
48113
|
-
(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 })
|
|
48114
48192
|
);
|
|
48115
48193
|
const result = raw.dispatch?.workflowRuns ? { ...raw, dispatch: { ...raw.dispatch, workflowRuns: raw.dispatch.workflowRuns.map(workflowRunWithEvidence) } } : raw;
|
|
48116
48194
|
const projectInfoSync = await withReleaseProgress(
|
|
@@ -48166,7 +48244,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
48166
48244
|
}
|
|
48167
48245
|
if (isWorkSuite) {
|
|
48168
48246
|
try {
|
|
48169
|
-
await requireSuiteReleaseSummary({}, o.announceSummaryFile);
|
|
48247
|
+
await requireSuiteReleaseSummary({}, o.announceSummaryFile, o.announceSummaryFileTr);
|
|
48170
48248
|
} catch (e) {
|
|
48171
48249
|
return fail(`release: ${e.message}`);
|
|
48172
48250
|
}
|
|
@@ -48177,14 +48255,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
48177
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`
|
|
48178
48256
|
);
|
|
48179
48257
|
}
|
|
48180
|
-
const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub);
|
|
48258
|
+
const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub, isWorkSuite);
|
|
48181
48259
|
if (refusal) return fail(refusal);
|
|
48182
48260
|
}
|
|
48183
48261
|
}
|
|
48184
48262
|
if (o.apply) {
|
|
48185
48263
|
try {
|
|
48186
48264
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
48187
|
-
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 });
|
|
48188
48266
|
const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
|
|
48189
48267
|
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
48190
48268
|
let projectInfoSync;
|
|
@@ -48369,16 +48447,16 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
48369
48447
|
if (o.suiteSolo !== o.ownerGo) return fail("hotfix start: solo suite hotfix requires BOTH --suite-solo --owner-go");
|
|
48370
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);
|
|
48371
48449
|
});
|
|
48372
|
-
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) => {
|
|
48373
48451
|
if (o.suiteSolo !== o.ownerGo) return fail("hotfix release: solo suite release requires BOTH --suite-solo --owner-go");
|
|
48374
48452
|
if (o.retryDeploy) {
|
|
48375
|
-
if (o.announceSummaryFile || o.carries) {
|
|
48453
|
+
if (o.announceSummaryFile || o.announceSummaryFileTr || o.carries) {
|
|
48376
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");
|
|
48377
48455
|
}
|
|
48378
48456
|
return runHotfixSub("retry-deploy", () => runHotfixRetryDeploy(trainApplyDeps(), version), o, renderHotfixRetryDeploy);
|
|
48379
48457
|
}
|
|
48380
48458
|
return runHotfixSub("release", async () => {
|
|
48381
|
-
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) } : {} });
|
|
48382
48460
|
const suitePin = o.suiteSolo ? await pinCompletedMember(result) : void 0;
|
|
48383
48461
|
const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
|
|
48384
48462
|
return { ...result, projectInfoSync, ...o.suiteSolo ? { suiteSolo: true, suitePin } : {} };
|
|
@@ -48711,10 +48789,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48711
48789
|
};
|
|
48712
48790
|
return {
|
|
48713
48791
|
recordPath,
|
|
48714
|
-
validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile),
|
|
48792
|
+
validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile, summaryFileTr),
|
|
48715
48793
|
validateReleaseSummaries: (releases) => validateReleaseSummaries(
|
|
48716
48794
|
releases,
|
|
48717
48795
|
{ ...memberSummaryFiles, [SUITE_REPO]: summaryFile },
|
|
48796
|
+
{ ...memberSummaryFilesTr, [SUITE_REPO]: summaryFileTr },
|
|
48718
48797
|
async (repo, tag) => {
|
|
48719
48798
|
try {
|
|
48720
48799
|
await gh(["release", "view", tag, "--repo", repo, "--json", "tagName"]);
|
|
@@ -48805,7 +48884,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48805
48884
|
},
|
|
48806
48885
|
// #7738: a member hotfixes as suite-solo; the lead hotfixes under its suite-lead capability (inTrain sets it).
|
|
48807
48886
|
hotfixStart: (repo, o) => inTrain(repo, async (deps) => {
|
|
48808
|
-
await requireSuiteReleaseSummary(deps, summaryFile);
|
|
48887
|
+
await requireSuiteReleaseSummary(deps, summaryFile, summaryFileTr);
|
|
48809
48888
|
const r = await runHotfixStart(deps, { from: o.from, ack: o.ack, suiteSolo: repo !== SUITE_REPO });
|
|
48810
48889
|
return { tag: r.tag };
|
|
48811
48890
|
}, repo !== SUITE_REPO),
|
|
@@ -48989,7 +49068,7 @@ function registerSuiteCommands(program3) {
|
|
|
48989
49068
|
return fail(`suite abandon: ${e.message}`);
|
|
48990
49069
|
}
|
|
48991
49070
|
});
|
|
48992
|
-
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
|
|
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) => {
|
|
48993
49072
|
try {
|
|
48994
49073
|
if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
|
|
48995
49074
|
const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
|
|
@@ -49012,7 +49091,7 @@ function registerSuiteCommands(program3) {
|
|
|
49012
49091
|
return fail(`suite release: ${e.message}`);
|
|
49013
49092
|
}
|
|
49014
49093
|
});
|
|
49015
|
-
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
|
|
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) => {
|
|
49016
49095
|
try {
|
|
49017
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");
|
|
49018
49097
|
const root = await leadRoot();
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -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
|
|
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.
|
package/skills/release/SKILL.md
CHANGED
|
@@ -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>`
|
|
19
|
-
|
|
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
|