@mutmutco/cli 3.30.1 → 3.32.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/README.md +10 -10
- package/dist/main.cjs +748 -383
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4337,7 +4337,7 @@ function formatScratchGcPlan(plan, applied, applyResult) {
|
|
|
4337
4337
|
function scratchGcBannerLine(safeAutoPruned, advisory) {
|
|
4338
4338
|
const parts = [];
|
|
4339
4339
|
if (safeAutoPruned > 0) parts.push(`pruned ${safeAutoPruned} scratch item(s)`);
|
|
4340
|
-
if (advisory > 0) parts.push(`${advisory} kept scratch item(s) need review \u2014 \`mmi-cli gc --scratch --dry-run\``);
|
|
4340
|
+
if (advisory > 0) parts.push(`${advisory} kept scratch item(s) need review \u2014 \`mmi-cli worktree gc --scratch --dry-run\``);
|
|
4341
4341
|
return parts.length ? `[cleanup] ${parts.join("; ")}` : void 0;
|
|
4342
4342
|
}
|
|
4343
4343
|
function isLinkLike(path2, symbolic = false) {
|
|
@@ -4359,9 +4359,9 @@ function treeOlderThan(root, now, floor) {
|
|
|
4359
4359
|
if (now - st.mtimeMs <= floor) return false;
|
|
4360
4360
|
if (!st.isDirectory()) continue;
|
|
4361
4361
|
for (const ent of (0, import_node_fs6.readdirSync)(current, { withFileTypes: true })) {
|
|
4362
|
-
const
|
|
4363
|
-
if (isLinkLike(
|
|
4364
|
-
stack.push(
|
|
4362
|
+
const child2 = (0, import_node_path4.join)(current, ent.name);
|
|
4363
|
+
if (isLinkLike(child2, ent.isSymbolicLink())) return false;
|
|
4364
|
+
stack.push(child2);
|
|
4365
4365
|
}
|
|
4366
4366
|
}
|
|
4367
4367
|
return true;
|
|
@@ -4481,16 +4481,16 @@ function rootScratchDirSnapshot(root, readdir, stat2) {
|
|
|
4481
4481
|
while (stack.length) {
|
|
4482
4482
|
const current = stack.pop();
|
|
4483
4483
|
for (const ent of readdir(current, { withFileTypes: true })) {
|
|
4484
|
-
const
|
|
4484
|
+
const child2 = (0, import_node_path4.join)(current, ent.name);
|
|
4485
4485
|
if (ent.isSymbolicLink?.()) return null;
|
|
4486
4486
|
try {
|
|
4487
|
-
(0, import_node_fs6.readlinkSync)(
|
|
4487
|
+
(0, import_node_fs6.readlinkSync)(child2);
|
|
4488
4488
|
return null;
|
|
4489
4489
|
} catch {
|
|
4490
4490
|
}
|
|
4491
|
-
const st = stat2(
|
|
4491
|
+
const st = stat2(child2);
|
|
4492
4492
|
mtimeMs = Math.max(mtimeMs, st.mtimeMs);
|
|
4493
|
-
if (ent.isDirectory()) stack.push(
|
|
4493
|
+
if (ent.isDirectory()) stack.push(child2);
|
|
4494
4494
|
else if (ent.isFile()) bytes += st.size;
|
|
4495
4495
|
}
|
|
4496
4496
|
}
|
|
@@ -5156,7 +5156,7 @@ var import_node_fs10 = require("node:fs");
|
|
|
5156
5156
|
|
|
5157
5157
|
// src/gc.ts
|
|
5158
5158
|
var import_node_path7 = require("node:path");
|
|
5159
|
-
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc sweep-deferred";
|
|
5159
|
+
var DEFERRED_SWEEP_COMMAND = "mmi-cli worktree gc sweep-deferred";
|
|
5160
5160
|
var DEFERRED_NOTE = "Worktree cleanup queued (IDE lock). Detached sweep will retry automatically \u2014 no human action required.";
|
|
5161
5161
|
var PRESERVED_WORKTREE_CONFIG = "mmi.preservedWorktreeBranch";
|
|
5162
5162
|
var WORKTREE_LOCK_RE = /EPERM|EBUSY|EACCES|ENOTEMPTY|permission denied|access is denied|used by another process|resource busy|directory not empty/i;
|
|
@@ -5858,11 +5858,11 @@ function selectSafeWorktreeCwd(worktrees, targetPath, options) {
|
|
|
5858
5858
|
return worktrees.find((w) => !samePath(w.path, targetPath) && exists(w.path))?.path;
|
|
5859
5859
|
}
|
|
5860
5860
|
function isPathUnderDirectory(childPath, parentPath) {
|
|
5861
|
-
const
|
|
5861
|
+
const child2 = normPath(childPath);
|
|
5862
5862
|
const parent = normPath(parentPath);
|
|
5863
|
-
if (!
|
|
5864
|
-
if (
|
|
5865
|
-
return
|
|
5863
|
+
if (!child2 || !parent) return false;
|
|
5864
|
+
if (child2 === parent) return true;
|
|
5865
|
+
return child2.startsWith(`${parent}/`);
|
|
5866
5866
|
}
|
|
5867
5867
|
function planReleaseCwdBeforeWorktreeRemoval(targetPath, safeCwd, currentCwd) {
|
|
5868
5868
|
if (!safeCwd || !isPathUnderDirectory(currentCwd, targetPath)) return void 0;
|
|
@@ -6113,7 +6113,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
6113
6113
|
return report;
|
|
6114
6114
|
}
|
|
6115
6115
|
function formatGcPlan(plan, apply) {
|
|
6116
|
-
const lines = [`mmi-cli gc: ${apply ? "apply" : "dry-run"}`];
|
|
6116
|
+
const lines = [`mmi-cli worktree gc: ${apply ? "apply" : "dry-run"}`];
|
|
6117
6117
|
if (!plan.branches.length && !plan.trackingRefs.length && !plan.worktreeDirs.length) lines.push("nothing to clean");
|
|
6118
6118
|
if (plan.branches.length) {
|
|
6119
6119
|
lines.push("local branches:");
|
|
@@ -6579,10 +6579,10 @@ async function secretsCapabilities(deps, opts) {
|
|
|
6579
6579
|
} catch (e) {
|
|
6580
6580
|
const message = e.message;
|
|
6581
6581
|
if (isTimeoutError(e)) {
|
|
6582
|
-
deps.err(`access capabilities: timed out after ${CAPABILITIES_TIMEOUT_MS}ms while aggregating vault scopes for ${repo}. No access conclusion was made; retry with a warm Hub or run scoped reads such as \`mmi-cli secrets list --repo ${repo}\` while investigating the slow source.`);
|
|
6582
|
+
deps.err(`org access capabilities: timed out after ${CAPABILITIES_TIMEOUT_MS}ms while aggregating vault scopes for ${repo}. No access conclusion was made; retry with a warm Hub or run scoped reads such as \`mmi-cli secrets list --repo ${repo}\` while investigating the slow source.`);
|
|
6583
6583
|
return;
|
|
6584
6584
|
}
|
|
6585
|
-
deps.err(`access capabilities: ${message}`);
|
|
6585
|
+
deps.err(`org access capabilities: ${message}`);
|
|
6586
6586
|
return;
|
|
6587
6587
|
}
|
|
6588
6588
|
if (!res.ok) {
|
|
@@ -6591,10 +6591,10 @@ async function secretsCapabilities(deps, opts) {
|
|
|
6591
6591
|
deps.err(upgrade);
|
|
6592
6592
|
} else if (res.status === 404) {
|
|
6593
6593
|
deps.err(
|
|
6594
|
-
"access capabilities: the Hub API did not recognize /secrets/capabilities (HTTP 404). This endpoint ships in the Hub, so a 404 means the deployed Hub predates this command and should answer once a Hub release carrying this command is deployed \u2014 it is not an authorization or missing-credential error."
|
|
6594
|
+
"org access capabilities: the Hub API did not recognize /secrets/capabilities (HTTP 404). This endpoint ships in the Hub, so a 404 means the deployed Hub predates this command and should answer once a Hub release carrying this command is deployed \u2014 it is not an authorization or missing-credential error."
|
|
6595
6595
|
);
|
|
6596
6596
|
} else {
|
|
6597
|
-
deps.err(`access capabilities failed: HTTP ${res.status}${await readErr(res)}`);
|
|
6597
|
+
deps.err(`org access capabilities failed: HTTP ${res.status}${await readErr(res)}`);
|
|
6598
6598
|
}
|
|
6599
6599
|
return;
|
|
6600
6600
|
}
|
|
@@ -7644,9 +7644,9 @@ function resolveBoardConfig(cfg) {
|
|
|
7644
7644
|
const gap = diagnoseBoardConfigGap(cfg);
|
|
7645
7645
|
if (gap) {
|
|
7646
7646
|
throw gap.kind === "no-project-detected" ? new Error(
|
|
7647
|
-
"no MMI project detected for this workspace (no Hub registry match for this repo, or no git origin in this folder); run `mmi-cli board read --repo <owner/repo>` to target a specific registered repo, or `mmi-cli project get <owner/repo>` to check registration"
|
|
7647
|
+
"no MMI project detected for this workspace (no Hub registry match for this repo, or no git origin in this folder); run `mmi-cli board read --repo <owner/repo>` to target a specific registered repo, or `mmi-cli org project get <owner/repo>` to check registration"
|
|
7648
7648
|
) : new Error(
|
|
7649
|
-
`Hub registry board META missing ${gap.missing.join(", ")}; run \`gh auth login\`, then \`mmi-cli project get <owner/repo>\`, or ask a master-admin to register/backfill board coords`
|
|
7649
|
+
`Hub registry board META missing ${gap.missing.join(", ")}; run \`gh auth login\`, then \`mmi-cli org project get <owner/repo>\`, or ask a master-admin to register/backfill board coords`
|
|
7650
7650
|
);
|
|
7651
7651
|
}
|
|
7652
7652
|
return {
|
|
@@ -9120,7 +9120,7 @@ function decidePrMergeNoCiGuard(checks, policyReason = "registry META ci:none")
|
|
|
9120
9120
|
message: [
|
|
9121
9121
|
`${staleNote} and are not green (${checks}).`,
|
|
9122
9122
|
"Fix the checks or use `mmi-cli pr land` so the CLI waits.",
|
|
9123
|
-
"Then correct registry META: `mmi-cli project set <owner/repo> --var requiredChecks=<check-run-display-names>`."
|
|
9123
|
+
"Then correct registry META: `mmi-cli org project set <owner/repo> --var requiredChecks=<check-run-display-names>`."
|
|
9124
9124
|
].join(" ")
|
|
9125
9125
|
};
|
|
9126
9126
|
}
|
|
@@ -9277,31 +9277,45 @@ async function activateProductRuleset(repo, rulesetBody, client) {
|
|
|
9277
9277
|
}
|
|
9278
9278
|
|
|
9279
9279
|
// src/workflow-context.ts
|
|
9280
|
-
function
|
|
9280
|
+
function parseWorkflowJobs(yaml) {
|
|
9281
9281
|
const lines = yaml.split(/\r?\n/);
|
|
9282
9282
|
let inJobs = false;
|
|
9283
9283
|
let jobIndent = -1;
|
|
9284
|
-
const
|
|
9284
|
+
const jobs = [];
|
|
9285
|
+
let current = null;
|
|
9285
9286
|
for (const line of lines) {
|
|
9286
9287
|
if (!inJobs) {
|
|
9287
9288
|
if (/^jobs:\s*$/.test(line)) inJobs = true;
|
|
9288
9289
|
continue;
|
|
9289
9290
|
}
|
|
9290
9291
|
if (/^\S/.test(line) && !line.startsWith("#")) break;
|
|
9291
|
-
const trimmed = line.trim();
|
|
9292
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9293
9292
|
const match = line.match(/^(\s+)([A-Za-z0-9_-]+):\s*$/);
|
|
9294
|
-
if (
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
jobIndent
|
|
9298
|
-
|
|
9299
|
-
|
|
9293
|
+
if (match) {
|
|
9294
|
+
const indent = match[1].length;
|
|
9295
|
+
if (jobIndent < 0) jobIndent = indent;
|
|
9296
|
+
if (indent === jobIndent) {
|
|
9297
|
+
current = { id: match[2], body: line, indent };
|
|
9298
|
+
jobs.push(current);
|
|
9299
|
+
continue;
|
|
9300
|
+
}
|
|
9301
|
+
if (indent < jobIndent) break;
|
|
9300
9302
|
}
|
|
9301
|
-
if (
|
|
9302
|
-
|
|
9303
|
+
if (current) current.body += `
|
|
9304
|
+
${line}`;
|
|
9303
9305
|
}
|
|
9304
|
-
return
|
|
9306
|
+
return jobs;
|
|
9307
|
+
}
|
|
9308
|
+
function jobReportsPullRequestCheck(job) {
|
|
9309
|
+
const lines = job.body.split(/\r?\n/).slice(1);
|
|
9310
|
+
const propertyIndent = lines.filter((line) => line.trim() && !line.trimStart().startsWith("#")).map((line) => line.match(/^(\s+)/)?.[1].length ?? 0).filter((indent) => indent > job.indent).reduce((minimum, indent) => Math.min(minimum, indent), Number.POSITIVE_INFINITY);
|
|
9311
|
+
if (!Number.isFinite(propertyIndent)) return true;
|
|
9312
|
+
const directIf = lines.find((line) => {
|
|
9313
|
+
const match = line.match(/^(\s*)if:\s*(.+)$/);
|
|
9314
|
+
return match?.[1].length === propertyIndent;
|
|
9315
|
+
});
|
|
9316
|
+
if (!directIf) return true;
|
|
9317
|
+
const expression = directIf.replace(/^\s*if:\s*/, "").trim().replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "").trim();
|
|
9318
|
+
return !/^(?:github\.event_name\s*==\s*['"]push['"]|['"]push['"]\s*==\s*github\.event_name)$/.test(expression);
|
|
9305
9319
|
}
|
|
9306
9320
|
function workflowTriggersPullRequest(yaml) {
|
|
9307
9321
|
const onBlock = extractOnBlock(yaml);
|
|
@@ -9340,7 +9354,9 @@ function collectPullRequestWorkflowContexts(workflows) {
|
|
|
9340
9354
|
const contexts = /* @__PURE__ */ new Set();
|
|
9341
9355
|
for (const wf of workflows) {
|
|
9342
9356
|
if (!workflowTriggersPullRequest(wf.body)) continue;
|
|
9343
|
-
for (const
|
|
9357
|
+
for (const job of parseWorkflowJobs(wf.body)) {
|
|
9358
|
+
if (jobReportsPullRequestCheck(job)) contexts.add(job.id);
|
|
9359
|
+
}
|
|
9344
9360
|
}
|
|
9345
9361
|
return [...contexts].sort((a, b) => a.localeCompare(b));
|
|
9346
9362
|
}
|
|
@@ -9926,7 +9942,13 @@ async function auditRepoCi(repo, deps) {
|
|
|
9926
9942
|
label: "delete_branch_on_merge enabled",
|
|
9927
9943
|
detail: info.delete_branch_on_merge === true ? void 0 : "false or unavailable"
|
|
9928
9944
|
});
|
|
9929
|
-
const
|
|
9945
|
+
const hasCanonicalGateWorkflow = repoClass === "hub" ? true : repoClass === "content" ? true : await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH);
|
|
9946
|
+
let prWorkflowPaths = repoClass === "deployable" && hasCanonicalGateWorkflow ? [PRODUCT_GATE_PATH] : [];
|
|
9947
|
+
if (repoClass === "deployable" && !hasCanonicalGateWorkflow) {
|
|
9948
|
+
const listedWorkflowPaths = await listWorkflowPaths(deps, repo, baseBranch);
|
|
9949
|
+
prWorkflowPaths = listedWorkflowPaths === void 0 ? [] : await filterPullRequestTriggered(deps, repo, baseBranch, listedWorkflowPaths);
|
|
9950
|
+
}
|
|
9951
|
+
const hasGateWorkflow = hasCanonicalGateWorkflow || prWorkflowPaths.length > 0;
|
|
9930
9952
|
let emittedPrContexts;
|
|
9931
9953
|
const getEmittedPrContexts = async () => {
|
|
9932
9954
|
emittedPrContexts ??= await resolveEmittedPrContexts(deps, repo, baseBranch);
|
|
@@ -9939,7 +9961,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
9939
9961
|
ok: false,
|
|
9940
9962
|
label: "registry no-ci contradicts PR workflows",
|
|
9941
9963
|
detail: `registry META declares no-ci but pull_request workflows emit [${emitted.join(", ")}]`,
|
|
9942
|
-
remediation: `Fix the workflows or correct registry META: mmi-cli project set ${repo} --var requiredChecks=${JSON.stringify(emitted)}`
|
|
9964
|
+
remediation: `Fix the workflows or correct registry META: mmi-cli org project set ${repo} --var requiredChecks=${JSON.stringify(emitted)}`
|
|
9943
9965
|
});
|
|
9944
9966
|
}
|
|
9945
9967
|
}
|
|
@@ -9947,7 +9969,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
9947
9969
|
checks.push({
|
|
9948
9970
|
ok: hasGateWorkflow,
|
|
9949
9971
|
label: `gate workflow committed on ${baseBranch}`,
|
|
9950
|
-
detail:
|
|
9972
|
+
detail: hasCanonicalGateWorkflow ? `read ${PRODUCT_GATE_PATH} at refs/heads/${baseBranch}` : prWorkflowPaths.length > 0 ? `read PR workflows at refs/heads/${baseBranch}: ${prWorkflowPaths.join(", ")}` : `missing ${PRODUCT_GATE_PATH} and no PR-triggered workflows at refs/heads/${baseBranch}`,
|
|
9951
9973
|
remediation: `mmi-cli bootstrap apply ${repo} --class deployable --execute (seeds gate.yml)`
|
|
9952
9974
|
});
|
|
9953
9975
|
checks.push({
|
|
@@ -9989,7 +10011,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
9989
10011
|
}
|
|
9990
10012
|
}
|
|
9991
10013
|
}
|
|
9992
|
-
const workflowPaths =
|
|
10014
|
+
const workflowPaths = repoClass === "deployable" ? prWorkflowPaths : [];
|
|
9993
10015
|
const { policy, reason } = resolveMergeCiPolicy({
|
|
9994
10016
|
workflowPaths,
|
|
9995
10017
|
registryCi,
|
|
@@ -10000,7 +10022,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
10000
10022
|
ok: explicitNoCi,
|
|
10001
10023
|
label: "registry META declares intentional no-ci",
|
|
10002
10024
|
detail: explicitNoCi ? void 0 : "set ci:none and requiredChecks:[] in registry META",
|
|
10003
|
-
remediation: `mmi-cli project set ${repo} --var ci=none --var requiredChecks=[]`
|
|
10025
|
+
remediation: `mmi-cli org project set ${repo} --var ci=none --var requiredChecks=[]`
|
|
10004
10026
|
});
|
|
10005
10027
|
} else if (deployableGated) {
|
|
10006
10028
|
checks.push({
|
|
@@ -10038,7 +10060,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
10038
10060
|
ok: trackExpectsRc === rcPresent,
|
|
10039
10061
|
label: "release track matches branch topology",
|
|
10040
10062
|
detail: trackExpectsRc === rcPresent ? void 0 : `registry resolves '${effectiveTrack}' but the rc branch ${rcPresent ? "exists" : "is absent"} \u2014 set release track '${wantTrack}'`,
|
|
10041
|
-
remediation: trackExpectsRc === rcPresent ? void 0 : `mmi-cli project set ${repo} --release-track ${wantTrack}`
|
|
10063
|
+
remediation: trackExpectsRc === rcPresent ? void 0 : `mmi-cli org project set ${repo} --release-track ${wantTrack}`
|
|
10042
10064
|
});
|
|
10043
10065
|
}
|
|
10044
10066
|
}
|
|
@@ -10163,7 +10185,7 @@ async function seedGateYml(repo, deps, meta, result) {
|
|
|
10163
10185
|
}
|
|
10164
10186
|
const gate = meta?.gate;
|
|
10165
10187
|
if (!gate || typeof gate === "object" && Object.keys(gate).length === 0) {
|
|
10166
|
-
result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli project set ${repo} --var gate={...}\` first, then re-run reconcile`);
|
|
10188
|
+
result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli org project set ${repo} --var gate={...}\` first, then re-run reconcile`);
|
|
10167
10189
|
return;
|
|
10168
10190
|
}
|
|
10169
10191
|
const derivedVars = withDerivedRepoVars({ ...gateConfigToVars(gate), REPO_SLUG: parsed.slug }, parsed, "deployable", releaseTrack);
|
|
@@ -10364,20 +10386,20 @@ var import_node_util6 = require("node:util");
|
|
|
10364
10386
|
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
|
|
10365
10387
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
10366
10388
|
var EARLY_EXIT_GRACE_MS = 2e3;
|
|
10367
|
-
function waitForProcessStability(
|
|
10389
|
+
function waitForProcessStability(child2, graceMs = EARLY_EXIT_GRACE_MS) {
|
|
10368
10390
|
return new Promise((resolve5, reject) => {
|
|
10369
10391
|
let settled = false;
|
|
10370
10392
|
const finish = (fn) => {
|
|
10371
10393
|
if (settled) return;
|
|
10372
10394
|
settled = true;
|
|
10373
10395
|
clearTimeout(timer);
|
|
10374
|
-
|
|
10375
|
-
|
|
10396
|
+
child2.removeAllListeners("error");
|
|
10397
|
+
child2.removeAllListeners("exit");
|
|
10376
10398
|
fn();
|
|
10377
10399
|
};
|
|
10378
10400
|
const timer = setTimeout(() => finish(resolve5), graceMs);
|
|
10379
|
-
|
|
10380
|
-
|
|
10401
|
+
child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
|
|
10402
|
+
child2.on("exit", (code, signal) => {
|
|
10381
10403
|
const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
|
|
10382
10404
|
finish(() => reject(new Error(`stage process exited before health check (${detail})`)));
|
|
10383
10405
|
});
|
|
@@ -10512,9 +10534,9 @@ function parseStagePortFlag(raw) {
|
|
|
10512
10534
|
return port;
|
|
10513
10535
|
}
|
|
10514
10536
|
function pathUnder(childPath, parentPath) {
|
|
10515
|
-
const
|
|
10537
|
+
const child2 = normPath2(childPath);
|
|
10516
10538
|
const parent = normPath2(parentPath);
|
|
10517
|
-
return Boolean(
|
|
10539
|
+
return Boolean(child2 && parent && (child2 === parent || child2.startsWith(`${parent}/`)));
|
|
10518
10540
|
}
|
|
10519
10541
|
function stageStateMatchesRequiredCwd(state, requiredCwd) {
|
|
10520
10542
|
if (!state) return false;
|
|
@@ -10868,7 +10890,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10868
10890
|
let up = sub(config.up.trim());
|
|
10869
10891
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
10870
10892
|
const identity = await resolveStageIdentity(cwd);
|
|
10871
|
-
const
|
|
10893
|
+
const child2 = (0, import_node_child_process7.spawn)(up, {
|
|
10872
10894
|
cwd,
|
|
10873
10895
|
shell: true,
|
|
10874
10896
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
@@ -10881,7 +10903,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10881
10903
|
env: { ...process.env, ...vaultProcessEnv, ...stageProcessEnv(stagePort, extraEnv) }
|
|
10882
10904
|
});
|
|
10883
10905
|
const state = {
|
|
10884
|
-
pid:
|
|
10906
|
+
pid: child2.pid ?? 0,
|
|
10885
10907
|
command: up,
|
|
10886
10908
|
cwd,
|
|
10887
10909
|
statePath,
|
|
@@ -10895,7 +10917,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10895
10917
|
if (globalStatePath && globalStatePath !== statePath) writeState(globalStatePath, state);
|
|
10896
10918
|
try {
|
|
10897
10919
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
10898
|
-
else await waitForProcessStability(
|
|
10920
|
+
else await waitForProcessStability(child2);
|
|
10899
10921
|
} catch (e) {
|
|
10900
10922
|
await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd);
|
|
10901
10923
|
throw e;
|
|
@@ -10910,7 +10932,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10910
10932
|
message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}`
|
|
10911
10933
|
};
|
|
10912
10934
|
opts.onReady?.(result);
|
|
10913
|
-
|
|
10935
|
+
child2.unref();
|
|
10914
10936
|
return result;
|
|
10915
10937
|
}
|
|
10916
10938
|
async function runStage(config = {}, opts = {}) {
|
|
@@ -11127,7 +11149,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11127
11149
|
if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
|
|
11128
11150
|
} };
|
|
11129
11151
|
const write = deps.write ?? import_promises.writeFile;
|
|
11130
|
-
const
|
|
11152
|
+
const remove2 = deps.remove ?? import_promises.unlink;
|
|
11131
11153
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
11132
11154
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
11133
11155
|
const file = (0, import_node_path13.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
@@ -11138,7 +11160,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11138
11160
|
args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
|
|
11139
11161
|
cleanup: async () => {
|
|
11140
11162
|
try {
|
|
11141
|
-
await
|
|
11163
|
+
await remove2(file);
|
|
11142
11164
|
} catch {
|
|
11143
11165
|
}
|
|
11144
11166
|
}
|
|
@@ -11353,9 +11375,9 @@ async function resolveIssueNodeId(runGh, ref, fallbackRepo) {
|
|
|
11353
11375
|
}
|
|
11354
11376
|
async function linkSubIssue(runGh, parentRef, childRef, defaultRepo) {
|
|
11355
11377
|
const parent = parseIssueRef(parentRef);
|
|
11356
|
-
const
|
|
11378
|
+
const child2 = parseIssueRef(childRef);
|
|
11357
11379
|
const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
|
|
11358
|
-
const subIssueId = await resolveIssueNodeId(runGh,
|
|
11380
|
+
const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
|
|
11359
11381
|
const stdout = await runGh(buildAddSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
|
|
11360
11382
|
const result = parseAddSubIssueResult(stdout);
|
|
11361
11383
|
if (!result) throw new Error(`addSubIssue returned an unexpected response:
|
|
@@ -11390,9 +11412,9 @@ function parseRemoveSubIssueResult(stdout) {
|
|
|
11390
11412
|
}
|
|
11391
11413
|
async function unlinkSubIssue(runGh, parentRef, childRef, defaultRepo) {
|
|
11392
11414
|
const parent = parseIssueRef(parentRef);
|
|
11393
|
-
const
|
|
11415
|
+
const child2 = parseIssueRef(childRef);
|
|
11394
11416
|
const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
|
|
11395
|
-
const subIssueId = await resolveIssueNodeId(runGh,
|
|
11417
|
+
const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
|
|
11396
11418
|
const stdout = await runGh(buildRemoveSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
|
|
11397
11419
|
const result = parseRemoveSubIssueResult(stdout);
|
|
11398
11420
|
if (!result) throw new Error(`removeSubIssue returned an unexpected response:
|
|
@@ -11463,6 +11485,235 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
11463
11485
|
return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
|
|
11464
11486
|
}
|
|
11465
11487
|
|
|
11488
|
+
// src/command-taxonomy.ts
|
|
11489
|
+
var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
|
|
11490
|
+
var PRIMARY_GROUPS = [
|
|
11491
|
+
["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
|
|
11492
|
+
["Plan and work", ["board", "issue", "worktree", "stage"]],
|
|
11493
|
+
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
11494
|
+
["Setup and support", ["bootstrap", "secrets", "docs"]],
|
|
11495
|
+
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
11496
|
+
];
|
|
11497
|
+
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
11498
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "wave", "report", "skill-lesson"]);
|
|
11499
|
+
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
11500
|
+
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
11501
|
+
var topLevelPosition = 0;
|
|
11502
|
+
for (const [helpGroup, names] of PRIMARY_GROUPS) {
|
|
11503
|
+
HELP_GROUP_ORDER.set(helpGroup, HELP_GROUP_ORDER.size);
|
|
11504
|
+
for (const name of names) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
|
|
11505
|
+
}
|
|
11506
|
+
HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
|
|
11507
|
+
HELP_GROUP_ORDER.set("Compatibility", HELP_GROUP_ORDER.size);
|
|
11508
|
+
HELP_GROUP_ORDER.set("Delegated", HELP_GROUP_ORDER.size);
|
|
11509
|
+
for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
|
|
11510
|
+
TOP_LEVEL_ORDER.set("lane", topLevelPosition++);
|
|
11511
|
+
TOP_LEVEL_ORDER.set("fusion", topLevelPosition++);
|
|
11512
|
+
var PATH_OVERRIDES = {
|
|
11513
|
+
"board slice-refresh": { category: "internal", discovery: "hidden", help_group: "Operations" },
|
|
11514
|
+
lane: { category: "delegated", discovery: "hidden", help_group: "Delegated", replacement: "jerv-cli lane" },
|
|
11515
|
+
fusion: { category: "delegated", discovery: "hidden", help_group: "Delegated", replacement: "jerv-cli fusion" }
|
|
11516
|
+
};
|
|
11517
|
+
var HIDDEN_OPTIONS = {
|
|
11518
|
+
"worktree create": ["--base"],
|
|
11519
|
+
"org project doctor": ["--v2"],
|
|
11520
|
+
"org project heal": ["--v2"],
|
|
11521
|
+
"org project set": ["--set"]
|
|
11522
|
+
};
|
|
11523
|
+
function primaryMetadata(name) {
|
|
11524
|
+
for (const [helpGroup, names] of PRIMARY_GROUPS) {
|
|
11525
|
+
if (names.includes(name)) {
|
|
11526
|
+
return {
|
|
11527
|
+
category: SUPPORT_PRIMARY.has(name) ? "support" : "core",
|
|
11528
|
+
discovery: "primary",
|
|
11529
|
+
help_group: helpGroup
|
|
11530
|
+
};
|
|
11531
|
+
}
|
|
11532
|
+
}
|
|
11533
|
+
return void 0;
|
|
11534
|
+
}
|
|
11535
|
+
function topLevelMetadata(name) {
|
|
11536
|
+
const primary = primaryMetadata(name);
|
|
11537
|
+
if (primary) return primary;
|
|
11538
|
+
if (name === "lane" || name === "fusion") return PATH_OVERRIDES[name];
|
|
11539
|
+
if (!OPERATIONAL_TOP_LEVEL.has(name)) {
|
|
11540
|
+
throw new Error(`command taxonomy: unclassified top-level command "${name}"`);
|
|
11541
|
+
}
|
|
11542
|
+
return {
|
|
11543
|
+
category: name === "plugin" ? "internal" : "admin",
|
|
11544
|
+
discovery: "all-only",
|
|
11545
|
+
help_group: "Operations"
|
|
11546
|
+
};
|
|
11547
|
+
}
|
|
11548
|
+
function setCommandMetadata(command, metadata) {
|
|
11549
|
+
command[COMMAND_METADATA] = metadata;
|
|
11550
|
+
}
|
|
11551
|
+
function commandMetadata(command) {
|
|
11552
|
+
return command[COMMAND_METADATA];
|
|
11553
|
+
}
|
|
11554
|
+
function classifyTree(command, path2, inherited) {
|
|
11555
|
+
const metadata = PATH_OVERRIDES[path2] ?? inherited;
|
|
11556
|
+
setCommandMetadata(command, metadata);
|
|
11557
|
+
if (metadata.discovery !== "primary") {
|
|
11558
|
+
command._hidden = true;
|
|
11559
|
+
}
|
|
11560
|
+
for (const option of command.options) {
|
|
11561
|
+
if ((HIDDEN_OPTIONS[path2] ?? []).includes(option.long ?? option.short ?? "")) option.hideHelp();
|
|
11562
|
+
}
|
|
11563
|
+
for (const child2 of command.commands) {
|
|
11564
|
+
const childPath = path2 ? `${path2} ${child2.name()}` : child2.name();
|
|
11565
|
+
classifyTree(child2, childPath, PATH_OVERRIDES[childPath] ?? metadata);
|
|
11566
|
+
}
|
|
11567
|
+
}
|
|
11568
|
+
function applyCommandTaxonomy(program3) {
|
|
11569
|
+
for (const command of program3.commands) {
|
|
11570
|
+
const metadata = topLevelMetadata(command.name());
|
|
11571
|
+
command.helpGroup(metadata.help_group);
|
|
11572
|
+
classifyTree(command, command.name(), metadata);
|
|
11573
|
+
}
|
|
11574
|
+
program3.configureHelp({
|
|
11575
|
+
visibleCommands(command) {
|
|
11576
|
+
const visible = command.commands.filter((child2) => !child2._hidden);
|
|
11577
|
+
const helpCommand = command._getHelpCommand();
|
|
11578
|
+
if (helpCommand && !helpCommand._hidden) visible.push(helpCommand);
|
|
11579
|
+
if (command === program3) visible.sort((a, b) => commandTaxonomyRank(a.name()) - commandTaxonomyRank(b.name()));
|
|
11580
|
+
return visible;
|
|
11581
|
+
},
|
|
11582
|
+
groupItems(unsortedItems, visibleItems, getGroup) {
|
|
11583
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11584
|
+
for (const item of unsortedItems) {
|
|
11585
|
+
const group = getGroup(item);
|
|
11586
|
+
if (!groups.has(group)) groups.set(group, []);
|
|
11587
|
+
}
|
|
11588
|
+
for (const item of visibleItems) {
|
|
11589
|
+
const group = getGroup(item);
|
|
11590
|
+
if (!groups.has(group)) groups.set(group, []);
|
|
11591
|
+
groups.get(group).push(item);
|
|
11592
|
+
}
|
|
11593
|
+
return new Map([...groups].sort(([left], [right]) => {
|
|
11594
|
+
const leftRank = HELP_GROUP_ORDER.get(left) ?? Number.MAX_SAFE_INTEGER;
|
|
11595
|
+
const rightRank = HELP_GROUP_ORDER.get(right) ?? Number.MAX_SAFE_INTEGER;
|
|
11596
|
+
return leftRank - rightRank;
|
|
11597
|
+
}));
|
|
11598
|
+
}
|
|
11599
|
+
});
|
|
11600
|
+
}
|
|
11601
|
+
function commandTaxonomyRank(name) {
|
|
11602
|
+
return TOP_LEVEL_ORDER.get(name) ?? Number.MAX_SAFE_INTEGER;
|
|
11603
|
+
}
|
|
11604
|
+
function commandHelpGroupRank(group) {
|
|
11605
|
+
return HELP_GROUP_ORDER.get(group) ?? Number.MAX_SAFE_INTEGER;
|
|
11606
|
+
}
|
|
11607
|
+
function isCanonicalSuggestion(metadata) {
|
|
11608
|
+
return metadata.category !== "internal" && metadata.category !== "delegated" && metadata.category !== "shim" && metadata.category !== "obsolete";
|
|
11609
|
+
}
|
|
11610
|
+
|
|
11611
|
+
// src/command-shims.ts
|
|
11612
|
+
var COMMAND_SHIMS = [
|
|
11613
|
+
{ legacy: "project deploy list", canonical: "org project deploy get", category: "shim" },
|
|
11614
|
+
{ legacy: "project resolve", canonical: "org project deploy get", category: "obsolete" },
|
|
11615
|
+
{ legacy: "tenant readiness", canonical: "runtime tenant status", category: "shim" },
|
|
11616
|
+
{ legacy: "secrets edit", canonical: "secrets set", category: "shim" },
|
|
11617
|
+
{ legacy: "registry org", canonical: "org config get", category: "shim" },
|
|
11618
|
+
{ legacy: "rules gitignore", canonical: "org rules gitignore", category: "shim" },
|
|
11619
|
+
{ legacy: "deploy status", canonical: "runtime deploy status", category: "shim" },
|
|
11620
|
+
{ legacy: "full-track readiness", canonical: "train readiness", category: "shim" },
|
|
11621
|
+
{ legacy: "docs-audit", canonical: "docs audit", category: "shim", subtree: true },
|
|
11622
|
+
{ legacy: "plugin-heal", canonical: "plugin heal", category: "shim" },
|
|
11623
|
+
{ legacy: "plugin-prune", canonical: "plugin prune", category: "shim" },
|
|
11624
|
+
{ legacy: "session-start", canonical: "plugin session-start", category: "shim" },
|
|
11625
|
+
{ legacy: "port-range", canonical: "stage port-range", category: "shim" },
|
|
11626
|
+
{ legacy: "schedules", canonical: "org schedules", category: "shim" },
|
|
11627
|
+
{ legacy: "guard", canonical: "plugin guard", category: "shim" },
|
|
11628
|
+
{ legacy: "project", canonical: "org project", category: "shim", subtree: true },
|
|
11629
|
+
{ legacy: "oauth", canonical: "org oauth", category: "shim", subtree: true },
|
|
11630
|
+
{ legacy: "access", canonical: "org access", category: "shim", subtree: true },
|
|
11631
|
+
{ legacy: "tenant", canonical: "runtime tenant", category: "shim", subtree: true },
|
|
11632
|
+
{ legacy: "box", canonical: "runtime box", category: "shim", subtree: true },
|
|
11633
|
+
{ legacy: "edge", canonical: "runtime edge", category: "shim", subtree: true },
|
|
11634
|
+
{ legacy: "gc", canonical: "worktree gc", category: "shim", subtree: true }
|
|
11635
|
+
];
|
|
11636
|
+
function rewriteLegacyArgv(argv) {
|
|
11637
|
+
const commandArgs = argv.slice(2);
|
|
11638
|
+
for (const shim of COMMAND_SHIMS) {
|
|
11639
|
+
const legacy = shim.legacy.split(" ");
|
|
11640
|
+
if (!legacy.every((token, index) => commandArgs[index] === token)) continue;
|
|
11641
|
+
const canonical = shim.canonical.split(" ");
|
|
11642
|
+
return {
|
|
11643
|
+
argv: [...argv.slice(0, 2), ...canonical, ...commandArgs.slice(legacy.length)],
|
|
11644
|
+
shim,
|
|
11645
|
+
warning: `mmi-cli: \`${shim.legacy}\` moved to \`${shim.canonical}\`; use \`mmi-cli ${shim.canonical}\`.`
|
|
11646
|
+
};
|
|
11647
|
+
}
|
|
11648
|
+
return { argv: [...argv] };
|
|
11649
|
+
}
|
|
11650
|
+
|
|
11651
|
+
// src/command-consolidation.ts
|
|
11652
|
+
var COMMAND_NAMESPACES_CONSOLIDATED = /* @__PURE__ */ Symbol.for("mmi.commandNamespaces.consolidated");
|
|
11653
|
+
function child(parent, name) {
|
|
11654
|
+
const found = parent.commands.find((command) => command.name() === name);
|
|
11655
|
+
if (!found) throw new Error(`command consolidation: missing "${parent.name()} ${name}"`);
|
|
11656
|
+
return found;
|
|
11657
|
+
}
|
|
11658
|
+
function detach(parent, command) {
|
|
11659
|
+
const commands = parent.commands;
|
|
11660
|
+
const index = commands.indexOf(command);
|
|
11661
|
+
if (index < 0) throw new Error(`command consolidation: "${command.name()}" is not a child of "${parent.name()}"`);
|
|
11662
|
+
commands.splice(index, 1);
|
|
11663
|
+
return command;
|
|
11664
|
+
}
|
|
11665
|
+
function move(parent, destination, name, nextName = name) {
|
|
11666
|
+
const command = detach(parent, child(parent, name));
|
|
11667
|
+
command.name(nextName);
|
|
11668
|
+
destination.addCommand(command);
|
|
11669
|
+
return command;
|
|
11670
|
+
}
|
|
11671
|
+
function remove(parent, name) {
|
|
11672
|
+
detach(parent, child(parent, name));
|
|
11673
|
+
}
|
|
11674
|
+
function consolidateCommandNamespaces(program3) {
|
|
11675
|
+
const org = program3.command("org").description("organization projects, access, rules, credentials, and schedules");
|
|
11676
|
+
const runtime = program3.command("runtime").description("tenant, deploy, box, and edge operations");
|
|
11677
|
+
const plugin = program3.command("plugin").description("plugin lifecycle and guard operations");
|
|
11678
|
+
move(program3, org, "project");
|
|
11679
|
+
move(program3, org, "oauth");
|
|
11680
|
+
move(program3, org, "access");
|
|
11681
|
+
move(program3, org, "schedules");
|
|
11682
|
+
const config = org.command("config").description("organization configuration");
|
|
11683
|
+
const registry2 = child(program3, "registry");
|
|
11684
|
+
move(registry2, config, "org", "get");
|
|
11685
|
+
remove(program3, "registry");
|
|
11686
|
+
const orgRules = org.command("rules").description("organization-managed repository rules");
|
|
11687
|
+
const rules2 = child(program3, "rules");
|
|
11688
|
+
move(rules2, orgRules, "gitignore");
|
|
11689
|
+
remove(program3, "rules");
|
|
11690
|
+
move(program3, runtime, "tenant");
|
|
11691
|
+
const runtimeDeploy = runtime.command("deploy").description("runtime deployment status");
|
|
11692
|
+
const deploy = child(program3, "deploy");
|
|
11693
|
+
move(deploy, runtimeDeploy, "status");
|
|
11694
|
+
remove(program3, "deploy");
|
|
11695
|
+
move(program3, runtime, "box");
|
|
11696
|
+
move(program3, runtime, "edge");
|
|
11697
|
+
move(program3, plugin, "guard");
|
|
11698
|
+
move(program3, plugin, "plugin-heal", "heal");
|
|
11699
|
+
move(program3, plugin, "plugin-prune", "prune");
|
|
11700
|
+
move(program3, plugin, "session-start");
|
|
11701
|
+
const docs2 = child(program3, "docs");
|
|
11702
|
+
move(program3, docs2, "docs-audit", "audit");
|
|
11703
|
+
const train = child(program3, "train");
|
|
11704
|
+
const fullTrack2 = child(program3, "full-track");
|
|
11705
|
+
move(fullTrack2, train, "readiness");
|
|
11706
|
+
remove(program3, "full-track");
|
|
11707
|
+
const worktree2 = child(program3, "worktree");
|
|
11708
|
+
move(program3, worktree2, "gc");
|
|
11709
|
+
const stage = child(program3, "stage");
|
|
11710
|
+
move(program3, stage, "port-range");
|
|
11711
|
+
program3[COMMAND_NAMESPACES_CONSOLIDATED] = true;
|
|
11712
|
+
}
|
|
11713
|
+
function hasConsolidatedCommandNamespaces(program3) {
|
|
11714
|
+
return program3[COMMAND_NAMESPACES_CONSOLIDATED] === true;
|
|
11715
|
+
}
|
|
11716
|
+
|
|
11466
11717
|
// src/command-manifest.ts
|
|
11467
11718
|
var EXAMPLES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.examples");
|
|
11468
11719
|
var COMMON_MISTAKES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.commonMistakes");
|
|
@@ -11502,17 +11753,24 @@ function buildOption(opt) {
|
|
|
11502
11753
|
if (opt.description) out.description = opt.description;
|
|
11503
11754
|
if (opt.defaultValue !== void 0) out.default = opt.defaultValue;
|
|
11504
11755
|
if (opt.argChoices && opt.argChoices.length) out.enum = [...opt.argChoices];
|
|
11756
|
+
if (opt.hidden) out.discovery = "all-only";
|
|
11505
11757
|
return out;
|
|
11506
11758
|
}
|
|
11507
11759
|
function buildCommand(cmd, path2) {
|
|
11760
|
+
const metadata = commandMetadata(cmd) ?? {
|
|
11761
|
+
category: "core",
|
|
11762
|
+
discovery: "primary",
|
|
11763
|
+
help_group: "Plan and work"
|
|
11764
|
+
};
|
|
11508
11765
|
const out = {
|
|
11509
11766
|
name: cmd.name(),
|
|
11510
11767
|
path: path2,
|
|
11511
11768
|
arguments: cmd.registeredArguments.map(buildArgument),
|
|
11512
11769
|
options: cmd.options.map(buildOption),
|
|
11513
11770
|
subcommands: cmd.commands.map(
|
|
11514
|
-
(
|
|
11515
|
-
)
|
|
11771
|
+
(child2) => buildCommand(child2, path2 ? `${path2} ${child2.name()}` : child2.name())
|
|
11772
|
+
),
|
|
11773
|
+
...metadata
|
|
11516
11774
|
};
|
|
11517
11775
|
const description = cmd.description();
|
|
11518
11776
|
if (description) out.description = description;
|
|
@@ -11522,39 +11780,140 @@ function buildCommand(cmd, path2) {
|
|
|
11522
11780
|
if (commonMistakes) out.common_mistakes = commonMistakes;
|
|
11523
11781
|
return out;
|
|
11524
11782
|
}
|
|
11783
|
+
function findTreeCommand(root, path2) {
|
|
11784
|
+
if (root.path === path2) return root;
|
|
11785
|
+
for (const child2 of root.subcommands) {
|
|
11786
|
+
const found = findTreeCommand(child2, path2);
|
|
11787
|
+
if (found) return found;
|
|
11788
|
+
}
|
|
11789
|
+
return void 0;
|
|
11790
|
+
}
|
|
11791
|
+
function virtualCopy(source, path2, shim) {
|
|
11792
|
+
return {
|
|
11793
|
+
...source,
|
|
11794
|
+
name: path2.split(" ").at(-1),
|
|
11795
|
+
path: path2,
|
|
11796
|
+
category: shim.category,
|
|
11797
|
+
discovery: "all-only",
|
|
11798
|
+
help_group: "Compatibility",
|
|
11799
|
+
replacement: source.path,
|
|
11800
|
+
subcommands: shim.subtree ? source.subcommands.map((child2) => virtualCopy(child2, `${path2} ${child2.name}`, shim)) : []
|
|
11801
|
+
};
|
|
11802
|
+
}
|
|
11803
|
+
function virtualParent(path2, shim) {
|
|
11804
|
+
return {
|
|
11805
|
+
name: path2.split(" ").at(-1),
|
|
11806
|
+
path: path2,
|
|
11807
|
+
description: `compatibility namespace; use mmi-cli ${shim.canonical}`,
|
|
11808
|
+
arguments: [],
|
|
11809
|
+
options: [],
|
|
11810
|
+
subcommands: [],
|
|
11811
|
+
category: "shim",
|
|
11812
|
+
discovery: "all-only",
|
|
11813
|
+
help_group: "Compatibility",
|
|
11814
|
+
replacement: shim.canonical
|
|
11815
|
+
};
|
|
11816
|
+
}
|
|
11817
|
+
function insertVirtualShim(root, shim) {
|
|
11818
|
+
const canonical = findTreeCommand(root, shim.canonical);
|
|
11819
|
+
if (!canonical) throw new Error(`command manifest: shim target missing "${shim.canonical}"`);
|
|
11820
|
+
const parts = shim.legacy.split(" ");
|
|
11821
|
+
let parent = root;
|
|
11822
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
11823
|
+
const path2 = parts.slice(0, index + 1).join(" ");
|
|
11824
|
+
let next = parent.subcommands.find((command) => command.name === parts[index]);
|
|
11825
|
+
if (!next) {
|
|
11826
|
+
next = virtualParent(path2, shim);
|
|
11827
|
+
parent.subcommands.push(next);
|
|
11828
|
+
}
|
|
11829
|
+
parent = next;
|
|
11830
|
+
}
|
|
11831
|
+
const copy = virtualCopy(canonical, shim.legacy, shim);
|
|
11832
|
+
const existingIndex = parent.subcommands.findIndex((command) => command.name === copy.name);
|
|
11833
|
+
if (existingIndex >= 0) parent.subcommands[existingIndex] = copy;
|
|
11834
|
+
else parent.subcommands.push(copy);
|
|
11835
|
+
}
|
|
11836
|
+
function addVirtualShims(root) {
|
|
11837
|
+
for (const shim of COMMAND_SHIMS.filter((entry) => entry.subtree)) insertVirtualShim(root, shim);
|
|
11838
|
+
for (const shim of COMMAND_SHIMS.filter((entry) => !entry.subtree)) insertVirtualShim(root, shim);
|
|
11839
|
+
}
|
|
11840
|
+
function primaryCopy(node, root = false) {
|
|
11841
|
+
if (!root && node.discovery !== "primary") return void 0;
|
|
11842
|
+
const subcommands = node.subcommands.map((child2) => primaryCopy(child2)).filter((child2) => Boolean(child2));
|
|
11843
|
+
if (root) subcommands.sort((a, b) => commandTaxonomyRank(a.name) - commandTaxonomyRank(b.name));
|
|
11844
|
+
return {
|
|
11845
|
+
...node,
|
|
11846
|
+
options: node.options.filter((option) => option.discovery !== "all-only" && option.discovery !== "hidden"),
|
|
11847
|
+
subcommands
|
|
11848
|
+
};
|
|
11849
|
+
}
|
|
11525
11850
|
function collectLeaves(node, acc) {
|
|
11526
11851
|
if (node.subcommands.length === 0) {
|
|
11527
11852
|
if (node.path) acc.push(node);
|
|
11528
11853
|
return;
|
|
11529
11854
|
}
|
|
11530
|
-
for (const
|
|
11855
|
+
for (const child2 of node.subcommands) collectLeaves(child2, acc);
|
|
11531
11856
|
}
|
|
11532
11857
|
function buildCommandManifest(program3) {
|
|
11533
11858
|
const tree = buildCommand(program3, "");
|
|
11859
|
+
if (hasConsolidatedCommandNamespaces(program3)) addVirtualShims(tree);
|
|
11534
11860
|
const index = [];
|
|
11535
11861
|
collectLeaves(tree, index);
|
|
11536
|
-
const
|
|
11862
|
+
const primaryTree = primaryCopy(tree, true);
|
|
11863
|
+
const primaryIndex = [];
|
|
11864
|
+
collectLeaves(primaryTree, primaryIndex);
|
|
11865
|
+
const manifest = {
|
|
11866
|
+
schema_version: 2,
|
|
11867
|
+
scope: "all",
|
|
11868
|
+
name: tree.name,
|
|
11869
|
+
tree,
|
|
11870
|
+
index,
|
|
11871
|
+
primary_tree: primaryTree,
|
|
11872
|
+
primary_index: primaryIndex,
|
|
11873
|
+
error_codes: ERROR_CODE_REFERENCE
|
|
11874
|
+
};
|
|
11537
11875
|
const version = program3.version();
|
|
11538
11876
|
if (version) manifest.version = version;
|
|
11539
11877
|
return manifest;
|
|
11540
11878
|
}
|
|
11879
|
+
function primaryCommandManifest(manifest) {
|
|
11880
|
+
return {
|
|
11881
|
+
schema_version: manifest.schema_version,
|
|
11882
|
+
scope: "primary",
|
|
11883
|
+
name: manifest.name,
|
|
11884
|
+
...manifest.version ? { version: manifest.version } : {},
|
|
11885
|
+
tree: manifest.primary_tree,
|
|
11886
|
+
index: manifest.primary_index,
|
|
11887
|
+
error_codes: manifest.error_codes
|
|
11888
|
+
};
|
|
11889
|
+
}
|
|
11541
11890
|
function argSignature(arg) {
|
|
11542
11891
|
const inner = arg.variadic ? `${arg.name}...` : arg.name;
|
|
11543
11892
|
return arg.required ? `<${inner}>` : `[${inner}]`;
|
|
11544
11893
|
}
|
|
11545
|
-
function formatManifestHuman(manifest) {
|
|
11546
|
-
const
|
|
11894
|
+
function formatManifestHuman(manifest, options = {}) {
|
|
11895
|
+
const root = options.all ? manifest.tree : manifest.primary_tree;
|
|
11896
|
+
const lines = [`${manifest.name}${manifest.version ? ` v${manifest.version}` : ""} \u2014 ${root.description ?? ""}`];
|
|
11547
11897
|
const render = (node, depth) => {
|
|
11548
11898
|
const indent = " ".repeat(depth);
|
|
11549
11899
|
const args = node.arguments.map(argSignature).join(" ");
|
|
11550
|
-
const head =
|
|
11900
|
+
const head = node.name + (args ? ` ${args}` : "");
|
|
11551
11901
|
lines.push(node.description ? `${indent}${head} \u2014 ${node.description}` : `${indent}${head}`);
|
|
11552
|
-
for (const opt of node.options) {
|
|
11902
|
+
for (const opt of options.all ? node.options : []) {
|
|
11553
11903
|
lines.push(`${indent} ${opt.flags}${opt.description ? ` ${opt.description}` : ""}`);
|
|
11554
11904
|
}
|
|
11555
|
-
for (const
|
|
11905
|
+
if (options.all) for (const child2 of node.subcommands) render(child2, depth + 1);
|
|
11556
11906
|
};
|
|
11557
|
-
|
|
11907
|
+
let currentGroup;
|
|
11908
|
+
const rootCommands = [...root.subcommands].sort((a, b) => commandHelpGroupRank(a.help_group) - commandHelpGroupRank(b.help_group) || commandTaxonomyRank(a.name) - commandTaxonomyRank(b.name));
|
|
11909
|
+
for (const command of rootCommands) {
|
|
11910
|
+
if (command.help_group !== currentGroup) {
|
|
11911
|
+
currentGroup = command.help_group;
|
|
11912
|
+
lines.push("", `${currentGroup}:`);
|
|
11913
|
+
}
|
|
11914
|
+
render(command, 1);
|
|
11915
|
+
}
|
|
11916
|
+
lines.push("", options.all ? "Use `mmi-cli explain <group|command>` for a focused map." : "Use `mmi-cli explain <group|command>` for detail or `mmi-cli commands --all` for operations and compatibility.");
|
|
11558
11917
|
return lines.join("\n");
|
|
11559
11918
|
}
|
|
11560
11919
|
|
|
@@ -11700,12 +12059,12 @@ function newestMtimeMs(path2, listDir, mtimeOf) {
|
|
|
11700
12059
|
return newest;
|
|
11701
12060
|
}
|
|
11702
12061
|
for (const e of entries) {
|
|
11703
|
-
const
|
|
12062
|
+
const child2 = `${path2}/${e.name}`;
|
|
11704
12063
|
let m = 0;
|
|
11705
|
-
if (e.isDirectory) m = newestMtimeMs(
|
|
12064
|
+
if (e.isDirectory) m = newestMtimeMs(child2, listDir, mtimeOf);
|
|
11706
12065
|
else {
|
|
11707
12066
|
try {
|
|
11708
|
-
m = mtimeOf(
|
|
12067
|
+
m = mtimeOf(child2);
|
|
11709
12068
|
} catch {
|
|
11710
12069
|
m = 0;
|
|
11711
12070
|
}
|
|
@@ -11781,12 +12140,12 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
|
|
|
11781
12140
|
const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
|
|
11782
12141
|
return { cacheRoot, present: true, keep, prune, bytes, running, ...stagingFields };
|
|
11783
12142
|
}
|
|
11784
|
-
function applyPluginCachePlan(plan,
|
|
12143
|
+
function applyPluginCachePlan(plan, remove2, stagingGuard) {
|
|
11785
12144
|
const removed = [];
|
|
11786
12145
|
const failed = [];
|
|
11787
12146
|
for (const version of plan.prune) {
|
|
11788
12147
|
try {
|
|
11789
|
-
|
|
12148
|
+
remove2(`${plan.cacheRoot}/${version}`);
|
|
11790
12149
|
removed.push(version);
|
|
11791
12150
|
} catch (e) {
|
|
11792
12151
|
failed.push({ version, error: e.message });
|
|
@@ -11814,7 +12173,7 @@ function applyPluginCachePlan(plan, remove, stagingGuard) {
|
|
|
11814
12173
|
}
|
|
11815
12174
|
}
|
|
11816
12175
|
try {
|
|
11817
|
-
|
|
12176
|
+
remove2(`${plan.stagingRoot}/${s.name}`);
|
|
11818
12177
|
removedStaging.push(s.name);
|
|
11819
12178
|
} catch (e) {
|
|
11820
12179
|
failedStaging.push({ name: s.name, error: e.message });
|
|
@@ -11834,7 +12193,7 @@ var CONCURRENT_SESSION_WARNING = "only the version THIS session runs from is pro
|
|
|
11834
12193
|
function renderPluginCachePlan(plan, applied) {
|
|
11835
12194
|
const mb = (b) => `${(b / 1e6).toFixed(1)} MB`;
|
|
11836
12195
|
if (!plan.present && plan.staging.length === 0) {
|
|
11837
|
-
return `plugin
|
|
12196
|
+
return `plugin prune: no plugin cache at ${plan.cacheRoot} \u2014 nothing to prune`;
|
|
11838
12197
|
}
|
|
11839
12198
|
const lines = [];
|
|
11840
12199
|
if (plan.present) {
|
|
@@ -11983,11 +12342,11 @@ function trainPlan(command, options = {}) {
|
|
|
11983
12342
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
11984
12343
|
{ label: "verify current branch is development", gated: true },
|
|
11985
12344
|
rcandVersionStep(options),
|
|
11986
|
-
{ label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
|
|
12345
|
+
{ label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
|
|
11987
12346
|
{ label: "preflight required rc secret names", command: "mmi-cli secrets preflight --stage rc --repo <owner/repo>", gated: true },
|
|
11988
12347
|
{ label: "merge development to rc", gated: true },
|
|
11989
12348
|
{ label: "trigger the deploy path for this repo model, returning Hub Actions run id/url data (and, with --watch, its outcome)", command: "tenant-container: gh workflow run tenant-deploy.yml ... then gh run list/watch; hub-serverless: no manual dispatch, deploy.yml auto-fires on rc push, correlate/watch that run", gated: true },
|
|
11990
|
-
{ label: "after a failed deploy, retry the existing rc ref (no re-tag/merge)", command: "mmi-cli tenant redeploy <owner/repo> rc --watch", gated: true }
|
|
12349
|
+
{ label: "after a failed deploy, retry the existing rc ref (no re-tag/merge)", command: "mmi-cli runtime tenant redeploy <owner/repo> rc --watch", gated: true }
|
|
11991
12350
|
];
|
|
11992
12351
|
}
|
|
11993
12352
|
if (command === "release") {
|
|
@@ -11995,7 +12354,7 @@ function trainPlan(command, options = {}) {
|
|
|
11995
12354
|
return [
|
|
11996
12355
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
11997
12356
|
{ label: "verify current branch is development", gated: true },
|
|
11998
|
-
{ label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
|
|
12357
|
+
{ label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
|
|
11999
12358
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12000
12359
|
{ label: "merge development to main", gated: true },
|
|
12001
12360
|
{ label: "fold the version bump into the release commit (Hub: full distribution set; app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
@@ -12009,20 +12368,20 @@ function trainPlan(command, options = {}) {
|
|
|
12009
12368
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12010
12369
|
{ label: "verify current branch is development", gated: true },
|
|
12011
12370
|
{ label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
|
|
12012
|
-
{ label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
|
|
12371
|
+
{ label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
|
|
12013
12372
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12014
12373
|
{ label: "merge development to main (rc skipped)", gated: true },
|
|
12015
12374
|
{ label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
12016
12375
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
12017
12376
|
{ label: "trigger the deploy path for this repo model, returning Hub Actions run id/url data (and, with --watch, its outcome)", command: "tenant-container: gh workflow run tenant-deploy.yml ... then gh run list/watch", gated: true },
|
|
12018
|
-
{ label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli tenant control <owner/repo> rc retire", gated: true },
|
|
12377
|
+
{ label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli runtime tenant control <owner/repo> rc retire", gated: true },
|
|
12019
12378
|
{ label: "roll development forward and align rc to the released main", gated: true }
|
|
12020
12379
|
];
|
|
12021
12380
|
}
|
|
12022
12381
|
return [
|
|
12023
12382
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12024
12383
|
{ label: "verify current branch is rc", gated: true },
|
|
12025
|
-
{ label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
|
|
12384
|
+
{ label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
|
|
12026
12385
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12027
12386
|
{ label: "verify every main-only hotfix commit is covered by the rc candidate (the guard runs automatically inside the apply step below; --ack <sha> overrides a verified, trailer-less port)", command: "mmi-cli release --apply [--ack <sha>]", gated: true },
|
|
12028
12387
|
{ label: "merge rc to main", gated: true },
|
|
@@ -12393,7 +12752,7 @@ function requireProjectMetaForTrain(load, repo) {
|
|
|
12393
12752
|
}
|
|
12394
12753
|
if (load.status === "missing") {
|
|
12395
12754
|
throw new Error(
|
|
12396
|
-
`${label}: no registry META for ${repo} \u2014 train apply refused; bootstrap the project or verify META with \`mmi-cli project get\` before promoting.`
|
|
12755
|
+
`${label}: no registry META for ${repo} \u2014 train apply refused; bootstrap the project or verify META with \`mmi-cli org project get\` before promoting.`
|
|
12397
12756
|
);
|
|
12398
12757
|
}
|
|
12399
12758
|
return load.meta;
|
|
@@ -12707,7 +13066,7 @@ function partialTrainRecoveryError(cause, input) {
|
|
|
12707
13066
|
partial train state: tag ${input.tag} is already pushed; origin/${branch} has not been pushed; ${releaseState}; deploy not dispatched.
|
|
12708
13067
|
Recovery sequence:
|
|
12709
13068
|
1. git push origin ${branch}` + releaseCommand + `
|
|
12710
|
-
${deployStep}. mmi-cli tenant redeploy ${input.repo} ${input.stage} --watch
|
|
13069
|
+
${deployStep}. mmi-cli runtime tenant redeploy ${input.repo} ${input.stage} --watch
|
|
12711
13070
|
Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
|
|
12712
13071
|
);
|
|
12713
13072
|
}
|
|
@@ -12889,7 +13248,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
12889
13248
|
if (dispatchFailure === "throw") throw e;
|
|
12890
13249
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12891
13250
|
return {
|
|
12892
|
-
note: `tenant-deploy dispatch FAILED: ${msg}. The promotion itself landed (merge/tag/Release pushed before the dispatch) \u2014 recover the deploy with \`mmi-cli tenant redeploy ${ctx.repo} ${stage}\`; never re-tag.`,
|
|
13251
|
+
note: `tenant-deploy dispatch FAILED: ${msg}. The promotion itself landed (merge/tag/Release pushed before the dispatch) \u2014 recover the deploy with \`mmi-cli runtime tenant redeploy ${ctx.repo} ${stage}\`; never re-tag.`,
|
|
12893
13252
|
deployStatus: "failure"
|
|
12894
13253
|
};
|
|
12895
13254
|
}
|
|
@@ -13476,14 +13835,14 @@ async function attemptRetire(deps, ctx) {
|
|
|
13476
13835
|
return {
|
|
13477
13836
|
status: "retired",
|
|
13478
13837
|
category: "retired",
|
|
13479
|
-
note: `rc runtime retired (tenant-control.yml${r.runUrl ? `, ${r.runUrl}` : ""}) \u2014 registry coords kept; /rcand or tenant redeploy recreates rc next cycle`
|
|
13838
|
+
note: `rc runtime retired (tenant-control.yml${r.runUrl ? `, ${r.runUrl}` : ""}) \u2014 registry coords kept; /rcand or runtime tenant redeploy recreates rc next cycle`
|
|
13480
13839
|
};
|
|
13481
13840
|
}
|
|
13482
13841
|
if (r.category === "wait-timeout") {
|
|
13483
13842
|
return {
|
|
13484
13843
|
status: "failed",
|
|
13485
13844
|
category: "wait-timeout",
|
|
13486
|
-
note: `rc retire dispatched but the run could not be observed \u2014 verify with: mmi-cli tenant control ${ctx.repo} rc status${r.runUrl ? ` (run ${r.runUrl})` : ""}`
|
|
13845
|
+
note: `rc retire dispatched but the run could not be observed \u2014 verify with: mmi-cli runtime tenant control ${ctx.repo} rc status${r.runUrl ? ` (run ${r.runUrl})` : ""}`
|
|
13487
13846
|
};
|
|
13488
13847
|
}
|
|
13489
13848
|
return { status: "failed", category: r.category ?? "transport-failed", note: r.note };
|
|
@@ -13498,7 +13857,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
13498
13857
|
if (deployStatus !== "success") {
|
|
13499
13858
|
return {
|
|
13500
13859
|
status: "skipped",
|
|
13501
|
-
note: `prod deploy outcome unconfirmed (run without --watch) \u2014 rc runtime left untouched; after verifying prod, retire with: mmi-cli tenant control ${ctx.repo} rc retire`
|
|
13860
|
+
note: `prod deploy outcome unconfirmed (run without --watch) \u2014 rc runtime left untouched; after verifying prod, retire with: mmi-cli runtime tenant control ${ctx.repo} rc retire`
|
|
13502
13861
|
};
|
|
13503
13862
|
}
|
|
13504
13863
|
try {
|
|
@@ -13520,7 +13879,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
13520
13879
|
}
|
|
13521
13880
|
const f = last;
|
|
13522
13881
|
if (f.category === "wait-timeout") return f;
|
|
13523
|
-
const note = `rc retirement failed (the release itself succeeded): ${f.note}. The rc runtime may be orphaned on the box \u2014 retire it with: mmi-cli tenant control ${ctx.repo} rc retire (or sweep all: mmi-cli tenant sweep-rc --retire --yes)`;
|
|
13882
|
+
const note = `rc retirement failed (the release itself succeeded): ${f.note}. The rc runtime may be orphaned on the box \u2014 retire it with: mmi-cli runtime tenant control ${ctx.repo} rc retire (or sweep all: mmi-cli runtime tenant sweep-rc --retire --yes)`;
|
|
13524
13883
|
return { status: "failed", category: f.category, note };
|
|
13525
13884
|
} catch (e) {
|
|
13526
13885
|
const err = e;
|
|
@@ -13563,7 +13922,7 @@ async function runTenantControl(deps, options) {
|
|
|
13563
13922
|
dispatched: false,
|
|
13564
13923
|
conclusion: "failure",
|
|
13565
13924
|
category: action === "retire" ? transport ? "transport-failed" : "dispatch-rejected" : void 0,
|
|
13566
|
-
note: transport ? `tenant control ${action} dispatch failed (transport) \u2014 safe to retry` : `tenant control ${action} rejected: ${d.error ?? "request rejected by the Hub"}`
|
|
13925
|
+
note: transport ? `runtime tenant control ${action} dispatch failed (transport) \u2014 safe to retry` : `runtime tenant control ${action} rejected: ${d.error ?? "request rejected by the Hub"}`
|
|
13567
13926
|
};
|
|
13568
13927
|
}
|
|
13569
13928
|
const { runId, runUrl } = await correlateControlRun(deps, since, [stage, action]);
|
|
@@ -13585,7 +13944,7 @@ async function runTenantControl(deps, options) {
|
|
|
13585
13944
|
return result;
|
|
13586
13945
|
}
|
|
13587
13946
|
function renderTenantControl(r) {
|
|
13588
|
-
const head = `tenant control ${r.repo} ${r.stage} ${r.action}: ${r.conclusion}${r.category ? ` (${r.category})` : ""}`;
|
|
13947
|
+
const head = `runtime tenant control ${r.repo} ${r.stage} ${r.action}: ${r.conclusion}${r.category ? ` (${r.category})` : ""}`;
|
|
13589
13948
|
const lines = [head];
|
|
13590
13949
|
if (r.runUrl) lines.push(` run: ${r.runUrl}`);
|
|
13591
13950
|
if (r.serviceState) lines.push(` serviceState: ${r.serviceState}`);
|
|
@@ -13746,7 +14105,7 @@ function pickRepo(p) {
|
|
|
13746
14105
|
}
|
|
13747
14106
|
async function sweepRcOrphans(deps, opts) {
|
|
13748
14107
|
const projects = await deps.listProjects();
|
|
13749
|
-
if (!projects) throw new Error("project list unavailable \u2014 Hub unreachable or this repo is not bootstrapped");
|
|
14108
|
+
if (!projects) throw new Error("org project list unavailable \u2014 Hub unreachable or this repo is not bootstrapped");
|
|
13750
14109
|
const targets = projects.filter(isRcBearingTenant);
|
|
13751
14110
|
const stages = [];
|
|
13752
14111
|
for (const p of targets) {
|
|
@@ -13783,7 +14142,7 @@ async function sweepRcOrphans(deps, opts) {
|
|
|
13783
14142
|
};
|
|
13784
14143
|
}
|
|
13785
14144
|
function renderSweep(r) {
|
|
13786
|
-
const lines = [`tenant sweep-rc: scanned ${r.scanned} tenant-container(s); ${r.running} rc runtime(s) running`];
|
|
14145
|
+
const lines = [`runtime tenant sweep-rc: scanned ${r.scanned} tenant-container(s); ${r.running} rc runtime(s) running`];
|
|
13787
14146
|
for (const s of r.stages) {
|
|
13788
14147
|
let line = ` ${s.repo} rc: ${s.orphanCandidate ? "RUNNING" : s.serviceState}`;
|
|
13789
14148
|
if (s.retired) line += ` -> ${s.retired}${s.category ? ` (${s.category}${s.reason ? `/${s.reason}` : ""})` : ""}`;
|
|
@@ -13791,7 +14150,7 @@ function renderSweep(r) {
|
|
|
13791
14150
|
lines.push(line);
|
|
13792
14151
|
}
|
|
13793
14152
|
if (r.running > 0 && !r.retireAttempted) {
|
|
13794
|
-
lines.push("Retire an orphan with: mmi-cli tenant control <owner/repo> rc retire (or sweep all running: mmi-cli tenant sweep-rc --retire --yes)");
|
|
14153
|
+
lines.push("Retire an orphan with: mmi-cli runtime tenant control <owner/repo> rc retire (or sweep all running: mmi-cli runtime tenant sweep-rc --retire --yes)");
|
|
13795
14154
|
}
|
|
13796
14155
|
const undetermined = r.stages.filter((s) => s.serviceState === "unknown").length;
|
|
13797
14156
|
if (undetermined > 0) {
|
|
@@ -14612,8 +14971,8 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
14612
14971
|
repo: OWNER2,
|
|
14613
14972
|
kind: "empty-target-inventory",
|
|
14614
14973
|
severity: "high",
|
|
14615
|
-
detail: "access audit received an empty repo inventory \u2014 zero repos to audit; a green audit of nothing is a false pass",
|
|
14616
|
-
remediation: `confirm the Hub registry is reachable and returns projects, then re-run \`mmi-cli access audit\`; or audit one repo explicitly with \`mmi-cli access audit --repo <owner/repo>\``
|
|
14974
|
+
detail: "org access audit received an empty repo inventory \u2014 zero repos to audit; a green audit of nothing is a false pass",
|
|
14975
|
+
remediation: `confirm the Hub registry is reachable and returns projects, then re-run \`mmi-cli org access audit\`; or audit one repo explicitly with \`mmi-cli org access audit --repo <owner/repo>\``
|
|
14617
14976
|
}],
|
|
14618
14977
|
repos: []
|
|
14619
14978
|
};
|
|
@@ -14628,7 +14987,7 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
14628
14987
|
kind: "owner-baseline-unavailable",
|
|
14629
14988
|
severity: "medium",
|
|
14630
14989
|
detail: `could not resolve org owners (role=admin) from the GitHub API (${e.message}); the overgrant audit needs this baseline and was skipped to avoid false HIGH findings \u2014 re-run when the members API is reachable`,
|
|
14631
|
-
remediation: `confirm the token can read GET /orgs/${OWNER2}/members?role=admin, then re-run \`mmi-cli access audit\``
|
|
14990
|
+
remediation: `confirm the token can read GET /orgs/${OWNER2}/members?role=admin, then re-run \`mmi-cli org access audit\``
|
|
14632
14991
|
});
|
|
14633
14992
|
return { ok: degraded.every((f) => f.severity !== "high"), owners: [], orgFindings: degraded, repos: [] };
|
|
14634
14993
|
}
|
|
@@ -14673,7 +15032,7 @@ function resolveOrgAuditTargets(registryProjects) {
|
|
|
14673
15032
|
kind: "registry-unreadable",
|
|
14674
15033
|
severity: "high",
|
|
14675
15034
|
detail: "the project registry SSOT (Hub API) was unreachable or unconfigured \u2014 the audit target set is unknown; auditing the stale committed projects.json instead could pass green while missing live repos",
|
|
14676
|
-
remediation: `confirm the Hub API base URL + token are configured and reachable, then re-run \`mmi-cli access audit\`; or audit one repo explicitly with \`mmi-cli access audit --repo <owner/repo>\``
|
|
15035
|
+
remediation: `confirm the Hub API base URL + token are configured and reachable, then re-run \`mmi-cli org access audit\`; or audit one repo explicitly with \`mmi-cli org access audit --repo <owner/repo>\``
|
|
14677
15036
|
}
|
|
14678
15037
|
};
|
|
14679
15038
|
}
|
|
@@ -14718,7 +15077,7 @@ function dataAccessContractsFromProjects(projects) {
|
|
|
14718
15077
|
return { consumers };
|
|
14719
15078
|
}
|
|
14720
15079
|
function renderAccessReport(report) {
|
|
14721
|
-
const lines = [`mmi-cli access audit: ${report.ok ? "OK" : "CHECK"} (owners: ${report.owners.map((o) => "@" + o).join(", ") || "none"})`];
|
|
15080
|
+
const lines = [`mmi-cli org access audit: ${report.ok ? "OK" : "CHECK"} (owners: ${report.owners.map((o) => "@" + o).join(", ") || "none"})`];
|
|
14722
15081
|
for (const finding of report.orgFindings ?? []) {
|
|
14723
15082
|
lines.push(` [${finding.severity}] ${finding.kind}: ${finding.detail}`);
|
|
14724
15083
|
if (finding.remediation) lines.push(` ${finding.remediation}`);
|
|
@@ -14858,20 +15217,20 @@ function docsAuditRecord(input) {
|
|
|
14858
15217
|
const date = input.date.trim();
|
|
14859
15218
|
const shaRange = input.shaRange.trim();
|
|
14860
15219
|
const checkerVendor = input.checkerVendor.trim();
|
|
14861
|
-
if (!repo) throw new Error("docs
|
|
14862
|
-
if (!date) throw new Error("docs
|
|
14863
|
-
if (!isValidIsoDate(date)) throw new Error(`docs
|
|
14864
|
-
if (!shaRange) throw new Error("docs
|
|
14865
|
-
if (!checkerVendor) throw new Error("docs
|
|
15220
|
+
if (!repo) throw new Error("docs audit record: repo is required");
|
|
15221
|
+
if (!date) throw new Error("docs audit record: date is required");
|
|
15222
|
+
if (!isValidIsoDate(date)) throw new Error(`docs audit record: date must be a real ISO date (YYYY-MM-DD), got "${date}"`);
|
|
15223
|
+
if (!shaRange) throw new Error("docs audit record: shaRange is required");
|
|
15224
|
+
if (!checkerVendor) throw new Error("docs audit record: checkerVendor is required");
|
|
14866
15225
|
if (input.outcome.kind === "refreshed" && !(Number.isInteger(input.outcome.count) && input.outcome.count > 0)) {
|
|
14867
|
-
throw new Error("docs
|
|
15226
|
+
throw new Error("docs audit record: a `refreshed` outcome must carry a positive integer count");
|
|
14868
15227
|
}
|
|
14869
15228
|
if (input.outcome.kind === "failed" && !input.outcome.reason.trim()) {
|
|
14870
|
-
throw new Error("docs
|
|
15229
|
+
throw new Error("docs audit record: a `failed` outcome must carry a reason");
|
|
14871
15230
|
}
|
|
14872
15231
|
const outcome = serializeOutcome(input.outcome);
|
|
14873
15232
|
if (!isValidOutcomeWire(outcome)) {
|
|
14874
|
-
throw new Error(`docs
|
|
15233
|
+
throw new Error(`docs audit record: outcome serialized to an invalid wire form "${outcome}"`);
|
|
14875
15234
|
}
|
|
14876
15235
|
return { repo, date, shaRange, outcome, checkerVendor };
|
|
14877
15236
|
}
|
|
@@ -14882,13 +15241,13 @@ function ageInDays(verdictDate, today) {
|
|
|
14882
15241
|
}
|
|
14883
15242
|
function docsAuditStatus(fetch2, opts) {
|
|
14884
15243
|
if ("notArmed" in fetch2) {
|
|
14885
|
-
return { ok: true, state: "not-armed", line: `docs
|
|
15244
|
+
return { ok: true, state: "not-armed", line: `docs audit: ${opts.repo} janitor not armed (registry route not live yet)` };
|
|
14886
15245
|
}
|
|
14887
15246
|
if (!fetch2.ok) {
|
|
14888
|
-
return { ok: false, state: "error", line: `docs
|
|
15247
|
+
return { ok: false, state: "error", line: `docs audit: ${opts.repo} registry read failed \u2014 ${fetch2.error}` };
|
|
14889
15248
|
}
|
|
14890
15249
|
if (fetch2.verdict === null) {
|
|
14891
|
-
return { ok: false, state: "missing", line: `docs
|
|
15250
|
+
return { ok: false, state: "missing", line: `docs audit: ${opts.repo} no verdict on record \u2014 janitor blind or dead` };
|
|
14892
15251
|
}
|
|
14893
15252
|
const verdict = fetch2.verdict;
|
|
14894
15253
|
for (const field of ["repo", "shaRange", "checkerVendor"]) {
|
|
@@ -14898,7 +15257,7 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
14898
15257
|
return {
|
|
14899
15258
|
ok: false,
|
|
14900
15259
|
state: "malformed",
|
|
14901
|
-
line: `docs
|
|
15260
|
+
line: `docs audit: ${opts.repo} malformed verdict ${field} (${shown}, expected a non-empty string) \u2014 registry record corrupt, treating as RED`
|
|
14902
15261
|
};
|
|
14903
15262
|
}
|
|
14904
15263
|
}
|
|
@@ -14906,29 +15265,29 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
14906
15265
|
return {
|
|
14907
15266
|
ok: false,
|
|
14908
15267
|
state: "malformed",
|
|
14909
|
-
line: `docs
|
|
15268
|
+
line: `docs audit: ${opts.repo} malformed verdict date "${verdict.date}" \u2014 registry record corrupt, treating as RED`
|
|
14910
15269
|
};
|
|
14911
15270
|
}
|
|
14912
15271
|
if (!isValidOutcomeWire(verdict.outcome)) {
|
|
14913
15272
|
return {
|
|
14914
15273
|
ok: false,
|
|
14915
15274
|
state: "malformed",
|
|
14916
|
-
line: `docs
|
|
15275
|
+
line: `docs audit: ${opts.repo} malformed verdict outcome "${verdict.outcome}" (expected clean|refreshed-N|failed) \u2014 registry record corrupt, treating as RED`
|
|
14917
15276
|
};
|
|
14918
15277
|
}
|
|
14919
15278
|
if (!isValidIsoDate(opts.today)) {
|
|
14920
|
-
throw new Error(`docs
|
|
15279
|
+
throw new Error(`docs audit status: today must be a real ISO date (YYYY-MM-DD), got "${opts.today}"`);
|
|
14921
15280
|
}
|
|
14922
15281
|
const cadence = opts.cadenceDays ?? 7;
|
|
14923
15282
|
const grace = opts.graceDays ?? 3;
|
|
14924
15283
|
const age = ageInDays(verdict.date, opts.today);
|
|
14925
15284
|
if (age > cadence + grace) {
|
|
14926
|
-
return { ok: false, state: "stale", line: `docs
|
|
15285
|
+
return { ok: false, state: "stale", line: `docs audit: ${opts.repo} last verdict ${age}d old \u2014 janitor blind or dead` };
|
|
14927
15286
|
}
|
|
14928
15287
|
if (verdict.outcome === "failed") {
|
|
14929
|
-
return { ok: false, state: "failed", line: `docs
|
|
15288
|
+
return { ok: false, state: "failed", line: `docs audit: ${opts.repo} last run FAILED (${verdict.date}) \u2014 janitor needs attention` };
|
|
14930
15289
|
}
|
|
14931
|
-
return { ok: true, state: "clean", line: `docs
|
|
15290
|
+
return { ok: true, state: "clean", line: `docs audit: ${opts.repo} ${verdict.outcome} (${verdict.date}, ${verdict.checkerVendor})` };
|
|
14932
15291
|
}
|
|
14933
15292
|
|
|
14934
15293
|
// src/project-readiness.ts
|
|
@@ -15042,7 +15401,7 @@ function appAttestationOf(meta) {
|
|
|
15042
15401
|
return typeof at === "string" && at.length > 0 && typeof by === "string" && by.length > 0 ? { at, by } : null;
|
|
15043
15402
|
}
|
|
15044
15403
|
function attestedLine(att) {
|
|
15045
|
-
return `App-owned readiness attested by @${att.by} on ${att.at.slice(0, 10)} \u2014 the static checklist is cleared (the doctor reads no product repo files); re-run \`mmi-cli project attest\` after app-owned structural changes.`;
|
|
15404
|
+
return `App-owned readiness attested by @${att.by} on ${att.at.slice(0, 10)} \u2014 the static checklist is cleared (the doctor reads no product repo files); re-run \`mmi-cli org project attest\` after app-owned structural changes.`;
|
|
15046
15405
|
}
|
|
15047
15406
|
var CONTRACT_UNDECLARED_LINE = "No runtime secrets declared \u2014 declare requiredRuntimeSecrets (a per-stage name map) in the registry META, or attest the app needs none with an explicit empty stage map ({ dev: [], rc: [], main: [] }).";
|
|
15048
15407
|
function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
|
|
@@ -15053,19 +15412,19 @@ function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
|
|
|
15053
15412
|
if (projectType === "content" || model === "content") return ["Content repo: keep app-owned work to docs/content changes; release train does not apply."];
|
|
15054
15413
|
if (projectType === "desktop-app") {
|
|
15055
15414
|
return [
|
|
15056
|
-
"Desktop app: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned installer/packaging outside the tenant train.",
|
|
15415
|
+
"Desktop app: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default; keep app-owned installer/packaging outside the tenant train.",
|
|
15057
15416
|
"Keep README.md and architecture.md explicit about the per-OS build and distribution path (installer, and any registry publish alongside it) so readiness does not guess web infra."
|
|
15058
15417
|
];
|
|
15059
15418
|
}
|
|
15060
15419
|
if (projectType === "desktop-game") {
|
|
15061
15420
|
return [
|
|
15062
|
-
"Desktop game: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned release packaging outside the tenant train.",
|
|
15421
|
+
"Desktop game: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default; keep app-owned release packaging outside the tenant train.",
|
|
15063
15422
|
"Keep README.md and architecture.md explicit about the game build/release path so readiness does not guess web infra."
|
|
15064
15423
|
];
|
|
15065
15424
|
}
|
|
15066
15425
|
if (projectType === "non-deployable" || model === "none") {
|
|
15067
15426
|
return [
|
|
15068
|
-
"Non-deployable repo: no Hub deploy coords, Google OAuth, DWD, or tenant control by default.",
|
|
15427
|
+
"Non-deployable repo: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default.",
|
|
15069
15428
|
"Keep README.md and architecture.md explicit about the repo purpose and any project-admin workflow it still needs."
|
|
15070
15429
|
];
|
|
15071
15430
|
}
|
|
@@ -15190,7 +15549,7 @@ function buildV2HealPatch(repoOrSlug, meta, mainDeployFact) {
|
|
|
15190
15549
|
patch.requiredRuntimeSecrets = next;
|
|
15191
15550
|
}
|
|
15192
15551
|
}
|
|
15193
|
-
const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli project set ${repo} --project-type <web-app|hub-service|content|desktop-app|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
|
|
15552
|
+
const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli org project set ${repo} --project-type <web-app|hub-service|content|desktop-app|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
|
|
15194
15553
|
if (boardRegistryGaps(meta).length) appOwnedGaps.unshift(boardRegistryGapMessage(repo));
|
|
15195
15554
|
return { slug, patch, appOwnedGaps };
|
|
15196
15555
|
}
|
|
@@ -15229,7 +15588,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
|
|
|
15229
15588
|
registryError: read.error,
|
|
15230
15589
|
hubOwned: { meta: { ok: false, missing: [] }, deployCoords: degradedStage, deployState: degradedStage, secrets: degradedSecrets },
|
|
15231
15590
|
autoHealAvailable: [],
|
|
15232
|
-
appOwnedGaps: [`Hub registry read failed (${read.error}) \u2014 diagnosis degraded; nothing is known about META, coords, or gaps. Likely transient (cold start, network, or auth blip): retry \`mmi-cli project doctor\` shortly.`]
|
|
15591
|
+
appOwnedGaps: [`Hub registry read failed (${read.error}) \u2014 diagnosis degraded; nothing is known about META, coords, or gaps. Likely transient (cold start, network, or auth blip): retry \`mmi-cli org project doctor\` shortly.`]
|
|
15233
15592
|
};
|
|
15234
15593
|
}
|
|
15235
15594
|
const meta = read.project;
|
|
@@ -15456,37 +15815,37 @@ function parseSecretsCatalogVar(raw) {
|
|
|
15456
15815
|
try {
|
|
15457
15816
|
parsed = JSON.parse(raw);
|
|
15458
15817
|
} catch {
|
|
15459
|
-
throw new Error('project set: secrets must be JSON keyed by KEY, e.g. {"SCRAPER_API_KEY":{"key":"SCRAPER_API_KEY","purpose":"scrape provider key","group":"scraper","owner":"oguz","stages":[],"consumers":["box"]}}');
|
|
15818
|
+
throw new Error('org project set: secrets must be JSON keyed by KEY, e.g. {"SCRAPER_API_KEY":{"key":"SCRAPER_API_KEY","purpose":"scrape provider key","group":"scraper","owner":"oguz","stages":[],"consumers":["box"]}}');
|
|
15460
15819
|
}
|
|
15461
15820
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15462
|
-
throw new Error("project set: secrets must be a map keyed by canonical KEY");
|
|
15821
|
+
throw new Error("org project set: secrets must be a map keyed by canonical KEY");
|
|
15463
15822
|
}
|
|
15464
15823
|
const nonEmpty = (v) => typeof v === "string" && v.trim().length > 0;
|
|
15465
15824
|
const overrideMapKey = /^([A-Z_][A-Z0-9_]*)@(dev|rc|main)$/;
|
|
15466
15825
|
for (const [mapKey, value] of Object.entries(parsed)) {
|
|
15467
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`project set: secrets["${mapKey}"] must be an object`);
|
|
15826
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`org project set: secrets["${mapKey}"] must be an object`);
|
|
15468
15827
|
const e = value;
|
|
15469
15828
|
if (typeof e.key !== "string" || !SECRET_ENV_NAME_RE.test(e.key)) {
|
|
15470
|
-
throw new Error(`project set: secrets["${mapKey}"].key must be an env-name (UPPER_SNAKE)`);
|
|
15829
|
+
throw new Error(`org project set: secrets["${mapKey}"].key must be an env-name (UPPER_SNAKE)`);
|
|
15471
15830
|
}
|
|
15472
15831
|
const override = overrideMapKey.exec(mapKey);
|
|
15473
15832
|
if (override) {
|
|
15474
15833
|
if (e.key !== override[1] || !Array.isArray(e.stages) || e.stages.length !== 1 || e.stages[0] !== override[2]) {
|
|
15475
|
-
throw new Error(`project set: secrets["${mapKey}"] is an override entry (#2528) \u2014 key must be ${override[1]} and stages exactly ["${override[2]}"]`);
|
|
15834
|
+
throw new Error(`org project set: secrets["${mapKey}"] is an override entry (#2528) \u2014 key must be ${override[1]} and stages exactly ["${override[2]}"]`);
|
|
15476
15835
|
}
|
|
15477
15836
|
} else if (e.key !== mapKey) {
|
|
15478
|
-
throw new Error(`project set: secrets["${mapKey}"].key must equal the map key (or use the KEY@<stage> override form, #2528)`);
|
|
15837
|
+
throw new Error(`org project set: secrets["${mapKey}"].key must equal the map key (or use the KEY@<stage> override form, #2528)`);
|
|
15479
15838
|
}
|
|
15480
|
-
if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
|
|
15481
|
-
if (e.provider !== void 0 && !nonEmpty(e.provider)) throw new Error(`project set: secrets["${mapKey}"].provider must be a non-empty string`);
|
|
15839
|
+
if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`org project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
|
|
15840
|
+
if (e.provider !== void 0 && !nonEmpty(e.provider)) throw new Error(`org project set: secrets["${mapKey}"].provider must be a non-empty string`);
|
|
15482
15841
|
if (!Array.isArray(e.stages) || e.stages.some((s) => !RUNTIME_SECRET_STAGES.includes(s))) {
|
|
15483
|
-
throw new Error(`project set: secrets["${mapKey}"].stages must be a subset of dev/rc/main ([] = shared)`);
|
|
15842
|
+
throw new Error(`org project set: secrets["${mapKey}"].stages must be a subset of dev/rc/main ([] = shared)`);
|
|
15484
15843
|
}
|
|
15485
15844
|
if (!Array.isArray(e.consumers) || e.consumers.some((c) => !SECRET_CONSUMERS.includes(c))) {
|
|
15486
|
-
throw new Error(`project set: secrets["${mapKey}"].consumers must be a subset of ${SECRET_CONSUMERS.join("/")}`);
|
|
15845
|
+
throw new Error(`org project set: secrets["${mapKey}"].consumers must be a subset of ${SECRET_CONSUMERS.join("/")}`);
|
|
15487
15846
|
}
|
|
15488
15847
|
if (e.aliases !== void 0 && (!Array.isArray(e.aliases) || e.aliases.some((a) => typeof a !== "string"))) {
|
|
15489
|
-
throw new Error(`project set: secrets["${mapKey}"].aliases must be a string array`);
|
|
15848
|
+
throw new Error(`org project set: secrets["${mapKey}"].aliases must be a string array`);
|
|
15490
15849
|
}
|
|
15491
15850
|
}
|
|
15492
15851
|
return parsed;
|
|
@@ -15496,19 +15855,19 @@ function parseRuntimeSecretsVar(raw) {
|
|
|
15496
15855
|
try {
|
|
15497
15856
|
parsed = JSON.parse(raw);
|
|
15498
15857
|
} catch {
|
|
15499
|
-
throw new Error('project set: requiredRuntimeSecrets must be JSON, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}');
|
|
15858
|
+
throw new Error('org project set: requiredRuntimeSecrets must be JSON, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}');
|
|
15500
15859
|
}
|
|
15501
15860
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15502
|
-
throw new Error("project set: requiredRuntimeSecrets must be a stage map (a flat array is not box-loadable)");
|
|
15861
|
+
throw new Error("org project set: requiredRuntimeSecrets must be a stage map (a flat array is not box-loadable)");
|
|
15503
15862
|
}
|
|
15504
15863
|
const map = parsed;
|
|
15505
15864
|
const out = {};
|
|
15506
15865
|
for (const [stage, names] of Object.entries(map)) {
|
|
15507
15866
|
if (!RUNTIME_SECRET_STAGES.includes(stage)) {
|
|
15508
|
-
throw new Error(`project set: requiredRuntimeSecrets stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
|
|
15867
|
+
throw new Error(`org project set: requiredRuntimeSecrets stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
|
|
15509
15868
|
}
|
|
15510
15869
|
if (!Array.isArray(names) || names.some((n) => typeof n !== "string" || !n.trim())) {
|
|
15511
|
-
throw new Error(`project set: requiredRuntimeSecrets.${stage} must be an array of non-empty secret names`);
|
|
15870
|
+
throw new Error(`org project set: requiredRuntimeSecrets.${stage} must be an array of non-empty secret names`);
|
|
15512
15871
|
}
|
|
15513
15872
|
out[stage] = names;
|
|
15514
15873
|
}
|
|
@@ -15519,13 +15878,13 @@ function parseBuildSecretsVar(raw) {
|
|
|
15519
15878
|
try {
|
|
15520
15879
|
parsed = JSON.parse(raw);
|
|
15521
15880
|
} catch {
|
|
15522
|
-
throw new Error('project set: requiredBuildSecrets must be JSON, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]');
|
|
15881
|
+
throw new Error('org project set: requiredBuildSecrets must be JSON, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]');
|
|
15523
15882
|
}
|
|
15524
15883
|
if (!Array.isArray(parsed)) {
|
|
15525
|
-
throw new Error("project set: requiredBuildSecrets must be a flat array (build secrets are not stage-mapped)");
|
|
15884
|
+
throw new Error("org project set: requiredBuildSecrets must be a flat array (build secrets are not stage-mapped)");
|
|
15526
15885
|
}
|
|
15527
15886
|
if (parsed.some((n) => typeof n !== "string" || !BUILD_SECRET_REF_RE.test(n) || n === "@github-packages-token")) {
|
|
15528
|
-
throw new Error("project set: requiredBuildSecrets must be a flat array of ENVID=<ref> or bare <ref> strings");
|
|
15887
|
+
throw new Error("org project set: requiredBuildSecrets must be a flat array of ENVID=<ref> or bare <ref> strings");
|
|
15529
15888
|
}
|
|
15530
15889
|
return parsed;
|
|
15531
15890
|
}
|
|
@@ -15534,19 +15893,19 @@ function parseEdgeDomainsVar(raw) {
|
|
|
15534
15893
|
try {
|
|
15535
15894
|
parsed = JSON.parse(raw);
|
|
15536
15895
|
} catch {
|
|
15537
|
-
throw new Error('project set: edgeDomains must be JSON, e.g. {"dev":"dev.example.co","rc":"rc.example.co","main":"example.co"}');
|
|
15896
|
+
throw new Error('org project set: edgeDomains must be JSON, e.g. {"dev":"dev.example.co","rc":"rc.example.co","main":"example.co"}');
|
|
15538
15897
|
}
|
|
15539
15898
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15540
|
-
throw new Error("project set: edgeDomains must be a {dev,rc,main} map of domain strings");
|
|
15899
|
+
throw new Error("org project set: edgeDomains must be a {dev,rc,main} map of domain strings");
|
|
15541
15900
|
}
|
|
15542
15901
|
const map = parsed;
|
|
15543
15902
|
const out = {};
|
|
15544
15903
|
for (const [stage, domain] of Object.entries(map)) {
|
|
15545
15904
|
if (!RUNTIME_SECRET_STAGES.includes(stage)) {
|
|
15546
|
-
throw new Error(`project set: edgeDomains stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
|
|
15905
|
+
throw new Error(`org project set: edgeDomains stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
|
|
15547
15906
|
}
|
|
15548
15907
|
if (typeof domain !== "string" || !domain.trim()) {
|
|
15549
|
-
throw new Error(`project set: edgeDomains.${stage} must be a non-empty domain string`);
|
|
15908
|
+
throw new Error(`org project set: edgeDomains.${stage} must be a non-empty domain string`);
|
|
15550
15909
|
}
|
|
15551
15910
|
out[stage] = domain.trim();
|
|
15552
15911
|
}
|
|
@@ -15558,19 +15917,19 @@ function parsePriorityOptionsVar(raw) {
|
|
|
15558
15917
|
try {
|
|
15559
15918
|
parsed = JSON.parse(raw);
|
|
15560
15919
|
} catch {
|
|
15561
|
-
throw new Error('project set: priorityOptions must be JSON, e.g. {"Urgent":"<id>","High":"<id>","Medium":"<id>","Low":"<id>"}');
|
|
15920
|
+
throw new Error('org project set: priorityOptions must be JSON, e.g. {"Urgent":"<id>","High":"<id>","Medium":"<id>","Low":"<id>"}');
|
|
15562
15921
|
}
|
|
15563
15922
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15564
|
-
throw new Error("project set: priorityOptions must be a {Urgent,High,Medium,Low} map of option-id strings");
|
|
15923
|
+
throw new Error("org project set: priorityOptions must be a {Urgent,High,Medium,Low} map of option-id strings");
|
|
15565
15924
|
}
|
|
15566
15925
|
const map = parsed;
|
|
15567
15926
|
const out = {};
|
|
15568
15927
|
for (const [name, id] of Object.entries(map)) {
|
|
15569
15928
|
if (!PRIORITY_OPTION_NAMES.includes(name)) {
|
|
15570
|
-
throw new Error(`project set: priorityOptions "${name}" \u2014 expected only ${PRIORITY_OPTION_NAMES.join("/")}`);
|
|
15929
|
+
throw new Error(`org project set: priorityOptions "${name}" \u2014 expected only ${PRIORITY_OPTION_NAMES.join("/")}`);
|
|
15571
15930
|
}
|
|
15572
15931
|
if (typeof id !== "string" || !id.trim()) {
|
|
15573
|
-
throw new Error(`project set: priorityOptions.${name} must be a non-empty option-id string`);
|
|
15932
|
+
throw new Error(`org project set: priorityOptions.${name} must be a non-empty option-id string`);
|
|
15574
15933
|
}
|
|
15575
15934
|
out[name] = id.trim();
|
|
15576
15935
|
}
|
|
@@ -15581,7 +15940,7 @@ function parsePortRangeVar(raw) {
|
|
|
15581
15940
|
try {
|
|
15582
15941
|
parsed = JSON.parse(raw);
|
|
15583
15942
|
} catch {
|
|
15584
|
-
throw new Error('project set: portRange must be JSON, e.g. {"start":3700,"end":3710} or [3700,3710]');
|
|
15943
|
+
throw new Error('org project set: portRange must be JSON, e.g. {"start":3700,"end":3710} or [3700,3710]');
|
|
15585
15944
|
}
|
|
15586
15945
|
let start;
|
|
15587
15946
|
let end;
|
|
@@ -15590,12 +15949,12 @@ function parsePortRangeVar(raw) {
|
|
|
15590
15949
|
} else if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
15591
15950
|
({ start, end } = parsed);
|
|
15592
15951
|
} else {
|
|
15593
|
-
throw new Error("project set: portRange must be {start,end} or a [start,end] tuple");
|
|
15952
|
+
throw new Error("org project set: portRange must be {start,end} or a [start,end] tuple");
|
|
15594
15953
|
}
|
|
15595
15954
|
if (typeof start !== "number" || typeof end !== "number" || !Number.isFinite(start) || !Number.isFinite(end)) {
|
|
15596
|
-
throw new Error("project set: portRange start/end must be finite numbers");
|
|
15955
|
+
throw new Error("org project set: portRange start/end must be finite numbers");
|
|
15597
15956
|
}
|
|
15598
|
-
if (start > end) throw new Error("project set: portRange start must be <= end");
|
|
15957
|
+
if (start > end) throw new Error("org project set: portRange start must be <= end");
|
|
15599
15958
|
return { start, end };
|
|
15600
15959
|
}
|
|
15601
15960
|
function parseStatusOptionsVar(raw) {
|
|
@@ -15603,16 +15962,16 @@ function parseStatusOptionsVar(raw) {
|
|
|
15603
15962
|
try {
|
|
15604
15963
|
parsed = JSON.parse(raw);
|
|
15605
15964
|
} catch {
|
|
15606
|
-
throw new Error('project set: statusOptions must be JSON, e.g. {"Todo":"<id>","In Progress":"<id>","In Review":"<id>","Done":"<id>"}');
|
|
15965
|
+
throw new Error('org project set: statusOptions must be JSON, e.g. {"Todo":"<id>","In Progress":"<id>","In Review":"<id>","Done":"<id>"}');
|
|
15607
15966
|
}
|
|
15608
15967
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15609
|
-
throw new Error("project set: statusOptions must be a map of status name to option-id string");
|
|
15968
|
+
throw new Error("org project set: statusOptions must be a map of status name to option-id string");
|
|
15610
15969
|
}
|
|
15611
15970
|
const map = parsed;
|
|
15612
15971
|
const out = {};
|
|
15613
15972
|
for (const [name, id] of Object.entries(map)) {
|
|
15614
15973
|
if (typeof id !== "string" || !id.trim()) {
|
|
15615
|
-
throw new Error(`project set: statusOptions.${name} must be a non-empty option-id string`);
|
|
15974
|
+
throw new Error(`org project set: statusOptions.${name} must be a non-empty option-id string`);
|
|
15616
15975
|
}
|
|
15617
15976
|
out[name] = id.trim();
|
|
15618
15977
|
}
|
|
@@ -15623,31 +15982,31 @@ function parseOauthVar(raw) {
|
|
|
15623
15982
|
try {
|
|
15624
15983
|
parsed = JSON.parse(raw);
|
|
15625
15984
|
} catch {
|
|
15626
|
-
throw new Error(`project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
|
|
15985
|
+
throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
|
|
15627
15986
|
}
|
|
15628
15987
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15629
|
-
throw new Error("project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
|
|
15988
|
+
throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
|
|
15630
15989
|
}
|
|
15631
15990
|
const map = parsed;
|
|
15632
15991
|
const out = {};
|
|
15633
15992
|
for (const [key, value] of Object.entries(map)) {
|
|
15634
15993
|
if (key === "subdomains" || key === "domains") {
|
|
15635
15994
|
if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || !v.trim())) {
|
|
15636
|
-
throw new Error(`project set: oauth.${key} must be an array of non-empty strings`);
|
|
15995
|
+
throw new Error(`org project set: oauth.${key} must be an array of non-empty strings`);
|
|
15637
15996
|
}
|
|
15638
15997
|
out[key] = value.map((v) => v.trim());
|
|
15639
15998
|
} else if (key === "callbackPath") {
|
|
15640
|
-
if (typeof value !== "string" || !value.trim()) throw new Error("project set: oauth.callbackPath must be a non-empty string");
|
|
15999
|
+
if (typeof value !== "string" || !value.trim()) throw new Error("org project set: oauth.callbackPath must be a non-empty string");
|
|
15641
16000
|
const callbackPath = value.trim();
|
|
15642
16001
|
if (callbackPath !== DEFAULT_CALLBACK_PATH) {
|
|
15643
|
-
throw new Error(`project set: oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
|
|
16002
|
+
throw new Error(`org project set: oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
|
|
15644
16003
|
}
|
|
15645
16004
|
out.callbackPath = callbackPath;
|
|
15646
16005
|
} else if (key === "fofuSubdomain") {
|
|
15647
|
-
if (typeof value !== "string") throw new Error('project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
|
|
16006
|
+
if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
|
|
15648
16007
|
out.fofuSubdomain = value.trim();
|
|
15649
16008
|
} else {
|
|
15650
|
-
throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
|
|
16009
|
+
throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
|
|
15651
16010
|
}
|
|
15652
16011
|
}
|
|
15653
16012
|
return out;
|
|
@@ -15657,49 +16016,49 @@ function parseReposVar(raw) {
|
|
|
15657
16016
|
try {
|
|
15658
16017
|
parsed = JSON.parse(raw);
|
|
15659
16018
|
} catch {
|
|
15660
|
-
throw new Error('project set: repos must be a JSON array, e.g. ["mutmutco/mm-foo"]');
|
|
16019
|
+
throw new Error('org project set: repos must be a JSON array, e.g. ["mutmutco/mm-foo"]');
|
|
15661
16020
|
}
|
|
15662
16021
|
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((r) => typeof r !== "string" || !r.trim())) {
|
|
15663
|
-
throw new Error("project set: repos must be a non-empty array of owner/name strings");
|
|
16022
|
+
throw new Error("org project set: repos must be a non-empty array of owner/name strings");
|
|
15664
16023
|
}
|
|
15665
16024
|
return parsed.map((r) => r.trim());
|
|
15666
16025
|
}
|
|
15667
16026
|
function parsePublishRequiredVar(raw) {
|
|
15668
16027
|
if (raw === "true") return true;
|
|
15669
16028
|
if (raw === "false") return false;
|
|
15670
|
-
throw new Error("project set: publishRequired must be true or false");
|
|
16029
|
+
throw new Error("org project set: publishRequired must be true or false");
|
|
15671
16030
|
}
|
|
15672
16031
|
function parseFofuEnabledVar(raw) {
|
|
15673
16032
|
if (raw === "true") return true;
|
|
15674
16033
|
if (raw === "false") return false;
|
|
15675
|
-
throw new Error("project set: fofuEnabled must be true or false");
|
|
16034
|
+
throw new Error("org project set: fofuEnabled must be true or false");
|
|
15676
16035
|
}
|
|
15677
16036
|
function parseRuntimeVaultOnlyVar(raw) {
|
|
15678
16037
|
if (raw === "true") return true;
|
|
15679
16038
|
if (raw === "false") return false;
|
|
15680
|
-
throw new Error("project set: runtimeVaultOnly must be true or false");
|
|
16039
|
+
throw new Error("org project set: runtimeVaultOnly must be true or false");
|
|
15681
16040
|
}
|
|
15682
16041
|
function parseConsumesDesignSystemVar(raw) {
|
|
15683
16042
|
if (raw === "fofu") return raw;
|
|
15684
|
-
throw new Error('project set: consumesDesignSystem must be "fofu"');
|
|
16043
|
+
throw new Error('org project set: consumesDesignSystem must be "fofu"');
|
|
15685
16044
|
}
|
|
15686
16045
|
function parsePublishDirVar(raw) {
|
|
15687
16046
|
const v = raw.trim();
|
|
15688
16047
|
if (v === "" || v === ".") {
|
|
15689
|
-
throw new Error("project set: publishDir must be a non-empty relative subpath, e.g. packages/ui (omit it or --unset publishDir for the repo root)");
|
|
16048
|
+
throw new Error("org project set: publishDir must be a non-empty relative subpath, e.g. packages/ui (omit it or --unset publishDir for the repo root)");
|
|
15690
16049
|
}
|
|
15691
16050
|
if (!/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
|
|
15692
|
-
throw new Error('project set: publishDir must be a safe relative subpath \u2014 no leading slash, no ".." segment');
|
|
16051
|
+
throw new Error('org project set: publishDir must be a safe relative subpath \u2014 no leading slash, no ".." segment');
|
|
15693
16052
|
}
|
|
15694
16053
|
return v;
|
|
15695
16054
|
}
|
|
15696
16055
|
function parseDsManifestPathVar(raw) {
|
|
15697
16056
|
const v = raw.trim();
|
|
15698
16057
|
if (v === "" || !/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
|
|
15699
|
-
throw new Error('project set: dsManifestPath must be a safe relative path \u2014 no leading slash, no ".." segment');
|
|
16058
|
+
throw new Error('org project set: dsManifestPath must be a safe relative path \u2014 no leading slash, no ".." segment');
|
|
15700
16059
|
}
|
|
15701
16060
|
if (v !== "package.json" && !v.endsWith("/package.json")) {
|
|
15702
|
-
throw new Error("project set: dsManifestPath must point to a package.json, e.g. web/package.json");
|
|
16061
|
+
throw new Error("org project set: dsManifestPath must point to a package.json, e.g. web/package.json");
|
|
15703
16062
|
}
|
|
15704
16063
|
return v;
|
|
15705
16064
|
}
|
|
@@ -15708,10 +16067,10 @@ function parseRequiredChecksVar(raw) {
|
|
|
15708
16067
|
try {
|
|
15709
16068
|
parsed = JSON.parse(raw);
|
|
15710
16069
|
} catch {
|
|
15711
|
-
throw new Error('project set: requiredChecks must be a JSON array, e.g. ["gate"] or [] for an intentional no-ci repo');
|
|
16070
|
+
throw new Error('org project set: requiredChecks must be a JSON array, e.g. ["gate"] or [] for an intentional no-ci repo');
|
|
15712
16071
|
}
|
|
15713
16072
|
if (!Array.isArray(parsed) || parsed.some((c) => typeof c !== "string" || !c.trim())) {
|
|
15714
|
-
throw new Error("project set: requiredChecks must be a JSON array of non-empty check-context strings (use [] for none)");
|
|
16073
|
+
throw new Error("org project set: requiredChecks must be a JSON array of non-empty check-context strings (use [] for none)");
|
|
15715
16074
|
}
|
|
15716
16075
|
return parsed.map((c) => c.trim());
|
|
15717
16076
|
}
|
|
@@ -15720,22 +16079,22 @@ function parseGateVar(raw) {
|
|
|
15720
16079
|
try {
|
|
15721
16080
|
parsed = JSON.parse(raw);
|
|
15722
16081
|
} catch {
|
|
15723
|
-
throw new Error('project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
|
|
16082
|
+
throw new Error('org project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
|
|
15724
16083
|
}
|
|
15725
16084
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15726
|
-
throw new Error("project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
|
|
16085
|
+
throw new Error("org project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
|
|
15727
16086
|
}
|
|
15728
16087
|
const map = parsed;
|
|
15729
16088
|
const out = {};
|
|
15730
16089
|
for (const [key, value] of Object.entries(map)) {
|
|
15731
16090
|
if (key === "runtime") {
|
|
15732
|
-
if (value !== "node" && value !== "python") throw new Error('project set: gate.runtime must be "node" or "python"');
|
|
16091
|
+
if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
|
|
15733
16092
|
out.runtime = value;
|
|
15734
16093
|
} else if (key === "cmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
|
|
15735
|
-
if (typeof value !== "string" || !value.trim()) throw new Error(`project set: gate.${key} must be a non-empty string`);
|
|
16094
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
|
|
15736
16095
|
out[key] = value.trim();
|
|
15737
16096
|
} else {
|
|
15738
|
-
throw new Error(`project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
|
|
16097
|
+
throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
|
|
15739
16098
|
}
|
|
15740
16099
|
}
|
|
15741
16100
|
return out;
|
|
@@ -15813,25 +16172,25 @@ function buildProjectSetPatch(input) {
|
|
|
15813
16172
|
const patch = {};
|
|
15814
16173
|
if (input.class) {
|
|
15815
16174
|
if (input.class !== "deployable" && input.class !== "content") {
|
|
15816
|
-
throw new Error("project set: --class must be deployable or content");
|
|
16175
|
+
throw new Error("org project set: --class must be deployable or content");
|
|
15817
16176
|
}
|
|
15818
16177
|
patch.class = input.class;
|
|
15819
16178
|
}
|
|
15820
16179
|
if (input.projectType) {
|
|
15821
16180
|
if (!isProjectType(input.projectType)) {
|
|
15822
|
-
throw new Error(`project set: --project-type must be one of: ${PROJECT_TYPES.join(", ")}`);
|
|
16181
|
+
throw new Error(`org project set: --project-type must be one of: ${PROJECT_TYPES.join(", ")}`);
|
|
15823
16182
|
}
|
|
15824
16183
|
patch.projectType = input.projectType;
|
|
15825
16184
|
}
|
|
15826
16185
|
if (input.deployModel) {
|
|
15827
16186
|
if (!isDeployModel(input.deployModel)) {
|
|
15828
|
-
throw new Error(`project set: --deploy-model must be one of: ${DEPLOY_MODELS.join(", ")}`);
|
|
16187
|
+
throw new Error(`org project set: --deploy-model must be one of: ${DEPLOY_MODELS.join(", ")}`);
|
|
15829
16188
|
}
|
|
15830
16189
|
patch.deployModel = input.deployModel;
|
|
15831
16190
|
}
|
|
15832
16191
|
if (input.releaseTrack) {
|
|
15833
16192
|
if (!isReleaseTrack(input.releaseTrack)) {
|
|
15834
|
-
throw new Error(`project set: --release-track must be one of: ${RELEASE_TRACKS.join(", ")}`);
|
|
16193
|
+
throw new Error(`org project set: --release-track must be one of: ${RELEASE_TRACKS.join(", ")}`);
|
|
15835
16194
|
}
|
|
15836
16195
|
patch.releaseTrack = input.releaseTrack;
|
|
15837
16196
|
}
|
|
@@ -15840,16 +16199,16 @@ function buildProjectSetPatch(input) {
|
|
|
15840
16199
|
if (eq <= 0) {
|
|
15841
16200
|
const looksPositional = value.includes("/") || !value.includes("=");
|
|
15842
16201
|
const hint = looksPositional ? " \u2014 if that is the [owner/repo] argument, put it BEFORE --var; a variadic flag swallows the tokens after it" : "";
|
|
15843
|
-
throw new Error(`project set: --var must be KEY=VALUE (got "${value}")${hint}`);
|
|
16202
|
+
throw new Error(`org project set: --var must be KEY=VALUE (got "${value}")${hint}`);
|
|
15844
16203
|
}
|
|
15845
16204
|
const key = value.slice(0, eq);
|
|
15846
16205
|
const raw = value.slice(eq + 1);
|
|
15847
16206
|
if (!SETTABLE_VAR_KEY_SET.has(key)) {
|
|
15848
|
-
throw new Error(`project set: --var KEY "${key}" is not settable \u2014 allowed keys: ${SETTABLE_VAR_KEYS.join(", ")}`);
|
|
16207
|
+
throw new Error(`org project set: --var KEY "${key}" is not settable \u2014 allowed keys: ${SETTABLE_VAR_KEYS.join(", ")}`);
|
|
15849
16208
|
}
|
|
15850
16209
|
if (key === "projectNumber") {
|
|
15851
16210
|
const n = Number(raw);
|
|
15852
|
-
if (!Number.isFinite(n)) throw new Error("project set: projectNumber must be numeric");
|
|
16211
|
+
if (!Number.isFinite(n)) throw new Error("org project set: projectNumber must be numeric");
|
|
15853
16212
|
patch[key] = n;
|
|
15854
16213
|
} else if (key === "requiredRuntimeSecrets") {
|
|
15855
16214
|
patch[key] = parseRuntimeSecretsVar(raw);
|
|
@@ -15882,7 +16241,7 @@ function buildProjectSetPatch(input) {
|
|
|
15882
16241
|
} else if (key === "dsManifestPath") {
|
|
15883
16242
|
patch[key] = parseDsManifestPathVar(raw);
|
|
15884
16243
|
} else if (key === "ci") {
|
|
15885
|
-
if (raw !== "none") throw new Error('project set: ci must be "none" (or use --unset ci to require checks)');
|
|
16244
|
+
if (raw !== "none") throw new Error('org project set: ci must be "none" (or use --unset ci to require checks)');
|
|
15886
16245
|
patch[key] = raw;
|
|
15887
16246
|
} else if (key === "requiredChecks") {
|
|
15888
16247
|
patch[key] = parseRequiredChecksVar(raw);
|
|
@@ -15894,7 +16253,7 @@ function buildProjectSetPatch(input) {
|
|
|
15894
16253
|
}
|
|
15895
16254
|
for (const key of input.unsets) {
|
|
15896
16255
|
if (!UNSET_KEY_SET.has(key)) {
|
|
15897
|
-
throw new Error(`project set: --unset must be one of: ${UNSET_KEYS.join(", ")}`);
|
|
16256
|
+
throw new Error(`org project set: --unset must be one of: ${UNSET_KEYS.join(", ")}`);
|
|
15898
16257
|
}
|
|
15899
16258
|
patch[key] = null;
|
|
15900
16259
|
}
|
|
@@ -15903,7 +16262,7 @@ function buildProjectSetPatch(input) {
|
|
|
15903
16262
|
patch.edgeDomains = null;
|
|
15904
16263
|
}
|
|
15905
16264
|
if (Object.keys(patch).length === 0) {
|
|
15906
|
-
throw new Error("project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, and/or --clear-web-profile");
|
|
16265
|
+
throw new Error("org project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, and/or --clear-web-profile");
|
|
15907
16266
|
}
|
|
15908
16267
|
return patch;
|
|
15909
16268
|
}
|
|
@@ -15915,36 +16274,36 @@ function buildSetDeployPatch(_slug, input) {
|
|
|
15915
16274
|
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
15916
16275
|
const stage = clean4(input.stage);
|
|
15917
16276
|
if (!stage || !DEPLOY_STAGES.includes(stage)) {
|
|
15918
|
-
throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
16277
|
+
throw new Error(`org project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
15919
16278
|
}
|
|
15920
16279
|
const substrate = clean4(input.substrate);
|
|
15921
16280
|
if (substrate !== void 0 && !DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
15922
|
-
throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
16281
|
+
throw new Error(`org project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
15923
16282
|
}
|
|
15924
16283
|
const sshHost = clean4(input.sshHost);
|
|
15925
16284
|
const sshUser = clean4(input.sshUser);
|
|
15926
16285
|
const deployPath = clean4(input.deployPath);
|
|
15927
16286
|
const serviceName = clean4(input.service);
|
|
15928
16287
|
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
15929
|
-
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
|
|
16288
|
+
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`org project set-deploy: ${label} contains unsafe characters`);
|
|
15930
16289
|
}
|
|
15931
16290
|
let port;
|
|
15932
16291
|
if (input.port !== void 0 && input.port !== null && `${input.port}`.trim() !== "") {
|
|
15933
16292
|
port = Number(input.port);
|
|
15934
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
|
|
16293
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("org project set-deploy: --port must be an integer 1..65535");
|
|
15935
16294
|
}
|
|
15936
16295
|
const domain = clean4(input.domain);
|
|
15937
|
-
if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
|
|
16296
|
+
if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`org project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
|
|
15938
16297
|
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
|
|
15939
16298
|
for (const a of aliases) {
|
|
15940
|
-
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
16299
|
+
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`org project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
15941
16300
|
}
|
|
15942
16301
|
const uniqueAliases = [...new Set(aliases)];
|
|
15943
16302
|
if (input.clearAliases && uniqueAliases.length) {
|
|
15944
|
-
throw new Error("project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
|
|
16303
|
+
throw new Error("org project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
|
|
15945
16304
|
}
|
|
15946
16305
|
if (input.noEnvFile !== void 0 && typeof input.noEnvFile !== "boolean") {
|
|
15947
|
-
throw new Error("project set-deploy: --no-env-file must be true or false");
|
|
16306
|
+
throw new Error("org project set-deploy: --no-env-file must be true or false");
|
|
15948
16307
|
}
|
|
15949
16308
|
return {
|
|
15950
16309
|
stage,
|
|
@@ -16034,7 +16393,7 @@ function evaluatePromotionFilelessGuard(input) {
|
|
|
16034
16393
|
return {
|
|
16035
16394
|
ok: false,
|
|
16036
16395
|
reason: `the ${branch} docker-compose.yml marks a service fileless (mmi.runtime-env: "true") but DEPLOY#${stage}.noEnvFile is not true \u2014 the ${stage} deploy would fail exit-78 ("compose is fileless but DEPLOY#${stage} noEnvFile is not true"). Fix BEFORE promoting, then re-run:
|
|
16037
|
-
mmi-cli project set-deploy --stage ${stage} --no-env-file true # register the fileless flag
|
|
16396
|
+
mmi-cli org project set-deploy --stage ${stage} --no-env-file true # register the fileless flag
|
|
16038
16397
|
gh workflow run tenant-reconcile.yml --repo mutmutco/MMI-Hub # re-render the box control script (#1056)
|
|
16039
16398
|
(or pass --skip-compose-guard to override this preflight)`
|
|
16040
16399
|
};
|
|
@@ -16058,11 +16417,11 @@ function vaultCleanupNote(slug) {
|
|
|
16058
16417
|
function buildRetirePlan(input) {
|
|
16059
16418
|
const target = input.target?.trim();
|
|
16060
16419
|
if (!target) {
|
|
16061
|
-
throw new Error("project retire: pass <owner/repo> or a slug to retire");
|
|
16420
|
+
throw new Error("org project retire: pass <owner/repo> or a slug to retire");
|
|
16062
16421
|
}
|
|
16063
16422
|
const slug = retireSlugOf(target);
|
|
16064
16423
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
16065
|
-
throw new Error(`project retire: "${target}" does not resolve to a valid slug`);
|
|
16424
|
+
throw new Error(`org project retire: "${target}" does not resolve to a valid slug`);
|
|
16066
16425
|
}
|
|
16067
16426
|
const repo = target.includes("/") ? target : `mutmutco/${slug}`;
|
|
16068
16427
|
return { slug, repo, apply: Boolean(input.apply), skipBoard: Boolean(input.skipBoard) };
|
|
@@ -16392,7 +16751,7 @@ function registerSecretsCommands(program3) {
|
|
|
16392
16751
|
const ok = await secretsRequest(d, key, o);
|
|
16393
16752
|
if (!ok) process.exitCode = 1;
|
|
16394
16753
|
}));
|
|
16395
|
-
secrets.command("declare <key>").description("project-admin self-declare: register a NEW secrets-catalog entry for your own repo (name/purpose/group/owner/consumers/stages metadata only \u2014 never a value); closes the declare-first master-only gap (MM-PM#261/#262). Catalog-declare only \u2014 wiring the key into requiredRuntimeSecrets stays master-only (`secrets request` to ask, or `project set --var requiredRuntimeSecrets=...`); dropped from self-serve after adversarial review found real escalation paths in that additive-merge logic (MMI-Hub#2780). Separate from `project set`, which stays master-only for this field.").requiredOption("--purpose <text>", "what this secret is for").requiredOption("--group <text>", "catalog grouping, e.g. database, auth").option("--owner <login>", "catalog owner login (default: your own resolved GitHub login)").option("--consumers <list>", "comma-separated consumers (default: box)").option("--stages <list>", "comma-separated dev/rc/main this entry applies to (default: none \u2014 one stageless shared value)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((key, o) => withSecrets(async (d) => {
|
|
16754
|
+
secrets.command("declare <key>").description("project-admin self-declare: register a NEW secrets-catalog entry for your own repo (name/purpose/group/owner/consumers/stages metadata only \u2014 never a value); closes the declare-first master-only gap (MM-PM#261/#262). Catalog-declare only \u2014 wiring the key into requiredRuntimeSecrets stays master-only (`secrets request` to ask, or `org project set --var requiredRuntimeSecrets=...`); dropped from self-serve after adversarial review found real escalation paths in that additive-merge logic (MMI-Hub#2780). Separate from `org project set`, which stays master-only for this field.").requiredOption("--purpose <text>", "what this secret is for").requiredOption("--group <text>", "catalog grouping, e.g. database, auth").option("--owner <login>", "catalog owner login (default: your own resolved GitHub login)").option("--consumers <list>", "comma-separated consumers (default: box)").option("--stages <list>", "comma-separated dev/rc/main this entry applies to (default: none \u2014 one stageless shared value)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((key, o) => withSecrets(async (d) => {
|
|
16396
16755
|
const owner = o.owner ?? await githubLogin();
|
|
16397
16756
|
if (!owner) {
|
|
16398
16757
|
fail("secrets declare: could not resolve your GitHub login for --owner; pass --owner explicitly");
|
|
@@ -16419,7 +16778,6 @@ function registerSecretsCommands(program3) {
|
|
|
16419
16778
|
if (!ok) process.exitCode = 1;
|
|
16420
16779
|
});
|
|
16421
16780
|
secrets.command("set <key>").description("write/rotate a secret; value from stdin (never an argument). DECLARE-FIRST (#2528): the key must be a declared catalog coordinate \u2014 bare <KEY> = the stageless canonical, <stage>/<KEY> = a declared per-stage override; undeclared writes are rejected with the fix").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
|
|
16422
|
-
secrets.command("edit <key>").description("alias for set \u2014 replace a secret value (read from stdin)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
|
|
16423
16781
|
secrets.command("copy").description("copy provider keys between vault tiers (audit-logged; org-tier source is master-gated)").requiredOption("--from <stage>", "source tier: dev, rc, or main").requiredOption("--to <stage>", "destination tier: dev, rc, or main").requiredOption("--keys <names>", "comma-separated secret names (encryption keys blocked)").option("--dry-run", "report copies without writing").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets(async (d) => {
|
|
16424
16782
|
const stages = ["dev", "rc", "main"];
|
|
16425
16783
|
if (!stages.includes(o.from) || !stages.includes(o.to)) {
|
|
@@ -16766,14 +17124,14 @@ async function fetchInventory() {
|
|
|
16766
17124
|
}
|
|
16767
17125
|
});
|
|
16768
17126
|
if (incomplete.length === PROJECTS.length) {
|
|
16769
|
-
throw new Error(`box: could not read any Hetzner project \u2014
|
|
17127
|
+
throw new Error(`runtime box: could not read any Hetzner project \u2014
|
|
16770
17128
|
${incomplete.join("\n ")}`);
|
|
16771
17129
|
}
|
|
16772
17130
|
return { boxes: boxes.sort((a, b) => a.name.localeCompare(b.name)), incomplete };
|
|
16773
17131
|
}
|
|
16774
17132
|
function warnIncomplete(incomplete) {
|
|
16775
|
-
for (const f of incomplete) console.error(`box: WARNING \u2014 ${f}`);
|
|
16776
|
-
if (incomplete.length) console.error("box: this inventory is INCOMPLETE \u2014 boxes may be missing from it.");
|
|
17133
|
+
for (const f of incomplete) console.error(`runtime box: WARNING \u2014 ${f}`);
|
|
17134
|
+
if (incomplete.length) console.error("runtime box: this inventory is INCOMPLETE \u2014 boxes may be missing from it.");
|
|
16777
17135
|
}
|
|
16778
17136
|
function registerBoxCommands(program3) {
|
|
16779
17137
|
const box = program3.command("box").description("the box inventory \u2014 which machines exist, their addresses, and the vault key that opens each (never key values)");
|
|
@@ -16798,12 +17156,12 @@ function registerBoxCommands(program3) {
|
|
|
16798
17156
|
if (result.kind === "unknown") {
|
|
16799
17157
|
warnIncomplete(result.reasons);
|
|
16800
17158
|
await failGraceful(
|
|
16801
|
-
`box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
|
|
17159
|
+
`runtime box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
|
|
16802
17160
|
);
|
|
16803
17161
|
return;
|
|
16804
17162
|
}
|
|
16805
17163
|
if (result.kind === "absent") {
|
|
16806
|
-
await failGraceful(`box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
|
|
17164
|
+
await failGraceful(`runtime box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
|
|
16807
17165
|
return;
|
|
16808
17166
|
}
|
|
16809
17167
|
const found = result.box;
|
|
@@ -16918,7 +17276,7 @@ function awsScheduleNames(payload) {
|
|
|
16918
17276
|
return schedules.map((s) => s && typeof s === "object" ? s.Name : void 0).filter((n) => typeof n === "string" && !isJervResource(n));
|
|
16919
17277
|
}
|
|
16920
17278
|
var DECLARED_ENTRIES = [
|
|
16921
|
-
{ name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli box get <box> --ssh)" },
|
|
17279
|
+
{ name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli runtime box get <box> --ssh)" },
|
|
16922
17280
|
{ name: "zuber-bake", cadence: "*:05/30 (every 30 min)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake.timer \u2192 rolling overlay bake, metro tier, next ~6h window (verify over ssh)" },
|
|
16923
17281
|
{ name: "zuber-bake-full", cadence: "00:30 UTC (03:30 TRT, daily)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake-full.timer \u2192 full 24h overlay rebuild, all 81 il (verify over ssh)" }
|
|
16924
17282
|
];
|
|
@@ -16950,7 +17308,7 @@ function renderDocSection(entries, generatedAtIso) {
|
|
|
16950
17308
|
}
|
|
16951
17309
|
return [
|
|
16952
17310
|
DOC_START_MARKER,
|
|
16953
|
-
`_Generated by \`mmi-cli schedules --doc\` at ${generatedAtIso}. Never hand-edit this section._`,
|
|
17311
|
+
`_Generated by \`mmi-cli org schedules --doc\` at ${generatedAtIso}. Never hand-edit this section._`,
|
|
16954
17312
|
"",
|
|
16955
17313
|
lines.join("\n"),
|
|
16956
17314
|
DOC_END_MARKER
|
|
@@ -16960,7 +17318,7 @@ function spliceDoc(docText, generatedSection) {
|
|
|
16960
17318
|
const start = docText.indexOf(DOC_START_MARKER);
|
|
16961
17319
|
const end = docText.indexOf(DOC_END_MARKER);
|
|
16962
17320
|
if (start === -1 || end === -1 || end < start) {
|
|
16963
|
-
throw new Error(`schedules --doc: the target doc is missing the ${DOC_START_MARKER} / ${DOC_END_MARKER} markers \u2014 refusing to guess where the generated inventory goes.`);
|
|
17321
|
+
throw new Error(`org schedules --doc: the target doc is missing the ${DOC_START_MARKER} / ${DOC_END_MARKER} markers \u2014 refusing to guess where the generated inventory goes.`);
|
|
16964
17322
|
}
|
|
16965
17323
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
16966
17324
|
}
|
|
@@ -17168,11 +17526,11 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
17168
17526
|
};
|
|
17169
17527
|
}
|
|
17170
17528
|
function warnIncomplete2(incomplete) {
|
|
17171
|
-
for (const f of incomplete) console.error(`schedules: WARNING \u2014 ${f}`);
|
|
17172
|
-
if (incomplete.length) console.error("schedules: this notebook is INCOMPLETE \u2014 schedules may be missing from it.");
|
|
17529
|
+
for (const f of incomplete) console.error(`org schedules: WARNING \u2014 ${f}`);
|
|
17530
|
+
if (incomplete.length) console.error("org schedules: this notebook is INCOMPLETE \u2014 schedules may be missing from it.");
|
|
17173
17531
|
}
|
|
17174
17532
|
function reportDrift(drift) {
|
|
17175
|
-
for (const d of drift) console.error(`schedules: DRIFT \u2014 ${d}`);
|
|
17533
|
+
for (const d of drift) console.error(`org schedules: DRIFT \u2014 ${d}`);
|
|
17176
17534
|
}
|
|
17177
17535
|
function registerSchedulesCommands(program3) {
|
|
17178
17536
|
program3.command("schedules").description("the org schedules notebook \u2014 every armed cron, timer, and scheduled LLM lane, resolved live (jerv side lives in jerv-cli schedules)").option("--json", "machine-readable output (consumed by jerv-cli schedules --all)").option("--doc <path>", "splice the generated inventory into the given doc between the schedules:inventory markers (docs/schedules.md)").action(async (o) => {
|
|
@@ -17181,14 +17539,14 @@ function registerSchedulesCommands(program3) {
|
|
|
17181
17539
|
if (o.doc) {
|
|
17182
17540
|
if (incomplete.length) {
|
|
17183
17541
|
warnIncomplete2(incomplete);
|
|
17184
|
-
await failGraceful("schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
17542
|
+
await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
17185
17543
|
return;
|
|
17186
17544
|
}
|
|
17187
17545
|
const docText = await (0, import_promises2.readFile)(o.doc, "utf8");
|
|
17188
17546
|
const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
|
|
17189
17547
|
await (0, import_promises2.writeFile)(o.doc, spliced, "utf8");
|
|
17190
17548
|
reportDrift(drift);
|
|
17191
|
-
console.log(`schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
17549
|
+
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
17192
17550
|
return;
|
|
17193
17551
|
}
|
|
17194
17552
|
if (o.json) {
|
|
@@ -17435,15 +17793,15 @@ async function runIssueChildren(deps, epic, opts) {
|
|
|
17435
17793
|
const seen = /* @__PURE__ */ new Set([childKey(repo, ref.number)]);
|
|
17436
17794
|
for (const raw of await queryChildren(deps, owner, name, ref.number)) {
|
|
17437
17795
|
if (out.length >= CHILDREN_MAX_TOTAL) break;
|
|
17438
|
-
const
|
|
17439
|
-
out.push(
|
|
17440
|
-
seen.add(childKey(
|
|
17796
|
+
const child2 = shapeChildNode(raw, 1, boardProjectId);
|
|
17797
|
+
out.push(child2);
|
|
17798
|
+
seen.add(childKey(child2.repo, child2.number));
|
|
17441
17799
|
}
|
|
17442
17800
|
if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
|
|
17443
17801
|
const queue = [];
|
|
17444
|
-
for (const
|
|
17445
|
-
const sp = trySplitRepo(
|
|
17446
|
-
if (sp) queue.push({ owner: sp.owner, name: sp.name, number:
|
|
17802
|
+
for (const child2 of out) {
|
|
17803
|
+
const sp = trySplitRepo(child2.repo);
|
|
17804
|
+
if (sp) queue.push({ owner: sp.owner, name: sp.name, number: child2.number, depth: 1 });
|
|
17447
17805
|
}
|
|
17448
17806
|
while (queue.length && out.length < CHILDREN_MAX_TOTAL) {
|
|
17449
17807
|
const frame = queue.shift();
|
|
@@ -17456,13 +17814,13 @@ async function runIssueChildren(deps, epic, opts) {
|
|
|
17456
17814
|
}
|
|
17457
17815
|
for (const raw of nodes) {
|
|
17458
17816
|
if (out.length >= CHILDREN_MAX_TOTAL) break;
|
|
17459
|
-
const
|
|
17460
|
-
const key = childKey(
|
|
17817
|
+
const child2 = shapeChildNode(raw, frame.depth + 1, boardProjectId);
|
|
17818
|
+
const key = childKey(child2.repo, child2.number);
|
|
17461
17819
|
if (seen.has(key)) continue;
|
|
17462
17820
|
seen.add(key);
|
|
17463
|
-
out.push(
|
|
17464
|
-
const sp = trySplitRepo(
|
|
17465
|
-
if (sp) queue.push({ owner: sp.owner, name: sp.name, number:
|
|
17821
|
+
out.push(child2);
|
|
17822
|
+
const sp = trySplitRepo(child2.repo);
|
|
17823
|
+
if (sp) queue.push({ owner: sp.owner, name: sp.name, number: child2.number, depth: frame.depth + 1 });
|
|
17466
17824
|
}
|
|
17467
17825
|
}
|
|
17468
17826
|
return out;
|
|
@@ -17810,9 +18168,12 @@ var DOC_PLACEHOLDER_PROMPTS = [
|
|
|
17810
18168
|
"install, dev server",
|
|
17811
18169
|
"lint, typecheck, repo gate script",
|
|
17812
18170
|
"ports, env from vault not files",
|
|
17813
|
-
"
|
|
17814
|
-
"
|
|
17815
|
-
"
|
|
18171
|
+
"top-level dir/module: what it is, in one line",
|
|
18172
|
+
"Owner/operator \u2014 who runs this day to day",
|
|
18173
|
+
"one human-readable command/steps to get this running locally",
|
|
18174
|
+
"the product/service/library, in one or two bullets",
|
|
18175
|
+
"(top-level dir)",
|
|
18176
|
+
"Languages, frameworks, datastores, major services",
|
|
17816
18177
|
"Build/test commands, CI gate, deploy target"
|
|
17817
18178
|
];
|
|
17818
18179
|
function unfilledDocPlaceholders(text) {
|
|
@@ -18210,7 +18571,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18210
18571
|
projectMeta: meta,
|
|
18211
18572
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
18212
18573
|
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
|
|
18213
|
-
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
18574
|
+
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
18214
18575
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
18215
18576
|
requiredGcpApis: (() => {
|
|
18216
18577
|
const v = meta?.requiredGcpApis;
|
|
@@ -18745,7 +19106,7 @@ function registerStageCommands(program3) {
|
|
|
18745
19106
|
const read = await fetchProjectBySlugChecked(slug, reg);
|
|
18746
19107
|
const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
|
|
18747
19108
|
if (decision.action === "fail") {
|
|
18748
|
-
return failGraceful(`port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
|
|
19109
|
+
return failGraceful(`stage port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
|
|
18749
19110
|
}
|
|
18750
19111
|
if (decision.action === "return") {
|
|
18751
19112
|
const [start2, end2] = decision.range;
|
|
@@ -18755,7 +19116,7 @@ function registerStageCommands(program3) {
|
|
|
18755
19116
|
const infra = resolvePortRangeInfra(process.cwd(), __dirname);
|
|
18756
19117
|
if (!infra) {
|
|
18757
19118
|
return failGraceful(
|
|
18758
|
-
`port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
|
|
19119
|
+
`stage port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
|
|
18759
19120
|
);
|
|
18760
19121
|
}
|
|
18761
19122
|
const path2 = infra.registryPath;
|
|
@@ -18768,7 +19129,7 @@ function registerStageCommands(program3) {
|
|
|
18768
19129
|
const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
|
|
18769
19130
|
const write = await upsertProject(slug, { portRange: { start, end } }, reg);
|
|
18770
19131
|
if (!write.ok && source === "ddb") {
|
|
18771
|
-
return failGraceful(`port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
|
|
19132
|
+
return failGraceful(`stage port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
|
|
18772
19133
|
}
|
|
18773
19134
|
if (o.json) {
|
|
18774
19135
|
printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
|
|
@@ -18808,7 +19169,7 @@ function registerStageCommands(program3) {
|
|
|
18808
19169
|
},
|
|
18809
19170
|
control: async ({ repo, action, host, ip }) => {
|
|
18810
19171
|
const res = await tenantControl({ repo, stage: "dev", action, host, ip }, rcDeps);
|
|
18811
|
-
if (!res.ok) throw new Error(`tenant control ${action} dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
|
|
19172
|
+
if (!res.ok) throw new Error(`runtime tenant control ${action} dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
|
|
18812
19173
|
}
|
|
18813
19174
|
};
|
|
18814
19175
|
try {
|
|
@@ -19418,7 +19779,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
19418
19779
|
});
|
|
19419
19780
|
}
|
|
19420
19781
|
function renderGcApplyResult(result) {
|
|
19421
|
-
const lines = ["gc apply result:"];
|
|
19782
|
+
const lines = ["worktree gc apply result:"];
|
|
19422
19783
|
lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
|
|
19423
19784
|
lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
|
|
19424
19785
|
lines.push(` tracking refs removed: ${result.removedTrackingRefs.length ? result.removedTrackingRefs.join(", ") : "none"}`);
|
|
@@ -19463,7 +19824,7 @@ function evaluatePrMergeHousekeeping(report, context, force = false) {
|
|
|
19463
19824
|
const detail = failed ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
|
|
19464
19825
|
return {
|
|
19465
19826
|
blocked: true,
|
|
19466
|
-
reason: `${context} housekeeping blocked merge: ${detail}. Run \`mmi-cli gc --scratch --apply\` to clear it, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
|
|
19827
|
+
reason: `${context} housekeeping blocked merge: ${detail}. Run \`mmi-cli worktree gc --scratch --apply\` to clear it, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
|
|
19467
19828
|
};
|
|
19468
19829
|
}
|
|
19469
19830
|
function assertPrMergeHousekeepingClean(startingPath, context, options = {}) {
|
|
@@ -19882,7 +20243,7 @@ function classifyStaleLeaks(input) {
|
|
|
19882
20243
|
kind: "stale-worktree",
|
|
19883
20244
|
ref: wt.branch,
|
|
19884
20245
|
detail: `merged/closed branch ${wt.branch} still checked out at ${wt.path}`,
|
|
19885
|
-
remediation: `cd "${wt.path}" && mmi-cli worktree land --apply (or: mmi-cli gc --apply)`
|
|
20246
|
+
remediation: `cd "${wt.path}" && mmi-cli worktree land --apply (or: mmi-cli worktree gc --apply)`
|
|
19886
20247
|
});
|
|
19887
20248
|
}
|
|
19888
20249
|
for (const branch of input.localBranches) {
|
|
@@ -19895,7 +20256,7 @@ function classifyStaleLeaks(input) {
|
|
|
19895
20256
|
kind: "stale-branch",
|
|
19896
20257
|
ref: branch,
|
|
19897
20258
|
detail: `merged/closed branch ${branch} has no worktree`,
|
|
19898
|
-
remediation: "mmi-cli gc --apply"
|
|
20259
|
+
remediation: "mmi-cli worktree gc --apply"
|
|
19899
20260
|
});
|
|
19900
20261
|
}
|
|
19901
20262
|
for (const wt of input.worktrees) {
|
|
@@ -19919,7 +20280,7 @@ function classifyStaleLeaks(input) {
|
|
|
19919
20280
|
kind: "orphan-dir",
|
|
19920
20281
|
ref: dir,
|
|
19921
20282
|
detail: `directory under the worktrees root is not a registered worktree`,
|
|
19922
|
-
remediation: `mmi-cli gc --apply (or: Remove-Item/rm -rf "${dir}")`
|
|
20283
|
+
remediation: `mmi-cli worktree gc --apply (or: Remove-Item/rm -rf "${dir}")`
|
|
19923
20284
|
});
|
|
19924
20285
|
}
|
|
19925
20286
|
}
|
|
@@ -20715,7 +21076,7 @@ function buildPluginGuardDecision(i) {
|
|
|
20715
21076
|
}
|
|
20716
21077
|
function buildGuardSessionStartLine(state, opts = {}) {
|
|
20717
21078
|
if (state === "healthy" || state === "not-org") return { exitCode: 0 };
|
|
20718
|
-
const recovery = opts.recovery ?? "mmi-cli plugin
|
|
21079
|
+
const recovery = opts.recovery ?? "mmi-cli plugin heal";
|
|
20719
21080
|
const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
|
|
20720
21081
|
const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its marketplace/cache is unresolved";
|
|
20721
21082
|
return {
|
|
@@ -21072,7 +21433,7 @@ function formatLastRun(r) {
|
|
|
21072
21433
|
}
|
|
21073
21434
|
function formatDeployStatus(r) {
|
|
21074
21435
|
const lines = [
|
|
21075
|
-
`deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
|
|
21436
|
+
`runtime deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
|
|
21076
21437
|
"",
|
|
21077
21438
|
`running version: ${r.runningVersion ?? "none stamped"}`,
|
|
21078
21439
|
`last deploy run: ${formatLastRun(r)}`,
|
|
@@ -21105,7 +21466,7 @@ function registerDeployCommands(program3) {
|
|
|
21105
21466
|
const deploy = program3.command("deploy").description("per-stage deploy observability \u2014 last run, health, and running version (#2688)");
|
|
21106
21467
|
deploy.command("status <stage>").description("last deploy run + runtime health probe + running version for a stage (defaults to the current repo)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (stage, o) => {
|
|
21107
21468
|
if (!STAGES2.includes(stage)) {
|
|
21108
|
-
return fail(`deploy status: <stage> must be dev, rc, or main`);
|
|
21469
|
+
return fail(`runtime deploy status: <stage> must be dev, rc, or main`);
|
|
21109
21470
|
}
|
|
21110
21471
|
try {
|
|
21111
21472
|
const cfg = await loadConfig();
|
|
@@ -21134,7 +21495,7 @@ function registerDeployCommands(program3) {
|
|
|
21134
21495
|
}
|
|
21135
21496
|
if (report.health?.ok === false) process.exitCode = 1;
|
|
21136
21497
|
} catch (e) {
|
|
21137
|
-
return failGraceful(`deploy status: ${e.message}`);
|
|
21498
|
+
return failGraceful(`runtime deploy status: ${e.message}`);
|
|
21138
21499
|
}
|
|
21139
21500
|
});
|
|
21140
21501
|
}
|
|
@@ -21253,7 +21614,7 @@ async function collectOnboardStatus() {
|
|
|
21253
21614
|
if (read.ok && read.project) {
|
|
21254
21615
|
registry2 = { ok: true, detail: `project "${read.project.name ?? slug}" registered` };
|
|
21255
21616
|
} else if (read.ok) {
|
|
21256
|
-
registry2 = { ok: false, detail: `repo "${slug}" not registered \u2014 run mmi-cli bootstrap or mmi-cli project set <owner/repo>` };
|
|
21617
|
+
registry2 = { ok: false, detail: `repo "${slug}" not registered \u2014 run mmi-cli bootstrap or mmi-cli org project set <owner/repo>` };
|
|
21257
21618
|
} else {
|
|
21258
21619
|
registry2 = { ok: false, detail: `read failed: ${read.error}` };
|
|
21259
21620
|
}
|
|
@@ -21280,7 +21641,7 @@ async function collectOnboardStatus() {
|
|
|
21280
21641
|
if (!cfg.sagaApiUrl) {
|
|
21281
21642
|
nextCommand = "mmi-cli doctor \u2014 fix Hub API URL configuration first";
|
|
21282
21643
|
} else if (!registry2.ok) {
|
|
21283
|
-
nextCommand = "mmi-cli project set <owner/repo>";
|
|
21644
|
+
nextCommand = "mmi-cli org project set <owner/repo>";
|
|
21284
21645
|
} else if (!board.ok) {
|
|
21285
21646
|
nextCommand = "mmi-cli board read";
|
|
21286
21647
|
} else {
|
|
@@ -21349,10 +21710,11 @@ var LOOP_PLAYBOOKS = {
|
|
|
21349
21710
|
agent: {
|
|
21350
21711
|
title: "Agent",
|
|
21351
21712
|
steps: [
|
|
21352
|
-
{ label: "
|
|
21713
|
+
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
21353
21714
|
{ label: "Read the next board item", command: "mmi-cli board read" },
|
|
21354
|
-
{ label: "
|
|
21715
|
+
{ label: "Claim it and create an isolated worktree", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
21355
21716
|
{ label: "Build and test in the touched package", command: "npm test && npm run build" },
|
|
21717
|
+
{ label: "Publish the branch", command: "git push -u origin HEAD" },
|
|
21356
21718
|
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
21357
21719
|
{ label: "Wait for checks and land to development", command: "mmi-cli pr checks-wait <PR-number> && mmi-cli pr land <PR-number>" },
|
|
21358
21720
|
{ label: "Release only after the gated train is authorized", command: "mmi-cli release --apply" }
|
|
@@ -21361,16 +21723,17 @@ var LOOP_PLAYBOOKS = {
|
|
|
21361
21723
|
"start-work": {
|
|
21362
21724
|
title: "Start Work",
|
|
21363
21725
|
steps: [
|
|
21364
|
-
{ label: "
|
|
21365
|
-
{ label: "
|
|
21366
|
-
{ label: "Claim the
|
|
21726
|
+
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
21727
|
+
{ label: "Read the board item", command: "mmi-cli board show <issue-number>" },
|
|
21728
|
+
{ label: "Claim it and create the worktree from development", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
21367
21729
|
{ label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
|
|
21368
21730
|
]
|
|
21369
21731
|
},
|
|
21370
21732
|
"ship-pr": {
|
|
21371
21733
|
title: "Ship PR",
|
|
21372
21734
|
steps: [
|
|
21373
|
-
{ label: "
|
|
21735
|
+
{ label: "Publish the branch", command: "git push -u origin HEAD" },
|
|
21736
|
+
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
21374
21737
|
{ label: "Wait for CI checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
21375
21738
|
{ label: "Land the PR (merge to development)", command: "mmi-cli pr land <PR-number>" }
|
|
21376
21739
|
]
|
|
@@ -21378,9 +21741,9 @@ var LOOP_PLAYBOOKS = {
|
|
|
21378
21741
|
"hotfix": {
|
|
21379
21742
|
title: "Hotfix",
|
|
21380
21743
|
steps: [
|
|
21381
|
-
{ label: "
|
|
21382
|
-
{ label: "
|
|
21383
|
-
{ label: "
|
|
21744
|
+
{ label: "Create the main-base hotfix PR from an already-merged fix", command: "mmi-cli hotfix start --from <pr#|sha>" },
|
|
21745
|
+
{ label: "Wait for the hotfix PR checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
21746
|
+
{ label: "After the PR is merged, run the gated release", command: "mmi-cli hotfix release <vX.Y.Z> --carries <pr#|sha>" }
|
|
21384
21747
|
]
|
|
21385
21748
|
}
|
|
21386
21749
|
};
|
|
@@ -21430,6 +21793,22 @@ function formatExplainCommand(cmd, rootName) {
|
|
|
21430
21793
|
}
|
|
21431
21794
|
return lines.join("\n").trimEnd();
|
|
21432
21795
|
}
|
|
21796
|
+
function formatExplainGroup(cmd, rootName) {
|
|
21797
|
+
const lines = [
|
|
21798
|
+
`${rootName} ${cmd.path}${cmd.description ? ` \u2014 ${cmd.description}` : ""}`,
|
|
21799
|
+
`Category: ${cmd.category} \xB7 Discovery: ${cmd.discovery}`,
|
|
21800
|
+
"",
|
|
21801
|
+
"Commands:"
|
|
21802
|
+
];
|
|
21803
|
+
for (const child2 of cmd.subcommands) {
|
|
21804
|
+
const args = child2.arguments.map((arg) => {
|
|
21805
|
+
const name = arg.variadic ? `${arg.name}...` : arg.name;
|
|
21806
|
+
return arg.required ? `<${name}>` : `[${name}]`;
|
|
21807
|
+
}).join(" ");
|
|
21808
|
+
lines.push(` ${child2.path}${args ? ` ${args}` : ""}${child2.description ? ` \u2014 ${child2.description}` : ""}`);
|
|
21809
|
+
}
|
|
21810
|
+
return lines.join("\n");
|
|
21811
|
+
}
|
|
21433
21812
|
function formatExplainLoop(playbook) {
|
|
21434
21813
|
const lines = [`${playbook.title} loop:`];
|
|
21435
21814
|
for (const step of playbook.steps) {
|
|
@@ -21439,7 +21818,15 @@ function formatExplainLoop(playbook) {
|
|
|
21439
21818
|
return lines.join("\n");
|
|
21440
21819
|
}
|
|
21441
21820
|
function findCommandInManifest(manifest, commandPath2) {
|
|
21442
|
-
|
|
21821
|
+
const visit = (command) => {
|
|
21822
|
+
if (command.path === commandPath2) return command;
|
|
21823
|
+
for (const child2 of command.subcommands) {
|
|
21824
|
+
const found = visit(child2);
|
|
21825
|
+
if (found) return found;
|
|
21826
|
+
}
|
|
21827
|
+
return void 0;
|
|
21828
|
+
};
|
|
21829
|
+
return visit(manifest.tree);
|
|
21443
21830
|
}
|
|
21444
21831
|
function registerExplainCommand(program3) {
|
|
21445
21832
|
program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").action((commandArgs, opts) => {
|
|
@@ -21454,18 +21841,16 @@ function registerExplainCommand(program3) {
|
|
|
21454
21841
|
}
|
|
21455
21842
|
const manifest = buildCommandManifest(program3);
|
|
21456
21843
|
if (!commandArgs.length) {
|
|
21457
|
-
console.log(
|
|
21458
|
-
console.log(manifest.tree.description ?? "");
|
|
21459
|
-
console.log("\nRun `mmi-cli commands` to list every subcommand + its flags.");
|
|
21844
|
+
console.log(formatManifestHuman(manifest));
|
|
21460
21845
|
return;
|
|
21461
21846
|
}
|
|
21462
21847
|
const commandPath2 = commandArgs.join(" ");
|
|
21463
|
-
const
|
|
21464
|
-
if (!
|
|
21848
|
+
const command = findCommandInManifest(manifest, commandPath2);
|
|
21849
|
+
if (!command) {
|
|
21465
21850
|
fail(`explain: unknown command "${commandPath2}"`, { code: ERROR_CODES.ERR_NOT_FOUND });
|
|
21466
21851
|
return;
|
|
21467
21852
|
}
|
|
21468
|
-
console.log(formatExplainCommand(
|
|
21853
|
+
console.log(command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
|
|
21469
21854
|
});
|
|
21470
21855
|
}
|
|
21471
21856
|
|
|
@@ -22102,7 +22487,7 @@ function checkClaudePlugin(probe) {
|
|
|
22102
22487
|
return {
|
|
22103
22488
|
ok: false,
|
|
22104
22489
|
label: guardState === "no-install" ? "Claude plugin \u2014 not installed" : "Claude plugin \u2014 unresolved (marketplace/cache missing)",
|
|
22105
|
-
fix: "run `mmi-cli plugin
|
|
22490
|
+
fix: "run `mmi-cli plugin heal` to reinstall the MMI marketplace + plugin, then restart Claude",
|
|
22106
22491
|
verbose: evidence
|
|
22107
22492
|
};
|
|
22108
22493
|
}
|
|
@@ -22161,7 +22546,7 @@ function checkPluginCache(input) {
|
|
|
22161
22546
|
// Lead with the versioned-cache framing when there are stale versions; otherwise report the staging litter
|
|
22162
22547
|
// on its own so a cache with zero versioned dirs still gets an honest ✗.
|
|
22163
22548
|
detail: input.stale.length ? `${input.cached} versions cached (${parts.join("; ")})` : parts.join("; "),
|
|
22164
|
-
fix: "run `mmi-cli plugin
|
|
22549
|
+
fix: "run `mmi-cli plugin prune` to review, then `mmi-cli plugin prune --apply` to delete",
|
|
22165
22550
|
verbose: evidence
|
|
22166
22551
|
};
|
|
22167
22552
|
}
|
|
@@ -22191,7 +22576,7 @@ function checkSchedules(probe) {
|
|
|
22191
22576
|
ok: false,
|
|
22192
22577
|
label: "schedules",
|
|
22193
22578
|
detail: `${probe.incomplete.length} source(s) unreadable`,
|
|
22194
|
-
fix: "run `mmi-cli schedules` for the full report \u2014 the notebook may be missing armed entries",
|
|
22579
|
+
fix: "run `mmi-cli org schedules` for the full report \u2014 the notebook may be missing armed entries",
|
|
22195
22580
|
verbose: evidence
|
|
22196
22581
|
};
|
|
22197
22582
|
}
|
|
@@ -22200,7 +22585,7 @@ function checkSchedules(probe) {
|
|
|
22200
22585
|
ok: false,
|
|
22201
22586
|
label: "schedules",
|
|
22202
22587
|
detail: `${probe.drift.length} drift finding(s)`,
|
|
22203
|
-
fix: "run `mmi-cli schedules` \u2014 file/registry/live disagree; each drift line names its per-class remedy, applied in the owning repo",
|
|
22588
|
+
fix: "run `mmi-cli org schedules` \u2014 file/registry/live disagree; each drift line names its per-class remedy, applied in the owning repo",
|
|
22204
22589
|
verbose: evidence
|
|
22205
22590
|
};
|
|
22206
22591
|
}
|
|
@@ -22556,11 +22941,13 @@ function allLongFlags() {
|
|
|
22556
22941
|
if (cachedLongFlags) return cachedLongFlags;
|
|
22557
22942
|
const acc = /* @__PURE__ */ new Set();
|
|
22558
22943
|
const walk = (node) => {
|
|
22944
|
+
if (!isCanonicalSuggestion(node)) return;
|
|
22559
22945
|
for (const opt of node.options) {
|
|
22946
|
+
if (opt.discovery) continue;
|
|
22560
22947
|
const m = /--[\w-]+/.exec(opt.flags);
|
|
22561
22948
|
if (m) acc.add(m[0]);
|
|
22562
22949
|
}
|
|
22563
|
-
for (const
|
|
22950
|
+
for (const child2 of node.subcommands) walk(child2);
|
|
22564
22951
|
};
|
|
22565
22952
|
walk(buildCommandManifest(program2).tree);
|
|
22566
22953
|
cachedLongFlags = [...acc];
|
|
@@ -22596,7 +22983,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
22596
22983
|
process.stderr.write(str);
|
|
22597
22984
|
}
|
|
22598
22985
|
var program2 = new Command();
|
|
22599
|
-
program2.name("mmi-cli").description("MMI Future CLI \u2014 org
|
|
22986
|
+
program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError("(run `mmi-cli commands` for the agentic-coding map, `mmi-cli explain <command>` for detail, or `mmi-cli commands --json --primary` for machine discovery). If you expected this command to exist, your installed mmi-cli may be stale \u2014 update it (see: mmi-cli doctor)");
|
|
22600
22987
|
function appActorDeps() {
|
|
22601
22988
|
return {
|
|
22602
22989
|
fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
|
|
@@ -22626,22 +23013,26 @@ rules.command("gitignore").option("--write", "upsert the managed block into .git
|
|
|
22626
23013
|
if (opts.write) {
|
|
22627
23014
|
if (plan.changed) {
|
|
22628
23015
|
(0, import_node_fs27.writeFileSync)(path2, plan.content, "utf8");
|
|
22629
|
-
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
23016
|
+
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
22630
23017
|
} else {
|
|
22631
|
-
console.log("mmi-cli rules gitignore: up to date");
|
|
23018
|
+
console.log("mmi-cli org rules gitignore: up to date");
|
|
22632
23019
|
}
|
|
22633
23020
|
return;
|
|
22634
23021
|
}
|
|
22635
23022
|
if (plan.changed) {
|
|
22636
|
-
console.error(`mmi-cli rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli rules gitignore --write\` and commit`);
|
|
23023
|
+
console.error(`mmi-cli org rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli org rules gitignore --write\` and commit`);
|
|
22637
23024
|
process.exitCode = 1;
|
|
22638
23025
|
} else {
|
|
22639
|
-
console.log("mmi-cli rules gitignore: up to date");
|
|
23026
|
+
console.log("mmi-cli org rules gitignore: up to date");
|
|
22640
23027
|
}
|
|
22641
23028
|
});
|
|
22642
|
-
program2.command("commands").description("print the
|
|
23029
|
+
program2.command("commands").description("print the grouped agentic-coding map; use --all for operational depth").option("--all", "include operational, internal, compatibility, and delegated depth").option("--json", "machine-readable command manifest").option("--primary", "with --json, emit only the compact primary tree and index").action((o) => {
|
|
22643
23030
|
const manifest = buildCommandManifest(program2);
|
|
22644
|
-
|
|
23031
|
+
if (o.json) {
|
|
23032
|
+
consoleIo.log(JSON.stringify(o.primary ? primaryCommandManifest(manifest) : manifest, null, 2));
|
|
23033
|
+
return;
|
|
23034
|
+
}
|
|
23035
|
+
consoleIo.log(formatManifestHuman(manifest, { all: o.all }));
|
|
22645
23036
|
});
|
|
22646
23037
|
async function runWhoami(io = consoleIo) {
|
|
22647
23038
|
const cfg = await loadConfig();
|
|
@@ -22718,21 +23109,21 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
22718
23109
|
);
|
|
22719
23110
|
if (o.json) return console.log(JSON.stringify(result));
|
|
22720
23111
|
if (!o.quiet || result.removed.length || result.stillDeferred.length || result.skipped.length) {
|
|
22721
|
-
if (result.removed.length) console.log(`gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
|
|
22722
|
-
if (result.stillDeferred.length) console.log(`gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
|
|
22723
|
-
if (result.skipped.length) console.log(`gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
|
|
22724
|
-
if (!result.removed.length && !result.stillDeferred.length && !result.skipped.length) console.log("gc sweep-deferred: nothing queued");
|
|
23112
|
+
if (result.removed.length) console.log(`worktree gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
|
|
23113
|
+
if (result.stillDeferred.length) console.log(`worktree gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
|
|
23114
|
+
if (result.skipped.length) console.log(`worktree gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
|
|
23115
|
+
if (!result.removed.length && !result.stillDeferred.length && !result.skipped.length) console.log("worktree gc sweep-deferred: nothing queued");
|
|
22725
23116
|
}
|
|
22726
23117
|
} catch (e) {
|
|
22727
|
-
fail(`gc sweep-deferred: ${e.message}`);
|
|
23118
|
+
fail(`worktree gc sweep-deferred: ${e.message}`);
|
|
22728
23119
|
}
|
|
22729
23120
|
}, DEFERRED_SWEEP_HARD_TIMEOUT_MS, () => {
|
|
22730
|
-
console.error("gc sweep-deferred: exceeded hard timeout \u2014 exiting so the detached worker cannot leak (#2841)");
|
|
23121
|
+
console.error("worktree gc sweep-deferred: exceeded hard timeout \u2014 exiting so the detached worker cannot leak (#2841)");
|
|
22731
23122
|
process.exit(process.exitCode ?? 0);
|
|
22732
23123
|
});
|
|
22733
23124
|
});
|
|
22734
23125
|
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 and plans whose current hash is confirmed synced to North Star (#1864) instead of git branches/refs").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
|
|
22735
|
-
if (o.apply && o.dryRun) return fail("gc: choose either --dry-run or --apply");
|
|
23126
|
+
if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
|
|
22736
23127
|
if (o.scratch) {
|
|
22737
23128
|
try {
|
|
22738
23129
|
const root = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
@@ -22740,11 +23131,11 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
22740
23131
|
if (o.json) return console.log(JSON.stringify({ dryRun: !o.apply, ...run }, null, 2));
|
|
22741
23132
|
return console.log(formatScratchGcPlan(run.plan, Boolean(o.apply), run.applied));
|
|
22742
23133
|
} catch (e) {
|
|
22743
|
-
return fail(`gc --scratch: ${e.message}`);
|
|
23134
|
+
return fail(`worktree gc --scratch: ${e.message}`);
|
|
22744
23135
|
}
|
|
22745
23136
|
}
|
|
22746
23137
|
const limit = Number.parseInt(o.limit, 10);
|
|
22747
|
-
if (!Number.isFinite(limit) || limit < 1) return fail("gc: --limit must be a positive integer");
|
|
23138
|
+
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
22748
23139
|
try {
|
|
22749
23140
|
const plan = await gcPlan(o.remote, limit);
|
|
22750
23141
|
if (o.apply && !o.json) console.log(formatGcPlan(plan, false));
|
|
@@ -22769,7 +23160,7 @@ ${renderGcApplyResult(applyResult)}`);
|
|
|
22769
23160
|
}
|
|
22770
23161
|
if (applyResult?.failed.length) process.exitCode = 1;
|
|
22771
23162
|
} catch (e) {
|
|
22772
|
-
fail(`gc: ${e.message}`);
|
|
23163
|
+
fail(`worktree gc: ${e.message}`);
|
|
22773
23164
|
}
|
|
22774
23165
|
});
|
|
22775
23166
|
var NPM_PROVISION_TIMEOUT_MS = 3e5;
|
|
@@ -22779,19 +23170,19 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
22779
23170
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
22780
23171
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
22781
23172
|
return new Promise((resolve5, reject) => {
|
|
22782
|
-
const
|
|
23173
|
+
const child2 = (0, import_node_child_process12.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
22783
23174
|
const timer = setTimeout(() => {
|
|
22784
23175
|
try {
|
|
22785
|
-
|
|
23176
|
+
child2.kill();
|
|
22786
23177
|
} catch {
|
|
22787
23178
|
}
|
|
22788
23179
|
reject(new Error(`${command} timed out after ${NPM_PROVISION_TIMEOUT_MS}ms in ${cwd}`));
|
|
22789
23180
|
}, NPM_PROVISION_TIMEOUT_MS);
|
|
22790
|
-
|
|
23181
|
+
child2.on("error", (e) => {
|
|
22791
23182
|
clearTimeout(timer);
|
|
22792
23183
|
reject(e);
|
|
22793
23184
|
});
|
|
22794
|
-
|
|
23185
|
+
child2.on("exit", (code) => {
|
|
22795
23186
|
clearTimeout(timer);
|
|
22796
23187
|
if (code === 0) resolve5();
|
|
22797
23188
|
else reject(new Error(`${command} exited ${code} in ${cwd}`));
|
|
@@ -22969,7 +23360,7 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
22969
23360
|
return { onBoard: false };
|
|
22970
23361
|
}
|
|
22971
23362
|
if (!cfg.projectId) {
|
|
22972
|
-
console.error(`issue create: board attach skipped \u2014 no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
|
|
23363
|
+
console.error(`issue create: board attach skipped \u2014 no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli org project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
|
|
22973
23364
|
return { onBoard: false };
|
|
22974
23365
|
}
|
|
22975
23366
|
try {
|
|
@@ -23049,7 +23440,7 @@ docsAudit.command("record").description("write a dated janitor verdict for one r
|
|
|
23049
23440
|
if (o.outcome === "clean") outcome = { kind: "clean" };
|
|
23050
23441
|
else if (o.outcome === "refreshed") outcome = { kind: "refreshed", count: Number(o.count) };
|
|
23051
23442
|
else if (o.outcome === "failed") outcome = { kind: "failed", reason: o.reason ?? "" };
|
|
23052
|
-
else return failGraceful(`docs
|
|
23443
|
+
else return failGraceful(`docs audit record: --outcome must be clean|refreshed|failed, got "${o.outcome}"`);
|
|
23053
23444
|
const verdict = docsAuditRecord({ repo, date, shaRange: o.shaRange, outcome, checkerVendor: o.checkerVendor });
|
|
23054
23445
|
await reportWrite("docs-audit record", await recordDocsAudit(verdict, registryClientDeps(await loadConfig())));
|
|
23055
23446
|
} catch (e) {
|
|
@@ -23084,43 +23475,36 @@ tenant.command("control <owner/repo> <stage> <action>").description("run bounded
|
|
|
23084
23475
|
const body = { ok: result.conclusion === "success", secrets: result.secrets, ssmStatus: result.conclusion === "success" ? "Success" : "Failed", raw: result.secretsRaw };
|
|
23085
23476
|
const { lines, failure } = renderVerifySecrets(body);
|
|
23086
23477
|
for (const line of lines) printLine(line);
|
|
23087
|
-
if (failure) return failGraceful(`tenant control ${stage} verify-secrets: ${failure}`);
|
|
23478
|
+
if (failure) return failGraceful(`runtime tenant control ${stage} verify-secrets: ${failure}`);
|
|
23088
23479
|
} else {
|
|
23089
23480
|
printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
|
|
23090
23481
|
}
|
|
23091
23482
|
if (result.conclusion === "failure") {
|
|
23092
|
-
return failGraceful(`tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
|
|
23483
|
+
return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
|
|
23093
23484
|
}
|
|
23094
23485
|
} catch (e) {
|
|
23095
|
-
return failGraceful(`tenant control: ${e.message}`);
|
|
23486
|
+
return failGraceful(`runtime tenant control: ${e.message}`);
|
|
23096
23487
|
}
|
|
23097
23488
|
});
|
|
23098
23489
|
tenant.command("status <owner/repo> <stage>").description("read tenant runtime readiness without dispatching tenant-control: DEPLOY row, last deploy run, public URL probe, and TLS/Caddy/Cloudflare hints").action(async (repo, stage) => {
|
|
23099
|
-
if (!["dev", "rc", "main"].includes(stage)) return fail("tenant status: <stage> must be dev, rc, or main");
|
|
23100
|
-
const cfg = await loadConfig();
|
|
23101
|
-
const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
|
|
23102
|
-
console.log(JSON.stringify(result, null, 2));
|
|
23103
|
-
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
23104
|
-
});
|
|
23105
|
-
tenant.command("readiness <owner/repo> <stage>").description("alias for tenant status: read-only tenant runtime readiness").action(async (repo, stage) => {
|
|
23106
|
-
if (!["dev", "rc", "main"].includes(stage)) return fail("tenant readiness: <stage> must be dev, rc, or main");
|
|
23490
|
+
if (!["dev", "rc", "main"].includes(stage)) return fail("runtime tenant status: <stage> must be dev, rc, or main");
|
|
23107
23491
|
const cfg = await loadConfig();
|
|
23108
23492
|
const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
|
|
23109
23493
|
console.log(JSON.stringify(result, null, 2));
|
|
23110
23494
|
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
23111
23495
|
});
|
|
23112
23496
|
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) => {
|
|
23113
|
-
if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("tenant redeploy: <stage> must be dev, rc, or main");
|
|
23497
|
+
if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime tenant redeploy: <stage> must be dev, rc, or main");
|
|
23114
23498
|
try {
|
|
23115
23499
|
const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage, ref: o.ref, watch: o.watch });
|
|
23116
23500
|
return printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantRedeploy(result));
|
|
23117
23501
|
} catch (e) {
|
|
23118
|
-
return failGraceful(`tenant redeploy: ${e.message}`);
|
|
23502
|
+
return failGraceful(`runtime tenant redeploy: ${e.message}`);
|
|
23119
23503
|
}
|
|
23120
23504
|
});
|
|
23121
23505
|
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) => {
|
|
23122
23506
|
if (o.retire && !o.yes) {
|
|
23123
|
-
return fail("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");
|
|
23507
|
+
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");
|
|
23124
23508
|
}
|
|
23125
23509
|
const cfg = await loadConfig();
|
|
23126
23510
|
const cdeps = registryClientDeps(cfg);
|
|
@@ -23140,7 +23524,7 @@ tenant.command("sweep-rc").description("discover (and optionally retire) running
|
|
|
23140
23524
|
}, { retire: !!o.retire });
|
|
23141
23525
|
return printLine(o.json ? JSON.stringify(result) : renderSweep(result));
|
|
23142
23526
|
} catch (e) {
|
|
23143
|
-
return failGraceful(`tenant sweep-rc: ${e.message}`);
|
|
23527
|
+
return failGraceful(`runtime tenant sweep-rc: ${e.message}`);
|
|
23144
23528
|
}
|
|
23145
23529
|
});
|
|
23146
23530
|
async function resolveDnsBounded(host, timeoutMs = 3e3) {
|
|
@@ -23249,7 +23633,7 @@ async function projectTarget(commandName, explicitTarget) {
|
|
|
23249
23633
|
project.command("list").description("list all projects (identity + board, never deploy coords)").option("--json", "machine-readable output").action(async (o) => {
|
|
23250
23634
|
const cfg = await loadConfig();
|
|
23251
23635
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
23252
|
-
if (!projects) return failGraceful("project list: Hub API unreachable or this repo is not bootstrapped");
|
|
23636
|
+
if (!projects) return failGraceful("org project list: Hub API unreachable or this repo is not bootstrapped");
|
|
23253
23637
|
if (o.json) {
|
|
23254
23638
|
console.log(JSON.stringify(projects));
|
|
23255
23639
|
return;
|
|
@@ -23262,16 +23646,16 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
23262
23646
|
const cfg = await loadConfig();
|
|
23263
23647
|
let target;
|
|
23264
23648
|
try {
|
|
23265
|
-
target = await projectTarget("project get", repoOrSlug);
|
|
23649
|
+
target = await projectTarget("org project get", repoOrSlug);
|
|
23266
23650
|
} catch (e) {
|
|
23267
23651
|
return fail(e.message);
|
|
23268
23652
|
}
|
|
23269
23653
|
const read = await fetchProjectBySlugChecked(slugOf(target), registryClientDeps(cfg));
|
|
23270
23654
|
if (!read.ok) {
|
|
23271
|
-
return failGraceful(`project get: Hub registry read failed (${read.error}) \u2014 likely transient (cold start, network, or auth blip); retry shortly`);
|
|
23655
|
+
return failGraceful(`org project get: Hub registry read failed (${read.error}) \u2014 likely transient (cold start, network, or auth blip); retry shortly`);
|
|
23272
23656
|
}
|
|
23273
23657
|
if (!read.project) {
|
|
23274
|
-
return failGraceful(`project get: no registry META for ${target} (unknown or unbootstrapped)`);
|
|
23658
|
+
return failGraceful(`org project get: no registry META for ${target} (unknown or unbootstrapped)`);
|
|
23275
23659
|
}
|
|
23276
23660
|
console.log(JSON.stringify(read.project));
|
|
23277
23661
|
if (!o.json) {
|
|
@@ -23282,55 +23666,31 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
23282
23666
|
console.error(
|
|
23283
23667
|
`${m.name ?? target} \xB7 class ${m.class ?? "?"} \xB7 deploy ${m.deployModel ?? "?"}
|
|
23284
23668
|
release track: ${track} \u2014 stages: ${stages}${note}
|
|
23285
|
-
deploys run centrally (tenant-deploy.yml); product repos carry no deploy files.
|
|
23669
|
+
deploys run centrally (tenant-deploy.yml); product repos carry no deploy files. Inspect nonsecret DEPLOY facts with \`mmi-cli org project deploy get\`; full coords remain OIDC-gated.`
|
|
23286
23670
|
);
|
|
23287
23671
|
}
|
|
23288
23672
|
});
|
|
23289
|
-
project.command("resolve <owner/repo>").description("deploy coords for a stage \u2014 for diagnosis. NOTE: /deploy-coords is OIDC-gated (a deploy job\u2019s id-token), so a gh-token CLI cannot read it from a dev machine").option("--stage <main|rc>", "deploy stage", "main").option("--json", "machine-readable output").action((_repoOrRepo, o) => {
|
|
23290
|
-
const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect nonsecret DEPLOY facts with `mmi-cli project deploy get`. Full coords stay OIDC-gated.";
|
|
23291
|
-
if (o.json) {
|
|
23292
|
-
console.log(JSON.stringify({ ok: false, stage: o.stage, error: msg }));
|
|
23293
|
-
process.exitCode = 1;
|
|
23294
|
-
return;
|
|
23295
|
-
}
|
|
23296
|
-
fail(msg);
|
|
23297
|
-
});
|
|
23298
23673
|
var projectDeploy = project.command("deploy").description("read nonsecret DEPLOY# facts (domain, port, deploy path, substrate, host presence)");
|
|
23299
23674
|
projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").option("--stage <stage>", "dev | rc | main").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
23300
23675
|
const cfg = await loadConfig();
|
|
23301
23676
|
let target;
|
|
23302
23677
|
try {
|
|
23303
|
-
target = await projectTarget("project deploy get", repoOrSlug);
|
|
23678
|
+
target = await projectTarget("org project deploy get", repoOrSlug);
|
|
23304
23679
|
} catch (e) {
|
|
23305
23680
|
return fail(e.message);
|
|
23306
23681
|
}
|
|
23307
23682
|
const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
|
|
23308
|
-
if (!out) return failGraceful(`project deploy get: Hub deploy facts read failed for ${target}`);
|
|
23683
|
+
if (!out) return failGraceful(`org project deploy get: Hub deploy facts read failed for ${target}`);
|
|
23309
23684
|
const stage = o.stage?.trim();
|
|
23310
|
-
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("project deploy get: --stage must be dev, rc, or main");
|
|
23685
|
+
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("org project deploy get: --stage must be dev, rc, or main");
|
|
23311
23686
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
23312
23687
|
console.log(JSON.stringify(payload));
|
|
23313
23688
|
});
|
|
23314
|
-
|
|
23689
|
+
project.command("doctor [owner/repo]").description("diagnose Hub v2 readiness for a repo without reading product repo files \u2014 appOwnedGaps are advisory templates; clear them with `org project attest`; defaults to the current repo").option("--v2", "compatibility flag; v2 readiness is the default").option("--json", "machine-readable output").action(async (repo, _o) => {
|
|
23315
23690
|
const cfg = await loadConfig();
|
|
23316
23691
|
let target;
|
|
23317
23692
|
try {
|
|
23318
|
-
target = await projectTarget("project
|
|
23319
|
-
} catch (e) {
|
|
23320
|
-
return fail(e.message);
|
|
23321
|
-
}
|
|
23322
|
-
const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
|
|
23323
|
-
if (!out) return failGraceful(`project deploy list: Hub deploy facts read failed for ${target}`);
|
|
23324
|
-
const stage = o.stage?.trim();
|
|
23325
|
-
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("project deploy list: --stage must be dev, rc, or main");
|
|
23326
|
-
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
23327
|
-
console.log(JSON.stringify(payload));
|
|
23328
|
-
});
|
|
23329
|
-
project.command("doctor [owner/repo]").description("diagnose Hub v2 readiness for a repo without reading product repo files \u2014 appOwnedGaps are advisory templates; clear them with `project attest`; defaults to the current repo").option("--v2", "compatibility flag; v2 readiness is the default").option("--json", "machine-readable output").action(async (repo, _o) => {
|
|
23330
|
-
const cfg = await loadConfig();
|
|
23331
|
-
let target;
|
|
23332
|
-
try {
|
|
23333
|
-
target = await projectTarget("project doctor", repo);
|
|
23693
|
+
target = await projectTarget("org project doctor", repo);
|
|
23334
23694
|
} catch (e) {
|
|
23335
23695
|
return fail(e.message);
|
|
23336
23696
|
}
|
|
@@ -23342,7 +23702,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23342
23702
|
const cfg = await loadConfig();
|
|
23343
23703
|
let target;
|
|
23344
23704
|
try {
|
|
23345
|
-
target = await projectTarget("project heal", repo);
|
|
23705
|
+
target = await projectTarget("org project heal", repo);
|
|
23346
23706
|
} catch (e) {
|
|
23347
23707
|
return fail(e.message);
|
|
23348
23708
|
}
|
|
@@ -23351,7 +23711,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23351
23711
|
fetchProject: (s) => fetchProjectBySlugChecked(s, reg),
|
|
23352
23712
|
upsertProject: (s, patch) => upsertProject(s, patch, reg)
|
|
23353
23713
|
});
|
|
23354
|
-
if (out.status === "aborted") return failGraceful(`project heal: ${out.error}`);
|
|
23714
|
+
if (out.status === "aborted") return failGraceful(`org project heal: ${out.error}`);
|
|
23355
23715
|
if (out.status === "planned") {
|
|
23356
23716
|
console.log(JSON.stringify({ ok: true, slug: out.slug, dryRun: true, patch: out.patch, appOwnedGaps: out.appOwnedGaps }));
|
|
23357
23717
|
return;
|
|
@@ -23360,15 +23720,15 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23360
23720
|
console.log(JSON.stringify({ ok: true, slug: out.slug, applied: [], appOwnedGaps: out.appOwnedGaps }));
|
|
23361
23721
|
return;
|
|
23362
23722
|
}
|
|
23363
|
-
if (out.status === "write-failed") return reportWrite("project heal", out.write);
|
|
23723
|
+
if (out.status === "write-failed") return reportWrite("org project heal", out.write);
|
|
23364
23724
|
console.log(JSON.stringify({ ok: true, slug: out.slug, applied: out.applied, appOwnedGaps: out.appOwnedGaps, result: out.result }));
|
|
23365
23725
|
});
|
|
23366
23726
|
project.command("readiness [owner/repo]").description("update the repo v2 readiness issue with Hub diagnosis and app-owned tasks; defaults to the current repo").option("--update-issue", "create/update the bounded v2 readiness issue section").option("--json", "machine-readable output").action(async (repo, o) => {
|
|
23367
|
-
if (!o.updateIssue) return fail("project readiness: pass --update-issue");
|
|
23727
|
+
if (!o.updateIssue) return fail("org project readiness: pass --update-issue");
|
|
23368
23728
|
const cfg = await loadConfig();
|
|
23369
23729
|
let target;
|
|
23370
23730
|
try {
|
|
23371
|
-
target = await projectTarget("project readiness", repo);
|
|
23731
|
+
target = await projectTarget("org project readiness", repo);
|
|
23372
23732
|
} catch (e) {
|
|
23373
23733
|
return fail(e.message);
|
|
23374
23734
|
}
|
|
@@ -23380,19 +23740,19 @@ project.command("attest [owner/repo]").description("attest this repo's app-owned
|
|
|
23380
23740
|
const cfg = await loadConfig();
|
|
23381
23741
|
let target;
|
|
23382
23742
|
try {
|
|
23383
|
-
target = await projectTarget("project attest", repoOrSlug);
|
|
23743
|
+
target = await projectTarget("org project attest", repoOrSlug);
|
|
23384
23744
|
} catch (e) {
|
|
23385
23745
|
return fail(e.message);
|
|
23386
23746
|
}
|
|
23387
23747
|
const repo = target.includes("/") ? target : `mutmutco/${slugOf(target)}`;
|
|
23388
23748
|
const res = await attestAppGaps(slugOf(target), repo, registryClientDeps(cfg));
|
|
23389
|
-
return reportWrite("project attest", res);
|
|
23749
|
+
return reportWrite("org project attest", res);
|
|
23390
23750
|
});
|
|
23391
23751
|
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").option("--class <class>", "deployable | content").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (v2 capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", "read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
23392
23752
|
const cfg = await loadConfig();
|
|
23393
23753
|
let target;
|
|
23394
23754
|
try {
|
|
23395
|
-
target = await projectTarget("project set", repoOrSlug);
|
|
23755
|
+
target = await projectTarget("org project set", repoOrSlug);
|
|
23396
23756
|
} catch (e) {
|
|
23397
23757
|
return fail(e.message);
|
|
23398
23758
|
}
|
|
@@ -23401,12 +23761,12 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23401
23761
|
const setAlias = o.set ?? [];
|
|
23402
23762
|
const vars = [...o.var ?? [], ...setAlias];
|
|
23403
23763
|
const dupe = duplicateVarKeyAcrossFlags(o.var ?? [], setAlias);
|
|
23404
|
-
if (dupe) return fail(`project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
23764
|
+
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
23405
23765
|
if (o.secretsFile) {
|
|
23406
23766
|
try {
|
|
23407
23767
|
vars.push(`secrets=${(0, import_node_fs27.readFileSync)(o.secretsFile, "utf8")}`);
|
|
23408
23768
|
} catch (e) {
|
|
23409
|
-
return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
23769
|
+
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
23410
23770
|
}
|
|
23411
23771
|
}
|
|
23412
23772
|
let patch;
|
|
@@ -23421,19 +23781,19 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23421
23781
|
clearWebProfile: Boolean(o.clearWebProfile)
|
|
23422
23782
|
});
|
|
23423
23783
|
} catch (e) {
|
|
23424
|
-
return fail(e.message.replace(/^project set: /, "project set: "));
|
|
23784
|
+
return fail(e.message.replace(/^org project set: /, "org project set: "));
|
|
23425
23785
|
}
|
|
23426
23786
|
const existing = await fetchProjectBySlug(slug, registryClientDeps(cfg));
|
|
23427
23787
|
const boardError = boardLinkWriteError(patch, existing);
|
|
23428
|
-
if (boardError) return fail(`project set: ${boardError}`);
|
|
23788
|
+
if (boardError) return fail(`org project set: ${boardError}`);
|
|
23429
23789
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
23430
|
-
return reportWrite("project set", res);
|
|
23790
|
+
return reportWrite("org project set", res);
|
|
23431
23791
|
});
|
|
23432
23792
|
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) => {
|
|
23433
23793
|
const cfg = await loadConfig();
|
|
23434
23794
|
let target;
|
|
23435
23795
|
try {
|
|
23436
|
-
target = await projectTarget("project retire", repoOrSlug);
|
|
23796
|
+
target = await projectTarget("org project retire", repoOrSlug);
|
|
23437
23797
|
} catch (e) {
|
|
23438
23798
|
return fail(e.message);
|
|
23439
23799
|
}
|
|
@@ -23459,7 +23819,7 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
23459
23819
|
printLine(JSON.stringify(structured));
|
|
23460
23820
|
} else {
|
|
23461
23821
|
const verb = result.applied ? "retired" : "WOULD retire (dry-run; pass --apply to delete)";
|
|
23462
|
-
printLine(`project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
23822
|
+
printLine(`org project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
23463
23823
|
if (result.applied) {
|
|
23464
23824
|
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED \u2014 ${result.removedMeta.error}`}`);
|
|
23465
23825
|
} else {
|
|
@@ -23474,10 +23834,10 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
23474
23834
|
printLine(` ${result.vaultNote}`);
|
|
23475
23835
|
}
|
|
23476
23836
|
if (result.applied && !result.removedMeta.ok) {
|
|
23477
|
-
return failGraceful(`project retire: META delete failed \u2014 ${result.removedMeta.error}`);
|
|
23837
|
+
return failGraceful(`org project retire: META delete failed \u2014 ${result.removedMeta.error}`);
|
|
23478
23838
|
}
|
|
23479
23839
|
});
|
|
23480
|
-
var fullTrack = program2.command("full-track").description("direct-to-
|
|
23840
|
+
var fullTrack = program2.command("full-track").description("direct-to-train readiness audits");
|
|
23481
23841
|
fullTrack.command("readiness <owner/repo>").description("aggregate branch topology, train authority, deploy facts, endpoint health, and rcand readiness for direct -> full switches").option("--json", "machine-readable output").action(async (repo, _o) => {
|
|
23482
23842
|
const cfg = await loadConfig();
|
|
23483
23843
|
const reg = registryClientDeps(cfg);
|
|
@@ -23490,7 +23850,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
23490
23850
|
buildTenantRuntimeStatusFor(repo, "rc", cfg),
|
|
23491
23851
|
buildTenantRuntimeStatusFor(repo, "main", cfg)
|
|
23492
23852
|
]);
|
|
23493
|
-
if (!metaRead.ok) return failGraceful(`
|
|
23853
|
+
if (!metaRead.ok) return failGraceful(`train readiness: Hub registry read failed (${metaRead.error})`);
|
|
23494
23854
|
const report = buildFullTrackReadinessReport({
|
|
23495
23855
|
repo,
|
|
23496
23856
|
slug,
|
|
@@ -23502,18 +23862,18 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
23502
23862
|
console.log(JSON.stringify(report, null, 2));
|
|
23503
23863
|
if (!report.rcand.canApply) process.exitCode = 1;
|
|
23504
23864
|
});
|
|
23505
|
-
project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 omitted flags keep their stored value; seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "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 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").option("--substrate <substrate>", "hetzner-ssh; omit to leave the row unchanged").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 true = #2662 env passthrough, no .env symlink; omit to leave the row unchanged").option("--force", "skip the #2748 fileless-compose guard (flip noEnvFile=true even if the stage branch compose still declares env_file:)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
23865
|
+
project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 omitted flags keep their stored value; seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "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 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").option("--substrate <substrate>", "hetzner-ssh; omit to leave the row unchanged").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 true = #2662 env passthrough, no .env symlink; omit to leave the row unchanged").option("--force", "skip the #2748 fileless-compose guard (flip noEnvFile=true even if the stage branch compose still declares env_file:)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
23506
23866
|
const cfg = await loadConfig();
|
|
23507
23867
|
let target;
|
|
23508
23868
|
try {
|
|
23509
|
-
target = await projectTarget("project set-deploy", repoOrSlug);
|
|
23869
|
+
target = await projectTarget("org project set-deploy", repoOrSlug);
|
|
23510
23870
|
} catch (e) {
|
|
23511
23871
|
return fail(e.message);
|
|
23512
23872
|
}
|
|
23513
23873
|
let noEnvFile;
|
|
23514
23874
|
if (typeof o.envFile === "string") {
|
|
23515
23875
|
const v = o.envFile.trim().toLowerCase();
|
|
23516
|
-
if (v !== "true" && v !== "false") return fail("project set-deploy: --no-env-file must be true or false");
|
|
23876
|
+
if (v !== "true" && v !== "false") return fail("org project set-deploy: --no-env-file must be true or false");
|
|
23517
23877
|
noEnvFile = v === "true";
|
|
23518
23878
|
}
|
|
23519
23879
|
if (noEnvFile === true) {
|
|
@@ -23523,10 +23883,10 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
|
|
|
23523
23883
|
const sameRepo = !repoOrSlug || Boolean(cwdRepo) && (repoOrSlug.includes("/") ? cwdRepo.toLowerCase() === target.toLowerCase() : slugOf(cwdRepo) === slugOf(target));
|
|
23524
23884
|
if (sameRepo) {
|
|
23525
23885
|
const verdict = evaluateFilelessComposeGuard({ composeText: await readStageBranchCompose(branch), branch, force: Boolean(o.force) });
|
|
23526
|
-
if (!verdict.ok) return fail(`project set-deploy: ${verdict.reason}`);
|
|
23527
|
-
if (verdict.warn) console.error(`project set-deploy: ${verdict.warn}`);
|
|
23886
|
+
if (!verdict.ok) return fail(`org project set-deploy: ${verdict.reason}`);
|
|
23887
|
+
if (verdict.warn) console.error(`org project set-deploy: ${verdict.warn}`);
|
|
23528
23888
|
} else {
|
|
23529
|
-
console.error(`project set-deploy: --no-env-file guard skipped \u2014 run from the ${target} checkout to verify the ${branch} branch compose (#2748).`);
|
|
23889
|
+
console.error(`org project set-deploy: --no-env-file guard skipped \u2014 run from the ${target} checkout to verify the ${branch} branch compose (#2748).`);
|
|
23530
23890
|
}
|
|
23531
23891
|
}
|
|
23532
23892
|
}
|
|
@@ -23550,13 +23910,13 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
|
|
|
23550
23910
|
return fail(e.message);
|
|
23551
23911
|
}
|
|
23552
23912
|
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
23553
|
-
return reportWrite("project set-deploy", res);
|
|
23913
|
+
return reportWrite("org project set-deploy", res);
|
|
23554
23914
|
});
|
|
23555
23915
|
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
23556
23916
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
23557
23917
|
const cfg = await loadConfig();
|
|
23558
23918
|
const org = await fetchOrgConfig(registryClientDeps(cfg));
|
|
23559
|
-
if (!org) return failGraceful("
|
|
23919
|
+
if (!org) return failGraceful("org config get: Hub API unreachable, unseeded, or this repo is not bootstrapped");
|
|
23560
23920
|
console.log(JSON.stringify(org));
|
|
23561
23921
|
});
|
|
23562
23922
|
var oauth = program2.command("oauth").description("per-repo Google OAuth \u2014 plan the canonical URI set, verify the client is port-agnostic");
|
|
@@ -23568,7 +23928,7 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
23568
23928
|
try {
|
|
23569
23929
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
23570
23930
|
} catch (e) {
|
|
23571
|
-
return failGraceful(`oauth plan: ${e.message}`);
|
|
23931
|
+
return failGraceful(`org oauth plan: ${e.message}`);
|
|
23572
23932
|
}
|
|
23573
23933
|
const origins = expectedJsOrigins(oc);
|
|
23574
23934
|
const redirects = expectedRedirectUris(oc);
|
|
@@ -23586,18 +23946,18 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
23586
23946
|
console.log(`
|
|
23587
23947
|
SSM cred params (under /mmi-future/${slug}/):`);
|
|
23588
23948
|
ssm.forEach((k) => console.log(` ${k}`));
|
|
23589
|
-
console.log("\nProvision/repair the Console client per docs/Guides/oauth-provision.md; store creds with `mmi-cli oauth set-creds`.");
|
|
23949
|
+
console.log("\nProvision/repair the Console client per docs/Guides/oauth-provision.md; store creds with `mmi-cli org oauth set-creds`.");
|
|
23590
23950
|
});
|
|
23591
23951
|
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) => {
|
|
23592
23952
|
const raw = await readStdin();
|
|
23593
23953
|
if (!raw.trim()) {
|
|
23594
|
-
return fail("oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli oauth set-creds --repo <owner/repo> < client.json");
|
|
23954
|
+
return fail("org oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli org oauth set-creds --repo <owner/repo> < client.json");
|
|
23595
23955
|
}
|
|
23596
23956
|
let creds;
|
|
23597
23957
|
try {
|
|
23598
23958
|
creds = parseOauthClientJson(raw);
|
|
23599
23959
|
} catch (e) {
|
|
23600
|
-
return fail(`oauth set-creds: ${e.message}`);
|
|
23960
|
+
return fail(`org oauth set-creds: ${e.message}`);
|
|
23601
23961
|
}
|
|
23602
23962
|
await withSecrets(async (d) => {
|
|
23603
23963
|
for (const key of oauthSsmKeys()) {
|
|
@@ -23607,7 +23967,7 @@ oauth.command("set-creds").description('store the OAuth client into the canonica
|
|
|
23607
23967
|
return;
|
|
23608
23968
|
}
|
|
23609
23969
|
}
|
|
23610
|
-
console.log(`OAuth client stored in the ${oauthSsmKeys().length} canonical stageless keys. Run \`mmi-cli oauth verify\` to confirm the client is port-agnostic.`);
|
|
23970
|
+
console.log(`OAuth client stored in the ${oauthSsmKeys().length} canonical stageless keys. Run \`mmi-cli org oauth verify\` to confirm the client is port-agnostic.`);
|
|
23611
23971
|
});
|
|
23612
23972
|
});
|
|
23613
23973
|
oauth.command("verify").description("probe Google authorize with an arbitrary port (:9123) to confirm the client is port-agnostic").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--client-id <id>", "OAuth client_id (else read the stageless GOOGLE_CLIENT_ID from SSM)").option("--json", "machine-readable output").action(async (o) => {
|
|
@@ -23618,7 +23978,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23618
23978
|
try {
|
|
23619
23979
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
23620
23980
|
} catch (e) {
|
|
23621
|
-
return failGraceful(`oauth verify: ${e.message}`);
|
|
23981
|
+
return failGraceful(`org oauth verify: ${e.message}`);
|
|
23622
23982
|
}
|
|
23623
23983
|
let clientId = o.clientId;
|
|
23624
23984
|
if (!clientId) {
|
|
@@ -23627,7 +23987,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23627
23987
|
});
|
|
23628
23988
|
}
|
|
23629
23989
|
if (!clientId) {
|
|
23630
|
-
return failGraceful("oauth verify: no client_id (pass --client-id, or provision the repo so GOOGLE_CLIENT_ID exists)");
|
|
23990
|
+
return failGraceful("org oauth verify: no client_id (pass --client-id, or provision the repo so GOOGLE_CLIENT_ID exists)");
|
|
23631
23991
|
}
|
|
23632
23992
|
const redirectUri = probeRedirectUri(oc.callbackPath);
|
|
23633
23993
|
let body = "";
|
|
@@ -23635,7 +23995,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23635
23995
|
const res = await fetch(buildAuthorizeProbeUrl(clientId, redirectUri), { redirect: "follow", signal: AbortSignal.timeout(1e4) });
|
|
23636
23996
|
body = await res.text();
|
|
23637
23997
|
} catch (e) {
|
|
23638
|
-
return failGraceful(`oauth verify: probe request failed: ${e.message}`);
|
|
23998
|
+
return failGraceful(`org oauth verify: probe request failed: ${e.message}`);
|
|
23639
23999
|
}
|
|
23640
24000
|
const mismatch = authorizeBodyHasMismatch(body);
|
|
23641
24001
|
if (o.json) {
|
|
@@ -24454,7 +24814,7 @@ function renderTrainApply(commandName, r) {
|
|
|
24454
24814
|
return r.announceNote ? `${base}; announce: ${r.announceNote}` : base;
|
|
24455
24815
|
}
|
|
24456
24816
|
function renderTenantRedeploy(r) {
|
|
24457
|
-
return `mmi-cli tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
24817
|
+
return `mmi-cli runtime tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
24458
24818
|
}
|
|
24459
24819
|
async function resolveRcandPlanTargets() {
|
|
24460
24820
|
try {
|
|
@@ -24594,7 +24954,7 @@ var access = program2.command("access").description("org access audit (read-only
|
|
|
24594
24954
|
access.command("role [repo]").description("D14 train authority for a repo (server-side Hub check): master | project-admin | developer").option("--json", "machine-readable output").action(async (repoArg) => {
|
|
24595
24955
|
const o = { json: rawFlag("--json") };
|
|
24596
24956
|
const repo = repoArg ?? await resolveRepo();
|
|
24597
|
-
if (!repo) return fail("access role: pass <owner/repo> or run inside a repo checkout");
|
|
24957
|
+
if (!repo) return fail("org access role: pass <owner/repo> or run inside a repo checkout");
|
|
24598
24958
|
const cfg = await loadConfig();
|
|
24599
24959
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
|
|
24600
24960
|
if (!verdict.ok) {
|
|
@@ -24603,7 +24963,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
24603
24963
|
process.exitCode = 1;
|
|
24604
24964
|
return;
|
|
24605
24965
|
}
|
|
24606
|
-
return failGraceful(`access role: ${verdict.error}`);
|
|
24966
|
+
return failGraceful(`org access role: ${verdict.error}`);
|
|
24607
24967
|
}
|
|
24608
24968
|
const a = verdict.authority;
|
|
24609
24969
|
if (o.json) {
|
|
@@ -24621,7 +24981,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24621
24981
|
const cfg = await loadConfig();
|
|
24622
24982
|
const registryProjects = await fetchProjectsList(registryClientDeps(cfg));
|
|
24623
24983
|
if (o.repo) {
|
|
24624
|
-
if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
|
|
24984
|
+
if (o.class !== "deployable" && o.class !== "content") return failGraceful("org access audit: --class must be deployable or content");
|
|
24625
24985
|
const meta = registryProjects?.find((project2) => (project2.repos ?? []).some((repo) => repo.toLowerCase() === o.repo.toLowerCase())) ?? null;
|
|
24626
24986
|
const repoClass = o.class;
|
|
24627
24987
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
@@ -24647,7 +25007,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24647
25007
|
});
|
|
24648
25008
|
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)));
|
|
24649
25009
|
var isWin2 = process.platform === "win32";
|
|
24650
|
-
program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; skips slow checks and network probes").option("--preflight", "eager version/plugin
|
|
25010
|
+
program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; skips slow checks and network probes").option("--preflight", "eager version/plugin-heal with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--ci", "stable CI output contract; no color, exit 0 for clear/advisory-only, 1 for hard gaps, 2 for doctor/tool failure").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").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, PATH, gh auth scopes, and hook wiring (offline-safe; suggests plugin-heal on a hard gap) (#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 2 doctor/tool execution failed before a normal verdict\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
|
|
24651
25011
|
if (opts.guide) {
|
|
24652
25012
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
24653
25013
|
return;
|
|
@@ -24677,11 +25037,11 @@ function directoryBytes(path2) {
|
|
|
24677
25037
|
return 0;
|
|
24678
25038
|
}
|
|
24679
25039
|
for (const entry of entries) {
|
|
24680
|
-
const
|
|
24681
|
-
if (entry.isDirectory()) total += directoryBytes(
|
|
25040
|
+
const child2 = (0, import_node_path23.join)(path2, entry.name);
|
|
25041
|
+
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
24682
25042
|
else {
|
|
24683
25043
|
try {
|
|
24684
|
-
total += (0, import_node_fs27.statSync)(
|
|
25044
|
+
total += (0, import_node_fs27.statSync)(child2).size;
|
|
24685
25045
|
} catch {
|
|
24686
25046
|
}
|
|
24687
25047
|
}
|
|
@@ -24748,7 +25108,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
24748
25108
|
});
|
|
24749
25109
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
24750
25110
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
24751
|
-
console.error("[mmi-hook] session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
|
|
25111
|
+
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.");
|
|
24752
25112
|
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
24753
25113
|
return;
|
|
24754
25114
|
}
|
|
@@ -24816,7 +25176,12 @@ program2.command("lane", { hidden: true }).action(() => {
|
|
|
24816
25176
|
program2.command("fusion", { hidden: true }).action(() => {
|
|
24817
25177
|
fail("fusion orchestration lives in jerv-cli \u2014 try: jerv-cli fusion --help");
|
|
24818
25178
|
});
|
|
24819
|
-
program2
|
|
25179
|
+
consolidateCommandNamespaces(program2);
|
|
25180
|
+
applyCommandTaxonomy(program2);
|
|
25181
|
+
var rewrittenArgv = rewriteLegacyArgv(process.argv);
|
|
25182
|
+
if (rewrittenArgv.warning) process.stderr.write(`${rewrittenArgv.warning}
|
|
25183
|
+
`);
|
|
25184
|
+
program2.parseAsync(rewrittenArgv.argv).then(() => finishCliRun()).catch((e) => failGraceful(e.message));
|
|
24820
25185
|
// Annotate the CommonJS export names for ESM import in node:
|
|
24821
25186
|
0 && (module.exports = {
|
|
24822
25187
|
DEFAULT_PRIORITY,
|