@mutmutco/cli 3.30.1 → 3.32.0

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