@mutmutco/cli 4.2.0 → 4.2.2

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 (2) hide show
  1. package/dist/main.cjs +345 -240
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3415,8 +3415,8 @@ var program = new Command();
3415
3415
 
3416
3416
  // src/index.ts
3417
3417
  var import_promises8 = require("node:fs/promises");
3418
- var import_node_fs42 = require("node:fs");
3419
- var import_node_child_process18 = require("node:child_process");
3418
+ var import_node_fs43 = require("node:fs");
3419
+ var import_node_child_process19 = require("node:child_process");
3420
3420
 
3421
3421
  // src/cli-shared.ts
3422
3422
  var import_node_child_process3 = require("node:child_process");
@@ -4522,10 +4522,29 @@ function targetFlagValueFromArgv(flag, argv) {
4522
4522
  if (!next || next.startsWith("-")) return void 0;
4523
4523
  return next;
4524
4524
  }
4525
+ function survivingFlagArgs(argv, rejectedFlag, ownFlags) {
4526
+ const out = [];
4527
+ for (let i = 0; i < argv.length; i += 1) {
4528
+ const token = argv[i];
4529
+ if (!token.startsWith("--")) continue;
4530
+ const [name, inlineValue] = token.includes("=") ? [token.slice(0, token.indexOf("=")), token.slice(token.indexOf("=") + 1)] : [token, void 0];
4531
+ const values = [];
4532
+ if (inlineValue === void 0) {
4533
+ while (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("-")) {
4534
+ values.push(argv[i + 1]);
4535
+ i += 1;
4536
+ }
4537
+ }
4538
+ if (name === rejectedFlag || !ownFlags.includes(name)) continue;
4539
+ out.push(inlineValue !== void 0 ? token : name, ...values);
4540
+ }
4541
+ return out;
4542
+ }
4525
4543
  function formatPositionalTarget(commandPath3, argName, opts = {}) {
4526
4544
  const concrete = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
4527
- if (concrete) return `mmi-cli ${commandPath3} ${concrete}`;
4528
- return `mmi-cli ${commandPath3} <${argName}>`;
4545
+ const target = concrete ?? `<${argName}>`;
4546
+ const kept = opts.flag && opts.argv && opts.ownFlags ? survivingFlagArgs(opts.argv, opts.flag, opts.ownFlags) : [];
4547
+ return [`mmi-cli ${commandPath3}`, target, ...kept].join(" ");
4529
4548
  }
4530
4549
  var FLAG_SYNONYMS = /* @__PURE__ */ new Map([["--outcome", "--reason"]]);
4531
4550
  function synonymFlagFor(flag, ownFlags) {
@@ -4545,6 +4564,27 @@ var GUIDES = /* @__PURE__ */ new Map([
4545
4564
  expected: ["learning report", "oracle issue create", "oracle issue list"],
4546
4565
  did_you_mean: "learning report"
4547
4566
  }
4567
+ ],
4568
+ // #5821: `list` is the verb `oracle issue list` and `devops pr list` teach, so callers reach for
4569
+ // `oracle board list` next. Distance ranks the OTHER houses' `list` leaves above the board's own
4570
+ // reader, so the receipt pointed at `devops pr list` — a different house, for a board query.
4571
+ [
4572
+ "oracle board:list",
4573
+ {
4574
+ hint: "no `oracle board list` \u2014 the bounded board query is `oracle board read` (add `--json` for the machine-readable partition)",
4575
+ expected: ["oracle board read"],
4576
+ did_you_mean: "oracle board read"
4577
+ }
4578
+ ],
4579
+ // #5823: `label` names a capability, not a command. The mutation door is `oracle issue edit`
4580
+ // (`--add-label` / `--remove-label`); distance cannot bridge noun→verb, so name it here.
4581
+ [
4582
+ "oracle issue:label",
4583
+ {
4584
+ hint: "no `oracle issue label` \u2014 labels are edited through `oracle issue edit --add-label <name>` / `--remove-label <name>`",
4585
+ expected: ["oracle issue edit"],
4586
+ did_you_mean: "oracle issue edit"
4587
+ }
4548
4588
  ]
4549
4589
  ]);
4550
4590
  function unknownCommandDomainGuide(parentPath, token) {
@@ -5538,7 +5578,7 @@ function commandLadderHint() {
5538
5578
  }
5539
5579
 
5540
5580
  // src/index.ts
5541
- var import_node_path39 = require("node:path");
5581
+ var import_node_path40 = require("node:path");
5542
5582
 
5543
5583
  // src/merge-ci-policy.ts
5544
5584
  function resolveMergeCiPolicy(input) {
@@ -11762,10 +11802,10 @@ var rollout_plan_default = {
11762
11802
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
11763
11803
  },
11764
11804
  baseline: {
11765
- version: "4.2.0",
11766
- tag: "v4.2.0",
11767
- commit: "d4802646ffca",
11768
- npm: "@mutmutco/cli@4.2.0"
11805
+ version: "4.2.2",
11806
+ tag: "v4.2.2",
11807
+ commit: "c347a86b94cc",
11808
+ npm: "@mutmutco/cli@4.2.2"
11769
11809
  },
11770
11810
  exitCriterion: "fleet-n-of-n",
11771
11811
  hubOnlyShortcut: "forbidden",
@@ -11782,14 +11822,14 @@ var rollout_plan_default = {
11782
11822
  repo: "mutmutco/mmi-hub",
11783
11823
  role: "canary",
11784
11824
  schedule: "train",
11785
- v3Target: "v4.2.0"
11825
+ v3Target: "v4.2.2"
11786
11826
  }
11787
11827
  ],
11788
11828
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
11789
11829
  rollback: {
11790
11830
  independent: true,
11791
- mechanism: "npm dist-tag latest -> 4.2.0 and redeploy the Hub Lambda from tag v4.2.0 (d4802646ffca); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
11792
- v3Target: "v4.2.0 (@mutmutco/cli@4.2.0, tag commit d4802646ffca \u2014 last known-good release carrying the repo-index v4-only contract)"
11831
+ mechanism: "npm dist-tag latest -> 4.2.2 and redeploy the Hub Lambda from tag v4.2.2 (c347a86b94cc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
11832
+ v3Target: "v4.2.2 (@mutmutco/cli@4.2.2, tag commit c347a86b94cc \u2014 last known-good release carrying the repo-index v4-only contract)"
11793
11833
  }
11794
11834
  },
11795
11835
  {
@@ -14739,10 +14779,16 @@ async function correlateWorkflowRun(deps, args) {
14739
14779
  async function watchTenantRun(deps, runId, repo = HUB_REPO3) {
14740
14780
  if (runId == null) return "pending";
14741
14781
  let watchSaidSuccess = true;
14782
+ const startedAt = Date.now();
14783
+ deps.warn?.(`watching workflow run ${runId} (${repo})`);
14784
+ const heartbeat = deps.warn ? setInterval(() => deps.warn?.(`workflow run ${runId} (${repo}) still running \u2014 ${Math.floor((Date.now() - startedAt) / 6e4)}m elapsed`), 6e4) : void 0;
14785
+ heartbeat?.unref();
14742
14786
  try {
14743
14787
  await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
14744
14788
  } catch {
14745
14789
  watchSaidSuccess = false;
14790
+ } finally {
14791
+ if (heartbeat) clearInterval(heartbeat);
14746
14792
  }
14747
14793
  const sleep2 = resolveSleep(deps);
14748
14794
  let confirmError;
@@ -16338,8 +16384,9 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16338
16384
  }
16339
16385
  };
16340
16386
  const run = await readRun();
16341
- if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || run.conclusion !== "failure") {
16342
- throw new Error(`run ${runId} is not a completed failed Hub publish release run; nothing was written`);
16387
+ const retryableConclusion = run.conclusion === "failure" || run.conclusion === "cancelled";
16388
+ if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || !retryableConclusion) {
16389
+ throw new Error(`run ${runId} is not a completed failed or cancelled Hub publish release run; nothing was written`);
16343
16390
  }
16344
16391
  const tag = run.headBranch ?? "";
16345
16392
  const tagSha = run.headSha ?? "";
@@ -16350,13 +16397,16 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16350
16397
  throw new Error(`run ${runId} does not match origin ${tag}; nothing was written`);
16351
16398
  }
16352
16399
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
16353
- await deps.run("gh", ["run", "rerun", String(runId), "--repo", ctx.repo, "--failed"]);
16354
- if (options.watch) {
16355
- await deps.run("gh", ["run", "watch", String(runId), "--repo", ctx.repo, "--exit-status"]);
16356
- const completed = await readRun();
16357
- if (completed.status !== "completed" || completed.conclusion !== "success") {
16358
- throw new Error(`publish run ${runId} did not finish successfully after retry`);
16359
- }
16400
+ await deps.run("gh", [
16401
+ "run",
16402
+ "rerun",
16403
+ String(runId),
16404
+ "--repo",
16405
+ ctx.repo,
16406
+ ...run.conclusion === "failure" ? ["--failed"] : []
16407
+ ]);
16408
+ if (options.watch && await watchTenantRun(deps, runId, ctx.repo) !== "success") {
16409
+ throw new Error(`publish run ${runId} did not finish successfully after retry`);
16360
16410
  }
16361
16411
  return {
16362
16412
  command: "release-retry-publish",
@@ -16366,7 +16416,7 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16366
16416
  runId,
16367
16417
  runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
16368
16418
  status: options.watch ? "success" : "pending",
16369
- note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} failed jobs queued for retry`
16419
+ note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} queued for retry`
16370
16420
  };
16371
16421
  }
16372
16422
  async function runTrainApplyPipeline(mode, input) {
@@ -17707,6 +17757,12 @@ async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
17707
17757
  return result;
17708
17758
  }
17709
17759
  }
17760
+ function ciReconcileExitFailed(input) {
17761
+ if (!input.apply || input.applyResults.length === 0) {
17762
+ return !input.audit.ok || input.applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
17763
+ }
17764
+ return input.applyResults.some((result) => result.errors.length > 0 || result.postApply == null || result.postApply.state !== "clean" && result.postApply.state !== "repaired");
17765
+ }
17710
17766
  async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
17711
17767
  if (authority.coversReleaseBranches) {
17712
17768
  const prOnly = authority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context));
@@ -20933,11 +20989,11 @@ function findBoardItem(items, selector, board) {
20933
20989
  }
20934
20990
  async function resolveCurrentRepo(options, deps) {
20935
20991
  if (options.repo) return options.repo;
20936
- const git2 = deps.git ?? defaultGit;
20992
+ const git3 = deps.git ?? defaultGit;
20937
20993
  let gitFailure;
20938
20994
  let originUrl = "";
20939
20995
  try {
20940
- originUrl = await git2(["remote", "get-url", "origin"]);
20996
+ originUrl = await git3(["remote", "get-url", "origin"]);
20941
20997
  } catch (e) {
20942
20998
  const read = gitReadState(e);
20943
20999
  if (read.state === "failed") gitFailure = read.detail;
@@ -23492,8 +23548,11 @@ var defaultDeps = {
23492
23548
  },
23493
23549
  // Re-exec the SAME command line in the fresh CLI with inherited stdio (spawn, not execFile, so
23494
23550
  // output streams live) and hand its exit code back — the stale parent exits with it (#5762).
23495
- reexec: () => new Promise((resolve6) => {
23496
- const args = process.argv.slice(2);
23551
+ // #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
23552
+ // the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
23553
+ // instead of `devops release …`) and the converged child refused with exit 2.
23554
+ reexec: (argv) => new Promise((resolve6) => {
23555
+ const args = [...argv];
23497
23556
  const child2 = isWin2 ? (0, import_node_child_process10.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process10.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
23498
23557
  child2.on("exit", (code) => resolve6(code ?? 1));
23499
23558
  child2.on("error", () => resolve6(1));
@@ -23515,7 +23574,7 @@ async function selfConvergeTrainCli(input) {
23515
23574
  error: `self-converge did not take effect: mmi-cli on PATH still resolves to ${installed ?? "unknown"}, expected >= ${input.releasedVersion}`
23516
23575
  };
23517
23576
  }
23518
- const reexecCode = await deps.reexec();
23577
+ const reexecCode = await deps.reexec(input.argv);
23519
23578
  return { kind: "converged", reexecCode };
23520
23579
  } catch (e) {
23521
23580
  return { kind: "failed", error: e instanceof Error ? e.message : String(e) };
@@ -23804,31 +23863,31 @@ var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
23804
23863
  function checkHotfixCoverage(options = {}) {
23805
23864
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
23806
23865
  const ack = (options.ack ?? []).filter(Boolean);
23807
- const git2 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
23866
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
23808
23867
  const revList = (range) => {
23809
- const out = git2(["rev-list", "--no-merges", range]).trim();
23868
+ const out = git3(["rev-list", "--no-merges", range]).trim();
23810
23869
  return out ? out.split("\n") : [];
23811
23870
  };
23812
23871
  const isAncestor = (sha, ref) => {
23813
23872
  try {
23814
- git2(["merge-base", "--is-ancestor", sha, ref]);
23873
+ git3(["merge-base", "--is-ancestor", sha, ref]);
23815
23874
  return true;
23816
23875
  } catch {
23817
23876
  return false;
23818
23877
  }
23819
23878
  };
23820
23879
  const patchId = (sha) => {
23821
- const diff = git2(["show", "--no-color", "--pretty=format:", sha]);
23880
+ const diff = git3(["show", "--no-color", "--pretty=format:", sha]);
23822
23881
  if (!diff.trim()) return null;
23823
- const out = git2(["patch-id", "--stable"], { input: diff }).trim();
23882
+ const out = git3(["patch-id", "--stable"], { input: diff }).trim();
23824
23883
  return out ? out.split(" ")[0] : null;
23825
23884
  };
23826
23885
  const changedPaths = (sha) => {
23827
- const out = git2(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
23886
+ const out = git3(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
23828
23887
  return out ? out.split("\n") : [];
23829
23888
  };
23830
23889
  const cherrySources = (sha) => {
23831
- const message = git2(["log", "-1", "--format=%B", sha]);
23890
+ const message = git3(["log", "-1", "--format=%B", sha]);
23832
23891
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
23833
23892
  };
23834
23893
  const mainOnly = revList(`${rcRef}..${mainRef}`);
@@ -23843,7 +23902,7 @@ function checkHotfixCoverage(options = {}) {
23843
23902
  }
23844
23903
  };
23845
23904
  const commits = mainOnly.map((sha) => {
23846
- const subject = git2(["log", "-1", "--format=%s", sha]).trim();
23905
+ const subject = git3(["log", "-1", "--format=%s", sha]).trim();
23847
23906
  const paths = changedPaths(sha);
23848
23907
  if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
23849
23908
  return { sha, subject, coverage: "exempt-distribution" };
@@ -23872,21 +23931,21 @@ function checkHotfixCoverage(options = {}) {
23872
23931
  }
23873
23932
  function checkHotfixCarries(options) {
23874
23933
  const { cwd = process.cwd(), branch, baseRef, targets } = options;
23875
- const git2 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
23934
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
23876
23935
  const isAncestor = (sha, ref) => {
23877
23936
  try {
23878
- git2(["merge-base", "--is-ancestor", sha, ref]);
23937
+ git3(["merge-base", "--is-ancestor", sha, ref]);
23879
23938
  return true;
23880
23939
  } catch {
23881
23940
  return false;
23882
23941
  }
23883
23942
  };
23884
23943
  const revList = (range) => {
23885
- const out = git2(["rev-list", "--no-merges", range]).trim();
23944
+ const out = git3(["rev-list", "--no-merges", range]).trim();
23886
23945
  return out ? out.split("\n") : [];
23887
23946
  };
23888
23947
  const cherrySources = (sha) => {
23889
- const message = git2(["log", "-1", "--format=%B", sha]);
23948
+ const message = git3(["log", "-1", "--format=%B", sha]);
23890
23949
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
23891
23950
  };
23892
23951
  const carried = /* @__PURE__ */ new Set();
@@ -25074,12 +25133,25 @@ function runSpawnPolicy(root) {
25074
25133
  }
25075
25134
 
25076
25135
  // src/test-policy-core.ts
25077
- var import_node_child_process14 = require("node:child_process");
25136
+ var import_node_child_process15 = require("node:child_process");
25078
25137
  var import_node_fs25 = require("node:fs");
25079
25138
  var import_node_path22 = require("node:path");
25080
25139
 
25081
25140
  // ../scripts/test-command-policy-core.mjs
25141
+ var import_node_child_process14 = require("node:child_process");
25082
25142
  var TEST_COMMAND_CLASS = "test";
25143
+ var TRAILER_KEY = "Test-Policy-Override";
25144
+ var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
25145
+ var REC = "";
25146
+ var FLD = "";
25147
+ var WAIVABLE_KINDS = [
25148
+ "mandatory-zone-untested",
25149
+ "unrequested-test-file",
25150
+ "protected-removed",
25151
+ "stale-protected-entry",
25152
+ "stale-satisfied-by"
25153
+ ];
25154
+ var TEST_WORK_KIND = "unrequested-test-file";
25083
25155
  function translateGlob(glob) {
25084
25156
  let out = "";
25085
25157
  for (let i = 0; i < glob.length; i += 1) {
@@ -25122,7 +25194,7 @@ function matchedMandatoryGlobs(paths, mandatory) {
25122
25194
  return list.some((path2) => re.test(path2));
25123
25195
  });
25124
25196
  }
25125
- function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
25197
+ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null } = {}) {
25126
25198
  const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
25127
25199
  if (!regulated) {
25128
25200
  return {
@@ -25135,7 +25207,8 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
25135
25207
  };
25136
25208
  }
25137
25209
  const matched = matchedMandatoryGlobs(paths, mandatory);
25138
- const testCommandsAllowed = matched.length > 0;
25210
+ const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
25211
+ const testCommandsAllowed = matched.length > 0 || overrideAuthorizes;
25139
25212
  return {
25140
25213
  configuredMandatoryCount,
25141
25214
  matchedMandatoryGlobs: matched,
@@ -25148,22 +25221,78 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
25148
25221
  }
25149
25222
  };
25150
25223
  }
25224
+ function git(args, cwd) {
25225
+ return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25226
+ }
25227
+ function parseScope(value) {
25228
+ const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
25229
+ if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
25230
+ const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
25231
+ return {
25232
+ kinds: named,
25233
+ reason: scoped[2].trim(),
25234
+ unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
25235
+ };
25236
+ }
25237
+ function invisibleTrailer(sha, line) {
25238
+ return {
25239
+ kind: "malformed-override-trailer",
25240
+ paths: [],
25241
+ detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
25242
+ ${line}
25243
+ but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
25244
+ exist only to the regex that granted it. The trailer was chosen over a flag because it can be
25245
+ AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
25246
+ Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
25247
+ trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
25248
+ block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
25249
+ This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
25250
+ The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
25251
+ };
25252
+ }
25253
+ function unknownScope(sha, unknown) {
25254
+ return {
25255
+ kind: "malformed-override-trailer",
25256
+ paths: [],
25257
+ detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
25258
+ gate cannot report.
25259
+ Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
25260
+ Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
25261
+ into a blanket exemption without anyone deciding it should.`
25262
+ };
25263
+ }
25264
+ function isShallowRepository(cwd) {
25265
+ try {
25266
+ return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
25267
+ } catch {
25268
+ return true;
25269
+ }
25270
+ }
25271
+ function readOverride(base, cwd) {
25272
+ const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
25273
+ const refusals = [];
25274
+ let override = null;
25275
+ for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
25276
+ const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
25277
+ if (!sha) continue;
25278
+ if (override) break;
25279
+ const value = (trailer ?? "").trim();
25280
+ if (!value) {
25281
+ const shaped = OVERRIDE_RE.exec(body ?? "");
25282
+ if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
25283
+ continue;
25284
+ }
25285
+ const { kinds, reason, unknown } = parseScope(value);
25286
+ if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
25287
+ else override = { sha, reason, kinds };
25288
+ }
25289
+ return { override, refusals };
25290
+ }
25151
25291
 
25152
25292
  // src/test-policy-core.ts
25153
25293
  var POLICY_FILE = "test-policy.json";
25154
25294
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
25155
25295
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
25156
- var TRAILER_KEY = "Test-Policy-Override";
25157
- var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
25158
- var REC = "";
25159
- var FLD = "";
25160
- var WAIVABLE_KINDS = [
25161
- "mandatory-zone-untested",
25162
- "unrequested-test-file",
25163
- "protected-removed",
25164
- "stale-protected-entry",
25165
- "stale-satisfied-by"
25166
- ];
25167
25296
  function translate(glob) {
25168
25297
  let out = "";
25169
25298
  for (let i = 0; i < glob.length; i++) {
@@ -25521,15 +25650,15 @@ function evaluate(changed, policy, present = () => false) {
25521
25650
  }
25522
25651
  return findings;
25523
25652
  }
25524
- function git(args, cwd) {
25525
- return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25653
+ function git2(args, cwd) {
25654
+ return (0, import_node_child_process15.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25526
25655
  }
25527
25656
  var COAUTHOR_KEY = "Co-authored-by";
25528
25657
  var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
25529
25658
  var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
25530
25659
  function parseTrailers(message, cwd) {
25531
25660
  try {
25532
- return (0, import_node_child_process14.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
25661
+ return (0, import_node_child_process15.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
25533
25662
  windowsHide: true,
25534
25663
  cwd,
25535
25664
  input: message,
@@ -25566,19 +25695,12 @@ function resolveBase(cwd, explicit) {
25566
25695
  const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
25567
25696
  for (const ref of candidates) {
25568
25697
  try {
25569
- return git(["merge-base", "HEAD", ref], cwd).trim();
25698
+ return git2(["merge-base", "HEAD", ref], cwd).trim();
25570
25699
  } catch {
25571
25700
  }
25572
25701
  }
25573
25702
  return null;
25574
25703
  }
25575
- function isShallowRepository(cwd) {
25576
- try {
25577
- return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
25578
- } catch {
25579
- return true;
25580
- }
25581
- }
25582
25704
  function untrustworthyRange(cwd, base) {
25583
25705
  if (!base) {
25584
25706
  return {
@@ -25600,65 +25722,11 @@ function untrustworthyRange(cwd, base) {
25600
25722
  Fetch full history \u2014 actions/checkout with \`fetch-depth: 0\`, or \`git fetch --unshallow\`.`
25601
25723
  };
25602
25724
  }
25603
- function parseScope(value) {
25604
- const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
25605
- if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
25606
- const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
25607
- return {
25608
- kinds: named,
25609
- reason: scoped[2].trim(),
25610
- unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
25611
- };
25612
- }
25613
- function invisibleTrailer(sha, line) {
25614
- return {
25615
- kind: "malformed-override-trailer",
25616
- paths: [],
25617
- detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
25618
- ${line}
25619
- but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
25620
- exist only to the regex that granted it. The trailer was chosen over a flag because it can be
25621
- AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
25622
- Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
25623
- trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
25624
- block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
25625
- This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
25626
- The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
25627
- };
25628
- }
25629
- function unknownScope(sha, unknown) {
25630
- return {
25631
- kind: "malformed-override-trailer",
25632
- paths: [],
25633
- detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
25634
- gate cannot report.
25635
- Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
25636
- Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
25637
- into a blanket exemption without anyone deciding it should.`
25638
- };
25639
- }
25640
- function readOverride(base, cwd) {
25641
- const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
25642
- const refusals = [];
25643
- let override = null;
25644
- for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
25645
- const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
25646
- if (!sha) continue;
25647
- if (override) break;
25648
- const value = (trailer ?? "").trim();
25649
- if (!value) {
25650
- const shaped = OVERRIDE_RE.exec(body ?? "");
25651
- if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
25652
- continue;
25653
- }
25654
- const { kinds, reason, unknown } = parseScope(value);
25655
- if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
25656
- else override = { sha, reason, kinds };
25657
- }
25658
- return { override, refusals };
25725
+ function readOverride2(base, cwd) {
25726
+ return readOverride(base, cwd);
25659
25727
  }
25660
25728
  function changedFilesSince(base, cwd) {
25661
- const raw = git(["diff", "--name-status", "-M", base, "--"], cwd).trim();
25729
+ const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
25662
25730
  const changed = raw ? raw.split("\n").map((line) => {
25663
25731
  const parts = line.split(" ");
25664
25732
  const norm = (p) => p.replace(/\\/g, "/");
@@ -25666,12 +25734,12 @@ function changedFilesSince(base, cwd) {
25666
25734
  if (parts.length > 2) row.from = norm(parts[1]);
25667
25735
  return row;
25668
25736
  }) : [];
25669
- const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
25737
+ const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
25670
25738
  return [...changed, ...untracked];
25671
25739
  }
25672
25740
  function blobAt(base, path2, cwd) {
25673
25741
  try {
25674
- return git(["show", `${base}:${path2}`], cwd);
25742
+ return git2(["show", `${base}:${path2}`], cwd);
25675
25743
  } catch {
25676
25744
  return null;
25677
25745
  }
@@ -25687,7 +25755,7 @@ function runTestPolicy(root, deps = {}) {
25687
25755
  before: blobAt(base, path2, root),
25688
25756
  after: readFileOrNull2((0, import_node_path22.join)(root, path2))
25689
25757
  }));
25690
- const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
25758
+ const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
25691
25759
  const present = (path2) => exists((0, import_node_path22.join)(root, path2));
25692
25760
  const removedByThisDiff = removedPaths(changed);
25693
25761
  const staleFindings = [];
@@ -25720,7 +25788,8 @@ function runTestPolicy(root, deps = {}) {
25720
25788
  const commandPolicy = evaluateTestCommandPolicy({
25721
25789
  paths: changed.map((f) => f.path),
25722
25790
  mandatory: policy.mandatory,
25723
- regulated: policy.declared !== false
25791
+ regulated: policy.declared !== false,
25792
+ override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
25724
25793
  });
25725
25794
  const result = {
25726
25795
  ok: findings.length === 0,
@@ -25742,7 +25811,7 @@ function runTestPolicy(root, deps = {}) {
25742
25811
  }
25743
25812
 
25744
25813
  // src/dist-drift.ts
25745
- var import_node_child_process15 = require("node:child_process");
25814
+ var import_node_child_process16 = require("node:child_process");
25746
25815
  var import_node_crypto7 = require("node:crypto");
25747
25816
  var import_node_fs27 = require("node:fs");
25748
25817
  var import_node_os13 = require("node:os");
@@ -25887,7 +25956,7 @@ function bomPathFor(root) {
25887
25956
  }
25888
25957
  }
25889
25958
  function rebuildTo(packageRoot, outDir) {
25890
- (0, import_node_child_process15.execFileSync)(process.execPath, ["build.mjs"], {
25959
+ (0, import_node_child_process16.execFileSync)(process.execPath, ["build.mjs"], {
25891
25960
  cwd: packageRoot,
25892
25961
  env: { ...process.env, MMI_DIST_OUTDIR: outDir },
25893
25962
  windowsHide: true,
@@ -27604,9 +27673,9 @@ ${SSH_RECIPE_AGENT_NOTE}`);
27604
27673
 
27605
27674
  // src/schedules-commands.ts
27606
27675
  var import_promises4 = require("node:fs/promises");
27607
- var import_node_child_process16 = require("node:child_process");
27676
+ var import_node_child_process17 = require("node:child_process");
27608
27677
  var import_node_util7 = require("node:util");
27609
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process16.execFile);
27678
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process17.execFile);
27610
27679
  var AWS_REGION = "eu-central-1";
27611
27680
  var AWS_TIMEOUT_MS = 3e4;
27612
27681
  var AWS_RETRY_DELAY_MS = 1500;
@@ -30932,6 +31001,9 @@ function buildPrMergeArgs(input) {
30932
31001
  if (input.auto) args.push("--auto");
30933
31002
  return args;
30934
31003
  }
31004
+ function buildPrDisableAutoArgs(input) {
31005
+ return ["pr", "merge", input.number, ...input.repoArgs, "--disable-auto"];
31006
+ }
30935
31007
  function basePolicyBlocksImmediateMerge(message) {
30936
31008
  return /base branch policy prohibits|protected branch|required status check|merge queue/i.test(message);
30937
31009
  }
@@ -31428,19 +31500,19 @@ function formatWorktreeRemovalFailureDetail(options) {
31428
31500
  const detail = options.removeError.trim() || "git worktree remove exited nonzero";
31429
31501
  return `${detail}; registered=${options.registered ? "yes" : "no"}; pathExists=${options.pathExists ? "yes" : "no"}; path=${options.wtPath}`;
31430
31502
  }
31431
- async function resolveMergeBaseTimestampMs(git2, baseRef, branch) {
31503
+ async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
31432
31504
  try {
31433
- const mergeBase = (await git2(["merge-base", baseRef, branch])).trim();
31505
+ const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
31434
31506
  if (!mergeBase) return 0;
31435
- const sec = parseCommitTimestampSec(await git2(["show", "-s", "--format=%ct", mergeBase]));
31507
+ const sec = parseCommitTimestampSec(await git3(["show", "-s", "--format=%ct", mergeBase]));
31436
31508
  return sec ? sec * 1e3 : 0;
31437
31509
  } catch {
31438
31510
  return 0;
31439
31511
  }
31440
31512
  }
31441
- async function verifyBranchHead(git2, branch, expectedHeadOid) {
31513
+ async function verifyBranchHead(git3, branch, expectedHeadOid) {
31442
31514
  if (!expectedHeadOid) return { ok: false, reason: "branch-head-unverified" };
31443
- const currentHead = (await git2(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
31515
+ const currentHead = (await git3(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
31444
31516
  if (!currentHead) return { ok: false, reason: "branch-head-unverified" };
31445
31517
  if (currentHead !== expectedHeadOid) {
31446
31518
  return { ok: false, reason: "unpushed-branch", error: `${currentHead} != ${expectedHeadOid}` };
@@ -31455,17 +31527,17 @@ async function teardownWorktreeStage(worktreePath) {
31455
31527
  return { status: "failed", error: e instanceof Error ? e.message : String(e) };
31456
31528
  }
31457
31529
  }
31458
- async function removeWorktreeWithReconcile(wtPath, git2, listWorktrees, pathExists) {
31530
+ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExists) {
31459
31531
  const attemptRemove = async () => {
31460
31532
  try {
31461
- await git2(["worktree", "remove", "--force", wtPath]);
31533
+ await git3(["worktree", "remove", "--force", wtPath]);
31462
31534
  return void 0;
31463
31535
  } catch (e) {
31464
31536
  return formatGitCommandError(e);
31465
31537
  }
31466
31538
  };
31467
31539
  const pruneAndVerify = async (reason) => {
31468
- await git2(["worktree", "prune"]).catch(() => "");
31540
+ await git3(["worktree", "prune"]).catch(() => "");
31469
31541
  let worktrees2;
31470
31542
  try {
31471
31543
  worktrees2 = await listWorktrees();
@@ -31495,7 +31567,7 @@ async function removeWorktreeWithReconcile(wtPath, git2, listWorktrees, pathExis
31495
31567
  };
31496
31568
  }
31497
31569
  if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-unregistered");
31498
- await git2(["worktree", "prune"]).catch(() => "");
31570
+ await git3(["worktree", "prune"]).catch(() => "");
31499
31571
  try {
31500
31572
  worktrees = await listWorktrees();
31501
31573
  } catch (e) {
@@ -31582,8 +31654,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31582
31654
  return report;
31583
31655
  }
31584
31656
  const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, { pathExists });
31585
- const git2 = (args) => safeCwd ? execGit(["-C", safeCwd, ...args]) : execGit(args);
31586
- const headCheck = await verifyBranchHead(git2, branch, options.expectedHeadOid);
31657
+ const git3 = (args) => safeCwd ? execGit(["-C", safeCwd, ...args]) : execGit(args);
31658
+ const headCheck = await verifyBranchHead(git3, branch, options.expectedHeadOid);
31587
31659
  if (!headCheck.ok) {
31588
31660
  report.localBranch = {
31589
31661
  name: branch,
@@ -31646,8 +31718,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31646
31718
  }
31647
31719
  const removal = await removeWorktreeWithReconcile(
31648
31720
  wtPath,
31649
- git2,
31650
- async () => parseGitWorktreePorcelain(await git2(["worktree", "list", "--porcelain"])),
31721
+ git3,
31722
+ async () => parseGitWorktreePorcelain(await git3(["worktree", "list", "--porcelain"])),
31651
31723
  pathExists
31652
31724
  );
31653
31725
  if (removal.status === "failed") {
@@ -31680,10 +31752,10 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31680
31752
  }
31681
31753
  }
31682
31754
  try {
31683
- await git2(["branch", "-D", branch]);
31755
+ await git3(["branch", "-D", branch]);
31684
31756
  report.localBranch = { name: branch, status: "deleted" };
31685
31757
  } catch (e) {
31686
- const remaining = await git2(["branch", "--list", branch]).catch(() => "");
31758
+ const remaining = await git3(["branch", "--list", branch]).catch(() => "");
31687
31759
  report.localBranch = remaining.trim() ? { name: branch, status: "failed", error: e instanceof Error ? e.message : String(e) } : { name: branch, status: "already-gone" };
31688
31760
  }
31689
31761
  return report;
@@ -31717,6 +31789,17 @@ function renderPrMergeCleanupLines(cleanup) {
31717
31789
  return lines;
31718
31790
  }
31719
31791
 
31792
+ // src/utf8-receipt.ts
31793
+ var import_node_fs37 = require("node:fs");
31794
+ var import_node_path35 = require("node:path");
31795
+ function writeUtf8Receipt(path2, text) {
31796
+ (0, import_node_fs37.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
31797
+ const body = text.endsWith("\n") ? text : `${text}
31798
+ `;
31799
+ (0, import_node_fs37.writeFileSync)(path2, body, "utf8");
31800
+ return Buffer.byteLength(body, "utf8");
31801
+ }
31802
+
31720
31803
  // src/board-commands.ts
31721
31804
  function refuseRateLimited(e, json) {
31722
31805
  if (!isRateLimitedError(e)) return false;
@@ -31744,7 +31827,9 @@ function registerBoardCommands(program3) {
31744
31827
  includeAllBodies: o.bodies,
31745
31828
  allowPartial: o.allowPartial
31746
31829
  }, o.direct ? {} : { snapshot: registryClientDeps(config) });
31747
- console.log(o.json ? JSON.stringify(report) : renderBoardReport(report));
31830
+ const output = o.json ? JSON.stringify(report) : renderBoardReport(report);
31831
+ if (!o.out) console.log(output);
31832
+ else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
31748
31833
  } catch (e) {
31749
31834
  return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
31750
31835
  }
@@ -31753,7 +31838,7 @@ function registerBoardCommands(program3) {
31753
31838
  return alreadyClaimed ? `Check ${ref}: claimed and In Progress, no live contest - claim would renew the lease (nothing written)` : `Check ${ref}: free - claim would proceed (nothing written)`;
31754
31839
  }
31755
31840
  const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
31756
- board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n").action((o) => runBoardRead(o));
31841
+ board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nNever capture the JSON with a shell redirect on Windows: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
31757
31842
  withExamples(mutating(
31758
31843
  board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
31759
31844
  (_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
@@ -32416,7 +32501,7 @@ async function checkDocsIndexAtHead(opts, deps) {
32416
32501
  }
32417
32502
 
32418
32503
  // src/issue-commands.ts
32419
- var import_node_fs37 = require("node:fs");
32504
+ var import_node_fs38 = require("node:fs");
32420
32505
  var import_node_crypto11 = require("node:crypto");
32421
32506
 
32422
32507
  // src/learning-closure-rate.ts
@@ -32603,7 +32688,7 @@ async function editIssue(client, options, deps = {}) {
32603
32688
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
32604
32689
  const patch = {};
32605
32690
  let bodyChanged = false;
32606
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs37.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
32691
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
32607
32692
  if (options.titleFile !== void 0) {
32608
32693
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
32609
32694
  } else if (options.title !== void 0) {
@@ -33220,7 +33305,7 @@ function extendCreateCommand(issue2, batchAttach) {
33220
33305
  if (opts.batch) {
33221
33306
  let specs;
33222
33307
  try {
33223
- const raw = (0, import_node_fs37.readFileSync)(opts.batch, "utf8");
33308
+ const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
33224
33309
  specs = JSON.parse(raw);
33225
33310
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33226
33311
  } catch (e) {
@@ -33295,8 +33380,8 @@ ${lines}`, {
33295
33380
  }
33296
33381
 
33297
33382
  // src/train-commands.ts
33298
- var import_node_fs38 = require("node:fs");
33299
- var import_node_path35 = require("node:path");
33383
+ var import_node_fs39 = require("node:fs");
33384
+ var import_node_path36 = require("node:path");
33300
33385
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
33301
33386
  function resolveReleaseBumpIntent(raw) {
33302
33387
  const intent = typeof raw === "string" ? raw.trim() : "";
@@ -33307,7 +33392,7 @@ function resolveReleaseBumpIntent(raw) {
33307
33392
  }
33308
33393
  function readRepoVersion() {
33309
33394
  try {
33310
- return JSON.parse((0, import_node_fs38.readFileSync)((0, import_node_path35.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
33395
+ return JSON.parse((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
33311
33396
  } catch {
33312
33397
  return void 0;
33313
33398
  }
@@ -33464,9 +33549,9 @@ function registerDeployCommands(program3) {
33464
33549
  }
33465
33550
 
33466
33551
  // src/discovery-commands.ts
33467
- var import_node_fs39 = require("node:fs");
33552
+ var import_node_fs40 = require("node:fs");
33468
33553
  var import_node_os17 = require("node:os");
33469
- var import_node_path36 = require("node:path");
33554
+ var import_node_path37 = require("node:path");
33470
33555
  var GC_GH_TIMEOUT_MS2 = 2e4;
33471
33556
  async function collectStatus() {
33472
33557
  const repo = await resolveRepo();
@@ -33656,8 +33741,8 @@ async function collectOnboardStatus(opts = {}) {
33656
33741
  }
33657
33742
  const home = (0, import_node_os17.homedir)();
33658
33743
  const plugin = onboardPluginGate({
33659
- readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
33660
- readSettings: () => readFileSyncSafe((0, import_node_path36.join)(home, ".claude", "settings.json"), import_node_fs39.readFileSync)
33744
+ readKnown: () => readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
33745
+ readSettings: () => readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync)
33661
33746
  });
33662
33747
  return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
33663
33748
  }
@@ -33865,12 +33950,18 @@ function catalogCommandPaths(manifest) {
33865
33950
  function nearestExplainTargets(query, paths, limit = 5) {
33866
33951
  const parentPath = query.includes(" ") ? query.slice(0, query.lastIndexOf(" ")) : void 0;
33867
33952
  const leaf = query.includes(" ") ? query.slice(query.lastIndexOf(" ") + 1) : query;
33868
- const guide = unknownCommandDomainGuide(parentPath, leaf);
33869
- if (guide) return guide.expected.slice(0, limit);
33870
33953
  const all = [...paths];
33871
33954
  const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
33955
+ const siblings = parentPath ? all.filter((p) => p !== query && p.startsWith(`${parentPath} `) && !p.slice(parentPath.length + 1).includes(" ")).sort() : [];
33956
+ const withSiblings = (primary) => {
33957
+ const out2 = [...primary];
33958
+ for (const sibling of siblings) if (!out2.includes(sibling)) out2.push(sibling);
33959
+ return out2.slice(0, limit);
33960
+ };
33961
+ const guide = unknownCommandDomainGuide(parentPath, leaf);
33962
+ if (guide) return withSiblings(guide.expected);
33872
33963
  const exactLeaf = all.filter((p) => p !== query && leafOf(p) === leaf).sort();
33873
- if (exactLeaf.length) return exactLeaf.slice(0, limit);
33964
+ if (exactLeaf.length) return withSiblings(exactLeaf);
33874
33965
  const out = [];
33875
33966
  const nearPath = didYouMean(query, all);
33876
33967
  if (nearPath) out.push(nearPath);
@@ -33881,7 +33972,7 @@ function nearestExplainTargets(query, paths, limit = 5) {
33881
33972
  if (out.length >= limit) break;
33882
33973
  }
33883
33974
  }
33884
- return out.slice(0, limit);
33975
+ return withSiblings(out);
33885
33976
  }
33886
33977
  function explainCommandManifest(manifest, command, opts = {}) {
33887
33978
  const base = {
@@ -34420,8 +34511,8 @@ function registerPrLifecycleCommands(program3) {
34420
34511
  }
34421
34512
 
34422
34513
  // src/post-merge-recon.ts
34423
- var import_node_fs40 = require("node:fs");
34424
- var import_node_path37 = require("node:path");
34514
+ var import_node_fs41 = require("node:fs");
34515
+ var import_node_path38 = require("node:path");
34425
34516
 
34426
34517
  // src/cross-repo-filing-issue.ts
34427
34518
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
@@ -34587,16 +34678,16 @@ function buildPostMergeReconRecovery(input) {
34587
34678
  }
34588
34679
  function writePostMergeReconRecovery(cwd, recovery) {
34589
34680
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
34590
- (0, import_node_fs40.mkdirSync)((0, import_node_path37.dirname)(path2), { recursive: true });
34591
- (0, import_node_fs40.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
34681
+ (0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
34682
+ (0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
34592
34683
  `, "utf8");
34593
34684
  return path2;
34594
34685
  }
34595
34686
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
34596
34687
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
34597
- if (!(0, import_node_fs40.existsSync)(path2)) return;
34688
+ if (!(0, import_node_fs41.existsSync)(path2)) return;
34598
34689
  try {
34599
- (0, import_node_fs40.unlinkSync)(path2);
34690
+ (0, import_node_fs41.unlinkSync)(path2);
34600
34691
  } catch {
34601
34692
  }
34602
34693
  }
@@ -37713,10 +37804,10 @@ function ghAccountCaveat(announcedLogin, accounts) {
37713
37804
  }
37714
37805
 
37715
37806
  // src/doctor-io.ts
37716
- var import_node_fs41 = require("node:fs");
37807
+ var import_node_fs42 = require("node:fs");
37717
37808
  var import_node_os18 = require("node:os");
37718
- var import_node_path38 = require("node:path");
37719
- var import_node_child_process17 = require("node:child_process");
37809
+ var import_node_path39 = require("node:path");
37810
+ var import_node_child_process18 = require("node:child_process");
37720
37811
  var import_node_util8 = require("node:util");
37721
37812
 
37722
37813
  // src/discard-sink.ts
@@ -37725,27 +37816,27 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
37725
37816
  }
37726
37817
 
37727
37818
  // src/doctor-io.ts
37728
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
37819
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
37729
37820
  function execFileCapture(file, args, opts = {}) {
37730
37821
  const sink = nodeDiscardSinkPath();
37731
- const inFd = (0, import_node_fs41.openSync)(sink, "r");
37732
- const errFd = (0, import_node_fs41.openSync)(sink, "w");
37822
+ const inFd = (0, import_node_fs42.openSync)(sink, "r");
37823
+ const errFd = (0, import_node_fs42.openSync)(sink, "w");
37733
37824
  try {
37734
- return (0, import_node_child_process17.execFileSync)(file, args, {
37825
+ return (0, import_node_child_process18.execFileSync)(file, args, {
37735
37826
  ...opts,
37736
37827
  encoding: "utf8",
37737
37828
  stdio: [inFd, "pipe", errFd]
37738
37829
  });
37739
37830
  } finally {
37740
- (0, import_node_fs41.closeSync)(inFd);
37741
- (0, import_node_fs41.closeSync)(errFd);
37831
+ (0, import_node_fs42.closeSync)(inFd);
37832
+ (0, import_node_fs42.closeSync)(errFd);
37742
37833
  }
37743
37834
  }
37744
37835
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
37745
37836
  function installedClaudePluginVersion() {
37746
37837
  try {
37747
37838
  const file = JSON.parse(
37748
- (0, import_node_fs41.readFileSync)((0, import_node_path38.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
37839
+ (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
37749
37840
  );
37750
37841
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
37751
37842
  if (versions.length === 0) return void 0;
@@ -37756,7 +37847,7 @@ function installedClaudePluginVersion() {
37756
37847
  }
37757
37848
  function manifestVersion(path2) {
37758
37849
  try {
37759
- const manifest = JSON.parse((0, import_node_fs41.readFileSync)(path2, "utf8"));
37850
+ const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
37760
37851
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
37761
37852
  } catch {
37762
37853
  return void 0;
@@ -37765,12 +37856,12 @@ function manifestVersion(path2) {
37765
37856
  function readHermesPluginEvidence(env = process.env) {
37766
37857
  const host = hermesConfigRoot(env);
37767
37858
  const root = hermesPluginRoot(env);
37768
- const installRecordPresent = (0, import_node_fs41.existsSync)(root);
37769
- const manifestPath = (0, import_node_path38.join)(root, "plugin.yaml");
37859
+ const installRecordPresent = (0, import_node_fs42.existsSync)(root);
37860
+ const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
37770
37861
  let installedVersion;
37771
37862
  let manifest = "missing";
37772
37863
  try {
37773
- const text = (0, import_node_fs41.readFileSync)(manifestPath, "utf8");
37864
+ const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
37774
37865
  let version;
37775
37866
  try {
37776
37867
  const parsed = JSON.parse(text).version;
@@ -37784,20 +37875,20 @@ function readHermesPluginEvidence(env = process.env) {
37784
37875
  if (version) {
37785
37876
  installedVersion = version;
37786
37877
  manifest = "valid";
37787
- } else if ((0, import_node_fs41.existsSync)(manifestPath)) manifest = "invalid";
37878
+ } else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
37788
37879
  } catch {
37789
- if ((0, import_node_fs41.existsSync)(manifestPath)) manifest = "invalid";
37880
+ if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
37790
37881
  }
37791
37882
  let skills = false;
37792
37883
  try {
37793
- skills = (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, "skills")) && (0, import_node_fs41.statSync)((0, import_node_path38.join)(root, "skills")).isDirectory();
37884
+ skills = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "skills")) && (0, import_node_fs42.statSync)((0, import_node_path39.join)(root, "skills")).isDirectory();
37794
37885
  } catch {
37795
37886
  }
37796
37887
  return {
37797
- hostPresent: (0, import_node_fs41.existsSync)(host),
37888
+ hostPresent: (0, import_node_fs42.existsSync)(host),
37798
37889
  installRecordPresent,
37799
37890
  manifest,
37800
- payloadPresent: (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, "__init__.py")) && skills && manifest === "valid",
37891
+ payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
37801
37892
  ...installedVersion ? { installedVersion } : {}
37802
37893
  };
37803
37894
  }
@@ -37805,7 +37896,7 @@ function installedSurfacePluginVersion(surface) {
37805
37896
  const token = surfaceToken(surface);
37806
37897
  if (token === "kilo") {
37807
37898
  try {
37808
- const stamp = (0, import_node_fs41.readFileSync)((0, import_node_path38.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
37899
+ const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
37809
37900
  return stamp || void 0;
37810
37901
  } catch {
37811
37902
  return void 0;
@@ -37813,13 +37904,13 @@ function installedSurfacePluginVersion(surface) {
37813
37904
  }
37814
37905
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
37815
37906
  if (token === "cursor") {
37816
- return manifestVersion((0, import_node_path38.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
37907
+ return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
37817
37908
  }
37818
37909
  if (token === "jervcode") {
37819
37910
  return installedJervCodePackageVersion();
37820
37911
  }
37821
37912
  if (token === "kimi") {
37822
- return manifestVersion((0, import_node_path38.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
37913
+ return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
37823
37914
  }
37824
37915
  if (token === "claude") return installedClaudePluginVersion();
37825
37916
  if (token !== "codex") return void 0;
@@ -37853,13 +37944,13 @@ function worktreeRootSync() {
37853
37944
  }
37854
37945
  var gitignorePath = () => {
37855
37946
  const root = worktreeRootSync();
37856
- return root === null ? null : (0, import_node_path38.join)(root, ".gitignore");
37947
+ return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
37857
37948
  };
37858
37949
  function readGitignore() {
37859
37950
  const path2 = gitignorePath();
37860
37951
  if (path2 === null) return null;
37861
37952
  try {
37862
- return (0, import_node_fs41.readFileSync)(path2, "utf8");
37953
+ return (0, import_node_fs42.readFileSync)(path2, "utf8");
37863
37954
  } catch {
37864
37955
  return null;
37865
37956
  }
@@ -37868,14 +37959,14 @@ function writeGitignore(content) {
37868
37959
  const path2 = gitignorePath();
37869
37960
  if (path2 === null) return false;
37870
37961
  try {
37871
- (0, import_node_fs41.writeFileSync)(path2, content, "utf8");
37962
+ (0, import_node_fs42.writeFileSync)(path2, content, "utf8");
37872
37963
  return true;
37873
37964
  } catch {
37874
37965
  return false;
37875
37966
  }
37876
37967
  }
37877
37968
  function lineEndingState(root) {
37878
- const attributesPresent = (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, ".gitattributes"));
37969
+ const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
37879
37970
  try {
37880
37971
  const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
37881
37972
  windowsHide: true
@@ -37942,8 +38033,8 @@ ${r.stderr ?? ""}`).catch(() => "");
37942
38033
  function ghMultiAccountCaveat(announcedLogin) {
37943
38034
  try {
37944
38035
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
37945
- if (!hostsPath || !(0, import_node_fs42.existsSync)(hostsPath)) return void 0;
37946
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs42.readFileSync)(hostsPath, "utf8")));
38036
+ if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
38037
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
37947
38038
  } catch {
37948
38039
  return void 0;
37949
38040
  }
@@ -37951,7 +38042,7 @@ function ghMultiAccountCaveat(announcedLogin) {
37951
38042
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
37952
38043
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
37953
38044
  function envHealLockPath(home) {
37954
- return (0, import_node_path39.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38045
+ return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
37955
38046
  }
37956
38047
  async function withEnvHealLock(what, run) {
37957
38048
  try {
@@ -38088,7 +38179,7 @@ function mmiDoctorDeps(opts = {}) {
38088
38179
  );
38089
38180
  const result = applyPluginCachePlan(
38090
38181
  plan,
38091
- (p) => (0, import_node_fs42.rmSync)(p, { recursive: true }),
38182
+ (p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
38092
38183
  stagingApplyFsGuard(configRoot)
38093
38184
  );
38094
38185
  return {
@@ -38126,7 +38217,7 @@ function mmiDoctorDeps(opts = {}) {
38126
38217
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38127
38218
  // get a permanent — demanding an artifact it never asked for.
38128
38219
  docsIndexState: (root) => {
38129
- if (!(0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, DOCS_INDEX_PATH))) return void 0;
38220
+ if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
38130
38221
  const real = createDocsIndexDeps(root);
38131
38222
  let docs2;
38132
38223
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38135,7 +38226,7 @@ function mmiDoctorDeps(opts = {}) {
38135
38226
  },
38136
38227
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38137
38228
  healDocsIndex: (root) => {
38138
- if (!(0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38229
+ if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38139
38230
  const real = createDocsIndexDeps(root);
38140
38231
  let docs2;
38141
38232
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38238,7 +38329,9 @@ async function requireFreshTrainCli(commandName) {
38238
38329
  const converge = await selfConvergeTrainCli({
38239
38330
  currentVersion: resolveClientVersion(),
38240
38331
  releasedVersion: report.releasedVersion,
38241
- commandName
38332
+ commandName,
38333
+ // The typed, house-prefixed argv — never the spliced process.argv (#5813).
38334
+ argv: INVOKED_ARGV
38242
38335
  });
38243
38336
  if (converge.kind === "failed") {
38244
38337
  throw new Error(`${commandName}: ${converge.error}. ${staleTrainCliMessage(report, commandName)}`);
@@ -38333,7 +38426,10 @@ function positionalTargetForm(cmd, opts = {}) {
38333
38426
  const args = cmd.registeredArguments ?? [];
38334
38427
  const first = args[0];
38335
38428
  if (!first) return void 0;
38336
- return formatPositionalTarget(canonicalPathFor(commandPath2(cmd)) ?? commandPath2(cmd), first.name(), opts);
38429
+ return formatPositionalTarget(canonicalPathFor(commandPath2(cmd)) ?? commandPath2(cmd), first.name(), {
38430
+ ...opts,
38431
+ ownFlags: commandOwnLongFlags(cmd)
38432
+ });
38337
38433
  }
38338
38434
  function unknownCommandCandidates(parent, allPaths) {
38339
38435
  if (!parent) return [...allPaths];
@@ -38428,6 +38524,7 @@ function envelopeAwareWriteErr(str) {
38428
38524
  if (unknownFlagJsonHandled && argvWantsJson2()) return;
38429
38525
  process.stderr.write(str);
38430
38526
  }
38527
+ var INVOKED_ARGV = process.argv.slice(2);
38431
38528
  var program2 = new Command();
38432
38529
  program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
38433
38530
  program2.addHelpText(
@@ -38456,19 +38553,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
38456
38553
  });
38457
38554
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
38458
38555
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
38459
- const path2 = (0, import_node_path39.join)(process.cwd(), ".gitignore");
38460
- const current = (0, import_node_fs42.existsSync)(path2) ? (0, import_node_fs42.readFileSync)(path2, "utf8") : null;
38556
+ const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
38557
+ const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
38461
38558
  const plan = planManagedGitignore(current);
38462
38559
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
38463
38560
  if (opts.json) {
38464
- if (opts.write && plan.changed) (0, import_node_fs42.writeFileSync)(path2, plan.content, "utf8");
38561
+ if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
38465
38562
  console.log(JSON.stringify(plan, null, 2));
38466
38563
  if (!opts.write && plan.changed) process.exitCode = 1;
38467
38564
  return;
38468
38565
  }
38469
38566
  if (opts.write) {
38470
38567
  if (plan.changed) {
38471
- (0, import_node_fs42.writeFileSync)(path2, plan.content, "utf8");
38568
+ (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
38472
38569
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
38473
38570
  } else {
38474
38571
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -38654,7 +38751,7 @@ function scheduleRelatedDiscovery(o) {
38654
38751
  try {
38655
38752
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
38656
38753
  if (o.repo) args.push("--repo", o.repo);
38657
- spawnDetachedSelf(args, { spawn: import_node_child_process18.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38754
+ spawnDetachedSelf(args, { spawn: import_node_child_process19.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38658
38755
  } catch {
38659
38756
  }
38660
38757
  }
@@ -39044,7 +39141,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
39044
39141
  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`);
39045
39142
  if (o.secretsFile) {
39046
39143
  try {
39047
- vars.push(`secrets=${(0, import_node_fs42.readFileSync)(o.secretsFile, "utf8")}`);
39144
+ vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
39048
39145
  } catch (e) {
39049
39146
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
39050
39147
  }
@@ -39869,11 +39966,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
39869
39966
  }
39870
39967
  });
39871
39968
  async function listCiWorkflowPaths(cwd = process.cwd()) {
39872
- const wfDir = (0, import_node_path39.join)(cwd, ".github", "workflows");
39873
- if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
39874
- return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39969
+ const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
39970
+ if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
39971
+ return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39875
39972
  try {
39876
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(wfDir, name), "utf8"));
39973
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
39877
39974
  } catch {
39878
39975
  return true;
39879
39976
  }
@@ -39939,16 +40036,16 @@ function ciAuditDeps() {
39939
40036
  // gate re-seed step is skipped gracefully rather than failing mid-run.
39940
40037
  readSeedFile: (path2) => {
39941
40038
  if (!root) return null;
39942
- const fullPath = (0, import_node_path39.join)(root, path2);
39943
- return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
40039
+ const fullPath = (0, import_node_path40.join)(root, path2);
40040
+ return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
39944
40041
  }
39945
40042
  };
39946
40043
  }
39947
40044
  function hubRoot() {
39948
- const fromPkg = (0, import_node_path39.join)(__dirname, "..", "..");
40045
+ const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
39949
40046
  const marker = "skills/bootstrap/seeds/manifest.json";
39950
- if ((0, import_node_fs42.existsSync)((0, import_node_path39.join)(fromPkg, marker))) return fromPkg;
39951
- if ((0, import_node_fs42.existsSync)((0, import_node_path39.join)(process.cwd(), marker))) return process.cwd();
40047
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
40048
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
39952
40049
  return null;
39953
40050
  }
39954
40051
  async function waitLoopCorePool(label) {
@@ -40220,9 +40317,18 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
40220
40317
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
40221
40318
  if (result.status === "failed") process.exitCode = 1;
40222
40319
  });
40223
- jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
40320
+ jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
40224
40321
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
40225
40322
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
40323
+ if (o.disableAuto) {
40324
+ await execFileP2("gh", buildPrDisableAutoArgs({ number, repoArgs }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e) => {
40325
+ const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
40326
+ if (note) throw new Error(`gh pr merge ${number} --disable-auto: ${note}`);
40327
+ throw e;
40328
+ });
40329
+ console.log(JSON.stringify({ mergeStatus: "auto-merge-disabled", pr: number, ...o.repo ? { repo: o.repo } : {} }));
40330
+ return;
40331
+ }
40226
40332
  const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
40227
40333
  const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid,state", "--jq", "{head: .headRefName, base: .baseRefName, oid: .headRefOid, state: .state}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
40228
40334
  if (!isGitHubRateLimitError(e) || !repoForPostCleanup) throw e;
@@ -40249,7 +40355,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40249
40355
  const mergeSquashBody = squashBodyTextForMerge(
40250
40356
  closingGuardInput,
40251
40357
  method === "--squash",
40252
- o.squashBodyFile ? (0, import_node_fs42.readFileSync)(o.squashBodyFile, "utf8") : void 0
40358
+ o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
40253
40359
  );
40254
40360
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
40255
40361
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -40263,7 +40369,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40263
40369
  return;
40264
40370
  }
40265
40371
  if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
40266
- if (prMeta.state !== "MERGED") {
40372
+ if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
40267
40373
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
40268
40374
  const repoSettings = await defaultGitHubClient().rest("GET", `repos/${repoForPostCleanup}`);
40269
40375
  assertRemoteBranchPreservedOnMerge(repoForPostCleanup, repoSettings.delete_branch_on_merge);
@@ -40338,7 +40444,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40338
40444
  }
40339
40445
  if (!repoForPostCleanup) throw e;
40340
40446
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
40341
- const commitMessage = bodyFile ? (0, import_node_fs42.readFileSync)(bodyFile, "utf8") : void 0;
40447
+ const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
40342
40448
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
40343
40449
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
40344
40450
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -40427,7 +40533,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40427
40533
  preserveWorktree: o.preserveWorktree,
40428
40534
  gcAcknowledged: o.gc,
40429
40535
  expectedHeadOid: headRefOid,
40430
- pathExists: (p) => (0, import_node_fs42.existsSync)(p),
40536
+ pathExists: (p) => (0, import_node_fs43.existsSync)(p),
40431
40537
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout
40432
40538
  });
40433
40539
  } catch (e) {
@@ -40715,7 +40821,7 @@ for (const commandName of ["rcand", "release"]) {
40715
40821
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
40716
40822
  { flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
40717
40823
  { flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
40718
- { flags: "--retry-publish <run-id>", description: "with --apply, retry failed jobs of one proven Hub publish release run (#3949)" }
40824
+ { flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
40719
40825
  ];
40720
40826
  for (const f of RELEASE_ONLY_FLAGS) {
40721
40827
  if (commandName === "release") {
@@ -41014,8 +41120,7 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
41014
41120
  console.log("\nDry-run \u2014 re-run with --apply to patch merge settings and activate product rulesets (master-admin).");
41015
41121
  }
41016
41122
  }
41017
- const applyFailed = applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
41018
- if (!audit.ok || applyFailed) process.exitCode = 1;
41123
+ if (ciReconcileExitFailed({ apply: o.apply === true, audit, applyResults })) process.exitCode = 1;
41019
41124
  });
41020
41125
  registerBootstrapCommands(program2);
41021
41126
  var access = program2.command("access").description("org access audit (read-only)");
@@ -41064,12 +41169,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
41064
41169
  targets = resolution.targets;
41065
41170
  }
41066
41171
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
41067
- const fileMatrix = (0, import_node_fs42.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs42.readFileSync)("access-matrix.json", "utf8")) : {};
41172
+ const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
41068
41173
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
41069
41174
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
41070
- const fileContracts = (0, import_node_fs42.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs42.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
41175
+ const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
41071
41176
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
41072
- const sanctioned = (0, import_node_fs42.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs42.readFileSync)("access-matrix.json", "utf8")) : {};
41177
+ const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
41073
41178
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
41074
41179
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
41075
41180
  if (!report.ok) process.exitCode = 1;
@@ -41101,16 +41206,16 @@ function directoryBytes(path2) {
41101
41206
  let total = 0;
41102
41207
  let entries;
41103
41208
  try {
41104
- entries = (0, import_node_fs42.readdirSync)(path2, { withFileTypes: true });
41209
+ entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
41105
41210
  } catch {
41106
41211
  return 0;
41107
41212
  }
41108
41213
  for (const entry of entries) {
41109
- const child2 = (0, import_node_path39.join)(path2, entry.name);
41214
+ const child2 = (0, import_node_path40.join)(path2, entry.name);
41110
41215
  if (entry.isDirectory()) total += directoryBytes(child2);
41111
41216
  else {
41112
41217
  try {
41113
- total += (0, import_node_fs42.statSync)(child2).size;
41218
+ total += (0, import_node_fs43.statSync)(child2).size;
41114
41219
  } catch {
41115
41220
  }
41116
41221
  }
@@ -41118,25 +41223,25 @@ function directoryBytes(path2) {
41118
41223
  return total;
41119
41224
  }
41120
41225
  function listDirEntries(dir) {
41121
- return (0, import_node_fs42.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41226
+ return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41122
41227
  }
41123
41228
  function readInstalledPluginRefs(configRoot) {
41124
41229
  const p = installedPluginsPathForConfig(configRoot);
41125
- if (!(0, import_node_fs42.existsSync)(p)) return [];
41230
+ if (!(0, import_node_fs43.existsSync)(p)) return [];
41126
41231
  try {
41127
- return installedPluginPaths((0, import_node_fs42.readFileSync)(p, "utf8"));
41232
+ return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
41128
41233
  } catch {
41129
41234
  return null;
41130
41235
  }
41131
41236
  }
41132
41237
  function pluginCacheFsDeps(configRoot, dirBytes) {
41133
41238
  return {
41134
- exists: (p) => (0, import_node_fs42.existsSync)(p),
41135
- listVersionDirs: (root) => (0, import_node_fs42.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
41239
+ exists: (p) => (0, import_node_fs43.existsSync)(p),
41240
+ listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
41136
41241
  dirBytes,
41137
- listStagingDirs: (root) => (0, import_node_fs42.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41242
+ listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41138
41243
  try {
41139
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path39.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs42.statSync)(p).mtimeMs) };
41244
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
41140
41245
  } catch {
41141
41246
  return { name: d.name, mtimeMs: Date.now() };
41142
41247
  }
@@ -41150,10 +41255,10 @@ function stagingApplyFsGuard(configRoot) {
41150
41255
  return {
41151
41256
  referencedPaths: () => readInstalledPluginRefs(configRoot),
41152
41257
  mtimeMs: (name) => {
41153
- const p = (0, import_node_path39.join)(stagingRoot, name);
41154
- if (!(0, import_node_fs42.existsSync)(p)) return null;
41258
+ const p = (0, import_node_path40.join)(stagingRoot, name);
41259
+ if (!(0, import_node_fs43.existsSync)(p)) return null;
41155
41260
  try {
41156
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs42.statSync)(q).mtimeMs);
41261
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
41157
41262
  } catch {
41158
41263
  return null;
41159
41264
  }
@@ -41179,7 +41284,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
41179
41284
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
41180
41285
  );
41181
41286
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
41182
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs42.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
41287
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
41183
41288
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
41184
41289
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
41185
41290
  else console.log(renderPluginCachePlan(plan, result));