@mutmutco/cli 3.113.0 → 3.115.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 +434 -265
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5001,7 +5001,7 @@ var program = new Command();
|
|
|
5001
5001
|
// src/index.ts
|
|
5002
5002
|
var import_promises11 = require("node:fs/promises");
|
|
5003
5003
|
var import_node_fs46 = require("node:fs");
|
|
5004
|
-
var
|
|
5004
|
+
var import_node_child_process20 = require("node:child_process");
|
|
5005
5005
|
|
|
5006
5006
|
// src/cli-shared.ts
|
|
5007
5007
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -7602,26 +7602,32 @@ function branchForTrackingRef(ref, remote) {
|
|
|
7602
7602
|
function sameWorktreeMetadataPath(a, b) {
|
|
7603
7603
|
return a.replace(/\\/g, "/").replace(/\/+$/, "") === b.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7604
7604
|
}
|
|
7605
|
-
function resolveSafeSiblingWorktreeCleanupTarget(worktreePath,
|
|
7606
|
-
let rootReal;
|
|
7605
|
+
function resolveSafeSiblingWorktreeCleanupTarget(worktreePath, siblingRoots, deps) {
|
|
7607
7606
|
let worktreeReal;
|
|
7608
|
-
try {
|
|
7609
|
-
rootReal = deps.realpath(siblingRoot);
|
|
7610
|
-
} catch {
|
|
7611
|
-
return { ok: false, reason: "sibling root could not be resolved" };
|
|
7612
|
-
}
|
|
7613
|
-
if (!isPathUnderDirectory2(rootReal, siblingRoot) || !isPathUnderDirectory2(siblingRoot, rootReal)) {
|
|
7614
|
-
return { ok: false, reason: "sibling root resolves outside expected path" };
|
|
7615
|
-
}
|
|
7616
7607
|
try {
|
|
7617
7608
|
worktreeReal = deps.realpath(worktreePath);
|
|
7618
7609
|
} catch {
|
|
7619
7610
|
return { ok: false, reason: "worktree path could not be resolved" };
|
|
7620
7611
|
}
|
|
7621
|
-
|
|
7622
|
-
|
|
7612
|
+
const reasons = [];
|
|
7613
|
+
for (const siblingRoot of siblingRoots) {
|
|
7614
|
+
let rootReal;
|
|
7615
|
+
try {
|
|
7616
|
+
rootReal = deps.realpath(siblingRoot);
|
|
7617
|
+
} catch {
|
|
7618
|
+
reasons.push(`${siblingRoot}: root could not be resolved`);
|
|
7619
|
+
continue;
|
|
7620
|
+
}
|
|
7621
|
+
if (!isPathUnderDirectory2(rootReal, siblingRoot) || !isPathUnderDirectory2(siblingRoot, rootReal)) {
|
|
7622
|
+
reasons.push(`${siblingRoot}: root resolves outside expected path`);
|
|
7623
|
+
continue;
|
|
7624
|
+
}
|
|
7625
|
+
if (isPathUnderDirectory2(worktreeReal, rootReal)) {
|
|
7626
|
+
return { ok: true, path: worktreePath };
|
|
7627
|
+
}
|
|
7628
|
+
reasons.push(`${siblingRoot}: resolved worktree path outside this root`);
|
|
7623
7629
|
}
|
|
7624
|
-
return { ok:
|
|
7630
|
+
return { ok: false, reason: reasons.join("; ") || "no worktrees root to check against" };
|
|
7625
7631
|
}
|
|
7626
7632
|
function siblingMmiWorktreesRoot(repoRoot2) {
|
|
7627
7633
|
const parent = (0, import_node_path9.dirname)(repoRoot2);
|
|
@@ -7630,6 +7636,9 @@ function siblingMmiWorktreesRoot(repoRoot2) {
|
|
|
7630
7636
|
if ((0, import_node_path9.basename)(grandparent).toLowerCase() === "mmi-worktrees") return grandparent;
|
|
7631
7637
|
return (0, import_node_path9.join)(parent, "mmi-worktrees");
|
|
7632
7638
|
}
|
|
7639
|
+
function agentWorktreesRoot(repoRoot2) {
|
|
7640
|
+
return (0, import_node_path9.join)(repoRoot2, ".claude", "worktrees");
|
|
7641
|
+
}
|
|
7633
7642
|
function worktreeScanDirs(root, repoRoot2, listDirs, isRepoCheckout) {
|
|
7634
7643
|
const projectsDir = (0, import_node_path9.dirname)(root);
|
|
7635
7644
|
const ownName = (0, import_node_path9.basename)(repoRoot2).toLowerCase();
|
|
@@ -8820,6 +8829,12 @@ var AUTO_UPDATE_TOGGLE_STEPS = "`/plugin` \u2192 Marketplaces tab \u2192 select
|
|
|
8820
8829
|
var CATALOG_CONTENT_REF = "main";
|
|
8821
8830
|
var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
|
|
8822
8831
|
var PIN_HEAL_COMMAND = "run `mmi-cli doctor`";
|
|
8832
|
+
var HOST_RUNNING_REMEDY = "quit Claude Code, then run `mmi-cli doctor`";
|
|
8833
|
+
function marketplaceRemedyText(remedy, action, manualSteps) {
|
|
8834
|
+
if (remedy === "manual") return manualSteps;
|
|
8835
|
+
if (remedy === "host-running") return `${HOST_RUNNING_REMEDY} to ${action}`;
|
|
8836
|
+
return `${PIN_HEAL_COMMAND} to ${action}`;
|
|
8837
|
+
}
|
|
8823
8838
|
var ORG_MARKETPLACE_PINS = { autoUpdate: true, ref: CATALOG_CONTENT_REF };
|
|
8824
8839
|
function resolveCatalogRef(probe) {
|
|
8825
8840
|
const { name, registered, ref, autoUpdate } = probe;
|
|
@@ -8843,7 +8858,7 @@ function resolveCatalogRef(probe) {
|
|
|
8843
8858
|
reason: `no ref pinned \u2014 the catalog is served from the repo's default branch while plugin content is pinned to ${CATALOG_CONTENT_REF}`
|
|
8844
8859
|
};
|
|
8845
8860
|
}
|
|
8846
|
-
function checkMarketplaceAutoUpdate(probe,
|
|
8861
|
+
function checkMarketplaceAutoUpdate(probe, remedy = "manual") {
|
|
8847
8862
|
if (!probe) return null;
|
|
8848
8863
|
const evidence = [`${probe.name}: auto-update ${probe.effective ? "on" : "off"} \u2014 ${probe.reason}`];
|
|
8849
8864
|
if (probe.effective) {
|
|
@@ -8854,11 +8869,11 @@ function checkMarketplaceAutoUpdate(probe, healable = false) {
|
|
|
8854
8869
|
ok: true,
|
|
8855
8870
|
warn: true,
|
|
8856
8871
|
label: `marketplace auto-update (${probe.name})`,
|
|
8857
|
-
detail: `off \u2014 this machine will not pick up a new plugin release on its own; ${
|
|
8872
|
+
detail: `off \u2014 this machine will not pick up a new plugin release on its own; ${marketplaceRemedyText(remedy, "turn it on", `turn it on with ${AUTO_UPDATE_TOGGLE_STEPS}`)}`,
|
|
8858
8873
|
verbose: evidence
|
|
8859
8874
|
};
|
|
8860
8875
|
}
|
|
8861
|
-
function checkMarketplaceCatalogRef(probe,
|
|
8876
|
+
function checkMarketplaceCatalogRef(probe, remedy = "manual") {
|
|
8862
8877
|
if (!probe) return null;
|
|
8863
8878
|
const label = `marketplace catalog ref (${probe.name})`;
|
|
8864
8879
|
const evidence = [`${probe.name}: ${probe.reason}`, `auto-update: ${probe.autoUpdate ? "on" : "off"}`];
|
|
@@ -8875,7 +8890,7 @@ function checkMarketplaceCatalogRef(probe, healable = false) {
|
|
|
8875
8890
|
// ✗ branch carried it in `fix` only because `renderCheckLine` discarded `detail` on failure — one
|
|
8876
8891
|
// row, two conventions, decided by which field would actually print.
|
|
8877
8892
|
detail: `${drift} while auto-update is ON`,
|
|
8878
|
-
fix: `this machine installs plugin releases advertised by a branch nobody released; ${
|
|
8893
|
+
fix: `this machine installs plugin releases advertised by a branch nobody released; ${marketplaceRemedyText(remedy, "pin it", CATALOG_REF_PIN_STEPS)}`,
|
|
8879
8894
|
verbose: evidence
|
|
8880
8895
|
};
|
|
8881
8896
|
}
|
|
@@ -8884,11 +8899,11 @@ function checkMarketplaceCatalogRef(probe, healable = false) {
|
|
|
8884
8899
|
ok: true,
|
|
8885
8900
|
warn: true,
|
|
8886
8901
|
label,
|
|
8887
|
-
detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${
|
|
8902
|
+
detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${marketplaceRemedyText(remedy, "pin it", CATALOG_REF_PIN_STEPS)}`,
|
|
8888
8903
|
verbose: evidence
|
|
8889
8904
|
};
|
|
8890
8905
|
}
|
|
8891
|
-
function marketplaceRows(name, known, settings,
|
|
8906
|
+
function marketplaceRows(name, known, settings, remedy = "manual") {
|
|
8892
8907
|
const { registered, declared, ref } = readKnownMarketplace(known, name);
|
|
8893
8908
|
const autoUpdate = resolveAutoUpdate({
|
|
8894
8909
|
name,
|
|
@@ -8896,9 +8911,9 @@ function marketplaceRows(name, known, settings, healable = false) {
|
|
|
8896
8911
|
declared,
|
|
8897
8912
|
settingsDeclared: readSettingsAutoUpdate(settings, name)
|
|
8898
8913
|
});
|
|
8899
|
-
const rows = [checkMarketplaceAutoUpdate(autoUpdate,
|
|
8914
|
+
const rows = [checkMarketplaceAutoUpdate(autoUpdate, remedy), checkMarketplaceCatalogRef(
|
|
8900
8915
|
resolveCatalogRef({ name, registered, ref, autoUpdate: autoUpdate.effective }),
|
|
8901
|
-
|
|
8916
|
+
remedy
|
|
8902
8917
|
)];
|
|
8903
8918
|
return rows.filter((r) => r !== null);
|
|
8904
8919
|
}
|
|
@@ -11210,11 +11225,114 @@ var import_node_path19 = require("node:path");
|
|
|
11210
11225
|
|
|
11211
11226
|
// src/plugin-guard-io.ts
|
|
11212
11227
|
var import_node_fs17 = require("node:fs");
|
|
11213
|
-
var
|
|
11228
|
+
var import_node_child_process7 = require("node:child_process");
|
|
11214
11229
|
var import_node_path16 = require("node:path");
|
|
11215
11230
|
var import_node_os5 = require("node:os");
|
|
11216
11231
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
11217
11232
|
|
|
11233
|
+
// src/npm-self-update.ts
|
|
11234
|
+
var import_node_child_process6 = require("node:child_process");
|
|
11235
|
+
var import_node_readline = require("node:readline");
|
|
11236
|
+
function npmSelfUpdateArgs(spec) {
|
|
11237
|
+
return [
|
|
11238
|
+
"install",
|
|
11239
|
+
"-g",
|
|
11240
|
+
spec,
|
|
11241
|
+
"--no-audit",
|
|
11242
|
+
"--no-fund",
|
|
11243
|
+
"--progress=false",
|
|
11244
|
+
"--loglevel=http",
|
|
11245
|
+
"--prefer-offline"
|
|
11246
|
+
];
|
|
11247
|
+
}
|
|
11248
|
+
function packageFromFetchLine(line) {
|
|
11249
|
+
const m = line.match(
|
|
11250
|
+
/GET\s+(?:\d+\s+)?https?:\/\/\S*?\/((?:@[^/\s]+(?:\/|%2[fF]))?[^/\s]+)\/-\/[^/\s]*?-(\d+\.\d+\.\d+[^/\s]*?)\.tgz/u
|
|
11251
|
+
);
|
|
11252
|
+
if (!m) return void 0;
|
|
11253
|
+
return `${m[1].replace(/%2[fF]/u, "/")}@${m[2]}`;
|
|
11254
|
+
}
|
|
11255
|
+
function narrateNpmLine(line) {
|
|
11256
|
+
const text = line.trim();
|
|
11257
|
+
if (!text) return void 0;
|
|
11258
|
+
if (/^npm\s+warn\s+deprecated\b/iu.test(text)) return void 0;
|
|
11259
|
+
if (/added \d+ packages?|removed \d+ packages?|changed \d+ packages?|up to date/iu.test(text)) return text;
|
|
11260
|
+
const fetched = /\bhttp fetch\b/iu.test(text) ? packageFromFetchLine(text) : void 0;
|
|
11261
|
+
if (fetched) return `downloading ${fetched}`;
|
|
11262
|
+
if (/^npm\s+(warn|error)\b/iu.test(text)) return text;
|
|
11263
|
+
if (/\b(idealTree|resolveWithNewModule|placeDep)\b/u.test(text)) return "resolving dependencies";
|
|
11264
|
+
if (/\bhttp fetch\b/iu.test(text)) return "resolving dependencies";
|
|
11265
|
+
if (/\b(reify|extract|ADDITION|CHANGE)\b/u.test(text)) return "installing packages";
|
|
11266
|
+
if (/\baudit\b/iu.test(text)) return "finishing";
|
|
11267
|
+
return void 0;
|
|
11268
|
+
}
|
|
11269
|
+
var NPM_LOG_POINTER = /^npm\s+(error|ERR!)\s+A complete log of this run can be found in/iu;
|
|
11270
|
+
function npmFailureTail(output, maxLines = 6, maxChars = 500) {
|
|
11271
|
+
const lines = output.split(/\r?\n/u).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0).filter((l) => !NPM_LOG_POINTER.test(l.trim())).filter((l) => !/^npm\s+notice\b/iu.test(l.trim()));
|
|
11272
|
+
const tail = lines.slice(-maxLines).join(" \xB7 ");
|
|
11273
|
+
if (!tail) return output.trim().replace(/\s+/gu, " ").slice(-maxChars) || "npm produced no output";
|
|
11274
|
+
return tail.length > maxChars ? `\u2026${tail.slice(-maxChars)}` : tail;
|
|
11275
|
+
}
|
|
11276
|
+
async function runNpmStreaming(opts) {
|
|
11277
|
+
const spawnFn = opts.spawnFn ?? import_node_child_process6.spawn;
|
|
11278
|
+
return await new Promise((resolveRun) => {
|
|
11279
|
+
const chunks = [];
|
|
11280
|
+
let settled = false;
|
|
11281
|
+
let child2;
|
|
11282
|
+
const finish = (result) => {
|
|
11283
|
+
if (settled) return;
|
|
11284
|
+
settled = true;
|
|
11285
|
+
clearTimeout(timer);
|
|
11286
|
+
resolveRun(result);
|
|
11287
|
+
};
|
|
11288
|
+
const timer = setTimeout(() => {
|
|
11289
|
+
child2?.kill("SIGTERM");
|
|
11290
|
+
chunks.push(`npm install timed out after ${opts.timeoutMs}ms`);
|
|
11291
|
+
finish({ ok: false, code: null, output: chunks.join("\n") });
|
|
11292
|
+
}, opts.timeoutMs);
|
|
11293
|
+
try {
|
|
11294
|
+
child2 = spawnFn(opts.file, [...opts.args], {
|
|
11295
|
+
env: opts.env ?? process.env,
|
|
11296
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11297
|
+
shell: false,
|
|
11298
|
+
windowsHide: true
|
|
11299
|
+
});
|
|
11300
|
+
} catch (e) {
|
|
11301
|
+
chunks.push(e instanceof Error ? e.message : String(e));
|
|
11302
|
+
finish({ ok: false, code: null, output: chunks.join("\n") });
|
|
11303
|
+
return;
|
|
11304
|
+
}
|
|
11305
|
+
const onLine = (line) => {
|
|
11306
|
+
chunks.push(line);
|
|
11307
|
+
opts.onLine?.(line);
|
|
11308
|
+
};
|
|
11309
|
+
if (child2.stdout) (0, import_node_readline.createInterface)({ input: child2.stdout }).on("line", onLine);
|
|
11310
|
+
if (child2.stderr) (0, import_node_readline.createInterface)({ input: child2.stderr }).on("line", onLine);
|
|
11311
|
+
child2.on("error", (e) => {
|
|
11312
|
+
chunks.push(e.message);
|
|
11313
|
+
finish({ ok: false, code: null, output: chunks.join("\n") });
|
|
11314
|
+
});
|
|
11315
|
+
child2.on("close", (code) => finish({ ok: code === 0, code, output: chunks.join("\n") }));
|
|
11316
|
+
});
|
|
11317
|
+
}
|
|
11318
|
+
async function verifyInstalledCli(probe, target) {
|
|
11319
|
+
let stdout;
|
|
11320
|
+
try {
|
|
11321
|
+
stdout = await probe();
|
|
11322
|
+
} catch (e) {
|
|
11323
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
11324
|
+
return { ok: false, detail: `the installed mmi-cli does not run (${message.trim().split(/\r?\n/u)[0]})` };
|
|
11325
|
+
}
|
|
11326
|
+
const version = /(\d+\.\d+\.\d+[^\s]*)/u.exec(stdout.trim())?.[1];
|
|
11327
|
+
if (!version) {
|
|
11328
|
+
return { ok: false, detail: `the installed mmi-cli printed no version (${stdout.trim().slice(0, 80) || "no output"})` };
|
|
11329
|
+
}
|
|
11330
|
+
if (target && version !== target) {
|
|
11331
|
+
return { ok: false, detail: `the installed mmi-cli reports ${version}, not the ${target} that was just installed` };
|
|
11332
|
+
}
|
|
11333
|
+
return { ok: true, detail: `verified: the installed mmi-cli runs ${version}` };
|
|
11334
|
+
}
|
|
11335
|
+
|
|
11218
11336
|
// src/plugin-guard.ts
|
|
11219
11337
|
function buildPluginGuardDecision(i) {
|
|
11220
11338
|
if (!i.isOrgRepo) return { state: "not-org" };
|
|
@@ -11448,12 +11566,12 @@ function marketplaceClonePresent(surface, home, exists = import_node_fs17.exists
|
|
|
11448
11566
|
return marketplaceCloneCandidates(surface, home, env).some(exists);
|
|
11449
11567
|
}
|
|
11450
11568
|
function runHostBinSync(bin, args) {
|
|
11451
|
-
return isWin ? (0,
|
|
11569
|
+
return isWin ? (0, import_node_child_process7.execFileSync)("cmd.exe", ["/c", bin, ...args], {
|
|
11452
11570
|
encoding: "utf8",
|
|
11453
11571
|
stdio: ["ignore", "pipe", "ignore"],
|
|
11454
11572
|
timeout: 15e3,
|
|
11455
11573
|
windowsHide: true
|
|
11456
|
-
}) : (0,
|
|
11574
|
+
}) : (0, import_node_child_process7.execFileSync)(bin, args, {
|
|
11457
11575
|
encoding: "utf8",
|
|
11458
11576
|
stdio: ["ignore", "pipe", "ignore"],
|
|
11459
11577
|
timeout: 15e3,
|
|
@@ -11521,15 +11639,28 @@ async function fetchNpmReleasedVersion() {
|
|
|
11521
11639
|
}
|
|
11522
11640
|
}
|
|
11523
11641
|
var NPM_INSTALL_TIMEOUT_MS = 12e4;
|
|
11524
|
-
|
|
11642
|
+
var CLI_VERSION_PROBE_TIMEOUT_MS = 3e4;
|
|
11643
|
+
async function npmSelfUpdateCli(target, onStep, deps = {}) {
|
|
11525
11644
|
const command = cliUpdateCommand(target);
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11645
|
+
const args = npmSelfUpdateArgs(`@mutmutco/cli@${target ?? "latest"}`);
|
|
11646
|
+
onStep?.(command);
|
|
11647
|
+
let last = "";
|
|
11648
|
+
const result = await (deps.run ?? runNpmStreaming)({
|
|
11649
|
+
file: isWin ? "cmd.exe" : "npm",
|
|
11650
|
+
args: isWin ? ["/c", "npm", ...args] : args,
|
|
11651
|
+
timeoutMs: NPM_INSTALL_TIMEOUT_MS,
|
|
11652
|
+
onLine: (line) => {
|
|
11653
|
+
const message = narrateNpmLine(line);
|
|
11654
|
+
if (!message || message === last) return;
|
|
11655
|
+
last = message;
|
|
11656
|
+
onStep?.(message);
|
|
11657
|
+
}
|
|
11658
|
+
});
|
|
11659
|
+
if (!result.ok) return { ok: false, detail: npmFailureTail(result.output) };
|
|
11660
|
+
const probe = deps.probeVersion ?? (async () => (await runHostBin("mmi-cli", ["--version"], { timeout: CLI_VERSION_PROBE_TIMEOUT_MS })).stdout);
|
|
11661
|
+
const verified = await verifyInstalledCli(probe, target);
|
|
11662
|
+
onStep?.(verified.detail);
|
|
11663
|
+
return verified.ok ? { ok: true, detail: `${command} exited 0 \u2014 ${verified.detail}` } : { ok: false, detail: `${command} exited 0 but ${verified.detail}` };
|
|
11533
11664
|
}
|
|
11534
11665
|
function kiloConfigListsPlugin(configRoot, home = (0, import_node_os5.homedir)(), read = (p) => (0, import_node_fs17.readFileSync)(p, "utf8"), exists = import_node_fs17.existsSync) {
|
|
11535
11666
|
const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
|
|
@@ -12016,8 +12147,11 @@ function readKnownMarketplacesFile(path2) {
|
|
|
12016
12147
|
return void 0;
|
|
12017
12148
|
}
|
|
12018
12149
|
}
|
|
12150
|
+
function claudeCodeEnvMarkerPresent(env = process.env) {
|
|
12151
|
+
return Boolean(env.CLAUDE_CODE_SESSION_ID?.trim() || env.CLAUDECODE?.trim() || env.CLAUDE_PLUGIN_ROOT?.trim());
|
|
12152
|
+
}
|
|
12019
12153
|
function claudeCodeIsRunning(env = process.env, listProcesses = defaultProcessList) {
|
|
12020
|
-
if (
|
|
12154
|
+
if (claudeCodeEnvMarkerPresent(env)) return true;
|
|
12021
12155
|
let table;
|
|
12022
12156
|
try {
|
|
12023
12157
|
table = listProcesses();
|
|
@@ -12028,7 +12162,7 @@ function claudeCodeIsRunning(env = process.env, listProcesses = defaultProcessLi
|
|
|
12028
12162
|
return table.split(/\r?\n/).some((line) => /(^|[/\\])claude(\.(exe|cmd|ps1))?\s/.test(`${line.trim()} `));
|
|
12029
12163
|
}
|
|
12030
12164
|
function defaultProcessList() {
|
|
12031
|
-
return isWin ? (0,
|
|
12165
|
+
return isWin ? (0, import_node_child_process7.execFileSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, maxBuffer: 32 * 1024 * 1024, timeout: 15e3 }) : (0, import_node_child_process7.execFileSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
12032
12166
|
}
|
|
12033
12167
|
function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive) {
|
|
12034
12168
|
if (pins.size === 0) return void 0;
|
|
@@ -12059,6 +12193,7 @@ function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsR
|
|
|
12059
12193
|
}
|
|
12060
12194
|
function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning) {
|
|
12061
12195
|
let landed = false;
|
|
12196
|
+
let blockedByHost = false;
|
|
12062
12197
|
const detail = writeMarketplacePinsOnDisk(
|
|
12063
12198
|
path2,
|
|
12064
12199
|
new Map(names.map((name) => [name, ORG_MARKETPLACE_PINS])),
|
|
@@ -12067,9 +12202,13 @@ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunni
|
|
|
12067
12202
|
return `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`;
|
|
12068
12203
|
},
|
|
12069
12204
|
"pin",
|
|
12070
|
-
() =>
|
|
12205
|
+
() => {
|
|
12206
|
+
if (!hostIsRunning()) return void 0;
|
|
12207
|
+
blockedByHost = true;
|
|
12208
|
+
return "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor`";
|
|
12209
|
+
}
|
|
12071
12210
|
);
|
|
12072
|
-
return detail === void 0 ? void 0 : { detail, wrote: landed };
|
|
12211
|
+
return detail === void 0 ? void 0 : { detail, wrote: landed, blockedByHost };
|
|
12073
12212
|
}
|
|
12074
12213
|
function writeMarketplacePinPending(path2, names, now = Date.now()) {
|
|
12075
12214
|
try {
|
|
@@ -12777,7 +12916,8 @@ function workflowEntry(repo, workflowPath, yamlText) {
|
|
|
12777
12916
|
executor: "github-actions",
|
|
12778
12917
|
llm: llmFromHeader(yamlText),
|
|
12779
12918
|
resolved: "live",
|
|
12780
|
-
source: `${ORG}/${repo}:${workflowPath}
|
|
12919
|
+
source: `${ORG}/${repo}:${workflowPath}`,
|
|
12920
|
+
harbourUnmanaged: isHarbourUnmanaged(yamlText)
|
|
12781
12921
|
};
|
|
12782
12922
|
}
|
|
12783
12923
|
function awsRuleEntries(payload) {
|
|
@@ -12980,12 +13120,13 @@ function cadenceStale(registryCadence, liveCadence) {
|
|
|
12980
13120
|
return !liveCrons.every((cron) => registered.has(cron));
|
|
12981
13121
|
}
|
|
12982
13122
|
function reconcileGithubActions(liveGithub, registry2, readRepos, activeWorkflowNames = /* @__PURE__ */ new Set(), disabledWorkflowNames = /* @__PURE__ */ new Set()) {
|
|
13123
|
+
const managedLiveGithub = liveGithub.filter((e) => !e.harbourUnmanaged);
|
|
12983
13124
|
const registryGithub = registry2.filter((r) => r.executor === "github-actions");
|
|
12984
|
-
const liveByName = new Map(
|
|
13125
|
+
const liveByName = new Map(managedLiveGithub.map((e) => [e.name, e]));
|
|
12985
13126
|
const registryById = new Map(registryGithub.map((r) => [r.id, r]));
|
|
12986
13127
|
const drifts = [];
|
|
12987
13128
|
const parked = [];
|
|
12988
|
-
for (const e of
|
|
13129
|
+
for (const e of managedLiveGithub) {
|
|
12989
13130
|
if (!registryById.has(e.name)) {
|
|
12990
13131
|
drifts.push({
|
|
12991
13132
|
class: "live-but-unregistered",
|
|
@@ -13063,9 +13204,11 @@ function strayCronDrift(name) {
|
|
|
13063
13204
|
remedy: "strip on: schedule \u2192 workflow_dispatch and let the harbour dispatcher fire on the registry cadence (docs/schedules.md rule 1)"
|
|
13064
13205
|
};
|
|
13065
13206
|
}
|
|
13066
|
-
|
|
13207
|
+
function isHarbourUnmanaged(yamlText) {
|
|
13208
|
+
return /^#\s*harbour:\s*unmanaged\s*$/im.test(yamlText);
|
|
13209
|
+
}
|
|
13067
13210
|
function strayCronDrifts(workflows) {
|
|
13068
|
-
return workflows.filter((w) => !
|
|
13211
|
+
return workflows.filter((w) => !isHarbourUnmanaged(w.yamlText)).filter((w) => workflowTriggersSchedule(w.yamlText)).map((w) => strayCronDrift(w.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
13069
13212
|
}
|
|
13070
13213
|
function unlauncheredLlmDrift(entry) {
|
|
13071
13214
|
if (entry.llm !== "yes") return null;
|
|
@@ -13180,7 +13323,7 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
13180
13323
|
if (!crons.length && !header.schedule) return null;
|
|
13181
13324
|
const basename6 = workflowPath.split("/").pop() ?? workflowPath;
|
|
13182
13325
|
const expectedId = `${repo}/${basename6.replace(/\.ya?ml$/, "")}`;
|
|
13183
|
-
if (
|
|
13326
|
+
if (isHarbourUnmanaged(yamlText)) return null;
|
|
13184
13327
|
const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
|
|
13185
13328
|
if (missing.length) {
|
|
13186
13329
|
throw new Error(`schedules register: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
|
|
@@ -13545,7 +13688,7 @@ ${lines.join("\n")}`;
|
|
|
13545
13688
|
}
|
|
13546
13689
|
|
|
13547
13690
|
// src/secrets.ts
|
|
13548
|
-
var
|
|
13691
|
+
var import_node_child_process8 = require("node:child_process");
|
|
13549
13692
|
var import_node_os7 = require("node:os");
|
|
13550
13693
|
|
|
13551
13694
|
// src/gh-create.ts
|
|
@@ -14817,7 +14960,7 @@ function spawnExitCode(status, signal) {
|
|
|
14817
14960
|
}
|
|
14818
14961
|
function defaultSpawn(command, args, env) {
|
|
14819
14962
|
const target = resolveSpawnTarget(command, args);
|
|
14820
|
-
const r = (0,
|
|
14963
|
+
const r = (0, import_node_child_process8.spawnSync)(target.cmd, target.args, {
|
|
14821
14964
|
windowsHide: true,
|
|
14822
14965
|
stdio: "inherit",
|
|
14823
14966
|
env,
|
|
@@ -15285,7 +15428,8 @@ async function siblingWorktreeDirs(explicitRoot) {
|
|
|
15285
15428
|
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path18.dirname)((0, import_node_path18.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
15286
15429
|
try {
|
|
15287
15430
|
const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
|
|
15288
|
-
|
|
15431
|
+
const agentDirs2 = listDirsIn(agentWorktreesRoot(primaryRepoRoot));
|
|
15432
|
+
return [...dirs, ...agentDirs2].map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
15289
15433
|
} catch {
|
|
15290
15434
|
return [];
|
|
15291
15435
|
}
|
|
@@ -15635,10 +15779,10 @@ var rollout_plan_default = {
|
|
|
15635
15779
|
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)."
|
|
15636
15780
|
},
|
|
15637
15781
|
baseline: {
|
|
15638
|
-
version: "3.
|
|
15639
|
-
tag: "v3.
|
|
15640
|
-
commit: "
|
|
15641
|
-
npm: "@mutmutco/cli@3.
|
|
15782
|
+
version: "3.115.0",
|
|
15783
|
+
tag: "v3.115.0",
|
|
15784
|
+
commit: "955ec7ae88f1",
|
|
15785
|
+
npm: "@mutmutco/cli@3.115.0"
|
|
15642
15786
|
},
|
|
15643
15787
|
exitCriterion: "fleet-n-of-n",
|
|
15644
15788
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15655,14 +15799,14 @@ var rollout_plan_default = {
|
|
|
15655
15799
|
repo: "mutmutco/mmi-hub",
|
|
15656
15800
|
role: "canary",
|
|
15657
15801
|
schedule: "train",
|
|
15658
|
-
v3Target: "v3.
|
|
15802
|
+
v3Target: "v3.115.0"
|
|
15659
15803
|
}
|
|
15660
15804
|
],
|
|
15661
15805
|
rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
|
|
15662
15806
|
rollback: {
|
|
15663
15807
|
independent: true,
|
|
15664
|
-
mechanism: "npm dist-tag latest -> 3.
|
|
15665
|
-
v3Target: "v3.
|
|
15808
|
+
mechanism: "npm dist-tag latest -> 3.115.0 and redeploy the Hub Lambda from tag v3.115.0 (955ec7ae88f1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15809
|
+
v3Target: "v3.115.0 (@mutmutco/cli@3.115.0, tag commit 955ec7ae88f1 \u2014 the preserved latest-v3 distribution, D6b)"
|
|
15666
15810
|
}
|
|
15667
15811
|
},
|
|
15668
15812
|
{
|
|
@@ -21316,7 +21460,7 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
21316
21460
|
var import_node_fs22 = require("node:fs");
|
|
21317
21461
|
|
|
21318
21462
|
// src/stage-runner.ts
|
|
21319
|
-
var
|
|
21463
|
+
var import_node_child_process9 = require("node:child_process");
|
|
21320
21464
|
var import_node_fs21 = require("node:fs");
|
|
21321
21465
|
var import_node_path20 = require("node:path");
|
|
21322
21466
|
var import_node_net = require("node:net");
|
|
@@ -21328,7 +21472,7 @@ function normalizeEol(s) {
|
|
|
21328
21472
|
}
|
|
21329
21473
|
|
|
21330
21474
|
// src/stage-runner.ts
|
|
21331
|
-
var execFileP3 = (0, import_node_util5.promisify)(
|
|
21475
|
+
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process9.execFile);
|
|
21332
21476
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
21333
21477
|
var EARLY_EXIT_GRACE_MS = 2e3;
|
|
21334
21478
|
function earlyExitGraceMs() {
|
|
@@ -21850,7 +21994,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
21850
21994
|
let up = sub(config.up.trim());
|
|
21851
21995
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
21852
21996
|
const identity = await resolveStageIdentity(cwd);
|
|
21853
|
-
const child2 = (0,
|
|
21997
|
+
const child2 = (0, import_node_child_process9.spawn)(up, {
|
|
21854
21998
|
cwd,
|
|
21855
21999
|
shell: true,
|
|
21856
22000
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
@@ -22054,7 +22198,7 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
22054
22198
|
var import_node_os19 = require("node:os");
|
|
22055
22199
|
|
|
22056
22200
|
// src/board.ts
|
|
22057
|
-
var
|
|
22201
|
+
var import_node_child_process10 = require("node:child_process");
|
|
22058
22202
|
var import_node_fs23 = require("node:fs");
|
|
22059
22203
|
var import_node_os8 = require("node:os");
|
|
22060
22204
|
var import_node_path21 = require("node:path");
|
|
@@ -22399,7 +22543,7 @@ function findDuplicateLesson(source, openLessons) {
|
|
|
22399
22543
|
var BOARD_STATUSES = ["Todo", "In Progress", "In Review", "Done"];
|
|
22400
22544
|
|
|
22401
22545
|
// src/board.ts
|
|
22402
|
-
var rawExecFileP3 = (0, import_node_util6.promisify)(
|
|
22546
|
+
var rawExecFileP3 = (0, import_node_util6.promisify)(import_node_child_process10.execFile);
|
|
22403
22547
|
var BOARD_GIT_TIMEOUT_MS = 1e4;
|
|
22404
22548
|
var WRITE_PROBE_CONCURRENCY = 8;
|
|
22405
22549
|
var CLAIM_CONCURRENCY = 5;
|
|
@@ -25452,10 +25596,10 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
25452
25596
|
}
|
|
25453
25597
|
|
|
25454
25598
|
// src/statusline-invalidate.ts
|
|
25455
|
-
var
|
|
25599
|
+
var import_node_child_process11 = require("node:child_process");
|
|
25456
25600
|
function invalidateStatuslineBoardCache() {
|
|
25457
25601
|
try {
|
|
25458
|
-
const child2 = (0,
|
|
25602
|
+
const child2 = (0, import_node_child_process11.spawn)("jerv-cli", ["lane", "ops", "invalidate-board"], {
|
|
25459
25603
|
detached: true,
|
|
25460
25604
|
stdio: "ignore",
|
|
25461
25605
|
windowsHide: true
|
|
@@ -25674,12 +25818,12 @@ function renderVerifyBroker(input) {
|
|
|
25674
25818
|
}
|
|
25675
25819
|
|
|
25676
25820
|
// src/hotfix-coverage.ts
|
|
25677
|
-
var
|
|
25821
|
+
var import_node_child_process12 = require("node:child_process");
|
|
25678
25822
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
25679
25823
|
function checkHotfixCoverage(options = {}) {
|
|
25680
25824
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
25681
25825
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
25682
|
-
const git2 = options.git ?? ((args, opts) => (0,
|
|
25826
|
+
const git2 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
25683
25827
|
const revList = (range) => {
|
|
25684
25828
|
const out = git2(["rev-list", "--no-merges", range]).trim();
|
|
25685
25829
|
return out ? out.split("\n") : [];
|
|
@@ -25747,7 +25891,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
25747
25891
|
}
|
|
25748
25892
|
function checkHotfixCarries(options) {
|
|
25749
25893
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
25750
|
-
const git2 = options.git ?? ((args, opts) => (0,
|
|
25894
|
+
const git2 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
25751
25895
|
const isAncestor = (sha, ref) => {
|
|
25752
25896
|
try {
|
|
25753
25897
|
git2(["merge-base", "--is-ancestor", sha, ref]);
|
|
@@ -26771,7 +26915,7 @@ async function announceRelease(deps, args) {
|
|
|
26771
26915
|
|
|
26772
26916
|
// src/repo-index.ts
|
|
26773
26917
|
var import_node_crypto4 = require("node:crypto");
|
|
26774
|
-
var
|
|
26918
|
+
var import_node_child_process13 = require("node:child_process");
|
|
26775
26919
|
var import_node_fs27 = require("node:fs");
|
|
26776
26920
|
var import_node_path25 = require("node:path");
|
|
26777
26921
|
var REPO_INDEX_SCHEMA = 1;
|
|
@@ -26917,7 +27061,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
26917
27061
|
function toPosix(p) {
|
|
26918
27062
|
return p.split(import_node_path25.sep).join("/");
|
|
26919
27063
|
}
|
|
26920
|
-
function listCandidatePaths(cwd, exec =
|
|
27064
|
+
function listCandidatePaths(cwd, exec = import_node_child_process13.execFileSync) {
|
|
26921
27065
|
try {
|
|
26922
27066
|
const out = exec("git", ["ls-files", "-z", "-c", "-o", "--exclude-standard"], {
|
|
26923
27067
|
cwd,
|
|
@@ -27040,7 +27184,7 @@ function searchRepoIndex(idx, query, limit = 20) {
|
|
|
27040
27184
|
}
|
|
27041
27185
|
return out;
|
|
27042
27186
|
}
|
|
27043
|
-
function inferRepoSlug(cwd, exec =
|
|
27187
|
+
function inferRepoSlug(cwd, exec = import_node_child_process13.execFileSync) {
|
|
27044
27188
|
try {
|
|
27045
27189
|
const url = String(exec("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" })).trim();
|
|
27046
27190
|
const m = /[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(url);
|
|
@@ -27190,7 +27334,7 @@ async function gcRepoIndexCloud(deps) {
|
|
|
27190
27334
|
var import_node_fs28 = require("node:fs");
|
|
27191
27335
|
var import_node_os12 = require("node:os");
|
|
27192
27336
|
var import_node_path26 = require("node:path");
|
|
27193
|
-
var
|
|
27337
|
+
var import_node_child_process14 = require("node:child_process");
|
|
27194
27338
|
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
27195
27339
|
function normalizeRepo(raw) {
|
|
27196
27340
|
const t = raw.trim().replace(/\.git$/, "");
|
|
@@ -27210,7 +27354,7 @@ function rosterRepos(projects) {
|
|
|
27210
27354
|
}
|
|
27211
27355
|
function shallowClone(repo, dest, token) {
|
|
27212
27356
|
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
27213
|
-
(0,
|
|
27357
|
+
(0, import_node_child_process14.execFileSync)(
|
|
27214
27358
|
"git",
|
|
27215
27359
|
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "clone", "--depth", "1", "--single-branch", `https://github.com/${repo}.git`, dest],
|
|
27216
27360
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
@@ -27479,7 +27623,7 @@ async function runRepoIndexHealth(opts) {
|
|
|
27479
27623
|
}
|
|
27480
27624
|
|
|
27481
27625
|
// src/spawn-policy-core.ts
|
|
27482
|
-
var
|
|
27626
|
+
var import_node_child_process15 = require("node:child_process");
|
|
27483
27627
|
var import_node_fs30 = require("node:fs");
|
|
27484
27628
|
var import_node_path27 = require("node:path");
|
|
27485
27629
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
@@ -27550,7 +27694,7 @@ function findViolationsInSource(raw) {
|
|
|
27550
27694
|
return found;
|
|
27551
27695
|
}
|
|
27552
27696
|
function policedFiles(root) {
|
|
27553
|
-
const r = (0,
|
|
27697
|
+
const r = (0, import_node_child_process15.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
27554
27698
|
cwd: root,
|
|
27555
27699
|
encoding: "utf8",
|
|
27556
27700
|
windowsHide: true,
|
|
@@ -27584,7 +27728,7 @@ function runSpawnPolicy(root) {
|
|
|
27584
27728
|
}
|
|
27585
27729
|
|
|
27586
27730
|
// src/test-policy-core.ts
|
|
27587
|
-
var
|
|
27731
|
+
var import_node_child_process16 = require("node:child_process");
|
|
27588
27732
|
var import_node_fs31 = require("node:fs");
|
|
27589
27733
|
var import_node_path28 = require("node:path");
|
|
27590
27734
|
var POLICY_FILE = "test-policy.json";
|
|
@@ -27714,14 +27858,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
27714
27858
|
return findings;
|
|
27715
27859
|
}
|
|
27716
27860
|
function git(args, cwd) {
|
|
27717
|
-
return (0,
|
|
27861
|
+
return (0, import_node_child_process16.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
27718
27862
|
}
|
|
27719
27863
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
27720
27864
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
27721
27865
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
27722
27866
|
function parseTrailers(message, cwd) {
|
|
27723
27867
|
try {
|
|
27724
|
-
return (0,
|
|
27868
|
+
return (0, import_node_child_process16.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
27725
27869
|
windowsHide: true,
|
|
27726
27870
|
cwd,
|
|
27727
27871
|
input: message,
|
|
@@ -29522,9 +29666,9 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
29522
29666
|
|
|
29523
29667
|
// src/schedules-commands.ts
|
|
29524
29668
|
var import_promises5 = require("node:fs/promises");
|
|
29525
|
-
var
|
|
29669
|
+
var import_node_child_process17 = require("node:child_process");
|
|
29526
29670
|
var import_node_util7 = require("node:util");
|
|
29527
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
29671
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process17.execFile);
|
|
29528
29672
|
var AWS_REGION = "eu-central-1";
|
|
29529
29673
|
var AWS_TIMEOUT_MS = 3e4;
|
|
29530
29674
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -32403,7 +32547,7 @@ var import_node_fs38 = require("node:fs");
|
|
|
32403
32547
|
var import_promises8 = require("node:fs/promises");
|
|
32404
32548
|
var import_node_path36 = require("node:path");
|
|
32405
32549
|
var import_node_os15 = require("node:os");
|
|
32406
|
-
var
|
|
32550
|
+
var import_node_child_process18 = require("node:child_process");
|
|
32407
32551
|
|
|
32408
32552
|
// src/board-advance.ts
|
|
32409
32553
|
function repoOf2(ref) {
|
|
@@ -32802,12 +32946,15 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
32802
32946
|
result.removedTrackingRefs.push(...branchTracking.removedTrackingRefs);
|
|
32803
32947
|
result.failed.push(...branchTracking.failed);
|
|
32804
32948
|
const removeDeps = worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
32805
|
-
const
|
|
32949
|
+
const cleanupRoots = [
|
|
32950
|
+
opts.root ? resolveExplicitScanRoot(opts.root, primaryRepoRoot) : siblingMmiWorktreesRoot(primaryRepoRoot),
|
|
32951
|
+
agentWorktreesRoot(primaryRepoRoot)
|
|
32952
|
+
];
|
|
32806
32953
|
for (const wt of worktreeDirsToRemove) {
|
|
32807
32954
|
const owner = findWorktreeOwner(owners, wt.path);
|
|
32808
32955
|
let removalAttempted = false;
|
|
32809
32956
|
try {
|
|
32810
|
-
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path,
|
|
32957
|
+
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, cleanupRoots, {
|
|
32811
32958
|
realpath: (path2) => (0, import_node_fs38.realpathSync)(path2)
|
|
32812
32959
|
});
|
|
32813
32960
|
if (!cleanupTarget.ok) {
|
|
@@ -33009,7 +33156,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
33009
33156
|
}
|
|
33010
33157
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
33011
33158
|
function spawnDeferredGcSweep() {
|
|
33012
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
33159
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process18.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
33013
33160
|
}
|
|
33014
33161
|
async function createDeferredWorktreeStore() {
|
|
33015
33162
|
try {
|
|
@@ -36689,7 +36836,8 @@ var RESTART_LINE = "\u21BB Restart Claude to finish.";
|
|
|
36689
36836
|
var VERBOSE_INDENT = " ";
|
|
36690
36837
|
function checkGlyph(check) {
|
|
36691
36838
|
if (!check.ok) return "\u2717";
|
|
36692
|
-
|
|
36839
|
+
if (check.verified === false) return "?";
|
|
36840
|
+
return check.warn ? "\u26A0" : "\u2713";
|
|
36693
36841
|
}
|
|
36694
36842
|
function renderCheckLine(check) {
|
|
36695
36843
|
const head = check.detail ? `${checkGlyph(check)} ${check.label} \u2014 ${check.detail}` : `${checkGlyph(check)} ${check.label}`;
|
|
@@ -36707,9 +36855,11 @@ function renderTally(checks) {
|
|
|
36707
36855
|
const total = checks.length;
|
|
36708
36856
|
const failed = checks.filter((c) => !c.ok).length;
|
|
36709
36857
|
const unverified = checks.filter((c) => c.ok && c.verified === false).length;
|
|
36710
|
-
const
|
|
36711
|
-
|
|
36858
|
+
const warned = checks.filter((c) => c.ok && c.verified !== false && c.warn).length;
|
|
36859
|
+
const healthy = total - failed - unverified - warned;
|
|
36860
|
+
if (failed === 0 && unverified === 0 && warned === 0) return `\u2713 all ${total} checks healthy`;
|
|
36712
36861
|
const parts = [`\u2713 ${healthy} verified healthy`];
|
|
36862
|
+
if (warned > 0) parts.push(`\u26A0 ${warned} warn`);
|
|
36713
36863
|
if (unverified > 0) parts.push(`? ${unverified} unverified`);
|
|
36714
36864
|
if (failed > 0) parts.push(`\u2717 ${failed} failed`);
|
|
36715
36865
|
return `${parts.join(" \xB7 ")} \u2014 ${total} checks`;
|
|
@@ -38359,18 +38509,19 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
38359
38509
|
if (payload) emitNow(payload);
|
|
38360
38510
|
}
|
|
38361
38511
|
async function runMarketplaceRows() {
|
|
38512
|
+
let healed;
|
|
38362
38513
|
if (applyEnv) {
|
|
38363
|
-
|
|
38364
|
-
if (healed) {
|
|
38514
|
+
healed = deps.healMarketplacePins();
|
|
38515
|
+
if (healed?.wrote) {
|
|
38365
38516
|
healIntent(`marketplace pins \u2014 ${healed.detail}`);
|
|
38366
|
-
|
|
38367
|
-
|
|
38368
|
-
|
|
38369
|
-
restartPending = true;
|
|
38370
|
-
}
|
|
38517
|
+
traceHeal("env-heals");
|
|
38518
|
+
markHealChanged();
|
|
38519
|
+
restartPending = true;
|
|
38371
38520
|
}
|
|
38372
38521
|
}
|
|
38373
|
-
for (const row of deps.marketplaceRows())
|
|
38522
|
+
for (const row of deps.marketplaceRows(healed?.blockedByHost)) {
|
|
38523
|
+
emitNow(healed && !healed.wrote ? { ...row, verbose: [...row.verbose ?? [], `heal: ${healed.detail}`] } : row);
|
|
38524
|
+
}
|
|
38374
38525
|
}
|
|
38375
38526
|
async function runTrainSyncRow() {
|
|
38376
38527
|
try {
|
|
@@ -38903,9 +39054,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
38903
39054
|
var import_node_fs45 = require("node:fs");
|
|
38904
39055
|
var import_node_os18 = require("node:os");
|
|
38905
39056
|
var import_node_path42 = require("node:path");
|
|
38906
|
-
var
|
|
39057
|
+
var import_node_child_process19 = require("node:child_process");
|
|
38907
39058
|
var import_node_util8 = require("node:util");
|
|
38908
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
39059
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
|
|
38909
39060
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
38910
39061
|
function installedClaudePluginVersion() {
|
|
38911
39062
|
try {
|
|
@@ -38951,12 +39102,12 @@ function installedSurfacePluginVersion(surface) {
|
|
|
38951
39102
|
if (token === "claude") return installedClaudePluginVersion();
|
|
38952
39103
|
if (token !== "codex") return void 0;
|
|
38953
39104
|
try {
|
|
38954
|
-
const raw = process.platform === "win32" ? (0,
|
|
39105
|
+
const raw = process.platform === "win32" ? (0, import_node_child_process19.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
38955
39106
|
encoding: "utf8",
|
|
38956
39107
|
stdio: ["ignore", "pipe", "ignore"],
|
|
38957
39108
|
timeout: 15e3,
|
|
38958
39109
|
windowsHide: true
|
|
38959
|
-
}) : (0,
|
|
39110
|
+
}) : (0, import_node_child_process19.execFileSync)("codex", ["plugin", "list", "--json"], {
|
|
38960
39111
|
encoding: "utf8",
|
|
38961
39112
|
stdio: ["ignore", "pipe", "ignore"],
|
|
38962
39113
|
timeout: 15e3,
|
|
@@ -38974,7 +39125,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
38974
39125
|
}
|
|
38975
39126
|
function worktreeRootSync() {
|
|
38976
39127
|
try {
|
|
38977
|
-
const out = (0,
|
|
39128
|
+
const out = (0, import_node_child_process19.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
38978
39129
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
38979
39130
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
38980
39131
|
return root || null;
|
|
@@ -39057,9 +39208,9 @@ async function withEnvHealLock(what, run) {
|
|
|
39057
39208
|
);
|
|
39058
39209
|
} catch (e) {
|
|
39059
39210
|
if (e instanceof FileLockBusyError) {
|
|
39060
|
-
return { ok: false, skipped: true, detail: `${what} skipped
|
|
39211
|
+
return { ok: false, skipped: true, detail: `${what} skipped \u2014 ${e.message}` };
|
|
39061
39212
|
}
|
|
39062
|
-
return { ok: false, detail: `${what} could not take the env-heal lock
|
|
39213
|
+
return { ok: false, detail: `${what} could not take the env-heal lock \u2014 ${e.message}` };
|
|
39063
39214
|
}
|
|
39064
39215
|
}
|
|
39065
39216
|
function throttledReleasedVersion() {
|
|
@@ -39115,13 +39266,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39115
39266
|
pluginTrustState: () => codexHookTrustState(),
|
|
39116
39267
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
39117
39268
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
39118
|
-
// #3272: the --apply self-heal for a stale running CLI
|
|
39269
|
+
// #3272: the --apply self-heal for a stale running CLI — npm global, shadows any plugin shim (#2879).
|
|
39119
39270
|
// #3489: serialised. Both heals mutate machine-global state (the npm global prefix; the Claude
|
|
39120
39271
|
// marketplace clone + plugin cache) and neither is idempotent under concurrency. They share ONE lock
|
|
39121
39272
|
// rather than one each, because they are not independent: the plugin heal reinstalls a bundle that
|
|
39122
39273
|
// carries a CLI shim, so a concurrent npm global install races the same PATH surface.
|
|
39123
39274
|
updateCli: (target, onStep) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target, onStep)),
|
|
39124
|
-
// #3282: the --apply self-heal for a stale/unresolved Claude plugin
|
|
39275
|
+
// #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
|
|
39125
39276
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
39126
39277
|
healPlugin: (onStep) => {
|
|
39127
39278
|
const surface = detectSurface(process.env);
|
|
@@ -39139,7 +39290,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39139
39290
|
// #2759: same fetch+ff-only sync SessionStart runs, wired here too so an on-demand `mmi-cli doctor`
|
|
39140
39291
|
// self-heals a checkout a long-running session left stale.
|
|
39141
39292
|
syncTrain: () => syncLocalTrainBranches(execFileGitRun),
|
|
39142
|
-
// #2903: doctor DETECTS stale cached plugin versions and defers the delete to `plugin-prune`
|
|
39293
|
+
// #2903: doctor DETECTS stale cached plugin versions and defers the delete to `plugin-prune` — it never
|
|
39143
39294
|
// writes to the harness-owned cache itself. Same plan builder the verb uses, so the two never disagree.
|
|
39144
39295
|
// No `withBytes` here: doctor runs on EVERY SessionStart, and sizing means recursively stat'ing every
|
|
39145
39296
|
// stale version tree. The count comes from one cheap readdir; `plugin-prune` reports the MB.
|
|
@@ -39223,11 +39374,11 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39223
39374
|
// binary is exactly the half-applied state being repaired.
|
|
39224
39375
|
healClaudeBinary: (onStep) => withEnvHealLock("claude binary restore", async () => healClaudeBinary({}, onStep)),
|
|
39225
39376
|
// #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
|
|
39226
|
-
// A local record read
|
|
39377
|
+
// A local record read — cheap enough for every lane, including the banner.
|
|
39227
39378
|
sessionPayload: () => readSessionPayload(process.cwd()),
|
|
39228
|
-
// #3485 items 9 and 8: why the MMI plugin has never printed an "updated
|
|
39379
|
+
// #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
|
|
39229
39380
|
// which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
|
|
39230
|
-
marketplaceRows: () => {
|
|
39381
|
+
marketplaceRows: (hostBlocked) => {
|
|
39231
39382
|
try {
|
|
39232
39383
|
if (detectSurface(process.env) === "codex") return [];
|
|
39233
39384
|
const home = (0, import_node_os19.homedir)();
|
|
@@ -39236,8 +39387,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39236
39387
|
readFileSyncSafe((0, import_node_path43.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
|
|
39237
39388
|
readFileSyncSafe((0, import_node_path43.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync),
|
|
39238
39389
|
// #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
|
|
39239
|
-
// edit.
|
|
39240
|
-
|
|
39390
|
+
// edit. #4792 narrows WHICH run: `applyOrgMarketplacePins` declines outright while Claude Code
|
|
39391
|
+
// is up, because the host owns this file and rewrites it from its own copy (#4083). Naming the
|
|
39392
|
+
// doctor there is the circular-and-false remedy the issue was filed on — doctor ran twice and
|
|
39393
|
+
// healed neither row. So the live host, when there is one, is what the rows name first.
|
|
39394
|
+
// The heal lane pays for the full process probe and hands its answer down; every other lane
|
|
39395
|
+
// reads the free env markers, which are set precisely when doctor runs inside a Claude session
|
|
39396
|
+
// — the case the owner hit. A cheap lane never shells a process listing for this.
|
|
39397
|
+
hostBlocked ?? claudeCodeEnvMarkerPresent() ? "host-running" : "doctor"
|
|
39241
39398
|
);
|
|
39242
39399
|
const pending = readMarketplacePinPending(
|
|
39243
39400
|
(0, import_node_path43.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
|
|
@@ -39251,7 +39408,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39251
39408
|
...rest,
|
|
39252
39409
|
ok: true,
|
|
39253
39410
|
warn: true,
|
|
39254
|
-
detail: `pending restart
|
|
39411
|
+
detail: `pending restart \u2014 doctor pinned main at ${pending.at}; restart Claude Code to load it`,
|
|
39255
39412
|
verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
|
|
39256
39413
|
};
|
|
39257
39414
|
});
|
|
@@ -39276,13 +39433,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39276
39433
|
}
|
|
39277
39434
|
},
|
|
39278
39435
|
// #4091: the same `docs index --check` comparison the required CI gate runs, in-process. Rooted at the
|
|
39279
|
-
// repo root doctor already resolved
|
|
39436
|
+
// repo root doctor already resolved — never `process.cwd()`, which would have `mmi-cli doctor` run from
|
|
39280
39437
|
// `cli/` measure a `cli/docs/` tree that does not exist.
|
|
39281
39438
|
//
|
|
39282
39439
|
// `existsSync` on `docs/index.md` is the adoption gate, and it sits UNDER the table's org-repo gate
|
|
39283
39440
|
// rather than replacing it: a missing index is drift by construction, so without this a repo that never
|
|
39284
39441
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
39285
|
-
// get a permanent
|
|
39442
|
+
// get a permanent — demanding an artifact it never asked for.
|
|
39286
39443
|
docsIndexState: (root) => {
|
|
39287
39444
|
if (!(0, import_node_fs46.existsSync)((0, import_node_path43.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
39288
39445
|
const real = createDocsIndexDeps(root);
|
|
@@ -39370,7 +39527,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39370
39527
|
const names = new Set((opts.program?.commands ?? []).map((c) => c.name()));
|
|
39371
39528
|
return required.filter((n) => !names.has(n));
|
|
39372
39529
|
},
|
|
39373
|
-
// #4156: short-timeout Hub status for the current repo. Fail-soft
|
|
39530
|
+
// #4156: short-timeout Hub status for the current repo. Fail-soft — never throws to doctor.
|
|
39374
39531
|
repoIndexCloudState: async (root) => {
|
|
39375
39532
|
const local = repoIndexStatus(root);
|
|
39376
39533
|
try {
|
|
@@ -39430,7 +39587,7 @@ async function requireFreshTrainCli(commandName) {
|
|
|
39430
39587
|
const releasedVersion = await fetchNpmReleasedVersion();
|
|
39431
39588
|
if (!releasedVersion) {
|
|
39432
39589
|
throw new Error(
|
|
39433
|
-
`${commandName}: the train staleness gate could not read the released CLI version (\`npm view @mutmutco/cli version\` returned nothing
|
|
39590
|
+
`${commandName}: the train staleness gate could not read the released CLI version (\`npm view @mutmutco/cli version\` returned nothing \u2014 registry unreachable, npm missing, or output unparseable). That is a FAILED read, not "the running CLI is fresh", so this train command refuses rather than running unproven. This gate runs before any train mutation, so this invocation changed nothing. Retry once the registry answers, or set MMI_TRAIN_FRESH_OVERRIDE=1 to run this command with the staleness check deliberately skipped.`
|
|
39434
39591
|
);
|
|
39435
39592
|
}
|
|
39436
39593
|
const report = buildVersionLagReport({
|
|
@@ -39475,7 +39632,7 @@ function argvWantsJson2() {
|
|
|
39475
39632
|
var unknownFlagJsonHandled = false;
|
|
39476
39633
|
var PARSE_HINT_SENTINEL = "@@mmi-parse-hint@@";
|
|
39477
39634
|
var DISCOVERY_HINT = "run `mmi-cli commands` for the agentic-coding map, `mmi-cli explain <command>` for detail, or `mmi-cli commands --json --primary` for machine discovery";
|
|
39478
|
-
var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale
|
|
39635
|
+
var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale \u2014 update it (see: mmi-cli doctor)`;
|
|
39479
39636
|
var lastParseErrorKind = "other";
|
|
39480
39637
|
var lastUnknownCommand;
|
|
39481
39638
|
function classifyParseError(plain) {
|
|
@@ -39541,7 +39698,7 @@ function resolveParseHint() {
|
|
|
39541
39698
|
const path2 = commandPath2(cmd);
|
|
39542
39699
|
return [
|
|
39543
39700
|
`Usage: mmi-cli ${path2} ${cmd.usage()}`,
|
|
39544
|
-
"This command exists and your mmi-cli is not the problem
|
|
39701
|
+
"This command exists and your mmi-cli is not the problem \u2014 the ARGUMENTS did not parse.",
|
|
39545
39702
|
`Run \`mmi-cli ${path2} --help\` for its signature, \`mmi-cli explain ${path2}\` for detail, or \`mmi-cli commands\` for the full map.`
|
|
39546
39703
|
].join("\n");
|
|
39547
39704
|
}
|
|
@@ -39578,7 +39735,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
39578
39735
|
const positional = TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked, { flag, argv }) : void 0;
|
|
39579
39736
|
if (argvWantsJson2()) {
|
|
39580
39737
|
const suggestion = positional ? void 0 : didYouMean(flag, commandOwnLongFlags(invoked));
|
|
39581
|
-
const corrected = suggestion ? `mmi-cli ${argv.map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
|
|
39738
|
+
const corrected = suggestion ? `mmi-cli ${canonicalArgvFor(argv.map((a) => a === flag ? suggestion : a)).join(" ")}` : void 0;
|
|
39582
39739
|
const message = positional ? unknownTargetFlagMessage(flag, positional) : `unknown option '${flag}'`;
|
|
39583
39740
|
process.stderr.write(
|
|
39584
39741
|
formatErrorEnvelope(message, {
|
|
@@ -39602,7 +39759,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
39602
39759
|
process.stderr.write(str);
|
|
39603
39760
|
}
|
|
39604
39761
|
var program2 = new Command();
|
|
39605
|
-
program2.name("mmi-cli").description("MMI Future Hub CLI
|
|
39762
|
+
program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
|
|
39606
39763
|
program2.addHelpText(
|
|
39607
39764
|
"before",
|
|
39608
39765
|
`Houses (canonical roots): oracle \xB7 harbour \xB7 devops \xB7 vault \xB7 learning
|
|
@@ -39663,7 +39820,7 @@ rules.command("gitignore").option("--write", "upsert the managed block into .git
|
|
|
39663
39820
|
return;
|
|
39664
39821
|
}
|
|
39665
39822
|
if (plan.changed) {
|
|
39666
|
-
console.error(`mmi-cli devops org rules gitignore: managed block drift (${drift})
|
|
39823
|
+
console.error(`mmi-cli devops org rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli devops org rules gitignore --write\` and commit`);
|
|
39667
39824
|
process.exitCode = 1;
|
|
39668
39825
|
} else {
|
|
39669
39826
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -39769,7 +39926,7 @@ wave.command("land").description("serial merge open PRs to development with reba
|
|
|
39769
39926
|
});
|
|
39770
39927
|
var gcCmd = program2.command("gc").description("dry-run cleanup for merged/closed PR local+remote branches, linked worktrees, stale tracking refs, and worktree metadata (--scratch: safe local scratch + confirmed-synced plans)");
|
|
39771
39928
|
var DEFERRED_SWEEP_HARD_TIMEOUT_MS = 12e4;
|
|
39772
|
-
gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree removals with backoff (#1932)
|
|
39929
|
+
gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree removals with backoff (#1932) \u2014 SessionStart + pr land spawn this detached").option("--quiet", "silent when registry empty or all cleared").option("--json", "machine-readable output").action(async (o) => {
|
|
39773
39930
|
await runWithSweepWatchdog(async () => {
|
|
39774
39931
|
try {
|
|
39775
39932
|
const deferredStore = await createDeferredWorktreeStore();
|
|
@@ -39777,7 +39934,7 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
39777
39934
|
const result = await sweepDeferredWorktreesWithRetry(
|
|
39778
39935
|
deferredStore,
|
|
39779
39936
|
// #2841: `-c core.fsmonitor=false` stops a git fsmonitor daemon from inheriting this detached
|
|
39780
|
-
// worker's stdio pipe (the likely Windows hang
|
|
39937
|
+
// worker's stdio pipe (the likely Windows hang — execFile then never sees EOF and never resolves).
|
|
39781
39938
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
39782
39939
|
{ removalContext }
|
|
39783
39940
|
);
|
|
@@ -39793,11 +39950,11 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
39793
39950
|
fail(`worktree gc sweep-deferred: ${e.message}`);
|
|
39794
39951
|
}
|
|
39795
39952
|
}, DEFERRED_SWEEP_HARD_TIMEOUT_MS, () => {
|
|
39796
|
-
console.error("worktree gc sweep-deferred: exceeded hard timeout
|
|
39953
|
+
console.error("worktree gc sweep-deferred: exceeded hard timeout \u2014 exiting so the detached worker cannot leak (#2841)");
|
|
39797
39954
|
process.exit(process.exitCode ?? 0);
|
|
39798
39955
|
});
|
|
39799
39956
|
});
|
|
39800
|
-
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to read PER BRANCH
|
|
39957
|
+
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to read PER BRANCH \u2014 an effort bound, not a correctness one: merge state is resolved per branch, so no branch is ever skipped for being old (#4227)", "200").option("--force", "remove worktrees even when the ownership registry shows another session created or worked in them recently (#3580), or when a dead worktree directory still holds leftover files (#4779)").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471) \u2014 descends into this repo container when present; ownership, dead-dir, and content guards still apply").action(async (o) => {
|
|
39801
39958
|
if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
|
|
39802
39959
|
if (o.scratch) {
|
|
39803
39960
|
try {
|
|
@@ -39818,7 +39975,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
39818
39975
|
if (!(0, import_node_fs46.existsSync)(root) || !(0, import_node_fs46.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
39819
39976
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
39820
39977
|
if (isPathUnderDirectory2(gcRepoRoot, root)) {
|
|
39821
|
-
return fail(`worktree gc: --root ${root} contains this checkout
|
|
39978
|
+
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
39822
39979
|
}
|
|
39823
39980
|
}
|
|
39824
39981
|
try {
|
|
@@ -39832,7 +39989,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
39832
39989
|
const removalContext = await currentWorktreeRemovalContext("worktree gc", o.force);
|
|
39833
39990
|
const sweepResult = await sweepDeferredWorktrees(
|
|
39834
39991
|
deferredStore,
|
|
39835
|
-
// #2841: same `-c core.fsmonitor=false` guard as the detached `gc sweep-deferred` worker
|
|
39992
|
+
// #2841: same `-c core.fsmonitor=false` guard as the detached `gc sweep-deferred` worker — keep a
|
|
39836
39993
|
// git fsmonitor daemon from inheriting this sweep's stdio pipe and wedging the git call on Windows.
|
|
39837
39994
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
39838
39995
|
removalContext
|
|
@@ -39864,11 +40021,11 @@ function runWorktreeInstall(command, cwd, quiet, opts) {
|
|
|
39864
40021
|
"pipe"
|
|
39865
40022
|
];
|
|
39866
40023
|
return new Promise((resolve5, reject) => {
|
|
39867
|
-
const child2 = opts?.shell ? (0,
|
|
40024
|
+
const child2 = opts?.shell ? (0, import_node_child_process20.spawn)(command, { cwd, stdio, windowsHide: true, shell: true }) : (() => {
|
|
39868
40025
|
const [bin, ...args] = command.split(" ");
|
|
39869
40026
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
39870
40027
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
39871
|
-
return (0,
|
|
40028
|
+
return (0, import_node_child_process20.spawn)(file, spawnArgs, { cwd, stdio, windowsHide: true });
|
|
39872
40029
|
})();
|
|
39873
40030
|
let stderrTail = "";
|
|
39874
40031
|
child2.stderr?.on("data", (chunk) => {
|
|
@@ -39919,7 +40076,7 @@ async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
|
39919
40076
|
return `'git worktree list' could not be read from ${repoRoot2}, so ${wtPath} could not be proven registered`;
|
|
39920
40077
|
}
|
|
39921
40078
|
if (!registered.some((w) => samePath(w.path, wtPath))) {
|
|
39922
|
-
return `${wtPath} exists on disk but 'git worktree list' does not name it
|
|
40079
|
+
return `${wtPath} exists on disk but 'git worktree list' does not name it \u2014 its git metadata is gone, so it is not a usable worktree`;
|
|
39923
40080
|
}
|
|
39924
40081
|
return void 0;
|
|
39925
40082
|
}
|
|
@@ -39969,15 +40126,15 @@ function acquireWorktreeSetupLock(worktreeRoot) {
|
|
|
39969
40126
|
return null;
|
|
39970
40127
|
}
|
|
39971
40128
|
}
|
|
39972
|
-
var worktree = program2.command("worktree").description("self-provisioning worktrees
|
|
40129
|
+
var worktree = program2.command("worktree").description("self-provisioning worktrees \u2014 install deps + copy local-only config");
|
|
39973
40130
|
withExamples(mutating(
|
|
39974
|
-
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<RepoName>/<branch>
|
|
40131
|
+
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<RepoName>/<branch> \u2014 authoritative; the <RepoName> segment is what makes ownership provable from the path, #3471. gc/list read this root in BOTH the nested and the legacy flat shape)").option("--remote <name>", "remote to fetch when --from is a <remote>/<branch> ref", "origin").option("--slug <slug>", "issue-ref form: override the slug derived from the issue title").option("--claim", "issue-ref form: claim the issue's board item after provisioning").option("--for <login>", "with --claim: assign the board item to this login instead of @me"),
|
|
39975
40132
|
worktreeCreatePlan
|
|
39976
40133
|
).action(async (target, o, cmd) => {
|
|
39977
40134
|
let step = "resolve the branch name";
|
|
39978
40135
|
try {
|
|
39979
40136
|
if (o.base !== void 0 && cmd.getOptionValueSource("from") === "cli" && o.base !== o.from) {
|
|
39980
|
-
return fail(`worktree create: --from and --base are the same option (--base is an alias); got --from ${o.from} and --base ${o.base}
|
|
40137
|
+
return fail(`worktree create: --from and --base are the same option (--base is an alias); got --from ${o.from} and --base ${o.base} \u2014 pass one`);
|
|
39981
40138
|
}
|
|
39982
40139
|
const fromRef = o.base ?? o.from;
|
|
39983
40140
|
const issueForm = isIssueRef(target);
|
|
@@ -39995,7 +40152,7 @@ withExamples(mutating(
|
|
|
39995
40152
|
const slug = o.slug ?? await fetchIssueTitle(selector.repo, selector.number).then((t) => t ? slugifyIssueTitle(t) : "");
|
|
39996
40153
|
branch = buildNewBranchName(selector.number, slug ?? "");
|
|
39997
40154
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
39998
|
-
return fail(`worktree create: generated branch name '${branch}' collides with a protected train branch
|
|
40155
|
+
return fail(`worktree create: generated branch name '${branch}' collides with a protected train branch \u2014 pass a branch name instead`);
|
|
39999
40156
|
}
|
|
40000
40157
|
} else if (o.claim || o.for) {
|
|
40001
40158
|
return fail("worktree create: --claim/--for need an issue-ref argument (e.g. worktree create 2687 --claim)");
|
|
@@ -40035,7 +40192,7 @@ withExamples(mutating(
|
|
|
40035
40192
|
return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
|
|
40036
40193
|
}
|
|
40037
40194
|
const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
40038
|
-
if (status) return fail(`worktree create: refusing to resume ${wtPath}
|
|
40195
|
+
if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
|
|
40039
40196
|
const head = await revParseRef(`refs/heads/${branch}`);
|
|
40040
40197
|
const baseOid = await revParseRef(base);
|
|
40041
40198
|
if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
|
|
@@ -40046,7 +40203,7 @@ withExamples(mutating(
|
|
|
40046
40203
|
// io-census-allow: an unprovable ancestor probe conservatively refuses the resume below rather than fast-forwarding onto unproven history
|
|
40047
40204
|
).then(() => true).catch(() => false);
|
|
40048
40205
|
if (!canFastForward) {
|
|
40049
|
-
return fail(`worktree create: refusing to resume '${branch}'
|
|
40206
|
+
return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
|
|
40050
40207
|
}
|
|
40051
40208
|
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
40052
40209
|
resumed = true;
|
|
@@ -40148,11 +40305,11 @@ withExamples(mutating(
|
|
|
40148
40305
|
console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
|
|
40149
40306
|
console.log(` copied: ${report.copied.join(", ") || "none"}`);
|
|
40150
40307
|
if (issueForm && o.claim && selector) {
|
|
40151
|
-
if (claimError) console.error(` warning: board claim failed (${claimError})
|
|
40308
|
+
if (claimError) console.error(` warning: board claim failed (${claimError}) \u2014 worktree is ready, finish the claim manually`);
|
|
40152
40309
|
else console.log(` board item ${selector.repo}#${selector.number} claimed`);
|
|
40153
40310
|
}
|
|
40154
40311
|
} catch (e) {
|
|
40155
|
-
fail(`worktree create: failed at step '${step}'
|
|
40312
|
+
fail(`worktree create: failed at step '${step}' \u2014 ${e.message}`);
|
|
40156
40313
|
}
|
|
40157
40314
|
}), [
|
|
40158
40315
|
"mmi-cli worktree create fix/gc-sweep-2687",
|
|
@@ -40163,7 +40320,7 @@ worktree.command("setup [path]").description("provision an existing worktree (in
|
|
|
40163
40320
|
const root = path2 ?? process.cwd();
|
|
40164
40321
|
const release = acquireWorktreeSetupLock(root);
|
|
40165
40322
|
if (!release) {
|
|
40166
|
-
if (!o.quiet && !o.json) console.log("worktree setup: another provision is in progress
|
|
40323
|
+
if (!o.quiet && !o.json) console.log("worktree setup: another provision is in progress \u2014 skipping");
|
|
40167
40324
|
return;
|
|
40168
40325
|
}
|
|
40169
40326
|
try {
|
|
@@ -40183,7 +40340,7 @@ worktree.command("setup [path]").description("provision an existing worktree (in
|
|
|
40183
40340
|
release();
|
|
40184
40341
|
}
|
|
40185
40342
|
});
|
|
40186
|
-
worktree.command("events").description("who created and who removed this repo's worktrees
|
|
40343
|
+
worktree.command("events").description("who created and who removed this repo's worktrees \u2014 the append-only attribution log (#3580)").option("--limit <n>", "most recent events to show", "50").option("--json", "machine-readable output").action(async (o) => {
|
|
40187
40344
|
const limit = Number.parseInt(o.limit, 10);
|
|
40188
40345
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree events: --limit must be a positive integer");
|
|
40189
40346
|
const primaryRoot = await primaryCheckoutRoot(process.cwd()) ?? process.cwd();
|
|
@@ -40201,11 +40358,11 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
40201
40358
|
try {
|
|
40202
40359
|
cfg = await loadConfigForRepo(targetRepo2);
|
|
40203
40360
|
} catch (e) {
|
|
40204
|
-
console.error(`issue create: board attach skipped
|
|
40361
|
+
console.error(`issue create: board attach skipped \u2014 ${e.message}`);
|
|
40205
40362
|
return { onBoard: false };
|
|
40206
40363
|
}
|
|
40207
40364
|
if (!cfg.projectId) {
|
|
40208
|
-
console.error(`issue create: board attach skipped
|
|
40365
|
+
console.error(`issue create: board attach skipped \u2014 no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli oracle org project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
|
|
40209
40366
|
return { onBoard: false };
|
|
40210
40367
|
}
|
|
40211
40368
|
try {
|
|
@@ -40251,7 +40408,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
40251
40408
|
try {
|
|
40252
40409
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
40253
40410
|
if (o.repo) args.push("--repo", o.repo);
|
|
40254
|
-
spawnDetachedSelf(args, { spawn:
|
|
40411
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
40255
40412
|
} catch {
|
|
40256
40413
|
}
|
|
40257
40414
|
}
|
|
@@ -40260,7 +40417,7 @@ registerEdgeCommands(program2);
|
|
|
40260
40417
|
registerBoxCommands(program2);
|
|
40261
40418
|
registerSchedulesCommands(program2);
|
|
40262
40419
|
registerSchedulesLiftCommand(program2);
|
|
40263
|
-
var repoIndex = program2.command("repo-index").description("Hub cloud + local pointer index
|
|
40420
|
+
var repoIndex = program2.command("repo-index").description("Hub cloud + local pointer index \u2014 paths/symbols only, never wiki prose (Hub#4133)");
|
|
40264
40421
|
repoIndex.command("rebuild").description("walk the checkout (git ls-files + gitignore/secrets walls), rebuild the local pointer index, drop orphans").option("--json", "machine-readable projection summary").action(async (o) => {
|
|
40265
40422
|
try {
|
|
40266
40423
|
const root = await repoRoot();
|
|
@@ -40278,7 +40435,7 @@ repoIndex.command("rebuild").description("walk the checkout (git ls-files + giti
|
|
|
40278
40435
|
}, null, 2));
|
|
40279
40436
|
return;
|
|
40280
40437
|
}
|
|
40281
|
-
console.log(`repo-index: rebuilt ${built.repo}
|
|
40438
|
+
console.log(`repo-index: rebuilt ${built.repo} \u2014 ${built.entries.length} files, ${symbolCount} symbols`);
|
|
40282
40439
|
} catch (e) {
|
|
40283
40440
|
await failGraceful(e.message);
|
|
40284
40441
|
}
|
|
@@ -40302,7 +40459,7 @@ repoIndex.command("publish").description("publish this checkout's projection to
|
|
|
40302
40459
|
const embRequested = Number(res.body.embRequested ?? 0);
|
|
40303
40460
|
const embCap = Number(res.body.embCap ?? 0);
|
|
40304
40461
|
const capNote = o.embed && embRequested > embCap && embCap > 0 ? ` (emb=${embCount} of ${embRequested} requested \u2014 capped at ${embCap}/publish, run \`--embed\` again to continue)` : "";
|
|
40305
|
-
console.log(`repo-index: published ${res.body.repo}
|
|
40462
|
+
console.log(`repo-index: published ${res.body.repo} \u2014 ${res.body.fileCount} files, emb=${embCount}${capNote}`);
|
|
40306
40463
|
} catch (e) {
|
|
40307
40464
|
return await failGraceful(e.message);
|
|
40308
40465
|
}
|
|
@@ -40378,7 +40535,7 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
|
|
|
40378
40535
|
const embGap = Number(st2.embGap ?? Math.max(0, fileCount - embCount));
|
|
40379
40536
|
const stale = st2.staleBuiltAt === true ? " stale-builtAt" : "";
|
|
40380
40537
|
console.log(
|
|
40381
|
-
`repo-index: cloud ${st2.repo} built ${st2.builtAt}
|
|
40538
|
+
`repo-index: cloud ${st2.repo} built ${st2.builtAt} \u2014 ${fileCount} files, emb=${embCount}/${fileCount} gap=${embGap}${stale}`
|
|
40382
40539
|
);
|
|
40383
40540
|
return;
|
|
40384
40541
|
}
|
|
@@ -40387,7 +40544,7 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
|
|
|
40387
40544
|
const present = st2.present;
|
|
40388
40545
|
if (count === 0 || present === false) {
|
|
40389
40546
|
console.error(
|
|
40390
|
-
"repo-index: no cloud projection yet
|
|
40547
|
+
"repo-index: no cloud projection yet \u2014 after Hub deploy, run harbour `repo-index-reconcile` or `mmi-cli oracle repo-index sync-estate` (see docs/Guides/repo-index-runbook.md)."
|
|
40391
40548
|
);
|
|
40392
40549
|
}
|
|
40393
40550
|
return;
|
|
@@ -40399,10 +40556,10 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
|
|
|
40399
40556
|
return;
|
|
40400
40557
|
}
|
|
40401
40558
|
if (!st.present) {
|
|
40402
|
-
console.log(`repo-index: local missing
|
|
40559
|
+
console.log(`repo-index: local missing \u2014 cloud search needs no rebuild; optional \`repo-index rebuild\` for --local`);
|
|
40403
40560
|
return;
|
|
40404
40561
|
}
|
|
40405
|
-
console.log(`repo-index: local ${st.repo} built ${st.builtAt}
|
|
40562
|
+
console.log(`repo-index: local ${st.repo} built ${st.builtAt} \u2014 ${st.fileCount} files, ${st.symbolCount} symbols`);
|
|
40406
40563
|
} catch (e) {
|
|
40407
40564
|
return await failGraceful(e.message);
|
|
40408
40565
|
}
|
|
@@ -40449,7 +40606,7 @@ repoIndex.command("health").description("post-deploy health gate: status + golde
|
|
|
40449
40606
|
return await failGraceful(e.message);
|
|
40450
40607
|
}
|
|
40451
40608
|
});
|
|
40452
|
-
repoIndex.command("gc").description("remove cloud projections for repos no longer on the registry roster").option("--cloud", "required
|
|
40609
|
+
repoIndex.command("gc").description("remove cloud projections for repos no longer on the registry roster").option("--cloud", "required \u2014 GC only applies to Hub cloud").option("--json", "machine-readable result").action(async (o) => {
|
|
40453
40610
|
try {
|
|
40454
40611
|
if (!o.cloud) return await failGraceful("repo-index gc requires --cloud");
|
|
40455
40612
|
const cfg = await loadConfig();
|
|
@@ -40483,7 +40640,7 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
|
|
|
40483
40640
|
for (const p of res.published) {
|
|
40484
40641
|
const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
|
|
40485
40642
|
const trunc = p.embTruncated ? " truncated" : "";
|
|
40486
|
-
console.log(`repo-index: published ${p.repo}
|
|
40643
|
+
console.log(`repo-index: published ${p.repo} \u2014 ${p.fileCount} files emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap}${trunc}`);
|
|
40487
40644
|
}
|
|
40488
40645
|
for (const f of res.failed) {
|
|
40489
40646
|
console.error(`repo-index: FAILED ${f.repo}: ${f.error}`);
|
|
@@ -40493,14 +40650,14 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
|
|
|
40493
40650
|
await failGraceful(e.message);
|
|
40494
40651
|
}
|
|
40495
40652
|
});
|
|
40496
|
-
var docs = program2.command("docs").description("generated docs surfaces
|
|
40497
|
-
docs.command("index").description("regenerate docs/index.md from the docs/ tree (--write, the default) or fail on drift (--check)
|
|
40653
|
+
var docs = program2.command("docs").description("generated docs surfaces \u2014 the routing index (org knowledge layer)");
|
|
40654
|
+
docs.command("index").description("regenerate docs/index.md from the docs/ tree (--write, the default) or fail on drift (--check) \u2014 the generated routing index, never hand-maintained").option("--check", "compare against the committed docs/index.md and exit 1 on drift; never write").option("--write", "regenerate docs/index.md when it has drifted (the default)").action(async (o) => {
|
|
40498
40655
|
try {
|
|
40499
40656
|
const root = await repoRoot();
|
|
40500
40657
|
const result = docsIndex(createDocsIndexDeps(root), { check: Boolean(o.check) });
|
|
40501
40658
|
if (o.check) {
|
|
40502
40659
|
if (result.drift) {
|
|
40503
|
-
return failGraceful(`docs index: ${DOCS_INDEX_PATH} is stale
|
|
40660
|
+
return failGraceful(`docs index: ${DOCS_INDEX_PATH} is stale \u2014 run \`mmi-cli oracle docs index --write\` and commit the result`);
|
|
40504
40661
|
}
|
|
40505
40662
|
console.log(`docs index: ${DOCS_INDEX_PATH} is current`);
|
|
40506
40663
|
return;
|
|
@@ -40510,7 +40667,7 @@ docs.command("index").description("regenerate docs/index.md from the docs/ tree
|
|
|
40510
40667
|
await failGraceful(e.message);
|
|
40511
40668
|
}
|
|
40512
40669
|
});
|
|
40513
|
-
docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by
|
|
40670
|
+
docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by \u2014 -->` comment across docs/** + README.md + architecture.md must resolve, or exit 1 (fleet-portable port of the Hub gate scripts/check-doc-refs.mjs, #3339)").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").action(async (o) => {
|
|
40514
40671
|
try {
|
|
40515
40672
|
const root = await repoRoot();
|
|
40516
40673
|
const commandPaths = new Set(
|
|
@@ -40538,7 +40695,7 @@ docs.command("refs").description("deterministic doc reference gate: every backti
|
|
|
40538
40695
|
await failGraceful(e.message);
|
|
40539
40696
|
}
|
|
40540
40697
|
});
|
|
40541
|
-
var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract
|
|
40698
|
+
var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract \u2014 every child process must be unable to pop a console window");
|
|
40542
40699
|
spawnCmd.command("policy").description("enforce the windowsHide contract across this repo's tracked source: a child_process call must set windowsHide, or take its options from a named constant, a type annotation, or a forwarded caller bag that does. Waive a call the scan cannot classify with a `// windows-hide-exempt: <reason>` comment on the line above it (#3979)").option("--json", "machine-readable result: { ok, scannedCount, findings[] }").action(async (o) => {
|
|
40543
40700
|
try {
|
|
40544
40701
|
const root = await repoRoot();
|
|
@@ -40554,14 +40711,14 @@ spawnCmd.command("policy").description("enforce the windowsHide contract across
|
|
|
40554
40711
|
}
|
|
40555
40712
|
for (const f of result.findings) console.error(`spawn policy: ${f.detail}`);
|
|
40556
40713
|
console.error(
|
|
40557
|
-
"\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps
|
|
40714
|
+
"\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps \u2014 it outlives the process as an empty frame. Set windowsHide: true, or\nroute the call through a helper that does."
|
|
40558
40715
|
);
|
|
40559
40716
|
process.exitCode = 1;
|
|
40560
40717
|
} catch (e) {
|
|
40561
40718
|
await failGraceful(e.message);
|
|
40562
40719
|
}
|
|
40563
40720
|
});
|
|
40564
|
-
var tests = program2.command("tests").description("a repo's test-policy.json
|
|
40721
|
+
var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
40565
40722
|
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
40566
40723
|
try {
|
|
40567
40724
|
const root = await repoRoot();
|
|
@@ -40574,7 +40731,7 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
|
|
|
40574
40731
|
if (result.overriddenBy && result.ok) {
|
|
40575
40732
|
const { sha, reason, kinds } = result.overriddenBy;
|
|
40576
40733
|
const scope = (result.waived ?? []).map((f) => f.kind).join(", ") || "nothing";
|
|
40577
|
-
console.log(`tests policy: overridden by ${sha.slice(0, 8)} (waiving ${scope}; scope ${kinds.join(", ")})
|
|
40734
|
+
console.log(`tests policy: overridden by ${sha.slice(0, 8)} (waiving ${scope}; scope ${kinds.join(", ")}) \u2014 ${reason}`);
|
|
40578
40735
|
return;
|
|
40579
40736
|
}
|
|
40580
40737
|
if (result.ok) {
|
|
@@ -40596,7 +40753,7 @@ async function reportWrite(label, res) {
|
|
|
40596
40753
|
}
|
|
40597
40754
|
if (res.error) return failGraceful(`${label}: ${res.error}`);
|
|
40598
40755
|
const detail = res.body?.error ?? "";
|
|
40599
|
-
return failGraceful(`${label}: HTTP ${res.status}${detail ? `
|
|
40756
|
+
return failGraceful(`${label}: HTTP ${res.status}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
40600
40757
|
}
|
|
40601
40758
|
var tenant = program2.command("tenant").description("tenant runtime control through Hub authority");
|
|
40602
40759
|
tenant.command("control <owner/repo> <stage> <action>").description("bounded tenant control plus value-free vault/broker verification; project-admin own dev/rc, master main").option("--watch", "block on the dispatched run and report its conclusion (status/retire/verify-secrets/verify-broker/logs watch by default)").option("--lines <n>", "logs only: trailing lines of the tenant service to return (1-2000, default 200)").option("--json", "machine-readable output").action(async (repo, stage, action, o) => {
|
|
@@ -40623,7 +40780,7 @@ tenant.command("control <owner/repo> <stage> <action>").description("bounded ten
|
|
|
40623
40780
|
printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
|
|
40624
40781
|
}
|
|
40625
40782
|
if (result.conclusion === "failure") {
|
|
40626
|
-
return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"}
|
|
40783
|
+
return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
|
|
40627
40784
|
}
|
|
40628
40785
|
} catch (e) {
|
|
40629
40786
|
return failGraceful(`runtime tenant control: ${e.message}`);
|
|
@@ -40632,7 +40789,7 @@ tenant.command("control <owner/repo> <stage> <action>").description("bounded ten
|
|
|
40632
40789
|
tenant.command("reconcile <owner/repo> <stage>").description("re-render this tenant stage's generated box assets from registry truth; project-admin own dev/rc, master main; watches by default").option("--watch", "wait for the tenant-reconcile.yml run (default)").option("--no-watch", "return after dispatch; do not redeploy until the reconcile run succeeds").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
40633
40790
|
try {
|
|
40634
40791
|
const result = await runTenantReconcile(trainApplyDeps(), { repo, stage, watch: o.watch });
|
|
40635
|
-
printLine(o.json ? JSON.stringify(result, null, 2) : `${result.note}${result.runUrl ? `
|
|
40792
|
+
printLine(o.json ? JSON.stringify(result, null, 2) : `${result.note}${result.runUrl ? ` \u2014 ${result.runUrl}` : ""}`);
|
|
40636
40793
|
if (result.conclusion === "failure" || !result.dispatched) process.exitCode = 1;
|
|
40637
40794
|
} catch (e) {
|
|
40638
40795
|
return failGraceful(`runtime tenant reconcile: ${e.message}`);
|
|
@@ -40645,7 +40802,7 @@ tenant.command("status <owner/repo> <stage>").description("read tenant runtime r
|
|
|
40645
40802
|
console.log(JSON.stringify(result, null, 2));
|
|
40646
40803
|
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
40647
40804
|
});
|
|
40648
|
-
tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev
|
|
40805
|
+
tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev \u2014 the promoted/staging ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
40649
40806
|
if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime tenant redeploy: <stage> must be dev, rc, or main");
|
|
40650
40807
|
try {
|
|
40651
40808
|
const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage, ref: o.ref, watch: o.watch });
|
|
@@ -40654,9 +40811,9 @@ tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the cen
|
|
|
40654
40811
|
return failGraceful(`runtime tenant redeploy: ${e.message}`);
|
|
40655
40812
|
}
|
|
40656
40813
|
});
|
|
40657
|
-
tenant.command("sweep-rc").description("discover (and optionally retire) running rc tenant runtimes across tenant-containers
|
|
40814
|
+
tenant.command("sweep-rc").description("discover (and optionally retire) running rc tenant runtimes across tenant-containers \u2014 orphan cleanup after a failed post-release retire (#942)").option("--retire", "retire every running rc runtime found (requires --yes) \u2014 WARNING: tears down a legitimately-staged rc too").option("--yes", "confirm the destructive --retire").option("--json", "machine-readable output").action(async (o) => {
|
|
40658
40815
|
if (o.retire && !o.yes) {
|
|
40659
|
-
return fail("runtime tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release)
|
|
40816
|
+
return fail("runtime tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release) \u2014 re-run with --yes to confirm");
|
|
40660
40817
|
}
|
|
40661
40818
|
const cfg = await loadConfig();
|
|
40662
40819
|
const cdeps = registryClientDeps(cfg);
|
|
@@ -40709,7 +40866,7 @@ async function buildTenantRuntimeStatusFor(target, stage, cfg) {
|
|
|
40709
40866
|
lastTenantDeployRun: (await fetchLastTenantDeployRun(slug, stage)).run
|
|
40710
40867
|
});
|
|
40711
40868
|
}
|
|
40712
|
-
var project = program2.command("project").description("the DDB org registry
|
|
40869
|
+
var project = program2.command("project").description("the DDB org registry \u2014 list/get projects (any member); set is master-only");
|
|
40713
40870
|
async function projectTarget(commandName, explicitTarget) {
|
|
40714
40871
|
return requireProjectTarget(commandName, explicitTarget, explicitTarget ? void 0 : await resolveRepo());
|
|
40715
40872
|
}
|
|
@@ -40753,7 +40910,7 @@ project.command("list").description("list all projects (identity + board, never
|
|
|
40753
40910
|
console.log(`${p.slug ?? "?"} - ${p.name ?? ""}${p.division ? ` [${p.division}]` : ""}${p.class ? ` (${p.class})` : ""}${p.projectType ? ` <${p.projectType}>` : ""}${p.deployModel ? ` {${p.deployModel}}` : ""}`);
|
|
40754
40911
|
}
|
|
40755
40912
|
});
|
|
40756
|
-
project.command("get [owner/repo]").description("a project's META (board ids + pointers) by repo or slug; defaults to the current repo
|
|
40913
|
+
project.command("get [owner/repo]").description("a project's META (board ids + pointers) by repo or slug; defaults to the current repo \u2014 identity, NOT deploy coords").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
40757
40914
|
const cfg = await loadConfig();
|
|
40758
40915
|
let target;
|
|
40759
40916
|
try {
|
|
@@ -40763,7 +40920,7 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
40763
40920
|
}
|
|
40764
40921
|
const read = await fetchProjectBySlugChecked(slugOf(target), registryClientDeps(cfg));
|
|
40765
40922
|
if (!read.ok) {
|
|
40766
|
-
return failGraceful(`org project get: Hub registry read failed (${read.error})
|
|
40923
|
+
return failGraceful(`org project get: Hub registry read failed (${read.error}) \u2014 likely transient (cold start, network, or auth blip); retry shortly`);
|
|
40767
40924
|
}
|
|
40768
40925
|
if (!read.project) {
|
|
40769
40926
|
return failGraceful(`org project get: no registry META for ${target} (unknown or unbootstrapped)`);
|
|
@@ -40773,10 +40930,10 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
40773
40930
|
const m = read.project;
|
|
40774
40931
|
const track = resolveReleaseTrack(m, void 0, target);
|
|
40775
40932
|
const stages = branchesForTrack(track).join(" -> ");
|
|
40776
|
-
const note = track === "direct" ? " (direct
|
|
40933
|
+
const note = track === "direct" ? " (direct \u2014 no rc; /rcand refuses, /release ships development -> main)" : track === "trunk" ? " (trunk \u2014 main only)" : "";
|
|
40777
40934
|
console.error(
|
|
40778
|
-
`${m.name ?? target}
|
|
40779
|
-
release track: ${track}
|
|
40935
|
+
`${m.name ?? target} \u2014 class ${m.class ?? "?"} \u2014 deploy ${m.deployModel ?? "?"}
|
|
40936
|
+
release track: ${track} \u2014 stages: ${stages}${note}
|
|
40780
40937
|
deploys run centrally (tenant-deploy.yml); product repos carry no deploy files. Inspect nonsecret DEPLOY facts with \`mmi-cli oracle org project deploy get\`; full coords remain OIDC-gated.`
|
|
40781
40938
|
);
|
|
40782
40939
|
}
|
|
@@ -40850,7 +41007,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
40850
41007
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
40851
41008
|
return reportWrite("org project set", res);
|
|
40852
41009
|
});
|
|
40853
|
-
project.command("retire [owner/repo]").description("retire an orphaned registry slug (master-only): soft-delete the PROJECT# META row + drop the repo's board items; secrets/vault left alone. DRY-RUN by default
|
|
41010
|
+
project.command("retire [owner/repo]").description("retire an orphaned registry slug (master-only): soft-delete the PROJECT# META row + drop the repo's board items; secrets/vault left alone. DRY-RUN by default \u2014 pass --apply to actually delete; defaults to the current repo").option("--apply", "actually delete (default is a dry-run preview of what would be removed)").option("--skip-board", "retire the registry META only \u2014 leave board items in place").option("--json", "machine-readable {slug, removedMeta, removedBoardItem} output").action(async (repoOrSlug, o) => {
|
|
40854
41011
|
const cfg = await loadConfig();
|
|
40855
41012
|
let target;
|
|
40856
41013
|
try {
|
|
@@ -40882,9 +41039,9 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
40882
41039
|
const verb = result.applied ? "retired" : "WOULD retire (dry-run; pass --apply to delete)";
|
|
40883
41040
|
printLine(`org project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
40884
41041
|
if (result.applied) {
|
|
40885
|
-
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED
|
|
41042
|
+
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED \u2014 ${result.removedMeta.error}`}`);
|
|
40886
41043
|
} else {
|
|
40887
|
-
printLine(` META: ${result.removedMeta.ok ? result.removedMeta.existing ? "live row present
|
|
41044
|
+
printLine(` META: ${result.removedMeta.ok ? result.removedMeta.existing ? "live row present \u2014 would be tombstoned" : "no live row (already absent/tombstoned)" : `read failed \u2014 ${result.removedMeta.error}`}`);
|
|
40888
41045
|
}
|
|
40889
41046
|
if (result.removedBoardItem) {
|
|
40890
41047
|
const b = result.removedBoardItem;
|
|
@@ -40895,7 +41052,7 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
40895
41052
|
printLine(` ${result.vaultNote}`);
|
|
40896
41053
|
}
|
|
40897
41054
|
if (result.applied && !result.removedMeta.ok) {
|
|
40898
|
-
return failGraceful(`org project retire: META delete failed
|
|
41055
|
+
return failGraceful(`org project retire: META delete failed \u2014 ${result.removedMeta.error}`);
|
|
40899
41056
|
}
|
|
40900
41057
|
});
|
|
40901
41058
|
var fullTrack = program2.command("full-track").description("direct-to-train readiness audits");
|
|
@@ -40923,7 +41080,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
40923
41080
|
console.log(JSON.stringify(report, null, 2));
|
|
40924
41081
|
if (!report.rcand.canApply) process.exitCode = 1;
|
|
40925
41082
|
});
|
|
40926
|
-
project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row
|
|
41083
|
+
project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row \u2014 project-admin may set only --no-env-file on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli devops runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
40927
41084
|
const cfg = await loadConfig();
|
|
40928
41085
|
let target;
|
|
40929
41086
|
try {
|
|
@@ -40949,7 +41106,7 @@ ${filelessTransitionGuide(target, o.stage)}`);
|
|
|
40949
41106
|
if (verdict.warn) console.error(`org project set-deploy: ${verdict.warn}`);
|
|
40950
41107
|
} else if (!o.force) {
|
|
40951
41108
|
return fail(
|
|
40952
|
-
`org project set-deploy: cannot verify ${target} ${branch} from this checkout
|
|
41109
|
+
`org project set-deploy: cannot verify ${target} ${branch} from this checkout \u2014 refusing to change noEnvFile. Run from the target repo with origin/${branch} fetched.
|
|
40953
41110
|
${filelessTransitionGuide(target, o.stage)}`
|
|
40954
41111
|
);
|
|
40955
41112
|
} else {
|
|
@@ -40984,14 +41141,14 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
40984
41141
|
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
40985
41142
|
return reportWrite("org project set-deploy", res);
|
|
40986
41143
|
});
|
|
40987
|
-
var registry = program2.command("registry").description("the DDB org registry
|
|
41144
|
+
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
40988
41145
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
40989
41146
|
const cfg = await loadConfig();
|
|
40990
41147
|
const org = await fetchOrgConfig(registryClientDeps(cfg));
|
|
40991
41148
|
if (!org) return failGraceful("org config get: Hub API unreachable, unseeded, or this repo is not bootstrapped");
|
|
40992
41149
|
console.log(JSON.stringify(org));
|
|
40993
41150
|
});
|
|
40994
|
-
var oauth = program2.command("oauth").description("per-repo Google OAuth
|
|
41151
|
+
var oauth = program2.command("oauth").description("per-repo Google OAuth \u2014 plan the canonical URI set, verify the client is port-agnostic");
|
|
40995
41152
|
oauth.command("plan", { isDefault: true }).description("print the canonical JS origins + redirect URIs + SSM cred param names for this repo").option("--repo <owner/repo>", "slug source (defaults to the current repo)").option("--json", "machine-readable output").action(async (o) => {
|
|
40996
41153
|
const cfg = await loadConfig();
|
|
40997
41154
|
const slug = (o.repo ? o.repo.split("/").pop() : cfg.project ?? await repoSlug()).toLowerCase();
|
|
@@ -41005,7 +41162,7 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
41005
41162
|
return failGraceful(
|
|
41006
41163
|
`org oauth plan: ${message}. Declare it in the registry META first:
|
|
41007
41164
|
mmi-cli oracle org project set ${o.repo ?? `mutmutco/${slug}`} --var 'oauth={"subdomains":["${defaultSubdomain(slug)}"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}'
|
|
41008
|
-
New repo? The GCP project and its Google Auth Platform consent screen must exist too
|
|
41165
|
+
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \u2014 New repo.`
|
|
41009
41166
|
);
|
|
41010
41167
|
}
|
|
41011
41168
|
return failGraceful(`org oauth plan: ${message}`);
|
|
@@ -41031,7 +41188,7 @@ SSM cred params (under /mmi-future/${slug}/):`);
|
|
|
41031
41188
|
oauth.command("set-creds").description('store the OAuth client into the canonical stageless GOOGLE_CLIENT_ID/SECRET pair (pipe the Console "Download JSON" on stdin)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
|
|
41032
41189
|
const raw = await readStdin();
|
|
41033
41190
|
if (!raw.trim()) {
|
|
41034
|
-
return fail("org oauth set-creds: pipe the Google client JSON on stdin
|
|
41191
|
+
return fail("org oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli vault org oauth set-creds --repo <owner/repo> < client.json");
|
|
41035
41192
|
}
|
|
41036
41193
|
let creds;
|
|
41037
41194
|
try {
|
|
@@ -41081,9 +41238,9 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
41081
41238
|
if (o.json) {
|
|
41082
41239
|
console.log(JSON.stringify({ slug, redirectUri, portAgnostic: !mismatch }));
|
|
41083
41240
|
} else if (mismatch) {
|
|
41084
|
-
console.error(`FAIL ${slug}: redirect_uri_mismatch for ${redirectUri}
|
|
41241
|
+
console.error(`FAIL ${slug}: redirect_uri_mismatch for ${redirectUri} \u2014 client is not port-agnostic (run /oauth-provision)`);
|
|
41085
41242
|
} else {
|
|
41086
|
-
console.log(`PASS ${slug}: ${redirectUri} accepted
|
|
41243
|
+
console.log(`PASS ${slug}: ${redirectUri} accepted \u2014 port-agnostic OAuth is live`);
|
|
41087
41244
|
}
|
|
41088
41245
|
if (mismatch) process.exitCode = 1;
|
|
41089
41246
|
});
|
|
@@ -41096,7 +41253,7 @@ function resolveCreatePriority(raw, command) {
|
|
|
41096
41253
|
try {
|
|
41097
41254
|
return normalizePriority(raw);
|
|
41098
41255
|
} catch {
|
|
41099
|
-
fail(`${command}: unknown priority "${raw}"
|
|
41256
|
+
fail(`${command}: unknown priority "${raw}" \u2014 expected one of: ${CLI_PRIORITIES.join(", ")}`, {
|
|
41100
41257
|
code: ERROR_CODES.ERR_BAD_ENUM,
|
|
41101
41258
|
offending_flag: "--priority",
|
|
41102
41259
|
expected: [...CLI_PRIORITIES]
|
|
@@ -41106,15 +41263,25 @@ function resolveCreatePriority(raw, command) {
|
|
|
41106
41263
|
function resolveCreateType(raw, command, labels) {
|
|
41107
41264
|
if (raw === void 0 || raw === "") {
|
|
41108
41265
|
const nearMiss = labels?.find((l) => ISSUE_TYPES.includes(l));
|
|
41109
|
-
const hint = nearMiss ? ` (you passed --label ${nearMiss}
|
|
41266
|
+
const hint = nearMiss ? ` (you passed --label ${nearMiss} \u2014 did you mean --type ${nearMiss}?)` : "";
|
|
41267
|
+
let correctedCommand;
|
|
41268
|
+
if (nearMiss) {
|
|
41269
|
+
const argv = process.argv.slice(2);
|
|
41270
|
+
const collisionIndex = argv.findIndex((a, i) => a === "--label" && argv[i + 1] === nearMiss);
|
|
41271
|
+
if (collisionIndex !== -1) {
|
|
41272
|
+
const rewritten = argv.slice();
|
|
41273
|
+
rewritten[collisionIndex] = "--type";
|
|
41274
|
+
correctedCommand = `mmi-cli ${canonicalArgvFor(rewritten).join(" ")}`;
|
|
41275
|
+
}
|
|
41276
|
+
}
|
|
41110
41277
|
fail(`${command}: --type is required (bug | feature | task) unless --batch supplies it per row${hint}`, {
|
|
41111
41278
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
41112
41279
|
offending_flag: "--type",
|
|
41113
|
-
...
|
|
41280
|
+
...correctedCommand ? { corrected_command: correctedCommand } : {}
|
|
41114
41281
|
});
|
|
41115
41282
|
}
|
|
41116
41283
|
if (!ISSUE_TYPES.includes(raw)) {
|
|
41117
|
-
fail(`${command}: unknown type "${raw}"
|
|
41284
|
+
fail(`${command}: unknown type "${raw}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`, {
|
|
41118
41285
|
code: ERROR_CODES.ERR_BAD_ENUM,
|
|
41119
41286
|
offending_flag: "--type",
|
|
41120
41287
|
expected: [...ISSUE_TYPES]
|
|
@@ -41128,9 +41295,9 @@ function resolveCreateSurface(opts) {
|
|
|
41128
41295
|
function surfaceWaived() {
|
|
41129
41296
|
return rawFlag("--no-surface");
|
|
41130
41297
|
}
|
|
41131
|
-
var issue = program2.command("issue").description("issues
|
|
41298
|
+
var issue = program2.command("issue").description("issues \u2014 reliable create with structured output");
|
|
41132
41299
|
withExamples(mutating(
|
|
41133
|
-
issue.command("create").description("create an issue (type
|
|
41300
|
+
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
41134
41301
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
41135
41302
|
// and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
|
|
41136
41303
|
// is not a plan of the mutation that will run (#3914).
|
|
@@ -41197,7 +41364,7 @@ withExamples(mutating(
|
|
|
41197
41364
|
}
|
|
41198
41365
|
targetRepo2 = await resolveRepo(o.repo);
|
|
41199
41366
|
if (!targetRepo2) {
|
|
41200
|
-
return fail("issue create: could not resolve the target repo
|
|
41367
|
+
return fail("issue create: could not resolve the target repo \u2014 run inside a git checkout or pass --repo <owner/repo>");
|
|
41201
41368
|
}
|
|
41202
41369
|
args = buildIssueArgs({
|
|
41203
41370
|
type: issueType,
|
|
@@ -41226,7 +41393,7 @@ withExamples(mutating(
|
|
|
41226
41393
|
labels: extraLabels.length ? extraLabels : void 0
|
|
41227
41394
|
});
|
|
41228
41395
|
process.stderr.write(
|
|
41229
|
-
`warning: --surface ${surfaceFlagLabel} was dropped
|
|
41396
|
+
`warning: --surface ${surfaceFlagLabel} was dropped \u2014 ${targetRepo2} defines no surface:* labels, and creating one here would switch the one-surface-label rule on for every later filing in it. Use --label ${surfaceFlagLabel} if you really mean to start that taxonomy.
|
|
41230
41397
|
`
|
|
41231
41398
|
);
|
|
41232
41399
|
}
|
|
@@ -41276,7 +41443,7 @@ async function readParentField(number, repo) {
|
|
|
41276
41443
|
}
|
|
41277
41444
|
return resolveParentField(payload);
|
|
41278
41445
|
}
|
|
41279
|
-
issue.command("view <number>").description('read an issue as structured JSON
|
|
41446
|
+
issue.command("view <number>").description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
|
|
41280
41447
|
const n = Number(number);
|
|
41281
41448
|
if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
|
|
41282
41449
|
const repo = await resolveRepo(o.repo);
|
|
@@ -41370,7 +41537,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
41370
41537
|
return fail(`issue comment: ${e.message}`);
|
|
41371
41538
|
}
|
|
41372
41539
|
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
41373
|
-
if (!repo) return fail("issue comment: could not resolve repo
|
|
41540
|
+
if (!repo) return fail("issue comment: could not resolve repo \u2014 pass --repo owner/repo");
|
|
41374
41541
|
try {
|
|
41375
41542
|
const result = await postIssueComment(defaultGitHubClient(), { ref, defaultRepo: repo, body });
|
|
41376
41543
|
console.log(JSON.stringify(result));
|
|
@@ -41379,7 +41546,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
41379
41546
|
return failGraceful(`issue comment: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
41380
41547
|
}
|
|
41381
41548
|
});
|
|
41382
|
-
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match
|
|
41549
|
+
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2014 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
41383
41550
|
let parsed;
|
|
41384
41551
|
try {
|
|
41385
41552
|
parsed = parseIssueRef(ref);
|
|
@@ -41387,7 +41554,7 @@ jsonParity(issue.command("check <ref>").description("tick (or with --off untick)
|
|
|
41387
41554
|
return fail(`issue check: ${e.message}`);
|
|
41388
41555
|
}
|
|
41389
41556
|
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
41390
|
-
if (!repo) return fail("issue check: could not resolve repo
|
|
41557
|
+
if (!repo) return fail("issue check: could not resolve repo \u2014 pass --repo owner/repo");
|
|
41391
41558
|
const checked = o.off !== true;
|
|
41392
41559
|
let body;
|
|
41393
41560
|
try {
|
|
@@ -41400,7 +41567,7 @@ jsonParity(issue.command("check <ref>").description("tick (or with --off untick)
|
|
|
41400
41567
|
if (!result.ok) {
|
|
41401
41568
|
if (result.reason === "ambiguous") {
|
|
41402
41569
|
const list = result.matches.map((m) => ` - ${m.text}`).join("\n");
|
|
41403
|
-
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number}
|
|
41570
|
+
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number} \u2014 narrow the text:
|
|
41404
41571
|
${list}`);
|
|
41405
41572
|
}
|
|
41406
41573
|
return fail(`issue check: no checklist item matching "${o.item}" in ${repo}#${parsed.number}`);
|
|
@@ -41419,7 +41586,7 @@ ${list}`);
|
|
|
41419
41586
|
}
|
|
41420
41587
|
console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
|
|
41421
41588
|
});
|
|
41422
|
-
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare
|
|
41589
|
+
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
|
|
41423
41590
|
let body;
|
|
41424
41591
|
let priority;
|
|
41425
41592
|
let title;
|
|
@@ -41429,14 +41596,16 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
41429
41596
|
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
41430
41597
|
priority = resolveCreatePriority(o.priority, "report");
|
|
41431
41598
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
41432
|
-
throw new Error(`unknown issue type "${o.type}"
|
|
41599
|
+
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
41433
41600
|
}
|
|
41434
41601
|
} catch (e) {
|
|
41435
41602
|
const m = e.message;
|
|
41436
41603
|
const payload = m === "pass --title or --title-file" ? {
|
|
41437
41604
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
41438
41605
|
offending_flag: "--title",
|
|
41439
|
-
|
|
41606
|
+
// #4803: process.argv's house token is already spliced by resolveHouseShim (#4438);
|
|
41607
|
+
// canonicalArgvFor re-prepends it so this stays the runnable house-prefixed form.
|
|
41608
|
+
corrected_command: `mmi-cli ${canonicalArgvFor(process.argv.slice(2)).join(" ")} --title "..."`
|
|
41440
41609
|
} : void 0;
|
|
41441
41610
|
return fail(`report: ${m}`, payload);
|
|
41442
41611
|
}
|
|
@@ -41466,7 +41635,7 @@ async function resolvePluginSha() {
|
|
|
41466
41635
|
return void 0;
|
|
41467
41636
|
}
|
|
41468
41637
|
}
|
|
41469
|
-
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").addOption(new Option("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).makeOptionMandatory().choices([...SKILL_NAMES])).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").option("--body <body>", "lesson body: what misfired, the evidence, and the proposed amendment (markdown)").option("--body-file <path|->", "read the lesson body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default
|
|
41638
|
+
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").addOption(new Option("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).makeOptionMandatory().choices([...SKILL_NAMES])).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").option("--body <body>", "lesson body: what misfired, the evidence, and the proposed amendment (markdown)").option("--body-file <path|->", "read the lesson body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 skill-lesson always prints JSON)").action(async (o) => {
|
|
41470
41639
|
const targetRepo2 = o.repo ?? HUB_REPO;
|
|
41471
41640
|
const sourceRepo = await resolveRepo(void 0);
|
|
41472
41641
|
const pluginSha = await resolvePluginSha();
|
|
@@ -41521,7 +41690,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
41521
41690
|
repo: targetRepo2,
|
|
41522
41691
|
labels: [SKILL_LESSON_LABEL],
|
|
41523
41692
|
command: "skill-lesson",
|
|
41524
|
-
waiver: { reason: "tooling lesson spans product surfaces
|
|
41693
|
+
waiver: { reason: "tooling lesson spans product surfaces \u2014 coop-proof class (#3789)" }
|
|
41525
41694
|
});
|
|
41526
41695
|
if (surfaceWarn) process.stderr.write(`${surfaceWarn}
|
|
41527
41696
|
`);
|
|
@@ -41533,7 +41702,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
41533
41702
|
const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo2, priority);
|
|
41534
41703
|
console.log(JSON.stringify({ ...created, deduped: false, label: SKILL_LESSON_LABEL, skill, priority, projectItemId, onBoard }));
|
|
41535
41704
|
});
|
|
41536
|
-
var pr = program2.command("pr").description("pull requests
|
|
41705
|
+
var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
41537
41706
|
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
41538
41707
|
let body;
|
|
41539
41708
|
let title;
|
|
@@ -41547,7 +41716,7 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
41547
41716
|
body = rewriteNegatedClosingPhrases(body);
|
|
41548
41717
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
41549
41718
|
if (docsCheck && !docsCheck.ok) {
|
|
41550
|
-
return fail(`pr create: ${docsCheck.detail}
|
|
41719
|
+
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
41551
41720
|
}
|
|
41552
41721
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
41553
41722
|
invalidateStatuslineBoardCache();
|
|
@@ -41560,7 +41729,7 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
41560
41729
|
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown.",
|
|
41561
41730
|
"Write that file under .jerv/ inside a worktree (#4405): any other untracked path makes `worktree land` refuse cleanup as untracked-files."
|
|
41562
41731
|
]);
|
|
41563
|
-
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit)
|
|
41732
|
+
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (number, o) => {
|
|
41564
41733
|
const n = Number(number);
|
|
41565
41734
|
if (!Number.isInteger(n) || n <= 0) return fail("pr view: <number> must be a positive integer");
|
|
41566
41735
|
const repo = await resolveRepo(o.repo);
|
|
@@ -41670,7 +41839,7 @@ pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs
|
|
|
41670
41839
|
if (o.json) return printLine(JSON.stringify(result));
|
|
41671
41840
|
printLine(`merge CI policy: ${result.policy} (${result.reason})`);
|
|
41672
41841
|
});
|
|
41673
|
-
pr.command("checks-wait <number>").description(`bounded wait for PR checks; skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR
|
|
41842
|
+
pr.command("checks-wait <number>").description(`bounded wait for PR checks; skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR \u2014 GitHub never queues checks for one (#2970). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED or the PR is CONFLICTING, exit ${PR_CHECKS_TIMEOUT_EXIT_CODE} = the wait window expired or the API pool ran dry (re-arm)`).option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--timeout <minutes>", `wait budget in minutes (default ${PR_CHECKS_TIMEOUT_MS / 6e4}) \u2014 raise it for serial self-hosted e2e queues`).action(async (number, o) => {
|
|
41674
41843
|
let timeoutMs;
|
|
41675
41844
|
if (o.timeout !== void 0) {
|
|
41676
41845
|
const minutes = Number(o.timeout);
|
|
@@ -41700,27 +41869,27 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
41700
41869
|
timeoutMs,
|
|
41701
41870
|
// Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
|
|
41702
41871
|
// an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
|
|
41703
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state}
|
|
41872
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
41704
41873
|
});
|
|
41705
41874
|
if (o.json) printLine(JSON.stringify(result));
|
|
41706
41875
|
else if (result.status === "conflicting") {
|
|
41707
|
-
printLine(`pr checks-wait: conflicting
|
|
41876
|
+
printLine(`pr checks-wait: conflicting \u2014 ${result.reason}`);
|
|
41708
41877
|
} else if (result.status === "timeout") {
|
|
41709
41878
|
const stuckQueuing = /no-checks-reported/i.test(result.detail ?? "");
|
|
41710
41879
|
printLine(
|
|
41711
|
-
`pr checks-wait: timeout
|
|
41880
|
+
`pr checks-wait: timeout \u2014 waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.` + (stuckQueuing ? " Tip: jobs never appeared \u2014 mmi-live may be saturated or Actions may be failing before checkout; check runner health (docs/Guides/gh-runner-runbook.md) and re-run the workflow." : "")
|
|
41712
41881
|
);
|
|
41713
41882
|
} else if (result.status === "rate-limited") {
|
|
41714
|
-
printLine(`pr checks-wait: rate-limited
|
|
41883
|
+
printLine(`pr checks-wait: rate-limited \u2014 ${result.reason ?? "REST pool below floor"}. No check failed; re-run after the pool resets.`);
|
|
41715
41884
|
} else if (result.detail === "runner-infra") {
|
|
41716
|
-
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure)
|
|
41885
|
+
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure) \u2014 ${result.reason}`);
|
|
41717
41886
|
} else if (result.detail === "stale-head") {
|
|
41718
|
-
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure)
|
|
41719
|
-
} else printLine(`pr checks-wait: ${result.status}${result.reason ? `
|
|
41887
|
+
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure) \u2014 ${result.reason}`);
|
|
41888
|
+
} else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
41720
41889
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
41721
41890
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
41722
41891
|
});
|
|
41723
|
-
pr.command("land <number>").description("agent merge path (#1440): train probe
|
|
41892
|
+
pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--preserve-worktree", "after merge, keep the local PR worktree/stage/branch for an active batch (#1888)").option("--force", "acknowledge and land past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").action(async (number, o) => {
|
|
41724
41893
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
41725
41894
|
const startingPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
41726
41895
|
assertPrMergeHousekeepingClean(startingPath || process.cwd(), "pr land", { force: o.force });
|
|
@@ -41754,8 +41923,8 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41754
41923
|
} catch {
|
|
41755
41924
|
}
|
|
41756
41925
|
if (isPromotionBase(base, track)) {
|
|
41757
|
-
const shape = track ? `${track} track` : "unresolved track
|
|
41758
|
-
throw new Error(`pr land: base branch ${base} is a promotion target (${shape})
|
|
41926
|
+
const shape = track ? `${track} track` : "unresolved track \u2014 strict reading";
|
|
41927
|
+
throw new Error(`pr land: base branch ${base} is a promotion target (${shape}) \u2014 promotion merges stay human-only`);
|
|
41759
41928
|
}
|
|
41760
41929
|
return repo;
|
|
41761
41930
|
},
|
|
@@ -41765,26 +41934,26 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41765
41934
|
const requiredContexts = await fetchRequiredCheckContexts(repo, "development").catch(() => null);
|
|
41766
41935
|
return waitForPrChecks({
|
|
41767
41936
|
resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
41768
|
-
// #3024: REST-only wait loop
|
|
41937
|
+
// #3024: REST-only wait loop — runPrLand's resolveRepo already guarantees `repo` is set.
|
|
41769
41938
|
pollChecks: () => pollRestPrChecks(prNumber, repo, void 0, requiredContexts),
|
|
41770
41939
|
// #2970: `pr land` only ever lands PRs based on development (resolveRepo above already rejects any
|
|
41771
41940
|
// other base), so the fast-fail message's base branch is always 'development' here.
|
|
41772
41941
|
pollMergeable: () => pollRestPrMergeable(prNumber, repo),
|
|
41773
41942
|
pollRateLimit: () => waitLoopCorePool("pr land"),
|
|
41774
|
-
// #3388: `pr land` is the batch path
|
|
41943
|
+
// #3388: `pr land` is the batch path — the one most likely to self-DOS the shared runner and
|
|
41775
41944
|
// then read its own wall-clock kill as a broken diff.
|
|
41776
41945
|
diagnoseFailure: () => waitLoopDiagnosis("pr land", prNumber, repo),
|
|
41777
41946
|
baseBranch: "development",
|
|
41778
41947
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
41779
41948
|
log: (message) => console.warn(message),
|
|
41780
|
-
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness
|
|
41949
|
+
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
|
|
41781
41950
|
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
41782
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks
|
|
41951
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
41783
41952
|
});
|
|
41784
41953
|
},
|
|
41785
|
-
// #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying
|
|
41954
|
+
// #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying — a
|
|
41786
41955
|
// fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
|
|
41787
|
-
// #4396: same required-status scoping as the wait loop above
|
|
41956
|
+
// #4396: same required-status scoping as the wait loop above — a non-required red must not read
|
|
41788
41957
|
// as "not ready to retry" any more than it should have blocked the wait itself.
|
|
41789
41958
|
probeMergeReady: async (prNumber, repo) => {
|
|
41790
41959
|
const [snapshot, requiredContexts] = await Promise.all([
|
|
@@ -41814,7 +41983,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41814
41983
|
if (read.state === "merged") return true;
|
|
41815
41984
|
if (read.state === "failed") {
|
|
41816
41985
|
lastFailure = read.error;
|
|
41817
|
-
console.warn(`pr land: merged-state read FAILED (${read.error})
|
|
41986
|
+
console.warn(`pr land: merged-state read FAILED (${read.error}) \u2014 retrying; this is not evidence the PR is unmerged`);
|
|
41818
41987
|
} else {
|
|
41819
41988
|
lastFailure = void 0;
|
|
41820
41989
|
}
|
|
@@ -41822,7 +41991,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41822
41991
|
}
|
|
41823
41992
|
if (lastFailure) {
|
|
41824
41993
|
throw new Error(
|
|
41825
|
-
`pr land: could not verify whether PR #${prNumber} merged
|
|
41994
|
+
`pr land: could not verify whether PR #${prNumber} merged \u2014 the last merged-state read FAILED (${lastFailure}). The auto-merge enqueue stands; read the PR directly (gh pr view ${prNumber} --repo ${repo} --json merged,state) and rerun cleanup if it merged.`
|
|
41826
41995
|
);
|
|
41827
41996
|
}
|
|
41828
41997
|
return false;
|
|
@@ -41830,7 +41999,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41830
41999
|
});
|
|
41831
42000
|
if (result.status !== "failed") {
|
|
41832
42001
|
const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
|
|
41833
|
-
console.warn(`pr land: merge confirmed; cleanup pending
|
|
42002
|
+
console.warn(`pr land: merge confirmed; cleanup pending \u2014 if this process is interrupted, resume with: mmi-cli devops pr merge ${number}${repoFlag} --squash`);
|
|
41834
42003
|
}
|
|
41835
42004
|
if (result.status !== "failed") {
|
|
41836
42005
|
try {
|
|
@@ -41861,13 +42030,13 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
41861
42030
|
}
|
|
41862
42031
|
if (o.json) printLine(JSON.stringify(result));
|
|
41863
42032
|
else {
|
|
41864
|
-
printLine(`pr land: ${result.status}${result.error ? `
|
|
42033
|
+
printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
41865
42034
|
for (const line of renderPrLandCleanupLines(result.cleanup)) printLine(line);
|
|
41866
42035
|
if (result.cleanupError) printLine(`pr land cleanup: ${result.cleanupError}`);
|
|
41867
42036
|
}
|
|
41868
42037
|
if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
|
|
41869
42038
|
});
|
|
41870
|
-
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree
|
|
42039
|
+
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree \u2014 no leftover local branch; on no-ci repos run pr ci-policy / checks-wait first (#1432)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "keep the local PR worktree/stage/branch for an active multi-issue batch (#1888)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
|
|
41871
42040
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
41872
42041
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
41873
42042
|
const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
|
|
@@ -41920,10 +42089,10 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41920
42089
|
baseBranch,
|
|
41921
42090
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
41922
42091
|
log: (message) => console.warn(message),
|
|
41923
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks
|
|
42092
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
41924
42093
|
});
|
|
41925
42094
|
if (wait.status !== "success" && wait.status !== "skipped") {
|
|
41926
|
-
console.warn(`pr merge: --wait stopped before merge
|
|
42095
|
+
console.warn(`pr merge: --wait stopped before merge \u2014 ${wait.status}${wait.reason ? `: ${wait.reason}` : ""}${wait.detail ? ` (${wait.detail})` : ""}`);
|
|
41927
42096
|
process.exitCode = wait.status === "timeout" || wait.status === "rate-limited" ? PR_CHECKS_TIMEOUT_EXIT_CODE : 1;
|
|
41928
42097
|
return;
|
|
41929
42098
|
}
|
|
@@ -41979,7 +42148,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41979
42148
|
return;
|
|
41980
42149
|
}
|
|
41981
42150
|
if (!o.auto && basePolicyBlocksImmediateMerge(message)) {
|
|
41982
|
-
console.warn(`pr merge: the base-branch policy blocks an immediate merge
|
|
42151
|
+
console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
|
|
41983
42152
|
upgradedToAuto = true;
|
|
41984
42153
|
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
41985
42154
|
const m2 = String(e2.message || "");
|
|
@@ -42014,7 +42183,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
42014
42183
|
const state = stateRead.ok ? stateRead.state : "";
|
|
42015
42184
|
const enqueued = describePrMergeEnqueuedReason(await pollGhPrChecks(number, repoArgs).catch(() => void 0));
|
|
42016
42185
|
console.log(JSON.stringify({ mergeStatus: "auto-merge-enqueued", enqueuedReason: enqueued.reason, pr: number, branch: headRef, state: state || "unknown", upgradedToAuto: upgradedToAuto || void 0 }));
|
|
42017
|
-
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged
|
|
42186
|
+
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged \u2014 ${enqueued.message}.`);
|
|
42018
42187
|
if (upgradedToAuto && !o.auto) {
|
|
42019
42188
|
console.warn(`pr merge: exiting ${PR_MERGE_ENQUEUED_EXIT_CODE} so a chained command does not treat this as a completed merge.`);
|
|
42020
42189
|
process.exitCode = PR_MERGE_ENQUEUED_EXIT_CODE;
|
|
@@ -42030,7 +42199,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
42030
42199
|
state: stateRead.ok ? stateRead.state : "unknown",
|
|
42031
42200
|
cleanupStatus: "skipped"
|
|
42032
42201
|
}));
|
|
42033
|
-
console.error(`pr merge: ${gate.message}. Nothing was deleted
|
|
42202
|
+
console.error(`pr merge: ${gate.message}. Nothing was deleted \u2014 re-check the PR and retry.`);
|
|
42034
42203
|
process.exitCode = 1;
|
|
42035
42204
|
return;
|
|
42036
42205
|
}
|
|
@@ -42133,7 +42302,7 @@ function trainApplyDeps() {
|
|
|
42133
42302
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
42134
42303
|
return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
|
|
42135
42304
|
},
|
|
42136
|
-
// Hub-App-authority dispatch of the central tenant deploy (#953)
|
|
42305
|
+
// Hub-App-authority dispatch of the central tenant deploy (#953) — the Hub fires the
|
|
42137
42306
|
// workflow_dispatch with its App token, so the caller needs no MMI-Hub Actions write.
|
|
42138
42307
|
dispatchTenantDeploy: async ({ repo, stage, ref }) => {
|
|
42139
42308
|
const res = await tenantDeploy({ repo, stage, ref }, registryClientDeps(await loadConfig()));
|
|
@@ -42142,7 +42311,7 @@ function trainApplyDeps() {
|
|
|
42142
42311
|
throw new Error(`tenant deploy dispatch failed: ${detail}`);
|
|
42143
42312
|
}
|
|
42144
42313
|
},
|
|
42145
|
-
// Hub-App-authority dispatch of the central tenant-control.yml (#1717)
|
|
42314
|
+
// Hub-App-authority dispatch of the central tenant-control.yml (#1717) — the Hub fires the
|
|
42146
42315
|
// workflow_dispatch with its App token. Never throws for an expected rejection: it returns the dispatch
|
|
42147
42316
|
// outcome so runTenantControl can map a 5xx (transport-failed, retryable) vs a 4xx (rejected) vs ok.
|
|
42148
42317
|
dispatchTenantControl: async ({ repo, stage, action, lines }) => {
|
|
@@ -42158,7 +42327,7 @@ function trainApplyDeps() {
|
|
|
42158
42327
|
return { ok: false, category: body?.category, error: body?.error ?? res.error };
|
|
42159
42328
|
},
|
|
42160
42329
|
// Hotfix-coverage guard (#958): runs against the local clone via real git. manifestPaths exempts the
|
|
42161
|
-
// release version fold (#976)
|
|
42330
|
+
// release version fold (#976) — a main-only commit touching ONLY the root package manifest is the
|
|
42162
42331
|
// fold's version metadata, which the candidate replaces with its own. (The Hub's wider distribution
|
|
42163
42332
|
// set never reaches this guard: the Hub is direct-track.)
|
|
42164
42333
|
hotfixCoverage: (input) => checkHotfixCoverage({ ...input, manifestPaths: ["package.json", "package-lock.json"] }),
|
|
@@ -42174,14 +42343,14 @@ function trainApplyDeps() {
|
|
|
42174
42343
|
removeFile: (path2) => (0, import_promises11.unlink)(path2)
|
|
42175
42344
|
}, args),
|
|
42176
42345
|
// #4713 (I/O-boundary census): `null` used to mean BOTH "this project configures no edge domains"
|
|
42177
|
-
// (a real answer) and "the registry read missed"
|
|
42346
|
+
// (a real answer) and "the registry read missed" — so a release verdict printed an environments block
|
|
42178
42347
|
// with no domains as though the project had none. The checked read separates them: an absent project
|
|
42179
42348
|
// or an absent edgeDomains block still answers null; a FAILED read refuses by name, and the caller's
|
|
42180
42349
|
// own best-effort catch degrades the block instead of the read pretending it saw an answer.
|
|
42181
42350
|
fetchEdgeDomains: async (slug) => {
|
|
42182
42351
|
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
|
|
42183
42352
|
if (!read.ok) {
|
|
42184
|
-
const message = `release environments: Hub registry read FAILED for ${slug} (${read.error})
|
|
42353
|
+
const message = `release environments: Hub registry read FAILED for ${slug} (${read.error}) \u2014 edge domains are UNREAD, not absent`;
|
|
42185
42354
|
consoleIo.err(`mmi-cli: ${message}`);
|
|
42186
42355
|
throw new Error(message);
|
|
42187
42356
|
}
|
|
@@ -42203,13 +42372,13 @@ function renderDeployLine(d) {
|
|
|
42203
42372
|
else if (d.runUrl) parts.push(`run ${d.runUrl}`);
|
|
42204
42373
|
if (d.deployStatus === "success") parts.push("deploy: SUCCEEDED");
|
|
42205
42374
|
else if (d.deployStatus === "failure") parts.push("deploy: FAILED (promotion stands; retry the deploy, do not re-tag)");
|
|
42206
|
-
else if (d.runId != null) parts.push(`deploy: UNVERIFIED
|
|
42207
|
-
else if (d.workflowRuns?.length) parts.push("deploy: UNVERIFIED
|
|
42208
|
-
else parts.push("deploy: UNVERIFIED
|
|
42375
|
+
else if (d.runId != null) parts.push(`deploy: UNVERIFIED \u2014 dispatched, not resolved (watch: gh run watch ${d.runId} --repo mutmutco/MMI-Hub --exit-status)`);
|
|
42376
|
+
else if (d.workflowRuns?.length) parts.push("deploy: UNVERIFIED \u2014 the runs above are enumerated, not resolved; watch each to conclusion before calling this release healthy (#3322)");
|
|
42377
|
+
else parts.push("deploy: UNVERIFIED \u2014 no run correlated or watched; resolve every workflow run on the release SHA before calling this release healthy (#3322)");
|
|
42209
42378
|
return parts.join("; ");
|
|
42210
42379
|
}
|
|
42211
42380
|
function renderReleaseResume(r) {
|
|
42212
|
-
const lines = [`mmi-cli devops release --resume: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]
|
|
42381
|
+
const lines = [`mmi-cli devops release --resume: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}] \u2014 ${r.note}`];
|
|
42213
42382
|
for (const step of r.steps) lines.push(` - ${step}`);
|
|
42214
42383
|
if (r.releaseUrl) lines.push(` release: ${r.releaseUrl}`);
|
|
42215
42384
|
if (r.announceNote) lines.push(` announce: ${r.announceNote}`);
|
|
@@ -42219,23 +42388,23 @@ function renderReleaseResume(r) {
|
|
|
42219
42388
|
return lines.join("\n");
|
|
42220
42389
|
}
|
|
42221
42390
|
function renderReleaseAbort(r) {
|
|
42222
|
-
return `mmi-cli devops release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)})
|
|
42391
|
+
return `mmi-cli devops release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
42223
42392
|
}
|
|
42224
42393
|
function renderReleasePublishRetry(r) {
|
|
42225
|
-
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)})
|
|
42394
|
+
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
42226
42395
|
}
|
|
42227
42396
|
function renderRcandResume(r) {
|
|
42228
|
-
return `mmi-cli devops rcand --resume: promoted ${r.repo}
|
|
42397
|
+
return `mmi-cli devops rcand --resume: promoted ${r.repo} \u2014 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
|
|
42229
42398
|
}
|
|
42230
42399
|
function renderAlignment(label, alignment) {
|
|
42231
42400
|
if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
|
|
42232
42401
|
if (alignment.autoMergeEnqueued) {
|
|
42233
42402
|
return `${label}: alignment PR #${alignment.prNumber ?? "?"} AUTO-MERGE ENQUEUED (merges when checks pass)${alignment.prUrl ? ` (${alignment.prUrl})` : ""}`;
|
|
42234
42403
|
}
|
|
42235
|
-
return `${label}: ALIGNMENT PR PENDING
|
|
42404
|
+
return `${label}: ALIGNMENT PR PENDING \u2014 land it with \`mmi-cli devops pr merge ${alignment.prNumber ?? "<number>"} --auto --merge\`${alignment.prUrl ? ` (${alignment.prUrl})` : ""}`;
|
|
42236
42405
|
}
|
|
42237
42406
|
function renderTrainApply(commandName, r) {
|
|
42238
|
-
let base = `mmi-cli ${commandName}: promoted ${r.repo}
|
|
42407
|
+
let base = `mmi-cli ${commandName}: promoted ${r.repo} \u2014 ${r.stage} at ${r.tag} [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
42239
42408
|
if (r.versionFold) base = `${base}; ${r.versionFold}`;
|
|
42240
42409
|
if (r.resumeNote) base = `${base}; ${r.resumeNote}`;
|
|
42241
42410
|
if (r.devNote) base = `${base}; ${r.devNote}`;
|
|
@@ -42298,16 +42467,16 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
42298
42467
|
return fail(`${commandName}: ${e.message}`);
|
|
42299
42468
|
}
|
|
42300
42469
|
if (o.ack && commandName !== "release") {
|
|
42301
|
-
return fail(`${commandName}: --ack applies only to release
|
|
42470
|
+
return fail(`${commandName}: --ack applies only to release \u2014 it overrides the rc -> main hotfix-coverage guard, which rcand does not run. Run: mmi-cli devops release --ack <shas>`);
|
|
42302
42471
|
}
|
|
42303
42472
|
if (o.dev && commandName !== "release") {
|
|
42304
|
-
return fail(`${commandName}: --dev applies only to release
|
|
42473
|
+
return fail(`${commandName}: --dev applies only to release \u2014 it ships development -> main skipping rc, which rcand cannot do. Run: mmi-cli devops release --dev`);
|
|
42305
42474
|
}
|
|
42306
42475
|
if (o.announceSummaryFile && commandName !== "release") {
|
|
42307
|
-
return fail(`${commandName}: --announce-summary-file applies only to release
|
|
42476
|
+
return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no Hub Slack announcement. Run: mmi-cli devops release --announce-summary-file <path>`);
|
|
42308
42477
|
}
|
|
42309
42478
|
if (o.abort && commandName !== "release") {
|
|
42310
|
-
return fail(`${commandName}: --abort applies only to release
|
|
42479
|
+
return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished Hub release tag. Run: mmi-cli devops release --abort --apply`);
|
|
42311
42480
|
}
|
|
42312
42481
|
if (o.retryPublish && commandName !== "release") {
|
|
42313
42482
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
@@ -42337,7 +42506,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
42337
42506
|
}
|
|
42338
42507
|
}
|
|
42339
42508
|
if (o.abort) {
|
|
42340
|
-
if (o.resume) return fail("release: --abort and --resume are mutually exclusive
|
|
42509
|
+
if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
|
|
42341
42510
|
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
42342
42511
|
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
42343
42512
|
return fail("release: --abort accepts only --apply, --repo and --json; promotion flags cannot be combined with rollback");
|
|
@@ -42354,7 +42523,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
42354
42523
|
}
|
|
42355
42524
|
}
|
|
42356
42525
|
if (o.resume) {
|
|
42357
|
-
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive
|
|
42526
|
+
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
|
|
42358
42527
|
try {
|
|
42359
42528
|
if (commandName === "rcand") {
|
|
42360
42529
|
const result2 = await runRcandResume(trainApplyDeps(), { watch: o.watch });
|
|
@@ -42410,7 +42579,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
42410
42579
|
projectInfoSync = await runProjectInfoSync(result.repo, true);
|
|
42411
42580
|
} catch (e) {
|
|
42412
42581
|
const error = e.message;
|
|
42413
|
-
projectInfoSync = { applied: false, note: `FAILED
|
|
42582
|
+
projectInfoSync = { applied: false, note: `FAILED \u2014 ${error}`, error };
|
|
42414
42583
|
}
|
|
42415
42584
|
}
|
|
42416
42585
|
const alignmentPending = result.devRollForward?.status === "pr-pending" || result.rcAlignment?.status === "pr-pending";
|
|
@@ -42452,7 +42621,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
42452
42621
|
}
|
|
42453
42622
|
if (!read.ok) {
|
|
42454
42623
|
return failGraceful(
|
|
42455
|
-
`${commandName}: Hub registry read failed for ${slugOf(repo)} (${read.error})
|
|
42624
|
+
`${commandName}: Hub registry read failed for ${slugOf(repo)} (${read.error}) \u2014 the release track behind this plan is unproven, and a dry-run plan built on the isHub fallback would name the wrong branches. Likely transient (cold start, network, or auth blip); retry shortly.`
|
|
42456
42625
|
);
|
|
42457
42626
|
}
|
|
42458
42627
|
const raw = read.project?.releaseTrack;
|
|
@@ -42478,13 +42647,13 @@ function renderHotfixRelease(r) {
|
|
|
42478
42647
|
` - ${r.verifyNote}`,
|
|
42479
42648
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
42480
42649
|
` - fold: ${r.foldNote}`,
|
|
42481
|
-
` - next: mmi-cli devops hotfix status ${r.tag} (no back-merge of the FIX
|
|
42650
|
+
` - next: mmi-cli devops hotfix status ${r.tag} (no back-merge of the FIX \u2014 development already has it; the version fold above is ported for you, #4410)`
|
|
42482
42651
|
].join("\n");
|
|
42483
42652
|
}
|
|
42484
42653
|
function renderHotfixStatus(r) {
|
|
42485
42654
|
return [
|
|
42486
|
-
`mmi-cli devops hotfix status: ${r.tag} on ${r.repo}
|
|
42487
|
-
` - branch: ${r.branchExists ? "pushed" : "absent"}
|
|
42655
|
+
`mmi-cli devops hotfix status: ${r.tag} on ${r.repo} \u2014 ${r.state}`,
|
|
42656
|
+
` - branch: ${r.branchExists ? "pushed" : "absent"} \u2014 PR: ${r.pr ? `#${r.pr.number} ${r.pr.state}` : "none"} \u2014 tag: ${r.tagPushed ? "pushed" : "absent"} \u2014 Release: ${r.releaseExists ? "exists" : "absent"}`,
|
|
42488
42657
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
42489
42658
|
` - npm @mutmutco/cli: ${r.npmVersion}`,
|
|
42490
42659
|
` - next: ${r.next}`,
|
|
@@ -42514,13 +42683,13 @@ async function runHotfixSub(sub, body, json, render) {
|
|
|
42514
42683
|
return failGraceful(`hotfix ${sub}: ${e.message}`);
|
|
42515
42684
|
}
|
|
42516
42685
|
}
|
|
42517
|
-
var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestrator: start
|
|
42686
|
+
var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestrator: start \u2014 release, with status (bare command prints the dry-run plan; no back-merge \u2014 #839)").option("--json", "machine-readable output").option("--apply", "not a verb; use the hotfix subcommands (start/release/status)").action(async (o) => {
|
|
42518
42687
|
try {
|
|
42519
42688
|
await requireFreshTrainCli("hotfix");
|
|
42520
42689
|
} catch (e) {
|
|
42521
42690
|
return fail(`hotfix: ${e.message}`);
|
|
42522
42691
|
}
|
|
42523
|
-
if (o.apply) return fail("hotfix: use the stepwise subcommands
|
|
42692
|
+
if (o.apply) return fail("hotfix: use the stepwise subcommands \u2014 mmi-cli devops hotfix start --from <pr#|sha> \u2014 release <vX.Y.Z> \u2014 status [vX.Y.Z]");
|
|
42524
42693
|
const steps = trainPlan("hotfix");
|
|
42525
42694
|
console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli devops hotfix: dry-run plan", steps));
|
|
42526
42695
|
});
|
|
@@ -42552,7 +42721,7 @@ ci.command("audit").description("read-only fleet scan: gate workflow, ruleset co
|
|
|
42552
42721
|
else console.log(renderCiAuditText(report));
|
|
42553
42722
|
if (!report.ok) process.exitCode = 1;
|
|
42554
42723
|
});
|
|
42555
|
-
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it
|
|
42724
|
+
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
|
|
42556
42725
|
if (o.apply && o.parkRuleset) {
|
|
42557
42726
|
return fail("ci reconcile: --apply and --park-ruleset ask for opposite things; pass one");
|
|
42558
42727
|
}
|
|
@@ -42575,7 +42744,7 @@ ci.command("reconcile").description("audit + optionally apply merge settings and
|
|
|
42575
42744
|
${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${r.errors.length ? ` errors=[${r.errors.join("; ")}]` : ""}`);
|
|
42576
42745
|
}
|
|
42577
42746
|
} else {
|
|
42578
|
-
console.log("\nDry-run
|
|
42747
|
+
console.log("\nDry-run \u2014 re-run with --apply to patch merge settings and activate product rulesets (master-admin).");
|
|
42579
42748
|
}
|
|
42580
42749
|
}
|
|
42581
42750
|
const applyFailed = applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
|
|
@@ -42603,7 +42772,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
42603
42772
|
if (!a.train) process.exitCode = 1;
|
|
42604
42773
|
return;
|
|
42605
42774
|
}
|
|
42606
|
-
console.log(`${a.repo}: @${a.login} is ${a.role}
|
|
42775
|
+
console.log(`${a.repo}: @${a.login} is ${a.role} \u2014 train ${a.train ? "AUTHORIZED" : "not authorized"}${a.hubTrainMasterOnly ? " (Hub train is master-only)" : ""}`);
|
|
42607
42776
|
if (!a.train) process.exitCode = 1;
|
|
42608
42777
|
});
|
|
42609
42778
|
access.command("audit").description("audit collaborator roles + train-branch push allowlists vs the locked state; read-only, emits gh remediation, never applies").option("--json", "machine-readable output").option("--repo <owner/repo>", "audit a single repo instead of the whole org").option("--class <class>", "repo class for --repo (deployable | content)", "deployable").action(async () => {
|
|
@@ -42638,9 +42807,9 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
42638
42807
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
42639
42808
|
if (!report.ok) process.exitCode = 1;
|
|
42640
42809
|
});
|
|
42641
|
-
access.command("capabilities").description("enumerate your effective vault reach
|
|
42810
|
+
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
42642
42811
|
var isWin2 = process.platform === "win32";
|
|
42643
|
-
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft
|
|
42812
|
+
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft \u2014 repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes (#4199, canon); heal env drift with a plain run or --no-repo-writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
|
|
42644
42813
|
if (opts.guide) {
|
|
42645
42814
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
42646
42815
|
return;
|
|
@@ -42794,7 +42963,7 @@ program2.command("plugin-release-catchup").description("install a newer released
|
|
|
42794
42963
|
});
|
|
42795
42964
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
42796
42965
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
42797
|
-
console.error("[mmi-hook] plugin session-start: cwd is a repository SUBDIRECTORY
|
|
42966
|
+
console.error("[mmi-hook] plugin session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
|
|
42798
42967
|
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
42799
42968
|
return;
|
|
42800
42969
|
}
|
|
@@ -42807,7 +42976,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
42807
42976
|
const bannerIo = measured.io;
|
|
42808
42977
|
const { parallel, sequential } = buildSessionStartPlan({
|
|
42809
42978
|
// whoami (#879): surface the resolved human so agents act --for them without asking. Silent
|
|
42810
|
-
// when unknown
|
|
42979
|
+
// when unknown — a missing gh login must not noise or fail the banner.
|
|
42811
42980
|
whoami: async (io) => {
|
|
42812
42981
|
const report = await resolveWhoami({
|
|
42813
42982
|
hubSession: async () => hubAuthSession({ baseUrl: (await loadConfig()).sagaApiUrl ?? defaultHubUrl(), githubToken }),
|
|
@@ -42819,14 +42988,14 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
42819
42988
|
if (authority) io.log(authority);
|
|
42820
42989
|
},
|
|
42821
42990
|
// command-ladder hint (#2609): compact gh?mmi-cli cheat sheet so agents reach for mmi-cli first,
|
|
42822
|
-
// not after a denied gh call. <1KB, pure in-memory
|
|
42991
|
+
// not after a denied gh call. <1KB, pure in-memory — no network, no fs, fail-soft.
|
|
42823
42992
|
commandLadderHint: async (io) => {
|
|
42824
42993
|
const hint = commandLadderHint();
|
|
42825
42994
|
if (hint) io.log(hint);
|
|
42826
42995
|
},
|
|
42827
42996
|
// stale worktrees: fast LOCAL-only git check (worktree list + branch -vv gone status). Flags leaked
|
|
42828
42997
|
// worktrees / merged-but-undeleted branches so the session never starts atop leftover state. No
|
|
42829
|
-
// network
|
|
42998
|
+
// network — never the ~20s `gh pr list` path of `worktree list --stale`. Fail-soft.
|
|
42830
42999
|
staleWorktrees: async (io) => {
|
|
42831
43000
|
const line = await gatherStaleWorktreeWarning();
|
|
42832
43001
|
if (line) io.log(line);
|
|
@@ -42839,7 +43008,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
42839
43008
|
const line = localTrainSyncBannerLine(result);
|
|
42840
43009
|
if (line) io.log(line);
|
|
42841
43010
|
},
|
|
42842
|
-
// #3485 item 7: the ONLY lane that throttles the npm read
|
|
43011
|
+
// #3485 item 7: the ONLY lane that throttles the npm read — it runs on every session start, on a
|
|
42843
43012
|
// blocking hook. Every interactive lane still reads live.
|
|
42844
43013
|
doctor: async (io) => {
|
|
42845
43014
|
await runDoctorClean({ banner: true }, io, mmiDoctorDeps({ throttleReleasedRead: true, program: program2 }));
|
|
@@ -42849,11 +43018,11 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
42849
43018
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
42850
43019
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
42851
43020
|
if (worktreeBanner) {
|
|
42852
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
43021
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
42853
43022
|
bannerIo.log(worktreeBanner);
|
|
42854
43023
|
}
|
|
42855
43024
|
if (shouldSpawnReleaseCatchup((0, import_node_os19.homedir)(), process.env, readReleaseCatchupState)) {
|
|
42856
|
-
spawnDetachedSelf(["plugin", "release-catchup", "--quiet"], { spawn:
|
|
43025
|
+
spawnDetachedSelf(["plugin", "release-catchup", "--quiet"], { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
42857
43026
|
}
|
|
42858
43027
|
if (isLinkedWorktree(process.cwd())) {
|
|
42859
43028
|
const primaryRoot = await primaryCheckoutRoot(process.cwd());
|