@mutmutco/cli 3.52.0 → 3.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +310 -157
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6229,11 +6229,27 @@ function resolveReleaseTrack(meta, hints, repo) {
|
|
|
6229
6229
|
if (inferred) return inferred;
|
|
6230
6230
|
return "full";
|
|
6231
6231
|
}
|
|
6232
|
+
function metaWithDeclaredClass(meta, declaredClass) {
|
|
6233
|
+
if (!declaredClass) return meta;
|
|
6234
|
+
const stated = typeof meta?.releaseTrack === "string" || typeof meta?.class === "string" || typeof meta?.deployModel === "string";
|
|
6235
|
+
if (stated) return meta;
|
|
6236
|
+
return { ...meta ?? {}, class: declaredClass };
|
|
6237
|
+
}
|
|
6232
6238
|
function branchesForTrack(track) {
|
|
6233
6239
|
if (track === "trunk") return ["main"];
|
|
6234
6240
|
if (track === "direct") return ["development", "main"];
|
|
6235
6241
|
return ["development", "rc", "main"];
|
|
6236
6242
|
}
|
|
6243
|
+
function promotionBranchesForTrack(track) {
|
|
6244
|
+
if (track === "trunk") return [];
|
|
6245
|
+
if (track === "direct") return ["main"];
|
|
6246
|
+
return ["rc", "main"];
|
|
6247
|
+
}
|
|
6248
|
+
function isPromotionBase(base, track) {
|
|
6249
|
+
if (!base) return false;
|
|
6250
|
+
if (!track) return base !== "development";
|
|
6251
|
+
return promotionBranchesForTrack(track).includes(base);
|
|
6252
|
+
}
|
|
6237
6253
|
|
|
6238
6254
|
// src/readiness-audit.ts
|
|
6239
6255
|
var TENANT_DEPLOY_RUN_SCAN_LIMIT = 100;
|
|
@@ -9092,6 +9108,7 @@ function cachedReadNote(cachedAt, now = Date.now()) {
|
|
|
9092
9108
|
// src/marketplace-autoupdate.ts
|
|
9093
9109
|
var KNOWN_MARKETPLACES_RELATIVE = [".claude", "plugins", "known_marketplaces.json"];
|
|
9094
9110
|
var MMI_MARKETPLACE_NAME = "mutmutco";
|
|
9111
|
+
var JERV_MARKETPLACE_NAME = "jervaise";
|
|
9095
9112
|
function readFileSyncSafe(path2, read) {
|
|
9096
9113
|
try {
|
|
9097
9114
|
return read(path2, "utf8");
|
|
@@ -9128,6 +9145,49 @@ function resolveAutoUpdate(probe) {
|
|
|
9128
9145
|
reason: "undeclared \u2014 third-party marketplaces are OFF by default, so nothing auto-updates and no update notice ever prints"
|
|
9129
9146
|
};
|
|
9130
9147
|
}
|
|
9148
|
+
function captureMarketplacePins(raw, names) {
|
|
9149
|
+
const out = /* @__PURE__ */ new Map();
|
|
9150
|
+
for (const name of names) {
|
|
9151
|
+
const { registered, declared, ref } = readKnownMarketplace(raw, name);
|
|
9152
|
+
if (!registered) continue;
|
|
9153
|
+
const pins = {
|
|
9154
|
+
...typeof declared === "boolean" ? { autoUpdate: declared } : {},
|
|
9155
|
+
...ref ? { ref } : {}
|
|
9156
|
+
};
|
|
9157
|
+
if (Object.keys(pins).length) out.set(name, pins);
|
|
9158
|
+
}
|
|
9159
|
+
return out;
|
|
9160
|
+
}
|
|
9161
|
+
function restoreMarketplacePins(raw, pins) {
|
|
9162
|
+
if (!raw || pins.size === 0) return null;
|
|
9163
|
+
let parsed;
|
|
9164
|
+
try {
|
|
9165
|
+
parsed = JSON.parse(raw);
|
|
9166
|
+
} catch {
|
|
9167
|
+
return null;
|
|
9168
|
+
}
|
|
9169
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
9170
|
+
const root = parsed;
|
|
9171
|
+
let changed = false;
|
|
9172
|
+
for (const [name, want] of pins) {
|
|
9173
|
+
const entry = root[name];
|
|
9174
|
+
if (!entry || typeof entry !== "object") continue;
|
|
9175
|
+
const e = entry;
|
|
9176
|
+
if (typeof want.autoUpdate === "boolean" && e.autoUpdate !== want.autoUpdate) {
|
|
9177
|
+
e.autoUpdate = want.autoUpdate;
|
|
9178
|
+
changed = true;
|
|
9179
|
+
}
|
|
9180
|
+
if (want.ref && e.source && typeof e.source === "object") {
|
|
9181
|
+
const source = e.source;
|
|
9182
|
+
if (source.ref !== want.ref) {
|
|
9183
|
+
source.ref = want.ref;
|
|
9184
|
+
changed = true;
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
return changed ? `${JSON.stringify(root, null, 2)}
|
|
9189
|
+
` : null;
|
|
9190
|
+
}
|
|
9131
9191
|
function readKnownMarketplace(raw, name) {
|
|
9132
9192
|
if (!raw) return { registered: false };
|
|
9133
9193
|
let parsed;
|
|
@@ -10018,6 +10078,112 @@ function planManagedGitignore(current) {
|
|
|
10018
10078
|
return { changed, content, added, removed };
|
|
10019
10079
|
}
|
|
10020
10080
|
|
|
10081
|
+
// src/docs-index-command.ts
|
|
10082
|
+
var import_node_fs15 = require("node:fs");
|
|
10083
|
+
var import_node_path13 = require("node:path");
|
|
10084
|
+
var DOCS_INDEX_PATH = "docs/index.md";
|
|
10085
|
+
function isRoutableDocsPath(relPath) {
|
|
10086
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
10087
|
+
return normalized !== "index.md" && normalized.split("/")[0]?.toLowerCase() !== "archive";
|
|
10088
|
+
}
|
|
10089
|
+
var GENERATED_HEADER = "<!-- Generated by `mmi-cli docs index --write`. Do not edit by hand. -->";
|
|
10090
|
+
function plainInline(text) {
|
|
10091
|
+
return text.replace(/\r?\n/g, " ").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/`/g, "").replace(/\*+/g, "").replace(/~~/g, "").replace(/__([^_]+)__/g, "$1").replace(/(^|[\s(])_([^_]+)_($|[\s).,!?:;])/g, "$1$2$3").replace(/^>\s*/, "").replace(/\\([\\`*_\[\]{}()#+.!|>-])/g, "$1").replace(/\s+/g, " ").trim();
|
|
10092
|
+
}
|
|
10093
|
+
function extractDocMeta(relPath, raw) {
|
|
10094
|
+
const lines = raw.split(/\r?\n/);
|
|
10095
|
+
let i = 0;
|
|
10096
|
+
if (lines[i]?.trim() === "---") {
|
|
10097
|
+
i++;
|
|
10098
|
+
while (i < lines.length && lines[i].trim() !== "---") i++;
|
|
10099
|
+
if (i < lines.length) i++;
|
|
10100
|
+
}
|
|
10101
|
+
let title = "";
|
|
10102
|
+
let scope = "";
|
|
10103
|
+
for (; i < lines.length; i++) {
|
|
10104
|
+
const line = lines[i];
|
|
10105
|
+
const trimmed = line.trim();
|
|
10106
|
+
if (trimmed === "") continue;
|
|
10107
|
+
if (trimmed.startsWith("<!--")) {
|
|
10108
|
+
while (i < lines.length && !lines[i].includes("-->")) i++;
|
|
10109
|
+
continue;
|
|
10110
|
+
}
|
|
10111
|
+
if (!title && trimmed.startsWith("# ")) {
|
|
10112
|
+
title = trimmed.slice(2).trim();
|
|
10113
|
+
continue;
|
|
10114
|
+
}
|
|
10115
|
+
if (!title) {
|
|
10116
|
+
break;
|
|
10117
|
+
}
|
|
10118
|
+
if (trimmed.startsWith("#")) continue;
|
|
10119
|
+
scope = plainInline(trimmed);
|
|
10120
|
+
break;
|
|
10121
|
+
}
|
|
10122
|
+
if (!title) {
|
|
10123
|
+
const base = relPath.split("/").pop() ?? relPath;
|
|
10124
|
+
title = base.replace(/\.md$/, "");
|
|
10125
|
+
}
|
|
10126
|
+
return { path: relPath, title, scope };
|
|
10127
|
+
}
|
|
10128
|
+
function renderDocsIndex(entries) {
|
|
10129
|
+
const lines = [
|
|
10130
|
+
GENERATED_HEADER,
|
|
10131
|
+
"",
|
|
10132
|
+
"# Documentation index",
|
|
10133
|
+
"",
|
|
10134
|
+
"Generated from the `docs/` tree by `mmi-cli docs index --write`. `--check` fails on drift, so this index",
|
|
10135
|
+
"can never diverge from the records it lists.",
|
|
10136
|
+
""
|
|
10137
|
+
];
|
|
10138
|
+
for (const entry of entries) {
|
|
10139
|
+
const link = `[${plainInline(entry.title)}](${entry.path})`;
|
|
10140
|
+
lines.push(entry.scope ? `- ${link} \u2014 ${plainInline(entry.scope)}` : `- ${link}`);
|
|
10141
|
+
}
|
|
10142
|
+
return lines.join("\n") + "\n";
|
|
10143
|
+
}
|
|
10144
|
+
function sameContent(a, b) {
|
|
10145
|
+
const normalize = (text) => text.replace(/\r\n/g, "\n");
|
|
10146
|
+
return normalize(a) === normalize(b);
|
|
10147
|
+
}
|
|
10148
|
+
function docsIndex(deps, opts = {}) {
|
|
10149
|
+
const entries = deps.listDocs().slice().sort().map((relPath) => extractDocMeta(relPath, deps.readDoc(relPath)));
|
|
10150
|
+
const content = renderDocsIndex(entries);
|
|
10151
|
+
const current = deps.readIndex();
|
|
10152
|
+
const drift = current === null || !sameContent(current, content);
|
|
10153
|
+
let wrote = false;
|
|
10154
|
+
if (!opts.check && drift) {
|
|
10155
|
+
deps.writeIndex(content);
|
|
10156
|
+
wrote = true;
|
|
10157
|
+
}
|
|
10158
|
+
return { content, drift, wrote };
|
|
10159
|
+
}
|
|
10160
|
+
function walkMarkdown(dir) {
|
|
10161
|
+
const out = [];
|
|
10162
|
+
const stack = [dir];
|
|
10163
|
+
while (stack.length) {
|
|
10164
|
+
const current = stack.pop();
|
|
10165
|
+
for (const entry of (0, import_node_fs15.readdirSync)(current, { withFileTypes: true })) {
|
|
10166
|
+
const full = (0, import_node_path13.join)(current, entry.name);
|
|
10167
|
+
if (entry.isDirectory()) {
|
|
10168
|
+
stack.push(full);
|
|
10169
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
10170
|
+
out.push((0, import_node_path13.relative)(dir, full).split(import_node_path13.sep).join("/"));
|
|
10171
|
+
}
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
10174
|
+
return out;
|
|
10175
|
+
}
|
|
10176
|
+
function createDocsIndexDeps(repoRoot2) {
|
|
10177
|
+
const docsDir = (0, import_node_path13.join)(repoRoot2, "docs");
|
|
10178
|
+
const indexPath = (0, import_node_path13.join)(repoRoot2, DOCS_INDEX_PATH);
|
|
10179
|
+
return {
|
|
10180
|
+
listDocs: () => (0, import_node_fs15.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
|
|
10181
|
+
readDoc: (relPath) => (0, import_node_fs15.readFileSync)((0, import_node_path13.join)(docsDir, relPath), "utf8"),
|
|
10182
|
+
readIndex: () => (0, import_node_fs15.existsSync)(indexPath) ? (0, import_node_fs15.readFileSync)(indexPath, "utf8") : null,
|
|
10183
|
+
writeIndex: (content) => (0, import_node_fs15.writeFileSync)(indexPath, content, "utf8")
|
|
10184
|
+
};
|
|
10185
|
+
}
|
|
10186
|
+
|
|
10021
10187
|
// src/gate-budget.ts
|
|
10022
10188
|
var BLESSED_RUN_WITH_BUDGET_SHA = "ea2cf8710949dec53566c29836e29cd7515a4e23";
|
|
10023
10189
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
@@ -10264,6 +10430,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
|
|
|
10264
10430
|
out.REPO_NAME ??= parsed.name;
|
|
10265
10431
|
out.REPO_SLUG ??= parsed.slug;
|
|
10266
10432
|
out.CLASS ??= cls;
|
|
10433
|
+
out.PROJECT_OWNER ??= parsed.owner;
|
|
10267
10434
|
const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
|
|
10268
10435
|
const runtime = out.GATE_RUNTIME === "python" ? "python" : "node";
|
|
10269
10436
|
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
|
|
@@ -10464,6 +10631,26 @@ function boardFieldsQueryArgs(projectId) {
|
|
|
10464
10631
|
const query = "query($id: ID!) { node(id: $id) { ... on ProjectV2 { number fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } } } } } }";
|
|
10465
10632
|
return ["api", "graphql", "-f", `query=${query}`, "-f", `id=${projectId}`];
|
|
10466
10633
|
}
|
|
10634
|
+
function seededDocsIndex(seeds) {
|
|
10635
|
+
const docs2 = seeds.map((s) => ({ rel: s.path.replace(/\\/g, "/"), content: s.content })).filter((s) => s.rel.startsWith("docs/") && s.rel.endsWith(".md")).map((s) => ({ rel: s.rel.slice("docs/".length), content: s.content })).filter((s) => isRoutableDocsPath(s.rel));
|
|
10636
|
+
if (!docs2.length) return null;
|
|
10637
|
+
const entries = docs2.slice().sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0).map((s) => extractDocMeta(s.rel, s.content));
|
|
10638
|
+
return renderDocsIndex(entries);
|
|
10639
|
+
}
|
|
10640
|
+
function linkedProjectsQueryArgs(owner, name) {
|
|
10641
|
+
const query = "query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { projectsV2(first: 10) { nodes { id number title } } } }";
|
|
10642
|
+
return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
|
|
10643
|
+
}
|
|
10644
|
+
function pickLinkedProject(json) {
|
|
10645
|
+
const nodes = json?.data?.repository?.projectsV2?.nodes;
|
|
10646
|
+
if (!Array.isArray(nodes)) return void 0;
|
|
10647
|
+
const usable = nodes.filter(
|
|
10648
|
+
(n) => typeof n?.id === "string" && Boolean(n.id)
|
|
10649
|
+
);
|
|
10650
|
+
if (usable.length !== 1) return void 0;
|
|
10651
|
+
const only = usable[0];
|
|
10652
|
+
return typeof only.number === "number" && Number.isFinite(only.number) ? { id: only.id, number: only.number } : { id: only.id };
|
|
10653
|
+
}
|
|
10467
10654
|
function decideSeedPrAction(openPrs) {
|
|
10468
10655
|
const list = Array.isArray(openPrs) ? openPrs : [];
|
|
10469
10656
|
for (const pr2 of list) {
|
|
@@ -11112,12 +11299,12 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
11112
11299
|
}
|
|
11113
11300
|
|
|
11114
11301
|
// src/wave-status.ts
|
|
11115
|
-
var
|
|
11302
|
+
var import_node_fs17 = require("node:fs");
|
|
11116
11303
|
|
|
11117
11304
|
// src/stage-runner.ts
|
|
11118
11305
|
var import_node_child_process7 = require("node:child_process");
|
|
11119
|
-
var
|
|
11120
|
-
var
|
|
11306
|
+
var import_node_fs16 = require("node:fs");
|
|
11307
|
+
var import_node_path14 = require("node:path");
|
|
11121
11308
|
var import_node_net = require("node:net");
|
|
11122
11309
|
var import_node_util6 = require("node:util");
|
|
11123
11310
|
|
|
@@ -11256,11 +11443,11 @@ function appendForceRecreate(up) {
|
|
|
11256
11443
|
return `${up.trimEnd()} --force-recreate`;
|
|
11257
11444
|
}
|
|
11258
11445
|
function stageStatePath(cwd = process.cwd()) {
|
|
11259
|
-
return (0,
|
|
11446
|
+
return (0, import_node_path14.join)(cwd, "tmp", "stage", "state.json");
|
|
11260
11447
|
}
|
|
11261
11448
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
11262
|
-
const dir = (0,
|
|
11263
|
-
return (0,
|
|
11449
|
+
const dir = (0, import_node_path14.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path14.resolve)(cwd, gitCommonDir);
|
|
11450
|
+
return (0, import_node_path14.join)(dir, "mmi", "stage", "state.json");
|
|
11264
11451
|
}
|
|
11265
11452
|
function normPath2(path2) {
|
|
11266
11453
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -11484,14 +11671,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
11484
11671
|
}
|
|
11485
11672
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
11486
11673
|
if (!config.ensureEnv) return;
|
|
11487
|
-
const target = (0,
|
|
11488
|
-
const example = (0,
|
|
11489
|
-
if (!(0,
|
|
11490
|
-
(0,
|
|
11491
|
-
} else if ((0,
|
|
11492
|
-
const stale = detectStaleEnvFile((0,
|
|
11493
|
-
exampleMtimeMs: (0,
|
|
11494
|
-
targetMtimeMs: (0,
|
|
11674
|
+
const target = (0, import_node_path14.join)(cwd, config.ensureEnv.target);
|
|
11675
|
+
const example = (0, import_node_path14.join)(cwd, config.ensureEnv.example);
|
|
11676
|
+
if (!(0, import_node_fs16.existsSync)(target) && (0, import_node_fs16.existsSync)(example)) {
|
|
11677
|
+
(0, import_node_fs16.copyFileSync)(example, target);
|
|
11678
|
+
} else if ((0, import_node_fs16.existsSync)(target) && (0, import_node_fs16.existsSync)(example)) {
|
|
11679
|
+
const stale = detectStaleEnvFile((0, import_node_fs16.readFileSync)(example, "utf8"), (0, import_node_fs16.readFileSync)(target, "utf8"), {
|
|
11680
|
+
exampleMtimeMs: (0, import_node_fs16.statSync)(example).mtimeMs,
|
|
11681
|
+
targetMtimeMs: (0, import_node_fs16.statSync)(target).mtimeMs
|
|
11495
11682
|
});
|
|
11496
11683
|
if (stale) {
|
|
11497
11684
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -11499,8 +11686,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
11499
11686
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
11500
11687
|
}
|
|
11501
11688
|
}
|
|
11502
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
11503
|
-
(0,
|
|
11689
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs16.existsSync)(target)) {
|
|
11690
|
+
(0, import_node_fs16.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs16.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
11504
11691
|
}
|
|
11505
11692
|
}
|
|
11506
11693
|
async function gitText(cwd, args) {
|
|
@@ -11530,20 +11717,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
11530
11717
|
return void 0;
|
|
11531
11718
|
}
|
|
11532
11719
|
function readState(path2) {
|
|
11533
|
-
if (!(0,
|
|
11720
|
+
if (!(0, import_node_fs16.existsSync)(path2)) return null;
|
|
11534
11721
|
try {
|
|
11535
|
-
return JSON.parse((0,
|
|
11722
|
+
return JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8"));
|
|
11536
11723
|
} catch {
|
|
11537
11724
|
return null;
|
|
11538
11725
|
}
|
|
11539
11726
|
}
|
|
11540
11727
|
function mkdirFor(path2) {
|
|
11541
11728
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
11542
|
-
(0,
|
|
11729
|
+
(0, import_node_fs16.mkdirSync)(dir, { recursive: true });
|
|
11543
11730
|
}
|
|
11544
11731
|
function writeState(path2, state) {
|
|
11545
11732
|
mkdirFor(path2);
|
|
11546
|
-
(0,
|
|
11733
|
+
(0, import_node_fs16.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
11547
11734
|
}
|
|
11548
11735
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
11549
11736
|
const reservation = {
|
|
@@ -11563,7 +11750,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
11563
11750
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
|
|
11564
11751
|
}
|
|
11565
11752
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
11566
|
-
(0,
|
|
11753
|
+
(0, import_node_fs16.rmSync)(path2, { force: true });
|
|
11567
11754
|
}
|
|
11568
11755
|
}
|
|
11569
11756
|
async function killTree(pid) {
|
|
@@ -11721,8 +11908,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
11721
11908
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
11722
11909
|
if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
|
|
11723
11910
|
} catch (e) {
|
|
11724
|
-
(0,
|
|
11725
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
11911
|
+
(0, import_node_fs16.rmSync)(statePath, { force: true });
|
|
11912
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs16.rmSync)(globalStatePath, { force: true });
|
|
11726
11913
|
throw e;
|
|
11727
11914
|
}
|
|
11728
11915
|
const started = await startStage(config, {
|
|
@@ -11743,9 +11930,9 @@ function parseNextFromHead(headText) {
|
|
|
11743
11930
|
}
|
|
11744
11931
|
function readStageSummary(worktreePath) {
|
|
11745
11932
|
const statePath = stageStatePath(worktreePath);
|
|
11746
|
-
if (!(0,
|
|
11933
|
+
if (!(0, import_node_fs17.existsSync)(statePath)) return void 0;
|
|
11747
11934
|
try {
|
|
11748
|
-
const state = JSON.parse((0,
|
|
11935
|
+
const state = JSON.parse((0, import_node_fs17.readFileSync)(statePath, "utf8"));
|
|
11749
11936
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
11750
11937
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
11751
11938
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -11864,7 +12051,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
11864
12051
|
// src/gh-create.ts
|
|
11865
12052
|
var import_promises = require("node:fs/promises");
|
|
11866
12053
|
var import_node_os3 = require("node:os");
|
|
11867
|
-
var
|
|
12054
|
+
var import_node_path15 = require("node:path");
|
|
11868
12055
|
var import_node_crypto3 = require("node:crypto");
|
|
11869
12056
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
11870
12057
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -11918,8 +12105,8 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11918
12105
|
const remove2 = deps.remove ?? import_promises.unlink;
|
|
11919
12106
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
11920
12107
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
11921
|
-
const file = (0,
|
|
11922
|
-
await ensureDir((0,
|
|
12108
|
+
const file = (0, import_node_path15.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
12109
|
+
await ensureDir((0, import_node_path15.dirname)(file), { recursive: true }).catch(() => {
|
|
11923
12110
|
});
|
|
11924
12111
|
await write(file, args[i + 1], "utf8");
|
|
11925
12112
|
return {
|
|
@@ -13194,8 +13381,8 @@ function parseVerifyBroker(stdout) {
|
|
|
13194
13381
|
}
|
|
13195
13382
|
|
|
13196
13383
|
// src/train-apply.ts
|
|
13197
|
-
var
|
|
13198
|
-
var
|
|
13384
|
+
var import_node_fs18 = require("node:fs");
|
|
13385
|
+
var import_node_path16 = require("node:path");
|
|
13199
13386
|
function resolveDeployModel2(meta, repo) {
|
|
13200
13387
|
const m = meta?.deployModel;
|
|
13201
13388
|
if (isDeployModel(m)) return m;
|
|
@@ -14103,17 +14290,17 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
14103
14290
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
14104
14291
|
}
|
|
14105
14292
|
function readLocalGateWorkflows() {
|
|
14106
|
-
const dir = (0,
|
|
14293
|
+
const dir = (0, import_node_path16.join)(".github", "workflows");
|
|
14107
14294
|
let names;
|
|
14108
14295
|
try {
|
|
14109
|
-
names = (0,
|
|
14296
|
+
names = (0, import_node_fs18.readdirSync)(dir);
|
|
14110
14297
|
} catch {
|
|
14111
14298
|
return null;
|
|
14112
14299
|
}
|
|
14113
14300
|
const files = [];
|
|
14114
14301
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
14115
14302
|
try {
|
|
14116
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
14303
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(dir, name), "utf8") });
|
|
14117
14304
|
} catch {
|
|
14118
14305
|
}
|
|
14119
14306
|
}
|
|
@@ -16009,112 +16196,6 @@ function renderAccessReport(report) {
|
|
|
16009
16196
|
return lines.join("\n");
|
|
16010
16197
|
}
|
|
16011
16198
|
|
|
16012
|
-
// src/docs-index-command.ts
|
|
16013
|
-
var import_node_fs18 = require("node:fs");
|
|
16014
|
-
var import_node_path16 = require("node:path");
|
|
16015
|
-
var DOCS_INDEX_PATH = "docs/index.md";
|
|
16016
|
-
function isRoutableDocsPath(relPath) {
|
|
16017
|
-
const normalized = relPath.replace(/\\/g, "/");
|
|
16018
|
-
return normalized !== "index.md" && normalized.split("/")[0]?.toLowerCase() !== "archive";
|
|
16019
|
-
}
|
|
16020
|
-
var GENERATED_HEADER = "<!-- Generated by `mmi-cli docs index --write`. Do not edit by hand. -->";
|
|
16021
|
-
function plainInline(text) {
|
|
16022
|
-
return text.replace(/\r?\n/g, " ").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/`/g, "").replace(/\*+/g, "").replace(/~~/g, "").replace(/__([^_]+)__/g, "$1").replace(/(^|[\s(])_([^_]+)_($|[\s).,!?:;])/g, "$1$2$3").replace(/^>\s*/, "").replace(/\\([\\`*_\[\]{}()#+.!|>-])/g, "$1").replace(/\s+/g, " ").trim();
|
|
16023
|
-
}
|
|
16024
|
-
function extractDocMeta(relPath, raw) {
|
|
16025
|
-
const lines = raw.split(/\r?\n/);
|
|
16026
|
-
let i = 0;
|
|
16027
|
-
if (lines[i]?.trim() === "---") {
|
|
16028
|
-
i++;
|
|
16029
|
-
while (i < lines.length && lines[i].trim() !== "---") i++;
|
|
16030
|
-
if (i < lines.length) i++;
|
|
16031
|
-
}
|
|
16032
|
-
let title = "";
|
|
16033
|
-
let scope = "";
|
|
16034
|
-
for (; i < lines.length; i++) {
|
|
16035
|
-
const line = lines[i];
|
|
16036
|
-
const trimmed = line.trim();
|
|
16037
|
-
if (trimmed === "") continue;
|
|
16038
|
-
if (trimmed.startsWith("<!--")) {
|
|
16039
|
-
while (i < lines.length && !lines[i].includes("-->")) i++;
|
|
16040
|
-
continue;
|
|
16041
|
-
}
|
|
16042
|
-
if (!title && trimmed.startsWith("# ")) {
|
|
16043
|
-
title = trimmed.slice(2).trim();
|
|
16044
|
-
continue;
|
|
16045
|
-
}
|
|
16046
|
-
if (!title) {
|
|
16047
|
-
break;
|
|
16048
|
-
}
|
|
16049
|
-
if (trimmed.startsWith("#")) continue;
|
|
16050
|
-
scope = plainInline(trimmed);
|
|
16051
|
-
break;
|
|
16052
|
-
}
|
|
16053
|
-
if (!title) {
|
|
16054
|
-
const base = relPath.split("/").pop() ?? relPath;
|
|
16055
|
-
title = base.replace(/\.md$/, "");
|
|
16056
|
-
}
|
|
16057
|
-
return { path: relPath, title, scope };
|
|
16058
|
-
}
|
|
16059
|
-
function renderDocsIndex(entries) {
|
|
16060
|
-
const lines = [
|
|
16061
|
-
GENERATED_HEADER,
|
|
16062
|
-
"",
|
|
16063
|
-
"# Documentation index",
|
|
16064
|
-
"",
|
|
16065
|
-
"Generated from the `docs/` tree by `mmi-cli docs index --write`. `--check` fails on drift, so this index",
|
|
16066
|
-
"can never diverge from the records it lists.",
|
|
16067
|
-
""
|
|
16068
|
-
];
|
|
16069
|
-
for (const entry of entries) {
|
|
16070
|
-
const link = `[${plainInline(entry.title)}](${entry.path})`;
|
|
16071
|
-
lines.push(entry.scope ? `- ${link} \u2014 ${plainInline(entry.scope)}` : `- ${link}`);
|
|
16072
|
-
}
|
|
16073
|
-
return lines.join("\n") + "\n";
|
|
16074
|
-
}
|
|
16075
|
-
function sameContent(a, b) {
|
|
16076
|
-
const normalize = (text) => text.replace(/\r\n/g, "\n");
|
|
16077
|
-
return normalize(a) === normalize(b);
|
|
16078
|
-
}
|
|
16079
|
-
function docsIndex(deps, opts = {}) {
|
|
16080
|
-
const entries = deps.listDocs().slice().sort().map((relPath) => extractDocMeta(relPath, deps.readDoc(relPath)));
|
|
16081
|
-
const content = renderDocsIndex(entries);
|
|
16082
|
-
const current = deps.readIndex();
|
|
16083
|
-
const drift = current === null || !sameContent(current, content);
|
|
16084
|
-
let wrote = false;
|
|
16085
|
-
if (!opts.check && drift) {
|
|
16086
|
-
deps.writeIndex(content);
|
|
16087
|
-
wrote = true;
|
|
16088
|
-
}
|
|
16089
|
-
return { content, drift, wrote };
|
|
16090
|
-
}
|
|
16091
|
-
function walkMarkdown(dir) {
|
|
16092
|
-
const out = [];
|
|
16093
|
-
const stack = [dir];
|
|
16094
|
-
while (stack.length) {
|
|
16095
|
-
const current = stack.pop();
|
|
16096
|
-
for (const entry of (0, import_node_fs18.readdirSync)(current, { withFileTypes: true })) {
|
|
16097
|
-
const full = (0, import_node_path16.join)(current, entry.name);
|
|
16098
|
-
if (entry.isDirectory()) {
|
|
16099
|
-
stack.push(full);
|
|
16100
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
16101
|
-
out.push((0, import_node_path16.relative)(dir, full).split(import_node_path16.sep).join("/"));
|
|
16102
|
-
}
|
|
16103
|
-
}
|
|
16104
|
-
}
|
|
16105
|
-
return out;
|
|
16106
|
-
}
|
|
16107
|
-
function createDocsIndexDeps(repoRoot2) {
|
|
16108
|
-
const docsDir = (0, import_node_path16.join)(repoRoot2, "docs");
|
|
16109
|
-
const indexPath = (0, import_node_path16.join)(repoRoot2, DOCS_INDEX_PATH);
|
|
16110
|
-
return {
|
|
16111
|
-
listDocs: () => (0, import_node_fs18.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
|
|
16112
|
-
readDoc: (relPath) => (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(docsDir, relPath), "utf8"),
|
|
16113
|
-
readIndex: () => (0, import_node_fs18.existsSync)(indexPath) ? (0, import_node_fs18.readFileSync)(indexPath, "utf8") : null,
|
|
16114
|
-
writeIndex: (content) => (0, import_node_fs18.writeFileSync)(indexPath, content, "utf8")
|
|
16115
|
-
};
|
|
16116
|
-
}
|
|
16117
|
-
|
|
16118
16199
|
// src/doc-refs-core.ts
|
|
16119
16200
|
var import_node_child_process9 = require("node:child_process");
|
|
16120
16201
|
var import_node_fs19 = require("node:fs");
|
|
@@ -19345,7 +19426,7 @@ var import_node_fs22 = require("node:fs");
|
|
|
19345
19426
|
|
|
19346
19427
|
// src/bootstrap-verify.ts
|
|
19347
19428
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
19348
|
-
var requiredDocs = ["README.md", "architecture.md", "docs/decisions/README.md"];
|
|
19429
|
+
var requiredDocs = ["README.md", "architecture.md", "docs/decisions/README.md", "docs/index.md"];
|
|
19349
19430
|
var requiredIssueTemplates = [
|
|
19350
19431
|
".github/ISSUE_TEMPLATE/bug.yml",
|
|
19351
19432
|
".github/ISSUE_TEMPLATE/feature.yml",
|
|
@@ -19502,6 +19583,11 @@ function unfilledDocPlaceholders(text) {
|
|
|
19502
19583
|
for (const prompt of DOC_PLACEHOLDER_PROMPTS) if (text.includes(prompt)) found.add(prompt);
|
|
19503
19584
|
return [...found];
|
|
19504
19585
|
}
|
|
19586
|
+
function filledDocCheck(label, text, path2) {
|
|
19587
|
+
if (text === null) return { ok: false, label, detail: `${path2} not readable via API` };
|
|
19588
|
+
const unfilled = unfilledDocPlaceholders(text);
|
|
19589
|
+
return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
|
|
19590
|
+
}
|
|
19505
19591
|
async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
19506
19592
|
const branchesWanted = expectedBranches(repoClass, releaseTrack);
|
|
19507
19593
|
const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
|
|
@@ -19575,19 +19661,9 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
19575
19661
|
label: "README has Agent context section",
|
|
19576
19662
|
detail: readme === null ? "README.md not readable via API" : void 0
|
|
19577
19663
|
});
|
|
19578
|
-
|
|
19579
|
-
checks.push({
|
|
19580
|
-
ok: readmePlaceholders.length === 0,
|
|
19581
|
-
label: "README Agent context is filled (no template placeholders)",
|
|
19582
|
-
detail: readmePlaceholders.length ? `unfilled: ${readmePlaceholders.join(", ")}` : void 0
|
|
19583
|
-
});
|
|
19664
|
+
checks.push(filledDocCheck("README Agent context is filled (no template placeholders)", readme, "README.md"));
|
|
19584
19665
|
const architecture = await contentText(deps, repo, baseBranch, "architecture.md");
|
|
19585
|
-
|
|
19586
|
-
checks.push({
|
|
19587
|
-
ok: archPlaceholders.length === 0,
|
|
19588
|
-
label: "architecture.md is filled (no template placeholders)",
|
|
19589
|
-
detail: archPlaceholders.length ? `unfilled: ${archPlaceholders.join(", ")}` : void 0
|
|
19590
|
-
});
|
|
19666
|
+
checks.push(filledDocCheck("architecture.md is filled (no template placeholders)", architecture, "architecture.md"));
|
|
19591
19667
|
const labels = await restPagedJson2(deps, `repos/${repo}/labels`, []);
|
|
19592
19668
|
const labelNames = new Set(labels.map((l) => l.name));
|
|
19593
19669
|
for (const label of requiredLabels) {
|
|
@@ -19910,7 +19986,11 @@ function registerBootstrapCommands(program3) {
|
|
|
19910
19986
|
return null;
|
|
19911
19987
|
}
|
|
19912
19988
|
}
|
|
19913
|
-
|
|
19989
|
+
// #3537: verify runs BEFORE registration by construction (skill Step 0a), so `meta` is null on every
|
|
19990
|
+
// fresh repo and `resolveReleaseTrack` falls through to `full` — which then outranks the operator's
|
|
19991
|
+
// own `--class content` inside expectedBranches, and the report demands a development and an rc the
|
|
19992
|
+
// track forbids. The declared class fills that gap only when the registry states nothing itself.
|
|
19993
|
+
}, resolveReleaseTrack(metaWithDeclaredClass(meta, o.class), void 0, repo));
|
|
19914
19994
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderBootstrapVerifyReport(report));
|
|
19915
19995
|
if (!report.ok) process.exitCode = 1;
|
|
19916
19996
|
});
|
|
@@ -19958,6 +20038,17 @@ function registerBootstrapCommands(program3) {
|
|
|
19958
20038
|
} catch {
|
|
19959
20039
|
}
|
|
19960
20040
|
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack);
|
|
20041
|
+
if (!vars.PROJECT_ID) {
|
|
20042
|
+
try {
|
|
20043
|
+
const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
|
|
20044
|
+
const linked = pickLinkedProject(JSON.parse(r.stdout));
|
|
20045
|
+
if (linked) {
|
|
20046
|
+
vars.PROJECT_ID = linked.id;
|
|
20047
|
+
if (linked.number != null && vars.PROJECT_NUMBER == null) vars.PROJECT_NUMBER = String(linked.number);
|
|
20048
|
+
}
|
|
20049
|
+
} catch {
|
|
20050
|
+
}
|
|
20051
|
+
}
|
|
19961
20052
|
if (vars.PROJECT_ID) {
|
|
19962
20053
|
try {
|
|
19963
20054
|
const r = await gh(boardFieldsQueryArgs(vars.PROJECT_ID));
|
|
@@ -19999,6 +20090,7 @@ function registerBootstrapCommands(program3) {
|
|
|
19999
20090
|
}
|
|
20000
20091
|
}
|
|
20001
20092
|
}
|
|
20093
|
+
const docsForIndex = [];
|
|
20002
20094
|
for (const seed of manifest.seeds) {
|
|
20003
20095
|
if (!seed.classes.includes(o.class)) continue;
|
|
20004
20096
|
if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
|
|
@@ -20026,12 +20118,38 @@ function registerBootstrapCommands(program3) {
|
|
|
20026
20118
|
const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile7) : null;
|
|
20027
20119
|
const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
|
|
20028
20120
|
actions.push(action);
|
|
20121
|
+
const docBody = content ?? remoteContent;
|
|
20122
|
+
if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
|
|
20123
|
+
docsForIndex.push({ path: resolved.target, content: docBody });
|
|
20124
|
+
}
|
|
20029
20125
|
if (o.execute && (action.action === "create" || action.action === "update")) {
|
|
20030
20126
|
await gh(contentPutArgs(repo, resolved.target, content, seedPlan.ref, action.action === "update" ? sha : void 0));
|
|
20031
20127
|
applied.push(`${action.action} ${resolved.target}`);
|
|
20032
20128
|
if (seedPlan.mode === "pr") seededToBranch++;
|
|
20033
20129
|
}
|
|
20034
20130
|
}
|
|
20131
|
+
const indexContent = seededDocsIndex(docsForIndex);
|
|
20132
|
+
if (indexContent) {
|
|
20133
|
+
let indexCurrent = null;
|
|
20134
|
+
try {
|
|
20135
|
+
const r = await gh(["api", `repos/${repo}/contents/${enc(DOCS_INDEX_PATH)}?ref=${seedPlan.ref}`]);
|
|
20136
|
+
const parsed = JSON.parse(r.stdout);
|
|
20137
|
+
indexCurrent = parsed.encoding === "base64" && typeof parsed.content === "string" ? Buffer.from(parsed.content, "base64").toString("utf8") : "";
|
|
20138
|
+
} catch {
|
|
20139
|
+
}
|
|
20140
|
+
const indexAction = indexCurrent === null ? "create" : "skip";
|
|
20141
|
+
actions.push({
|
|
20142
|
+
target: DOCS_INDEX_PATH,
|
|
20143
|
+
action: indexAction,
|
|
20144
|
+
ownership: "org",
|
|
20145
|
+
reason: indexCurrent === null ? "generated routing index (#3545)" : "routing index present \u2014 regenerate with `mmi-cli docs index --write`, which sees the whole tree"
|
|
20146
|
+
});
|
|
20147
|
+
if (o.execute && indexAction === "create") {
|
|
20148
|
+
await gh(contentPutArgs(repo, DOCS_INDEX_PATH, indexContent, seedPlan.ref, void 0));
|
|
20149
|
+
applied.push(`${indexAction} ${DOCS_INDEX_PATH}`);
|
|
20150
|
+
if (seedPlan.mode === "pr") seededToBranch++;
|
|
20151
|
+
}
|
|
20152
|
+
}
|
|
20035
20153
|
let seedPrUrl;
|
|
20036
20154
|
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && seededToBranch > 0) {
|
|
20037
20155
|
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-f", "delete_branch_on_merge=true"]).catch(() => {
|
|
@@ -22668,12 +22786,40 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
|
22668
22786
|
return { ok: false, detail: `not a Claude surface (${surface}) \u2014 no Hub-shipped plugin to reinstall` };
|
|
22669
22787
|
}
|
|
22670
22788
|
const steps = [];
|
|
22789
|
+
const pinsPath = (0, import_node_path26.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
22790
|
+
const before = readKnownMarketplacesFile(pinsPath);
|
|
22791
|
+
const pins = captureMarketplacePins(before, [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]);
|
|
22671
22792
|
const ok = await applyPluginHeal(surface, (msg) => steps.push(msg.trim()));
|
|
22793
|
+
const restored = ok ? restoreMarketplacePinsOnDisk(pinsPath, pins) : void 0;
|
|
22672
22794
|
return {
|
|
22673
22795
|
ok,
|
|
22674
|
-
detail: ok ?
|
|
22796
|
+
detail: ok ? `marketplace remove \u2192 add \u2192 install succeeded${restored ? `; ${restored}` : ""}` : `\`claude plugin\` reinstall failed or was skipped${steps.length ? ` (${steps[steps.length - 1]})` : ""}`
|
|
22675
22797
|
};
|
|
22676
22798
|
}
|
|
22799
|
+
function readKnownMarketplacesFile(path2) {
|
|
22800
|
+
try {
|
|
22801
|
+
return (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : void 0;
|
|
22802
|
+
} catch {
|
|
22803
|
+
return void 0;
|
|
22804
|
+
}
|
|
22805
|
+
}
|
|
22806
|
+
function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
22807
|
+
if (pins.size === 0) return void 0;
|
|
22808
|
+
const after = readKnownMarketplacesFile(path2);
|
|
22809
|
+
const next = restoreMarketplacePins(after, pins);
|
|
22810
|
+
if (next === null) return void 0;
|
|
22811
|
+
try {
|
|
22812
|
+
(0, import_node_fs28.writeFileSync)(path2, next, "utf8");
|
|
22813
|
+
} catch {
|
|
22814
|
+
return `could NOT restore ${[...pins.keys()].join(", ")} \u2014 re-pin by hand`;
|
|
22815
|
+
}
|
|
22816
|
+
const verify = readKnownMarketplacesFile(path2);
|
|
22817
|
+
const failed = [...pins].filter(([name, want]) => {
|
|
22818
|
+
const got = readKnownMarketplace(verify, name);
|
|
22819
|
+
return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
|
|
22820
|
+
});
|
|
22821
|
+
return failed.length ? `restore did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 re-pin by hand` : `re-pinned ${[...pins.keys()].join(", ")} (auto-update + catalog ref survive the reinstall)`;
|
|
22822
|
+
}
|
|
22677
22823
|
async function runGuard(readOrigin) {
|
|
22678
22824
|
try {
|
|
22679
22825
|
const surface = detectSurface(process.env);
|
|
@@ -26323,11 +26469,18 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
26323
26469
|
const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
|
|
26324
26470
|
const viewed = (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS2 })).stdout.trim();
|
|
26325
26471
|
const [repoFromGh, base] = viewed.split(/\s+/);
|
|
26326
|
-
if (base && base !== "development") {
|
|
26327
|
-
throw new Error(`pr land: base branch must be development (got ${base}) \u2014 promotion merges stay human-only`);
|
|
26328
|
-
}
|
|
26329
26472
|
const repo = repoOpt ?? repoFromGh;
|
|
26330
26473
|
if (!repo) throw new Error("pr land: could not resolve PR repo");
|
|
26474
|
+
let track;
|
|
26475
|
+
try {
|
|
26476
|
+
const meta = await fetchProjectBySlug(slugOf(repo), registryClientDeps(await loadConfig()));
|
|
26477
|
+
if (meta) track = resolveReleaseTrack(meta, void 0, repo);
|
|
26478
|
+
} catch {
|
|
26479
|
+
}
|
|
26480
|
+
if (isPromotionBase(base, track)) {
|
|
26481
|
+
const shape = track ? `${track} track` : "unresolved track \u2014 strict reading";
|
|
26482
|
+
throw new Error(`pr land: base branch ${base} is a promotion target (${shape}) \u2014 promotion merges stay human-only`);
|
|
26483
|
+
}
|
|
26331
26484
|
return repo;
|
|
26332
26485
|
},
|
|
26333
26486
|
fetchTrainAuthority: async (repo) => fetchTrainAuthority(repo, registryClientDeps(await loadConfig())),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.54.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|