@mutmutco/cli 3.31.0 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +10 -10
  2. package/dist/main.cjs +703 -363
  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
  }
@@ -9961,7 +9961,7 @@ async function auditRepoCi(repo, deps) {
9961
9961
  ok: false,
9962
9962
  label: "registry no-ci contradicts PR workflows",
9963
9963
  detail: `registry META declares no-ci but pull_request workflows emit [${emitted.join(", ")}]`,
9964
- remediation: `Fix the workflows or correct registry META: mmi-cli project set ${repo} --var requiredChecks=${JSON.stringify(emitted)}`
9964
+ remediation: `Fix the workflows or correct registry META: mmi-cli org project set ${repo} --var requiredChecks=${JSON.stringify(emitted)}`
9965
9965
  });
9966
9966
  }
9967
9967
  }
@@ -10022,7 +10022,7 @@ async function auditRepoCi(repo, deps) {
10022
10022
  ok: explicitNoCi,
10023
10023
  label: "registry META declares intentional no-ci",
10024
10024
  detail: explicitNoCi ? void 0 : "set ci:none and requiredChecks:[] in registry META",
10025
- remediation: `mmi-cli project set ${repo} --var ci=none --var requiredChecks=[]`
10025
+ remediation: `mmi-cli org project set ${repo} --var ci=none --var requiredChecks=[]`
10026
10026
  });
10027
10027
  } else if (deployableGated) {
10028
10028
  checks.push({
@@ -10060,7 +10060,7 @@ async function auditRepoCi(repo, deps) {
10060
10060
  ok: trackExpectsRc === rcPresent,
10061
10061
  label: "release track matches branch topology",
10062
10062
  detail: trackExpectsRc === rcPresent ? void 0 : `registry resolves '${effectiveTrack}' but the rc branch ${rcPresent ? "exists" : "is absent"} \u2014 set release track '${wantTrack}'`,
10063
- remediation: trackExpectsRc === rcPresent ? void 0 : `mmi-cli project set ${repo} --release-track ${wantTrack}`
10063
+ remediation: trackExpectsRc === rcPresent ? void 0 : `mmi-cli org project set ${repo} --release-track ${wantTrack}`
10064
10064
  });
10065
10065
  }
10066
10066
  }
@@ -10185,7 +10185,7 @@ async function seedGateYml(repo, deps, meta, result) {
10185
10185
  }
10186
10186
  const gate = meta?.gate;
10187
10187
  if (!gate || typeof gate === "object" && Object.keys(gate).length === 0) {
10188
- result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli project set ${repo} --var gate={...}\` first, then re-run reconcile`);
10188
+ result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli org project set ${repo} --var gate={...}\` first, then re-run reconcile`);
10189
10189
  return;
10190
10190
  }
10191
10191
  const derivedVars = withDerivedRepoVars({ ...gateConfigToVars(gate), REPO_SLUG: parsed.slug }, parsed, "deployable", releaseTrack);
@@ -10386,20 +10386,20 @@ var import_node_util6 = require("node:util");
10386
10386
  var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
10387
10387
  var DOCKER_TIMEOUT_MS = 15e3;
10388
10388
  var EARLY_EXIT_GRACE_MS = 2e3;
10389
- function waitForProcessStability(child, graceMs = EARLY_EXIT_GRACE_MS) {
10389
+ function waitForProcessStability(child2, graceMs = EARLY_EXIT_GRACE_MS) {
10390
10390
  return new Promise((resolve5, reject) => {
10391
10391
  let settled = false;
10392
10392
  const finish = (fn) => {
10393
10393
  if (settled) return;
10394
10394
  settled = true;
10395
10395
  clearTimeout(timer);
10396
- child.removeAllListeners("error");
10397
- child.removeAllListeners("exit");
10396
+ child2.removeAllListeners("error");
10397
+ child2.removeAllListeners("exit");
10398
10398
  fn();
10399
10399
  };
10400
10400
  const timer = setTimeout(() => finish(resolve5), graceMs);
10401
- child.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
10402
- 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) => {
10403
10403
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
10404
10404
  finish(() => reject(new Error(`stage process exited before health check (${detail})`)));
10405
10405
  });
@@ -10534,9 +10534,9 @@ function parseStagePortFlag(raw) {
10534
10534
  return port;
10535
10535
  }
10536
10536
  function pathUnder(childPath, parentPath) {
10537
- const child = normPath2(childPath);
10537
+ const child2 = normPath2(childPath);
10538
10538
  const parent = normPath2(parentPath);
10539
- return Boolean(child && parent && (child === parent || child.startsWith(`${parent}/`)));
10539
+ return Boolean(child2 && parent && (child2 === parent || child2.startsWith(`${parent}/`)));
10540
10540
  }
10541
10541
  function stageStateMatchesRequiredCwd(state, requiredCwd) {
10542
10542
  if (!state) return false;
@@ -10890,7 +10890,7 @@ async function startStage(config = {}, opts = {}) {
10890
10890
  let up = sub(config.up.trim());
10891
10891
  if (opts.forceRecreate) up = appendForceRecreate(up);
10892
10892
  const identity = await resolveStageIdentity(cwd);
10893
- const child = (0, import_node_child_process7.spawn)(up, {
10893
+ const child2 = (0, import_node_child_process7.spawn)(up, {
10894
10894
  cwd,
10895
10895
  shell: true,
10896
10896
  // POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
@@ -10903,7 +10903,7 @@ async function startStage(config = {}, opts = {}) {
10903
10903
  env: { ...process.env, ...vaultProcessEnv, ...stageProcessEnv(stagePort, extraEnv) }
10904
10904
  });
10905
10905
  const state = {
10906
- pid: child.pid ?? 0,
10906
+ pid: child2.pid ?? 0,
10907
10907
  command: up,
10908
10908
  cwd,
10909
10909
  statePath,
@@ -10917,7 +10917,7 @@ async function startStage(config = {}, opts = {}) {
10917
10917
  if (globalStatePath && globalStatePath !== statePath) writeState(globalStatePath, state);
10918
10918
  try {
10919
10919
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
10920
- else await waitForProcessStability(child);
10920
+ else await waitForProcessStability(child2);
10921
10921
  } catch (e) {
10922
10922
  await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd);
10923
10923
  throw e;
@@ -10932,7 +10932,7 @@ async function startStage(config = {}, opts = {}) {
10932
10932
  message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}`
10933
10933
  };
10934
10934
  opts.onReady?.(result);
10935
- child.unref();
10935
+ child2.unref();
10936
10936
  return result;
10937
10937
  }
10938
10938
  async function runStage(config = {}, opts = {}) {
@@ -11149,7 +11149,7 @@ async function bodyArgsViaFile(args, deps = {}) {
11149
11149
  if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
11150
11150
  } };
11151
11151
  const write = deps.write ?? import_promises.writeFile;
11152
- const remove = deps.remove ?? import_promises.unlink;
11152
+ const remove2 = deps.remove ?? import_promises.unlink;
11153
11153
  const ensureDir = deps.ensureDir ?? import_promises.mkdir;
11154
11154
  const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
11155
11155
  const file = (0, import_node_path13.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
@@ -11160,7 +11160,7 @@ async function bodyArgsViaFile(args, deps = {}) {
11160
11160
  args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
11161
11161
  cleanup: async () => {
11162
11162
  try {
11163
- await remove(file);
11163
+ await remove2(file);
11164
11164
  } catch {
11165
11165
  }
11166
11166
  }
@@ -11375,9 +11375,9 @@ async function resolveIssueNodeId(runGh, ref, fallbackRepo) {
11375
11375
  }
11376
11376
  async function linkSubIssue(runGh, parentRef, childRef, defaultRepo) {
11377
11377
  const parent = parseIssueRef(parentRef);
11378
- const child = parseIssueRef(childRef);
11378
+ const child2 = parseIssueRef(childRef);
11379
11379
  const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
11380
- const subIssueId = await resolveIssueNodeId(runGh, child, defaultRepo);
11380
+ const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
11381
11381
  const stdout = await runGh(buildAddSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
11382
11382
  const result = parseAddSubIssueResult(stdout);
11383
11383
  if (!result) throw new Error(`addSubIssue returned an unexpected response:
@@ -11412,9 +11412,9 @@ function parseRemoveSubIssueResult(stdout) {
11412
11412
  }
11413
11413
  async function unlinkSubIssue(runGh, parentRef, childRef, defaultRepo) {
11414
11414
  const parent = parseIssueRef(parentRef);
11415
- const child = parseIssueRef(childRef);
11415
+ const child2 = parseIssueRef(childRef);
11416
11416
  const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
11417
- const subIssueId = await resolveIssueNodeId(runGh, child, defaultRepo);
11417
+ const subIssueId = await resolveIssueNodeId(runGh, child2, defaultRepo);
11418
11418
  const stdout = await runGh(buildRemoveSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
11419
11419
  const result = parseRemoveSubIssueResult(stdout);
11420
11420
  if (!result) throw new Error(`removeSubIssue returned an unexpected response:
@@ -11485,6 +11485,235 @@ function applyChecklistCheck(body, query, checked) {
11485
11485
  return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
11486
11486
  }
11487
11487
 
11488
+ // src/command-taxonomy.ts
11489
+ var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
11490
+ var PRIMARY_GROUPS = [
11491
+ ["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
11492
+ ["Plan and work", ["board", "issue", "worktree", "stage"]],
11493
+ ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
11494
+ ["Setup and support", ["bootstrap", "secrets", "docs"]],
11495
+ ["Coordinate and improve", ["wave", "report", "skill-lesson"]]
11496
+ ];
11497
+ var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
11498
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "wave", "report", "skill-lesson"]);
11499
+ var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
11500
+ var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
11501
+ var topLevelPosition = 0;
11502
+ for (const [helpGroup, names] of PRIMARY_GROUPS) {
11503
+ HELP_GROUP_ORDER.set(helpGroup, HELP_GROUP_ORDER.size);
11504
+ for (const name of names) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
11505
+ }
11506
+ HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
11507
+ HELP_GROUP_ORDER.set("Compatibility", HELP_GROUP_ORDER.size);
11508
+ HELP_GROUP_ORDER.set("Delegated", HELP_GROUP_ORDER.size);
11509
+ for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
11510
+ TOP_LEVEL_ORDER.set("lane", topLevelPosition++);
11511
+ TOP_LEVEL_ORDER.set("fusion", topLevelPosition++);
11512
+ var PATH_OVERRIDES = {
11513
+ "board slice-refresh": { category: "internal", discovery: "hidden", help_group: "Operations" },
11514
+ lane: { category: "delegated", discovery: "hidden", help_group: "Delegated", replacement: "jerv-cli lane" },
11515
+ fusion: { category: "delegated", discovery: "hidden", help_group: "Delegated", replacement: "jerv-cli fusion" }
11516
+ };
11517
+ var HIDDEN_OPTIONS = {
11518
+ "worktree create": ["--base"],
11519
+ "org project doctor": ["--v2"],
11520
+ "org project heal": ["--v2"],
11521
+ "org project set": ["--set"]
11522
+ };
11523
+ function primaryMetadata(name) {
11524
+ for (const [helpGroup, names] of PRIMARY_GROUPS) {
11525
+ if (names.includes(name)) {
11526
+ return {
11527
+ category: SUPPORT_PRIMARY.has(name) ? "support" : "core",
11528
+ discovery: "primary",
11529
+ help_group: helpGroup
11530
+ };
11531
+ }
11532
+ }
11533
+ return void 0;
11534
+ }
11535
+ function topLevelMetadata(name) {
11536
+ const primary = primaryMetadata(name);
11537
+ if (primary) return primary;
11538
+ if (name === "lane" || name === "fusion") return PATH_OVERRIDES[name];
11539
+ if (!OPERATIONAL_TOP_LEVEL.has(name)) {
11540
+ throw new Error(`command taxonomy: unclassified top-level command "${name}"`);
11541
+ }
11542
+ return {
11543
+ category: name === "plugin" ? "internal" : "admin",
11544
+ discovery: "all-only",
11545
+ help_group: "Operations"
11546
+ };
11547
+ }
11548
+ function setCommandMetadata(command, metadata) {
11549
+ command[COMMAND_METADATA] = metadata;
11550
+ }
11551
+ function commandMetadata(command) {
11552
+ return command[COMMAND_METADATA];
11553
+ }
11554
+ function classifyTree(command, path2, inherited) {
11555
+ const metadata = PATH_OVERRIDES[path2] ?? inherited;
11556
+ setCommandMetadata(command, metadata);
11557
+ if (metadata.discovery !== "primary") {
11558
+ command._hidden = true;
11559
+ }
11560
+ for (const option of command.options) {
11561
+ if ((HIDDEN_OPTIONS[path2] ?? []).includes(option.long ?? option.short ?? "")) option.hideHelp();
11562
+ }
11563
+ for (const child2 of command.commands) {
11564
+ const childPath = path2 ? `${path2} ${child2.name()}` : child2.name();
11565
+ classifyTree(child2, childPath, PATH_OVERRIDES[childPath] ?? metadata);
11566
+ }
11567
+ }
11568
+ function applyCommandTaxonomy(program3) {
11569
+ for (const command of program3.commands) {
11570
+ const metadata = topLevelMetadata(command.name());
11571
+ command.helpGroup(metadata.help_group);
11572
+ classifyTree(command, command.name(), metadata);
11573
+ }
11574
+ program3.configureHelp({
11575
+ visibleCommands(command) {
11576
+ const visible = command.commands.filter((child2) => !child2._hidden);
11577
+ const helpCommand = command._getHelpCommand();
11578
+ if (helpCommand && !helpCommand._hidden) visible.push(helpCommand);
11579
+ if (command === program3) visible.sort((a, b) => commandTaxonomyRank(a.name()) - commandTaxonomyRank(b.name()));
11580
+ return visible;
11581
+ },
11582
+ groupItems(unsortedItems, visibleItems, getGroup) {
11583
+ const groups = /* @__PURE__ */ new Map();
11584
+ for (const item of unsortedItems) {
11585
+ const group = getGroup(item);
11586
+ if (!groups.has(group)) groups.set(group, []);
11587
+ }
11588
+ for (const item of visibleItems) {
11589
+ const group = getGroup(item);
11590
+ if (!groups.has(group)) groups.set(group, []);
11591
+ groups.get(group).push(item);
11592
+ }
11593
+ return new Map([...groups].sort(([left], [right]) => {
11594
+ const leftRank = HELP_GROUP_ORDER.get(left) ?? Number.MAX_SAFE_INTEGER;
11595
+ const rightRank = HELP_GROUP_ORDER.get(right) ?? Number.MAX_SAFE_INTEGER;
11596
+ return leftRank - rightRank;
11597
+ }));
11598
+ }
11599
+ });
11600
+ }
11601
+ function commandTaxonomyRank(name) {
11602
+ return TOP_LEVEL_ORDER.get(name) ?? Number.MAX_SAFE_INTEGER;
11603
+ }
11604
+ function commandHelpGroupRank(group) {
11605
+ return HELP_GROUP_ORDER.get(group) ?? Number.MAX_SAFE_INTEGER;
11606
+ }
11607
+ function isCanonicalSuggestion(metadata) {
11608
+ return metadata.category !== "internal" && metadata.category !== "delegated" && metadata.category !== "shim" && metadata.category !== "obsolete";
11609
+ }
11610
+
11611
+ // src/command-shims.ts
11612
+ var COMMAND_SHIMS = [
11613
+ { legacy: "project deploy list", canonical: "org project deploy get", category: "shim" },
11614
+ { legacy: "project resolve", canonical: "org project deploy get", category: "obsolete" },
11615
+ { legacy: "tenant readiness", canonical: "runtime tenant status", category: "shim" },
11616
+ { legacy: "secrets edit", canonical: "secrets set", category: "shim" },
11617
+ { legacy: "registry org", canonical: "org config get", category: "shim" },
11618
+ { legacy: "rules gitignore", canonical: "org rules gitignore", category: "shim" },
11619
+ { legacy: "deploy status", canonical: "runtime deploy status", category: "shim" },
11620
+ { legacy: "full-track readiness", canonical: "train readiness", category: "shim" },
11621
+ { legacy: "docs-audit", canonical: "docs audit", category: "shim", subtree: true },
11622
+ { legacy: "plugin-heal", canonical: "plugin heal", category: "shim" },
11623
+ { legacy: "plugin-prune", canonical: "plugin prune", category: "shim" },
11624
+ { legacy: "session-start", canonical: "plugin session-start", category: "shim" },
11625
+ { legacy: "port-range", canonical: "stage port-range", category: "shim" },
11626
+ { legacy: "schedules", canonical: "org schedules", category: "shim" },
11627
+ { legacy: "guard", canonical: "plugin guard", category: "shim" },
11628
+ { legacy: "project", canonical: "org project", category: "shim", subtree: true },
11629
+ { legacy: "oauth", canonical: "org oauth", category: "shim", subtree: true },
11630
+ { legacy: "access", canonical: "org access", category: "shim", subtree: true },
11631
+ { legacy: "tenant", canonical: "runtime tenant", category: "shim", subtree: true },
11632
+ { legacy: "box", canonical: "runtime box", category: "shim", subtree: true },
11633
+ { legacy: "edge", canonical: "runtime edge", category: "shim", subtree: true },
11634
+ { legacy: "gc", canonical: "worktree gc", category: "shim", subtree: true }
11635
+ ];
11636
+ function rewriteLegacyArgv(argv) {
11637
+ const commandArgs = argv.slice(2);
11638
+ for (const shim of COMMAND_SHIMS) {
11639
+ const legacy = shim.legacy.split(" ");
11640
+ if (!legacy.every((token, index) => commandArgs[index] === token)) continue;
11641
+ const canonical = shim.canonical.split(" ");
11642
+ return {
11643
+ argv: [...argv.slice(0, 2), ...canonical, ...commandArgs.slice(legacy.length)],
11644
+ shim,
11645
+ warning: `mmi-cli: \`${shim.legacy}\` moved to \`${shim.canonical}\`; use \`mmi-cli ${shim.canonical}\`.`
11646
+ };
11647
+ }
11648
+ return { argv: [...argv] };
11649
+ }
11650
+
11651
+ // src/command-consolidation.ts
11652
+ var COMMAND_NAMESPACES_CONSOLIDATED = /* @__PURE__ */ Symbol.for("mmi.commandNamespaces.consolidated");
11653
+ function child(parent, name) {
11654
+ const found = parent.commands.find((command) => command.name() === name);
11655
+ if (!found) throw new Error(`command consolidation: missing "${parent.name()} ${name}"`);
11656
+ return found;
11657
+ }
11658
+ function detach(parent, command) {
11659
+ const commands = parent.commands;
11660
+ const index = commands.indexOf(command);
11661
+ if (index < 0) throw new Error(`command consolidation: "${command.name()}" is not a child of "${parent.name()}"`);
11662
+ commands.splice(index, 1);
11663
+ return command;
11664
+ }
11665
+ function move(parent, destination, name, nextName = name) {
11666
+ const command = detach(parent, child(parent, name));
11667
+ command.name(nextName);
11668
+ destination.addCommand(command);
11669
+ return command;
11670
+ }
11671
+ function remove(parent, name) {
11672
+ detach(parent, child(parent, name));
11673
+ }
11674
+ function consolidateCommandNamespaces(program3) {
11675
+ const org = program3.command("org").description("organization projects, access, rules, credentials, and schedules");
11676
+ const runtime = program3.command("runtime").description("tenant, deploy, box, and edge operations");
11677
+ const plugin = program3.command("plugin").description("plugin lifecycle and guard operations");
11678
+ move(program3, org, "project");
11679
+ move(program3, org, "oauth");
11680
+ move(program3, org, "access");
11681
+ move(program3, org, "schedules");
11682
+ const config = org.command("config").description("organization configuration");
11683
+ const registry2 = child(program3, "registry");
11684
+ move(registry2, config, "org", "get");
11685
+ remove(program3, "registry");
11686
+ const orgRules = org.command("rules").description("organization-managed repository rules");
11687
+ const rules2 = child(program3, "rules");
11688
+ move(rules2, orgRules, "gitignore");
11689
+ remove(program3, "rules");
11690
+ move(program3, runtime, "tenant");
11691
+ const runtimeDeploy = runtime.command("deploy").description("runtime deployment status");
11692
+ const deploy = child(program3, "deploy");
11693
+ move(deploy, runtimeDeploy, "status");
11694
+ remove(program3, "deploy");
11695
+ move(program3, runtime, "box");
11696
+ move(program3, runtime, "edge");
11697
+ move(program3, plugin, "guard");
11698
+ move(program3, plugin, "plugin-heal", "heal");
11699
+ move(program3, plugin, "plugin-prune", "prune");
11700
+ move(program3, plugin, "session-start");
11701
+ const docs2 = child(program3, "docs");
11702
+ move(program3, docs2, "docs-audit", "audit");
11703
+ const train = child(program3, "train");
11704
+ const fullTrack2 = child(program3, "full-track");
11705
+ move(fullTrack2, train, "readiness");
11706
+ remove(program3, "full-track");
11707
+ const worktree2 = child(program3, "worktree");
11708
+ move(program3, worktree2, "gc");
11709
+ const stage = child(program3, "stage");
11710
+ move(program3, stage, "port-range");
11711
+ program3[COMMAND_NAMESPACES_CONSOLIDATED] = true;
11712
+ }
11713
+ function hasConsolidatedCommandNamespaces(program3) {
11714
+ return program3[COMMAND_NAMESPACES_CONSOLIDATED] === true;
11715
+ }
11716
+
11488
11717
  // src/command-manifest.ts
11489
11718
  var EXAMPLES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.examples");
11490
11719
  var COMMON_MISTAKES = /* @__PURE__ */ Symbol.for("mmi.commandManifest.commonMistakes");
@@ -11524,17 +11753,24 @@ function buildOption(opt) {
11524
11753
  if (opt.description) out.description = opt.description;
11525
11754
  if (opt.defaultValue !== void 0) out.default = opt.defaultValue;
11526
11755
  if (opt.argChoices && opt.argChoices.length) out.enum = [...opt.argChoices];
11756
+ if (opt.hidden) out.discovery = "all-only";
11527
11757
  return out;
11528
11758
  }
11529
11759
  function buildCommand(cmd, path2) {
11760
+ const metadata = commandMetadata(cmd) ?? {
11761
+ category: "core",
11762
+ discovery: "primary",
11763
+ help_group: "Plan and work"
11764
+ };
11530
11765
  const out = {
11531
11766
  name: cmd.name(),
11532
11767
  path: path2,
11533
11768
  arguments: cmd.registeredArguments.map(buildArgument),
11534
11769
  options: cmd.options.map(buildOption),
11535
11770
  subcommands: cmd.commands.map(
11536
- (child) => buildCommand(child, path2 ? `${path2} ${child.name()}` : child.name())
11537
- )
11771
+ (child2) => buildCommand(child2, path2 ? `${path2} ${child2.name()}` : child2.name())
11772
+ ),
11773
+ ...metadata
11538
11774
  };
11539
11775
  const description = cmd.description();
11540
11776
  if (description) out.description = description;
@@ -11544,39 +11780,140 @@ function buildCommand(cmd, path2) {
11544
11780
  if (commonMistakes) out.common_mistakes = commonMistakes;
11545
11781
  return out;
11546
11782
  }
11783
+ function findTreeCommand(root, path2) {
11784
+ if (root.path === path2) return root;
11785
+ for (const child2 of root.subcommands) {
11786
+ const found = findTreeCommand(child2, path2);
11787
+ if (found) return found;
11788
+ }
11789
+ return void 0;
11790
+ }
11791
+ function virtualCopy(source, path2, shim) {
11792
+ return {
11793
+ ...source,
11794
+ name: path2.split(" ").at(-1),
11795
+ path: path2,
11796
+ category: shim.category,
11797
+ discovery: "all-only",
11798
+ help_group: "Compatibility",
11799
+ replacement: source.path,
11800
+ subcommands: shim.subtree ? source.subcommands.map((child2) => virtualCopy(child2, `${path2} ${child2.name}`, shim)) : []
11801
+ };
11802
+ }
11803
+ function virtualParent(path2, shim) {
11804
+ return {
11805
+ name: path2.split(" ").at(-1),
11806
+ path: path2,
11807
+ description: `compatibility namespace; use mmi-cli ${shim.canonical}`,
11808
+ arguments: [],
11809
+ options: [],
11810
+ subcommands: [],
11811
+ category: "shim",
11812
+ discovery: "all-only",
11813
+ help_group: "Compatibility",
11814
+ replacement: shim.canonical
11815
+ };
11816
+ }
11817
+ function insertVirtualShim(root, shim) {
11818
+ const canonical = findTreeCommand(root, shim.canonical);
11819
+ if (!canonical) throw new Error(`command manifest: shim target missing "${shim.canonical}"`);
11820
+ const parts = shim.legacy.split(" ");
11821
+ let parent = root;
11822
+ for (let index = 0; index < parts.length - 1; index += 1) {
11823
+ const path2 = parts.slice(0, index + 1).join(" ");
11824
+ let next = parent.subcommands.find((command) => command.name === parts[index]);
11825
+ if (!next) {
11826
+ next = virtualParent(path2, shim);
11827
+ parent.subcommands.push(next);
11828
+ }
11829
+ parent = next;
11830
+ }
11831
+ const copy = virtualCopy(canonical, shim.legacy, shim);
11832
+ const existingIndex = parent.subcommands.findIndex((command) => command.name === copy.name);
11833
+ if (existingIndex >= 0) parent.subcommands[existingIndex] = copy;
11834
+ else parent.subcommands.push(copy);
11835
+ }
11836
+ function addVirtualShims(root) {
11837
+ for (const shim of COMMAND_SHIMS.filter((entry) => entry.subtree)) insertVirtualShim(root, shim);
11838
+ for (const shim of COMMAND_SHIMS.filter((entry) => !entry.subtree)) insertVirtualShim(root, shim);
11839
+ }
11840
+ function primaryCopy(node, root = false) {
11841
+ if (!root && node.discovery !== "primary") return void 0;
11842
+ const subcommands = node.subcommands.map((child2) => primaryCopy(child2)).filter((child2) => Boolean(child2));
11843
+ if (root) subcommands.sort((a, b) => commandTaxonomyRank(a.name) - commandTaxonomyRank(b.name));
11844
+ return {
11845
+ ...node,
11846
+ options: node.options.filter((option) => option.discovery !== "all-only" && option.discovery !== "hidden"),
11847
+ subcommands
11848
+ };
11849
+ }
11547
11850
  function collectLeaves(node, acc) {
11548
11851
  if (node.subcommands.length === 0) {
11549
11852
  if (node.path) acc.push(node);
11550
11853
  return;
11551
11854
  }
11552
- for (const child of node.subcommands) collectLeaves(child, acc);
11855
+ for (const child2 of node.subcommands) collectLeaves(child2, acc);
11553
11856
  }
11554
11857
  function buildCommandManifest(program3) {
11555
11858
  const tree = buildCommand(program3, "");
11859
+ if (hasConsolidatedCommandNamespaces(program3)) addVirtualShims(tree);
11556
11860
  const index = [];
11557
11861
  collectLeaves(tree, index);
11558
- const 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
+ };
11559
11875
  const version = program3.version();
11560
11876
  if (version) manifest.version = version;
11561
11877
  return manifest;
11562
11878
  }
11879
+ function primaryCommandManifest(manifest) {
11880
+ return {
11881
+ schema_version: manifest.schema_version,
11882
+ scope: "primary",
11883
+ name: manifest.name,
11884
+ ...manifest.version ? { version: manifest.version } : {},
11885
+ tree: manifest.primary_tree,
11886
+ index: manifest.primary_index,
11887
+ error_codes: manifest.error_codes
11888
+ };
11889
+ }
11563
11890
  function argSignature(arg) {
11564
11891
  const inner = arg.variadic ? `${arg.name}...` : arg.name;
11565
11892
  return arg.required ? `<${inner}>` : `[${inner}]`;
11566
11893
  }
11567
- function formatManifestHuman(manifest) {
11568
- const 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 ?? ""}`];
11569
11897
  const render = (node, depth) => {
11570
11898
  const indent = " ".repeat(depth);
11571
11899
  const args = node.arguments.map(argSignature).join(" ");
11572
- const head = depth === 0 ? `${node.name}${manifest.version ? ` v${manifest.version}` : ""}` : node.name + (args ? ` ${args}` : "");
11900
+ const head = node.name + (args ? ` ${args}` : "");
11573
11901
  lines.push(node.description ? `${indent}${head} \u2014 ${node.description}` : `${indent}${head}`);
11574
- for (const opt of node.options) {
11902
+ for (const opt of options.all ? node.options : []) {
11575
11903
  lines.push(`${indent} ${opt.flags}${opt.description ? ` ${opt.description}` : ""}`);
11576
11904
  }
11577
- for (const child of node.subcommands) render(child, depth + 1);
11905
+ if (options.all) for (const child2 of node.subcommands) render(child2, depth + 1);
11578
11906
  };
11579
- 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.");
11580
11917
  return lines.join("\n");
11581
11918
  }
11582
11919
 
@@ -11722,12 +12059,12 @@ function newestMtimeMs(path2, listDir, mtimeOf) {
11722
12059
  return newest;
11723
12060
  }
11724
12061
  for (const e of entries) {
11725
- const child = `${path2}/${e.name}`;
12062
+ const child2 = `${path2}/${e.name}`;
11726
12063
  let m = 0;
11727
- if (e.isDirectory) m = newestMtimeMs(child, listDir, mtimeOf);
12064
+ if (e.isDirectory) m = newestMtimeMs(child2, listDir, mtimeOf);
11728
12065
  else {
11729
12066
  try {
11730
- m = mtimeOf(child);
12067
+ m = mtimeOf(child2);
11731
12068
  } catch {
11732
12069
  m = 0;
11733
12070
  }
@@ -11803,12 +12140,12 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
11803
12140
  const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
11804
12141
  return { cacheRoot, present: true, keep, prune, bytes, running, ...stagingFields };
11805
12142
  }
11806
- function applyPluginCachePlan(plan, remove, stagingGuard) {
12143
+ function applyPluginCachePlan(plan, remove2, stagingGuard) {
11807
12144
  const removed = [];
11808
12145
  const failed = [];
11809
12146
  for (const version of plan.prune) {
11810
12147
  try {
11811
- remove(`${plan.cacheRoot}/${version}`);
12148
+ remove2(`${plan.cacheRoot}/${version}`);
11812
12149
  removed.push(version);
11813
12150
  } catch (e) {
11814
12151
  failed.push({ version, error: e.message });
@@ -11836,7 +12173,7 @@ function applyPluginCachePlan(plan, remove, stagingGuard) {
11836
12173
  }
11837
12174
  }
11838
12175
  try {
11839
- remove(`${plan.stagingRoot}/${s.name}`);
12176
+ remove2(`${plan.stagingRoot}/${s.name}`);
11840
12177
  removedStaging.push(s.name);
11841
12178
  } catch (e) {
11842
12179
  failedStaging.push({ name: s.name, error: e.message });
@@ -11856,7 +12193,7 @@ var CONCURRENT_SESSION_WARNING = "only the version THIS session runs from is pro
11856
12193
  function renderPluginCachePlan(plan, applied) {
11857
12194
  const mb = (b) => `${(b / 1e6).toFixed(1)} MB`;
11858
12195
  if (!plan.present && plan.staging.length === 0) {
11859
- return `plugin-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`;
11860
12197
  }
11861
12198
  const lines = [];
11862
12199
  if (plan.present) {
@@ -12005,11 +12342,11 @@ function trainPlan(command, options = {}) {
12005
12342
  { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
12006
12343
  { label: "verify current branch is development", gated: true },
12007
12344
  rcandVersionStep(options),
12008
- { label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
12345
+ { label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
12009
12346
  { label: "preflight required rc secret names", command: "mmi-cli secrets preflight --stage rc --repo <owner/repo>", gated: true },
12010
12347
  { label: "merge development to rc", gated: true },
12011
12348
  { label: "trigger the deploy path for this repo model, returning Hub Actions run id/url data (and, with --watch, its outcome)", command: "tenant-container: gh workflow run tenant-deploy.yml ... then gh run list/watch; hub-serverless: no manual dispatch, deploy.yml auto-fires on rc push, correlate/watch that run", gated: true },
12012
- { label: "after a failed deploy, retry the existing rc ref (no re-tag/merge)", command: "mmi-cli tenant redeploy <owner/repo> rc --watch", gated: true }
12349
+ { label: "after a failed deploy, retry the existing rc ref (no re-tag/merge)", command: "mmi-cli runtime tenant redeploy <owner/repo> rc --watch", gated: true }
12013
12350
  ];
12014
12351
  }
12015
12352
  if (command === "release") {
@@ -12017,7 +12354,7 @@ function trainPlan(command, options = {}) {
12017
12354
  return [
12018
12355
  { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
12019
12356
  { label: "verify current branch is development", gated: true },
12020
- { label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
12357
+ { label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
12021
12358
  { label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
12022
12359
  { label: "merge development to main", gated: true },
12023
12360
  { label: "fold the version bump into the release commit (Hub: full distribution set; app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
@@ -12031,20 +12368,20 @@ function trainPlan(command, options = {}) {
12031
12368
  { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
12032
12369
  { label: "verify current branch is development", gated: true },
12033
12370
  { label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
12034
- { label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
12371
+ { label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
12035
12372
  { label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
12036
12373
  { label: "merge development to main (rc skipped)", gated: true },
12037
12374
  { label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
12038
12375
  { label: "tag release and publish GitHub Release", gated: true },
12039
12376
  { label: "trigger the deploy path for this repo model, returning Hub Actions run id/url data (and, with --watch, its outcome)", command: "tenant-container: gh workflow run tenant-deploy.yml ... then gh run list/watch", gated: true },
12040
- { label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli tenant control <owner/repo> rc retire", gated: true },
12377
+ { label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli runtime tenant control <owner/repo> rc retire", gated: true },
12041
12378
  { label: "roll development forward and align rc to the released main", gated: true }
12042
12379
  ];
12043
12380
  }
12044
12381
  return [
12045
12382
  { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
12046
12383
  { label: "verify current branch is rc", gated: true },
12047
- { label: "verify registry META for this project", command: "mmi-cli project get <owner/repo>", gated: true },
12384
+ { label: "verify registry META for this project", command: "mmi-cli org project get <owner/repo>", gated: true },
12048
12385
  { label: "preflight required main secret names", command: "mmi-cli secrets preflight --stage main --repo <owner/repo>", gated: true },
12049
12386
  { label: "verify every main-only hotfix commit is covered by the rc candidate (the guard runs automatically inside the apply step below; --ack <sha> overrides a verified, trailer-less port)", command: "mmi-cli release --apply [--ack <sha>]", gated: true },
12050
12387
  { label: "merge rc to main", gated: true },
@@ -12415,7 +12752,7 @@ function requireProjectMetaForTrain(load, repo) {
12415
12752
  }
12416
12753
  if (load.status === "missing") {
12417
12754
  throw new Error(
12418
- `${label}: no registry META for ${repo} \u2014 train apply refused; bootstrap the project or verify META with \`mmi-cli project get\` before promoting.`
12755
+ `${label}: no registry META for ${repo} \u2014 train apply refused; bootstrap the project or verify META with \`mmi-cli org project get\` before promoting.`
12419
12756
  );
12420
12757
  }
12421
12758
  return load.meta;
@@ -12729,7 +13066,7 @@ function partialTrainRecoveryError(cause, input) {
12729
13066
  partial train state: tag ${input.tag} is already pushed; origin/${branch} has not been pushed; ${releaseState}; deploy not dispatched.
12730
13067
  Recovery sequence:
12731
13068
  1. git push origin ${branch}` + releaseCommand + `
12732
- ${deployStep}. mmi-cli tenant redeploy ${input.repo} ${input.stage} --watch
13069
+ ${deployStep}. mmi-cli runtime tenant redeploy ${input.repo} ${input.stage} --watch
12733
13070
  Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
12734
13071
  );
12735
13072
  }
@@ -12911,7 +13248,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
12911
13248
  if (dispatchFailure === "throw") throw e;
12912
13249
  const msg = e instanceof Error ? e.message : String(e);
12913
13250
  return {
12914
- note: `tenant-deploy dispatch FAILED: ${msg}. The promotion itself landed (merge/tag/Release pushed before the dispatch) \u2014 recover the deploy with \`mmi-cli tenant redeploy ${ctx.repo} ${stage}\`; never re-tag.`,
13251
+ note: `tenant-deploy dispatch FAILED: ${msg}. The promotion itself landed (merge/tag/Release pushed before the dispatch) \u2014 recover the deploy with \`mmi-cli runtime tenant redeploy ${ctx.repo} ${stage}\`; never re-tag.`,
12915
13252
  deployStatus: "failure"
12916
13253
  };
12917
13254
  }
@@ -13498,14 +13835,14 @@ async function attemptRetire(deps, ctx) {
13498
13835
  return {
13499
13836
  status: "retired",
13500
13837
  category: "retired",
13501
- note: `rc runtime retired (tenant-control.yml${r.runUrl ? `, ${r.runUrl}` : ""}) \u2014 registry coords kept; /rcand or tenant redeploy recreates rc next cycle`
13838
+ note: `rc runtime retired (tenant-control.yml${r.runUrl ? `, ${r.runUrl}` : ""}) \u2014 registry coords kept; /rcand or runtime tenant redeploy recreates rc next cycle`
13502
13839
  };
13503
13840
  }
13504
13841
  if (r.category === "wait-timeout") {
13505
13842
  return {
13506
13843
  status: "failed",
13507
13844
  category: "wait-timeout",
13508
- note: `rc retire dispatched but the run could not be observed \u2014 verify with: mmi-cli tenant control ${ctx.repo} rc status${r.runUrl ? ` (run ${r.runUrl})` : ""}`
13845
+ note: `rc retire dispatched but the run could not be observed \u2014 verify with: mmi-cli runtime tenant control ${ctx.repo} rc status${r.runUrl ? ` (run ${r.runUrl})` : ""}`
13509
13846
  };
13510
13847
  }
13511
13848
  return { status: "failed", category: r.category ?? "transport-failed", note: r.note };
@@ -13520,7 +13857,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
13520
13857
  if (deployStatus !== "success") {
13521
13858
  return {
13522
13859
  status: "skipped",
13523
- note: `prod deploy outcome unconfirmed (run without --watch) \u2014 rc runtime left untouched; after verifying prod, retire with: mmi-cli tenant control ${ctx.repo} rc retire`
13860
+ note: `prod deploy outcome unconfirmed (run without --watch) \u2014 rc runtime left untouched; after verifying prod, retire with: mmi-cli runtime tenant control ${ctx.repo} rc retire`
13524
13861
  };
13525
13862
  }
13526
13863
  try {
@@ -13542,7 +13879,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
13542
13879
  }
13543
13880
  const f = last;
13544
13881
  if (f.category === "wait-timeout") return f;
13545
- const note = `rc retirement failed (the release itself succeeded): ${f.note}. The rc runtime may be orphaned on the box \u2014 retire it with: mmi-cli tenant control ${ctx.repo} rc retire (or sweep all: mmi-cli tenant sweep-rc --retire --yes)`;
13882
+ const note = `rc retirement failed (the release itself succeeded): ${f.note}. The rc runtime may be orphaned on the box \u2014 retire it with: mmi-cli runtime tenant control ${ctx.repo} rc retire (or sweep all: mmi-cli runtime tenant sweep-rc --retire --yes)`;
13546
13883
  return { status: "failed", category: f.category, note };
13547
13884
  } catch (e) {
13548
13885
  const err = e;
@@ -13585,7 +13922,7 @@ async function runTenantControl(deps, options) {
13585
13922
  dispatched: false,
13586
13923
  conclusion: "failure",
13587
13924
  category: action === "retire" ? transport ? "transport-failed" : "dispatch-rejected" : void 0,
13588
- note: transport ? `tenant control ${action} dispatch failed (transport) \u2014 safe to retry` : `tenant control ${action} rejected: ${d.error ?? "request rejected by the Hub"}`
13925
+ note: transport ? `runtime tenant control ${action} dispatch failed (transport) \u2014 safe to retry` : `runtime tenant control ${action} rejected: ${d.error ?? "request rejected by the Hub"}`
13589
13926
  };
13590
13927
  }
13591
13928
  const { runId, runUrl } = await correlateControlRun(deps, since, [stage, action]);
@@ -13607,7 +13944,7 @@ async function runTenantControl(deps, options) {
13607
13944
  return result;
13608
13945
  }
13609
13946
  function renderTenantControl(r) {
13610
- const head = `tenant control ${r.repo} ${r.stage} ${r.action}: ${r.conclusion}${r.category ? ` (${r.category})` : ""}`;
13947
+ const head = `runtime tenant control ${r.repo} ${r.stage} ${r.action}: ${r.conclusion}${r.category ? ` (${r.category})` : ""}`;
13611
13948
  const lines = [head];
13612
13949
  if (r.runUrl) lines.push(` run: ${r.runUrl}`);
13613
13950
  if (r.serviceState) lines.push(` serviceState: ${r.serviceState}`);
@@ -13768,7 +14105,7 @@ function pickRepo(p) {
13768
14105
  }
13769
14106
  async function sweepRcOrphans(deps, opts) {
13770
14107
  const projects = await deps.listProjects();
13771
- if (!projects) throw new Error("project list unavailable \u2014 Hub unreachable or this repo is not bootstrapped");
14108
+ if (!projects) throw new Error("org project list unavailable \u2014 Hub unreachable or this repo is not bootstrapped");
13772
14109
  const targets = projects.filter(isRcBearingTenant);
13773
14110
  const stages = [];
13774
14111
  for (const p of targets) {
@@ -13805,7 +14142,7 @@ async function sweepRcOrphans(deps, opts) {
13805
14142
  };
13806
14143
  }
13807
14144
  function renderSweep(r) {
13808
- const lines = [`tenant sweep-rc: scanned ${r.scanned} tenant-container(s); ${r.running} rc runtime(s) running`];
14145
+ const lines = [`runtime tenant sweep-rc: scanned ${r.scanned} tenant-container(s); ${r.running} rc runtime(s) running`];
13809
14146
  for (const s of r.stages) {
13810
14147
  let line = ` ${s.repo} rc: ${s.orphanCandidate ? "RUNNING" : s.serviceState}`;
13811
14148
  if (s.retired) line += ` -> ${s.retired}${s.category ? ` (${s.category}${s.reason ? `/${s.reason}` : ""})` : ""}`;
@@ -13813,7 +14150,7 @@ function renderSweep(r) {
13813
14150
  lines.push(line);
13814
14151
  }
13815
14152
  if (r.running > 0 && !r.retireAttempted) {
13816
- lines.push("Retire an orphan with: mmi-cli tenant control <owner/repo> rc retire (or sweep all running: mmi-cli tenant sweep-rc --retire --yes)");
14153
+ lines.push("Retire an orphan with: mmi-cli runtime tenant control <owner/repo> rc retire (or sweep all running: mmi-cli runtime tenant sweep-rc --retire --yes)");
13817
14154
  }
13818
14155
  const undetermined = r.stages.filter((s) => s.serviceState === "unknown").length;
13819
14156
  if (undetermined > 0) {
@@ -14634,8 +14971,8 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
14634
14971
  repo: OWNER2,
14635
14972
  kind: "empty-target-inventory",
14636
14973
  severity: "high",
14637
- detail: "access audit received an empty repo inventory \u2014 zero repos to audit; a green audit of nothing is a false pass",
14638
- remediation: `confirm the Hub registry is reachable and returns projects, then re-run \`mmi-cli access audit\`; or audit one repo explicitly with \`mmi-cli access audit --repo <owner/repo>\``
14974
+ detail: "org access audit received an empty repo inventory \u2014 zero repos to audit; a green audit of nothing is a false pass",
14975
+ remediation: `confirm the Hub registry is reachable and returns projects, then re-run \`mmi-cli org access audit\`; or audit one repo explicitly with \`mmi-cli org access audit --repo <owner/repo>\``
14639
14976
  }],
14640
14977
  repos: []
14641
14978
  };
@@ -14650,7 +14987,7 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
14650
14987
  kind: "owner-baseline-unavailable",
14651
14988
  severity: "medium",
14652
14989
  detail: `could not resolve org owners (role=admin) from the GitHub API (${e.message}); the overgrant audit needs this baseline and was skipped to avoid false HIGH findings \u2014 re-run when the members API is reachable`,
14653
- remediation: `confirm the token can read GET /orgs/${OWNER2}/members?role=admin, then re-run \`mmi-cli access audit\``
14990
+ remediation: `confirm the token can read GET /orgs/${OWNER2}/members?role=admin, then re-run \`mmi-cli org access audit\``
14654
14991
  });
14655
14992
  return { ok: degraded.every((f) => f.severity !== "high"), owners: [], orgFindings: degraded, repos: [] };
14656
14993
  }
@@ -14695,7 +15032,7 @@ function resolveOrgAuditTargets(registryProjects) {
14695
15032
  kind: "registry-unreadable",
14696
15033
  severity: "high",
14697
15034
  detail: "the project registry SSOT (Hub API) was unreachable or unconfigured \u2014 the audit target set is unknown; auditing the stale committed projects.json instead could pass green while missing live repos",
14698
- remediation: `confirm the Hub API base URL + token are configured and reachable, then re-run \`mmi-cli access audit\`; or audit one repo explicitly with \`mmi-cli access audit --repo <owner/repo>\``
15035
+ remediation: `confirm the Hub API base URL + token are configured and reachable, then re-run \`mmi-cli org access audit\`; or audit one repo explicitly with \`mmi-cli org access audit --repo <owner/repo>\``
14699
15036
  }
14700
15037
  };
14701
15038
  }
@@ -14740,7 +15077,7 @@ function dataAccessContractsFromProjects(projects) {
14740
15077
  return { consumers };
14741
15078
  }
14742
15079
  function renderAccessReport(report) {
14743
- const lines = [`mmi-cli access audit: ${report.ok ? "OK" : "CHECK"} (owners: ${report.owners.map((o) => "@" + o).join(", ") || "none"})`];
15080
+ const lines = [`mmi-cli org access audit: ${report.ok ? "OK" : "CHECK"} (owners: ${report.owners.map((o) => "@" + o).join(", ") || "none"})`];
14744
15081
  for (const finding of report.orgFindings ?? []) {
14745
15082
  lines.push(` [${finding.severity}] ${finding.kind}: ${finding.detail}`);
14746
15083
  if (finding.remediation) lines.push(` ${finding.remediation}`);
@@ -14880,20 +15217,20 @@ function docsAuditRecord(input) {
14880
15217
  const date = input.date.trim();
14881
15218
  const shaRange = input.shaRange.trim();
14882
15219
  const checkerVendor = input.checkerVendor.trim();
14883
- if (!repo) throw new Error("docs-audit record: repo is required");
14884
- if (!date) throw new Error("docs-audit record: date is required");
14885
- if (!isValidIsoDate(date)) throw new Error(`docs-audit record: date must be a real ISO date (YYYY-MM-DD), got "${date}"`);
14886
- if (!shaRange) throw new Error("docs-audit record: shaRange is required");
14887
- 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");
14888
15225
  if (input.outcome.kind === "refreshed" && !(Number.isInteger(input.outcome.count) && input.outcome.count > 0)) {
14889
- 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");
14890
15227
  }
14891
15228
  if (input.outcome.kind === "failed" && !input.outcome.reason.trim()) {
14892
- 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");
14893
15230
  }
14894
15231
  const outcome = serializeOutcome(input.outcome);
14895
15232
  if (!isValidOutcomeWire(outcome)) {
14896
- 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}"`);
14897
15234
  }
14898
15235
  return { repo, date, shaRange, outcome, checkerVendor };
14899
15236
  }
@@ -14904,13 +15241,13 @@ function ageInDays(verdictDate, today) {
14904
15241
  }
14905
15242
  function docsAuditStatus(fetch2, opts) {
14906
15243
  if ("notArmed" in fetch2) {
14907
- return { ok: true, state: "not-armed", line: `docs-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)` };
14908
15245
  }
14909
15246
  if (!fetch2.ok) {
14910
- 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}` };
14911
15248
  }
14912
15249
  if (fetch2.verdict === null) {
14913
- 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` };
14914
15251
  }
14915
15252
  const verdict = fetch2.verdict;
14916
15253
  for (const field of ["repo", "shaRange", "checkerVendor"]) {
@@ -14920,7 +15257,7 @@ function docsAuditStatus(fetch2, opts) {
14920
15257
  return {
14921
15258
  ok: false,
14922
15259
  state: "malformed",
14923
- line: `docs-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`
14924
15261
  };
14925
15262
  }
14926
15263
  }
@@ -14928,29 +15265,29 @@ function docsAuditStatus(fetch2, opts) {
14928
15265
  return {
14929
15266
  ok: false,
14930
15267
  state: "malformed",
14931
- line: `docs-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`
14932
15269
  };
14933
15270
  }
14934
15271
  if (!isValidOutcomeWire(verdict.outcome)) {
14935
15272
  return {
14936
15273
  ok: false,
14937
15274
  state: "malformed",
14938
- 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`
14939
15276
  };
14940
15277
  }
14941
15278
  if (!isValidIsoDate(opts.today)) {
14942
- 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}"`);
14943
15280
  }
14944
15281
  const cadence = opts.cadenceDays ?? 7;
14945
15282
  const grace = opts.graceDays ?? 3;
14946
15283
  const age = ageInDays(verdict.date, opts.today);
14947
15284
  if (age > cadence + grace) {
14948
- return { ok: false, state: "stale", line: `docs-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` };
14949
15286
  }
14950
15287
  if (verdict.outcome === "failed") {
14951
- 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` };
14952
15289
  }
14953
- 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})` };
14954
15291
  }
14955
15292
 
14956
15293
  // src/project-readiness.ts
@@ -15064,7 +15401,7 @@ function appAttestationOf(meta) {
15064
15401
  return typeof at === "string" && at.length > 0 && typeof by === "string" && by.length > 0 ? { at, by } : null;
15065
15402
  }
15066
15403
  function attestedLine(att) {
15067
- return `App-owned readiness attested by @${att.by} on ${att.at.slice(0, 10)} \u2014 the static checklist is cleared (the doctor reads no product repo files); re-run \`mmi-cli project attest\` after app-owned structural changes.`;
15404
+ return `App-owned readiness attested by @${att.by} on ${att.at.slice(0, 10)} \u2014 the static checklist is cleared (the doctor reads no product repo files); re-run \`mmi-cli org project attest\` after app-owned structural changes.`;
15068
15405
  }
15069
15406
  var CONTRACT_UNDECLARED_LINE = "No runtime secrets declared \u2014 declare requiredRuntimeSecrets (a per-stage name map) in the registry META, or attest the app needs none with an explicit empty stage map ({ dev: [], rc: [], main: [] }).";
15070
15407
  function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
@@ -15075,19 +15412,19 @@ function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
15075
15412
  if (projectType === "content" || model === "content") return ["Content repo: keep app-owned work to docs/content changes; release train does not apply."];
15076
15413
  if (projectType === "desktop-app") {
15077
15414
  return [
15078
- "Desktop app: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned installer/packaging outside the tenant train.",
15415
+ "Desktop app: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default; keep app-owned installer/packaging outside the tenant train.",
15079
15416
  "Keep README.md and architecture.md explicit about the per-OS build and distribution path (installer, and any registry publish alongside it) so readiness does not guess web infra."
15080
15417
  ];
15081
15418
  }
15082
15419
  if (projectType === "desktop-game") {
15083
15420
  return [
15084
- "Desktop game: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned release packaging outside the tenant train.",
15421
+ "Desktop game: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default; keep app-owned release packaging outside the tenant train.",
15085
15422
  "Keep README.md and architecture.md explicit about the game build/release path so readiness does not guess web infra."
15086
15423
  ];
15087
15424
  }
15088
15425
  if (projectType === "non-deployable" || model === "none") {
15089
15426
  return [
15090
- "Non-deployable repo: no Hub deploy coords, Google OAuth, DWD, or tenant control by default.",
15427
+ "Non-deployable repo: no Hub deploy coords, Google OAuth, DWD, or runtime tenant control by default.",
15091
15428
  "Keep README.md and architecture.md explicit about the repo purpose and any project-admin workflow it still needs."
15092
15429
  ];
15093
15430
  }
@@ -15212,7 +15549,7 @@ function buildV2HealPatch(repoOrSlug, meta, mainDeployFact) {
15212
15549
  patch.requiredRuntimeSecrets = next;
15213
15550
  }
15214
15551
  }
15215
- const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli project set ${repo} --project-type <web-app|hub-service|content|desktop-app|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
15552
+ const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli org project set ${repo} --project-type <web-app|hub-service|content|desktop-app|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
15216
15553
  if (boardRegistryGaps(meta).length) appOwnedGaps.unshift(boardRegistryGapMessage(repo));
15217
15554
  return { slug, patch, appOwnedGaps };
15218
15555
  }
@@ -15251,7 +15588,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
15251
15588
  registryError: read.error,
15252
15589
  hubOwned: { meta: { ok: false, missing: [] }, deployCoords: degradedStage, deployState: degradedStage, secrets: degradedSecrets },
15253
15590
  autoHealAvailable: [],
15254
- appOwnedGaps: [`Hub registry read failed (${read.error}) \u2014 diagnosis degraded; nothing is known about META, coords, or gaps. Likely transient (cold start, network, or auth blip): retry \`mmi-cli project doctor\` shortly.`]
15591
+ appOwnedGaps: [`Hub registry read failed (${read.error}) \u2014 diagnosis degraded; nothing is known about META, coords, or gaps. Likely transient (cold start, network, or auth blip): retry \`mmi-cli org project doctor\` shortly.`]
15255
15592
  };
15256
15593
  }
15257
15594
  const meta = read.project;
@@ -15478,37 +15815,37 @@ function parseSecretsCatalogVar(raw) {
15478
15815
  try {
15479
15816
  parsed = JSON.parse(raw);
15480
15817
  } catch {
15481
- throw new Error('project set: secrets must be JSON keyed by KEY, e.g. {"SCRAPER_API_KEY":{"key":"SCRAPER_API_KEY","purpose":"scrape provider key","group":"scraper","owner":"oguz","stages":[],"consumers":["box"]}}');
15818
+ throw new Error('org project set: secrets must be JSON keyed by KEY, e.g. {"SCRAPER_API_KEY":{"key":"SCRAPER_API_KEY","purpose":"scrape provider key","group":"scraper","owner":"oguz","stages":[],"consumers":["box"]}}');
15482
15819
  }
15483
15820
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15484
- throw new Error("project set: secrets must be a map keyed by canonical KEY");
15821
+ throw new Error("org project set: secrets must be a map keyed by canonical KEY");
15485
15822
  }
15486
15823
  const nonEmpty = (v) => typeof v === "string" && v.trim().length > 0;
15487
15824
  const overrideMapKey = /^([A-Z_][A-Z0-9_]*)@(dev|rc|main)$/;
15488
15825
  for (const [mapKey, value] of Object.entries(parsed)) {
15489
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`project set: secrets["${mapKey}"] must be an object`);
15826
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`org project set: secrets["${mapKey}"] must be an object`);
15490
15827
  const e = value;
15491
15828
  if (typeof e.key !== "string" || !SECRET_ENV_NAME_RE.test(e.key)) {
15492
- throw new Error(`project set: secrets["${mapKey}"].key must be an env-name (UPPER_SNAKE)`);
15829
+ throw new Error(`org project set: secrets["${mapKey}"].key must be an env-name (UPPER_SNAKE)`);
15493
15830
  }
15494
15831
  const override = overrideMapKey.exec(mapKey);
15495
15832
  if (override) {
15496
15833
  if (e.key !== override[1] || !Array.isArray(e.stages) || e.stages.length !== 1 || e.stages[0] !== override[2]) {
15497
- throw new Error(`project set: secrets["${mapKey}"] is an override entry (#2528) \u2014 key must be ${override[1]} and stages exactly ["${override[2]}"]`);
15834
+ throw new Error(`org project set: secrets["${mapKey}"] is an override entry (#2528) \u2014 key must be ${override[1]} and stages exactly ["${override[2]}"]`);
15498
15835
  }
15499
15836
  } else if (e.key !== mapKey) {
15500
- throw new Error(`project set: secrets["${mapKey}"].key must equal the map key (or use the KEY@<stage> override form, #2528)`);
15837
+ throw new Error(`org project set: secrets["${mapKey}"].key must equal the map key (or use the KEY@<stage> override form, #2528)`);
15501
15838
  }
15502
- if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
15503
- if (e.provider !== void 0 && !nonEmpty(e.provider)) throw new Error(`project set: secrets["${mapKey}"].provider must be a non-empty string`);
15839
+ if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`org project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
15840
+ if (e.provider !== void 0 && !nonEmpty(e.provider)) throw new Error(`org project set: secrets["${mapKey}"].provider must be a non-empty string`);
15504
15841
  if (!Array.isArray(e.stages) || e.stages.some((s) => !RUNTIME_SECRET_STAGES.includes(s))) {
15505
- throw new Error(`project set: secrets["${mapKey}"].stages must be a subset of dev/rc/main ([] = shared)`);
15842
+ throw new Error(`org project set: secrets["${mapKey}"].stages must be a subset of dev/rc/main ([] = shared)`);
15506
15843
  }
15507
15844
  if (!Array.isArray(e.consumers) || e.consumers.some((c) => !SECRET_CONSUMERS.includes(c))) {
15508
- throw new Error(`project set: secrets["${mapKey}"].consumers must be a subset of ${SECRET_CONSUMERS.join("/")}`);
15845
+ throw new Error(`org project set: secrets["${mapKey}"].consumers must be a subset of ${SECRET_CONSUMERS.join("/")}`);
15509
15846
  }
15510
15847
  if (e.aliases !== void 0 && (!Array.isArray(e.aliases) || e.aliases.some((a) => typeof a !== "string"))) {
15511
- throw new Error(`project set: secrets["${mapKey}"].aliases must be a string array`);
15848
+ throw new Error(`org project set: secrets["${mapKey}"].aliases must be a string array`);
15512
15849
  }
15513
15850
  }
15514
15851
  return parsed;
@@ -15518,19 +15855,19 @@ function parseRuntimeSecretsVar(raw) {
15518
15855
  try {
15519
15856
  parsed = JSON.parse(raw);
15520
15857
  } catch {
15521
- throw new Error('project set: requiredRuntimeSecrets must be JSON, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}');
15858
+ throw new Error('org project set: requiredRuntimeSecrets must be JSON, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}');
15522
15859
  }
15523
15860
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15524
- throw new Error("project set: requiredRuntimeSecrets must be a stage map (a flat array is not box-loadable)");
15861
+ throw new Error("org project set: requiredRuntimeSecrets must be a stage map (a flat array is not box-loadable)");
15525
15862
  }
15526
15863
  const map = parsed;
15527
15864
  const out = {};
15528
15865
  for (const [stage, names] of Object.entries(map)) {
15529
15866
  if (!RUNTIME_SECRET_STAGES.includes(stage)) {
15530
- throw new Error(`project set: requiredRuntimeSecrets stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
15867
+ throw new Error(`org project set: requiredRuntimeSecrets stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
15531
15868
  }
15532
15869
  if (!Array.isArray(names) || names.some((n) => typeof n !== "string" || !n.trim())) {
15533
- throw new Error(`project set: requiredRuntimeSecrets.${stage} must be an array of non-empty secret names`);
15870
+ throw new Error(`org project set: requiredRuntimeSecrets.${stage} must be an array of non-empty secret names`);
15534
15871
  }
15535
15872
  out[stage] = names;
15536
15873
  }
@@ -15541,13 +15878,13 @@ function parseBuildSecretsVar(raw) {
15541
15878
  try {
15542
15879
  parsed = JSON.parse(raw);
15543
15880
  } catch {
15544
- throw new Error('project set: requiredBuildSecrets must be JSON, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]');
15881
+ throw new Error('org project set: requiredBuildSecrets must be JSON, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]');
15545
15882
  }
15546
15883
  if (!Array.isArray(parsed)) {
15547
- throw new Error("project set: requiredBuildSecrets must be a flat array (build secrets are not stage-mapped)");
15884
+ throw new Error("org project set: requiredBuildSecrets must be a flat array (build secrets are not stage-mapped)");
15548
15885
  }
15549
15886
  if (parsed.some((n) => typeof n !== "string" || !BUILD_SECRET_REF_RE.test(n) || n === "@github-packages-token")) {
15550
- throw new Error("project set: requiredBuildSecrets must be a flat array of ENVID=<ref> or bare <ref> strings");
15887
+ throw new Error("org project set: requiredBuildSecrets must be a flat array of ENVID=<ref> or bare <ref> strings");
15551
15888
  }
15552
15889
  return parsed;
15553
15890
  }
@@ -15556,19 +15893,19 @@ function parseEdgeDomainsVar(raw) {
15556
15893
  try {
15557
15894
  parsed = JSON.parse(raw);
15558
15895
  } catch {
15559
- throw new Error('project set: edgeDomains must be JSON, e.g. {"dev":"dev.example.co","rc":"rc.example.co","main":"example.co"}');
15896
+ throw new Error('org project set: edgeDomains must be JSON, e.g. {"dev":"dev.example.co","rc":"rc.example.co","main":"example.co"}');
15560
15897
  }
15561
15898
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15562
- throw new Error("project set: edgeDomains must be a {dev,rc,main} map of domain strings");
15899
+ throw new Error("org project set: edgeDomains must be a {dev,rc,main} map of domain strings");
15563
15900
  }
15564
15901
  const map = parsed;
15565
15902
  const out = {};
15566
15903
  for (const [stage, domain] of Object.entries(map)) {
15567
15904
  if (!RUNTIME_SECRET_STAGES.includes(stage)) {
15568
- throw new Error(`project set: edgeDomains stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
15905
+ throw new Error(`org project set: edgeDomains stage "${stage}" \u2014 expected only ${RUNTIME_SECRET_STAGES.join("/")}`);
15569
15906
  }
15570
15907
  if (typeof domain !== "string" || !domain.trim()) {
15571
- throw new Error(`project set: edgeDomains.${stage} must be a non-empty domain string`);
15908
+ throw new Error(`org project set: edgeDomains.${stage} must be a non-empty domain string`);
15572
15909
  }
15573
15910
  out[stage] = domain.trim();
15574
15911
  }
@@ -15580,19 +15917,19 @@ function parsePriorityOptionsVar(raw) {
15580
15917
  try {
15581
15918
  parsed = JSON.parse(raw);
15582
15919
  } catch {
15583
- throw new Error('project set: priorityOptions must be JSON, e.g. {"Urgent":"<id>","High":"<id>","Medium":"<id>","Low":"<id>"}');
15920
+ throw new Error('org project set: priorityOptions must be JSON, e.g. {"Urgent":"<id>","High":"<id>","Medium":"<id>","Low":"<id>"}');
15584
15921
  }
15585
15922
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15586
- throw new Error("project set: priorityOptions must be a {Urgent,High,Medium,Low} map of option-id strings");
15923
+ throw new Error("org project set: priorityOptions must be a {Urgent,High,Medium,Low} map of option-id strings");
15587
15924
  }
15588
15925
  const map = parsed;
15589
15926
  const out = {};
15590
15927
  for (const [name, id] of Object.entries(map)) {
15591
15928
  if (!PRIORITY_OPTION_NAMES.includes(name)) {
15592
- throw new Error(`project set: priorityOptions "${name}" \u2014 expected only ${PRIORITY_OPTION_NAMES.join("/")}`);
15929
+ throw new Error(`org project set: priorityOptions "${name}" \u2014 expected only ${PRIORITY_OPTION_NAMES.join("/")}`);
15593
15930
  }
15594
15931
  if (typeof id !== "string" || !id.trim()) {
15595
- throw new Error(`project set: priorityOptions.${name} must be a non-empty option-id string`);
15932
+ throw new Error(`org project set: priorityOptions.${name} must be a non-empty option-id string`);
15596
15933
  }
15597
15934
  out[name] = id.trim();
15598
15935
  }
@@ -15603,7 +15940,7 @@ function parsePortRangeVar(raw) {
15603
15940
  try {
15604
15941
  parsed = JSON.parse(raw);
15605
15942
  } catch {
15606
- throw new Error('project set: portRange must be JSON, e.g. {"start":3700,"end":3710} or [3700,3710]');
15943
+ throw new Error('org project set: portRange must be JSON, e.g. {"start":3700,"end":3710} or [3700,3710]');
15607
15944
  }
15608
15945
  let start;
15609
15946
  let end;
@@ -15612,12 +15949,12 @@ function parsePortRangeVar(raw) {
15612
15949
  } else if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
15613
15950
  ({ start, end } = parsed);
15614
15951
  } else {
15615
- throw new Error("project set: portRange must be {start,end} or a [start,end] tuple");
15952
+ throw new Error("org project set: portRange must be {start,end} or a [start,end] tuple");
15616
15953
  }
15617
15954
  if (typeof start !== "number" || typeof end !== "number" || !Number.isFinite(start) || !Number.isFinite(end)) {
15618
- throw new Error("project set: portRange start/end must be finite numbers");
15955
+ throw new Error("org project set: portRange start/end must be finite numbers");
15619
15956
  }
15620
- if (start > end) throw new Error("project set: portRange start must be <= end");
15957
+ if (start > end) throw new Error("org project set: portRange start must be <= end");
15621
15958
  return { start, end };
15622
15959
  }
15623
15960
  function parseStatusOptionsVar(raw) {
@@ -15625,16 +15962,16 @@ function parseStatusOptionsVar(raw) {
15625
15962
  try {
15626
15963
  parsed = JSON.parse(raw);
15627
15964
  } catch {
15628
- throw new Error('project set: statusOptions must be JSON, e.g. {"Todo":"<id>","In Progress":"<id>","In Review":"<id>","Done":"<id>"}');
15965
+ throw new Error('org project set: statusOptions must be JSON, e.g. {"Todo":"<id>","In Progress":"<id>","In Review":"<id>","Done":"<id>"}');
15629
15966
  }
15630
15967
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15631
- throw new Error("project set: statusOptions must be a map of status name to option-id string");
15968
+ throw new Error("org project set: statusOptions must be a map of status name to option-id string");
15632
15969
  }
15633
15970
  const map = parsed;
15634
15971
  const out = {};
15635
15972
  for (const [name, id] of Object.entries(map)) {
15636
15973
  if (typeof id !== "string" || !id.trim()) {
15637
- throw new Error(`project set: statusOptions.${name} must be a non-empty option-id string`);
15974
+ throw new Error(`org project set: statusOptions.${name} must be a non-empty option-id string`);
15638
15975
  }
15639
15976
  out[name] = id.trim();
15640
15977
  }
@@ -15645,31 +15982,31 @@ function parseOauthVar(raw) {
15645
15982
  try {
15646
15983
  parsed = JSON.parse(raw);
15647
15984
  } catch {
15648
- throw new Error(`project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
15985
+ throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
15649
15986
  }
15650
15987
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15651
- throw new Error("project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
15988
+ throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
15652
15989
  }
15653
15990
  const map = parsed;
15654
15991
  const out = {};
15655
15992
  for (const [key, value] of Object.entries(map)) {
15656
15993
  if (key === "subdomains" || key === "domains") {
15657
15994
  if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || !v.trim())) {
15658
- throw new Error(`project set: oauth.${key} must be an array of non-empty strings`);
15995
+ throw new Error(`org project set: oauth.${key} must be an array of non-empty strings`);
15659
15996
  }
15660
15997
  out[key] = value.map((v) => v.trim());
15661
15998
  } else if (key === "callbackPath") {
15662
- if (typeof value !== "string" || !value.trim()) throw new Error("project set: oauth.callbackPath must be a non-empty string");
15999
+ if (typeof value !== "string" || !value.trim()) throw new Error("org project set: oauth.callbackPath must be a non-empty string");
15663
16000
  const callbackPath = value.trim();
15664
16001
  if (callbackPath !== DEFAULT_CALLBACK_PATH) {
15665
- throw new Error(`project set: oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
16002
+ throw new Error(`org project set: oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
15666
16003
  }
15667
16004
  out.callbackPath = callbackPath;
15668
16005
  } else if (key === "fofuSubdomain") {
15669
- if (typeof value !== "string") throw new Error('project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
16006
+ if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
15670
16007
  out.fofuSubdomain = value.trim();
15671
16008
  } else {
15672
- throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
16009
+ throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
15673
16010
  }
15674
16011
  }
15675
16012
  return out;
@@ -15679,49 +16016,49 @@ function parseReposVar(raw) {
15679
16016
  try {
15680
16017
  parsed = JSON.parse(raw);
15681
16018
  } catch {
15682
- throw new Error('project set: repos must be a JSON array, e.g. ["mutmutco/mm-foo"]');
16019
+ throw new Error('org project set: repos must be a JSON array, e.g. ["mutmutco/mm-foo"]');
15683
16020
  }
15684
16021
  if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((r) => typeof r !== "string" || !r.trim())) {
15685
- throw new Error("project set: repos must be a non-empty array of owner/name strings");
16022
+ throw new Error("org project set: repos must be a non-empty array of owner/name strings");
15686
16023
  }
15687
16024
  return parsed.map((r) => r.trim());
15688
16025
  }
15689
16026
  function parsePublishRequiredVar(raw) {
15690
16027
  if (raw === "true") return true;
15691
16028
  if (raw === "false") return false;
15692
- throw new Error("project set: publishRequired must be true or false");
16029
+ throw new Error("org project set: publishRequired must be true or false");
15693
16030
  }
15694
16031
  function parseFofuEnabledVar(raw) {
15695
16032
  if (raw === "true") return true;
15696
16033
  if (raw === "false") return false;
15697
- throw new Error("project set: fofuEnabled must be true or false");
16034
+ throw new Error("org project set: fofuEnabled must be true or false");
15698
16035
  }
15699
16036
  function parseRuntimeVaultOnlyVar(raw) {
15700
16037
  if (raw === "true") return true;
15701
16038
  if (raw === "false") return false;
15702
- throw new Error("project set: runtimeVaultOnly must be true or false");
16039
+ throw new Error("org project set: runtimeVaultOnly must be true or false");
15703
16040
  }
15704
16041
  function parseConsumesDesignSystemVar(raw) {
15705
16042
  if (raw === "fofu") return raw;
15706
- throw new Error('project set: consumesDesignSystem must be "fofu"');
16043
+ throw new Error('org project set: consumesDesignSystem must be "fofu"');
15707
16044
  }
15708
16045
  function parsePublishDirVar(raw) {
15709
16046
  const v = raw.trim();
15710
16047
  if (v === "" || v === ".") {
15711
- throw new Error("project set: publishDir must be a non-empty relative subpath, e.g. packages/ui (omit it or --unset publishDir for the repo root)");
16048
+ throw new Error("org project set: publishDir must be a non-empty relative subpath, e.g. packages/ui (omit it or --unset publishDir for the repo root)");
15712
16049
  }
15713
16050
  if (!/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
15714
- throw new Error('project set: publishDir must be a safe relative subpath \u2014 no leading slash, no ".." segment');
16051
+ throw new Error('org project set: publishDir must be a safe relative subpath \u2014 no leading slash, no ".." segment');
15715
16052
  }
15716
16053
  return v;
15717
16054
  }
15718
16055
  function parseDsManifestPathVar(raw) {
15719
16056
  const v = raw.trim();
15720
16057
  if (v === "" || !/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
15721
- throw new Error('project set: dsManifestPath must be a safe relative path \u2014 no leading slash, no ".." segment');
16058
+ throw new Error('org project set: dsManifestPath must be a safe relative path \u2014 no leading slash, no ".." segment');
15722
16059
  }
15723
16060
  if (v !== "package.json" && !v.endsWith("/package.json")) {
15724
- throw new Error("project set: dsManifestPath must point to a package.json, e.g. web/package.json");
16061
+ throw new Error("org project set: dsManifestPath must point to a package.json, e.g. web/package.json");
15725
16062
  }
15726
16063
  return v;
15727
16064
  }
@@ -15730,10 +16067,10 @@ function parseRequiredChecksVar(raw) {
15730
16067
  try {
15731
16068
  parsed = JSON.parse(raw);
15732
16069
  } catch {
15733
- throw new Error('project set: requiredChecks must be a JSON array, e.g. ["gate"] or [] for an intentional no-ci repo');
16070
+ throw new Error('org project set: requiredChecks must be a JSON array, e.g. ["gate"] or [] for an intentional no-ci repo');
15734
16071
  }
15735
16072
  if (!Array.isArray(parsed) || parsed.some((c) => typeof c !== "string" || !c.trim())) {
15736
- throw new Error("project set: requiredChecks must be a JSON array of non-empty check-context strings (use [] for none)");
16073
+ throw new Error("org project set: requiredChecks must be a JSON array of non-empty check-context strings (use [] for none)");
15737
16074
  }
15738
16075
  return parsed.map((c) => c.trim());
15739
16076
  }
@@ -15742,22 +16079,22 @@ function parseGateVar(raw) {
15742
16079
  try {
15743
16080
  parsed = JSON.parse(raw);
15744
16081
  } catch {
15745
- throw new Error('project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
16082
+ throw new Error('org project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
15746
16083
  }
15747
16084
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15748
- throw new Error("project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
16085
+ throw new Error("org project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
15749
16086
  }
15750
16087
  const map = parsed;
15751
16088
  const out = {};
15752
16089
  for (const [key, value] of Object.entries(map)) {
15753
16090
  if (key === "runtime") {
15754
- if (value !== "node" && value !== "python") throw new Error('project set: gate.runtime must be "node" or "python"');
16091
+ if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
15755
16092
  out.runtime = value;
15756
16093
  } else if (key === "cmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
15757
- if (typeof value !== "string" || !value.trim()) throw new Error(`project set: gate.${key} must be a non-empty string`);
16094
+ if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
15758
16095
  out[key] = value.trim();
15759
16096
  } else {
15760
- throw new Error(`project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
16097
+ throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
15761
16098
  }
15762
16099
  }
15763
16100
  return out;
@@ -15835,25 +16172,25 @@ function buildProjectSetPatch(input) {
15835
16172
  const patch = {};
15836
16173
  if (input.class) {
15837
16174
  if (input.class !== "deployable" && input.class !== "content") {
15838
- throw new Error("project set: --class must be deployable or content");
16175
+ throw new Error("org project set: --class must be deployable or content");
15839
16176
  }
15840
16177
  patch.class = input.class;
15841
16178
  }
15842
16179
  if (input.projectType) {
15843
16180
  if (!isProjectType(input.projectType)) {
15844
- throw new Error(`project set: --project-type must be one of: ${PROJECT_TYPES.join(", ")}`);
16181
+ throw new Error(`org project set: --project-type must be one of: ${PROJECT_TYPES.join(", ")}`);
15845
16182
  }
15846
16183
  patch.projectType = input.projectType;
15847
16184
  }
15848
16185
  if (input.deployModel) {
15849
16186
  if (!isDeployModel(input.deployModel)) {
15850
- throw new Error(`project set: --deploy-model must be one of: ${DEPLOY_MODELS.join(", ")}`);
16187
+ throw new Error(`org project set: --deploy-model must be one of: ${DEPLOY_MODELS.join(", ")}`);
15851
16188
  }
15852
16189
  patch.deployModel = input.deployModel;
15853
16190
  }
15854
16191
  if (input.releaseTrack) {
15855
16192
  if (!isReleaseTrack(input.releaseTrack)) {
15856
- throw new Error(`project set: --release-track must be one of: ${RELEASE_TRACKS.join(", ")}`);
16193
+ throw new Error(`org project set: --release-track must be one of: ${RELEASE_TRACKS.join(", ")}`);
15857
16194
  }
15858
16195
  patch.releaseTrack = input.releaseTrack;
15859
16196
  }
@@ -15862,16 +16199,16 @@ function buildProjectSetPatch(input) {
15862
16199
  if (eq <= 0) {
15863
16200
  const looksPositional = value.includes("/") || !value.includes("=");
15864
16201
  const hint = looksPositional ? " \u2014 if that is the [owner/repo] argument, put it BEFORE --var; a variadic flag swallows the tokens after it" : "";
15865
- throw new Error(`project set: --var must be KEY=VALUE (got "${value}")${hint}`);
16202
+ throw new Error(`org project set: --var must be KEY=VALUE (got "${value}")${hint}`);
15866
16203
  }
15867
16204
  const key = value.slice(0, eq);
15868
16205
  const raw = value.slice(eq + 1);
15869
16206
  if (!SETTABLE_VAR_KEY_SET.has(key)) {
15870
- throw new Error(`project set: --var KEY "${key}" is not settable \u2014 allowed keys: ${SETTABLE_VAR_KEYS.join(", ")}`);
16207
+ throw new Error(`org project set: --var KEY "${key}" is not settable \u2014 allowed keys: ${SETTABLE_VAR_KEYS.join(", ")}`);
15871
16208
  }
15872
16209
  if (key === "projectNumber") {
15873
16210
  const n = Number(raw);
15874
- if (!Number.isFinite(n)) throw new Error("project set: projectNumber must be numeric");
16211
+ if (!Number.isFinite(n)) throw new Error("org project set: projectNumber must be numeric");
15875
16212
  patch[key] = n;
15876
16213
  } else if (key === "requiredRuntimeSecrets") {
15877
16214
  patch[key] = parseRuntimeSecretsVar(raw);
@@ -15904,7 +16241,7 @@ function buildProjectSetPatch(input) {
15904
16241
  } else if (key === "dsManifestPath") {
15905
16242
  patch[key] = parseDsManifestPathVar(raw);
15906
16243
  } else if (key === "ci") {
15907
- if (raw !== "none") throw new Error('project set: ci must be "none" (or use --unset ci to require checks)');
16244
+ if (raw !== "none") throw new Error('org project set: ci must be "none" (or use --unset ci to require checks)');
15908
16245
  patch[key] = raw;
15909
16246
  } else if (key === "requiredChecks") {
15910
16247
  patch[key] = parseRequiredChecksVar(raw);
@@ -15916,7 +16253,7 @@ function buildProjectSetPatch(input) {
15916
16253
  }
15917
16254
  for (const key of input.unsets) {
15918
16255
  if (!UNSET_KEY_SET.has(key)) {
15919
- throw new Error(`project set: --unset must be one of: ${UNSET_KEYS.join(", ")}`);
16256
+ throw new Error(`org project set: --unset must be one of: ${UNSET_KEYS.join(", ")}`);
15920
16257
  }
15921
16258
  patch[key] = null;
15922
16259
  }
@@ -15925,7 +16262,7 @@ function buildProjectSetPatch(input) {
15925
16262
  patch.edgeDomains = null;
15926
16263
  }
15927
16264
  if (Object.keys(patch).length === 0) {
15928
- throw new Error("project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, and/or --clear-web-profile");
16265
+ throw new Error("org project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, and/or --clear-web-profile");
15929
16266
  }
15930
16267
  return patch;
15931
16268
  }
@@ -15937,36 +16274,36 @@ function buildSetDeployPatch(_slug, input) {
15937
16274
  const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
15938
16275
  const stage = clean4(input.stage);
15939
16276
  if (!stage || !DEPLOY_STAGES.includes(stage)) {
15940
- throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
16277
+ throw new Error(`org project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
15941
16278
  }
15942
16279
  const substrate = clean4(input.substrate);
15943
16280
  if (substrate !== void 0 && !DEPLOY_SUBSTRATES.includes(substrate)) {
15944
- throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
16281
+ throw new Error(`org project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
15945
16282
  }
15946
16283
  const sshHost = clean4(input.sshHost);
15947
16284
  const sshUser = clean4(input.sshUser);
15948
16285
  const deployPath = clean4(input.deployPath);
15949
16286
  const serviceName = clean4(input.service);
15950
16287
  for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
15951
- if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
16288
+ if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`org project set-deploy: ${label} contains unsafe characters`);
15952
16289
  }
15953
16290
  let port;
15954
16291
  if (input.port !== void 0 && input.port !== null && `${input.port}`.trim() !== "") {
15955
16292
  port = Number(input.port);
15956
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
16293
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("org project set-deploy: --port must be an integer 1..65535");
15957
16294
  }
15958
16295
  const domain = clean4(input.domain);
15959
- if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
16296
+ if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`org project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
15960
16297
  const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
15961
16298
  for (const a of aliases) {
15962
- if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
16299
+ if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`org project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
15963
16300
  }
15964
16301
  const uniqueAliases = [...new Set(aliases)];
15965
16302
  if (input.clearAliases && uniqueAliases.length) {
15966
- throw new Error("project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
16303
+ throw new Error("org project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
15967
16304
  }
15968
16305
  if (input.noEnvFile !== void 0 && typeof input.noEnvFile !== "boolean") {
15969
- throw new Error("project set-deploy: --no-env-file must be true or false");
16306
+ throw new Error("org project set-deploy: --no-env-file must be true or false");
15970
16307
  }
15971
16308
  return {
15972
16309
  stage,
@@ -16056,7 +16393,7 @@ function evaluatePromotionFilelessGuard(input) {
16056
16393
  return {
16057
16394
  ok: false,
16058
16395
  reason: `the ${branch} docker-compose.yml marks a service fileless (mmi.runtime-env: "true") but DEPLOY#${stage}.noEnvFile is not true \u2014 the ${stage} deploy would fail exit-78 ("compose is fileless but DEPLOY#${stage} noEnvFile is not true"). Fix BEFORE promoting, then re-run:
16059
- mmi-cli project set-deploy --stage ${stage} --no-env-file true # register the fileless flag
16396
+ mmi-cli org project set-deploy --stage ${stage} --no-env-file true # register the fileless flag
16060
16397
  gh workflow run tenant-reconcile.yml --repo mutmutco/MMI-Hub # re-render the box control script (#1056)
16061
16398
  (or pass --skip-compose-guard to override this preflight)`
16062
16399
  };
@@ -16080,11 +16417,11 @@ function vaultCleanupNote(slug) {
16080
16417
  function buildRetirePlan(input) {
16081
16418
  const target = input.target?.trim();
16082
16419
  if (!target) {
16083
- throw new Error("project retire: pass <owner/repo> or a slug to retire");
16420
+ throw new Error("org project retire: pass <owner/repo> or a slug to retire");
16084
16421
  }
16085
16422
  const slug = retireSlugOf(target);
16086
16423
  if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
16087
- throw new Error(`project retire: "${target}" does not resolve to a valid slug`);
16424
+ throw new Error(`org project retire: "${target}" does not resolve to a valid slug`);
16088
16425
  }
16089
16426
  const repo = target.includes("/") ? target : `mutmutco/${slug}`;
16090
16427
  return { slug, repo, apply: Boolean(input.apply), skipBoard: Boolean(input.skipBoard) };
@@ -16414,7 +16751,7 @@ function registerSecretsCommands(program3) {
16414
16751
  const ok = await secretsRequest(d, key, o);
16415
16752
  if (!ok) process.exitCode = 1;
16416
16753
  }));
16417
- secrets.command("declare <key>").description("project-admin self-declare: register a NEW secrets-catalog entry for your own repo (name/purpose/group/owner/consumers/stages metadata only \u2014 never a value); closes the declare-first master-only gap (MM-PM#261/#262). Catalog-declare only \u2014 wiring the key into requiredRuntimeSecrets stays master-only (`secrets request` to ask, or `project set --var requiredRuntimeSecrets=...`); dropped from self-serve after adversarial review found real escalation paths in that additive-merge logic (MMI-Hub#2780). Separate from `project set`, which stays master-only for this field.").requiredOption("--purpose <text>", "what this secret is for").requiredOption("--group <text>", "catalog grouping, e.g. database, auth").option("--owner <login>", "catalog owner login (default: your own resolved GitHub login)").option("--consumers <list>", "comma-separated consumers (default: box)").option("--stages <list>", "comma-separated dev/rc/main this entry applies to (default: none \u2014 one stageless shared value)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((key, o) => withSecrets(async (d) => {
16754
+ secrets.command("declare <key>").description("project-admin self-declare: register a NEW secrets-catalog entry for your own repo (name/purpose/group/owner/consumers/stages metadata only \u2014 never a value); closes the declare-first master-only gap (MM-PM#261/#262). Catalog-declare only \u2014 wiring the key into requiredRuntimeSecrets stays master-only (`secrets request` to ask, or `org project set --var requiredRuntimeSecrets=...`); dropped from self-serve after adversarial review found real escalation paths in that additive-merge logic (MMI-Hub#2780). Separate from `org project set`, which stays master-only for this field.").requiredOption("--purpose <text>", "what this secret is for").requiredOption("--group <text>", "catalog grouping, e.g. database, auth").option("--owner <login>", "catalog owner login (default: your own resolved GitHub login)").option("--consumers <list>", "comma-separated consumers (default: box)").option("--stages <list>", "comma-separated dev/rc/main this entry applies to (default: none \u2014 one stageless shared value)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((key, o) => withSecrets(async (d) => {
16418
16755
  const owner = o.owner ?? await githubLogin();
16419
16756
  if (!owner) {
16420
16757
  fail("secrets declare: could not resolve your GitHub login for --owner; pass --owner explicitly");
@@ -16441,7 +16778,6 @@ function registerSecretsCommands(program3) {
16441
16778
  if (!ok) process.exitCode = 1;
16442
16779
  });
16443
16780
  secrets.command("set <key>").description("write/rotate a secret; value from stdin (never an argument). DECLARE-FIRST (#2528): the key must be a declared catalog coordinate \u2014 bare <KEY> = the stageless canonical, <stage>/<KEY> = a declared per-stage override; undeclared writes are rejected with the fix").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
16444
- secrets.command("edit <key>").description("alias for set \u2014 replace a secret value (read from stdin)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
16445
16781
  secrets.command("copy").description("copy provider keys between vault tiers (audit-logged; org-tier source is master-gated)").requiredOption("--from <stage>", "source tier: dev, rc, or main").requiredOption("--to <stage>", "destination tier: dev, rc, or main").requiredOption("--keys <names>", "comma-separated secret names (encryption keys blocked)").option("--dry-run", "report copies without writing").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets(async (d) => {
16446
16782
  const stages = ["dev", "rc", "main"];
16447
16783
  if (!stages.includes(o.from) || !stages.includes(o.to)) {
@@ -16788,14 +17124,14 @@ async function fetchInventory() {
16788
17124
  }
16789
17125
  });
16790
17126
  if (incomplete.length === PROJECTS.length) {
16791
- throw new Error(`box: could not read any Hetzner project \u2014
17127
+ throw new Error(`runtime box: could not read any Hetzner project \u2014
16792
17128
  ${incomplete.join("\n ")}`);
16793
17129
  }
16794
17130
  return { boxes: boxes.sort((a, b) => a.name.localeCompare(b.name)), incomplete };
16795
17131
  }
16796
17132
  function warnIncomplete(incomplete) {
16797
- for (const f of incomplete) console.error(`box: WARNING \u2014 ${f}`);
16798
- if (incomplete.length) console.error("box: this inventory is INCOMPLETE \u2014 boxes may be missing from it.");
17133
+ for (const f of incomplete) console.error(`runtime box: WARNING \u2014 ${f}`);
17134
+ if (incomplete.length) console.error("runtime box: this inventory is INCOMPLETE \u2014 boxes may be missing from it.");
16799
17135
  }
16800
17136
  function registerBoxCommands(program3) {
16801
17137
  const box = program3.command("box").description("the box inventory \u2014 which machines exist, their addresses, and the vault key that opens each (never key values)");
@@ -16820,12 +17156,12 @@ function registerBoxCommands(program3) {
16820
17156
  if (result.kind === "unknown") {
16821
17157
  warnIncomplete(result.reasons);
16822
17158
  await failGraceful(
16823
- `box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
17159
+ `runtime box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
16824
17160
  );
16825
17161
  return;
16826
17162
  }
16827
17163
  if (result.kind === "absent") {
16828
- await failGraceful(`box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
17164
+ await failGraceful(`runtime box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
16829
17165
  return;
16830
17166
  }
16831
17167
  const found = result.box;
@@ -16940,7 +17276,7 @@ function awsScheduleNames(payload) {
16940
17276
  return schedules.map((s) => s && typeof s === "object" ? s.Name : void 0).filter((n) => typeof n === "string" && !isJervResource(n));
16941
17277
  }
16942
17278
  var DECLARED_ENTRIES = [
16943
- { name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli box get <box> --ssh)" },
17279
+ { name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli runtime box get <box> --ssh)" },
16944
17280
  { name: "zuber-bake", cadence: "*:05/30 (every 30 min)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake.timer \u2192 rolling overlay bake, metro tier, next ~6h window (verify over ssh)" },
16945
17281
  { name: "zuber-bake-full", cadence: "00:30 UTC (03:30 TRT, daily)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake-full.timer \u2192 full 24h overlay rebuild, all 81 il (verify over ssh)" }
16946
17282
  ];
@@ -16972,7 +17308,7 @@ function renderDocSection(entries, generatedAtIso) {
16972
17308
  }
16973
17309
  return [
16974
17310
  DOC_START_MARKER,
16975
- `_Generated by \`mmi-cli schedules --doc\` at ${generatedAtIso}. Never hand-edit this section._`,
17311
+ `_Generated by \`mmi-cli org schedules --doc\` at ${generatedAtIso}. Never hand-edit this section._`,
16976
17312
  "",
16977
17313
  lines.join("\n"),
16978
17314
  DOC_END_MARKER
@@ -16982,7 +17318,7 @@ function spliceDoc(docText, generatedSection) {
16982
17318
  const start = docText.indexOf(DOC_START_MARKER);
16983
17319
  const end = docText.indexOf(DOC_END_MARKER);
16984
17320
  if (start === -1 || end === -1 || end < start) {
16985
- throw new Error(`schedules --doc: the target doc is missing the ${DOC_START_MARKER} / ${DOC_END_MARKER} markers \u2014 refusing to guess where the generated inventory goes.`);
17321
+ throw new Error(`org schedules --doc: the target doc is missing the ${DOC_START_MARKER} / ${DOC_END_MARKER} markers \u2014 refusing to guess where the generated inventory goes.`);
16986
17322
  }
16987
17323
  return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
16988
17324
  }
@@ -17190,11 +17526,11 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
17190
17526
  };
17191
17527
  }
17192
17528
  function warnIncomplete2(incomplete) {
17193
- for (const f of incomplete) console.error(`schedules: WARNING \u2014 ${f}`);
17194
- if (incomplete.length) console.error("schedules: this notebook is INCOMPLETE \u2014 schedules may be missing from it.");
17529
+ for (const f of incomplete) console.error(`org schedules: WARNING \u2014 ${f}`);
17530
+ if (incomplete.length) console.error("org schedules: this notebook is INCOMPLETE \u2014 schedules may be missing from it.");
17195
17531
  }
17196
17532
  function reportDrift(drift) {
17197
- for (const d of drift) console.error(`schedules: DRIFT \u2014 ${d}`);
17533
+ for (const d of drift) console.error(`org schedules: DRIFT \u2014 ${d}`);
17198
17534
  }
17199
17535
  function registerSchedulesCommands(program3) {
17200
17536
  program3.command("schedules").description("the org schedules notebook \u2014 every armed cron, timer, and scheduled LLM lane, resolved live (jerv side lives in jerv-cli schedules)").option("--json", "machine-readable output (consumed by jerv-cli schedules --all)").option("--doc <path>", "splice the generated inventory into the given doc between the schedules:inventory markers (docs/schedules.md)").action(async (o) => {
@@ -17203,14 +17539,14 @@ function registerSchedulesCommands(program3) {
17203
17539
  if (o.doc) {
17204
17540
  if (incomplete.length) {
17205
17541
  warnIncomplete2(incomplete);
17206
- await failGraceful("schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
17542
+ await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
17207
17543
  return;
17208
17544
  }
17209
17545
  const docText = await (0, import_promises2.readFile)(o.doc, "utf8");
17210
17546
  const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
17211
17547
  await (0, import_promises2.writeFile)(o.doc, spliced, "utf8");
17212
17548
  reportDrift(drift);
17213
- console.log(`schedules: wrote ${entries.length} entries into ${o.doc}`);
17549
+ console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
17214
17550
  return;
17215
17551
  }
17216
17552
  if (o.json) {
@@ -17457,15 +17793,15 @@ async function runIssueChildren(deps, epic, opts) {
17457
17793
  const seen = /* @__PURE__ */ new Set([childKey(repo, ref.number)]);
17458
17794
  for (const raw of await queryChildren(deps, owner, name, ref.number)) {
17459
17795
  if (out.length >= CHILDREN_MAX_TOTAL) break;
17460
- const child = shapeChildNode(raw, 1, boardProjectId);
17461
- out.push(child);
17462
- 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));
17463
17799
  }
17464
17800
  if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
17465
17801
  const queue = [];
17466
- for (const child of out) {
17467
- const sp = trySplitRepo(child.repo);
17468
- 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 });
17469
17805
  }
17470
17806
  while (queue.length && out.length < CHILDREN_MAX_TOTAL) {
17471
17807
  const frame = queue.shift();
@@ -17478,13 +17814,13 @@ async function runIssueChildren(deps, epic, opts) {
17478
17814
  }
17479
17815
  for (const raw of nodes) {
17480
17816
  if (out.length >= CHILDREN_MAX_TOTAL) break;
17481
- const child = shapeChildNode(raw, frame.depth + 1, boardProjectId);
17482
- const key = childKey(child.repo, child.number);
17817
+ const child2 = shapeChildNode(raw, frame.depth + 1, boardProjectId);
17818
+ const key = childKey(child2.repo, child2.number);
17483
17819
  if (seen.has(key)) continue;
17484
17820
  seen.add(key);
17485
- out.push(child);
17486
- const sp = trySplitRepo(child.repo);
17487
- 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 });
17488
17824
  }
17489
17825
  }
17490
17826
  return out;
@@ -18235,7 +18571,7 @@ function registerBootstrapCommands(program3) {
18235
18571
  projectMeta: meta,
18236
18572
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
18237
18573
  readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
18238
- // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
18574
+ // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
18239
18575
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
18240
18576
  requiredGcpApis: (() => {
18241
18577
  const v = meta?.requiredGcpApis;
@@ -18770,7 +19106,7 @@ function registerStageCommands(program3) {
18770
19106
  const read = await fetchProjectBySlugChecked(slug, reg);
18771
19107
  const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
18772
19108
  if (decision.action === "fail") {
18773
- return failGraceful(`port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
19109
+ return failGraceful(`stage port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
18774
19110
  }
18775
19111
  if (decision.action === "return") {
18776
19112
  const [start2, end2] = decision.range;
@@ -18780,7 +19116,7 @@ function registerStageCommands(program3) {
18780
19116
  const infra = resolvePortRangeInfra(process.cwd(), __dirname);
18781
19117
  if (!infra) {
18782
19118
  return failGraceful(
18783
- `port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
19119
+ `stage port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
18784
19120
  );
18785
19121
  }
18786
19122
  const path2 = infra.registryPath;
@@ -18793,7 +19129,7 @@ function registerStageCommands(program3) {
18793
19129
  const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
18794
19130
  const write = await upsertProject(slug, { portRange: { start, end } }, reg);
18795
19131
  if (!write.ok && source === "ddb") {
18796
- return failGraceful(`port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
19132
+ return failGraceful(`stage port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
18797
19133
  }
18798
19134
  if (o.json) {
18799
19135
  printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
@@ -18833,7 +19169,7 @@ function registerStageCommands(program3) {
18833
19169
  },
18834
19170
  control: async ({ repo, action, host, ip }) => {
18835
19171
  const res = await tenantControl({ repo, stage: "dev", action, host, ip }, rcDeps);
18836
- if (!res.ok) throw new Error(`tenant control ${action} dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
19172
+ if (!res.ok) throw new Error(`runtime tenant control ${action} dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
18837
19173
  }
18838
19174
  };
18839
19175
  try {
@@ -19443,7 +19779,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
19443
19779
  });
19444
19780
  }
19445
19781
  function renderGcApplyResult(result) {
19446
- const lines = ["gc apply result:"];
19782
+ const lines = ["worktree gc apply result:"];
19447
19783
  lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
19448
19784
  lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
19449
19785
  lines.push(` tracking refs removed: ${result.removedTrackingRefs.length ? result.removedTrackingRefs.join(", ") : "none"}`);
@@ -19488,7 +19824,7 @@ function evaluatePrMergeHousekeeping(report, context, force = false) {
19488
19824
  const detail = failed ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
19489
19825
  return {
19490
19826
  blocked: true,
19491
- reason: `${context} housekeeping blocked merge: ${detail}. Run \`mmi-cli gc --scratch --apply\` to clear it, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
19827
+ reason: `${context} housekeeping blocked merge: ${detail}. Run \`mmi-cli worktree gc --scratch --apply\` to clear it, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
19492
19828
  };
19493
19829
  }
19494
19830
  function assertPrMergeHousekeepingClean(startingPath, context, options = {}) {
@@ -19907,7 +20243,7 @@ function classifyStaleLeaks(input) {
19907
20243
  kind: "stale-worktree",
19908
20244
  ref: wt.branch,
19909
20245
  detail: `merged/closed branch ${wt.branch} still checked out at ${wt.path}`,
19910
- remediation: `cd "${wt.path}" && mmi-cli worktree land --apply (or: mmi-cli gc --apply)`
20246
+ remediation: `cd "${wt.path}" && mmi-cli worktree land --apply (or: mmi-cli worktree gc --apply)`
19911
20247
  });
19912
20248
  }
19913
20249
  for (const branch of input.localBranches) {
@@ -19920,7 +20256,7 @@ function classifyStaleLeaks(input) {
19920
20256
  kind: "stale-branch",
19921
20257
  ref: branch,
19922
20258
  detail: `merged/closed branch ${branch} has no worktree`,
19923
- remediation: "mmi-cli gc --apply"
20259
+ remediation: "mmi-cli worktree gc --apply"
19924
20260
  });
19925
20261
  }
19926
20262
  for (const wt of input.worktrees) {
@@ -19944,7 +20280,7 @@ function classifyStaleLeaks(input) {
19944
20280
  kind: "orphan-dir",
19945
20281
  ref: dir,
19946
20282
  detail: `directory under the worktrees root is not a registered worktree`,
19947
- remediation: `mmi-cli gc --apply (or: Remove-Item/rm -rf "${dir}")`
20283
+ remediation: `mmi-cli worktree gc --apply (or: Remove-Item/rm -rf "${dir}")`
19948
20284
  });
19949
20285
  }
19950
20286
  }
@@ -20740,7 +21076,7 @@ function buildPluginGuardDecision(i) {
20740
21076
  }
20741
21077
  function buildGuardSessionStartLine(state, opts = {}) {
20742
21078
  if (state === "healthy" || state === "not-org") return { exitCode: 0 };
20743
- const recovery = opts.recovery ?? "mmi-cli plugin-heal";
21079
+ const recovery = opts.recovery ?? "mmi-cli plugin heal";
20744
21080
  const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
20745
21081
  const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its marketplace/cache is unresolved";
20746
21082
  return {
@@ -21097,7 +21433,7 @@ function formatLastRun(r) {
21097
21433
  }
21098
21434
  function formatDeployStatus(r) {
21099
21435
  const lines = [
21100
- `deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
21436
+ `runtime deploy status \u2014 ${r.repo} (${r.slug}) stage ${r.stage}`,
21101
21437
  "",
21102
21438
  `running version: ${r.runningVersion ?? "none stamped"}`,
21103
21439
  `last deploy run: ${formatLastRun(r)}`,
@@ -21130,7 +21466,7 @@ function registerDeployCommands(program3) {
21130
21466
  const deploy = program3.command("deploy").description("per-stage deploy observability \u2014 last run, health, and running version (#2688)");
21131
21467
  deploy.command("status <stage>").description("last deploy run + runtime health probe + running version for a stage (defaults to the current repo)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (stage, o) => {
21132
21468
  if (!STAGES2.includes(stage)) {
21133
- return fail(`deploy status: <stage> must be dev, rc, or main`);
21469
+ return fail(`runtime deploy status: <stage> must be dev, rc, or main`);
21134
21470
  }
21135
21471
  try {
21136
21472
  const cfg = await loadConfig();
@@ -21159,7 +21495,7 @@ function registerDeployCommands(program3) {
21159
21495
  }
21160
21496
  if (report.health?.ok === false) process.exitCode = 1;
21161
21497
  } catch (e) {
21162
- return failGraceful(`deploy status: ${e.message}`);
21498
+ return failGraceful(`runtime deploy status: ${e.message}`);
21163
21499
  }
21164
21500
  });
21165
21501
  }
@@ -21278,7 +21614,7 @@ async function collectOnboardStatus() {
21278
21614
  if (read.ok && read.project) {
21279
21615
  registry2 = { ok: true, detail: `project "${read.project.name ?? slug}" registered` };
21280
21616
  } else if (read.ok) {
21281
- registry2 = { ok: false, detail: `repo "${slug}" not registered \u2014 run mmi-cli bootstrap or mmi-cli project set <owner/repo>` };
21617
+ registry2 = { ok: false, detail: `repo "${slug}" not registered \u2014 run mmi-cli bootstrap or mmi-cli org project set <owner/repo>` };
21282
21618
  } else {
21283
21619
  registry2 = { ok: false, detail: `read failed: ${read.error}` };
21284
21620
  }
@@ -21305,7 +21641,7 @@ async function collectOnboardStatus() {
21305
21641
  if (!cfg.sagaApiUrl) {
21306
21642
  nextCommand = "mmi-cli doctor \u2014 fix Hub API URL configuration first";
21307
21643
  } else if (!registry2.ok) {
21308
- nextCommand = "mmi-cli project set <owner/repo>";
21644
+ nextCommand = "mmi-cli org project set <owner/repo>";
21309
21645
  } else if (!board.ok) {
21310
21646
  nextCommand = "mmi-cli board read";
21311
21647
  } else {
@@ -21374,10 +21710,11 @@ var LOOP_PLAYBOOKS = {
21374
21710
  agent: {
21375
21711
  title: "Agent",
21376
21712
  steps: [
21377
- { label: "Resume continuity from Jerv's Memory", command: "jerv-cli resume" },
21713
+ { label: "Orient in the current repository", command: "mmi-cli onboard" },
21378
21714
  { label: "Read the next board item", command: "mmi-cli board read" },
21379
- { label: "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" },
21380
21716
  { label: "Build and test in the touched package", command: "npm test && npm run build" },
21717
+ { label: "Publish the branch", command: "git push -u origin HEAD" },
21381
21718
  { label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
21382
21719
  { label: "Wait for checks and land to development", command: "mmi-cli pr checks-wait <PR-number> && mmi-cli pr land <PR-number>" },
21383
21720
  { label: "Release only after the gated train is authorized", command: "mmi-cli release --apply" }
@@ -21386,16 +21723,17 @@ var LOOP_PLAYBOOKS = {
21386
21723
  "start-work": {
21387
21724
  title: "Start Work",
21388
21725
  steps: [
21389
- { label: "Pull latest development", command: "git checkout development && git pull origin development" },
21390
- { label: "Create a feature branch", command: "git checkout -b feature/<issue-number>-<short-description>" },
21391
- { 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" },
21392
21729
  { label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
21393
21730
  ]
21394
21731
  },
21395
21732
  "ship-pr": {
21396
21733
  title: "Ship PR",
21397
21734
  steps: [
21398
- { label: "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' },
21399
21737
  { label: "Wait for CI checks", command: "mmi-cli pr checks-wait <PR-number>" },
21400
21738
  { label: "Land the PR (merge to development)", command: "mmi-cli pr land <PR-number>" }
21401
21739
  ]
@@ -21403,9 +21741,9 @@ var LOOP_PLAYBOOKS = {
21403
21741
  "hotfix": {
21404
21742
  title: "Hotfix",
21405
21743
  steps: [
21406
- { label: "Start hotfix", command: "mmi-cli hotfix start" },
21407
- { label: "Apply fix commits", command: "git cherry-pick -x <commit-sha>" },
21408
- { 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>" }
21409
21747
  ]
21410
21748
  }
21411
21749
  };
@@ -21455,6 +21793,22 @@ function formatExplainCommand(cmd, rootName) {
21455
21793
  }
21456
21794
  return lines.join("\n").trimEnd();
21457
21795
  }
21796
+ function formatExplainGroup(cmd, rootName) {
21797
+ const lines = [
21798
+ `${rootName} ${cmd.path}${cmd.description ? ` \u2014 ${cmd.description}` : ""}`,
21799
+ `Category: ${cmd.category} \xB7 Discovery: ${cmd.discovery}`,
21800
+ "",
21801
+ "Commands:"
21802
+ ];
21803
+ for (const child2 of cmd.subcommands) {
21804
+ const args = child2.arguments.map((arg) => {
21805
+ const name = arg.variadic ? `${arg.name}...` : arg.name;
21806
+ return arg.required ? `<${name}>` : `[${name}]`;
21807
+ }).join(" ");
21808
+ lines.push(` ${child2.path}${args ? ` ${args}` : ""}${child2.description ? ` \u2014 ${child2.description}` : ""}`);
21809
+ }
21810
+ return lines.join("\n");
21811
+ }
21458
21812
  function formatExplainLoop(playbook) {
21459
21813
  const lines = [`${playbook.title} loop:`];
21460
21814
  for (const step of playbook.steps) {
@@ -21464,7 +21818,15 @@ function formatExplainLoop(playbook) {
21464
21818
  return lines.join("\n");
21465
21819
  }
21466
21820
  function findCommandInManifest(manifest, commandPath2) {
21467
- 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);
21468
21830
  }
21469
21831
  function registerExplainCommand(program3) {
21470
21832
  program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").action((commandArgs, opts) => {
@@ -21479,18 +21841,16 @@ function registerExplainCommand(program3) {
21479
21841
  }
21480
21842
  const manifest = buildCommandManifest(program3);
21481
21843
  if (!commandArgs.length) {
21482
- console.log(manifest.name + (manifest.version ? ` v${manifest.version}` : ""));
21483
- console.log(manifest.tree.description ?? "");
21484
- console.log("\nRun `mmi-cli commands` to list every subcommand + its flags.");
21844
+ console.log(formatManifestHuman(manifest));
21485
21845
  return;
21486
21846
  }
21487
21847
  const commandPath2 = commandArgs.join(" ");
21488
- const leaf = findCommandInManifest(manifest, commandPath2);
21489
- if (!leaf) {
21848
+ const command = findCommandInManifest(manifest, commandPath2);
21849
+ if (!command) {
21490
21850
  fail(`explain: unknown command "${commandPath2}"`, { code: ERROR_CODES.ERR_NOT_FOUND });
21491
21851
  return;
21492
21852
  }
21493
- console.log(formatExplainCommand(leaf, manifest.name));
21853
+ console.log(command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
21494
21854
  });
21495
21855
  }
21496
21856
 
@@ -22127,7 +22487,7 @@ function checkClaudePlugin(probe) {
22127
22487
  return {
22128
22488
  ok: false,
22129
22489
  label: guardState === "no-install" ? "Claude plugin \u2014 not installed" : "Claude plugin \u2014 unresolved (marketplace/cache missing)",
22130
- fix: "run `mmi-cli plugin-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",
22131
22491
  verbose: evidence
22132
22492
  };
22133
22493
  }
@@ -22186,7 +22546,7 @@ function checkPluginCache(input) {
22186
22546
  // Lead with the versioned-cache framing when there are stale versions; otherwise report the staging litter
22187
22547
  // on its own so a cache with zero versioned dirs still gets an honest ✗.
22188
22548
  detail: input.stale.length ? `${input.cached} versions cached (${parts.join("; ")})` : parts.join("; "),
22189
- fix: "run `mmi-cli plugin-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",
22190
22550
  verbose: evidence
22191
22551
  };
22192
22552
  }
@@ -22216,7 +22576,7 @@ function checkSchedules(probe) {
22216
22576
  ok: false,
22217
22577
  label: "schedules",
22218
22578
  detail: `${probe.incomplete.length} source(s) unreadable`,
22219
- fix: "run `mmi-cli schedules` for the full report \u2014 the notebook may be missing armed entries",
22579
+ fix: "run `mmi-cli org schedules` for the full report \u2014 the notebook may be missing armed entries",
22220
22580
  verbose: evidence
22221
22581
  };
22222
22582
  }
@@ -22225,7 +22585,7 @@ function checkSchedules(probe) {
22225
22585
  ok: false,
22226
22586
  label: "schedules",
22227
22587
  detail: `${probe.drift.length} drift finding(s)`,
22228
- fix: "run `mmi-cli schedules` \u2014 file/registry/live disagree; each drift line names its per-class remedy, applied in the owning repo",
22588
+ fix: "run `mmi-cli org schedules` \u2014 file/registry/live disagree; each drift line names its per-class remedy, applied in the owning repo",
22229
22589
  verbose: evidence
22230
22590
  };
22231
22591
  }
@@ -22581,11 +22941,13 @@ function allLongFlags() {
22581
22941
  if (cachedLongFlags) return cachedLongFlags;
22582
22942
  const acc = /* @__PURE__ */ new Set();
22583
22943
  const walk = (node) => {
22944
+ if (!isCanonicalSuggestion(node)) return;
22584
22945
  for (const opt of node.options) {
22946
+ if (opt.discovery) continue;
22585
22947
  const m = /--[\w-]+/.exec(opt.flags);
22586
22948
  if (m) acc.add(m[0]);
22587
22949
  }
22588
- for (const child of node.subcommands) walk(child);
22950
+ for (const child2 of node.subcommands) walk(child2);
22589
22951
  };
22590
22952
  walk(buildCommandManifest(program2).tree);
22591
22953
  cachedLongFlags = [...acc];
@@ -22621,7 +22983,7 @@ function envelopeAwareWriteErr(str) {
22621
22983
  process.stderr.write(str);
22622
22984
  }
22623
22985
  var program2 = new Command();
22624
- program2.name("mmi-cli").description("MMI Future CLI \u2014 org 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)");
22625
22987
  function appActorDeps() {
22626
22988
  return {
22627
22989
  fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
@@ -22651,22 +23013,26 @@ rules.command("gitignore").option("--write", "upsert the managed block into .git
22651
23013
  if (opts.write) {
22652
23014
  if (plan.changed) {
22653
23015
  (0, import_node_fs27.writeFileSync)(path2, plan.content, "utf8");
22654
- console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
23016
+ console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
22655
23017
  } else {
22656
- console.log("mmi-cli rules gitignore: up to date");
23018
+ console.log("mmi-cli org rules gitignore: up to date");
22657
23019
  }
22658
23020
  return;
22659
23021
  }
22660
23022
  if (plan.changed) {
22661
- console.error(`mmi-cli rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli rules gitignore --write\` and commit`);
23023
+ console.error(`mmi-cli org rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli org rules gitignore --write\` and commit`);
22662
23024
  process.exitCode = 1;
22663
23025
  } else {
22664
- console.log("mmi-cli rules gitignore: up to date");
23026
+ console.log("mmi-cli org rules gitignore: up to date");
22665
23027
  }
22666
23028
  });
22667
- program2.command("commands").description("print the 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) => {
22668
23030
  const manifest = buildCommandManifest(program2);
22669
- 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 }));
22670
23036
  });
22671
23037
  async function runWhoami(io = consoleIo) {
22672
23038
  const cfg = await loadConfig();
@@ -22743,21 +23109,21 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
22743
23109
  );
22744
23110
  if (o.json) return console.log(JSON.stringify(result));
22745
23111
  if (!o.quiet || result.removed.length || result.stillDeferred.length || result.skipped.length) {
22746
- if (result.removed.length) console.log(`gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
22747
- if (result.stillDeferred.length) console.log(`gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
22748
- if (result.skipped.length) console.log(`gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
22749
- if (!result.removed.length && !result.stillDeferred.length && !result.skipped.length) console.log("gc sweep-deferred: nothing queued");
23112
+ if (result.removed.length) console.log(`worktree gc sweep-deferred: removed ${result.removed.length} worktree(s)`);
23113
+ if (result.stillDeferred.length) console.log(`worktree gc sweep-deferred: ${result.stillDeferred.length} still queued (will retry on next session)`);
23114
+ if (result.skipped.length) console.log(`worktree gc sweep-deferred: ${result.skipped.length} dropped (repurposed for different work since queued, left untouched)`);
23115
+ if (!result.removed.length && !result.stillDeferred.length && !result.skipped.length) console.log("worktree gc sweep-deferred: nothing queued");
22750
23116
  }
22751
23117
  } catch (e) {
22752
- fail(`gc sweep-deferred: ${e.message}`);
23118
+ fail(`worktree gc sweep-deferred: ${e.message}`);
22753
23119
  }
22754
23120
  }, DEFERRED_SWEEP_HARD_TIMEOUT_MS, () => {
22755
- console.error("gc sweep-deferred: exceeded hard timeout \u2014 exiting so the detached worker cannot leak (#2841)");
23121
+ console.error("worktree gc sweep-deferred: exceeded hard timeout \u2014 exiting so the detached worker cannot leak (#2841)");
22756
23122
  process.exit(process.exitCode ?? 0);
22757
23123
  });
22758
23124
  });
22759
23125
  gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch and plans whose current hash is confirmed synced to North Star (#1864) instead of git branches/refs").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
22760
- if (o.apply && o.dryRun) return fail("gc: choose either --dry-run or --apply");
23126
+ if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
22761
23127
  if (o.scratch) {
22762
23128
  try {
22763
23129
  const root = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
@@ -22765,11 +23131,11 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
22765
23131
  if (o.json) return console.log(JSON.stringify({ dryRun: !o.apply, ...run }, null, 2));
22766
23132
  return console.log(formatScratchGcPlan(run.plan, Boolean(o.apply), run.applied));
22767
23133
  } catch (e) {
22768
- return fail(`gc --scratch: ${e.message}`);
23134
+ return fail(`worktree gc --scratch: ${e.message}`);
22769
23135
  }
22770
23136
  }
22771
23137
  const limit = Number.parseInt(o.limit, 10);
22772
- if (!Number.isFinite(limit) || limit < 1) return fail("gc: --limit must be a positive integer");
23138
+ if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
22773
23139
  try {
22774
23140
  const plan = await gcPlan(o.remote, limit);
22775
23141
  if (o.apply && !o.json) console.log(formatGcPlan(plan, false));
@@ -22794,7 +23160,7 @@ ${renderGcApplyResult(applyResult)}`);
22794
23160
  }
22795
23161
  if (applyResult?.failed.length) process.exitCode = 1;
22796
23162
  } catch (e) {
22797
- fail(`gc: ${e.message}`);
23163
+ fail(`worktree gc: ${e.message}`);
22798
23164
  }
22799
23165
  });
22800
23166
  var NPM_PROVISION_TIMEOUT_MS = 3e5;
@@ -22804,19 +23170,19 @@ function runWorktreeInstall(command, cwd, quiet) {
22804
23170
  const file = isWin2 ? "cmd.exe" : bin;
22805
23171
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
22806
23172
  return new Promise((resolve5, reject) => {
22807
- const 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 });
22808
23174
  const timer = setTimeout(() => {
22809
23175
  try {
22810
- child.kill();
23176
+ child2.kill();
22811
23177
  } catch {
22812
23178
  }
22813
23179
  reject(new Error(`${command} timed out after ${NPM_PROVISION_TIMEOUT_MS}ms in ${cwd}`));
22814
23180
  }, NPM_PROVISION_TIMEOUT_MS);
22815
- child.on("error", (e) => {
23181
+ child2.on("error", (e) => {
22816
23182
  clearTimeout(timer);
22817
23183
  reject(e);
22818
23184
  });
22819
- child.on("exit", (code) => {
23185
+ child2.on("exit", (code) => {
22820
23186
  clearTimeout(timer);
22821
23187
  if (code === 0) resolve5();
22822
23188
  else reject(new Error(`${command} exited ${code} in ${cwd}`));
@@ -22994,7 +23360,7 @@ async function attachToProject(issueNumber, repo, priority) {
22994
23360
  return { onBoard: false };
22995
23361
  }
22996
23362
  if (!cfg.projectId) {
22997
- console.error(`issue create: board attach skipped \u2014 no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
23363
+ console.error(`issue create: board attach skipped \u2014 no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli org project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
22998
23364
  return { onBoard: false };
22999
23365
  }
23000
23366
  try {
@@ -23074,7 +23440,7 @@ docsAudit.command("record").description("write a dated janitor verdict for one r
23074
23440
  if (o.outcome === "clean") outcome = { kind: "clean" };
23075
23441
  else if (o.outcome === "refreshed") outcome = { kind: "refreshed", count: Number(o.count) };
23076
23442
  else if (o.outcome === "failed") outcome = { kind: "failed", reason: o.reason ?? "" };
23077
- else return failGraceful(`docs-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}"`);
23078
23444
  const verdict = docsAuditRecord({ repo, date, shaRange: o.shaRange, outcome, checkerVendor: o.checkerVendor });
23079
23445
  await reportWrite("docs-audit record", await recordDocsAudit(verdict, registryClientDeps(await loadConfig())));
23080
23446
  } catch (e) {
@@ -23109,43 +23475,36 @@ tenant.command("control <owner/repo> <stage> <action>").description("run bounded
23109
23475
  const body = { ok: result.conclusion === "success", secrets: result.secrets, ssmStatus: result.conclusion === "success" ? "Success" : "Failed", raw: result.secretsRaw };
23110
23476
  const { lines, failure } = renderVerifySecrets(body);
23111
23477
  for (const line of lines) printLine(line);
23112
- if (failure) return failGraceful(`tenant control ${stage} verify-secrets: ${failure}`);
23478
+ if (failure) return failGraceful(`runtime tenant control ${stage} verify-secrets: ${failure}`);
23113
23479
  } else {
23114
23480
  printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
23115
23481
  }
23116
23482
  if (result.conclusion === "failure") {
23117
- return failGraceful(`tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
23483
+ return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
23118
23484
  }
23119
23485
  } catch (e) {
23120
- return failGraceful(`tenant control: ${e.message}`);
23486
+ return failGraceful(`runtime tenant control: ${e.message}`);
23121
23487
  }
23122
23488
  });
23123
23489
  tenant.command("status <owner/repo> <stage>").description("read tenant runtime readiness without dispatching tenant-control: DEPLOY row, last deploy run, public URL probe, and TLS/Caddy/Cloudflare hints").action(async (repo, stage) => {
23124
- if (!["dev", "rc", "main"].includes(stage)) return fail("tenant status: <stage> must be dev, rc, or main");
23125
- const cfg = await loadConfig();
23126
- const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
23127
- console.log(JSON.stringify(result, null, 2));
23128
- if (result.publicProbe?.ok === false) process.exitCode = 1;
23129
- });
23130
- tenant.command("readiness <owner/repo> <stage>").description("alias for tenant status: read-only tenant runtime readiness").action(async (repo, stage) => {
23131
- if (!["dev", "rc", "main"].includes(stage)) return fail("tenant readiness: <stage> must be dev, rc, or main");
23490
+ if (!["dev", "rc", "main"].includes(stage)) return fail("runtime tenant status: <stage> must be dev, rc, or main");
23132
23491
  const cfg = await loadConfig();
23133
23492
  const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
23134
23493
  console.log(JSON.stringify(result, null, 2));
23135
23494
  if (result.publicProbe?.ok === false) process.exitCode = 1;
23136
23495
  });
23137
23496
  tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev \u2014 the promoted/staging ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
23138
- if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("tenant redeploy: <stage> must be dev, rc, or main");
23497
+ if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime tenant redeploy: <stage> must be dev, rc, or main");
23139
23498
  try {
23140
23499
  const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage, ref: o.ref, watch: o.watch });
23141
23500
  return printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantRedeploy(result));
23142
23501
  } catch (e) {
23143
- return failGraceful(`tenant redeploy: ${e.message}`);
23502
+ return failGraceful(`runtime tenant redeploy: ${e.message}`);
23144
23503
  }
23145
23504
  });
23146
23505
  tenant.command("sweep-rc").description("discover (and optionally retire) running rc tenant runtimes across tenant-containers \u2014 orphan cleanup after a failed post-release retire (#942)").option("--retire", "retire every running rc runtime found (requires --yes) \u2014 WARNING: tears down a legitimately-staged rc too").option("--yes", "confirm the destructive --retire").option("--json", "machine-readable output").action(async (o) => {
23147
23506
  if (o.retire && !o.yes) {
23148
- return fail("tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release) \u2014 re-run with --yes to confirm");
23507
+ return fail("runtime tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release) \u2014 re-run with --yes to confirm");
23149
23508
  }
23150
23509
  const cfg = await loadConfig();
23151
23510
  const cdeps = registryClientDeps(cfg);
@@ -23165,7 +23524,7 @@ tenant.command("sweep-rc").description("discover (and optionally retire) running
23165
23524
  }, { retire: !!o.retire });
23166
23525
  return printLine(o.json ? JSON.stringify(result) : renderSweep(result));
23167
23526
  } catch (e) {
23168
- return failGraceful(`tenant sweep-rc: ${e.message}`);
23527
+ return failGraceful(`runtime tenant sweep-rc: ${e.message}`);
23169
23528
  }
23170
23529
  });
23171
23530
  async function resolveDnsBounded(host, timeoutMs = 3e3) {
@@ -23274,7 +23633,7 @@ async function projectTarget(commandName, explicitTarget) {
23274
23633
  project.command("list").description("list all projects (identity + board, never deploy coords)").option("--json", "machine-readable output").action(async (o) => {
23275
23634
  const cfg = await loadConfig();
23276
23635
  const projects = await fetchProjectsList(registryClientDeps(cfg));
23277
- if (!projects) return failGraceful("project list: Hub API unreachable or this repo is not bootstrapped");
23636
+ if (!projects) return failGraceful("org project list: Hub API unreachable or this repo is not bootstrapped");
23278
23637
  if (o.json) {
23279
23638
  console.log(JSON.stringify(projects));
23280
23639
  return;
@@ -23287,16 +23646,16 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
23287
23646
  const cfg = await loadConfig();
23288
23647
  let target;
23289
23648
  try {
23290
- target = await projectTarget("project get", repoOrSlug);
23649
+ target = await projectTarget("org project get", repoOrSlug);
23291
23650
  } catch (e) {
23292
23651
  return fail(e.message);
23293
23652
  }
23294
23653
  const read = await fetchProjectBySlugChecked(slugOf(target), registryClientDeps(cfg));
23295
23654
  if (!read.ok) {
23296
- return failGraceful(`project get: Hub registry read failed (${read.error}) \u2014 likely transient (cold start, network, or auth blip); retry shortly`);
23655
+ return failGraceful(`org project get: Hub registry read failed (${read.error}) \u2014 likely transient (cold start, network, or auth blip); retry shortly`);
23297
23656
  }
23298
23657
  if (!read.project) {
23299
- return failGraceful(`project get: no registry META for ${target} (unknown or unbootstrapped)`);
23658
+ return failGraceful(`org project get: no registry META for ${target} (unknown or unbootstrapped)`);
23300
23659
  }
23301
23660
  console.log(JSON.stringify(read.project));
23302
23661
  if (!o.json) {
@@ -23307,55 +23666,31 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
23307
23666
  console.error(
23308
23667
  `${m.name ?? target} \xB7 class ${m.class ?? "?"} \xB7 deploy ${m.deployModel ?? "?"}
23309
23668
  release track: ${track} \u2014 stages: ${stages}${note}
23310
- deploys run centrally (tenant-deploy.yml); product repos carry no deploy files. 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.`
23311
23670
  );
23312
23671
  }
23313
23672
  });
23314
- project.command("resolve <owner/repo>").description("deploy coords for a stage \u2014 for diagnosis. NOTE: /deploy-coords is OIDC-gated (a deploy job\u2019s id-token), so a gh-token CLI cannot read it from a dev machine").option("--stage <main|rc>", "deploy stage", "main").option("--json", "machine-readable output").action((_repoOrRepo, o) => {
23315
- const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect nonsecret DEPLOY facts with `mmi-cli project deploy get`. Full coords stay OIDC-gated.";
23316
- if (o.json) {
23317
- console.log(JSON.stringify({ ok: false, stage: o.stage, error: msg }));
23318
- process.exitCode = 1;
23319
- return;
23320
- }
23321
- fail(msg);
23322
- });
23323
23673
  var projectDeploy = project.command("deploy").description("read nonsecret DEPLOY# facts (domain, port, deploy path, substrate, host presence)");
23324
23674
  projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").option("--stage <stage>", "dev | rc | main").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
23325
23675
  const cfg = await loadConfig();
23326
23676
  let target;
23327
23677
  try {
23328
- target = await projectTarget("project deploy get", repoOrSlug);
23678
+ target = await projectTarget("org project deploy get", repoOrSlug);
23329
23679
  } catch (e) {
23330
23680
  return fail(e.message);
23331
23681
  }
23332
23682
  const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
23333
- if (!out) return failGraceful(`project deploy get: Hub deploy facts read failed for ${target}`);
23683
+ if (!out) return failGraceful(`org project deploy get: Hub deploy facts read failed for ${target}`);
23334
23684
  const stage = o.stage?.trim();
23335
- if (stage && !["dev", "rc", "main"].includes(stage)) return fail("project deploy get: --stage must be dev, rc, or main");
23685
+ if (stage && !["dev", "rc", "main"].includes(stage)) return fail("org project deploy get: --stage must be dev, rc, or main");
23336
23686
  const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
23337
23687
  console.log(JSON.stringify(payload));
23338
23688
  });
23339
- 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) => {
23340
23690
  const cfg = await loadConfig();
23341
23691
  let target;
23342
23692
  try {
23343
- target = await projectTarget("project deploy list", repoOrSlug);
23344
- } catch (e) {
23345
- return fail(e.message);
23346
- }
23347
- const out = await fetchDeployFactsBySlug(slugOf(target), registryClientDeps(cfg));
23348
- if (!out) return failGraceful(`project deploy list: Hub deploy facts read failed for ${target}`);
23349
- const stage = o.stage?.trim();
23350
- if (stage && !["dev", "rc", "main"].includes(stage)) return fail("project deploy list: --stage must be dev, rc, or main");
23351
- const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
23352
- console.log(JSON.stringify(payload));
23353
- });
23354
- project.command("doctor [owner/repo]").description("diagnose Hub v2 readiness for a repo without reading product repo files \u2014 appOwnedGaps are advisory templates; clear them with `project attest`; defaults to the current repo").option("--v2", "compatibility flag; v2 readiness is the default").option("--json", "machine-readable output").action(async (repo, _o) => {
23355
- const cfg = await loadConfig();
23356
- let target;
23357
- try {
23358
- target = await projectTarget("project doctor", repo);
23693
+ target = await projectTarget("org project doctor", repo);
23359
23694
  } catch (e) {
23360
23695
  return fail(e.message);
23361
23696
  }
@@ -23367,7 +23702,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
23367
23702
  const cfg = await loadConfig();
23368
23703
  let target;
23369
23704
  try {
23370
- target = await projectTarget("project heal", repo);
23705
+ target = await projectTarget("org project heal", repo);
23371
23706
  } catch (e) {
23372
23707
  return fail(e.message);
23373
23708
  }
@@ -23376,7 +23711,7 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
23376
23711
  fetchProject: (s) => fetchProjectBySlugChecked(s, reg),
23377
23712
  upsertProject: (s, patch) => upsertProject(s, patch, reg)
23378
23713
  });
23379
- if (out.status === "aborted") return failGraceful(`project heal: ${out.error}`);
23714
+ if (out.status === "aborted") return failGraceful(`org project heal: ${out.error}`);
23380
23715
  if (out.status === "planned") {
23381
23716
  console.log(JSON.stringify({ ok: true, slug: out.slug, dryRun: true, patch: out.patch, appOwnedGaps: out.appOwnedGaps }));
23382
23717
  return;
@@ -23385,15 +23720,15 @@ project.command("heal [owner/repo]").description("repair Hub-owned v2 readiness
23385
23720
  console.log(JSON.stringify({ ok: true, slug: out.slug, applied: [], appOwnedGaps: out.appOwnedGaps }));
23386
23721
  return;
23387
23722
  }
23388
- if (out.status === "write-failed") return reportWrite("project heal", out.write);
23723
+ if (out.status === "write-failed") return reportWrite("org project heal", out.write);
23389
23724
  console.log(JSON.stringify({ ok: true, slug: out.slug, applied: out.applied, appOwnedGaps: out.appOwnedGaps, result: out.result }));
23390
23725
  });
23391
23726
  project.command("readiness [owner/repo]").description("update the repo v2 readiness issue with Hub diagnosis and app-owned tasks; defaults to the current repo").option("--update-issue", "create/update the bounded v2 readiness issue section").option("--json", "machine-readable output").action(async (repo, o) => {
23392
- if (!o.updateIssue) return fail("project readiness: pass --update-issue");
23727
+ if (!o.updateIssue) return fail("org project readiness: pass --update-issue");
23393
23728
  const cfg = await loadConfig();
23394
23729
  let target;
23395
23730
  try {
23396
- target = await projectTarget("project readiness", repo);
23731
+ target = await projectTarget("org project readiness", repo);
23397
23732
  } catch (e) {
23398
23733
  return fail(e.message);
23399
23734
  }
@@ -23405,19 +23740,19 @@ project.command("attest [owner/repo]").description("attest this repo's app-owned
23405
23740
  const cfg = await loadConfig();
23406
23741
  let target;
23407
23742
  try {
23408
- target = await projectTarget("project attest", repoOrSlug);
23743
+ target = await projectTarget("org project attest", repoOrSlug);
23409
23744
  } catch (e) {
23410
23745
  return fail(e.message);
23411
23746
  }
23412
23747
  const repo = target.includes("/") ? target : `mutmutco/${slugOf(target)}`;
23413
23748
  const res = await attestAppGaps(slugOf(target), repo, registryClientDeps(cfg));
23414
- return reportWrite("project attest", res);
23749
+ return reportWrite("org project attest", res);
23415
23750
  });
23416
23751
  project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").option("--class <class>", "deployable | content").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (v2 capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", "read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
23417
23752
  const cfg = await loadConfig();
23418
23753
  let target;
23419
23754
  try {
23420
- target = await projectTarget("project set", repoOrSlug);
23755
+ target = await projectTarget("org project set", repoOrSlug);
23421
23756
  } catch (e) {
23422
23757
  return fail(e.message);
23423
23758
  }
@@ -23426,12 +23761,12 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
23426
23761
  const setAlias = o.set ?? [];
23427
23762
  const vars = [...o.var ?? [], ...setAlias];
23428
23763
  const dupe = duplicateVarKeyAcrossFlags(o.var ?? [], setAlias);
23429
- if (dupe) return fail(`project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
23764
+ if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
23430
23765
  if (o.secretsFile) {
23431
23766
  try {
23432
23767
  vars.push(`secrets=${(0, import_node_fs27.readFileSync)(o.secretsFile, "utf8")}`);
23433
23768
  } catch (e) {
23434
- return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
23769
+ return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
23435
23770
  }
23436
23771
  }
23437
23772
  let patch;
@@ -23446,19 +23781,19 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
23446
23781
  clearWebProfile: Boolean(o.clearWebProfile)
23447
23782
  });
23448
23783
  } catch (e) {
23449
- return fail(e.message.replace(/^project set: /, "project set: "));
23784
+ return fail(e.message.replace(/^org project set: /, "org project set: "));
23450
23785
  }
23451
23786
  const existing = await fetchProjectBySlug(slug, registryClientDeps(cfg));
23452
23787
  const boardError = boardLinkWriteError(patch, existing);
23453
- if (boardError) return fail(`project set: ${boardError}`);
23788
+ if (boardError) return fail(`org project set: ${boardError}`);
23454
23789
  const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
23455
- return reportWrite("project set", res);
23790
+ return reportWrite("org project set", res);
23456
23791
  });
23457
23792
  project.command("retire [owner/repo]").description("retire an orphaned registry slug (master-only): soft-delete the PROJECT# META row + drop the repo's board items; secrets/vault left alone. DRY-RUN by default \u2014 pass --apply to actually delete; defaults to the current repo").option("--apply", "actually delete (default is a dry-run preview of what would be removed)").option("--skip-board", "retire the registry META only \u2014 leave board items in place").option("--json", "machine-readable {slug, removedMeta, removedBoardItem} output").action(async (repoOrSlug, o) => {
23458
23793
  const cfg = await loadConfig();
23459
23794
  let target;
23460
23795
  try {
23461
- target = await projectTarget("project retire", repoOrSlug);
23796
+ target = await projectTarget("org project retire", repoOrSlug);
23462
23797
  } catch (e) {
23463
23798
  return fail(e.message);
23464
23799
  }
@@ -23484,7 +23819,7 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
23484
23819
  printLine(JSON.stringify(structured));
23485
23820
  } else {
23486
23821
  const verb = result.applied ? "retired" : "WOULD retire (dry-run; pass --apply to delete)";
23487
- printLine(`project retire: ${verb} ${result.slug} (${result.repo})`);
23822
+ printLine(`org project retire: ${verb} ${result.slug} (${result.repo})`);
23488
23823
  if (result.applied) {
23489
23824
  printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED \u2014 ${result.removedMeta.error}`}`);
23490
23825
  } else {
@@ -23499,10 +23834,10 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
23499
23834
  printLine(` ${result.vaultNote}`);
23500
23835
  }
23501
23836
  if (result.applied && !result.removedMeta.ok) {
23502
- return failGraceful(`project retire: META delete failed \u2014 ${result.removedMeta.error}`);
23837
+ return failGraceful(`org project retire: META delete failed \u2014 ${result.removedMeta.error}`);
23503
23838
  }
23504
23839
  });
23505
- var fullTrack = program2.command("full-track").description("direct-to-full-track readiness audits");
23840
+ var fullTrack = program2.command("full-track").description("direct-to-train readiness audits");
23506
23841
  fullTrack.command("readiness <owner/repo>").description("aggregate branch topology, train authority, deploy facts, endpoint health, and rcand readiness for direct -> full switches").option("--json", "machine-readable output").action(async (repo, _o) => {
23507
23842
  const cfg = await loadConfig();
23508
23843
  const reg = registryClientDeps(cfg);
@@ -23515,7 +23850,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
23515
23850
  buildTenantRuntimeStatusFor(repo, "rc", cfg),
23516
23851
  buildTenantRuntimeStatusFor(repo, "main", cfg)
23517
23852
  ]);
23518
- if (!metaRead.ok) return failGraceful(`full-track readiness: Hub registry read failed (${metaRead.error})`);
23853
+ if (!metaRead.ok) return failGraceful(`train readiness: Hub registry read failed (${metaRead.error})`);
23519
23854
  const report = buildFullTrackReadinessReport({
23520
23855
  repo,
23521
23856
  slug,
@@ -23527,18 +23862,18 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
23527
23862
  console.log(JSON.stringify(report, null, 2));
23528
23863
  if (!report.rcand.canApply) process.exitCode = 1;
23529
23864
  });
23530
- project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 omitted flags keep their stored value; seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "dev | rc | main").option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").option("--substrate <substrate>", "hetzner-ssh; omit to leave the row unchanged").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 true = #2662 env passthrough, no .env symlink; omit to leave the row unchanged").option("--force", "skip the #2748 fileless-compose guard (flip noEnvFile=true even if the stage branch compose still declares env_file:)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
23865
+ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 omitted flags keep their stored value; seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "dev | rc | main").option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").option("--substrate <substrate>", "hetzner-ssh; omit to leave the row unchanged").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 true = #2662 env passthrough, no .env symlink; omit to leave the row unchanged").option("--force", "skip the #2748 fileless-compose guard (flip noEnvFile=true even if the stage branch compose still declares env_file:)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
23531
23866
  const cfg = await loadConfig();
23532
23867
  let target;
23533
23868
  try {
23534
- target = await projectTarget("project set-deploy", repoOrSlug);
23869
+ target = await projectTarget("org project set-deploy", repoOrSlug);
23535
23870
  } catch (e) {
23536
23871
  return fail(e.message);
23537
23872
  }
23538
23873
  let noEnvFile;
23539
23874
  if (typeof o.envFile === "string") {
23540
23875
  const v = o.envFile.trim().toLowerCase();
23541
- if (v !== "true" && v !== "false") return fail("project set-deploy: --no-env-file must be true or false");
23876
+ if (v !== "true" && v !== "false") return fail("org project set-deploy: --no-env-file must be true or false");
23542
23877
  noEnvFile = v === "true";
23543
23878
  }
23544
23879
  if (noEnvFile === true) {
@@ -23548,10 +23883,10 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
23548
23883
  const sameRepo = !repoOrSlug || Boolean(cwdRepo) && (repoOrSlug.includes("/") ? cwdRepo.toLowerCase() === target.toLowerCase() : slugOf(cwdRepo) === slugOf(target));
23549
23884
  if (sameRepo) {
23550
23885
  const verdict = evaluateFilelessComposeGuard({ composeText: await readStageBranchCompose(branch), branch, force: Boolean(o.force) });
23551
- if (!verdict.ok) return fail(`project set-deploy: ${verdict.reason}`);
23552
- if (verdict.warn) console.error(`project set-deploy: ${verdict.warn}`);
23886
+ if (!verdict.ok) return fail(`org project set-deploy: ${verdict.reason}`);
23887
+ if (verdict.warn) console.error(`org project set-deploy: ${verdict.warn}`);
23553
23888
  } else {
23554
- console.error(`project set-deploy: --no-env-file guard skipped \u2014 run from the ${target} checkout to verify the ${branch} branch compose (#2748).`);
23889
+ console.error(`org project set-deploy: --no-env-file guard skipped \u2014 run from the ${target} checkout to verify the ${branch} branch compose (#2748).`);
23555
23890
  }
23556
23891
  }
23557
23892
  }
@@ -23575,13 +23910,13 @@ project.command("set-deploy [owner/repo]").description("patch the DEPLOY#<stage>
23575
23910
  return fail(e.message);
23576
23911
  }
23577
23912
  const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
23578
- return reportWrite("project set-deploy", res);
23913
+ return reportWrite("org project set-deploy", res);
23579
23914
  });
23580
23915
  var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
23581
23916
  registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
23582
23917
  const cfg = await loadConfig();
23583
23918
  const org = await fetchOrgConfig(registryClientDeps(cfg));
23584
- if (!org) return failGraceful("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");
23585
23920
  console.log(JSON.stringify(org));
23586
23921
  });
23587
23922
  var oauth = program2.command("oauth").description("per-repo Google OAuth \u2014 plan the canonical URI set, verify the client is port-agnostic");
@@ -23593,7 +23928,7 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
23593
23928
  try {
23594
23929
  oc = parseOauthConfig(meta ?? {}, slug);
23595
23930
  } catch (e) {
23596
- return failGraceful(`oauth plan: ${e.message}`);
23931
+ return failGraceful(`org oauth plan: ${e.message}`);
23597
23932
  }
23598
23933
  const origins = expectedJsOrigins(oc);
23599
23934
  const redirects = expectedRedirectUris(oc);
@@ -23611,18 +23946,18 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
23611
23946
  console.log(`
23612
23947
  SSM cred params (under /mmi-future/${slug}/):`);
23613
23948
  ssm.forEach((k) => console.log(` ${k}`));
23614
- console.log("\nProvision/repair the Console client per docs/Guides/oauth-provision.md; store creds with `mmi-cli oauth set-creds`.");
23949
+ console.log("\nProvision/repair the Console client per docs/Guides/oauth-provision.md; store creds with `mmi-cli org oauth set-creds`.");
23615
23950
  });
23616
23951
  oauth.command("set-creds").description('store the OAuth client into the canonical stageless GOOGLE_CLIENT_ID/SECRET pair (pipe the Console "Download JSON" on stdin)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
23617
23952
  const raw = await readStdin();
23618
23953
  if (!raw.trim()) {
23619
- return fail("oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli oauth set-creds --repo <owner/repo> < client.json");
23954
+ return fail("org oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli org oauth set-creds --repo <owner/repo> < client.json");
23620
23955
  }
23621
23956
  let creds;
23622
23957
  try {
23623
23958
  creds = parseOauthClientJson(raw);
23624
23959
  } catch (e) {
23625
- return fail(`oauth set-creds: ${e.message}`);
23960
+ return fail(`org oauth set-creds: ${e.message}`);
23626
23961
  }
23627
23962
  await withSecrets(async (d) => {
23628
23963
  for (const key of oauthSsmKeys()) {
@@ -23632,7 +23967,7 @@ oauth.command("set-creds").description('store the OAuth client into the canonica
23632
23967
  return;
23633
23968
  }
23634
23969
  }
23635
- console.log(`OAuth client stored in the ${oauthSsmKeys().length} canonical stageless keys. Run \`mmi-cli oauth verify\` to confirm the client is port-agnostic.`);
23970
+ console.log(`OAuth client stored in the ${oauthSsmKeys().length} canonical stageless keys. Run \`mmi-cli org oauth verify\` to confirm the client is port-agnostic.`);
23636
23971
  });
23637
23972
  });
23638
23973
  oauth.command("verify").description("probe Google authorize with an arbitrary port (:9123) to confirm the client is port-agnostic").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--client-id <id>", "OAuth client_id (else read the stageless GOOGLE_CLIENT_ID from SSM)").option("--json", "machine-readable output").action(async (o) => {
@@ -23643,7 +23978,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
23643
23978
  try {
23644
23979
  oc = parseOauthConfig(meta ?? {}, slug);
23645
23980
  } catch (e) {
23646
- return failGraceful(`oauth verify: ${e.message}`);
23981
+ return failGraceful(`org oauth verify: ${e.message}`);
23647
23982
  }
23648
23983
  let clientId = o.clientId;
23649
23984
  if (!clientId) {
@@ -23652,7 +23987,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
23652
23987
  });
23653
23988
  }
23654
23989
  if (!clientId) {
23655
- return failGraceful("oauth verify: no client_id (pass --client-id, or provision the repo so GOOGLE_CLIENT_ID exists)");
23990
+ return failGraceful("org oauth verify: no client_id (pass --client-id, or provision the repo so GOOGLE_CLIENT_ID exists)");
23656
23991
  }
23657
23992
  const redirectUri = probeRedirectUri(oc.callbackPath);
23658
23993
  let body = "";
@@ -23660,7 +23995,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
23660
23995
  const res = await fetch(buildAuthorizeProbeUrl(clientId, redirectUri), { redirect: "follow", signal: AbortSignal.timeout(1e4) });
23661
23996
  body = await res.text();
23662
23997
  } catch (e) {
23663
- return failGraceful(`oauth verify: probe request failed: ${e.message}`);
23998
+ return failGraceful(`org oauth verify: probe request failed: ${e.message}`);
23664
23999
  }
23665
24000
  const mismatch = authorizeBodyHasMismatch(body);
23666
24001
  if (o.json) {
@@ -24479,7 +24814,7 @@ function renderTrainApply(commandName, r) {
24479
24814
  return r.announceNote ? `${base}; announce: ${r.announceNote}` : base;
24480
24815
  }
24481
24816
  function renderTenantRedeploy(r) {
24482
- return `mmi-cli tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
24817
+ return `mmi-cli runtime tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
24483
24818
  }
24484
24819
  async function resolveRcandPlanTargets() {
24485
24820
  try {
@@ -24619,7 +24954,7 @@ var access = program2.command("access").description("org access audit (read-only
24619
24954
  access.command("role [repo]").description("D14 train authority for a repo (server-side Hub check): master | project-admin | developer").option("--json", "machine-readable output").action(async (repoArg) => {
24620
24955
  const o = { json: rawFlag("--json") };
24621
24956
  const repo = repoArg ?? await resolveRepo();
24622
- if (!repo) return fail("access role: pass <owner/repo> or run inside a repo checkout");
24957
+ if (!repo) return fail("org access role: pass <owner/repo> or run inside a repo checkout");
24623
24958
  const cfg = await loadConfig();
24624
24959
  const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
24625
24960
  if (!verdict.ok) {
@@ -24628,7 +24963,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
24628
24963
  process.exitCode = 1;
24629
24964
  return;
24630
24965
  }
24631
- return failGraceful(`access role: ${verdict.error}`);
24966
+ return failGraceful(`org access role: ${verdict.error}`);
24632
24967
  }
24633
24968
  const a = verdict.authority;
24634
24969
  if (o.json) {
@@ -24646,7 +24981,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
24646
24981
  const cfg = await loadConfig();
24647
24982
  const registryProjects = await fetchProjectsList(registryClientDeps(cfg));
24648
24983
  if (o.repo) {
24649
- if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
24984
+ if (o.class !== "deployable" && o.class !== "content") return failGraceful("org access audit: --class must be deployable or content");
24650
24985
  const meta = registryProjects?.find((project2) => (project2.repos ?? []).some((repo) => repo.toLowerCase() === o.repo.toLowerCase())) ?? null;
24651
24986
  const repoClass = o.class;
24652
24987
  targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
@@ -24672,7 +25007,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
24672
25007
  });
24673
25008
  access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
24674
25009
  var isWin2 = process.platform === "win32";
24675
- program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; skips slow checks and network probes").option("--preflight", "eager version/plugin 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) => {
24676
25011
  if (opts.guide) {
24677
25012
  consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
24678
25013
  return;
@@ -24702,11 +25037,11 @@ function directoryBytes(path2) {
24702
25037
  return 0;
24703
25038
  }
24704
25039
  for (const entry of entries) {
24705
- const child = (0, import_node_path23.join)(path2, entry.name);
24706
- if (entry.isDirectory()) total += directoryBytes(child);
25040
+ const child2 = (0, import_node_path23.join)(path2, entry.name);
25041
+ if (entry.isDirectory()) total += directoryBytes(child2);
24707
25042
  else {
24708
25043
  try {
24709
- total += (0, import_node_fs27.statSync)(child).size;
25044
+ total += (0, import_node_fs27.statSync)(child2).size;
24710
25045
  } catch {
24711
25046
  }
24712
25047
  }
@@ -24773,7 +25108,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
24773
25108
  });
24774
25109
  program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
24775
25110
  if (isInsideRepoSubdir(process.cwd())) {
24776
- console.error("[mmi-hook] session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
25111
+ console.error("[mmi-hook] plugin session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
24777
25112
  appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
24778
25113
  return;
24779
25114
  }
@@ -24841,7 +25176,12 @@ program2.command("lane", { hidden: true }).action(() => {
24841
25176
  program2.command("fusion", { hidden: true }).action(() => {
24842
25177
  fail("fusion orchestration lives in jerv-cli \u2014 try: jerv-cli fusion --help");
24843
25178
  });
24844
- program2.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));
24845
25185
  // Annotate the CommonJS export names for ESM import in node:
24846
25186
  0 && (module.exports = {
24847
25187
  DEFAULT_PRIORITY,