@mutmutco/cli 4.2.0 → 4.2.1

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 +279 -229
  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");
@@ -5538,7 +5538,7 @@ function commandLadderHint() {
5538
5538
  }
5539
5539
 
5540
5540
  // src/index.ts
5541
- var import_node_path39 = require("node:path");
5541
+ var import_node_path40 = require("node:path");
5542
5542
 
5543
5543
  // src/merge-ci-policy.ts
5544
5544
  function resolveMergeCiPolicy(input) {
@@ -11762,10 +11762,10 @@ var rollout_plan_default = {
11762
11762
  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
11763
  },
11764
11764
  baseline: {
11765
- version: "4.2.0",
11766
- tag: "v4.2.0",
11767
- commit: "d4802646ffca",
11768
- npm: "@mutmutco/cli@4.2.0"
11765
+ version: "4.2.1",
11766
+ tag: "v4.2.1",
11767
+ commit: "66e67b6905a1",
11768
+ npm: "@mutmutco/cli@4.2.1"
11769
11769
  },
11770
11770
  exitCriterion: "fleet-n-of-n",
11771
11771
  hubOnlyShortcut: "forbidden",
@@ -11782,14 +11782,14 @@ var rollout_plan_default = {
11782
11782
  repo: "mutmutco/mmi-hub",
11783
11783
  role: "canary",
11784
11784
  schedule: "train",
11785
- v3Target: "v4.2.0"
11785
+ v3Target: "v4.2.1"
11786
11786
  }
11787
11787
  ],
11788
11788
  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
11789
  rollback: {
11790
11790
  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)"
11791
+ mechanism: "npm dist-tag latest -> 4.2.1 and redeploy the Hub Lambda from tag v4.2.1 (66e67b6905a1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
11792
+ v3Target: "v4.2.1 (@mutmutco/cli@4.2.1, tag commit 66e67b6905a1 \u2014 last known-good release carrying the repo-index v4-only contract)"
11793
11793
  }
11794
11794
  },
11795
11795
  {
@@ -14739,10 +14739,16 @@ async function correlateWorkflowRun(deps, args) {
14739
14739
  async function watchTenantRun(deps, runId, repo = HUB_REPO3) {
14740
14740
  if (runId == null) return "pending";
14741
14741
  let watchSaidSuccess = true;
14742
+ const startedAt = Date.now();
14743
+ deps.warn?.(`watching workflow run ${runId} (${repo})`);
14744
+ 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;
14745
+ heartbeat?.unref();
14742
14746
  try {
14743
14747
  await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
14744
14748
  } catch {
14745
14749
  watchSaidSuccess = false;
14750
+ } finally {
14751
+ if (heartbeat) clearInterval(heartbeat);
14746
14752
  }
14747
14753
  const sleep2 = resolveSleep(deps);
14748
14754
  let confirmError;
@@ -16338,8 +16344,9 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16338
16344
  }
16339
16345
  };
16340
16346
  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`);
16347
+ const retryableConclusion = run.conclusion === "failure" || run.conclusion === "cancelled";
16348
+ if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || !retryableConclusion) {
16349
+ throw new Error(`run ${runId} is not a completed failed or cancelled Hub publish release run; nothing was written`);
16343
16350
  }
16344
16351
  const tag = run.headBranch ?? "";
16345
16352
  const tagSha = run.headSha ?? "";
@@ -16350,13 +16357,16 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16350
16357
  throw new Error(`run ${runId} does not match origin ${tag}; nothing was written`);
16351
16358
  }
16352
16359
  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
- }
16360
+ await deps.run("gh", [
16361
+ "run",
16362
+ "rerun",
16363
+ String(runId),
16364
+ "--repo",
16365
+ ctx.repo,
16366
+ ...run.conclusion === "failure" ? ["--failed"] : []
16367
+ ]);
16368
+ if (options.watch && await watchTenantRun(deps, runId, ctx.repo) !== "success") {
16369
+ throw new Error(`publish run ${runId} did not finish successfully after retry`);
16360
16370
  }
16361
16371
  return {
16362
16372
  command: "release-retry-publish",
@@ -16366,7 +16376,7 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
16366
16376
  runId,
16367
16377
  runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
16368
16378
  status: options.watch ? "success" : "pending",
16369
- note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} failed jobs queued for retry`
16379
+ note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} queued for retry`
16370
16380
  };
16371
16381
  }
16372
16382
  async function runTrainApplyPipeline(mode, input) {
@@ -17707,6 +17717,12 @@ async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
17707
17717
  return result;
17708
17718
  }
17709
17719
  }
17720
+ function ciReconcileExitFailed(input) {
17721
+ if (!input.apply || input.applyResults.length === 0) {
17722
+ return !input.audit.ok || input.applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
17723
+ }
17724
+ return input.applyResults.some((result) => result.errors.length > 0 || result.postApply == null || result.postApply.state !== "clean" && result.postApply.state !== "repaired");
17725
+ }
17710
17726
  async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
17711
17727
  if (authority.coversReleaseBranches) {
17712
17728
  const prOnly = authority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context));
@@ -20933,11 +20949,11 @@ function findBoardItem(items, selector, board) {
20933
20949
  }
20934
20950
  async function resolveCurrentRepo(options, deps) {
20935
20951
  if (options.repo) return options.repo;
20936
- const git2 = deps.git ?? defaultGit;
20952
+ const git3 = deps.git ?? defaultGit;
20937
20953
  let gitFailure;
20938
20954
  let originUrl = "";
20939
20955
  try {
20940
- originUrl = await git2(["remote", "get-url", "origin"]);
20956
+ originUrl = await git3(["remote", "get-url", "origin"]);
20941
20957
  } catch (e) {
20942
20958
  const read = gitReadState(e);
20943
20959
  if (read.state === "failed") gitFailure = read.detail;
@@ -23804,31 +23820,31 @@ var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
23804
23820
  function checkHotfixCoverage(options = {}) {
23805
23821
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
23806
23822
  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"] }));
23823
+ 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
23824
  const revList = (range) => {
23809
- const out = git2(["rev-list", "--no-merges", range]).trim();
23825
+ const out = git3(["rev-list", "--no-merges", range]).trim();
23810
23826
  return out ? out.split("\n") : [];
23811
23827
  };
23812
23828
  const isAncestor = (sha, ref) => {
23813
23829
  try {
23814
- git2(["merge-base", "--is-ancestor", sha, ref]);
23830
+ git3(["merge-base", "--is-ancestor", sha, ref]);
23815
23831
  return true;
23816
23832
  } catch {
23817
23833
  return false;
23818
23834
  }
23819
23835
  };
23820
23836
  const patchId = (sha) => {
23821
- const diff = git2(["show", "--no-color", "--pretty=format:", sha]);
23837
+ const diff = git3(["show", "--no-color", "--pretty=format:", sha]);
23822
23838
  if (!diff.trim()) return null;
23823
- const out = git2(["patch-id", "--stable"], { input: diff }).trim();
23839
+ const out = git3(["patch-id", "--stable"], { input: diff }).trim();
23824
23840
  return out ? out.split(" ")[0] : null;
23825
23841
  };
23826
23842
  const changedPaths = (sha) => {
23827
- const out = git2(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
23843
+ const out = git3(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
23828
23844
  return out ? out.split("\n") : [];
23829
23845
  };
23830
23846
  const cherrySources = (sha) => {
23831
- const message = git2(["log", "-1", "--format=%B", sha]);
23847
+ const message = git3(["log", "-1", "--format=%B", sha]);
23832
23848
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
23833
23849
  };
23834
23850
  const mainOnly = revList(`${rcRef}..${mainRef}`);
@@ -23843,7 +23859,7 @@ function checkHotfixCoverage(options = {}) {
23843
23859
  }
23844
23860
  };
23845
23861
  const commits = mainOnly.map((sha) => {
23846
- const subject = git2(["log", "-1", "--format=%s", sha]).trim();
23862
+ const subject = git3(["log", "-1", "--format=%s", sha]).trim();
23847
23863
  const paths = changedPaths(sha);
23848
23864
  if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
23849
23865
  return { sha, subject, coverage: "exempt-distribution" };
@@ -23872,21 +23888,21 @@ function checkHotfixCoverage(options = {}) {
23872
23888
  }
23873
23889
  function checkHotfixCarries(options) {
23874
23890
  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"] }));
23891
+ 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
23892
  const isAncestor = (sha, ref) => {
23877
23893
  try {
23878
- git2(["merge-base", "--is-ancestor", sha, ref]);
23894
+ git3(["merge-base", "--is-ancestor", sha, ref]);
23879
23895
  return true;
23880
23896
  } catch {
23881
23897
  return false;
23882
23898
  }
23883
23899
  };
23884
23900
  const revList = (range) => {
23885
- const out = git2(["rev-list", "--no-merges", range]).trim();
23901
+ const out = git3(["rev-list", "--no-merges", range]).trim();
23886
23902
  return out ? out.split("\n") : [];
23887
23903
  };
23888
23904
  const cherrySources = (sha) => {
23889
- const message = git2(["log", "-1", "--format=%B", sha]);
23905
+ const message = git3(["log", "-1", "--format=%B", sha]);
23890
23906
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
23891
23907
  };
23892
23908
  const carried = /* @__PURE__ */ new Set();
@@ -25074,12 +25090,25 @@ function runSpawnPolicy(root) {
25074
25090
  }
25075
25091
 
25076
25092
  // src/test-policy-core.ts
25077
- var import_node_child_process14 = require("node:child_process");
25093
+ var import_node_child_process15 = require("node:child_process");
25078
25094
  var import_node_fs25 = require("node:fs");
25079
25095
  var import_node_path22 = require("node:path");
25080
25096
 
25081
25097
  // ../scripts/test-command-policy-core.mjs
25098
+ var import_node_child_process14 = require("node:child_process");
25082
25099
  var TEST_COMMAND_CLASS = "test";
25100
+ var TRAILER_KEY = "Test-Policy-Override";
25101
+ var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
25102
+ var REC = "";
25103
+ var FLD = "";
25104
+ var WAIVABLE_KINDS = [
25105
+ "mandatory-zone-untested",
25106
+ "unrequested-test-file",
25107
+ "protected-removed",
25108
+ "stale-protected-entry",
25109
+ "stale-satisfied-by"
25110
+ ];
25111
+ var TEST_WORK_KIND = "unrequested-test-file";
25083
25112
  function translateGlob(glob) {
25084
25113
  let out = "";
25085
25114
  for (let i = 0; i < glob.length; i += 1) {
@@ -25122,7 +25151,7 @@ function matchedMandatoryGlobs(paths, mandatory) {
25122
25151
  return list.some((path2) => re.test(path2));
25123
25152
  });
25124
25153
  }
25125
- function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
25154
+ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null } = {}) {
25126
25155
  const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
25127
25156
  if (!regulated) {
25128
25157
  return {
@@ -25135,7 +25164,8 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
25135
25164
  };
25136
25165
  }
25137
25166
  const matched = matchedMandatoryGlobs(paths, mandatory);
25138
- const testCommandsAllowed = matched.length > 0;
25167
+ const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
25168
+ const testCommandsAllowed = matched.length > 0 || overrideAuthorizes;
25139
25169
  return {
25140
25170
  configuredMandatoryCount,
25141
25171
  matchedMandatoryGlobs: matched,
@@ -25148,22 +25178,78 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
25148
25178
  }
25149
25179
  };
25150
25180
  }
25181
+ function git(args, cwd) {
25182
+ return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25183
+ }
25184
+ function parseScope(value) {
25185
+ const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
25186
+ if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
25187
+ const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
25188
+ return {
25189
+ kinds: named,
25190
+ reason: scoped[2].trim(),
25191
+ unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
25192
+ };
25193
+ }
25194
+ function invisibleTrailer(sha, line) {
25195
+ return {
25196
+ kind: "malformed-override-trailer",
25197
+ paths: [],
25198
+ detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
25199
+ ${line}
25200
+ but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
25201
+ exist only to the regex that granted it. The trailer was chosen over a flag because it can be
25202
+ AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
25203
+ Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
25204
+ trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
25205
+ block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
25206
+ This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
25207
+ The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
25208
+ };
25209
+ }
25210
+ function unknownScope(sha, unknown) {
25211
+ return {
25212
+ kind: "malformed-override-trailer",
25213
+ paths: [],
25214
+ detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
25215
+ gate cannot report.
25216
+ Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
25217
+ Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
25218
+ into a blanket exemption without anyone deciding it should.`
25219
+ };
25220
+ }
25221
+ function isShallowRepository(cwd) {
25222
+ try {
25223
+ return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
25224
+ } catch {
25225
+ return true;
25226
+ }
25227
+ }
25228
+ function readOverride(base, cwd) {
25229
+ const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
25230
+ const refusals = [];
25231
+ let override = null;
25232
+ for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
25233
+ const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
25234
+ if (!sha) continue;
25235
+ if (override) break;
25236
+ const value = (trailer ?? "").trim();
25237
+ if (!value) {
25238
+ const shaped = OVERRIDE_RE.exec(body ?? "");
25239
+ if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
25240
+ continue;
25241
+ }
25242
+ const { kinds, reason, unknown } = parseScope(value);
25243
+ if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
25244
+ else override = { sha, reason, kinds };
25245
+ }
25246
+ return { override, refusals };
25247
+ }
25151
25248
 
25152
25249
  // src/test-policy-core.ts
25153
25250
  var POLICY_FILE = "test-policy.json";
25154
25251
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
25155
25252
  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
25253
  function translate(glob) {
25168
25254
  let out = "";
25169
25255
  for (let i = 0; i < glob.length; i++) {
@@ -25521,15 +25607,15 @@ function evaluate(changed, policy, present = () => false) {
25521
25607
  }
25522
25608
  return findings;
25523
25609
  }
25524
- function git(args, cwd) {
25525
- return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25610
+ function git2(args, cwd) {
25611
+ return (0, import_node_child_process15.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
25526
25612
  }
25527
25613
  var COAUTHOR_KEY = "Co-authored-by";
25528
25614
  var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
25529
25615
  var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
25530
25616
  function parseTrailers(message, cwd) {
25531
25617
  try {
25532
- return (0, import_node_child_process14.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
25618
+ return (0, import_node_child_process15.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
25533
25619
  windowsHide: true,
25534
25620
  cwd,
25535
25621
  input: message,
@@ -25566,19 +25652,12 @@ function resolveBase(cwd, explicit) {
25566
25652
  const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
25567
25653
  for (const ref of candidates) {
25568
25654
  try {
25569
- return git(["merge-base", "HEAD", ref], cwd).trim();
25655
+ return git2(["merge-base", "HEAD", ref], cwd).trim();
25570
25656
  } catch {
25571
25657
  }
25572
25658
  }
25573
25659
  return null;
25574
25660
  }
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
25661
  function untrustworthyRange(cwd, base) {
25583
25662
  if (!base) {
25584
25663
  return {
@@ -25600,65 +25679,11 @@ function untrustworthyRange(cwd, base) {
25600
25679
  Fetch full history \u2014 actions/checkout with \`fetch-depth: 0\`, or \`git fetch --unshallow\`.`
25601
25680
  };
25602
25681
  }
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 };
25682
+ function readOverride2(base, cwd) {
25683
+ return readOverride(base, cwd);
25659
25684
  }
25660
25685
  function changedFilesSince(base, cwd) {
25661
- const raw = git(["diff", "--name-status", "-M", base, "--"], cwd).trim();
25686
+ const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
25662
25687
  const changed = raw ? raw.split("\n").map((line) => {
25663
25688
  const parts = line.split(" ");
25664
25689
  const norm = (p) => p.replace(/\\/g, "/");
@@ -25666,12 +25691,12 @@ function changedFilesSince(base, cwd) {
25666
25691
  if (parts.length > 2) row.from = norm(parts[1]);
25667
25692
  return row;
25668
25693
  }) : [];
25669
- const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
25694
+ const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
25670
25695
  return [...changed, ...untracked];
25671
25696
  }
25672
25697
  function blobAt(base, path2, cwd) {
25673
25698
  try {
25674
- return git(["show", `${base}:${path2}`], cwd);
25699
+ return git2(["show", `${base}:${path2}`], cwd);
25675
25700
  } catch {
25676
25701
  return null;
25677
25702
  }
@@ -25687,7 +25712,7 @@ function runTestPolicy(root, deps = {}) {
25687
25712
  before: blobAt(base, path2, root),
25688
25713
  after: readFileOrNull2((0, import_node_path22.join)(root, path2))
25689
25714
  }));
25690
- const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
25715
+ const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
25691
25716
  const present = (path2) => exists((0, import_node_path22.join)(root, path2));
25692
25717
  const removedByThisDiff = removedPaths(changed);
25693
25718
  const staleFindings = [];
@@ -25720,7 +25745,8 @@ function runTestPolicy(root, deps = {}) {
25720
25745
  const commandPolicy = evaluateTestCommandPolicy({
25721
25746
  paths: changed.map((f) => f.path),
25722
25747
  mandatory: policy.mandatory,
25723
- regulated: policy.declared !== false
25748
+ regulated: policy.declared !== false,
25749
+ override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
25724
25750
  });
25725
25751
  const result = {
25726
25752
  ok: findings.length === 0,
@@ -25742,7 +25768,7 @@ function runTestPolicy(root, deps = {}) {
25742
25768
  }
25743
25769
 
25744
25770
  // src/dist-drift.ts
25745
- var import_node_child_process15 = require("node:child_process");
25771
+ var import_node_child_process16 = require("node:child_process");
25746
25772
  var import_node_crypto7 = require("node:crypto");
25747
25773
  var import_node_fs27 = require("node:fs");
25748
25774
  var import_node_os13 = require("node:os");
@@ -25887,7 +25913,7 @@ function bomPathFor(root) {
25887
25913
  }
25888
25914
  }
25889
25915
  function rebuildTo(packageRoot, outDir) {
25890
- (0, import_node_child_process15.execFileSync)(process.execPath, ["build.mjs"], {
25916
+ (0, import_node_child_process16.execFileSync)(process.execPath, ["build.mjs"], {
25891
25917
  cwd: packageRoot,
25892
25918
  env: { ...process.env, MMI_DIST_OUTDIR: outDir },
25893
25919
  windowsHide: true,
@@ -27604,9 +27630,9 @@ ${SSH_RECIPE_AGENT_NOTE}`);
27604
27630
 
27605
27631
  // src/schedules-commands.ts
27606
27632
  var import_promises4 = require("node:fs/promises");
27607
- var import_node_child_process16 = require("node:child_process");
27633
+ var import_node_child_process17 = require("node:child_process");
27608
27634
  var import_node_util7 = require("node:util");
27609
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process16.execFile);
27635
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process17.execFile);
27610
27636
  var AWS_REGION = "eu-central-1";
27611
27637
  var AWS_TIMEOUT_MS = 3e4;
27612
27638
  var AWS_RETRY_DELAY_MS = 1500;
@@ -30932,6 +30958,9 @@ function buildPrMergeArgs(input) {
30932
30958
  if (input.auto) args.push("--auto");
30933
30959
  return args;
30934
30960
  }
30961
+ function buildPrDisableAutoArgs(input) {
30962
+ return ["pr", "merge", input.number, ...input.repoArgs, "--disable-auto"];
30963
+ }
30935
30964
  function basePolicyBlocksImmediateMerge(message) {
30936
30965
  return /base branch policy prohibits|protected branch|required status check|merge queue/i.test(message);
30937
30966
  }
@@ -31428,19 +31457,19 @@ function formatWorktreeRemovalFailureDetail(options) {
31428
31457
  const detail = options.removeError.trim() || "git worktree remove exited nonzero";
31429
31458
  return `${detail}; registered=${options.registered ? "yes" : "no"}; pathExists=${options.pathExists ? "yes" : "no"}; path=${options.wtPath}`;
31430
31459
  }
31431
- async function resolveMergeBaseTimestampMs(git2, baseRef, branch) {
31460
+ async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
31432
31461
  try {
31433
- const mergeBase = (await git2(["merge-base", baseRef, branch])).trim();
31462
+ const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
31434
31463
  if (!mergeBase) return 0;
31435
- const sec = parseCommitTimestampSec(await git2(["show", "-s", "--format=%ct", mergeBase]));
31464
+ const sec = parseCommitTimestampSec(await git3(["show", "-s", "--format=%ct", mergeBase]));
31436
31465
  return sec ? sec * 1e3 : 0;
31437
31466
  } catch {
31438
31467
  return 0;
31439
31468
  }
31440
31469
  }
31441
- async function verifyBranchHead(git2, branch, expectedHeadOid) {
31470
+ async function verifyBranchHead(git3, branch, expectedHeadOid) {
31442
31471
  if (!expectedHeadOid) return { ok: false, reason: "branch-head-unverified" };
31443
- const currentHead = (await git2(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
31472
+ const currentHead = (await git3(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
31444
31473
  if (!currentHead) return { ok: false, reason: "branch-head-unverified" };
31445
31474
  if (currentHead !== expectedHeadOid) {
31446
31475
  return { ok: false, reason: "unpushed-branch", error: `${currentHead} != ${expectedHeadOid}` };
@@ -31455,17 +31484,17 @@ async function teardownWorktreeStage(worktreePath) {
31455
31484
  return { status: "failed", error: e instanceof Error ? e.message : String(e) };
31456
31485
  }
31457
31486
  }
31458
- async function removeWorktreeWithReconcile(wtPath, git2, listWorktrees, pathExists) {
31487
+ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExists) {
31459
31488
  const attemptRemove = async () => {
31460
31489
  try {
31461
- await git2(["worktree", "remove", "--force", wtPath]);
31490
+ await git3(["worktree", "remove", "--force", wtPath]);
31462
31491
  return void 0;
31463
31492
  } catch (e) {
31464
31493
  return formatGitCommandError(e);
31465
31494
  }
31466
31495
  };
31467
31496
  const pruneAndVerify = async (reason) => {
31468
- await git2(["worktree", "prune"]).catch(() => "");
31497
+ await git3(["worktree", "prune"]).catch(() => "");
31469
31498
  let worktrees2;
31470
31499
  try {
31471
31500
  worktrees2 = await listWorktrees();
@@ -31495,7 +31524,7 @@ async function removeWorktreeWithReconcile(wtPath, git2, listWorktrees, pathExis
31495
31524
  };
31496
31525
  }
31497
31526
  if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-unregistered");
31498
- await git2(["worktree", "prune"]).catch(() => "");
31527
+ await git3(["worktree", "prune"]).catch(() => "");
31499
31528
  try {
31500
31529
  worktrees = await listWorktrees();
31501
31530
  } catch (e) {
@@ -31582,8 +31611,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31582
31611
  return report;
31583
31612
  }
31584
31613
  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);
31614
+ const git3 = (args) => safeCwd ? execGit(["-C", safeCwd, ...args]) : execGit(args);
31615
+ const headCheck = await verifyBranchHead(git3, branch, options.expectedHeadOid);
31587
31616
  if (!headCheck.ok) {
31588
31617
  report.localBranch = {
31589
31618
  name: branch,
@@ -31646,8 +31675,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31646
31675
  }
31647
31676
  const removal = await removeWorktreeWithReconcile(
31648
31677
  wtPath,
31649
- git2,
31650
- async () => parseGitWorktreePorcelain(await git2(["worktree", "list", "--porcelain"])),
31678
+ git3,
31679
+ async () => parseGitWorktreePorcelain(await git3(["worktree", "list", "--porcelain"])),
31651
31680
  pathExists
31652
31681
  );
31653
31682
  if (removal.status === "failed") {
@@ -31680,10 +31709,10 @@ async function cleanupPrMergeLocalBranch(branch, options) {
31680
31709
  }
31681
31710
  }
31682
31711
  try {
31683
- await git2(["branch", "-D", branch]);
31712
+ await git3(["branch", "-D", branch]);
31684
31713
  report.localBranch = { name: branch, status: "deleted" };
31685
31714
  } catch (e) {
31686
- const remaining = await git2(["branch", "--list", branch]).catch(() => "");
31715
+ const remaining = await git3(["branch", "--list", branch]).catch(() => "");
31687
31716
  report.localBranch = remaining.trim() ? { name: branch, status: "failed", error: e instanceof Error ? e.message : String(e) } : { name: branch, status: "already-gone" };
31688
31717
  }
31689
31718
  return report;
@@ -31717,6 +31746,17 @@ function renderPrMergeCleanupLines(cleanup) {
31717
31746
  return lines;
31718
31747
  }
31719
31748
 
31749
+ // src/utf8-receipt.ts
31750
+ var import_node_fs37 = require("node:fs");
31751
+ var import_node_path35 = require("node:path");
31752
+ function writeUtf8Receipt(path2, text) {
31753
+ (0, import_node_fs37.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
31754
+ const body = text.endsWith("\n") ? text : `${text}
31755
+ `;
31756
+ (0, import_node_fs37.writeFileSync)(path2, body, "utf8");
31757
+ return Buffer.byteLength(body, "utf8");
31758
+ }
31759
+
31720
31760
  // src/board-commands.ts
31721
31761
  function refuseRateLimited(e, json) {
31722
31762
  if (!isRateLimitedError(e)) return false;
@@ -31744,7 +31784,9 @@ function registerBoardCommands(program3) {
31744
31784
  includeAllBodies: o.bodies,
31745
31785
  allowPartial: o.allowPartial
31746
31786
  }, o.direct ? {} : { snapshot: registryClientDeps(config) });
31747
- console.log(o.json ? JSON.stringify(report) : renderBoardReport(report));
31787
+ const output = o.json ? JSON.stringify(report) : renderBoardReport(report);
31788
+ if (!o.out) console.log(output);
31789
+ else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
31748
31790
  } catch (e) {
31749
31791
  return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
31750
31792
  }
@@ -31753,7 +31795,7 @@ function registerBoardCommands(program3) {
31753
31795
  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
31796
  }
31755
31797
  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));
31798
+ 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
31799
  withExamples(mutating(
31758
31800
  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
31801
  (_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
@@ -32416,7 +32458,7 @@ async function checkDocsIndexAtHead(opts, deps) {
32416
32458
  }
32417
32459
 
32418
32460
  // src/issue-commands.ts
32419
- var import_node_fs37 = require("node:fs");
32461
+ var import_node_fs38 = require("node:fs");
32420
32462
  var import_node_crypto11 = require("node:crypto");
32421
32463
 
32422
32464
  // src/learning-closure-rate.ts
@@ -32603,7 +32645,7 @@ async function editIssue(client, options, deps = {}) {
32603
32645
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
32604
32646
  const patch = {};
32605
32647
  let bodyChanged = false;
32606
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs37.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
32648
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
32607
32649
  if (options.titleFile !== void 0) {
32608
32650
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
32609
32651
  } else if (options.title !== void 0) {
@@ -33220,7 +33262,7 @@ function extendCreateCommand(issue2, batchAttach) {
33220
33262
  if (opts.batch) {
33221
33263
  let specs;
33222
33264
  try {
33223
- const raw = (0, import_node_fs37.readFileSync)(opts.batch, "utf8");
33265
+ const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
33224
33266
  specs = JSON.parse(raw);
33225
33267
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33226
33268
  } catch (e) {
@@ -33295,8 +33337,8 @@ ${lines}`, {
33295
33337
  }
33296
33338
 
33297
33339
  // src/train-commands.ts
33298
- var import_node_fs38 = require("node:fs");
33299
- var import_node_path35 = require("node:path");
33340
+ var import_node_fs39 = require("node:fs");
33341
+ var import_node_path36 = require("node:path");
33300
33342
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
33301
33343
  function resolveReleaseBumpIntent(raw) {
33302
33344
  const intent = typeof raw === "string" ? raw.trim() : "";
@@ -33307,7 +33349,7 @@ function resolveReleaseBumpIntent(raw) {
33307
33349
  }
33308
33350
  function readRepoVersion() {
33309
33351
  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;
33352
+ return JSON.parse((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
33311
33353
  } catch {
33312
33354
  return void 0;
33313
33355
  }
@@ -33464,9 +33506,9 @@ function registerDeployCommands(program3) {
33464
33506
  }
33465
33507
 
33466
33508
  // src/discovery-commands.ts
33467
- var import_node_fs39 = require("node:fs");
33509
+ var import_node_fs40 = require("node:fs");
33468
33510
  var import_node_os17 = require("node:os");
33469
- var import_node_path36 = require("node:path");
33511
+ var import_node_path37 = require("node:path");
33470
33512
  var GC_GH_TIMEOUT_MS2 = 2e4;
33471
33513
  async function collectStatus() {
33472
33514
  const repo = await resolveRepo();
@@ -33656,8 +33698,8 @@ async function collectOnboardStatus(opts = {}) {
33656
33698
  }
33657
33699
  const home = (0, import_node_os17.homedir)();
33658
33700
  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)
33701
+ readKnown: () => readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
33702
+ readSettings: () => readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync)
33661
33703
  });
33662
33704
  return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
33663
33705
  }
@@ -34420,8 +34462,8 @@ function registerPrLifecycleCommands(program3) {
34420
34462
  }
34421
34463
 
34422
34464
  // src/post-merge-recon.ts
34423
- var import_node_fs40 = require("node:fs");
34424
- var import_node_path37 = require("node:path");
34465
+ var import_node_fs41 = require("node:fs");
34466
+ var import_node_path38 = require("node:path");
34425
34467
 
34426
34468
  // src/cross-repo-filing-issue.ts
34427
34469
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
@@ -34587,16 +34629,16 @@ function buildPostMergeReconRecovery(input) {
34587
34629
  }
34588
34630
  function writePostMergeReconRecovery(cwd, recovery) {
34589
34631
  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)}
34632
+ (0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
34633
+ (0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
34592
34634
  `, "utf8");
34593
34635
  return path2;
34594
34636
  }
34595
34637
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
34596
34638
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
34597
- if (!(0, import_node_fs40.existsSync)(path2)) return;
34639
+ if (!(0, import_node_fs41.existsSync)(path2)) return;
34598
34640
  try {
34599
- (0, import_node_fs40.unlinkSync)(path2);
34641
+ (0, import_node_fs41.unlinkSync)(path2);
34600
34642
  } catch {
34601
34643
  }
34602
34644
  }
@@ -37713,10 +37755,10 @@ function ghAccountCaveat(announcedLogin, accounts) {
37713
37755
  }
37714
37756
 
37715
37757
  // src/doctor-io.ts
37716
- var import_node_fs41 = require("node:fs");
37758
+ var import_node_fs42 = require("node:fs");
37717
37759
  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");
37760
+ var import_node_path39 = require("node:path");
37761
+ var import_node_child_process18 = require("node:child_process");
37720
37762
  var import_node_util8 = require("node:util");
37721
37763
 
37722
37764
  // src/discard-sink.ts
@@ -37725,27 +37767,27 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
37725
37767
  }
37726
37768
 
37727
37769
  // src/doctor-io.ts
37728
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
37770
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
37729
37771
  function execFileCapture(file, args, opts = {}) {
37730
37772
  const sink = nodeDiscardSinkPath();
37731
- const inFd = (0, import_node_fs41.openSync)(sink, "r");
37732
- const errFd = (0, import_node_fs41.openSync)(sink, "w");
37773
+ const inFd = (0, import_node_fs42.openSync)(sink, "r");
37774
+ const errFd = (0, import_node_fs42.openSync)(sink, "w");
37733
37775
  try {
37734
- return (0, import_node_child_process17.execFileSync)(file, args, {
37776
+ return (0, import_node_child_process18.execFileSync)(file, args, {
37735
37777
  ...opts,
37736
37778
  encoding: "utf8",
37737
37779
  stdio: [inFd, "pipe", errFd]
37738
37780
  });
37739
37781
  } finally {
37740
- (0, import_node_fs41.closeSync)(inFd);
37741
- (0, import_node_fs41.closeSync)(errFd);
37782
+ (0, import_node_fs42.closeSync)(inFd);
37783
+ (0, import_node_fs42.closeSync)(errFd);
37742
37784
  }
37743
37785
  }
37744
37786
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
37745
37787
  function installedClaudePluginVersion() {
37746
37788
  try {
37747
37789
  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")
37790
+ (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
37749
37791
  );
37750
37792
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
37751
37793
  if (versions.length === 0) return void 0;
@@ -37756,7 +37798,7 @@ function installedClaudePluginVersion() {
37756
37798
  }
37757
37799
  function manifestVersion(path2) {
37758
37800
  try {
37759
- const manifest = JSON.parse((0, import_node_fs41.readFileSync)(path2, "utf8"));
37801
+ const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
37760
37802
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
37761
37803
  } catch {
37762
37804
  return void 0;
@@ -37765,12 +37807,12 @@ function manifestVersion(path2) {
37765
37807
  function readHermesPluginEvidence(env = process.env) {
37766
37808
  const host = hermesConfigRoot(env);
37767
37809
  const root = hermesPluginRoot(env);
37768
- const installRecordPresent = (0, import_node_fs41.existsSync)(root);
37769
- const manifestPath = (0, import_node_path38.join)(root, "plugin.yaml");
37810
+ const installRecordPresent = (0, import_node_fs42.existsSync)(root);
37811
+ const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
37770
37812
  let installedVersion;
37771
37813
  let manifest = "missing";
37772
37814
  try {
37773
- const text = (0, import_node_fs41.readFileSync)(manifestPath, "utf8");
37815
+ const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
37774
37816
  let version;
37775
37817
  try {
37776
37818
  const parsed = JSON.parse(text).version;
@@ -37784,20 +37826,20 @@ function readHermesPluginEvidence(env = process.env) {
37784
37826
  if (version) {
37785
37827
  installedVersion = version;
37786
37828
  manifest = "valid";
37787
- } else if ((0, import_node_fs41.existsSync)(manifestPath)) manifest = "invalid";
37829
+ } else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
37788
37830
  } catch {
37789
- if ((0, import_node_fs41.existsSync)(manifestPath)) manifest = "invalid";
37831
+ if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
37790
37832
  }
37791
37833
  let skills = false;
37792
37834
  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();
37835
+ 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
37836
  } catch {
37795
37837
  }
37796
37838
  return {
37797
- hostPresent: (0, import_node_fs41.existsSync)(host),
37839
+ hostPresent: (0, import_node_fs42.existsSync)(host),
37798
37840
  installRecordPresent,
37799
37841
  manifest,
37800
- payloadPresent: (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, "__init__.py")) && skills && manifest === "valid",
37842
+ payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
37801
37843
  ...installedVersion ? { installedVersion } : {}
37802
37844
  };
37803
37845
  }
@@ -37805,7 +37847,7 @@ function installedSurfacePluginVersion(surface) {
37805
37847
  const token = surfaceToken(surface);
37806
37848
  if (token === "kilo") {
37807
37849
  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();
37850
+ const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
37809
37851
  return stamp || void 0;
37810
37852
  } catch {
37811
37853
  return void 0;
@@ -37813,13 +37855,13 @@ function installedSurfacePluginVersion(surface) {
37813
37855
  }
37814
37856
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
37815
37857
  if (token === "cursor") {
37816
- return manifestVersion((0, import_node_path38.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
37858
+ return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
37817
37859
  }
37818
37860
  if (token === "jervcode") {
37819
37861
  return installedJervCodePackageVersion();
37820
37862
  }
37821
37863
  if (token === "kimi") {
37822
- return manifestVersion((0, import_node_path38.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
37864
+ return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
37823
37865
  }
37824
37866
  if (token === "claude") return installedClaudePluginVersion();
37825
37867
  if (token !== "codex") return void 0;
@@ -37853,13 +37895,13 @@ function worktreeRootSync() {
37853
37895
  }
37854
37896
  var gitignorePath = () => {
37855
37897
  const root = worktreeRootSync();
37856
- return root === null ? null : (0, import_node_path38.join)(root, ".gitignore");
37898
+ return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
37857
37899
  };
37858
37900
  function readGitignore() {
37859
37901
  const path2 = gitignorePath();
37860
37902
  if (path2 === null) return null;
37861
37903
  try {
37862
- return (0, import_node_fs41.readFileSync)(path2, "utf8");
37904
+ return (0, import_node_fs42.readFileSync)(path2, "utf8");
37863
37905
  } catch {
37864
37906
  return null;
37865
37907
  }
@@ -37868,14 +37910,14 @@ function writeGitignore(content) {
37868
37910
  const path2 = gitignorePath();
37869
37911
  if (path2 === null) return false;
37870
37912
  try {
37871
- (0, import_node_fs41.writeFileSync)(path2, content, "utf8");
37913
+ (0, import_node_fs42.writeFileSync)(path2, content, "utf8");
37872
37914
  return true;
37873
37915
  } catch {
37874
37916
  return false;
37875
37917
  }
37876
37918
  }
37877
37919
  function lineEndingState(root) {
37878
- const attributesPresent = (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, ".gitattributes"));
37920
+ const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
37879
37921
  try {
37880
37922
  const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
37881
37923
  windowsHide: true
@@ -37942,8 +37984,8 @@ ${r.stderr ?? ""}`).catch(() => "");
37942
37984
  function ghMultiAccountCaveat(announcedLogin) {
37943
37985
  try {
37944
37986
  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")));
37987
+ if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
37988
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
37947
37989
  } catch {
37948
37990
  return void 0;
37949
37991
  }
@@ -37951,7 +37993,7 @@ function ghMultiAccountCaveat(announcedLogin) {
37951
37993
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
37952
37994
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
37953
37995
  function envHealLockPath(home) {
37954
- return (0, import_node_path39.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
37996
+ return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
37955
37997
  }
37956
37998
  async function withEnvHealLock(what, run) {
37957
37999
  try {
@@ -38088,7 +38130,7 @@ function mmiDoctorDeps(opts = {}) {
38088
38130
  );
38089
38131
  const result = applyPluginCachePlan(
38090
38132
  plan,
38091
- (p) => (0, import_node_fs42.rmSync)(p, { recursive: true }),
38133
+ (p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
38092
38134
  stagingApplyFsGuard(configRoot)
38093
38135
  );
38094
38136
  return {
@@ -38126,7 +38168,7 @@ function mmiDoctorDeps(opts = {}) {
38126
38168
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38127
38169
  // get a permanent — demanding an artifact it never asked for.
38128
38170
  docsIndexState: (root) => {
38129
- if (!(0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, DOCS_INDEX_PATH))) return void 0;
38171
+ if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
38130
38172
  const real = createDocsIndexDeps(root);
38131
38173
  let docs2;
38132
38174
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38135,7 +38177,7 @@ function mmiDoctorDeps(opts = {}) {
38135
38177
  },
38136
38178
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38137
38179
  healDocsIndex: (root) => {
38138
- if (!(0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38180
+ if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38139
38181
  const real = createDocsIndexDeps(root);
38140
38182
  let docs2;
38141
38183
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38456,19 +38498,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
38456
38498
  });
38457
38499
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
38458
38500
  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;
38501
+ const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
38502
+ const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
38461
38503
  const plan = planManagedGitignore(current);
38462
38504
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
38463
38505
  if (opts.json) {
38464
- if (opts.write && plan.changed) (0, import_node_fs42.writeFileSync)(path2, plan.content, "utf8");
38506
+ if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
38465
38507
  console.log(JSON.stringify(plan, null, 2));
38466
38508
  if (!opts.write && plan.changed) process.exitCode = 1;
38467
38509
  return;
38468
38510
  }
38469
38511
  if (opts.write) {
38470
38512
  if (plan.changed) {
38471
- (0, import_node_fs42.writeFileSync)(path2, plan.content, "utf8");
38513
+ (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
38472
38514
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
38473
38515
  } else {
38474
38516
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -38654,7 +38696,7 @@ function scheduleRelatedDiscovery(o) {
38654
38696
  try {
38655
38697
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
38656
38698
  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() });
38699
+ spawnDetachedSelf(args, { spawn: import_node_child_process19.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38658
38700
  } catch {
38659
38701
  }
38660
38702
  }
@@ -39044,7 +39086,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
39044
39086
  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
39087
  if (o.secretsFile) {
39046
39088
  try {
39047
- vars.push(`secrets=${(0, import_node_fs42.readFileSync)(o.secretsFile, "utf8")}`);
39089
+ vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
39048
39090
  } catch (e) {
39049
39091
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
39050
39092
  }
@@ -39869,11 +39911,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
39869
39911
  }
39870
39912
  });
39871
39913
  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) => {
39914
+ const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
39915
+ if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
39916
+ return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39875
39917
  try {
39876
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(wfDir, name), "utf8"));
39918
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
39877
39919
  } catch {
39878
39920
  return true;
39879
39921
  }
@@ -39939,16 +39981,16 @@ function ciAuditDeps() {
39939
39981
  // gate re-seed step is skipped gracefully rather than failing mid-run.
39940
39982
  readSeedFile: (path2) => {
39941
39983
  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;
39984
+ const fullPath = (0, import_node_path40.join)(root, path2);
39985
+ return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
39944
39986
  }
39945
39987
  };
39946
39988
  }
39947
39989
  function hubRoot() {
39948
- const fromPkg = (0, import_node_path39.join)(__dirname, "..", "..");
39990
+ const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
39949
39991
  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();
39992
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
39993
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
39952
39994
  return null;
39953
39995
  }
39954
39996
  async function waitLoopCorePool(label) {
@@ -40220,9 +40262,18 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
40220
40262
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
40221
40263
  if (result.status === "failed") process.exitCode = 1;
40222
40264
  });
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) => {
40265
+ 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
40266
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
40225
40267
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
40268
+ if (o.disableAuto) {
40269
+ await execFileP2("gh", buildPrDisableAutoArgs({ number, repoArgs }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e) => {
40270
+ const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
40271
+ if (note) throw new Error(`gh pr merge ${number} --disable-auto: ${note}`);
40272
+ throw e;
40273
+ });
40274
+ console.log(JSON.stringify({ mergeStatus: "auto-merge-disabled", pr: number, ...o.repo ? { repo: o.repo } : {} }));
40275
+ return;
40276
+ }
40226
40277
  const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
40227
40278
  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
40279
  if (!isGitHubRateLimitError(e) || !repoForPostCleanup) throw e;
@@ -40249,7 +40300,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40249
40300
  const mergeSquashBody = squashBodyTextForMerge(
40250
40301
  closingGuardInput,
40251
40302
  method === "--squash",
40252
- o.squashBodyFile ? (0, import_node_fs42.readFileSync)(o.squashBodyFile, "utf8") : void 0
40303
+ o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
40253
40304
  );
40254
40305
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
40255
40306
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -40263,7 +40314,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40263
40314
  return;
40264
40315
  }
40265
40316
  if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
40266
- if (prMeta.state !== "MERGED") {
40317
+ if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
40267
40318
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
40268
40319
  const repoSettings = await defaultGitHubClient().rest("GET", `repos/${repoForPostCleanup}`);
40269
40320
  assertRemoteBranchPreservedOnMerge(repoForPostCleanup, repoSettings.delete_branch_on_merge);
@@ -40338,7 +40389,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40338
40389
  }
40339
40390
  if (!repoForPostCleanup) throw e;
40340
40391
  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;
40392
+ const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
40342
40393
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
40343
40394
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
40344
40395
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -40427,7 +40478,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
40427
40478
  preserveWorktree: o.preserveWorktree,
40428
40479
  gcAcknowledged: o.gc,
40429
40480
  expectedHeadOid: headRefOid,
40430
- pathExists: (p) => (0, import_node_fs42.existsSync)(p),
40481
+ pathExists: (p) => (0, import_node_fs43.existsSync)(p),
40431
40482
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout
40432
40483
  });
40433
40484
  } catch (e) {
@@ -40715,7 +40766,7 @@ for (const commandName of ["rcand", "release"]) {
40715
40766
  { 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
40767
  { 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
40768
  { 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)" }
40769
+ { flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
40719
40770
  ];
40720
40771
  for (const f of RELEASE_ONLY_FLAGS) {
40721
40772
  if (commandName === "release") {
@@ -41014,8 +41065,7 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
41014
41065
  console.log("\nDry-run \u2014 re-run with --apply to patch merge settings and activate product rulesets (master-admin).");
41015
41066
  }
41016
41067
  }
41017
- const applyFailed = applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
41018
- if (!audit.ok || applyFailed) process.exitCode = 1;
41068
+ if (ciReconcileExitFailed({ apply: o.apply === true, audit, applyResults })) process.exitCode = 1;
41019
41069
  });
41020
41070
  registerBootstrapCommands(program2);
41021
41071
  var access = program2.command("access").description("org access audit (read-only)");
@@ -41064,12 +41114,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
41064
41114
  targets = resolution.targets;
41065
41115
  }
41066
41116
  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")) : {};
41117
+ const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
41068
41118
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
41069
41119
  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: {} };
41120
+ const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
41071
41121
  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")) : {};
41122
+ const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
41073
41123
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
41074
41124
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
41075
41125
  if (!report.ok) process.exitCode = 1;
@@ -41101,16 +41151,16 @@ function directoryBytes(path2) {
41101
41151
  let total = 0;
41102
41152
  let entries;
41103
41153
  try {
41104
- entries = (0, import_node_fs42.readdirSync)(path2, { withFileTypes: true });
41154
+ entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
41105
41155
  } catch {
41106
41156
  return 0;
41107
41157
  }
41108
41158
  for (const entry of entries) {
41109
- const child2 = (0, import_node_path39.join)(path2, entry.name);
41159
+ const child2 = (0, import_node_path40.join)(path2, entry.name);
41110
41160
  if (entry.isDirectory()) total += directoryBytes(child2);
41111
41161
  else {
41112
41162
  try {
41113
- total += (0, import_node_fs42.statSync)(child2).size;
41163
+ total += (0, import_node_fs43.statSync)(child2).size;
41114
41164
  } catch {
41115
41165
  }
41116
41166
  }
@@ -41118,25 +41168,25 @@ function directoryBytes(path2) {
41118
41168
  return total;
41119
41169
  }
41120
41170
  function listDirEntries(dir) {
41121
- return (0, import_node_fs42.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41171
+ return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41122
41172
  }
41123
41173
  function readInstalledPluginRefs(configRoot) {
41124
41174
  const p = installedPluginsPathForConfig(configRoot);
41125
- if (!(0, import_node_fs42.existsSync)(p)) return [];
41175
+ if (!(0, import_node_fs43.existsSync)(p)) return [];
41126
41176
  try {
41127
- return installedPluginPaths((0, import_node_fs42.readFileSync)(p, "utf8"));
41177
+ return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
41128
41178
  } catch {
41129
41179
  return null;
41130
41180
  }
41131
41181
  }
41132
41182
  function pluginCacheFsDeps(configRoot, dirBytes) {
41133
41183
  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),
41184
+ exists: (p) => (0, import_node_fs43.existsSync)(p),
41185
+ listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
41136
41186
  dirBytes,
41137
- listStagingDirs: (root) => (0, import_node_fs42.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41187
+ listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41138
41188
  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) };
41189
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
41140
41190
  } catch {
41141
41191
  return { name: d.name, mtimeMs: Date.now() };
41142
41192
  }
@@ -41150,10 +41200,10 @@ function stagingApplyFsGuard(configRoot) {
41150
41200
  return {
41151
41201
  referencedPaths: () => readInstalledPluginRefs(configRoot),
41152
41202
  mtimeMs: (name) => {
41153
- const p = (0, import_node_path39.join)(stagingRoot, name);
41154
- if (!(0, import_node_fs42.existsSync)(p)) return null;
41203
+ const p = (0, import_node_path40.join)(stagingRoot, name);
41204
+ if (!(0, import_node_fs43.existsSync)(p)) return null;
41155
41205
  try {
41156
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs42.statSync)(q).mtimeMs);
41206
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
41157
41207
  } catch {
41158
41208
  return null;
41159
41209
  }
@@ -41179,7 +41229,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
41179
41229
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
41180
41230
  );
41181
41231
  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;
41232
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
41183
41233
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
41184
41234
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
41185
41235
  else console.log(renderPluginCachePlan(plan, result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",