@mutmutco/codex-plugin 4.5.169 → 4.5.171
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
|
@@ -10145,6 +10145,19 @@ function retriedFetch(deps, url, init) {
|
|
|
10145
10145
|
sleep: deps.retrySleep
|
|
10146
10146
|
});
|
|
10147
10147
|
}
|
|
10148
|
+
async function backfillDeployHistory(since, apply, deps) {
|
|
10149
|
+
if (!deps.baseUrl) throw new Error("Hub API URL not configured");
|
|
10150
|
+
const token = await deps.token();
|
|
10151
|
+
if (!token) throw new Error("no Hub session token (run `gh auth login`)");
|
|
10152
|
+
const res = await retriedFetch({ ...deps, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS }, `${deps.baseUrl.replace(/\/$/, "")}/deploy-history/backfill`, {
|
|
10153
|
+
method: "POST",
|
|
10154
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
10155
|
+
body: JSON.stringify({ since, dryRun: !apply })
|
|
10156
|
+
});
|
|
10157
|
+
const body = await res.json();
|
|
10158
|
+
if (!res.ok) throw new Error(`deploy history backfill HTTP ${res.status}: ${body.error ?? res.statusText}`);
|
|
10159
|
+
return body;
|
|
10160
|
+
}
|
|
10148
10161
|
async function fetchTrainAuthority(repo, deps) {
|
|
10149
10162
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
10150
10163
|
const token = await deps.token();
|
|
@@ -19074,10 +19087,10 @@ var rollout_plan_default = {
|
|
|
19074
19087
|
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)."
|
|
19075
19088
|
},
|
|
19076
19089
|
baseline: {
|
|
19077
|
-
version: "4.5.
|
|
19078
|
-
tag: "v4.5.
|
|
19079
|
-
commit: "
|
|
19080
|
-
npm: "@mutmutco/cli@4.5.
|
|
19090
|
+
version: "4.5.171",
|
|
19091
|
+
tag: "v4.5.171",
|
|
19092
|
+
commit: "79faa07090b1",
|
|
19093
|
+
npm: "@mutmutco/cli@4.5.171"
|
|
19081
19094
|
},
|
|
19082
19095
|
exitCriterion: "fleet-n-of-n",
|
|
19083
19096
|
hubOnlyShortcut: "forbidden",
|
|
@@ -19094,14 +19107,14 @@ var rollout_plan_default = {
|
|
|
19094
19107
|
repo: "mutmutco/mmi-hub",
|
|
19095
19108
|
role: "canary",
|
|
19096
19109
|
schedule: "train",
|
|
19097
|
-
v3Target: "v4.5.
|
|
19110
|
+
v3Target: "v4.5.171"
|
|
19098
19111
|
}
|
|
19099
19112
|
],
|
|
19100
19113
|
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.",
|
|
19101
19114
|
rollback: {
|
|
19102
19115
|
independent: true,
|
|
19103
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
19104
|
-
v3Target: "v4.5.
|
|
19116
|
+
mechanism: "npm dist-tag latest -> 4.5.171 and redeploy the Hub Lambda from tag v4.5.171 (79faa07090b1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
19117
|
+
v3Target: "v4.5.171 (@mutmutco/cli@4.5.171, tag commit 79faa07090b1 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
19105
19118
|
}
|
|
19106
19119
|
},
|
|
19107
19120
|
{
|
|
@@ -19291,7 +19304,7 @@ function formatTrainStatus(r) {
|
|
|
19291
19304
|
"",
|
|
19292
19305
|
`release track: ${r.releaseTrack} \u2014 stages: ${r.branches.join(" -> ")}`,
|
|
19293
19306
|
`cli version: ${r.cliVersion}${r.repoVersion ? ` (repo: ${r.repoVersion})` : ""}${r.releasedVersion ? ` (released: ${r.releasedVersion})` : ""}`,
|
|
19294
|
-
`release bump intent: ${r.bumpIntent ?? "not declared \u2014 an ordinary train defaults to patch (#4929);
|
|
19307
|
+
`release bump intent: ${r.bumpIntent ?? "not declared \u2014 an ordinary train defaults to patch (#4929)"} (proposed: patch; minor needs MMI_BUMP_REASON, major also MMI_MAJOR_APPROVED=<version> \u2014 docs/versioning.md)`
|
|
19295
19308
|
];
|
|
19296
19309
|
if (r.versionLag.ok) {
|
|
19297
19310
|
lines2.push("version lag: none \u2014 CLI is current");
|
|
@@ -26549,6 +26562,53 @@ async function selfConvergeTrainCli(input) {
|
|
|
26549
26562
|
}
|
|
26550
26563
|
}
|
|
26551
26564
|
|
|
26565
|
+
// src/bump-proposal.ts
|
|
26566
|
+
var BREAKING_BANG = /^[a-z]+(\([^)]*\))?!:/i;
|
|
26567
|
+
var BREAKING_TRAILER = /^BREAKING[ -]CHANGE:/m;
|
|
26568
|
+
var MAX_BUMP_REASON = 200;
|
|
26569
|
+
function proposeBump(commits, _opts = {}) {
|
|
26570
|
+
const breaking = [];
|
|
26571
|
+
for (const c of commits) {
|
|
26572
|
+
if (BREAKING_BANG.test(c.subject) || BREAKING_TRAILER.test(c.body)) breaking.push(`${c.sha.slice(0, 8)} ${c.subject}`);
|
|
26573
|
+
}
|
|
26574
|
+
return { proposed: "patch", breaking };
|
|
26575
|
+
}
|
|
26576
|
+
function parseRangeLog(raw) {
|
|
26577
|
+
return raw.split("").map((r) => r.replace(/^\n+/, "")).filter((r) => r.trim()).map((r) => {
|
|
26578
|
+
const [sha = "", subject = "", body = ""] = r.split("");
|
|
26579
|
+
return { sha: sha.trim(), subject: subject.trim(), body };
|
|
26580
|
+
});
|
|
26581
|
+
}
|
|
26582
|
+
var RANGE_LOG_FORMAT = "--format=%H%x1f%s%x1f%b%x1e";
|
|
26583
|
+
function enforceBumpRules(input) {
|
|
26584
|
+
const { env, proposal } = input;
|
|
26585
|
+
const effective = input.preOneZero && input.intent === "major" ? "minor" : input.intent;
|
|
26586
|
+
const reason = (env.MMI_BUMP_REASON ?? "").trim();
|
|
26587
|
+
const evidence = proposal.breaking.join("; ");
|
|
26588
|
+
const decision = () => ({ intent: effective, reason: reason || null, proposed: proposal.proposed, breaking: proposal.breaking });
|
|
26589
|
+
if (effective !== "patch") {
|
|
26590
|
+
if (!reason) throw new Error(`MMI_BUMP_INTENT=${input.intent} needs MMI_BUMP_REASON \u2014 a one-line reason (\u2264${MAX_BUMP_REASON} chars) for a real feature or significant change; patch is the default (#8499)`);
|
|
26591
|
+
if (/[\r\n]/.test(reason) || reason.length > MAX_BUMP_REASON) throw new Error(`MMI_BUMP_REASON must be one line of at most ${MAX_BUMP_REASON} chars (#8499)`);
|
|
26592
|
+
}
|
|
26593
|
+
if (effective === "major") {
|
|
26594
|
+
const approved = (env.MMI_MAJOR_APPROVED ?? "").trim().replace(/^v/, "");
|
|
26595
|
+
const target = (input.majorTarget ?? "").replace(/^v/, "");
|
|
26596
|
+
if (!approved) throw new Error(`a major release is owner-gated: set MMI_MAJOR_APPROVED=${target || "<target version>"} only after Jervaise's fresh approval in this turn (#8499)`);
|
|
26597
|
+
if (approved !== target) throw new Error(`MMI_MAJOR_APPROVED=${approved} does not match the major target ${target}; a stale approval cannot tag a different major (#8499)`);
|
|
26598
|
+
return decision();
|
|
26599
|
+
}
|
|
26600
|
+
if (proposal.breaking.length && effective === "patch") {
|
|
26601
|
+
throw new Error(input.preOneZero ? `range carries breaking changes (${evidence}): this repo is held on 0.x.y, so declare MMI_BUMP_INTENT=minor with MMI_BUMP_REASON, or ship a shim (patch) (#8499)` : `range carries breaking changes (${evidence}): ship a shim (patch) or declare an owner-gated major (MMI_BUMP_INTENT=major, MMI_BUMP_REASON, MMI_MAJOR_APPROVED=<version>) (#8499)`);
|
|
26602
|
+
}
|
|
26603
|
+
if (proposal.breaking.length && !input.preOneZero) {
|
|
26604
|
+
throw new Error(`range carries breaking changes (${evidence}): ship a shim (patch) or declare an owner-gated major (#8499)`);
|
|
26605
|
+
}
|
|
26606
|
+
return decision();
|
|
26607
|
+
}
|
|
26608
|
+
function bumpNotesLine(d) {
|
|
26609
|
+
return `Version bump: ${d.intent} \u2014 ${d.reason ?? "default patch"}`;
|
|
26610
|
+
}
|
|
26611
|
+
|
|
26552
26612
|
// src/train-doctor.ts
|
|
26553
26613
|
var TRAIN_LANES = ["release", "rcand", "hotfix"];
|
|
26554
26614
|
var TROUBLESHOOTING_GUIDE = "docs/Guides/train-troubleshooting.md";
|
|
@@ -26844,6 +26904,17 @@ async function runTrainDoctor(input) {
|
|
|
26844
26904
|
if (workflows === null && !startBranchMissing) {
|
|
26845
26905
|
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
26846
26906
|
}
|
|
26907
|
+
if (lane === "release" && !startBranchMissing && startBranch) {
|
|
26908
|
+
try {
|
|
26909
|
+
const proposal = proposeBump(parseRangeLog(await git3(["log", `origin/main..origin/${startBranch}`, RANGE_LOG_FORMAT])));
|
|
26910
|
+
add({ code: "bump-proposal", severity: "info", source: "origin", title: `bump proposal: ${proposal.proposed}`, remedy: "nothing \u2014 patch is the default; minor needs MMI_BUMP_REASON, major also MMI_MAJOR_APPROVED=<version> (docs/versioning.md)" });
|
|
26911
|
+
for (const evidence of proposal.breaking) {
|
|
26912
|
+
add({ code: "bump-breaking", severity: "warning", source: "origin", title: `breaking signal in the release range: ${evidence}`, remedy: "ship a shim (patch) or declare an owner-gated major; the train refuses otherwise" });
|
|
26913
|
+
}
|
|
26914
|
+
} catch (e) {
|
|
26915
|
+
void e;
|
|
26916
|
+
}
|
|
26917
|
+
}
|
|
26847
26918
|
if (lane === "release" && !startBranchMissing) {
|
|
26848
26919
|
const required = meta?.requiredChecks?.length ? meta.requiredChecks : ["gate"];
|
|
26849
26920
|
const emitters = (workflows ?? []).filter((f) => parseWorkflowJobIds(f.body).some((id) => required.some((context) => contextMatchesJobId(id, context))));
|
|
@@ -29610,6 +29681,17 @@ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease, pr
|
|
|
29610
29681
|
}
|
|
29611
29682
|
return "patch";
|
|
29612
29683
|
}
|
|
29684
|
+
async function enforceTrainBump(deps, env, intent, preOneZero, source) {
|
|
29685
|
+
const raw = await deps.run("git", ["log", `origin/main..${source}`, RANGE_LOG_FORMAT]);
|
|
29686
|
+
const proposal = proposeBump(parseRangeLog(raw), { preOneZero });
|
|
29687
|
+
let majorTarget;
|
|
29688
|
+
if (intent === "major") {
|
|
29689
|
+
const explicit = (env.MMI_RELEASE_VERSION ?? "").trim().replace(/^v/, "");
|
|
29690
|
+
const latest = await latestLocalReleaseTag(deps, void 0, preOneZero);
|
|
29691
|
+
majorTarget = explicit || `${latest.major + 1}.0.0`;
|
|
29692
|
+
}
|
|
29693
|
+
return enforceBumpRules({ intent, env, proposal, preOneZero, majorTarget });
|
|
29694
|
+
}
|
|
29613
29695
|
async function resolveHotfixVersion(deps, env = process.env, preOneZero = false) {
|
|
29614
29696
|
const latest = await latestLocalReleaseTag(deps, "origin/main", preOneZero);
|
|
29615
29697
|
if (latest.major === 0 && latest.minor === 0 && latest.patch === 0) {
|
|
@@ -29655,9 +29737,19 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
29655
29737
|
await requireCleanTree(deps);
|
|
29656
29738
|
await runGitRemoteRead(deps, ["fetch", "origin"]);
|
|
29657
29739
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
29658
|
-
const
|
|
29740
|
+
const declaredIntent = await resolveTrainBumpIntent(deps, process.env, void 0, preOneZeroInForce(meta));
|
|
29659
29741
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
29660
29742
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
29743
|
+
const bump = await enforceTrainBump(
|
|
29744
|
+
deps,
|
|
29745
|
+
process.env,
|
|
29746
|
+
declaredIntent,
|
|
29747
|
+
preOneZeroInForce(meta),
|
|
29748
|
+
command === "release" && !directTrack && options.dev !== true ? "origin/rc" : "origin/development"
|
|
29749
|
+
);
|
|
29750
|
+
const bumpIntent = bump.intent;
|
|
29751
|
+
const bumpReceipt = { intent: bump.intent, reason: bump.reason, proposed: bump.proposed, breaking: bump.breaking };
|
|
29752
|
+
options = { ...options, bumpLine: bumpNotesLine(bump) };
|
|
29661
29753
|
assertSuiteAutoVersion(ctx.repo, meta, process.env, command === "release" && !directTrack);
|
|
29662
29754
|
if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
|
|
29663
29755
|
const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
|
|
@@ -29665,28 +29757,31 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
29665
29757
|
if (directTrack) {
|
|
29666
29758
|
throw new Error("direct-track repos release straight to main (no rc); run `mmi-cli devops release --apply` from development instead of rcand");
|
|
29667
29759
|
}
|
|
29668
|
-
return withCheckoutRestoredAfterRelease(deps, await
|
|
29760
|
+
return withCheckoutRestoredAfterRelease(deps, await runPipelineWithBump(bumpReceipt, "rcand", pipelineInput), "development");
|
|
29669
29761
|
}
|
|
29670
29762
|
if (directTrack) {
|
|
29671
29763
|
return withCheckoutRestoredAfterRelease(
|
|
29672
29764
|
deps,
|
|
29673
|
-
await
|
|
29765
|
+
await runPipelineWithBump(bumpReceipt, "release-dev", { ...pipelineInput, directTrack: true }),
|
|
29674
29766
|
"development"
|
|
29675
29767
|
);
|
|
29676
29768
|
}
|
|
29677
29769
|
if (command === "release" && options.dev) {
|
|
29678
29770
|
return withCheckoutRestoredAfterRelease(
|
|
29679
29771
|
deps,
|
|
29680
|
-
await
|
|
29772
|
+
await runPipelineWithBump(bumpReceipt, "release-dev", pipelineInput),
|
|
29681
29773
|
"development"
|
|
29682
29774
|
);
|
|
29683
29775
|
}
|
|
29684
29776
|
return withCheckoutRestoredAfterRelease(
|
|
29685
29777
|
deps,
|
|
29686
|
-
await
|
|
29778
|
+
await runPipelineWithBump(bumpReceipt, "release-full", pipelineInput),
|
|
29687
29779
|
"rc"
|
|
29688
29780
|
);
|
|
29689
29781
|
}
|
|
29782
|
+
async function runPipelineWithBump(bump, mode, input) {
|
|
29783
|
+
return { ...await runTrainApplyPipeline(mode, input), bump };
|
|
29784
|
+
}
|
|
29690
29785
|
|
|
29691
29786
|
// src/ci-audit.ts
|
|
29692
29787
|
var HUB_REPO4 = "mutmutco/MMI-Hub";
|
|
@@ -31076,6 +31171,7 @@ function consolidateCommandNamespaces(program3) {
|
|
|
31076
31171
|
const runtimeDeploy = runtime.command("deploy").description("runtime deployment status");
|
|
31077
31172
|
const deploy = child(program3, "deploy");
|
|
31078
31173
|
move(deploy, runtimeDeploy, "status");
|
|
31174
|
+
move(deploy, runtimeDeploy, "history-backfill");
|
|
31079
31175
|
remove(program3, "deploy");
|
|
31080
31176
|
move(program3, runtime, "box");
|
|
31081
31177
|
move(program3, runtime, "edge");
|
|
@@ -32476,7 +32572,7 @@ function parseTrainTag(tag) {
|
|
|
32476
32572
|
function cmpTrainRelease(a, b) {
|
|
32477
32573
|
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
32478
32574
|
}
|
|
32479
|
-
function trainPlanTargetsFromTags(tags, explicitRaw) {
|
|
32575
|
+
function trainPlanTargetsFromTags(tags, explicitRaw, intent = process.env.MMI_BUMP_INTENT) {
|
|
32480
32576
|
const all = tags.map(parseTrainTag).filter((v) => Boolean(v));
|
|
32481
32577
|
const release = all.find((v) => v.rc === null) ?? { major: 0, minor: 0, patch: 0, rc: null };
|
|
32482
32578
|
const targets = {};
|
|
@@ -32493,7 +32589,8 @@ function trainPlanTargetsFromTags(tags, explicitRaw) {
|
|
|
32493
32589
|
}
|
|
32494
32590
|
}
|
|
32495
32591
|
if (!targets.plannedReleaseError) {
|
|
32496
|
-
const
|
|
32592
|
+
const bumped = intent === "major" ? { major: release.major + 1, minor: 0, patch: 0 } : intent === "minor" ? { major: release.major, minor: release.minor + 1, patch: 0 } : { major: release.major, minor: release.minor, patch: release.patch + 1 };
|
|
32593
|
+
const planned = explicit ?? { ...bumped, rc: null };
|
|
32497
32594
|
targets.plannedRelease = `v${planned.major}.${planned.minor}.${planned.patch}`;
|
|
32498
32595
|
}
|
|
32499
32596
|
let rcs = all.filter((v) => v.rc !== null);
|
|
@@ -32983,6 +33080,9 @@ function parseSecretsCatalogVar(raw) {
|
|
|
32983
33080
|
if (e.aliases !== void 0 && (!Array.isArray(e.aliases) || e.aliases.some((a) => typeof a !== "string"))) {
|
|
32984
33081
|
throw new Error(`org project set: secrets["${mapKey}"].aliases must be a string array`);
|
|
32985
33082
|
}
|
|
33083
|
+
if (e.branchReadable !== void 0 && typeof e.branchReadable !== "boolean") {
|
|
33084
|
+
throw new Error(`org project set: secrets["${mapKey}"].branchReadable must be a boolean`);
|
|
33085
|
+
}
|
|
32986
33086
|
}
|
|
32987
33087
|
return parsed;
|
|
32988
33088
|
}
|
|
@@ -33481,7 +33581,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
33481
33581
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
33482
33582
|
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",output?:"file",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
33483
33583
|
internalPeers: "JSON {owner/repo:[dev|rc|main]} \u2014 same-box suite peers via the box Caddy; clear with --unset internalPeers (#7886)",
|
|
33484
|
-
secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[]} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
|
|
33584
|
+
secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[],branchReadable?} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
|
|
33485
33585
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
33486
33586
|
statusOptions: "JSON name\u2192id map",
|
|
33487
33587
|
priorityOptions: "JSON {Urgent,High,Medium,Low}\u2192id map",
|
|
@@ -37883,6 +37983,19 @@ function formatDeployStatus(r) {
|
|
|
37883
37983
|
var STAGES2 = ["dev", "rc", "main"];
|
|
37884
37984
|
function registerDeployCommands(program3) {
|
|
37885
37985
|
const deploy = program3.command("deploy").description("per-stage deploy observability \u2014 last run, health, and running version (#2688)");
|
|
37986
|
+
deploy.command("history-backfill").description("master-only registry history backfill from Hub tenant-deploy runs (dry-run by default; no dispatch)").requiredOption("--since <ISO>", "include runs created at or after this timestamp").option("--apply", "write successful runs to Hub deploy history").option("--json", "machine-readable counts").action(async (o) => {
|
|
37987
|
+
try {
|
|
37988
|
+
const report = await backfillDeployHistory(o.since, o.apply === true, registryClientDeps(await loadConfig()));
|
|
37989
|
+
if (o.json) console.log(JSON.stringify(report, null, 2));
|
|
37990
|
+
else {
|
|
37991
|
+
console.log(`${report.dryRun ? "Dry-run" : "Applied"} since ${report.since}: ${report.totalRuns} runs, ${report.written} written`);
|
|
37992
|
+
for (const [repo, counts] of Object.entries(report.perRepo)) console.log(`${repo}: ${counts.success} success, ${counts.failed} failed, ${counts.cancelled} cancelled, ${counts.other} other`);
|
|
37993
|
+
console.log(`Skipped: ${report.skipped.nonSha} non-SHA, ${report.skipped.unregistered} unregistered/ambiguous, ${report.skipped.badTitle} bad title`);
|
|
37994
|
+
}
|
|
37995
|
+
} catch (e) {
|
|
37996
|
+
return failGraceful(`deploy history-backfill: ${e.message}`);
|
|
37997
|
+
}
|
|
37998
|
+
});
|
|
37886
37999
|
deploy.command("status <stage>").description("last deploy run + runtime health probe + running version for a stage (defaults to the current repo); a root answering 401/403/404 is re-probed at /health and the `url` field names the endpoint that answered (#6137)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (stage, o) => {
|
|
37887
38000
|
if (!STAGES2.includes(stage)) {
|
|
37888
38001
|
return fail(`runtime deploy status: <stage> must be dev, rc, or main`);
|
|
@@ -42995,6 +43108,15 @@ async function openSuitePinPr(deps, input) {
|
|
|
42995
43108
|
}
|
|
42996
43109
|
}
|
|
42997
43110
|
|
|
43111
|
+
// src/pr-title-check.ts
|
|
43112
|
+
var CONVENTIONAL_PR_TITLE = /^(feat|fix|docs|chore|refactor|perf|test|build|ci|revert|style)(\([^)]+\))?!?: .+/;
|
|
43113
|
+
var EXEMPT_PR_TITLE = /^Merge pull request /;
|
|
43114
|
+
function prTitleRefusal(title) {
|
|
43115
|
+
const t = title.trim();
|
|
43116
|
+
if (CONVENTIONAL_PR_TITLE.test(t) || EXEMPT_PR_TITLE.test(t)) return null;
|
|
43117
|
+
return `pr land: PR title "${t}" is not a Conventional type. Expected \`<type>(<scope>)?(!)?: <summary>\` with type one of feat|fix|docs|chore|refactor|perf|test|build|ci|revert|style (e.g. \`fix(cli): refuse a stale tag\`); retitle with \`gh pr edit <n> --title \u2026\` and land again (#8499)`;
|
|
43118
|
+
}
|
|
43119
|
+
|
|
42998
43120
|
// src/command-register-collaboration.ts
|
|
42999
43121
|
var import_node_child_process17 = require("node:child_process");
|
|
43000
43122
|
var import_node_fs47 = require("node:fs");
|
|
@@ -47935,6 +48057,9 @@ ${list}`);
|
|
|
47935
48057
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
47936
48058
|
}
|
|
47937
48059
|
);
|
|
48060
|
+
const landTitle = await execFileP("gh", ["pr", "view", number, ...repoArgs, "--json", "title", "--jq", ".title"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => r.stdout.trim(), () => "");
|
|
48061
|
+
const landTitleRefusal = landTitle ? prTitleRefusal(landTitle) : null;
|
|
48062
|
+
if (landTitleRefusal) return refused(landTitleRefusal);
|
|
47938
48063
|
const landSquashBody = squashBodyTextForMerge(landClosingGuardInput, true);
|
|
47939
48064
|
warnSquashBodyClosingStrip("pr land", landClosingGuardInput, landSquashBody);
|
|
47940
48065
|
const landClosingGuardVerdict = evaluateClosingGuard(landClosingGuardInput, {
|
|
@@ -57656,7 +57781,7 @@ projectDeploy.command("retire [owner/repo]").description("retire (tombstone) one
|
|
|
57656
57781
|
}
|
|
57657
57782
|
if (!result.ok) return failGraceful(`org project deploy retire: ${result.error ?? "read failed"}`);
|
|
57658
57783
|
});
|
|
57659
|
-
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset-secret <KEY...>", "remove exactly these #2244 catalog entries (repeatable; KEY or KEY@dev|rc|main). Refuses while a stage still requires the key \u2014 drop it from requiredRuntimeSecrets/requiredBuildSecrets first").option("--unset-task <NAME...>", "retire exactly these tenant tasks (repeatable; lowercase kebab-case names). Refuses a name that is not declared. tenantTasks merges per entry and is capped at 20 stored in total").option("--confirm-clear-secrets", "required to let --unset secrets wipe the WHOLE catalog (the refusal names how many entries that removes)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets (the WHOLE catalog \u2014 needs --confirm-clear-secrets), edgeDomains, requiredGcpApis, publishRequired, releaseChannel, releaseLanguage").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
57784
|
+
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>", "branchReadable":<optional boolean, master-only #8497>}').option("--unset-secret <KEY...>", "remove exactly these #2244 catalog entries (repeatable; KEY or KEY@dev|rc|main). Refuses while a stage still requires the key \u2014 drop it from requiredRuntimeSecrets/requiredBuildSecrets first").option("--unset-task <NAME...>", "retire exactly these tenant tasks (repeatable; lowercase kebab-case names). Refuses a name that is not declared. tenantTasks merges per entry and is capped at 20 stored in total").option("--confirm-clear-secrets", "required to let --unset secrets wipe the WHOLE catalog (the refusal names how many entries that removes)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets (the WHOLE catalog \u2014 needs --confirm-clear-secrets), edgeDomains, requiredGcpApis, publishRequired, releaseChannel, releaseLanguage").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
57660
57785
|
const cfg = await loadConfig();
|
|
57661
57786
|
let target;
|
|
57662
57787
|
try {
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -118,6 +118,10 @@ Production changes require explicit authorization from the authorized human.
|
|
|
118
118
|
Read the selected release skill before operating the train. Let it choose versions, run gates,
|
|
119
119
|
publish, deploy, and verify the resulting release. A refusal requires fixing its cause; do not
|
|
120
120
|
finish by hand, bypass checks, or start a duplicate release.
|
|
121
|
+
Versions: patch is the default for fixes and small compatible additions. Minor is a deliberate call
|
|
122
|
+
for a real feature, with a one-line reason. Major is a new start or direction and needs the owner's
|
|
123
|
+
fresh approval before tagging. A breaking change refuses until it is shimmed or made an approved
|
|
124
|
+
major. The [release](../../release/SKILL.md) skill names the exact switches.
|
|
121
125
|
A release announces itself in Slack once its deploy and publish are green: the Hub to the org alerts channel, a product to its own
|
|
122
126
|
project channel when the project's registry META sets `releaseChannel`; never post release notes
|
|
123
127
|
by hand. The Hub always needs a curated `--announce-summary-file`, and so does any product whose
|
package/skills/release/SKILL.md
CHANGED
|
@@ -7,6 +7,8 @@ description: Ship rc or direct-track development to main and production.
|
|
|
7
7
|
|
|
8
8
|
**Argument:** `/release minor`, `/release major`, or `/release patch` — a bare bump word, never a `--flag` or env var. No argument → `patch`; a `/release patch` from development is a legitimate ordinary release, `/hotfix` stays the cherry-pick promotion lane.
|
|
9
9
|
|
|
10
|
+
**Bump rules** ([docs/versioning.md](../../docs/versioning.md)): patch is the default and needs nothing. `minor` only for a real feature or significant change, with a one-line reason exported as `MMI_BUMP_REASON`. `major` only with Jervaise's fresh approval in this turn: export `MMI_BUMP_REASON` and `MMI_MAJOR_APPROVED=<exact target version>` (e.g. `5.0.0`) only after that approval, never earlier or from a prior turn. A breaking signal in the range (`feat!:`, `BREAKING CHANGE:`) makes the train refuse until it is shimmed (patch) or declared an owner-gated major.
|
|
11
|
+
|
|
10
12
|
# /release — ship to main + prod
|
|
11
13
|
|
|
12
14
|
Full-track repos ship exactly what is on `rc` (never `development`): merge `rc → main`, tag `vX.Y.0`, publish the GitHub Release, deploy prod, roll `development` forward. Direct-track repos ship `development → main`, and the release event fires the repo's own deploy/publish workflow (MMI-Hub: `deploy.yml` + `publish.yml`). `rc` is ephemeral: `/rcand` creates its runtime, `/release` retires it after a confirmed prod deploy (`rcRetirement`). A prod release needs the authorized human's explicit go in the current turn — an agent never self-initiates it. The board needs no touch: items reached `Done` when their PRs merged to `development`.
|