@mutmutco/cli 3.31.0 → 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 +703 -363
- 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
|
}
|
|
@@ -9961,7 +9961,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
9961
9961
|
ok: false,
|
|
9962
9962
|
label: "registry no-ci contradicts PR workflows",
|
|
9963
9963
|
detail: `registry META declares no-ci but pull_request workflows emit [${emitted.join(", ")}]`,
|
|
9964
|
-
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)}`
|
|
9965
9965
|
});
|
|
9966
9966
|
}
|
|
9967
9967
|
}
|
|
@@ -10022,7 +10022,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
10022
10022
|
ok: explicitNoCi,
|
|
10023
10023
|
label: "registry META declares intentional no-ci",
|
|
10024
10024
|
detail: explicitNoCi ? void 0 : "set ci:none and requiredChecks:[] in registry META",
|
|
10025
|
-
remediation: `mmi-cli project set ${repo} --var ci=none --var requiredChecks=[]`
|
|
10025
|
+
remediation: `mmi-cli org project set ${repo} --var ci=none --var requiredChecks=[]`
|
|
10026
10026
|
});
|
|
10027
10027
|
} else if (deployableGated) {
|
|
10028
10028
|
checks.push({
|
|
@@ -10060,7 +10060,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
10060
10060
|
ok: trackExpectsRc === rcPresent,
|
|
10061
10061
|
label: "release track matches branch topology",
|
|
10062
10062
|
detail: trackExpectsRc === rcPresent ? void 0 : `registry resolves '${effectiveTrack}' but the rc branch ${rcPresent ? "exists" : "is absent"} \u2014 set release track '${wantTrack}'`,
|
|
10063
|
-
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}`
|
|
10064
10064
|
});
|
|
10065
10065
|
}
|
|
10066
10066
|
}
|
|
@@ -10185,7 +10185,7 @@ async function seedGateYml(repo, deps, meta, result) {
|
|
|
10185
10185
|
}
|
|
10186
10186
|
const gate = meta?.gate;
|
|
10187
10187
|
if (!gate || typeof gate === "object" && Object.keys(gate).length === 0) {
|
|
10188
|
-
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`);
|
|
10189
10189
|
return;
|
|
10190
10190
|
}
|
|
10191
10191
|
const derivedVars = withDerivedRepoVars({ ...gateConfigToVars(gate), REPO_SLUG: parsed.slug }, parsed, "deployable", releaseTrack);
|
|
@@ -10386,20 +10386,20 @@ var import_node_util6 = require("node:util");
|
|
|
10386
10386
|
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
|
|
10387
10387
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
10388
10388
|
var EARLY_EXIT_GRACE_MS = 2e3;
|
|
10389
|
-
function waitForProcessStability(
|
|
10389
|
+
function waitForProcessStability(child2, graceMs = EARLY_EXIT_GRACE_MS) {
|
|
10390
10390
|
return new Promise((resolve5, reject) => {
|
|
10391
10391
|
let settled = false;
|
|
10392
10392
|
const finish = (fn) => {
|
|
10393
10393
|
if (settled) return;
|
|
10394
10394
|
settled = true;
|
|
10395
10395
|
clearTimeout(timer);
|
|
10396
|
-
|
|
10397
|
-
|
|
10396
|
+
child2.removeAllListeners("error");
|
|
10397
|
+
child2.removeAllListeners("exit");
|
|
10398
10398
|
fn();
|
|
10399
10399
|
};
|
|
10400
10400
|
const timer = setTimeout(() => finish(resolve5), graceMs);
|
|
10401
|
-
|
|
10402
|
-
|
|
10401
|
+
child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
|
|
10402
|
+
child2.on("exit", (code, signal) => {
|
|
10403
10403
|
const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
|
|
10404
10404
|
finish(() => reject(new Error(`stage process exited before health check (${detail})`)));
|
|
10405
10405
|
});
|
|
@@ -10534,9 +10534,9 @@ function parseStagePortFlag(raw) {
|
|
|
10534
10534
|
return port;
|
|
10535
10535
|
}
|
|
10536
10536
|
function pathUnder(childPath, parentPath) {
|
|
10537
|
-
const
|
|
10537
|
+
const child2 = normPath2(childPath);
|
|
10538
10538
|
const parent = normPath2(parentPath);
|
|
10539
|
-
return Boolean(
|
|
10539
|
+
return Boolean(child2 && parent && (child2 === parent || child2.startsWith(`${parent}/`)));
|
|
10540
10540
|
}
|
|
10541
10541
|
function stageStateMatchesRequiredCwd(state, requiredCwd) {
|
|
10542
10542
|
if (!state) return false;
|
|
@@ -10890,7 +10890,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10890
10890
|
let up = sub(config.up.trim());
|
|
10891
10891
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
10892
10892
|
const identity = await resolveStageIdentity(cwd);
|
|
10893
|
-
const
|
|
10893
|
+
const child2 = (0, import_node_child_process7.spawn)(up, {
|
|
10894
10894
|
cwd,
|
|
10895
10895
|
shell: true,
|
|
10896
10896
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
@@ -10903,7 +10903,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10903
10903
|
env: { ...process.env, ...vaultProcessEnv, ...stageProcessEnv(stagePort, extraEnv) }
|
|
10904
10904
|
});
|
|
10905
10905
|
const state = {
|
|
10906
|
-
pid:
|
|
10906
|
+
pid: child2.pid ?? 0,
|
|
10907
10907
|
command: up,
|
|
10908
10908
|
cwd,
|
|
10909
10909
|
statePath,
|
|
@@ -10917,7 +10917,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10917
10917
|
if (globalStatePath && globalStatePath !== statePath) writeState(globalStatePath, state);
|
|
10918
10918
|
try {
|
|
10919
10919
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
10920
|
-
else await waitForProcessStability(
|
|
10920
|
+
else await waitForProcessStability(child2);
|
|
10921
10921
|
} catch (e) {
|
|
10922
10922
|
await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd);
|
|
10923
10923
|
throw e;
|
|
@@ -10932,7 +10932,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
10932
10932
|
message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}`
|
|
10933
10933
|
};
|
|
10934
10934
|
opts.onReady?.(result);
|
|
10935
|
-
|
|
10935
|
+
child2.unref();
|
|
10936
10936
|
return result;
|
|
10937
10937
|
}
|
|
10938
10938
|
async function runStage(config = {}, opts = {}) {
|
|
@@ -11149,7 +11149,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11149
11149
|
if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
|
|
11150
11150
|
} };
|
|
11151
11151
|
const write = deps.write ?? import_promises.writeFile;
|
|
11152
|
-
const
|
|
11152
|
+
const remove2 = deps.remove ?? import_promises.unlink;
|
|
11153
11153
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
11154
11154
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
11155
11155
|
const file = (0, import_node_path13.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
@@ -11160,7 +11160,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
11160
11160
|
args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
|
|
11161
11161
|
cleanup: async () => {
|
|
11162
11162
|
try {
|
|
11163
|
-
await
|
|
11163
|
+
await remove2(file);
|
|
11164
11164
|
} catch {
|
|
11165
11165
|
}
|
|
11166
11166
|
}
|
|
@@ -11375,9 +11375,9 @@ async function resolveIssueNodeId(runGh, ref, fallbackRepo) {
|
|
|
11375
11375
|
}
|
|
11376
11376
|
async function linkSubIssue(runGh, parentRef, childRef, defaultRepo) {
|
|
11377
11377
|
const parent = parseIssueRef(parentRef);
|
|
11378
|
-
const
|
|
11378
|
+
const child2 = parseIssueRef(childRef);
|
|
11379
11379
|
const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
|
|
11380
|
-
const subIssueId = await resolveIssueNodeId(runGh,
|
|
11380
|
+
const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
|
|
11381
11381
|
const stdout = await runGh(buildAddSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
|
|
11382
11382
|
const result = parseAddSubIssueResult(stdout);
|
|
11383
11383
|
if (!result) throw new Error(`addSubIssue returned an unexpected response:
|
|
@@ -11412,9 +11412,9 @@ function parseRemoveSubIssueResult(stdout) {
|
|
|
11412
11412
|
}
|
|
11413
11413
|
async function unlinkSubIssue(runGh, parentRef, childRef, defaultRepo) {
|
|
11414
11414
|
const parent = parseIssueRef(parentRef);
|
|
11415
|
-
const
|
|
11415
|
+
const child2 = parseIssueRef(childRef);
|
|
11416
11416
|
const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
|
|
11417
|
-
const subIssueId = await resolveIssueNodeId(runGh,
|
|
11417
|
+
const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
|
|
11418
11418
|
const stdout = await runGh(buildRemoveSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
|
|
11419
11419
|
const result = parseRemoveSubIssueResult(stdout);
|
|
11420
11420
|
if (!result) throw new Error(`removeSubIssue returned an unexpected response:
|
|
@@ -11485,6 +11485,235 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
11485
11485
|
return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
|
|
11486
11486
|
}
|
|
11487
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
|
+
|
|
11488
11717
|
// src/command-manifest.ts
|
|
11489
11718
|
var EXAMPLES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.examples");
|
|
11490
11719
|
var COMMON_MISTAKES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.commonMistakes");
|
|
@@ -11524,17 +11753,24 @@ function buildOption(opt) {
|
|
|
11524
11753
|
if (opt.description) out.description = opt.description;
|
|
11525
11754
|
if (opt.defaultValue !== void 0) out.default = opt.defaultValue;
|
|
11526
11755
|
if (opt.argChoices && opt.argChoices.length) out.enum = [...opt.argChoices];
|
|
11756
|
+
if (opt.hidden) out.discovery = "all-only";
|
|
11527
11757
|
return out;
|
|
11528
11758
|
}
|
|
11529
11759
|
function buildCommand(cmd, path2) {
|
|
11760
|
+
const metadata = commandMetadata(cmd) ?? {
|
|
11761
|
+
category: "core",
|
|
11762
|
+
discovery: "primary",
|
|
11763
|
+
help_group: "Plan and work"
|
|
11764
|
+
};
|
|
11530
11765
|
const out = {
|
|
11531
11766
|
name: cmd.name(),
|
|
11532
11767
|
path: path2,
|
|
11533
11768
|
arguments: cmd.registeredArguments.map(buildArgument),
|
|
11534
11769
|
options: cmd.options.map(buildOption),
|
|
11535
11770
|
subcommands: cmd.commands.map(
|
|
11536
|
-
(
|
|
11537
|
-
)
|
|
11771
|
+
(child2) => buildCommand(child2, path2 ? `${path2} ${child2.name()}` : child2.name())
|
|
11772
|
+
),
|
|
11773
|
+
...metadata
|
|
11538
11774
|
};
|
|
11539
11775
|
const description = cmd.description();
|
|
11540
11776
|
if (description) out.description = description;
|
|
@@ -11544,39 +11780,140 @@ function buildCommand(cmd, path2) {
|
|
|
11544
11780
|
if (commonMistakes) out.common_mistakes = commonMistakes;
|
|
11545
11781
|
return out;
|
|
11546
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
|
+
}
|
|
11547
11850
|
function collectLeaves(node, acc) {
|
|
11548
11851
|
if (node.subcommands.length === 0) {
|
|
11549
11852
|
if (node.path) acc.push(node);
|
|
11550
11853
|
return;
|
|
11551
11854
|
}
|
|
11552
|
-
for (const
|
|
11855
|
+
for (const child2 of node.subcommands) collectLeaves(child2, acc);
|
|
11553
11856
|
}
|
|
11554
11857
|
function buildCommandManifest(program3) {
|
|
11555
11858
|
const tree = buildCommand(program3, "");
|
|
11859
|
+
if (hasConsolidatedCommandNamespaces(program3)) addVirtualShims(tree);
|
|
11556
11860
|
const index = [];
|
|
11557
11861
|
collectLeaves(tree, index);
|
|
11558
|
-
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
|
+
};
|
|
11559
11875
|
const version = program3.version();
|
|
11560
11876
|
if (version) manifest.version = version;
|
|
11561
11877
|
return manifest;
|
|
11562
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
|
+
}
|
|
11563
11890
|
function argSignature(arg) {
|
|
11564
11891
|
const inner = arg.variadic ? `${arg.name}...` : arg.name;
|
|
11565
11892
|
return arg.required ? `<${inner}>` : `[${inner}]`;
|
|
11566
11893
|
}
|
|
11567
|
-
function formatManifestHuman(manifest) {
|
|
11568
|
-
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 ?? ""}`];
|
|
11569
11897
|
const render = (node, depth) => {
|
|
11570
11898
|
const indent = " ".repeat(depth);
|
|
11571
11899
|
const args = node.arguments.map(argSignature).join(" ");
|
|
11572
|
-
const head =
|
|
11900
|
+
const head = node.name + (args ? ` ${args}` : "");
|
|
11573
11901
|
lines.push(node.description ? `${indent}${head} \u2014 ${node.description}` : `${indent}${head}`);
|
|
11574
|
-
for (const opt of node.options) {
|
|
11902
|
+
for (const opt of options.all ? node.options : []) {
|
|
11575
11903
|
lines.push(`${indent} ${opt.flags}${opt.description ? ` ${opt.description}` : ""}`);
|
|
11576
11904
|
}
|
|
11577
|
-
for (const
|
|
11905
|
+
if (options.all) for (const child2 of node.subcommands) render(child2, depth + 1);
|
|
11578
11906
|
};
|
|
11579
|
-
|
|
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.");
|
|
11580
11917
|
return lines.join("\n");
|
|
11581
11918
|
}
|
|
11582
11919
|
|
|
@@ -11722,12 +12059,12 @@ function newestMtimeMs(path2, listDir, mtimeOf) {
|
|
|
11722
12059
|
return newest;
|
|
11723
12060
|
}
|
|
11724
12061
|
for (const e of entries) {
|
|
11725
|
-
const
|
|
12062
|
+
const child2 = `${path2}/${e.name}`;
|
|
11726
12063
|
let m = 0;
|
|
11727
|
-
if (e.isDirectory) m = newestMtimeMs(
|
|
12064
|
+
if (e.isDirectory) m = newestMtimeMs(child2, listDir, mtimeOf);
|
|
11728
12065
|
else {
|
|
11729
12066
|
try {
|
|
11730
|
-
m = mtimeOf(
|
|
12067
|
+
m = mtimeOf(child2);
|
|
11731
12068
|
} catch {
|
|
11732
12069
|
m = 0;
|
|
11733
12070
|
}
|
|
@@ -11803,12 +12140,12 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
|
|
|
11803
12140
|
const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
|
|
11804
12141
|
return { cacheRoot, present: true, keep, prune, bytes, running, ...stagingFields };
|
|
11805
12142
|
}
|
|
11806
|
-
function applyPluginCachePlan(plan,
|
|
12143
|
+
function applyPluginCachePlan(plan, remove2, stagingGuard) {
|
|
11807
12144
|
const removed = [];
|
|
11808
12145
|
const failed = [];
|
|
11809
12146
|
for (const version of plan.prune) {
|
|
11810
12147
|
try {
|
|
11811
|
-
|
|
12148
|
+
remove2(`${plan.cacheRoot}/${version}`);
|
|
11812
12149
|
removed.push(version);
|
|
11813
12150
|
} catch (e) {
|
|
11814
12151
|
failed.push({ version, error: e.message });
|
|
@@ -11836,7 +12173,7 @@ function applyPluginCachePlan(plan, remove, stagingGuard) {
|
|
|
11836
12173
|
}
|
|
11837
12174
|
}
|
|
11838
12175
|
try {
|
|
11839
|
-
|
|
12176
|
+
remove2(`${plan.stagingRoot}/${s.name}`);
|
|
11840
12177
|
removedStaging.push(s.name);
|
|
11841
12178
|
} catch (e) {
|
|
11842
12179
|
failedStaging.push({ name: s.name, error: e.message });
|
|
@@ -11856,7 +12193,7 @@ var CONCURRENT_SESSION_WARNING = "only the version THIS session runs from is pro
|
|
|
11856
12193
|
function renderPluginCachePlan(plan, applied) {
|
|
11857
12194
|
const mb = (b) => `${(b / 1e6).toFixed(1)} MB`;
|
|
11858
12195
|
if (!plan.present && plan.staging.length === 0) {
|
|
11859
|
-
return `plugin
|
|
12196
|
+
return `plugin prune: no plugin cache at ${plan.cacheRoot} \u2014 nothing to prune`;
|
|
11860
12197
|
}
|
|
11861
12198
|
const lines = [];
|
|
11862
12199
|
if (plan.present) {
|
|
@@ -12005,11 +12342,11 @@ function trainPlan(command, options = {}) {
|
|
|
12005
12342
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12006
12343
|
{ label: "verify current branch is development", gated: true },
|
|
12007
12344
|
rcandVersionStep(options),
|
|
12008
|
-
{ 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 },
|
|
12009
12346
|
{ label: "preflight required rc secret names", command: "mmi-cli secrets preflight --stage rc --repo <owner/repo>", gated: true },
|
|
12010
12347
|
{ label: "merge development to rc", gated: true },
|
|
12011
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 },
|
|
12012
|
-
{ 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 }
|
|
12013
12350
|
];
|
|
12014
12351
|
}
|
|
12015
12352
|
if (command === "release") {
|
|
@@ -12017,7 +12354,7 @@ function trainPlan(command, options = {}) {
|
|
|
12017
12354
|
return [
|
|
12018
12355
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12019
12356
|
{ label: "verify current branch is development", gated: true },
|
|
12020
|
-
{ 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 },
|
|
12021
12358
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12022
12359
|
{ label: "merge development to main", gated: true },
|
|
12023
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 },
|
|
@@ -12031,20 +12368,20 @@ function trainPlan(command, options = {}) {
|
|
|
12031
12368
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12032
12369
|
{ label: "verify current branch is development", gated: true },
|
|
12033
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 },
|
|
12034
|
-
{ 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 },
|
|
12035
12372
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12036
12373
|
{ label: "merge development to main (rc skipped)", gated: true },
|
|
12037
12374
|
{ label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
12038
12375
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
12039
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 },
|
|
12040
|
-
{ 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 },
|
|
12041
12378
|
{ label: "roll development forward and align rc to the released main", gated: true }
|
|
12042
12379
|
];
|
|
12043
12380
|
}
|
|
12044
12381
|
return [
|
|
12045
12382
|
{ label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
|
|
12046
12383
|
{ label: "verify current branch is rc", gated: true },
|
|
12047
|
-
{ 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 },
|
|
12048
12385
|
{ label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
12049
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 },
|
|
12050
12387
|
{ label: "merge rc to main", gated: true },
|
|
@@ -12415,7 +12752,7 @@ function requireProjectMetaForTrain(load, repo) {
|
|
|
12415
12752
|
}
|
|
12416
12753
|
if (load.status === "missing") {
|
|
12417
12754
|
throw new Error(
|
|
12418
|
-
`${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.`
|
|
12419
12756
|
);
|
|
12420
12757
|
}
|
|
12421
12758
|
return load.meta;
|
|
@@ -12729,7 +13066,7 @@ function partialTrainRecoveryError(cause, input) {
|
|
|
12729
13066
|
partial train state: tag ${input.tag} is already pushed; origin/${branch} has not been pushed; ${releaseState}; deploy not dispatched.
|
|
12730
13067
|
Recovery sequence:
|
|
12731
13068
|
1. git push origin ${branch}` + releaseCommand + `
|
|
12732
|
-
${deployStep}. mmi-cli tenant redeploy ${input.repo} ${input.stage} --watch
|
|
13069
|
+
${deployStep}. mmi-cli runtime tenant redeploy ${input.repo} ${input.stage} --watch
|
|
12733
13070
|
Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
|
|
12734
13071
|
);
|
|
12735
13072
|
}
|
|
@@ -12911,7 +13248,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
12911
13248
|
if (dispatchFailure === "throw") throw e;
|
|
12912
13249
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12913
13250
|
return {
|
|
12914
|
-
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.`,
|
|
12915
13252
|
deployStatus: "failure"
|
|
12916
13253
|
};
|
|
12917
13254
|
}
|
|
@@ -13498,14 +13835,14 @@ async function attemptRetire(deps, ctx) {
|
|
|
13498
13835
|
return {
|
|
13499
13836
|
status: "retired",
|
|
13500
13837
|
category: "retired",
|
|
13501
|
-
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`
|
|
13502
13839
|
};
|
|
13503
13840
|
}
|
|
13504
13841
|
if (r.category === "wait-timeout") {
|
|
13505
13842
|
return {
|
|
13506
13843
|
status: "failed",
|
|
13507
13844
|
category: "wait-timeout",
|
|
13508
|
-
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})` : ""}`
|
|
13509
13846
|
};
|
|
13510
13847
|
}
|
|
13511
13848
|
return { status: "failed", category: r.category ?? "transport-failed", note: r.note };
|
|
@@ -13520,7 +13857,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
13520
13857
|
if (deployStatus !== "success") {
|
|
13521
13858
|
return {
|
|
13522
13859
|
status: "skipped",
|
|
13523
|
-
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`
|
|
13524
13861
|
};
|
|
13525
13862
|
}
|
|
13526
13863
|
try {
|
|
@@ -13542,7 +13879,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
13542
13879
|
}
|
|
13543
13880
|
const f = last;
|
|
13544
13881
|
if (f.category === "wait-timeout") return f;
|
|
13545
|
-
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)`;
|
|
13546
13883
|
return { status: "failed", category: f.category, note };
|
|
13547
13884
|
} catch (e) {
|
|
13548
13885
|
const err = e;
|
|
@@ -13585,7 +13922,7 @@ async function runTenantControl(deps, options) {
|
|
|
13585
13922
|
dispatched: false,
|
|
13586
13923
|
conclusion: "failure",
|
|
13587
13924
|
category: action === "retire" ? transport ? "transport-failed" : "dispatch-rejected" : void 0,
|
|
13588
|
-
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"}`
|
|
13589
13926
|
};
|
|
13590
13927
|
}
|
|
13591
13928
|
const { runId, runUrl } = await correlateControlRun(deps, since, [stage, action]);
|
|
@@ -13607,7 +13944,7 @@ async function runTenantControl(deps, options) {
|
|
|
13607
13944
|
return result;
|
|
13608
13945
|
}
|
|
13609
13946
|
function renderTenantControl(r) {
|
|
13610
|
-
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})` : ""}`;
|
|
13611
13948
|
const lines = [head];
|
|
13612
13949
|
if (r.runUrl) lines.push(` run: ${r.runUrl}`);
|
|
13613
13950
|
if (r.serviceState) lines.push(` serviceState: ${r.serviceState}`);
|
|
@@ -13768,7 +14105,7 @@ function pickRepo(p) {
|
|
|
13768
14105
|
}
|
|
13769
14106
|
async function sweepRcOrphans(deps, opts) {
|
|
13770
14107
|
const projects = await deps.listProjects();
|
|
13771
|
-
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");
|
|
13772
14109
|
const targets = projects.filter(isRcBearingTenant);
|
|
13773
14110
|
const stages = [];
|
|
13774
14111
|
for (const p of targets) {
|
|
@@ -13805,7 +14142,7 @@ async function sweepRcOrphans(deps, opts) {
|
|
|
13805
14142
|
};
|
|
13806
14143
|
}
|
|
13807
14144
|
function renderSweep(r) {
|
|
13808
|
-
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`];
|
|
13809
14146
|
for (const s of r.stages) {
|
|
13810
14147
|
let line = ` ${s.repo} rc: ${s.orphanCandidate ? "RUNNING" : s.serviceState}`;
|
|
13811
14148
|
if (s.retired) line += ` -> ${s.retired}${s.category ? ` (${s.category}${s.reason ? `/${s.reason}` : ""})` : ""}`;
|
|
@@ -13813,7 +14150,7 @@ function renderSweep(r) {
|
|
|
13813
14150
|
lines.push(line);
|
|
13814
14151
|
}
|
|
13815
14152
|
if (r.running > 0 && !r.retireAttempted) {
|
|
13816
|
-
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)");
|
|
13817
14154
|
}
|
|
13818
14155
|
const undetermined = r.stages.filter((s) => s.serviceState === "unknown").length;
|
|
13819
14156
|
if (undetermined > 0) {
|
|
@@ -14634,8 +14971,8 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
14634
14971
|
repo: OWNER2,
|
|
14635
14972
|
kind: "empty-target-inventory",
|
|
14636
14973
|
severity: "high",
|
|
14637
|
-
detail: "access audit received an empty repo inventory \u2014 zero repos to audit; a green audit of nothing is a false pass",
|
|
14638
|
-
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>\``
|
|
14639
14976
|
}],
|
|
14640
14977
|
repos: []
|
|
14641
14978
|
};
|
|
@@ -14650,7 +14987,7 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
14650
14987
|
kind: "owner-baseline-unavailable",
|
|
14651
14988
|
severity: "medium",
|
|
14652
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`,
|
|
14653
|
-
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\``
|
|
14654
14991
|
});
|
|
14655
14992
|
return { ok: degraded.every((f) => f.severity !== "high"), owners: [], orgFindings: degraded, repos: [] };
|
|
14656
14993
|
}
|
|
@@ -14695,7 +15032,7 @@ function resolveOrgAuditTargets(registryProjects) {
|
|
|
14695
15032
|
kind: "registry-unreadable",
|
|
14696
15033
|
severity: "high",
|
|
14697
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",
|
|
14698
|
-
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>\``
|
|
14699
15036
|
}
|
|
14700
15037
|
};
|
|
14701
15038
|
}
|
|
@@ -14740,7 +15077,7 @@ function dataAccessContractsFromProjects(projects) {
|
|
|
14740
15077
|
return { consumers };
|
|
14741
15078
|
}
|
|
14742
15079
|
function renderAccessReport(report) {
|
|
14743
|
-
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"})`];
|
|
14744
15081
|
for (const finding of report.orgFindings ?? []) {
|
|
14745
15082
|
lines.push(` [${finding.severity}] ${finding.kind}: ${finding.detail}`);
|
|
14746
15083
|
if (finding.remediation) lines.push(` ${finding.remediation}`);
|
|
@@ -14880,20 +15217,20 @@ function docsAuditRecord(input) {
|
|
|
14880
15217
|
const date = input.date.trim();
|
|
14881
15218
|
const shaRange = input.shaRange.trim();
|
|
14882
15219
|
const checkerVendor = input.checkerVendor.trim();
|
|
14883
|
-
if (!repo) throw new Error("docs
|
|
14884
|
-
if (!date) throw new Error("docs
|
|
14885
|
-
if (!isValidIsoDate(date)) throw new Error(`docs
|
|
14886
|
-
if (!shaRange) throw new Error("docs
|
|
14887
|
-
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");
|
|
14888
15225
|
if (input.outcome.kind === "refreshed" && !(Number.isInteger(input.outcome.count) && input.outcome.count > 0)) {
|
|
14889
|
-
throw new Error("docs
|
|
15226
|
+
throw new Error("docs audit record: a `refreshed` outcome must carry a positive integer count");
|
|
14890
15227
|
}
|
|
14891
15228
|
if (input.outcome.kind === "failed" && !input.outcome.reason.trim()) {
|
|
14892
|
-
throw new Error("docs
|
|
15229
|
+
throw new Error("docs audit record: a `failed` outcome must carry a reason");
|
|
14893
15230
|
}
|
|
14894
15231
|
const outcome = serializeOutcome(input.outcome);
|
|
14895
15232
|
if (!isValidOutcomeWire(outcome)) {
|
|
14896
|
-
throw new Error(`docs
|
|
15233
|
+
throw new Error(`docs audit record: outcome serialized to an invalid wire form "${outcome}"`);
|
|
14897
15234
|
}
|
|
14898
15235
|
return { repo, date, shaRange, outcome, checkerVendor };
|
|
14899
15236
|
}
|
|
@@ -14904,13 +15241,13 @@ function ageInDays(verdictDate, today) {
|
|
|
14904
15241
|
}
|
|
14905
15242
|
function docsAuditStatus(fetch2, opts) {
|
|
14906
15243
|
if ("notArmed" in fetch2) {
|
|
14907
|
-
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)` };
|
|
14908
15245
|
}
|
|
14909
15246
|
if (!fetch2.ok) {
|
|
14910
|
-
return { ok: false, state: "error", line: `docs
|
|
15247
|
+
return { ok: false, state: "error", line: `docs audit: ${opts.repo} registry read failed \u2014 ${fetch2.error}` };
|
|
14911
15248
|
}
|
|
14912
15249
|
if (fetch2.verdict === null) {
|
|
14913
|
-
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` };
|
|
14914
15251
|
}
|
|
14915
15252
|
const verdict = fetch2.verdict;
|
|
14916
15253
|
for (const field of ["repo", "shaRange", "checkerVendor"]) {
|
|
@@ -14920,7 +15257,7 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
14920
15257
|
return {
|
|
14921
15258
|
ok: false,
|
|
14922
15259
|
state: "malformed",
|
|
14923
|
-
line: `docs
|
|
15260
|
+
line: `docs audit: ${opts.repo} malformed verdict ${field} (${shown}, expected a non-empty string) \u2014 registry record corrupt, treating as RED`
|
|
14924
15261
|
};
|
|
14925
15262
|
}
|
|
14926
15263
|
}
|
|
@@ -14928,29 +15265,29 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
14928
15265
|
return {
|
|
14929
15266
|
ok: false,
|
|
14930
15267
|
state: "malformed",
|
|
14931
|
-
line: `docs
|
|
15268
|
+
line: `docs audit: ${opts.repo} malformed verdict date "${verdict.date}" \u2014 registry record corrupt, treating as RED`
|
|
14932
15269
|
};
|
|
14933
15270
|
}
|
|
14934
15271
|
if (!isValidOutcomeWire(verdict.outcome)) {
|
|
14935
15272
|
return {
|
|
14936
15273
|
ok: false,
|
|
14937
15274
|
state: "malformed",
|
|
14938
|
-
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`
|
|
14939
15276
|
};
|
|
14940
15277
|
}
|
|
14941
15278
|
if (!isValidIsoDate(opts.today)) {
|
|
14942
|
-
throw new Error(`docs
|
|
15279
|
+
throw new Error(`docs audit status: today must be a real ISO date (YYYY-MM-DD), got "${opts.today}"`);
|
|
14943
15280
|
}
|
|
14944
15281
|
const cadence = opts.cadenceDays ?? 7;
|
|
14945
15282
|
const grace = opts.graceDays ?? 3;
|
|
14946
15283
|
const age = ageInDays(verdict.date, opts.today);
|
|
14947
15284
|
if (age > cadence + grace) {
|
|
14948
|
-
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` };
|
|
14949
15286
|
}
|
|
14950
15287
|
if (verdict.outcome === "failed") {
|
|
14951
|
-
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` };
|
|
14952
15289
|
}
|
|
14953
|
-
return { ok: true, state: "clean", line: `docs
|
|
15290
|
+
return { ok: true, state: "clean", line: `docs audit: ${opts.repo} ${verdict.outcome} (${verdict.date}, ${verdict.checkerVendor})` };
|
|
14954
15291
|
}
|
|
14955
15292
|
|
|
14956
15293
|
// src/project-readiness.ts
|
|
@@ -15064,7 +15401,7 @@ function appAttestationOf(meta) {
|
|
|
15064
15401
|
return typeof at === "string" && at.length > 0 && typeof by === "string" && by.length > 0 ? { at, by } : null;
|
|
15065
15402
|
}
|
|
15066
15403
|
function attestedLine(att) {
|
|
15067
|
-
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.`;
|
|
15068
15405
|
}
|
|
15069
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: [] }).";
|
|
15070
15407
|
function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
|
|
@@ -15075,19 +15412,19 @@ function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
|
|
|
15075
15412
|
if (projectType === "content" || model === "content") return ["Content repo: keep app-owned work to docs/content changes; release train does not apply."];
|
|
15076
15413
|
if (projectType === "desktop-app") {
|
|
15077
15414
|
return [
|
|
15078
|
-
"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.",
|
|
15079
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."
|
|
15080
15417
|
];
|
|
15081
15418
|
}
|
|
15082
15419
|
if (projectType === "desktop-game") {
|
|
15083
15420
|
return [
|
|
15084
|
-
"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.",
|
|
15085
15422
|
"Keep README.md and architecture.md explicit about the game build/release path so readiness does not guess web infra."
|
|
15086
15423
|
];
|
|
15087
15424
|
}
|
|
15088
15425
|
if (projectType === "non-deployable" || model === "none") {
|
|
15089
15426
|
return [
|
|
15090
|
-
"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.",
|
|
15091
15428
|
"Keep README.md and architecture.md explicit about the repo purpose and any project-admin workflow it still needs."
|
|
15092
15429
|
];
|
|
15093
15430
|
}
|
|
@@ -15212,7 +15549,7 @@ function buildV2HealPatch(repoOrSlug, meta, mainDeployFact) {
|
|
|
15212
15549
|
patch.requiredRuntimeSecrets = next;
|
|
15213
15550
|
}
|
|
15214
15551
|
}
|
|
15215
|
-
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).`];
|
|
15216
15553
|
if (boardRegistryGaps(meta).length) appOwnedGaps.unshift(boardRegistryGapMessage(repo));
|
|
15217
15554
|
return { slug, patch, appOwnedGaps };
|
|
15218
15555
|
}
|
|
@@ -15251,7 +15588,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
|
|
|
15251
15588
|
registryError: read.error,
|
|
15252
15589
|
hubOwned: { meta: { ok: false, missing: [] }, deployCoords: degradedStage, deployState: degradedStage, secrets: degradedSecrets },
|
|
15253
15590
|
autoHealAvailable: [],
|
|
15254
|
-
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.`]
|
|
15255
15592
|
};
|
|
15256
15593
|
}
|
|
15257
15594
|
const meta = read.project;
|
|
@@ -15478,37 +15815,37 @@ function parseSecretsCatalogVar(raw) {
|
|
|
15478
15815
|
try {
|
|
15479
15816
|
parsed = JSON.parse(raw);
|
|
15480
15817
|
} catch {
|
|
15481
|
-
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"]}}');
|
|
15482
15819
|
}
|
|
15483
15820
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15484
|
-
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");
|
|
15485
15822
|
}
|
|
15486
15823
|
const nonEmpty = (v) => typeof v === "string" && v.trim().length > 0;
|
|
15487
15824
|
const overrideMapKey = /^([A-Z_][A-Z0-9_]*)@(dev|rc|main)$/;
|
|
15488
15825
|
for (const [mapKey, value] of Object.entries(parsed)) {
|
|
15489
|
-
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`);
|
|
15490
15827
|
const e = value;
|
|
15491
15828
|
if (typeof e.key !== "string" || !SECRET_ENV_NAME_RE.test(e.key)) {
|
|
15492
|
-
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)`);
|
|
15493
15830
|
}
|
|
15494
15831
|
const override = overrideMapKey.exec(mapKey);
|
|
15495
15832
|
if (override) {
|
|
15496
15833
|
if (e.key !== override[1] || !Array.isArray(e.stages) || e.stages.length !== 1 || e.stages[0] !== override[2]) {
|
|
15497
|
-
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]}"]`);
|
|
15498
15835
|
}
|
|
15499
15836
|
} else if (e.key !== mapKey) {
|
|
15500
|
-
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)`);
|
|
15501
15838
|
}
|
|
15502
|
-
if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
|
|
15503
|
-
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`);
|
|
15504
15841
|
if (!Array.isArray(e.stages) || e.stages.some((s) => !RUNTIME_SECRET_STAGES.includes(s))) {
|
|
15505
|
-
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)`);
|
|
15506
15843
|
}
|
|
15507
15844
|
if (!Array.isArray(e.consumers) || e.consumers.some((c) => !SECRET_CONSUMERS.includes(c))) {
|
|
15508
|
-
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("/")}`);
|
|
15509
15846
|
}
|
|
15510
15847
|
if (e.aliases !== void 0 && (!Array.isArray(e.aliases) || e.aliases.some((a) => typeof a !== "string"))) {
|
|
15511
|
-
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`);
|
|
15512
15849
|
}
|
|
15513
15850
|
}
|
|
15514
15851
|
return parsed;
|
|
@@ -15518,19 +15855,19 @@ function parseRuntimeSecretsVar(raw) {
|
|
|
15518
15855
|
try {
|
|
15519
15856
|
parsed = JSON.parse(raw);
|
|
15520
15857
|
} catch {
|
|
15521
|
-
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"]}');
|
|
15522
15859
|
}
|
|
15523
15860
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15524
|
-
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)");
|
|
15525
15862
|
}
|
|
15526
15863
|
const map = parsed;
|
|
15527
15864
|
const out = {};
|
|
15528
15865
|
for (const [stage, names] of Object.entries(map)) {
|
|
15529
15866
|
if (!RUNTIME_SECRET_STAGES.includes(stage)) {
|
|
15530
|
-
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("/")}`);
|
|
15531
15868
|
}
|
|
15532
15869
|
if (!Array.isArray(names) || names.some((n) => typeof n !== "string" || !n.trim())) {
|
|
15533
|
-
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`);
|
|
15534
15871
|
}
|
|
15535
15872
|
out[stage] = names;
|
|
15536
15873
|
}
|
|
@@ -15541,13 +15878,13 @@ function parseBuildSecretsVar(raw) {
|
|
|
15541
15878
|
try {
|
|
15542
15879
|
parsed = JSON.parse(raw);
|
|
15543
15880
|
} catch {
|
|
15544
|
-
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"]');
|
|
15545
15882
|
}
|
|
15546
15883
|
if (!Array.isArray(parsed)) {
|
|
15547
|
-
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)");
|
|
15548
15885
|
}
|
|
15549
15886
|
if (parsed.some((n) => typeof n !== "string" || !BUILD_SECRET_REF_RE.test(n) || n === "@github-packages-token")) {
|
|
15550
|
-
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");
|
|
15551
15888
|
}
|
|
15552
15889
|
return parsed;
|
|
15553
15890
|
}
|
|
@@ -15556,19 +15893,19 @@ function parseEdgeDomainsVar(raw) {
|
|
|
15556
15893
|
try {
|
|
15557
15894
|
parsed = JSON.parse(raw);
|
|
15558
15895
|
} catch {
|
|
15559
|
-
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"}');
|
|
15560
15897
|
}
|
|
15561
15898
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15562
|
-
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");
|
|
15563
15900
|
}
|
|
15564
15901
|
const map = parsed;
|
|
15565
15902
|
const out = {};
|
|
15566
15903
|
for (const [stage, domain] of Object.entries(map)) {
|
|
15567
15904
|
if (!RUNTIME_SECRET_STAGES.includes(stage)) {
|
|
15568
|
-
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("/")}`);
|
|
15569
15906
|
}
|
|
15570
15907
|
if (typeof domain !== "string" || !domain.trim()) {
|
|
15571
|
-
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`);
|
|
15572
15909
|
}
|
|
15573
15910
|
out[stage] = domain.trim();
|
|
15574
15911
|
}
|
|
@@ -15580,19 +15917,19 @@ function parsePriorityOptionsVar(raw) {
|
|
|
15580
15917
|
try {
|
|
15581
15918
|
parsed = JSON.parse(raw);
|
|
15582
15919
|
} catch {
|
|
15583
|
-
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>"}');
|
|
15584
15921
|
}
|
|
15585
15922
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15586
|
-
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");
|
|
15587
15924
|
}
|
|
15588
15925
|
const map = parsed;
|
|
15589
15926
|
const out = {};
|
|
15590
15927
|
for (const [name, id] of Object.entries(map)) {
|
|
15591
15928
|
if (!PRIORITY_OPTION_NAMES.includes(name)) {
|
|
15592
|
-
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("/")}`);
|
|
15593
15930
|
}
|
|
15594
15931
|
if (typeof id !== "string" || !id.trim()) {
|
|
15595
|
-
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`);
|
|
15596
15933
|
}
|
|
15597
15934
|
out[name] = id.trim();
|
|
15598
15935
|
}
|
|
@@ -15603,7 +15940,7 @@ function parsePortRangeVar(raw) {
|
|
|
15603
15940
|
try {
|
|
15604
15941
|
parsed = JSON.parse(raw);
|
|
15605
15942
|
} catch {
|
|
15606
|
-
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]');
|
|
15607
15944
|
}
|
|
15608
15945
|
let start;
|
|
15609
15946
|
let end;
|
|
@@ -15612,12 +15949,12 @@ function parsePortRangeVar(raw) {
|
|
|
15612
15949
|
} else if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
15613
15950
|
({ start, end } = parsed);
|
|
15614
15951
|
} else {
|
|
15615
|
-
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");
|
|
15616
15953
|
}
|
|
15617
15954
|
if (typeof start !== "number" || typeof end !== "number" || !Number.isFinite(start) || !Number.isFinite(end)) {
|
|
15618
|
-
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");
|
|
15619
15956
|
}
|
|
15620
|
-
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");
|
|
15621
15958
|
return { start, end };
|
|
15622
15959
|
}
|
|
15623
15960
|
function parseStatusOptionsVar(raw) {
|
|
@@ -15625,16 +15962,16 @@ function parseStatusOptionsVar(raw) {
|
|
|
15625
15962
|
try {
|
|
15626
15963
|
parsed = JSON.parse(raw);
|
|
15627
15964
|
} catch {
|
|
15628
|
-
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>"}');
|
|
15629
15966
|
}
|
|
15630
15967
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15631
|
-
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");
|
|
15632
15969
|
}
|
|
15633
15970
|
const map = parsed;
|
|
15634
15971
|
const out = {};
|
|
15635
15972
|
for (const [name, id] of Object.entries(map)) {
|
|
15636
15973
|
if (typeof id !== "string" || !id.trim()) {
|
|
15637
|
-
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`);
|
|
15638
15975
|
}
|
|
15639
15976
|
out[name] = id.trim();
|
|
15640
15977
|
}
|
|
@@ -15645,31 +15982,31 @@ function parseOauthVar(raw) {
|
|
|
15645
15982
|
try {
|
|
15646
15983
|
parsed = JSON.parse(raw);
|
|
15647
15984
|
} catch {
|
|
15648
|
-
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}"}`);
|
|
15649
15986
|
}
|
|
15650
15987
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15651
|
-
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");
|
|
15652
15989
|
}
|
|
15653
15990
|
const map = parsed;
|
|
15654
15991
|
const out = {};
|
|
15655
15992
|
for (const [key, value] of Object.entries(map)) {
|
|
15656
15993
|
if (key === "subdomains" || key === "domains") {
|
|
15657
15994
|
if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || !v.trim())) {
|
|
15658
|
-
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`);
|
|
15659
15996
|
}
|
|
15660
15997
|
out[key] = value.map((v) => v.trim());
|
|
15661
15998
|
} else if (key === "callbackPath") {
|
|
15662
|
-
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");
|
|
15663
16000
|
const callbackPath = value.trim();
|
|
15664
16001
|
if (callbackPath !== DEFAULT_CALLBACK_PATH) {
|
|
15665
|
-
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)})`);
|
|
15666
16003
|
}
|
|
15667
16004
|
out.callbackPath = callbackPath;
|
|
15668
16005
|
} else if (key === "fofuSubdomain") {
|
|
15669
|
-
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)');
|
|
15670
16007
|
out.fofuSubdomain = value.trim();
|
|
15671
16008
|
} else {
|
|
15672
|
-
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`);
|
|
15673
16010
|
}
|
|
15674
16011
|
}
|
|
15675
16012
|
return out;
|
|
@@ -15679,49 +16016,49 @@ function parseReposVar(raw) {
|
|
|
15679
16016
|
try {
|
|
15680
16017
|
parsed = JSON.parse(raw);
|
|
15681
16018
|
} catch {
|
|
15682
|
-
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"]');
|
|
15683
16020
|
}
|
|
15684
16021
|
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((r) => typeof r !== "string" || !r.trim())) {
|
|
15685
|
-
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");
|
|
15686
16023
|
}
|
|
15687
16024
|
return parsed.map((r) => r.trim());
|
|
15688
16025
|
}
|
|
15689
16026
|
function parsePublishRequiredVar(raw) {
|
|
15690
16027
|
if (raw === "true") return true;
|
|
15691
16028
|
if (raw === "false") return false;
|
|
15692
|
-
throw new Error("project set: publishRequired must be true or false");
|
|
16029
|
+
throw new Error("org project set: publishRequired must be true or false");
|
|
15693
16030
|
}
|
|
15694
16031
|
function parseFofuEnabledVar(raw) {
|
|
15695
16032
|
if (raw === "true") return true;
|
|
15696
16033
|
if (raw === "false") return false;
|
|
15697
|
-
throw new Error("project set: fofuEnabled must be true or false");
|
|
16034
|
+
throw new Error("org project set: fofuEnabled must be true or false");
|
|
15698
16035
|
}
|
|
15699
16036
|
function parseRuntimeVaultOnlyVar(raw) {
|
|
15700
16037
|
if (raw === "true") return true;
|
|
15701
16038
|
if (raw === "false") return false;
|
|
15702
|
-
throw new Error("project set: runtimeVaultOnly must be true or false");
|
|
16039
|
+
throw new Error("org project set: runtimeVaultOnly must be true or false");
|
|
15703
16040
|
}
|
|
15704
16041
|
function parseConsumesDesignSystemVar(raw) {
|
|
15705
16042
|
if (raw === "fofu") return raw;
|
|
15706
|
-
throw new Error('project set: consumesDesignSystem must be "fofu"');
|
|
16043
|
+
throw new Error('org project set: consumesDesignSystem must be "fofu"');
|
|
15707
16044
|
}
|
|
15708
16045
|
function parsePublishDirVar(raw) {
|
|
15709
16046
|
const v = raw.trim();
|
|
15710
16047
|
if (v === "" || v === ".") {
|
|
15711
|
-
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)");
|
|
15712
16049
|
}
|
|
15713
16050
|
if (!/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
|
|
15714
|
-
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');
|
|
15715
16052
|
}
|
|
15716
16053
|
return v;
|
|
15717
16054
|
}
|
|
15718
16055
|
function parseDsManifestPathVar(raw) {
|
|
15719
16056
|
const v = raw.trim();
|
|
15720
16057
|
if (v === "" || !/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
|
|
15721
|
-
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');
|
|
15722
16059
|
}
|
|
15723
16060
|
if (v !== "package.json" && !v.endsWith("/package.json")) {
|
|
15724
|
-
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");
|
|
15725
16062
|
}
|
|
15726
16063
|
return v;
|
|
15727
16064
|
}
|
|
@@ -15730,10 +16067,10 @@ function parseRequiredChecksVar(raw) {
|
|
|
15730
16067
|
try {
|
|
15731
16068
|
parsed = JSON.parse(raw);
|
|
15732
16069
|
} catch {
|
|
15733
|
-
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');
|
|
15734
16071
|
}
|
|
15735
16072
|
if (!Array.isArray(parsed) || parsed.some((c) => typeof c !== "string" || !c.trim())) {
|
|
15736
|
-
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)");
|
|
15737
16074
|
}
|
|
15738
16075
|
return parsed.map((c) => c.trim());
|
|
15739
16076
|
}
|
|
@@ -15742,22 +16079,22 @@ function parseGateVar(raw) {
|
|
|
15742
16079
|
try {
|
|
15743
16080
|
parsed = JSON.parse(raw);
|
|
15744
16081
|
} catch {
|
|
15745
|
-
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"}');
|
|
15746
16083
|
}
|
|
15747
16084
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15748
|
-
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");
|
|
15749
16086
|
}
|
|
15750
16087
|
const map = parsed;
|
|
15751
16088
|
const out = {};
|
|
15752
16089
|
for (const [key, value] of Object.entries(map)) {
|
|
15753
16090
|
if (key === "runtime") {
|
|
15754
|
-
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"');
|
|
15755
16092
|
out.runtime = value;
|
|
15756
16093
|
} else if (key === "cmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
|
|
15757
|
-
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`);
|
|
15758
16095
|
out[key] = value.trim();
|
|
15759
16096
|
} else {
|
|
15760
|
-
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`);
|
|
15761
16098
|
}
|
|
15762
16099
|
}
|
|
15763
16100
|
return out;
|
|
@@ -15835,25 +16172,25 @@ function buildProjectSetPatch(input) {
|
|
|
15835
16172
|
const patch = {};
|
|
15836
16173
|
if (input.class) {
|
|
15837
16174
|
if (input.class !== "deployable" && input.class !== "content") {
|
|
15838
|
-
throw new Error("project set: --class must be deployable or content");
|
|
16175
|
+
throw new Error("org project set: --class must be deployable or content");
|
|
15839
16176
|
}
|
|
15840
16177
|
patch.class = input.class;
|
|
15841
16178
|
}
|
|
15842
16179
|
if (input.projectType) {
|
|
15843
16180
|
if (!isProjectType(input.projectType)) {
|
|
15844
|
-
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(", ")}`);
|
|
15845
16182
|
}
|
|
15846
16183
|
patch.projectType = input.projectType;
|
|
15847
16184
|
}
|
|
15848
16185
|
if (input.deployModel) {
|
|
15849
16186
|
if (!isDeployModel(input.deployModel)) {
|
|
15850
|
-
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(", ")}`);
|
|
15851
16188
|
}
|
|
15852
16189
|
patch.deployModel = input.deployModel;
|
|
15853
16190
|
}
|
|
15854
16191
|
if (input.releaseTrack) {
|
|
15855
16192
|
if (!isReleaseTrack(input.releaseTrack)) {
|
|
15856
|
-
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(", ")}`);
|
|
15857
16194
|
}
|
|
15858
16195
|
patch.releaseTrack = input.releaseTrack;
|
|
15859
16196
|
}
|
|
@@ -15862,16 +16199,16 @@ function buildProjectSetPatch(input) {
|
|
|
15862
16199
|
if (eq <= 0) {
|
|
15863
16200
|
const looksPositional = value.includes("/") || !value.includes("=");
|
|
15864
16201
|
const hint = looksPositional ? " \u2014 if that is the [owner/repo] argument, put it BEFORE --var; a variadic flag swallows the tokens after it" : "";
|
|
15865
|
-
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}`);
|
|
15866
16203
|
}
|
|
15867
16204
|
const key = value.slice(0, eq);
|
|
15868
16205
|
const raw = value.slice(eq + 1);
|
|
15869
16206
|
if (!SETTABLE_VAR_KEY_SET.has(key)) {
|
|
15870
|
-
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(", ")}`);
|
|
15871
16208
|
}
|
|
15872
16209
|
if (key === "projectNumber") {
|
|
15873
16210
|
const n = Number(raw);
|
|
15874
|
-
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");
|
|
15875
16212
|
patch[key] = n;
|
|
15876
16213
|
} else if (key === "requiredRuntimeSecrets") {
|
|
15877
16214
|
patch[key] = parseRuntimeSecretsVar(raw);
|
|
@@ -15904,7 +16241,7 @@ function buildProjectSetPatch(input) {
|
|
|
15904
16241
|
} else if (key === "dsManifestPath") {
|
|
15905
16242
|
patch[key] = parseDsManifestPathVar(raw);
|
|
15906
16243
|
} else if (key === "ci") {
|
|
15907
|
-
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)');
|
|
15908
16245
|
patch[key] = raw;
|
|
15909
16246
|
} else if (key === "requiredChecks") {
|
|
15910
16247
|
patch[key] = parseRequiredChecksVar(raw);
|
|
@@ -15916,7 +16253,7 @@ function buildProjectSetPatch(input) {
|
|
|
15916
16253
|
}
|
|
15917
16254
|
for (const key of input.unsets) {
|
|
15918
16255
|
if (!UNSET_KEY_SET.has(key)) {
|
|
15919
|
-
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(", ")}`);
|
|
15920
16257
|
}
|
|
15921
16258
|
patch[key] = null;
|
|
15922
16259
|
}
|
|
@@ -15925,7 +16262,7 @@ function buildProjectSetPatch(input) {
|
|
|
15925
16262
|
patch.edgeDomains = null;
|
|
15926
16263
|
}
|
|
15927
16264
|
if (Object.keys(patch).length === 0) {
|
|
15928
|
-
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");
|
|
15929
16266
|
}
|
|
15930
16267
|
return patch;
|
|
15931
16268
|
}
|
|
@@ -15937,36 +16274,36 @@ function buildSetDeployPatch(_slug, input) {
|
|
|
15937
16274
|
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
15938
16275
|
const stage = clean4(input.stage);
|
|
15939
16276
|
if (!stage || !DEPLOY_STAGES.includes(stage)) {
|
|
15940
|
-
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(", ")}`);
|
|
15941
16278
|
}
|
|
15942
16279
|
const substrate = clean4(input.substrate);
|
|
15943
16280
|
if (substrate !== void 0 && !DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
15944
|
-
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(", ")}`);
|
|
15945
16282
|
}
|
|
15946
16283
|
const sshHost = clean4(input.sshHost);
|
|
15947
16284
|
const sshUser = clean4(input.sshUser);
|
|
15948
16285
|
const deployPath = clean4(input.deployPath);
|
|
15949
16286
|
const serviceName = clean4(input.service);
|
|
15950
16287
|
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
15951
|
-
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`);
|
|
15952
16289
|
}
|
|
15953
16290
|
let port;
|
|
15954
16291
|
if (input.port !== void 0 && input.port !== null && `${input.port}`.trim() !== "") {
|
|
15955
16292
|
port = Number(input.port);
|
|
15956
|
-
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");
|
|
15957
16294
|
}
|
|
15958
16295
|
const domain = clean4(input.domain);
|
|
15959
|
-
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)}`);
|
|
15960
16297
|
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
|
|
15961
16298
|
for (const a of aliases) {
|
|
15962
|
-
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)}`);
|
|
15963
16300
|
}
|
|
15964
16301
|
const uniqueAliases = [...new Set(aliases)];
|
|
15965
16302
|
if (input.clearAliases && uniqueAliases.length) {
|
|
15966
|
-
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)");
|
|
15967
16304
|
}
|
|
15968
16305
|
if (input.noEnvFile !== void 0 && typeof input.noEnvFile !== "boolean") {
|
|
15969
|
-
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");
|
|
15970
16307
|
}
|
|
15971
16308
|
return {
|
|
15972
16309
|
stage,
|
|
@@ -16056,7 +16393,7 @@ function evaluatePromotionFilelessGuard(input) {
|
|
|
16056
16393
|
return {
|
|
16057
16394
|
ok: false,
|
|
16058
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:
|
|
16059
|
-
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
|
|
16060
16397
|
gh workflow run tenant-reconcile.yml --repo mutmutco/MMI-Hub # re-render the box control script (#1056)
|
|
16061
16398
|
(or pass --skip-compose-guard to override this preflight)`
|
|
16062
16399
|
};
|
|
@@ -16080,11 +16417,11 @@ function vaultCleanupNote(slug) {
|
|
|
16080
16417
|
function buildRetirePlan(input) {
|
|
16081
16418
|
const target = input.target?.trim();
|
|
16082
16419
|
if (!target) {
|
|
16083
|
-
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");
|
|
16084
16421
|
}
|
|
16085
16422
|
const slug = retireSlugOf(target);
|
|
16086
16423
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
16087
|
-
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`);
|
|
16088
16425
|
}
|
|
16089
16426
|
const repo = target.includes("/") ? target : `mutmutco/${slug}`;
|
|
16090
16427
|
return { slug, repo, apply: Boolean(input.apply), skipBoard: Boolean(input.skipBoard) };
|
|
@@ -16414,7 +16751,7 @@ function registerSecretsCommands(program3) {
|
|
|
16414
16751
|
const ok = await secretsRequest(d, key, o);
|
|
16415
16752
|
if (!ok) process.exitCode = 1;
|
|
16416
16753
|
}));
|
|
16417
|
-
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) => {
|
|
16418
16755
|
const owner = o.owner ?? await githubLogin();
|
|
16419
16756
|
if (!owner) {
|
|
16420
16757
|
fail("secrets declare: could not resolve your GitHub login for --owner; pass --owner explicitly");
|
|
@@ -16441,7 +16778,6 @@ function registerSecretsCommands(program3) {
|
|
|
16441
16778
|
if (!ok) process.exitCode = 1;
|
|
16442
16779
|
});
|
|
16443
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);
|
|
16444
|
-
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);
|
|
16445
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) => {
|
|
16446
16782
|
const stages = ["dev", "rc", "main"];
|
|
16447
16783
|
if (!stages.includes(o.from) || !stages.includes(o.to)) {
|
|
@@ -16788,14 +17124,14 @@ async function fetchInventory() {
|
|
|
16788
17124
|
}
|
|
16789
17125
|
});
|
|
16790
17126
|
if (incomplete.length === PROJECTS.length) {
|
|
16791
|
-
throw new Error(`box: could not read any Hetzner project \u2014
|
|
17127
|
+
throw new Error(`runtime box: could not read any Hetzner project \u2014
|
|
16792
17128
|
${incomplete.join("\n ")}`);
|
|
16793
17129
|
}
|
|
16794
17130
|
return { boxes: boxes.sort((a, b) => a.name.localeCompare(b.name)), incomplete };
|
|
16795
17131
|
}
|
|
16796
17132
|
function warnIncomplete(incomplete) {
|
|
16797
|
-
for (const f of incomplete) console.error(`box: WARNING \u2014 ${f}`);
|
|
16798
|
-
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.");
|
|
16799
17135
|
}
|
|
16800
17136
|
function registerBoxCommands(program3) {
|
|
16801
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)");
|
|
@@ -16820,12 +17156,12 @@ function registerBoxCommands(program3) {
|
|
|
16820
17156
|
if (result.kind === "unknown") {
|
|
16821
17157
|
warnIncomplete(result.reasons);
|
|
16822
17158
|
await failGraceful(
|
|
16823
|
-
`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.`
|
|
16824
17160
|
);
|
|
16825
17161
|
return;
|
|
16826
17162
|
}
|
|
16827
17163
|
if (result.kind === "absent") {
|
|
16828
|
-
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)"}`);
|
|
16829
17165
|
return;
|
|
16830
17166
|
}
|
|
16831
17167
|
const found = result.box;
|
|
@@ -16940,7 +17276,7 @@ function awsScheduleNames(payload) {
|
|
|
16940
17276
|
return schedules.map((s) => s && typeof s === "object" ? s.Name : void 0).filter((n) => typeof n === "string" && !isJervResource(n));
|
|
16941
17277
|
}
|
|
16942
17278
|
var DECLARED_ENTRIES = [
|
|
16943
|
-
{ 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)" },
|
|
16944
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)" },
|
|
16945
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)" }
|
|
16946
17282
|
];
|
|
@@ -16972,7 +17308,7 @@ function renderDocSection(entries, generatedAtIso) {
|
|
|
16972
17308
|
}
|
|
16973
17309
|
return [
|
|
16974
17310
|
DOC_START_MARKER,
|
|
16975
|
-
`_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._`,
|
|
16976
17312
|
"",
|
|
16977
17313
|
lines.join("\n"),
|
|
16978
17314
|
DOC_END_MARKER
|
|
@@ -16982,7 +17318,7 @@ function spliceDoc(docText, generatedSection) {
|
|
|
16982
17318
|
const start = docText.indexOf(DOC_START_MARKER);
|
|
16983
17319
|
const end = docText.indexOf(DOC_END_MARKER);
|
|
16984
17320
|
if (start === -1 || end === -1 || end < start) {
|
|
16985
|
-
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.`);
|
|
16986
17322
|
}
|
|
16987
17323
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
16988
17324
|
}
|
|
@@ -17190,11 +17526,11 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
17190
17526
|
};
|
|
17191
17527
|
}
|
|
17192
17528
|
function warnIncomplete2(incomplete) {
|
|
17193
|
-
for (const f of incomplete) console.error(`schedules: WARNING \u2014 ${f}`);
|
|
17194
|
-
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.");
|
|
17195
17531
|
}
|
|
17196
17532
|
function reportDrift(drift) {
|
|
17197
|
-
for (const d of drift) console.error(`schedules: DRIFT \u2014 ${d}`);
|
|
17533
|
+
for (const d of drift) console.error(`org schedules: DRIFT \u2014 ${d}`);
|
|
17198
17534
|
}
|
|
17199
17535
|
function registerSchedulesCommands(program3) {
|
|
17200
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) => {
|
|
@@ -17203,14 +17539,14 @@ function registerSchedulesCommands(program3) {
|
|
|
17203
17539
|
if (o.doc) {
|
|
17204
17540
|
if (incomplete.length) {
|
|
17205
17541
|
warnIncomplete2(incomplete);
|
|
17206
|
-
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).");
|
|
17207
17543
|
return;
|
|
17208
17544
|
}
|
|
17209
17545
|
const docText = await (0, import_promises2.readFile)(o.doc, "utf8");
|
|
17210
17546
|
const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
|
|
17211
17547
|
await (0, import_promises2.writeFile)(o.doc, spliced, "utf8");
|
|
17212
17548
|
reportDrift(drift);
|
|
17213
|
-
console.log(`schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
17549
|
+
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
17214
17550
|
return;
|
|
17215
17551
|
}
|
|
17216
17552
|
if (o.json) {
|
|
@@ -17457,15 +17793,15 @@ async function runIssueChildren(deps, epic, opts) {
|
|
|
17457
17793
|
const seen = /* @__PURE__ */ new Set([childKey(repo, ref.number)]);
|
|
17458
17794
|
for (const raw of await queryChildren(deps, owner, name, ref.number)) {
|
|
17459
17795
|
if (out.length >= CHILDREN_MAX_TOTAL) break;
|
|
17460
|
-
const
|
|
17461
|
-
out.push(
|
|
17462
|
-
seen.add(childKey(
|
|
17796
|
+
const child2 = shapeChildNode(raw, 1, boardProjectId);
|
|
17797
|
+
out.push(child2);
|
|
17798
|
+
seen.add(childKey(child2.repo, child2.number));
|
|
17463
17799
|
}
|
|
17464
17800
|
if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
|
|
17465
17801
|
const queue = [];
|
|
17466
|
-
for (const
|
|
17467
|
-
const sp = trySplitRepo(
|
|
17468
|
-
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 });
|
|
17469
17805
|
}
|
|
17470
17806
|
while (queue.length && out.length < CHILDREN_MAX_TOTAL) {
|
|
17471
17807
|
const frame = queue.shift();
|
|
@@ -17478,13 +17814,13 @@ async function runIssueChildren(deps, epic, opts) {
|
|
|
17478
17814
|
}
|
|
17479
17815
|
for (const raw of nodes) {
|
|
17480
17816
|
if (out.length >= CHILDREN_MAX_TOTAL) break;
|
|
17481
|
-
const
|
|
17482
|
-
const key = childKey(
|
|
17817
|
+
const child2 = shapeChildNode(raw, frame.depth + 1, boardProjectId);
|
|
17818
|
+
const key = childKey(child2.repo, child2.number);
|
|
17483
17819
|
if (seen.has(key)) continue;
|
|
17484
17820
|
seen.add(key);
|
|
17485
|
-
out.push(
|
|
17486
|
-
const sp = trySplitRepo(
|
|
17487
|
-
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 });
|
|
17488
17824
|
}
|
|
17489
17825
|
}
|
|
17490
17826
|
return out;
|
|
@@ -18235,7 +18571,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18235
18571
|
projectMeta: meta,
|
|
18236
18572
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
18237
18573
|
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
|
|
18238
|
-
// 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
|
|
18239
18575
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
18240
18576
|
requiredGcpApis: (() => {
|
|
18241
18577
|
const v = meta?.requiredGcpApis;
|
|
@@ -18770,7 +19106,7 @@ function registerStageCommands(program3) {
|
|
|
18770
19106
|
const read = await fetchProjectBySlugChecked(slug, reg);
|
|
18771
19107
|
const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
|
|
18772
19108
|
if (decision.action === "fail") {
|
|
18773
|
-
return failGraceful(`port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
|
|
19109
|
+
return failGraceful(`stage port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
|
|
18774
19110
|
}
|
|
18775
19111
|
if (decision.action === "return") {
|
|
18776
19112
|
const [start2, end2] = decision.range;
|
|
@@ -18780,7 +19116,7 @@ function registerStageCommands(program3) {
|
|
|
18780
19116
|
const infra = resolvePortRangeInfra(process.cwd(), __dirname);
|
|
18781
19117
|
if (!infra) {
|
|
18782
19118
|
return failGraceful(
|
|
18783
|
-
`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`
|
|
18784
19120
|
);
|
|
18785
19121
|
}
|
|
18786
19122
|
const path2 = infra.registryPath;
|
|
@@ -18793,7 +19129,7 @@ function registerStageCommands(program3) {
|
|
|
18793
19129
|
const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
|
|
18794
19130
|
const write = await upsertProject(slug, { portRange: { start, end } }, reg);
|
|
18795
19131
|
if (!write.ok && source === "ddb") {
|
|
18796
|
-
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`);
|
|
18797
19133
|
}
|
|
18798
19134
|
if (o.json) {
|
|
18799
19135
|
printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
|
|
@@ -18833,7 +19169,7 @@ function registerStageCommands(program3) {
|
|
|
18833
19169
|
},
|
|
18834
19170
|
control: async ({ repo, action, host, ip }) => {
|
|
18835
19171
|
const res = await tenantControl({ repo, stage: "dev", action, host, ip }, rcDeps);
|
|
18836
|
-
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}`}`);
|
|
18837
19173
|
}
|
|
18838
19174
|
};
|
|
18839
19175
|
try {
|
|
@@ -19443,7 +19779,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
19443
19779
|
});
|
|
19444
19780
|
}
|
|
19445
19781
|
function renderGcApplyResult(result) {
|
|
19446
|
-
const lines = ["gc apply result:"];
|
|
19782
|
+
const lines = ["worktree gc apply result:"];
|
|
19447
19783
|
lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
|
|
19448
19784
|
lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
|
|
19449
19785
|
lines.push(` tracking refs removed: ${result.removedTrackingRefs.length ? result.removedTrackingRefs.join(", ") : "none"}`);
|
|
@@ -19488,7 +19824,7 @@ function evaluatePrMergeHousekeeping(report, context, force = false) {
|
|
|
19488
19824
|
const detail = failed ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
|
|
19489
19825
|
return {
|
|
19490
19826
|
blocked: true,
|
|
19491
|
-
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.)`
|
|
19492
19828
|
};
|
|
19493
19829
|
}
|
|
19494
19830
|
function assertPrMergeHousekeepingClean(startingPath, context, options = {}) {
|
|
@@ -19907,7 +20243,7 @@ function classifyStaleLeaks(input) {
|
|
|
19907
20243
|
kind: "stale-worktree",
|
|
19908
20244
|
ref: wt.branch,
|
|
19909
20245
|
detail: `merged/closed branch ${wt.branch} still checked out at ${wt.path}`,
|
|
19910
|
-
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)`
|
|
19911
20247
|
});
|
|
19912
20248
|
}
|
|
19913
20249
|
for (const branch of input.localBranches) {
|
|
@@ -19920,7 +20256,7 @@ function classifyStaleLeaks(input) {
|
|
|
19920
20256
|
kind: "stale-branch",
|
|
19921
20257
|
ref: branch,
|
|
19922
20258
|
detail: `merged/closed branch ${branch} has no worktree`,
|
|
19923
|
-
remediation: "mmi-cli gc --apply"
|
|
20259
|
+
remediation: "mmi-cli worktree gc --apply"
|
|
19924
20260
|
});
|
|
19925
20261
|
}
|
|
19926
20262
|
for (const wt of input.worktrees) {
|
|
@@ -19944,7 +20280,7 @@ function classifyStaleLeaks(input) {
|
|
|
19944
20280
|
kind: "orphan-dir",
|
|
19945
20281
|
ref: dir,
|
|
19946
20282
|
detail: `directory under the worktrees root is not a registered worktree`,
|
|
19947
|
-
remediation: `mmi-cli gc --apply (or: Remove-Item/rm -rf "${dir}")`
|
|
20283
|
+
remediation: `mmi-cli worktree gc --apply (or: Remove-Item/rm -rf "${dir}")`
|
|
19948
20284
|
});
|
|
19949
20285
|
}
|
|
19950
20286
|
}
|
|
@@ -20740,7 +21076,7 @@ function buildPluginGuardDecision(i) {
|
|
|
20740
21076
|
}
|
|
20741
21077
|
function buildGuardSessionStartLine(state, opts = {}) {
|
|
20742
21078
|
if (state === "healthy" || state === "not-org") return { exitCode: 0 };
|
|
20743
|
-
const recovery = opts.recovery ?? "mmi-cli plugin
|
|
21079
|
+
const recovery = opts.recovery ?? "mmi-cli plugin heal";
|
|
20744
21080
|
const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
|
|
20745
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";
|
|
20746
21082
|
return {
|
|
@@ -21097,7 +21433,7 @@ function formatLastRun(r) {
|
|
|
21097
21433
|
}
|
|
21098
21434
|
function formatDeployStatus(r) {
|
|
21099
21435
|
const lines = [
|
|
21100
|
-
`deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
|
|
21436
|
+
`runtime deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
|
|
21101
21437
|
"",
|
|
21102
21438
|
`running version: ${r.runningVersion ?? "none stamped"}`,
|
|
21103
21439
|
`last deploy run: ${formatLastRun(r)}`,
|
|
@@ -21130,7 +21466,7 @@ function registerDeployCommands(program3) {
|
|
|
21130
21466
|
const deploy = program3.command("deploy").description("per-stage deploy observability \u2014 last run, health, and running version (#2688)");
|
|
21131
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) => {
|
|
21132
21468
|
if (!STAGES2.includes(stage)) {
|
|
21133
|
-
return fail(`deploy status: <stage> must be dev, rc, or main`);
|
|
21469
|
+
return fail(`runtime deploy status: <stage> must be dev, rc, or main`);
|
|
21134
21470
|
}
|
|
21135
21471
|
try {
|
|
21136
21472
|
const cfg = await loadConfig();
|
|
@@ -21159,7 +21495,7 @@ function registerDeployCommands(program3) {
|
|
|
21159
21495
|
}
|
|
21160
21496
|
if (report.health?.ok === false) process.exitCode = 1;
|
|
21161
21497
|
} catch (e) {
|
|
21162
|
-
return failGraceful(`deploy status: ${e.message}`);
|
|
21498
|
+
return failGraceful(`runtime deploy status: ${e.message}`);
|
|
21163
21499
|
}
|
|
21164
21500
|
});
|
|
21165
21501
|
}
|
|
@@ -21278,7 +21614,7 @@ async function collectOnboardStatus() {
|
|
|
21278
21614
|
if (read.ok && read.project) {
|
|
21279
21615
|
registry2 = { ok: true, detail: `project "${read.project.name ?? slug}" registered` };
|
|
21280
21616
|
} else if (read.ok) {
|
|
21281
|
-
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>` };
|
|
21282
21618
|
} else {
|
|
21283
21619
|
registry2 = { ok: false, detail: `read failed: ${read.error}` };
|
|
21284
21620
|
}
|
|
@@ -21305,7 +21641,7 @@ async function collectOnboardStatus() {
|
|
|
21305
21641
|
if (!cfg.sagaApiUrl) {
|
|
21306
21642
|
nextCommand = "mmi-cli doctor \u2014 fix Hub API URL configuration first";
|
|
21307
21643
|
} else if (!registry2.ok) {
|
|
21308
|
-
nextCommand = "mmi-cli project set <owner/repo>";
|
|
21644
|
+
nextCommand = "mmi-cli org project set <owner/repo>";
|
|
21309
21645
|
} else if (!board.ok) {
|
|
21310
21646
|
nextCommand = "mmi-cli board read";
|
|
21311
21647
|
} else {
|
|
@@ -21374,10 +21710,11 @@ var LOOP_PLAYBOOKS = {
|
|
|
21374
21710
|
agent: {
|
|
21375
21711
|
title: "Agent",
|
|
21376
21712
|
steps: [
|
|
21377
|
-
{ label: "
|
|
21713
|
+
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
21378
21714
|
{ label: "Read the next board item", command: "mmi-cli board read" },
|
|
21379
|
-
{ label: "
|
|
21715
|
+
{ label: "Claim it and create an isolated worktree", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
21380
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" },
|
|
21381
21718
|
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
21382
21719
|
{ label: "Wait for checks and land to development", command: "mmi-cli pr checks-wait <PR-number> && mmi-cli pr land <PR-number>" },
|
|
21383
21720
|
{ label: "Release only after the gated train is authorized", command: "mmi-cli release --apply" }
|
|
@@ -21386,16 +21723,17 @@ var LOOP_PLAYBOOKS = {
|
|
|
21386
21723
|
"start-work": {
|
|
21387
21724
|
title: "Start Work",
|
|
21388
21725
|
steps: [
|
|
21389
|
-
{ label: "
|
|
21390
|
-
{ label: "
|
|
21391
|
-
{ 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" },
|
|
21392
21729
|
{ label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
|
|
21393
21730
|
]
|
|
21394
21731
|
},
|
|
21395
21732
|
"ship-pr": {
|
|
21396
21733
|
title: "Ship PR",
|
|
21397
21734
|
steps: [
|
|
21398
|
-
{ 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' },
|
|
21399
21737
|
{ label: "Wait for CI checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
21400
21738
|
{ label: "Land the PR (merge to development)", command: "mmi-cli pr land <PR-number>" }
|
|
21401
21739
|
]
|
|
@@ -21403,9 +21741,9 @@ var LOOP_PLAYBOOKS = {
|
|
|
21403
21741
|
"hotfix": {
|
|
21404
21742
|
title: "Hotfix",
|
|
21405
21743
|
steps: [
|
|
21406
|
-
{ label: "
|
|
21407
|
-
{ label: "
|
|
21408
|
-
{ 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>" }
|
|
21409
21747
|
]
|
|
21410
21748
|
}
|
|
21411
21749
|
};
|
|
@@ -21455,6 +21793,22 @@ function formatExplainCommand(cmd, rootName) {
|
|
|
21455
21793
|
}
|
|
21456
21794
|
return lines.join("\n").trimEnd();
|
|
21457
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
|
+
}
|
|
21458
21812
|
function formatExplainLoop(playbook) {
|
|
21459
21813
|
const lines = [`${playbook.title} loop:`];
|
|
21460
21814
|
for (const step of playbook.steps) {
|
|
@@ -21464,7 +21818,15 @@ function formatExplainLoop(playbook) {
|
|
|
21464
21818
|
return lines.join("\n");
|
|
21465
21819
|
}
|
|
21466
21820
|
function findCommandInManifest(manifest, commandPath2) {
|
|
21467
|
-
|
|
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);
|
|
21468
21830
|
}
|
|
21469
21831
|
function registerExplainCommand(program3) {
|
|
21470
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) => {
|
|
@@ -21479,18 +21841,16 @@ function registerExplainCommand(program3) {
|
|
|
21479
21841
|
}
|
|
21480
21842
|
const manifest = buildCommandManifest(program3);
|
|
21481
21843
|
if (!commandArgs.length) {
|
|
21482
|
-
console.log(
|
|
21483
|
-
console.log(manifest.tree.description ?? "");
|
|
21484
|
-
console.log("\nRun `mmi-cli commands` to list every subcommand + its flags.");
|
|
21844
|
+
console.log(formatManifestHuman(manifest));
|
|
21485
21845
|
return;
|
|
21486
21846
|
}
|
|
21487
21847
|
const commandPath2 = commandArgs.join(" ");
|
|
21488
|
-
const
|
|
21489
|
-
if (!
|
|
21848
|
+
const command = findCommandInManifest(manifest, commandPath2);
|
|
21849
|
+
if (!command) {
|
|
21490
21850
|
fail(`explain: unknown command "${commandPath2}"`, { code: ERROR_CODES.ERR_NOT_FOUND });
|
|
21491
21851
|
return;
|
|
21492
21852
|
}
|
|
21493
|
-
console.log(formatExplainCommand(
|
|
21853
|
+
console.log(command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
|
|
21494
21854
|
});
|
|
21495
21855
|
}
|
|
21496
21856
|
|
|
@@ -22127,7 +22487,7 @@ function checkClaudePlugin(probe) {
|
|
|
22127
22487
|
return {
|
|
22128
22488
|
ok: false,
|
|
22129
22489
|
label: guardState === "no-install" ? "Claude plugin \u2014 not installed" : "Claude plugin \u2014 unresolved (marketplace/cache missing)",
|
|
22130
|
-
fix: "run `mmi-cli plugin
|
|
22490
|
+
fix: "run `mmi-cli plugin heal` to reinstall the MMI marketplace + plugin, then restart Claude",
|
|
22131
22491
|
verbose: evidence
|
|
22132
22492
|
};
|
|
22133
22493
|
}
|
|
@@ -22186,7 +22546,7 @@ function checkPluginCache(input) {
|
|
|
22186
22546
|
// Lead with the versioned-cache framing when there are stale versions; otherwise report the staging litter
|
|
22187
22547
|
// on its own so a cache with zero versioned dirs still gets an honest ✗.
|
|
22188
22548
|
detail: input.stale.length ? `${input.cached} versions cached (${parts.join("; ")})` : parts.join("; "),
|
|
22189
|
-
fix: "run `mmi-cli plugin
|
|
22549
|
+
fix: "run `mmi-cli plugin prune` to review, then `mmi-cli plugin prune --apply` to delete",
|
|
22190
22550
|
verbose: evidence
|
|
22191
22551
|
};
|
|
22192
22552
|
}
|
|
@@ -22216,7 +22576,7 @@ function checkSchedules(probe) {
|
|
|
22216
22576
|
ok: false,
|
|
22217
22577
|
label: "schedules",
|
|
22218
22578
|
detail: `${probe.incomplete.length} source(s) unreadable`,
|
|
22219
|
-
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",
|
|
22220
22580
|
verbose: evidence
|
|
22221
22581
|
};
|
|
22222
22582
|
}
|
|
@@ -22225,7 +22585,7 @@ function checkSchedules(probe) {
|
|
|
22225
22585
|
ok: false,
|
|
22226
22586
|
label: "schedules",
|
|
22227
22587
|
detail: `${probe.drift.length} drift finding(s)`,
|
|
22228
|
-
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",
|
|
22229
22589
|
verbose: evidence
|
|
22230
22590
|
};
|
|
22231
22591
|
}
|
|
@@ -22581,11 +22941,13 @@ function allLongFlags() {
|
|
|
22581
22941
|
if (cachedLongFlags) return cachedLongFlags;
|
|
22582
22942
|
const acc = /* @__PURE__ */ new Set();
|
|
22583
22943
|
const walk = (node) => {
|
|
22944
|
+
if (!isCanonicalSuggestion(node)) return;
|
|
22584
22945
|
for (const opt of node.options) {
|
|
22946
|
+
if (opt.discovery) continue;
|
|
22585
22947
|
const m = /--[\w-]+/.exec(opt.flags);
|
|
22586
22948
|
if (m) acc.add(m[0]);
|
|
22587
22949
|
}
|
|
22588
|
-
for (const
|
|
22950
|
+
for (const child2 of node.subcommands) walk(child2);
|
|
22589
22951
|
};
|
|
22590
22952
|
walk(buildCommandManifest(program2).tree);
|
|
22591
22953
|
cachedLongFlags = [...acc];
|
|
@@ -22621,7 +22983,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
22621
22983
|
process.stderr.write(str);
|
|
22622
22984
|
}
|
|
22623
22985
|
var program2 = new Command();
|
|
22624
|
-
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)");
|
|
22625
22987
|
function appActorDeps() {
|
|
22626
22988
|
return {
|
|
22627
22989
|
fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
|
|
@@ -22651,22 +23013,26 @@ rules.command("gitignore").option("--write", "upsert the managed block into .git
|
|
|
22651
23013
|
if (opts.write) {
|
|
22652
23014
|
if (plan.changed) {
|
|
22653
23015
|
(0, import_node_fs27.writeFileSync)(path2, plan.content, "utf8");
|
|
22654
|
-
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
23016
|
+
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
22655
23017
|
} else {
|
|
22656
|
-
console.log("mmi-cli rules gitignore: up to date");
|
|
23018
|
+
console.log("mmi-cli org rules gitignore: up to date");
|
|
22657
23019
|
}
|
|
22658
23020
|
return;
|
|
22659
23021
|
}
|
|
22660
23022
|
if (plan.changed) {
|
|
22661
|
-
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`);
|
|
22662
23024
|
process.exitCode = 1;
|
|
22663
23025
|
} else {
|
|
22664
|
-
console.log("mmi-cli rules gitignore: up to date");
|
|
23026
|
+
console.log("mmi-cli org rules gitignore: up to date");
|
|
22665
23027
|
}
|
|
22666
23028
|
});
|
|
22667
|
-
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) => {
|
|
22668
23030
|
const manifest = buildCommandManifest(program2);
|
|
22669
|
-
|
|
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 }));
|
|
22670
23036
|
});
|
|
22671
23037
|
async function runWhoami(io = consoleIo) {
|
|
22672
23038
|
const cfg = await loadConfig();
|
|
@@ -22743,21 +23109,21 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
22743
23109
|
);
|
|
22744
23110
|
if (o.json) return console.log(JSON.stringify(result));
|
|
22745
23111
|
if (!o.quiet || result.removed.length || result.stillDeferred.length || result.skipped.length) {
|
|
22746
|
-
if (result.removed.length) console.log(`gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
|
|
22747
|
-
if (result.stillDeferred.length) console.log(`gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
|
|
22748
|
-
if (result.skipped.length) console.log(`gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
|
|
22749
|
-
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");
|
|
22750
23116
|
}
|
|
22751
23117
|
} catch (e) {
|
|
22752
|
-
fail(`gc sweep-deferred: ${e.message}`);
|
|
23118
|
+
fail(`worktree gc sweep-deferred: ${e.message}`);
|
|
22753
23119
|
}
|
|
22754
23120
|
}, DEFERRED_SWEEP_HARD_TIMEOUT_MS, () => {
|
|
22755
|
-
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)");
|
|
22756
23122
|
process.exit(process.exitCode ?? 0);
|
|
22757
23123
|
});
|
|
22758
23124
|
});
|
|
22759
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) => {
|
|
22760
|
-
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");
|
|
22761
23127
|
if (o.scratch) {
|
|
22762
23128
|
try {
|
|
22763
23129
|
const root = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
@@ -22765,11 +23131,11 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
22765
23131
|
if (o.json) return console.log(JSON.stringify({ dryRun: !o.apply, ...run }, null, 2));
|
|
22766
23132
|
return console.log(formatScratchGcPlan(run.plan, Boolean(o.apply), run.applied));
|
|
22767
23133
|
} catch (e) {
|
|
22768
|
-
return fail(`gc --scratch: ${e.message}`);
|
|
23134
|
+
return fail(`worktree gc --scratch: ${e.message}`);
|
|
22769
23135
|
}
|
|
22770
23136
|
}
|
|
22771
23137
|
const limit = Number.parseInt(o.limit, 10);
|
|
22772
|
-
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");
|
|
22773
23139
|
try {
|
|
22774
23140
|
const plan = await gcPlan(o.remote, limit);
|
|
22775
23141
|
if (o.apply && !o.json) console.log(formatGcPlan(plan, false));
|
|
@@ -22794,7 +23160,7 @@ ${renderGcApplyResult(applyResult)}`);
|
|
|
22794
23160
|
}
|
|
22795
23161
|
if (applyResult?.failed.length) process.exitCode = 1;
|
|
22796
23162
|
} catch (e) {
|
|
22797
|
-
fail(`gc: ${e.message}`);
|
|
23163
|
+
fail(`worktree gc: ${e.message}`);
|
|
22798
23164
|
}
|
|
22799
23165
|
});
|
|
22800
23166
|
var NPM_PROVISION_TIMEOUT_MS = 3e5;
|
|
@@ -22804,19 +23170,19 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
22804
23170
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
22805
23171
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
22806
23172
|
return new Promise((resolve5, reject) => {
|
|
22807
|
-
const
|
|
23173
|
+
const child2 = (0, import_node_child_process12.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
22808
23174
|
const timer = setTimeout(() => {
|
|
22809
23175
|
try {
|
|
22810
|
-
|
|
23176
|
+
child2.kill();
|
|
22811
23177
|
} catch {
|
|
22812
23178
|
}
|
|
22813
23179
|
reject(new Error(`${command} timed out after ${NPM_PROVISION_TIMEOUT_MS}ms in ${cwd}`));
|
|
22814
23180
|
}, NPM_PROVISION_TIMEOUT_MS);
|
|
22815
|
-
|
|
23181
|
+
child2.on("error", (e) => {
|
|
22816
23182
|
clearTimeout(timer);
|
|
22817
23183
|
reject(e);
|
|
22818
23184
|
});
|
|
22819
|
-
|
|
23185
|
+
child2.on("exit", (code) => {
|
|
22820
23186
|
clearTimeout(timer);
|
|
22821
23187
|
if (code === 0) resolve5();
|
|
22822
23188
|
else reject(new Error(`${command} exited ${code} in ${cwd}`));
|
|
@@ -22994,7 +23360,7 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
22994
23360
|
return { onBoard: false };
|
|
22995
23361
|
}
|
|
22996
23362
|
if (!cfg.projectId) {
|
|
22997
|
-
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`);
|
|
22998
23364
|
return { onBoard: false };
|
|
22999
23365
|
}
|
|
23000
23366
|
try {
|
|
@@ -23074,7 +23440,7 @@ docsAudit.command("record").description("write a dated janitor verdict for one r
|
|
|
23074
23440
|
if (o.outcome === "clean") outcome = { kind: "clean" };
|
|
23075
23441
|
else if (o.outcome === "refreshed") outcome = { kind: "refreshed", count: Number(o.count) };
|
|
23076
23442
|
else if (o.outcome === "failed") outcome = { kind: "failed", reason: o.reason ?? "" };
|
|
23077
|
-
else return failGraceful(`docs
|
|
23443
|
+
else return failGraceful(`docs audit record: --outcome must be clean|refreshed|failed, got "${o.outcome}"`);
|
|
23078
23444
|
const verdict = docsAuditRecord({ repo, date, shaRange: o.shaRange, outcome, checkerVendor: o.checkerVendor });
|
|
23079
23445
|
await reportWrite("docs-audit record", await recordDocsAudit(verdict, registryClientDeps(await loadConfig())));
|
|
23080
23446
|
} catch (e) {
|
|
@@ -23109,43 +23475,36 @@ tenant.command("control <owner/repo> <stage> <action>").description("run bounded
|
|
|
23109
23475
|
const body = { ok: result.conclusion === "success", secrets: result.secrets, ssmStatus: result.conclusion === "success" ? "Success" : "Failed", raw: result.secretsRaw };
|
|
23110
23476
|
const { lines, failure } = renderVerifySecrets(body);
|
|
23111
23477
|
for (const line of lines) printLine(line);
|
|
23112
|
-
if (failure) return failGraceful(`tenant control ${stage} verify-secrets: ${failure}`);
|
|
23478
|
+
if (failure) return failGraceful(`runtime tenant control ${stage} verify-secrets: ${failure}`);
|
|
23113
23479
|
} else {
|
|
23114
23480
|
printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
|
|
23115
23481
|
}
|
|
23116
23482
|
if (result.conclusion === "failure") {
|
|
23117
|
-
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}`);
|
|
23118
23484
|
}
|
|
23119
23485
|
} catch (e) {
|
|
23120
|
-
return failGraceful(`tenant control: ${e.message}`);
|
|
23486
|
+
return failGraceful(`runtime tenant control: ${e.message}`);
|
|
23121
23487
|
}
|
|
23122
23488
|
});
|
|
23123
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) => {
|
|
23124
|
-
if (!["dev", "rc", "main"].includes(stage)) return fail("tenant status: <stage> must be dev, rc, or main");
|
|
23125
|
-
const cfg = await loadConfig();
|
|
23126
|
-
const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
|
|
23127
|
-
console.log(JSON.stringify(result, null, 2));
|
|
23128
|
-
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
23129
|
-
});
|
|
23130
|
-
tenant.command("readiness <owner/repo> <stage>").description("alias for tenant status: read-only tenant runtime readiness").action(async (repo, stage) => {
|
|
23131
|
-
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");
|
|
23132
23491
|
const cfg = await loadConfig();
|
|
23133
23492
|
const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
|
|
23134
23493
|
console.log(JSON.stringify(result, null, 2));
|
|
23135
23494
|
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
23136
23495
|
});
|
|
23137
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) => {
|
|
23138
|
-
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");
|
|
23139
23498
|
try {
|
|
23140
23499
|
const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage, ref: o.ref, watch: o.watch });
|
|
23141
23500
|
return printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantRedeploy(result));
|
|
23142
23501
|
} catch (e) {
|
|
23143
|
-
return failGraceful(`tenant redeploy: ${e.message}`);
|
|
23502
|
+
return failGraceful(`runtime tenant redeploy: ${e.message}`);
|
|
23144
23503
|
}
|
|
23145
23504
|
});
|
|
23146
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) => {
|
|
23147
23506
|
if (o.retire && !o.yes) {
|
|
23148
|
-
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");
|
|
23149
23508
|
}
|
|
23150
23509
|
const cfg = await loadConfig();
|
|
23151
23510
|
const cdeps = registryClientDeps(cfg);
|
|
@@ -23165,7 +23524,7 @@ tenant.command("sweep-rc").description("discover (and optionally retire) running
|
|
|
23165
23524
|
}, { retire: !!o.retire });
|
|
23166
23525
|
return printLine(o.json ? JSON.stringify(result) : renderSweep(result));
|
|
23167
23526
|
} catch (e) {
|
|
23168
|
-
return failGraceful(`tenant sweep-rc: ${e.message}`);
|
|
23527
|
+
return failGraceful(`runtime tenant sweep-rc: ${e.message}`);
|
|
23169
23528
|
}
|
|
23170
23529
|
});
|
|
23171
23530
|
async function resolveDnsBounded(host, timeoutMs = 3e3) {
|
|
@@ -23274,7 +23633,7 @@ async function projectTarget(commandName, explicitTarget) {
|
|
|
23274
23633
|
project.command("list").description("list all projects (identity + board, never deploy coords)").option("--json", "machine-readable output").action(async (o) => {
|
|
23275
23634
|
const cfg = await loadConfig();
|
|
23276
23635
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
23277
|
-
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");
|
|
23278
23637
|
if (o.json) {
|
|
23279
23638
|
console.log(JSON.stringify(projects));
|
|
23280
23639
|
return;
|
|
@@ -23287,16 +23646,16 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
23287
23646
|
const cfg = await loadConfig();
|
|
23288
23647
|
let target;
|
|
23289
23648
|
try {
|
|
23290
|
-
target = await projectTarget("project get", repoOrSlug);
|
|
23649
|
+
target = await projectTarget("org project get", repoOrSlug);
|
|
23291
23650
|
} catch (e) {
|
|
23292
23651
|
return fail(e.message);
|
|
23293
23652
|
}
|
|
23294
23653
|
const read = await fetchProjectBySlugChecked(slugOf(target), registryClientDeps(cfg));
|
|
23295
23654
|
if (!read.ok) {
|
|
23296
|
-
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`);
|
|
23297
23656
|
}
|
|
23298
23657
|
if (!read.project) {
|
|
23299
|
-
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)`);
|
|
23300
23659
|
}
|
|
23301
23660
|
console.log(JSON.stringify(read.project));
|
|
23302
23661
|
if (!o.json) {
|
|
@@ -23307,55 +23666,31 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
23307
23666
|
console.error(
|
|
23308
23667
|
`${m.name ?? target} \xB7 class ${m.class ?? "?"} \xB7 deploy ${m.deployModel ?? "?"}
|
|
23309
23668
|
release track: ${track} \u2014 stages: ${stages}${note}
|
|
23310
|
-
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.`
|
|
23311
23670
|
);
|
|
23312
23671
|
}
|
|
23313
23672
|
});
|
|
23314
|
-
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) => {
|
|
23315
|
-
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.";
|
|
23316
|
-
if (o.json) {
|
|
23317
|
-
console.log(JSON.stringify({ ok: false, stage: o.stage, error: msg }));
|
|
23318
|
-
process.exitCode = 1;
|
|
23319
|
-
return;
|
|
23320
|
-
}
|
|
23321
|
-
fail(msg);
|
|
23322
|
-
});
|
|
23323
23673
|
var projectDeploy = project.command("deploy").description("read nonsecret DEPLOY# facts (domain, port, deploy path, substrate, host presence)");
|
|
23324
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) => {
|
|
23325
23675
|
const cfg = await loadConfig();
|
|
23326
23676
|
let target;
|
|
23327
23677
|
try {
|
|
23328
|
-
target = await projectTarget("project deploy get", repoOrSlug);
|
|
23678
|
+
target = await projectTarget("org project deploy get", repoOrSlug);
|
|
23329
23679
|
} catch (e) {
|
|
23330
23680
|
return fail(e.message);
|
|
23331
23681
|
}
|
|
23332
23682
|
const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
|
|
23333
|
-
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}`);
|
|
23334
23684
|
const stage = o.stage?.trim();
|
|
23335
|
-
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");
|
|
23336
23686
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
23337
23687
|
console.log(JSON.stringify(payload));
|
|
23338
23688
|
});
|
|
23339
|
-
|
|
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) => {
|
|
23340
23690
|
const cfg = await loadConfig();
|
|
23341
23691
|
let target;
|
|
23342
23692
|
try {
|
|
23343
|
-
target = await projectTarget("project
|
|
23344
|
-
} catch (e) {
|
|
23345
|
-
return fail(e.message);
|
|
23346
|
-
}
|
|
23347
|
-
const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
|
|
23348
|
-
if (!out) return failGraceful(`project deploy list: Hub deploy facts read failed for ${target}`);
|
|
23349
|
-
const stage = o.stage?.trim();
|
|
23350
|
-
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("project deploy list: --stage must be dev, rc, or main");
|
|
23351
|
-
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
23352
|
-
console.log(JSON.stringify(payload));
|
|
23353
|
-
});
|
|
23354
|
-
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) => {
|
|
23355
|
-
const cfg = await loadConfig();
|
|
23356
|
-
let target;
|
|
23357
|
-
try {
|
|
23358
|
-
target = await projectTarget("project doctor", repo);
|
|
23693
|
+
target = await projectTarget("org project doctor", repo);
|
|
23359
23694
|
} catch (e) {
|
|
23360
23695
|
return fail(e.message);
|
|
23361
23696
|
}
|
|
@@ -23367,7 +23702,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23367
23702
|
const cfg = await loadConfig();
|
|
23368
23703
|
let target;
|
|
23369
23704
|
try {
|
|
23370
|
-
target = await projectTarget("project heal", repo);
|
|
23705
|
+
target = await projectTarget("org project heal", repo);
|
|
23371
23706
|
} catch (e) {
|
|
23372
23707
|
return fail(e.message);
|
|
23373
23708
|
}
|
|
@@ -23376,7 +23711,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23376
23711
|
fetchProject: (s) => fetchProjectBySlugChecked(s, reg),
|
|
23377
23712
|
upsertProject: (s, patch) => upsertProject(s, patch, reg)
|
|
23378
23713
|
});
|
|
23379
|
-
if (out.status === "aborted") return failGraceful(`project heal: ${out.error}`);
|
|
23714
|
+
if (out.status === "aborted") return failGraceful(`org project heal: ${out.error}`);
|
|
23380
23715
|
if (out.status === "planned") {
|
|
23381
23716
|
console.log(JSON.stringify({ ok: true, slug: out.slug, dryRun: true, patch: out.patch, appOwnedGaps: out.appOwnedGaps }));
|
|
23382
23717
|
return;
|
|
@@ -23385,15 +23720,15 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
|
|
|
23385
23720
|
console.log(JSON.stringify({ ok: true, slug: out.slug, applied: [], appOwnedGaps: out.appOwnedGaps }));
|
|
23386
23721
|
return;
|
|
23387
23722
|
}
|
|
23388
|
-
if (out.status === "write-failed") return reportWrite("project heal", out.write);
|
|
23723
|
+
if (out.status === "write-failed") return reportWrite("org project heal", out.write);
|
|
23389
23724
|
console.log(JSON.stringify({ ok: true, slug: out.slug, applied: out.applied, appOwnedGaps: out.appOwnedGaps, result: out.result }));
|
|
23390
23725
|
});
|
|
23391
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) => {
|
|
23392
|
-
if (!o.updateIssue) return fail("project readiness: pass --update-issue");
|
|
23727
|
+
if (!o.updateIssue) return fail("org project readiness: pass --update-issue");
|
|
23393
23728
|
const cfg = await loadConfig();
|
|
23394
23729
|
let target;
|
|
23395
23730
|
try {
|
|
23396
|
-
target = await projectTarget("project readiness", repo);
|
|
23731
|
+
target = await projectTarget("org project readiness", repo);
|
|
23397
23732
|
} catch (e) {
|
|
23398
23733
|
return fail(e.message);
|
|
23399
23734
|
}
|
|
@@ -23405,19 +23740,19 @@ project.command("attest [owner/repo]").description("attest this repo's app-owned
|
|
|
23405
23740
|
const cfg = await loadConfig();
|
|
23406
23741
|
let target;
|
|
23407
23742
|
try {
|
|
23408
|
-
target = await projectTarget("project attest", repoOrSlug);
|
|
23743
|
+
target = await projectTarget("org project attest", repoOrSlug);
|
|
23409
23744
|
} catch (e) {
|
|
23410
23745
|
return fail(e.message);
|
|
23411
23746
|
}
|
|
23412
23747
|
const repo = target.includes("/") ? target : `mutmutco/${slugOf(target)}`;
|
|
23413
23748
|
const res = await attestAppGaps(slugOf(target), repo, registryClientDeps(cfg));
|
|
23414
|
-
return reportWrite("project attest", res);
|
|
23749
|
+
return reportWrite("org project attest", res);
|
|
23415
23750
|
});
|
|
23416
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) => {
|
|
23417
23752
|
const cfg = await loadConfig();
|
|
23418
23753
|
let target;
|
|
23419
23754
|
try {
|
|
23420
|
-
target = await projectTarget("project set", repoOrSlug);
|
|
23755
|
+
target = await projectTarget("org project set", repoOrSlug);
|
|
23421
23756
|
} catch (e) {
|
|
23422
23757
|
return fail(e.message);
|
|
23423
23758
|
}
|
|
@@ -23426,12 +23761,12 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23426
23761
|
const setAlias = o.set ?? [];
|
|
23427
23762
|
const vars = [...o.var ?? [], ...setAlias];
|
|
23428
23763
|
const dupe = duplicateVarKeyAcrossFlags(o.var ?? [], setAlias);
|
|
23429
|
-
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`);
|
|
23430
23765
|
if (o.secretsFile) {
|
|
23431
23766
|
try {
|
|
23432
23767
|
vars.push(`secrets=${(0, import_node_fs27.readFileSync)(o.secretsFile, "utf8")}`);
|
|
23433
23768
|
} catch (e) {
|
|
23434
|
-
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}`);
|
|
23435
23770
|
}
|
|
23436
23771
|
}
|
|
23437
23772
|
let patch;
|
|
@@ -23446,19 +23781,19 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23446
23781
|
clearWebProfile: Boolean(o.clearWebProfile)
|
|
23447
23782
|
});
|
|
23448
23783
|
} catch (e) {
|
|
23449
|
-
return fail(e.message.replace(/^project set: /, "project set: "));
|
|
23784
|
+
return fail(e.message.replace(/^org project set: /, "org project set: "));
|
|
23450
23785
|
}
|
|
23451
23786
|
const existing = await fetchProjectBySlug(slug, registryClientDeps(cfg));
|
|
23452
23787
|
const boardError = boardLinkWriteError(patch, existing);
|
|
23453
|
-
if (boardError) return fail(`project set: ${boardError}`);
|
|
23788
|
+
if (boardError) return fail(`org project set: ${boardError}`);
|
|
23454
23789
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
23455
|
-
return reportWrite("project set", res);
|
|
23790
|
+
return reportWrite("org project set", res);
|
|
23456
23791
|
});
|
|
23457
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) => {
|
|
23458
23793
|
const cfg = await loadConfig();
|
|
23459
23794
|
let target;
|
|
23460
23795
|
try {
|
|
23461
|
-
target = await projectTarget("project retire", repoOrSlug);
|
|
23796
|
+
target = await projectTarget("org project retire", repoOrSlug);
|
|
23462
23797
|
} catch (e) {
|
|
23463
23798
|
return fail(e.message);
|
|
23464
23799
|
}
|
|
@@ -23484,7 +23819,7 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
23484
23819
|
printLine(JSON.stringify(structured));
|
|
23485
23820
|
} else {
|
|
23486
23821
|
const verb = result.applied ? "retired" : "WOULD retire (dry-run; pass --apply to delete)";
|
|
23487
|
-
printLine(`project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
23822
|
+
printLine(`org project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
23488
23823
|
if (result.applied) {
|
|
23489
23824
|
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED \u2014 ${result.removedMeta.error}`}`);
|
|
23490
23825
|
} else {
|
|
@@ -23499,10 +23834,10 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
23499
23834
|
printLine(` ${result.vaultNote}`);
|
|
23500
23835
|
}
|
|
23501
23836
|
if (result.applied && !result.removedMeta.ok) {
|
|
23502
|
-
return failGraceful(`project retire: META delete failed \u2014 ${result.removedMeta.error}`);
|
|
23837
|
+
return failGraceful(`org project retire: META delete failed \u2014 ${result.removedMeta.error}`);
|
|
23503
23838
|
}
|
|
23504
23839
|
});
|
|
23505
|
-
var fullTrack = program2.command("full-track").description("direct-to-
|
|
23840
|
+
var fullTrack = program2.command("full-track").description("direct-to-train readiness audits");
|
|
23506
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) => {
|
|
23507
23842
|
const cfg = await loadConfig();
|
|
23508
23843
|
const reg = registryClientDeps(cfg);
|
|
@@ -23515,7 +23850,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
23515
23850
|
buildTenantRuntimeStatusFor(repo, "rc", cfg),
|
|
23516
23851
|
buildTenantRuntimeStatusFor(repo, "main", cfg)
|
|
23517
23852
|
]);
|
|
23518
|
-
if (!metaRead.ok) return failGraceful(`
|
|
23853
|
+
if (!metaRead.ok) return failGraceful(`train readiness: Hub registry read failed (${metaRead.error})`);
|
|
23519
23854
|
const report = buildFullTrackReadinessReport({
|
|
23520
23855
|
repo,
|
|
23521
23856
|
slug,
|
|
@@ -23527,18 +23862,18 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
23527
23862
|
console.log(JSON.stringify(report, null, 2));
|
|
23528
23863
|
if (!report.rcand.canApply) process.exitCode = 1;
|
|
23529
23864
|
});
|
|
23530
|
-
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) => {
|
|
23531
23866
|
const cfg = await loadConfig();
|
|
23532
23867
|
let target;
|
|
23533
23868
|
try {
|
|
23534
|
-
target = await projectTarget("project set-deploy", repoOrSlug);
|
|
23869
|
+
target = await projectTarget("org project set-deploy", repoOrSlug);
|
|
23535
23870
|
} catch (e) {
|
|
23536
23871
|
return fail(e.message);
|
|
23537
23872
|
}
|
|
23538
23873
|
let noEnvFile;
|
|
23539
23874
|
if (typeof o.envFile === "string") {
|
|
23540
23875
|
const v = o.envFile.trim().toLowerCase();
|
|
23541
|
-
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");
|
|
23542
23877
|
noEnvFile = v === "true";
|
|
23543
23878
|
}
|
|
23544
23879
|
if (noEnvFile === true) {
|
|
@@ -23548,10 +23883,10 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
|
|
|
23548
23883
|
const sameRepo = !repoOrSlug || Boolean(cwdRepo) && (repoOrSlug.includes("/") ? cwdRepo.toLowerCase() === target.toLowerCase() : slugOf(cwdRepo) === slugOf(target));
|
|
23549
23884
|
if (sameRepo) {
|
|
23550
23885
|
const verdict = evaluateFilelessComposeGuard({ composeText: await readStageBranchCompose(branch), branch, force: Boolean(o.force) });
|
|
23551
|
-
if (!verdict.ok) return fail(`project set-deploy: ${verdict.reason}`);
|
|
23552
|
-
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}`);
|
|
23553
23888
|
} else {
|
|
23554
|
-
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).`);
|
|
23555
23890
|
}
|
|
23556
23891
|
}
|
|
23557
23892
|
}
|
|
@@ -23575,13 +23910,13 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
|
|
|
23575
23910
|
return fail(e.message);
|
|
23576
23911
|
}
|
|
23577
23912
|
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
23578
|
-
return reportWrite("project set-deploy", res);
|
|
23913
|
+
return reportWrite("org project set-deploy", res);
|
|
23579
23914
|
});
|
|
23580
23915
|
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
23581
23916
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
23582
23917
|
const cfg = await loadConfig();
|
|
23583
23918
|
const org = await fetchOrgConfig(registryClientDeps(cfg));
|
|
23584
|
-
if (!org) return failGraceful("
|
|
23919
|
+
if (!org) return failGraceful("org config get: Hub API unreachable, unseeded, or this repo is not bootstrapped");
|
|
23585
23920
|
console.log(JSON.stringify(org));
|
|
23586
23921
|
});
|
|
23587
23922
|
var oauth = program2.command("oauth").description("per-repo Google OAuth \u2014 plan the canonical URI set, verify the client is port-agnostic");
|
|
@@ -23593,7 +23928,7 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
23593
23928
|
try {
|
|
23594
23929
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
23595
23930
|
} catch (e) {
|
|
23596
|
-
return failGraceful(`oauth plan: ${e.message}`);
|
|
23931
|
+
return failGraceful(`org oauth plan: ${e.message}`);
|
|
23597
23932
|
}
|
|
23598
23933
|
const origins = expectedJsOrigins(oc);
|
|
23599
23934
|
const redirects = expectedRedirectUris(oc);
|
|
@@ -23611,18 +23946,18 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
23611
23946
|
console.log(`
|
|
23612
23947
|
SSM cred params (under /mmi-future/${slug}/):`);
|
|
23613
23948
|
ssm.forEach((k) => console.log(` ${k}`));
|
|
23614
|
-
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`.");
|
|
23615
23950
|
});
|
|
23616
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) => {
|
|
23617
23952
|
const raw = await readStdin();
|
|
23618
23953
|
if (!raw.trim()) {
|
|
23619
|
-
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");
|
|
23620
23955
|
}
|
|
23621
23956
|
let creds;
|
|
23622
23957
|
try {
|
|
23623
23958
|
creds = parseOauthClientJson(raw);
|
|
23624
23959
|
} catch (e) {
|
|
23625
|
-
return fail(`oauth set-creds: ${e.message}`);
|
|
23960
|
+
return fail(`org oauth set-creds: ${e.message}`);
|
|
23626
23961
|
}
|
|
23627
23962
|
await withSecrets(async (d) => {
|
|
23628
23963
|
for (const key of oauthSsmKeys()) {
|
|
@@ -23632,7 +23967,7 @@ oauth.command("set-creds").description('store the OAuth client into the canonica
|
|
|
23632
23967
|
return;
|
|
23633
23968
|
}
|
|
23634
23969
|
}
|
|
23635
|
-
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.`);
|
|
23636
23971
|
});
|
|
23637
23972
|
});
|
|
23638
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) => {
|
|
@@ -23643,7 +23978,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23643
23978
|
try {
|
|
23644
23979
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
23645
23980
|
} catch (e) {
|
|
23646
|
-
return failGraceful(`oauth verify: ${e.message}`);
|
|
23981
|
+
return failGraceful(`org oauth verify: ${e.message}`);
|
|
23647
23982
|
}
|
|
23648
23983
|
let clientId = o.clientId;
|
|
23649
23984
|
if (!clientId) {
|
|
@@ -23652,7 +23987,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23652
23987
|
});
|
|
23653
23988
|
}
|
|
23654
23989
|
if (!clientId) {
|
|
23655
|
-
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)");
|
|
23656
23991
|
}
|
|
23657
23992
|
const redirectUri = probeRedirectUri(oc.callbackPath);
|
|
23658
23993
|
let body = "";
|
|
@@ -23660,7 +23995,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
23660
23995
|
const res = await fetch(buildAuthorizeProbeUrl(clientId, redirectUri), { redirect: "follow", signal: AbortSignal.timeout(1e4) });
|
|
23661
23996
|
body = await res.text();
|
|
23662
23997
|
} catch (e) {
|
|
23663
|
-
return failGraceful(`oauth verify: probe request failed: ${e.message}`);
|
|
23998
|
+
return failGraceful(`org oauth verify: probe request failed: ${e.message}`);
|
|
23664
23999
|
}
|
|
23665
24000
|
const mismatch = authorizeBodyHasMismatch(body);
|
|
23666
24001
|
if (o.json) {
|
|
@@ -24479,7 +24814,7 @@ function renderTrainApply(commandName, r) {
|
|
|
24479
24814
|
return r.announceNote ? `${base}; announce: ${r.announceNote}` : base;
|
|
24480
24815
|
}
|
|
24481
24816
|
function renderTenantRedeploy(r) {
|
|
24482
|
-
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)}`;
|
|
24483
24818
|
}
|
|
24484
24819
|
async function resolveRcandPlanTargets() {
|
|
24485
24820
|
try {
|
|
@@ -24619,7 +24954,7 @@ var access = program2.command("access").description("org access audit (read-only
|
|
|
24619
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) => {
|
|
24620
24955
|
const o = { json: rawFlag("--json") };
|
|
24621
24956
|
const repo = repoArg ?? await resolveRepo();
|
|
24622
|
-
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");
|
|
24623
24958
|
const cfg = await loadConfig();
|
|
24624
24959
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
|
|
24625
24960
|
if (!verdict.ok) {
|
|
@@ -24628,7 +24963,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
24628
24963
|
process.exitCode = 1;
|
|
24629
24964
|
return;
|
|
24630
24965
|
}
|
|
24631
|
-
return failGraceful(`access role: ${verdict.error}`);
|
|
24966
|
+
return failGraceful(`org access role: ${verdict.error}`);
|
|
24632
24967
|
}
|
|
24633
24968
|
const a = verdict.authority;
|
|
24634
24969
|
if (o.json) {
|
|
@@ -24646,7 +24981,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24646
24981
|
const cfg = await loadConfig();
|
|
24647
24982
|
const registryProjects = await fetchProjectsList(registryClientDeps(cfg));
|
|
24648
24983
|
if (o.repo) {
|
|
24649
|
-
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");
|
|
24650
24985
|
const meta = registryProjects?.find((project2) => (project2.repos ?? []).some((repo) => repo.toLowerCase() === o.repo.toLowerCase())) ?? null;
|
|
24651
24986
|
const repoClass = o.class;
|
|
24652
24987
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
@@ -24672,7 +25007,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24672
25007
|
});
|
|
24673
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)));
|
|
24674
25009
|
var isWin2 = process.platform === "win32";
|
|
24675
|
-
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) => {
|
|
24676
25011
|
if (opts.guide) {
|
|
24677
25012
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
24678
25013
|
return;
|
|
@@ -24702,11 +25037,11 @@ function directoryBytes(path2) {
|
|
|
24702
25037
|
return 0;
|
|
24703
25038
|
}
|
|
24704
25039
|
for (const entry of entries) {
|
|
24705
|
-
const
|
|
24706
|
-
if (entry.isDirectory()) total += directoryBytes(
|
|
25040
|
+
const child2 = (0, import_node_path23.join)(path2, entry.name);
|
|
25041
|
+
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
24707
25042
|
else {
|
|
24708
25043
|
try {
|
|
24709
|
-
total += (0, import_node_fs27.statSync)(
|
|
25044
|
+
total += (0, import_node_fs27.statSync)(child2).size;
|
|
24710
25045
|
} catch {
|
|
24711
25046
|
}
|
|
24712
25047
|
}
|
|
@@ -24773,7 +25108,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
24773
25108
|
});
|
|
24774
25109
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
24775
25110
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
24776
|
-
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.");
|
|
24777
25112
|
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
24778
25113
|
return;
|
|
24779
25114
|
}
|
|
@@ -24841,7 +25176,12 @@ program2.command("lane", { hidden: true }).action(() => {
|
|
|
24841
25176
|
program2.command("fusion", { hidden: true }).action(() => {
|
|
24842
25177
|
fail("fusion orchestration lives in jerv-cli \u2014 try: jerv-cli fusion --help");
|
|
24843
25178
|
});
|
|
24844
|
-
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));
|
|
24845
25185
|
// Annotate the CommonJS export names for ESM import in node:
|
|
24846
25186
|
0 && (module.exports = {
|
|
24847
25187
|
DEFAULT_PRIORITY,
|