@mutmutco/cli 3.52.0 → 3.53.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 +237 -156
- 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;
|
|
@@ -10018,6 +10034,112 @@ function planManagedGitignore(current) {
|
|
|
10018
10034
|
return { changed, content, added, removed };
|
|
10019
10035
|
}
|
|
10020
10036
|
|
|
10037
|
+
// src/docs-index-command.ts
|
|
10038
|
+
var import_node_fs15 = require("node:fs");
|
|
10039
|
+
var import_node_path13 = require("node:path");
|
|
10040
|
+
var DOCS_INDEX_PATH = "docs/index.md";
|
|
10041
|
+
function isRoutableDocsPath(relPath) {
|
|
10042
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
10043
|
+
return normalized !== "index.md" && normalized.split("/")[0]?.toLowerCase() !== "archive";
|
|
10044
|
+
}
|
|
10045
|
+
var GENERATED_HEADER = "<!-- Generated by `mmi-cli docs index --write`. Do not edit by hand. -->";
|
|
10046
|
+
function plainInline(text) {
|
|
10047
|
+
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();
|
|
10048
|
+
}
|
|
10049
|
+
function extractDocMeta(relPath, raw) {
|
|
10050
|
+
const lines = raw.split(/\r?\n/);
|
|
10051
|
+
let i = 0;
|
|
10052
|
+
if (lines[i]?.trim() === "---") {
|
|
10053
|
+
i++;
|
|
10054
|
+
while (i < lines.length && lines[i].trim() !== "---") i++;
|
|
10055
|
+
if (i < lines.length) i++;
|
|
10056
|
+
}
|
|
10057
|
+
let title = "";
|
|
10058
|
+
let scope = "";
|
|
10059
|
+
for (; i < lines.length; i++) {
|
|
10060
|
+
const line = lines[i];
|
|
10061
|
+
const trimmed = line.trim();
|
|
10062
|
+
if (trimmed === "") continue;
|
|
10063
|
+
if (trimmed.startsWith("<!--")) {
|
|
10064
|
+
while (i < lines.length && !lines[i].includes("-->")) i++;
|
|
10065
|
+
continue;
|
|
10066
|
+
}
|
|
10067
|
+
if (!title && trimmed.startsWith("# ")) {
|
|
10068
|
+
title = trimmed.slice(2).trim();
|
|
10069
|
+
continue;
|
|
10070
|
+
}
|
|
10071
|
+
if (!title) {
|
|
10072
|
+
break;
|
|
10073
|
+
}
|
|
10074
|
+
if (trimmed.startsWith("#")) continue;
|
|
10075
|
+
scope = plainInline(trimmed);
|
|
10076
|
+
break;
|
|
10077
|
+
}
|
|
10078
|
+
if (!title) {
|
|
10079
|
+
const base = relPath.split("/").pop() ?? relPath;
|
|
10080
|
+
title = base.replace(/\.md$/, "");
|
|
10081
|
+
}
|
|
10082
|
+
return { path: relPath, title, scope };
|
|
10083
|
+
}
|
|
10084
|
+
function renderDocsIndex(entries) {
|
|
10085
|
+
const lines = [
|
|
10086
|
+
GENERATED_HEADER,
|
|
10087
|
+
"",
|
|
10088
|
+
"# Documentation index",
|
|
10089
|
+
"",
|
|
10090
|
+
"Generated from the `docs/` tree by `mmi-cli docs index --write`. `--check` fails on drift, so this index",
|
|
10091
|
+
"can never diverge from the records it lists.",
|
|
10092
|
+
""
|
|
10093
|
+
];
|
|
10094
|
+
for (const entry of entries) {
|
|
10095
|
+
const link = `[${plainInline(entry.title)}](${entry.path})`;
|
|
10096
|
+
lines.push(entry.scope ? `- ${link} \u2014 ${plainInline(entry.scope)}` : `- ${link}`);
|
|
10097
|
+
}
|
|
10098
|
+
return lines.join("\n") + "\n";
|
|
10099
|
+
}
|
|
10100
|
+
function sameContent(a, b) {
|
|
10101
|
+
const normalize = (text) => text.replace(/\r\n/g, "\n");
|
|
10102
|
+
return normalize(a) === normalize(b);
|
|
10103
|
+
}
|
|
10104
|
+
function docsIndex(deps, opts = {}) {
|
|
10105
|
+
const entries = deps.listDocs().slice().sort().map((relPath) => extractDocMeta(relPath, deps.readDoc(relPath)));
|
|
10106
|
+
const content = renderDocsIndex(entries);
|
|
10107
|
+
const current = deps.readIndex();
|
|
10108
|
+
const drift = current === null || !sameContent(current, content);
|
|
10109
|
+
let wrote = false;
|
|
10110
|
+
if (!opts.check && drift) {
|
|
10111
|
+
deps.writeIndex(content);
|
|
10112
|
+
wrote = true;
|
|
10113
|
+
}
|
|
10114
|
+
return { content, drift, wrote };
|
|
10115
|
+
}
|
|
10116
|
+
function walkMarkdown(dir) {
|
|
10117
|
+
const out = [];
|
|
10118
|
+
const stack = [dir];
|
|
10119
|
+
while (stack.length) {
|
|
10120
|
+
const current = stack.pop();
|
|
10121
|
+
for (const entry of (0, import_node_fs15.readdirSync)(current, { withFileTypes: true })) {
|
|
10122
|
+
const full = (0, import_node_path13.join)(current, entry.name);
|
|
10123
|
+
if (entry.isDirectory()) {
|
|
10124
|
+
stack.push(full);
|
|
10125
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
10126
|
+
out.push((0, import_node_path13.relative)(dir, full).split(import_node_path13.sep).join("/"));
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
}
|
|
10130
|
+
return out;
|
|
10131
|
+
}
|
|
10132
|
+
function createDocsIndexDeps(repoRoot2) {
|
|
10133
|
+
const docsDir = (0, import_node_path13.join)(repoRoot2, "docs");
|
|
10134
|
+
const indexPath = (0, import_node_path13.join)(repoRoot2, DOCS_INDEX_PATH);
|
|
10135
|
+
return {
|
|
10136
|
+
listDocs: () => (0, import_node_fs15.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
|
|
10137
|
+
readDoc: (relPath) => (0, import_node_fs15.readFileSync)((0, import_node_path13.join)(docsDir, relPath), "utf8"),
|
|
10138
|
+
readIndex: () => (0, import_node_fs15.existsSync)(indexPath) ? (0, import_node_fs15.readFileSync)(indexPath, "utf8") : null,
|
|
10139
|
+
writeIndex: (content) => (0, import_node_fs15.writeFileSync)(indexPath, content, "utf8")
|
|
10140
|
+
};
|
|
10141
|
+
}
|
|
10142
|
+
|
|
10021
10143
|
// src/gate-budget.ts
|
|
10022
10144
|
var BLESSED_RUN_WITH_BUDGET_SHA = "ea2cf8710949dec53566c29836e29cd7515a4e23";
|
|
10023
10145
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
@@ -10264,6 +10386,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
|
|
|
10264
10386
|
out.REPO_NAME ??= parsed.name;
|
|
10265
10387
|
out.REPO_SLUG ??= parsed.slug;
|
|
10266
10388
|
out.CLASS ??= cls;
|
|
10389
|
+
out.PROJECT_OWNER ??= parsed.owner;
|
|
10267
10390
|
const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
|
|
10268
10391
|
const runtime = out.GATE_RUNTIME === "python" ? "python" : "node";
|
|
10269
10392
|
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
|
|
@@ -10464,6 +10587,26 @@ function boardFieldsQueryArgs(projectId) {
|
|
|
10464
10587
|
const query = "query($id: ID!) { node(id: $id) { ... on ProjectV2 { number fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } } } } } }";
|
|
10465
10588
|
return ["api", "graphql", "-f", `query=${query}`, "-f", `id=${projectId}`];
|
|
10466
10589
|
}
|
|
10590
|
+
function seededDocsIndex(seeds) {
|
|
10591
|
+
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));
|
|
10592
|
+
if (!docs2.length) return null;
|
|
10593
|
+
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));
|
|
10594
|
+
return renderDocsIndex(entries);
|
|
10595
|
+
}
|
|
10596
|
+
function linkedProjectsQueryArgs(owner, name) {
|
|
10597
|
+
const query = "query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { projectsV2(first: 10) { nodes { id number title } } } }";
|
|
10598
|
+
return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
|
|
10599
|
+
}
|
|
10600
|
+
function pickLinkedProject(json) {
|
|
10601
|
+
const nodes = json?.data?.repository?.projectsV2?.nodes;
|
|
10602
|
+
if (!Array.isArray(nodes)) return void 0;
|
|
10603
|
+
const usable = nodes.filter(
|
|
10604
|
+
(n) => typeof n?.id === "string" && Boolean(n.id)
|
|
10605
|
+
);
|
|
10606
|
+
if (usable.length !== 1) return void 0;
|
|
10607
|
+
const only = usable[0];
|
|
10608
|
+
return typeof only.number === "number" && Number.isFinite(only.number) ? { id: only.id, number: only.number } : { id: only.id };
|
|
10609
|
+
}
|
|
10467
10610
|
function decideSeedPrAction(openPrs) {
|
|
10468
10611
|
const list = Array.isArray(openPrs) ? openPrs : [];
|
|
10469
10612
|
for (const pr2 of list) {
|
|
@@ -11112,12 +11255,12 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
11112
11255
|
}
|
|
11113
11256
|
|
|
11114
11257
|
// src/wave-status.ts
|
|
11115
|
-
var
|
|
11258
|
+
var import_node_fs17 = require("node:fs");
|
|
11116
11259
|
|
|
11117
11260
|
// src/stage-runner.ts
|
|
11118
11261
|
var import_node_child_process7 = require("node:child_process");
|
|
11119
|
-
var
|
|
11120
|
-
var
|
|
11262
|
+
var import_node_fs16 = require("node:fs");
|
|
11263
|
+
var import_node_path14 = require("node:path");
|
|
11121
11264
|
var import_node_net = require("node:net");
|
|
11122
11265
|
var import_node_util6 = require("node:util");
|
|
11123
11266
|
|
|
@@ -11256,11 +11399,11 @@ function appendForceRecreate(up) {
|
|
|
11256
11399
|
return `${up.trimEnd()} --force-recreate`;
|
|
11257
11400
|
}
|
|
11258
11401
|
function stageStatePath(cwd = process.cwd()) {
|
|
11259
|
-
return (0,
|
|
11402
|
+
return (0, import_node_path14.join)(cwd, "tmp", "stage", "state.json");
|
|
11260
11403
|
}
|
|
11261
11404
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
11262
|
-
const dir = (0,
|
|
11263
|
-
return (0,
|
|
11405
|
+
const dir = (0, import_node_path14.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path14.resolve)(cwd, gitCommonDir);
|
|
11406
|
+
return (0, import_node_path14.join)(dir, "mmi", "stage", "state.json");
|
|
11264
11407
|
}
|
|
11265
11408
|
function normPath2(path2) {
|
|
11266
11409
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -11484,14 +11627,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
11484
11627
|
}
|
|
11485
11628
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
11486
11629
|
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,
|
|
11630
|
+
const target = (0, import_node_path14.join)(cwd, config.ensureEnv.target);
|
|
11631
|
+
const example = (0, import_node_path14.join)(cwd, config.ensureEnv.example);
|
|
11632
|
+
if (!(0, import_node_fs16.existsSync)(target) && (0, import_node_fs16.existsSync)(example)) {
|
|
11633
|
+
(0, import_node_fs16.copyFileSync)(example, target);
|
|
11634
|
+
} else if ((0, import_node_fs16.existsSync)(target) && (0, import_node_fs16.existsSync)(example)) {
|
|
11635
|
+
const stale = detectStaleEnvFile((0, import_node_fs16.readFileSync)(example, "utf8"), (0, import_node_fs16.readFileSync)(target, "utf8"), {
|
|
11636
|
+
exampleMtimeMs: (0, import_node_fs16.statSync)(example).mtimeMs,
|
|
11637
|
+
targetMtimeMs: (0, import_node_fs16.statSync)(target).mtimeMs
|
|
11495
11638
|
});
|
|
11496
11639
|
if (stale) {
|
|
11497
11640
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -11499,8 +11642,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
11499
11642
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
11500
11643
|
}
|
|
11501
11644
|
}
|
|
11502
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
11503
|
-
(0,
|
|
11645
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs16.existsSync)(target)) {
|
|
11646
|
+
(0, import_node_fs16.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs16.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
11504
11647
|
}
|
|
11505
11648
|
}
|
|
11506
11649
|
async function gitText(cwd, args) {
|
|
@@ -11530,20 +11673,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
11530
11673
|
return void 0;
|
|
11531
11674
|
}
|
|
11532
11675
|
function readState(path2) {
|
|
11533
|
-
if (!(0,
|
|
11676
|
+
if (!(0, import_node_fs16.existsSync)(path2)) return null;
|
|
11534
11677
|
try {
|
|
11535
|
-
return JSON.parse((0,
|
|
11678
|
+
return JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8"));
|
|
11536
11679
|
} catch {
|
|
11537
11680
|
return null;
|
|
11538
11681
|
}
|
|
11539
11682
|
}
|
|
11540
11683
|
function mkdirFor(path2) {
|
|
11541
11684
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
11542
|
-
(0,
|
|
11685
|
+
(0, import_node_fs16.mkdirSync)(dir, { recursive: true });
|
|
11543
11686
|
}
|
|
11544
11687
|
function writeState(path2, state) {
|
|
11545
11688
|
mkdirFor(path2);
|
|
11546
|
-
(0,
|
|
11689
|
+
(0, import_node_fs16.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
11547
11690
|
}
|
|
11548
11691
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
11549
11692
|
const reservation = {
|
|
@@ -11563,7 +11706,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
11563
11706
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
|
|
11564
11707
|
}
|
|
11565
11708
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
11566
|
-
(0,
|
|
11709
|
+
(0, import_node_fs16.rmSync)(path2, { force: true });
|
|
11567
11710
|
}
|
|
11568
11711
|
}
|
|
11569
11712
|
async function killTree(pid) {
|
|
@@ -11721,8 +11864,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
11721
11864
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
11722
11865
|
if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
|
|
11723
11866
|
} catch (e) {
|
|
11724
|
-
(0,
|
|
11725
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
11867
|
+
(0, import_node_fs16.rmSync)(statePath, { force: true });
|
|
11868
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs16.rmSync)(globalStatePath, { force: true });
|
|
11726
11869
|
throw e;
|
|
11727
11870
|
}
|
|
11728
11871
|
const started = await startStage(config, {
|
|
@@ -11743,9 +11886,9 @@ function parseNextFromHead(headText) {
|
|
|
11743
11886
|
}
|
|
11744
11887
|
function readStageSummary(worktreePath) {
|
|
11745
11888
|
const statePath = stageStatePath(worktreePath);
|
|
11746
|
-
if (!(0,
|
|
11889
|
+
if (!(0, import_node_fs17.existsSync)(statePath)) return void 0;
|
|
11747
11890
|
try {
|
|
11748
|
-
const state = JSON.parse((0,
|
|
11891
|
+
const state = JSON.parse((0, import_node_fs17.readFileSync)(statePath, "utf8"));
|
|
11749
11892
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
11750
11893
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
11751
11894
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -11864,7 +12007,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
11864
12007
|
// src/gh-create.ts
|
|
11865
12008
|
var import_promises = require("node:fs/promises");
|
|
11866
12009
|
var import_node_os3 = require("node:os");
|
|
11867
|
-
var
|
|
12010
|
+
var import_node_path15 = require("node:path");
|
|
11868
12011
|
var import_node_crypto3 = require("node:crypto");
|
|
11869
12012
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
11870
12013
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -11918,8 +12061,8 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11918
12061
|
const remove2 = deps.remove ?? import_promises.unlink;
|
|
11919
12062
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
11920
12063
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
11921
|
-
const file = (0,
|
|
11922
|
-
await ensureDir((0,
|
|
12064
|
+
const file = (0, import_node_path15.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
12065
|
+
await ensureDir((0, import_node_path15.dirname)(file), { recursive: true }).catch(() => {
|
|
11923
12066
|
});
|
|
11924
12067
|
await write(file, args[i + 1], "utf8");
|
|
11925
12068
|
return {
|
|
@@ -13194,8 +13337,8 @@ function parseVerifyBroker(stdout) {
|
|
|
13194
13337
|
}
|
|
13195
13338
|
|
|
13196
13339
|
// src/train-apply.ts
|
|
13197
|
-
var
|
|
13198
|
-
var
|
|
13340
|
+
var import_node_fs18 = require("node:fs");
|
|
13341
|
+
var import_node_path16 = require("node:path");
|
|
13199
13342
|
function resolveDeployModel2(meta, repo) {
|
|
13200
13343
|
const m = meta?.deployModel;
|
|
13201
13344
|
if (isDeployModel(m)) return m;
|
|
@@ -14103,17 +14246,17 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
14103
14246
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
14104
14247
|
}
|
|
14105
14248
|
function readLocalGateWorkflows() {
|
|
14106
|
-
const dir = (0,
|
|
14249
|
+
const dir = (0, import_node_path16.join)(".github", "workflows");
|
|
14107
14250
|
let names;
|
|
14108
14251
|
try {
|
|
14109
|
-
names = (0,
|
|
14252
|
+
names = (0, import_node_fs18.readdirSync)(dir);
|
|
14110
14253
|
} catch {
|
|
14111
14254
|
return null;
|
|
14112
14255
|
}
|
|
14113
14256
|
const files = [];
|
|
14114
14257
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
14115
14258
|
try {
|
|
14116
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
14259
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(dir, name), "utf8") });
|
|
14117
14260
|
} catch {
|
|
14118
14261
|
}
|
|
14119
14262
|
}
|
|
@@ -16009,112 +16152,6 @@ function renderAccessReport(report) {
|
|
|
16009
16152
|
return lines.join("\n");
|
|
16010
16153
|
}
|
|
16011
16154
|
|
|
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
16155
|
// src/doc-refs-core.ts
|
|
16119
16156
|
var import_node_child_process9 = require("node:child_process");
|
|
16120
16157
|
var import_node_fs19 = require("node:fs");
|
|
@@ -19345,7 +19382,7 @@ var import_node_fs22 = require("node:fs");
|
|
|
19345
19382
|
|
|
19346
19383
|
// src/bootstrap-verify.ts
|
|
19347
19384
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
19348
|
-
var requiredDocs = ["README.md", "architecture.md", "docs/decisions/README.md"];
|
|
19385
|
+
var requiredDocs = ["README.md", "architecture.md", "docs/decisions/README.md", "docs/index.md"];
|
|
19349
19386
|
var requiredIssueTemplates = [
|
|
19350
19387
|
".github/ISSUE_TEMPLATE/bug.yml",
|
|
19351
19388
|
".github/ISSUE_TEMPLATE/feature.yml",
|
|
@@ -19502,6 +19539,11 @@ function unfilledDocPlaceholders(text) {
|
|
|
19502
19539
|
for (const prompt of DOC_PLACEHOLDER_PROMPTS) if (text.includes(prompt)) found.add(prompt);
|
|
19503
19540
|
return [...found];
|
|
19504
19541
|
}
|
|
19542
|
+
function filledDocCheck(label, text, path2) {
|
|
19543
|
+
if (text === null) return { ok: false, label, detail: `${path2} not readable via API` };
|
|
19544
|
+
const unfilled = unfilledDocPlaceholders(text);
|
|
19545
|
+
return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
|
|
19546
|
+
}
|
|
19505
19547
|
async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
19506
19548
|
const branchesWanted = expectedBranches(repoClass, releaseTrack);
|
|
19507
19549
|
const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
|
|
@@ -19575,19 +19617,9 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
19575
19617
|
label: "README has Agent context section",
|
|
19576
19618
|
detail: readme === null ? "README.md not readable via API" : void 0
|
|
19577
19619
|
});
|
|
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
|
-
});
|
|
19620
|
+
checks.push(filledDocCheck("README Agent context is filled (no template placeholders)", readme, "README.md"));
|
|
19584
19621
|
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
|
-
});
|
|
19622
|
+
checks.push(filledDocCheck("architecture.md is filled (no template placeholders)", architecture, "architecture.md"));
|
|
19591
19623
|
const labels = await restPagedJson2(deps, `repos/${repo}/labels`, []);
|
|
19592
19624
|
const labelNames = new Set(labels.map((l) => l.name));
|
|
19593
19625
|
for (const label of requiredLabels) {
|
|
@@ -19910,7 +19942,11 @@ function registerBootstrapCommands(program3) {
|
|
|
19910
19942
|
return null;
|
|
19911
19943
|
}
|
|
19912
19944
|
}
|
|
19913
|
-
|
|
19945
|
+
// #3537: verify runs BEFORE registration by construction (skill Step 0a), so `meta` is null on every
|
|
19946
|
+
// fresh repo and `resolveReleaseTrack` falls through to `full` — which then outranks the operator's
|
|
19947
|
+
// own `--class content` inside expectedBranches, and the report demands a development and an rc the
|
|
19948
|
+
// track forbids. The declared class fills that gap only when the registry states nothing itself.
|
|
19949
|
+
}, resolveReleaseTrack(metaWithDeclaredClass(meta, o.class), void 0, repo));
|
|
19914
19950
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderBootstrapVerifyReport(report));
|
|
19915
19951
|
if (!report.ok) process.exitCode = 1;
|
|
19916
19952
|
});
|
|
@@ -19958,6 +19994,17 @@ function registerBootstrapCommands(program3) {
|
|
|
19958
19994
|
} catch {
|
|
19959
19995
|
}
|
|
19960
19996
|
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack);
|
|
19997
|
+
if (!vars.PROJECT_ID) {
|
|
19998
|
+
try {
|
|
19999
|
+
const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
|
|
20000
|
+
const linked = pickLinkedProject(JSON.parse(r.stdout));
|
|
20001
|
+
if (linked) {
|
|
20002
|
+
vars.PROJECT_ID = linked.id;
|
|
20003
|
+
if (linked.number != null && vars.PROJECT_NUMBER == null) vars.PROJECT_NUMBER = String(linked.number);
|
|
20004
|
+
}
|
|
20005
|
+
} catch {
|
|
20006
|
+
}
|
|
20007
|
+
}
|
|
19961
20008
|
if (vars.PROJECT_ID) {
|
|
19962
20009
|
try {
|
|
19963
20010
|
const r = await gh(boardFieldsQueryArgs(vars.PROJECT_ID));
|
|
@@ -19999,6 +20046,7 @@ function registerBootstrapCommands(program3) {
|
|
|
19999
20046
|
}
|
|
20000
20047
|
}
|
|
20001
20048
|
}
|
|
20049
|
+
const docsForIndex = [];
|
|
20002
20050
|
for (const seed of manifest.seeds) {
|
|
20003
20051
|
if (!seed.classes.includes(o.class)) continue;
|
|
20004
20052
|
if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
|
|
@@ -20026,12 +20074,38 @@ function registerBootstrapCommands(program3) {
|
|
|
20026
20074
|
const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile7) : null;
|
|
20027
20075
|
const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
|
|
20028
20076
|
actions.push(action);
|
|
20077
|
+
const docBody = content ?? remoteContent;
|
|
20078
|
+
if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
|
|
20079
|
+
docsForIndex.push({ path: resolved.target, content: docBody });
|
|
20080
|
+
}
|
|
20029
20081
|
if (o.execute && (action.action === "create" || action.action === "update")) {
|
|
20030
20082
|
await gh(contentPutArgs(repo, resolved.target, content, seedPlan.ref, action.action === "update" ? sha : void 0));
|
|
20031
20083
|
applied.push(`${action.action} ${resolved.target}`);
|
|
20032
20084
|
if (seedPlan.mode === "pr") seededToBranch++;
|
|
20033
20085
|
}
|
|
20034
20086
|
}
|
|
20087
|
+
const indexContent = seededDocsIndex(docsForIndex);
|
|
20088
|
+
if (indexContent) {
|
|
20089
|
+
let indexCurrent = null;
|
|
20090
|
+
try {
|
|
20091
|
+
const r = await gh(["api", `repos/${repo}/contents/${enc(DOCS_INDEX_PATH)}?ref=${seedPlan.ref}`]);
|
|
20092
|
+
const parsed = JSON.parse(r.stdout);
|
|
20093
|
+
indexCurrent = parsed.encoding === "base64" && typeof parsed.content === "string" ? Buffer.from(parsed.content, "base64").toString("utf8") : "";
|
|
20094
|
+
} catch {
|
|
20095
|
+
}
|
|
20096
|
+
const indexAction = indexCurrent === null ? "create" : "skip";
|
|
20097
|
+
actions.push({
|
|
20098
|
+
target: DOCS_INDEX_PATH,
|
|
20099
|
+
action: indexAction,
|
|
20100
|
+
ownership: "org",
|
|
20101
|
+
reason: indexCurrent === null ? "generated routing index (#3545)" : "routing index present \u2014 regenerate with `mmi-cli docs index --write`, which sees the whole tree"
|
|
20102
|
+
});
|
|
20103
|
+
if (o.execute && indexAction === "create") {
|
|
20104
|
+
await gh(contentPutArgs(repo, DOCS_INDEX_PATH, indexContent, seedPlan.ref, void 0));
|
|
20105
|
+
applied.push(`${indexAction} ${DOCS_INDEX_PATH}`);
|
|
20106
|
+
if (seedPlan.mode === "pr") seededToBranch++;
|
|
20107
|
+
}
|
|
20108
|
+
}
|
|
20035
20109
|
let seedPrUrl;
|
|
20036
20110
|
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && seededToBranch > 0) {
|
|
20037
20111
|
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-f", "delete_branch_on_merge=true"]).catch(() => {
|
|
@@ -26323,11 +26397,18 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
26323
26397
|
const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
|
|
26324
26398
|
const viewed = (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS2 })).stdout.trim();
|
|
26325
26399
|
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
26400
|
const repo = repoOpt ?? repoFromGh;
|
|
26330
26401
|
if (!repo) throw new Error("pr land: could not resolve PR repo");
|
|
26402
|
+
let track;
|
|
26403
|
+
try {
|
|
26404
|
+
const meta = await fetchProjectBySlug(slugOf(repo), registryClientDeps(await loadConfig()));
|
|
26405
|
+
if (meta) track = resolveReleaseTrack(meta, void 0, repo);
|
|
26406
|
+
} catch {
|
|
26407
|
+
}
|
|
26408
|
+
if (isPromotionBase(base, track)) {
|
|
26409
|
+
const shape = track ? `${track} track` : "unresolved track \u2014 strict reading";
|
|
26410
|
+
throw new Error(`pr land: base branch ${base} is a promotion target (${shape}) \u2014 promotion merges stay human-only`);
|
|
26411
|
+
}
|
|
26331
26412
|
return repo;
|
|
26332
26413
|
},
|
|
26333
26414
|
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.53.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",
|