@mutmutco/cli 4.3.15 → 4.3.17

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 +606 -266
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ function useColor() {
3416
3416
  var program = new Command();
3417
3417
 
3418
3418
  // src/command-composition.ts
3419
- var import_node_fs49 = require("node:fs");
3419
+ var import_node_fs50 = require("node:fs");
3420
3420
 
3421
3421
  // src/clean-exit.ts
3422
3422
  var UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
@@ -5226,8 +5226,8 @@ function unknownCommandDomainGuide(parentPath, token) {
5226
5226
  }
5227
5227
 
5228
5228
  // src/command-composition.ts
5229
- var import_node_os21 = require("node:os");
5230
- var import_node_path46 = require("node:path");
5229
+ var import_node_os22 = require("node:os");
5230
+ var import_node_path47 = require("node:path");
5231
5231
 
5232
5232
  // src/board-read.ts
5233
5233
  var import_node_fs13 = require("node:fs");
@@ -12917,6 +12917,18 @@ function rulesetRequiredContexts(ruleset) {
12917
12917
  }
12918
12918
  return contexts;
12919
12919
  }
12920
+ function rulesetStrictPolicy(ruleset) {
12921
+ const rules = (ruleset.rules ?? []).filter((rule) => rule.type === "required_status_checks");
12922
+ return rules.length > 0 && rules.every((rule) => rule.parameters?.strict_required_status_checks_policy === true);
12923
+ }
12924
+ function patchRulesetStrictPolicy(body) {
12925
+ const rules = (body.rules ?? []).map((rule) => {
12926
+ const r = rule;
12927
+ if (r.type !== "required_status_checks") return rule;
12928
+ return { ...r, parameters: { ...r.parameters, strict_required_status_checks_policy: true } };
12929
+ });
12930
+ return { ...body, rules };
12931
+ }
12920
12932
  function rulesetBranchIncludes(ruleset) {
12921
12933
  const raw = ruleset.conditions?.ref_name?.include;
12922
12934
  return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
@@ -12936,7 +12948,8 @@ function patchRulesetRequiredContexts(body, contexts) {
12936
12948
  ...r,
12937
12949
  parameters: {
12938
12950
  ...r.parameters,
12939
- strict_required_status_checks_policy: r.parameters?.strict_required_status_checks_policy ?? false,
12951
+ strict_required_status_checks_policy: true,
12952
+ // #6263: org policy — see patchRulesetStrictPolicy
12940
12953
  required_status_checks: sorted.map((context) => ({ context }))
12941
12954
  }
12942
12955
  };
@@ -13023,6 +13036,7 @@ async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_G
13023
13036
  }
13024
13037
  async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
13025
13038
  const want = new Set(rulesetRequiredContexts({ rules: rulesetBody.rules }));
13039
+ const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
13026
13040
  const wantBranches = rulesetBranchIncludes(rulesetBody);
13027
13041
  const list = await client.rest("GET", `repos/${repo}/rulesets`, { timeoutMs: 2e4 });
13028
13042
  const existing = findProductRuleset(list ?? []);
@@ -13034,8 +13048,9 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
13034
13048
  const have = new Set(rulesetRequiredContexts(detail));
13035
13049
  const haveBranches = rulesetBranchIncludes(detail);
13036
13050
  const branchesMatch = haveBranches.length === wantBranches.length && wantBranches.every((ref, index) => ref === haveBranches[index]);
13037
- if (detail.enforcement === effective && branchesMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
13038
- return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts and branches${kept}` };
13051
+ const strictMatch = rulesetStrictPolicy(detail) === wantStrict;
13052
+ if (detail.enforcement === effective && branchesMatch && strictMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
13053
+ return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts, branches and up-to-date policy${kept}` };
13039
13054
  }
13040
13055
  await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
13041
13056
  return { action: "updated", enforcement: effective, detail: `ruleset ${existing.id}${kept}` };
@@ -13706,7 +13721,7 @@ function createDocsIndexDeps(repoRoot2) {
13706
13721
  }
13707
13722
 
13708
13723
  // src/gate-budget.ts
13709
- var BLESSED_RUN_WITH_BUDGET_SHA = "ea2cf8710949dec53566c29836e29cd7515a4e23";
13724
+ var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
13710
13725
  var BLESSED_RUNNER_GATE_SHA = "a6ee756d94fd95884451381e94408f0348eb11db";
13711
13726
  var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
13712
13727
  var LOCAL_USE = "./.github/actions/run-with-budget";
@@ -14044,6 +14059,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
14044
14059
  const runtimeVars = {
14045
14060
  GATE_RUNTIME: runtime,
14046
14061
  GATE_CMD: rt.cmd,
14062
+ // #6254: affected-only PR command; empty means "same as GATE_CMD" (withDerivedRepoVars fills it).
14063
+ GATE_AFFECTED_CMD: "",
14047
14064
  GATE_INSTALL_CMD: rt.install,
14048
14065
  GATE_WORKDIR: ".",
14049
14066
  GATE_CACHE_DEP_PATH: "package-lock.json",
@@ -14091,6 +14108,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
14091
14108
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
14092
14109
  out[key] ??= value;
14093
14110
  }
14111
+ if (!out.GATE_AFFECTED_CMD.trim()) out.GATE_AFFECTED_CMD = out.GATE_CMD;
14094
14112
  return out;
14095
14113
  }
14096
14114
  function gateConfigToVars(gate) {
@@ -14098,6 +14116,7 @@ function gateConfigToVars(gate) {
14098
14116
  if (!gate || typeof gate !== "object") return out;
14099
14117
  if (gate.runtime === "node" || gate.runtime === "python") out.GATE_RUNTIME = gate.runtime;
14100
14118
  if (typeof gate.cmd === "string" && gate.cmd.trim()) out.GATE_CMD = gate.cmd;
14119
+ if (typeof gate.affectedCmd === "string" && gate.affectedCmd.trim()) out.GATE_AFFECTED_CMD = gate.affectedCmd;
14101
14120
  if (typeof gate.workdir === "string" && gate.workdir.trim()) out.GATE_WORKDIR = gate.workdir;
14102
14121
  if (typeof gate.cacheDepPath === "string" && gate.cacheDepPath.trim()) out.GATE_CACHE_DEP_PATH = gate.cacheDepPath;
14103
14122
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
@@ -15483,10 +15502,10 @@ var rollout_plan_default = {
15483
15502
  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)."
15484
15503
  },
15485
15504
  baseline: {
15486
- version: "4.3.15",
15487
- tag: "v4.3.15",
15488
- commit: "006f11ed8921",
15489
- npm: "@mutmutco/cli@4.3.15"
15505
+ version: "4.3.17",
15506
+ tag: "v4.3.17",
15507
+ commit: "3b07c941910b",
15508
+ npm: "@mutmutco/cli@4.3.17"
15490
15509
  },
15491
15510
  exitCriterion: "fleet-n-of-n",
15492
15511
  hubOnlyShortcut: "forbidden",
@@ -15503,14 +15522,14 @@ var rollout_plan_default = {
15503
15522
  repo: "mutmutco/mmi-hub",
15504
15523
  role: "canary",
15505
15524
  schedule: "train",
15506
- v3Target: "v4.3.15"
15525
+ v3Target: "v4.3.17"
15507
15526
  }
15508
15527
  ],
15509
15528
  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.",
15510
15529
  rollback: {
15511
15530
  independent: true,
15512
- mechanism: "npm dist-tag latest -> 4.3.15 and redeploy the Hub Lambda from tag v4.3.15 (006f11ed8921); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15513
- v3Target: "v4.3.15 (@mutmutco/cli@4.3.15, tag commit 006f11ed8921 \u2014 last known-good release carrying the repo-index v4-only contract)"
15531
+ mechanism: "npm dist-tag latest -> 4.3.17 and redeploy the Hub Lambda from tag v4.3.17 (3b07c941910b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15532
+ v3Target: "v4.3.17 (@mutmutco/cli@4.3.17, tag commit 3b07c941910b \u2014 last known-good release carrying the repo-index v4-only contract)"
15514
15533
  }
15515
15534
  },
15516
15535
  {
@@ -23507,12 +23526,13 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
23507
23526
  const registryContexts = registryRequiredContexts(meta);
23508
23527
  const contexts = sortedUnique(registryContexts ?? committedContexts);
23509
23528
  const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
23510
- let apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
23529
+ const committedStrict = rulesetStrictPolicy(committedPayload);
23530
+ let apiPayload = patchRulesetStrictPolicy(registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts));
23511
23531
  if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
23512
23532
  const branchIncludes = rulesetBranchIncludes(apiPayload);
23513
23533
  const authoritativeFilePayload = {
23514
23534
  ...filePayload,
23515
- ...registryContexts == null ? {} : { rules: apiPayload.rules },
23535
+ ...registryContexts == null && committedStrict ? {} : { rules: apiPayload.rules },
23516
23536
  ...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
23517
23537
  };
23518
23538
  return {
@@ -23522,6 +23542,7 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
23522
23542
  contexts,
23523
23543
  branchIncludes,
23524
23544
  committedBranchIncludes,
23545
+ committedStrict,
23525
23546
  branchSource: explicitBranches ? "registry META requiredCheckBranches" : "committed ruleset reference",
23526
23547
  source: registryContexts == null ? "committed ruleset reference" : "registry META requiredChecks",
23527
23548
  coversReleaseBranches: rulesetCoversReleaseBranches(apiPayload, resolveReleaseTrack(meta, void 0, repo))
@@ -23533,8 +23554,9 @@ function sameContexts(left, right) {
23533
23554
  function resolveProductRulesetReconcilePlan(input) {
23534
23555
  const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
23535
23556
  const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
23557
+ const liveNeedsStrictConvergence = input.liveStrict === false;
23536
23558
  const liveIsActive = input.liveEnforcement === "active";
23537
- if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence) {
23559
+ if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence && !liveNeedsStrictConvergence) {
23538
23560
  return { shouldActivate: false, targetEnforcement: "active" };
23539
23561
  }
23540
23562
  if (input.unsafeContexts.length > 0) {
@@ -23722,6 +23744,7 @@ async function auditRepoCi(repo, deps) {
23722
23744
  const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
23723
23745
  const liveContexts = productRuleset == null ? [] : sortedUnique(rulesetRequiredContexts(productRuleset));
23724
23746
  const liveBranchIncludes = productRuleset == null ? [] : rulesetBranchIncludes(productRuleset);
23747
+ const liveStrict = productRuleset != null && rulesetStrictPolicy(productRuleset);
23725
23748
  const hasRequiredChecks = productRuleset?.enforcement === "active" && liveContexts.length > 0;
23726
23749
  checks.push({
23727
23750
  ok: hasRequiredChecks,
@@ -23742,11 +23765,12 @@ async function auditRepoCi(repo, deps) {
23742
23765
  const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
23743
23766
  const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
23744
23767
  const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
23745
- const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned;
23768
+ const strictAligned = authoritativeRuleset.committedStrict && liveStrict;
23769
+ const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned && strictAligned;
23746
23770
  checks.push({
23747
23771
  ok: aligned,
23748
23772
  label: RULESET_REFERENCE_MATCH_LABEL,
23749
- detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}] and branches [${authoritativeRuleset.branchIncludes.join(", ")}] match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]`,
23773
+ detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}], branches [${authoritativeRuleset.branchIncludes.join(", ")}] and the up-to-date policy match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]` + (strictAligned ? "" : `; strict_required_status_checks_policy must be true (require branches to be up to date before merging, #6263) \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`),
23750
23774
  remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
23751
23775
  });
23752
23776
  }
@@ -24280,7 +24304,7 @@ async function applyCiReconcileRepo(repo, deps) {
24280
24304
  result.errors.push(`${authority.source} declares no required contexts`);
24281
24305
  return finalizeCiReconcile(repo, deps, result, report);
24282
24306
  }
24283
- const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes);
24307
+ const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
24284
24308
  if (sourceNeedsConvergence) {
24285
24309
  try {
24286
24310
  const delivery = await deliverSeedFile(
@@ -24335,6 +24359,7 @@ async function applyCiReconcileRepo(repo, deps) {
24335
24359
  authorityContexts: authority.contexts,
24336
24360
  liveBranchIncludes,
24337
24361
  authorityBranchIncludes: authority.branchIncludes,
24362
+ liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
24338
24363
  gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
24339
24364
  unsafeContexts: unsafe
24340
24365
  });
@@ -24343,7 +24368,7 @@ async function applyCiReconcileRepo(repo, deps) {
24343
24368
  result.skipped.push(plan.holdReason);
24344
24369
  return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
24345
24370
  }
24346
- result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts and branches already match declared authority`);
24371
+ result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
24347
24372
  return finalizeCiReconcile(repo, deps, result, report);
24348
24373
  }
24349
24374
  try {
@@ -27142,6 +27167,11 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
27142
27167
  label: "product required-check ruleset enforcement active",
27143
27168
  detail: productRuleset?.enforcement !== "active" ? `${PRODUCT_RULESET_NAME} is ${productRuleset?.enforcement ?? "missing"} \u2014 run mmi-cli devops ci reconcile --apply --repo ${repo} once the gate is green` : void 0
27144
27169
  });
27170
+ checks.push({
27171
+ ok: productRuleset != null && rulesetStrictPolicy(productRuleset),
27172
+ label: "product required-check ruleset requires an up-to-date branch",
27173
+ detail: productRuleset != null && rulesetStrictPolicy(productRuleset) ? void 0 : `${PRODUCT_RULESET_NAME} ${productRuleset == null ? "is missing" : "has strict_required_status_checks_policy=false"} \u2014 run mmi-cli devops ci reconcile --apply --repo ${repo} (#6263)`
27174
+ });
27145
27175
  const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
27146
27176
  const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
27147
27177
  checks.push({
@@ -28778,7 +28808,7 @@ function parseGateVar(raw) {
28778
28808
  throw new Error('org project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
28779
28809
  }
28780
28810
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
28781
- throw new Error("org project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
28811
+ throw new Error("org project set: gate must be a {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion} object");
28782
28812
  }
28783
28813
  const map = parsed;
28784
28814
  const out = {};
@@ -28786,11 +28816,11 @@ function parseGateVar(raw) {
28786
28816
  if (key === "runtime") {
28787
28817
  if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
28788
28818
  out.runtime = value;
28789
- } else if (key === "cmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
28819
+ } else if (key === "cmd" || key === "affectedCmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
28790
28820
  if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
28791
28821
  out[key] = value.trim();
28792
28822
  } else {
28793
- throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
28823
+ throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion`);
28794
28824
  }
28795
28825
  }
28796
28826
  return out;
@@ -28891,7 +28921,7 @@ var SETTABLE_VAR_HINTS = {
28891
28921
  requiredChecks: 'JSON array, e.g. ["gate"] or [] for no-ci',
28892
28922
  requiredCheckBranches: "non-empty JSON branch-name array; exact product-ruleset scope (#5210)",
28893
28923
  ciExemptReason: "non-empty string \u2014 declares this deployable repo has no CI surface (#4265)",
28894
- gate: "JSON {runtime,cmd,workdir,cacheDepPath,pyVersion}"
28924
+ gate: "JSON {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion}"
28895
28925
  };
28896
28926
  function settableVarHelp() {
28897
28927
  const keys = SETTABLE_VAR_KEYS.map((k) => {
@@ -35908,8 +35938,8 @@ function renderVerifySecrets(body) {
35908
35938
  }
35909
35939
 
35910
35940
  // src/command-register-collaboration.ts
35911
- var import_node_child_process15 = require("node:child_process");
35912
- var import_node_fs42 = require("node:fs");
35941
+ var import_node_child_process16 = require("node:child_process");
35942
+ var import_node_fs43 = require("node:fs");
35913
35943
  var import_promises7 = require("node:fs/promises");
35914
35944
 
35915
35945
  // src/session-runtime.ts
@@ -35929,7 +35959,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
35929
35959
  }
35930
35960
 
35931
35961
  // src/command-register-collaboration.ts
35932
- var import_node_path39 = require("node:path");
35962
+ var import_node_path40 = require("node:path");
35933
35963
 
35934
35964
  // src/attach-to-project.ts
35935
35965
  function boardAttachRateLimitedReceipt(resetEpochSeconds) {
@@ -36006,6 +36036,47 @@ function applyChecklistCheck(body, query, checked) {
36006
36036
  }
36007
36037
 
36008
36038
  // src/pr-land.ts
36039
+ function prHeadBehindBase(input) {
36040
+ if ((input.mergeStateStatus ?? "").toUpperCase() === "BEHIND") return true;
36041
+ return /out[- ]of[- ]date|not up to date|up to date with the base|update (?:the|your) branch/i.test(input.message ?? "");
36042
+ }
36043
+ function prHeadUpdateRemedy(head, base) {
36044
+ return `git fetch origin ${base} && git merge origin/${base} (on ${head}), resolve the conflicts, push, then rerun \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
36045
+ }
36046
+ var PR_HEAD_UPDATE_API_READ_RETRIES = 5;
36047
+ var PR_HEAD_UPDATE_API_READ_DELAY_MS = 2e3;
36048
+ async function updatePrHeadFromBase(input) {
36049
+ const remedy = prHeadUpdateRemedy(input.head, input.base);
36050
+ if (input.localCheckedOut) {
36051
+ const from2 = (await input.git(["rev-parse", "HEAD"])).trim();
36052
+ await input.git(["fetch", "origin", input.base]);
36053
+ try {
36054
+ await input.git(["merge", "--no-ff", "--no-edit", `origin/${input.base}`]);
36055
+ } catch (e) {
36056
+ await input.git(["merge", "--abort"]).catch(() => {
36057
+ });
36058
+ throw new Error(`updating ${input.head} from ${input.base} conflicted (${e.message.trim()}) \u2014 ${remedy}`);
36059
+ }
36060
+ await input.git(["push", "origin", input.head]);
36061
+ const to = (await input.git(["rev-parse", "HEAD"])).trim();
36062
+ return { from: from2, to, base: input.base, via: "local-merge" };
36063
+ }
36064
+ const pullPath = `repos/${input.repo}/pulls/${input.prNumber}`;
36065
+ const readHead2 = async () => (await input.rest("GET", pullPath)).head?.sha ?? "";
36066
+ const from = await readHead2();
36067
+ try {
36068
+ await input.rest("PUT", `${pullPath}/update-branch`, from ? { expected_head_sha: from } : void 0);
36069
+ } catch (e) {
36070
+ throw new Error(`GitHub could not update ${input.head} from ${input.base} (${e.message.trim()}) \u2014 ${remedy}`);
36071
+ }
36072
+ const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
36073
+ for (let attempt = 0; attempt < PR_HEAD_UPDATE_API_READ_RETRIES; attempt++) {
36074
+ const to = await readHead2();
36075
+ if (to && to !== from) return { from, to, base: input.base, via: "update-branch-api" };
36076
+ await sleep2(PR_HEAD_UPDATE_API_READ_DELAY_MS);
36077
+ }
36078
+ throw new Error(`GitHub accepted the update of ${input.head} from ${input.base} but the head did not move \u2014 check the PR, then rerun`);
36079
+ }
36009
36080
  var PR_LAND_POLL_MS = 3e4;
36010
36081
  var PR_LAND_ENQUEUE_TIMEOUT_MS = 10 * 6e4;
36011
36082
  var PR_LAND_STATE_READ_RETRIES = 3;
@@ -36076,21 +36147,30 @@ async function runPrLand(prNumber, options, deps) {
36076
36147
  }
36077
36148
  const ciPolicy = await deps.resolveCiPolicy(repo);
36078
36149
  base.ciPolicy = ciPolicy;
36079
- const checksWait = await deps.waitForChecks(prNumber, repo);
36080
- base.checksWait = checksWait;
36081
- if (checksWait.status === "conflicting") {
36082
- return {
36083
- ...base,
36084
- error: `checks-wait conflicting: ${checksWait.reason ?? "PR is conflicting with the base branch"}`
36085
- };
36086
- }
36087
- if (checksWait.status === "failure" || checksWait.status === "timeout" || checksWait.status === "rate-limited") {
36088
- return {
36089
- ...base,
36090
- error: `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`
36091
- };
36150
+ const checksWaitError = (checksWait) => {
36151
+ if (checksWait.status === "conflicting") {
36152
+ return `checks-wait conflicting: ${checksWait.reason ?? "PR is conflicting with the base branch"}`;
36153
+ }
36154
+ if (checksWait.status === "failure" || checksWait.status === "timeout" || checksWait.status === "rate-limited") {
36155
+ return `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`;
36156
+ }
36157
+ return void 0;
36158
+ };
36159
+ base.checksWait = await deps.waitForChecks(prNumber, repo);
36160
+ const waitError = checksWaitError(base.checksWait);
36161
+ if (waitError) return { ...base, error: waitError };
36162
+ let prState = await deps.readPrState(prNumber, repo);
36163
+ if (prState.mergeStateStatus === "BEHIND" && deps.updateBranch) {
36164
+ try {
36165
+ base.branchUpdated = await deps.updateBranch(prNumber, repo);
36166
+ } catch (e) {
36167
+ return { ...base, error: `pr land #${prNumber}: ${e.message}` };
36168
+ }
36169
+ base.checksWait = await deps.waitForChecks(prNumber, repo);
36170
+ const reWaitError = checksWaitError(base.checksWait);
36171
+ if (reWaitError) return { ...base, error: reWaitError };
36172
+ prState = await deps.readPrState(prNumber, repo);
36092
36173
  }
36093
- const prState = await deps.readPrState(prNumber, repo);
36094
36174
  if (prState.mergeStateStatus === "DIRTY" || prState.mergeable === "CONFLICTING") {
36095
36175
  return {
36096
36176
  ...base,
@@ -37581,15 +37661,193 @@ function postMergeReconWarnings(input) {
37581
37661
  return lines2;
37582
37662
  }
37583
37663
 
37584
- // src/pr-create-docs-check.ts
37664
+ // src/review-verdict.ts
37585
37665
  var import_node_child_process14 = require("node:child_process");
37666
+ var import_node_fs39 = require("node:fs");
37667
+ var import_node_os19 = require("node:os");
37668
+ var import_node_path36 = require("node:path");
37669
+ var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
37670
+ var REVIEW_WAIVER_MARKER = "<!-- zeroci-review-waived v1 -->";
37671
+ var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
37672
+ function isReviewVerdict(value) {
37673
+ return typeof value === "string" && REVIEW_VERDICTS.includes(value);
37674
+ }
37675
+ function renderReviewVerdictComment(input) {
37676
+ const payload = {
37677
+ v: 1,
37678
+ patch: input.patch,
37679
+ head: input.head,
37680
+ verdict: input.verdict,
37681
+ scope: input.scope,
37682
+ risk: input.risk,
37683
+ unverified: input.unverified,
37684
+ reviewer: input.reviewer
37685
+ };
37686
+ const findings = input.findings?.trim();
37687
+ return `${REVIEW_VERDICT_MARKER}
37688
+ \`\`\`json
37689
+ ${JSON.stringify(payload, null, 2)}
37690
+ \`\`\`
37691
+ ${findings ? `
37692
+ ${findings}
37693
+ ` : ""}`;
37694
+ }
37695
+ function isReviewVerdictComment(body) {
37696
+ return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
37697
+ }
37698
+ var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
37699
+ function parseReviewVerdictComment(body) {
37700
+ if (!isReviewVerdictComment(body)) return void 0;
37701
+ const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
37702
+ const fence = FENCE_RE.exec(rest);
37703
+ if (!fence) return void 0;
37704
+ let parsed;
37705
+ try {
37706
+ parsed = JSON.parse(fence[1]);
37707
+ } catch {
37708
+ return void 0;
37709
+ }
37710
+ if (!parsed || typeof parsed !== "object") return void 0;
37711
+ const p = parsed;
37712
+ if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
37713
+ if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
37714
+ if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
37715
+ const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
37716
+ return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
37717
+ }
37718
+ function evaluateReviewVerdict(comments, currentPatchId) {
37719
+ let latest;
37720
+ for (const c of comments) {
37721
+ if (!isReviewVerdictComment(c.body)) continue;
37722
+ if (!latest || c.createdAt >= latest.createdAt) latest = c;
37723
+ }
37724
+ if (!latest) return { ok: false, reason: "none" };
37725
+ const payload = parseReviewVerdictComment(latest.body);
37726
+ if (!payload) return { ok: false, reason: "malformed" };
37727
+ if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
37728
+ if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
37729
+ return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
37730
+ }
37731
+ function reviewVerdictRemedyCommand(number, repo) {
37732
+ return `mmi-cli devops pr review-verdict ${number} --repo ${repo} --verdict PROCEED --scope "<one line>" --risk "<one line>" --reviewer <seat-or-model-id>`;
37733
+ }
37734
+ function reviewVerdictRefusalMessage(context, number, repo, evaluation, currentPatchId) {
37735
+ const why = {
37736
+ none: "no review verdict comment on the PR",
37737
+ stale: `the latest verdict is bound to patch ${evaluation.patch} but the PR's current diff is ${currentPatchId} \u2014 the content changed since the review`,
37738
+ "not-proceed": `the latest verdict is ${evaluation.verdict}, not PROCEED`,
37739
+ malformed: "the latest verdict comment is malformed (marker present, JSON block unreadable or wrong shape)",
37740
+ proceed: "proceed"
37741
+ }[evaluation.reason];
37742
+ return `${context}: REVIEW VERDICT REQUIRED (#6255) \u2014 ${why}. Review PR #${number} and post the verdict, then rerun:
37743
+ ${reviewVerdictRemedyCommand(number, repo)}
37744
+ To land without a review, pass --without-review "<reason>" \u2014 it posts a waiver comment on the PR.`;
37745
+ }
37746
+ function renderReviewWaiverComment(reason, actor) {
37747
+ return `${REVIEW_WAIVER_MARKER}
37748
+ Review gate waived by @${actor}: ${reason.trim()}
37749
+ `;
37750
+ }
37751
+ function computePrPatchId(number, repo) {
37752
+ return new Promise((resolve7, reject) => {
37753
+ const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
37754
+ const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
37755
+ let out = "";
37756
+ let ghErr = "";
37757
+ let gitErr = "";
37758
+ const timer = setTimeout(() => {
37759
+ gh.kill();
37760
+ git3.kill();
37761
+ reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
37762
+ }, GC_GH_TIMEOUT_MS4);
37763
+ gh.stdout.pipe(git3.stdin);
37764
+ gh.stderr.on("data", (d) => {
37765
+ ghErr += d.toString();
37766
+ });
37767
+ git3.stderr.on("data", (d) => {
37768
+ gitErr += d.toString();
37769
+ });
37770
+ git3.stdout.on("data", (d) => {
37771
+ out += d.toString();
37772
+ });
37773
+ gh.on("error", (e) => {
37774
+ clearTimeout(timer);
37775
+ reject(e);
37776
+ });
37777
+ git3.on("error", (e) => {
37778
+ clearTimeout(timer);
37779
+ reject(e);
37780
+ });
37781
+ let ghCode;
37782
+ gh.on("close", (code) => {
37783
+ ghCode = code;
37784
+ });
37785
+ git3.on("close", (code) => {
37786
+ clearTimeout(timer);
37787
+ if (ghCode) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
37788
+ if (code) return reject(new Error(`git patch-id --stable exited ${code}: ${gitErr.trim()}`));
37789
+ const id = out.trim().split(/\s+/)[0];
37790
+ if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
37791
+ resolve7(id);
37792
+ });
37793
+ });
37794
+ }
37795
+ async function readPrHeadSha(number, repo) {
37796
+ const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
37797
+ const sha = stdout.trim();
37798
+ if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
37799
+ return sha;
37800
+ }
37801
+ async function readPrIssueComments(number, repo) {
37802
+ const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {body, createdAt: .created_at}"], { timeout: GC_GH_TIMEOUT_MS4 });
37803
+ return parseNdjsonLines(stdout);
37804
+ }
37805
+ async function postPrCommentFromFile(number, repo, body) {
37806
+ const dir = (0, import_node_fs39.mkdtempSync)((0, import_node_path36.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
37807
+ const path2 = (0, import_node_path36.join)(dir, "body.md");
37808
+ try {
37809
+ (0, import_node_fs39.writeFileSync)(path2, body, "utf8");
37810
+ const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
37811
+ return stdout.trim();
37812
+ } finally {
37813
+ try {
37814
+ (0, import_node_fs39.rmSync)(dir, { recursive: true, force: true });
37815
+ } catch {
37816
+ }
37817
+ }
37818
+ }
37819
+ async function requireReviewVerdict(context, number, repo, opts) {
37820
+ if (opts.withoutReview !== void 0) {
37821
+ const reason = opts.withoutReview.trim();
37822
+ if (!reason) throw new Error(`${context}: --without-review needs a non-empty reason`);
37823
+ const actor = await execFileP("gh", ["api", "user", "--jq", ".login"], { timeout: GC_GH_TIMEOUT_MS4 }).then((r) => r.stdout.trim()).catch(() => "") || "unknown";
37824
+ await postPrCommentFromFile(number, repo, renderReviewWaiverComment(reason, actor));
37825
+ console.warn(`${context}: review gate WAIVED by @${actor} \u2014 ${reason} (waiver comment posted on PR #${number})`);
37826
+ return void 0;
37827
+ }
37828
+ let comments;
37829
+ let currentPatchId;
37830
+ try {
37831
+ [comments, currentPatchId] = await Promise.all([readPrIssueComments(number, repo), computePrPatchId(number, repo)]);
37832
+ } catch (e) {
37833
+ const detail = e.message || String(e);
37834
+ const limited = isGitHubRateLimitError(e) ? " (gh rate-limited \u2014 retry after the pool resets)" : "";
37835
+ throw new Error(`${context}: cannot read PR #${number}'s review verdict${limited} \u2014 ${detail}. Refusing to merge WITHOUT the review gate (#6255).`);
37836
+ }
37837
+ const evaluation = evaluateReviewVerdict(comments, currentPatchId);
37838
+ if (evaluation.ok) return void 0;
37839
+ return reviewVerdictRefusalMessage(context, number, repo, evaluation, currentPatchId);
37840
+ }
37841
+
37842
+ // src/pr-create-docs-check.ts
37843
+ var import_node_child_process15 = require("node:child_process");
37586
37844
  var GIT_TIMEOUT_MS2 = 15e3;
37587
37845
  function catFileBatch(root, ref, paths) {
37588
37846
  if (paths.length === 0) return Promise.resolve([]);
37589
37847
  return new Promise((resolve7) => {
37590
37848
  const chunks = [];
37591
37849
  let settled = false;
37592
- const child2 = (0, import_node_child_process14.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
37850
+ const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
37593
37851
  const finish = () => {
37594
37852
  if (settled) return;
37595
37853
  settled = true;
@@ -37759,26 +38017,26 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
37759
38017
  }
37760
38018
 
37761
38019
  // src/worktree-merge-cleanup.ts
37762
- var import_node_fs41 = require("node:fs");
37763
- var import_node_path38 = require("node:path");
38020
+ var import_node_fs42 = require("node:fs");
38021
+ var import_node_path39 = require("node:path");
37764
38022
 
37765
38023
  // src/jervcode-node-modules-cleanup.ts
37766
- var import_node_fs39 = require("node:fs");
37767
- var import_node_os19 = require("node:os");
37768
- var import_node_path36 = require("node:path");
37769
- var JERVCODE_PACKAGE_ENTRY = (0, import_node_path36.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
38024
+ var import_node_fs40 = require("node:fs");
38025
+ var import_node_os20 = require("node:os");
38026
+ var import_node_path37 = require("node:path");
38027
+ var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
37770
38028
  var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
37771
38029
  var POSIX_NAMES2 = ["jervcode"];
37772
38030
  var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
37773
- function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os19.homedir)(), platform2 = process.platform) {
38031
+ function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
37774
38032
  const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
37775
38033
  const out = [];
37776
38034
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
37777
- for (const name of names) out.push((0, import_node_path36.join)(dir, name));
38035
+ for (const name of names) out.push((0, import_node_path37.join)(dir, name));
37778
38036
  }
37779
38037
  return out;
37780
38038
  }
37781
- function resolveJervcodePath(env = process.env, home = (0, import_node_os19.homedir)(), platform2 = process.platform, exists = import_node_fs39.existsSync) {
38039
+ function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
37782
38040
  for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
37783
38041
  if (exists(candidate)) return candidate;
37784
38042
  }
@@ -37786,10 +38044,10 @@ function resolveJervcodePath(env = process.env, home = (0, import_node_os19.home
37786
38044
  }
37787
38045
  function jervcodeExecFileArgs(args, opts = {}) {
37788
38046
  const platform2 = opts.platform ?? process.platform;
37789
- const exists = opts.exists ?? import_node_fs39.existsSync;
37790
- const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os19.homedir)(), platform2, exists);
38047
+ const exists = opts.exists ?? import_node_fs40.existsSync;
38048
+ const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
37791
38049
  if (resolved) {
37792
- const entry = (0, import_node_path36.join)((0, import_node_path36.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
38050
+ const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
37793
38051
  if (exists(entry)) {
37794
38052
  return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
37795
38053
  }
@@ -37820,8 +38078,8 @@ async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
37820
38078
  }
37821
38079
 
37822
38080
  // src/worktree-evidence-archive.ts
37823
- var import_node_fs40 = require("node:fs");
37824
- var import_node_path37 = require("node:path");
38081
+ var import_node_fs41 = require("node:fs");
38082
+ var import_node_path38 = require("node:path");
37825
38083
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
37826
38084
  function sanitizeArchiveSegment(value, max = 80) {
37827
38085
  const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -37836,24 +38094,24 @@ function resolveArtifactRunScope(env = process.env) {
37836
38094
  }
37837
38095
  function defaultIsDirectory(path2) {
37838
38096
  try {
37839
- return (0, import_node_fs40.lstatSync)(path2).isDirectory();
38097
+ return (0, import_node_fs41.lstatSync)(path2).isDirectory();
37840
38098
  } catch {
37841
38099
  return false;
37842
38100
  }
37843
38101
  }
37844
38102
  function archiveWorktreeJervArtifacts(args, deps = {}) {
37845
- const exists = deps.exists ?? import_node_fs40.existsSync;
38103
+ const exists = deps.exists ?? import_node_fs41.existsSync;
37846
38104
  const isDirectory = deps.isDirectory ?? defaultIsDirectory;
37847
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
38105
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
37848
38106
  const mkdirp = deps.mkdirp ?? ((path2) => {
37849
- (0, import_node_fs40.mkdirSync)(path2, { recursive: true });
38107
+ (0, import_node_fs41.mkdirSync)(path2, { recursive: true });
37850
38108
  });
37851
38109
  const env = deps.env ?? process.env;
37852
38110
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
37853
38111
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
37854
38112
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
37855
38113
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
37856
- const source = (0, import_node_path37.join)(args.worktreePath, ".jerv");
38114
+ const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
37857
38115
  if (!exists(source)) return { status: "absent" };
37858
38116
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
37859
38117
  const runScope = resolveArtifactRunScope(env);
@@ -37861,7 +38119,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
37861
38119
  const stamp = now().toISOString().replace(/[:.]/g, "-");
37862
38120
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
37863
38121
  try {
37864
- mkdirp((0, import_node_path37.dirname)(dest));
38122
+ mkdirp((0, import_node_path38.dirname)(dest));
37865
38123
  copyDir(source, dest);
37866
38124
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
37867
38125
  return { status: "archived", path: dest, runScope };
@@ -37870,14 +38128,14 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
37870
38128
  }
37871
38129
  }
37872
38130
  function defaultStat(path2) {
37873
- const st = (0, import_node_fs40.statSync)(path2);
38131
+ const st = (0, import_node_fs41.statSync)(path2);
37874
38132
  return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
37875
38133
  }
37876
38134
  function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
37877
- const exists = deps.exists ?? import_node_fs40.existsSync;
38135
+ const exists = deps.exists ?? import_node_fs41.existsSync;
37878
38136
  const stat4 = deps.stat ?? defaultStat;
37879
- const readdir2 = deps.readdir ?? import_node_fs40.readdirSync;
37880
- const tmpRoot = (0, import_node_path37.join)(worktreePath, "tmp");
38137
+ const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
38138
+ const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
37881
38139
  if (!exists(tmpRoot)) return [];
37882
38140
  const entries = [];
37883
38141
  const walk2 = (dir) => {
@@ -37888,14 +38146,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
37888
38146
  return;
37889
38147
  }
37890
38148
  for (const name of names) {
37891
- const full = (0, import_node_path37.join)(dir, name);
38149
+ const full = (0, import_node_path38.join)(dir, name);
37892
38150
  let st;
37893
38151
  try {
37894
38152
  st = stat4(full);
37895
38153
  } catch {
37896
38154
  continue;
37897
38155
  }
37898
- const relPath = (0, import_node_path37.relative)(worktreePath, full).replace(/\\/g, "/");
38156
+ const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
37899
38157
  if (st.isDirectory()) {
37900
38158
  if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
37901
38159
  walk2(full);
@@ -37909,10 +38167,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
37909
38167
  return entries;
37910
38168
  }
37911
38169
  function archiveWorktreeTmpArtifacts(args, deps = {}) {
37912
- const exists = deps.exists ?? import_node_fs40.existsSync;
37913
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
38170
+ const exists = deps.exists ?? import_node_fs41.existsSync;
38171
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
37914
38172
  const mkdirp = deps.mkdirp ?? ((path2) => {
37915
- (0, import_node_fs40.mkdirSync)(path2, { recursive: true });
38173
+ (0, import_node_fs41.mkdirSync)(path2, { recursive: true });
37916
38174
  });
37917
38175
  const env = deps.env ?? process.env;
37918
38176
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
@@ -37920,7 +38178,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
37920
38178
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
37921
38179
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
37922
38180
  const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
37923
- const source = (0, import_node_path37.join)(args.worktreePath, "tmp");
38181
+ const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
37924
38182
  if (!scanned.length) return { status: "absent" };
37925
38183
  if (!exists(source)) return { status: "absent" };
37926
38184
  const runScope = resolveArtifactRunScope(env);
@@ -37928,7 +38186,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
37928
38186
  const stamp = now().toISOString().replace(/[:.]/g, "-");
37929
38187
  const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
37930
38188
  try {
37931
- mkdirp((0, import_node_path37.dirname)(dest));
38189
+ mkdirp((0, import_node_path38.dirname)(dest));
37932
38190
  copyDir(source, dest);
37933
38191
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
37934
38192
  const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
@@ -37997,9 +38255,9 @@ function normPath2(p) {
37997
38255
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
37998
38256
  }
37999
38257
  function unlinkNodeModulesJunction(wtPath) {
38000
- const nm = (0, import_node_path38.join)(wtPath, "node_modules");
38258
+ const nm = (0, import_node_path39.join)(wtPath, "node_modules");
38001
38259
  try {
38002
- if ((0, import_node_fs41.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs41.rmdirSync)(nm);
38260
+ if ((0, import_node_fs42.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs42.rmdirSync)(nm);
38003
38261
  return { ok: true };
38004
38262
  } catch (e) {
38005
38263
  if (e.code === "ENOENT") return { ok: true };
@@ -38010,8 +38268,8 @@ function defaultSleep2(ms) {
38010
38268
  return new Promise((resolve7) => setTimeout(resolve7, ms));
38011
38269
  }
38012
38270
  async function removeResidueDirectory(wtPath, options = {}) {
38013
- const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs41.rmdirSync, exists = import_node_fs41.existsSync } = options;
38014
- const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs41.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
38271
+ const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs42.rmdirSync, exists = import_node_fs42.existsSync } = options;
38272
+ const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs42.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
38015
38273
  const junction = unlinkNodeModulesJunction(wtPath);
38016
38274
  if (!junction.ok) return junction;
38017
38275
  try {
@@ -38134,31 +38392,31 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
38134
38392
  }
38135
38393
  async function listNestedIgnoredNodeModules(wtPath, execGit) {
38136
38394
  const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
38137
- return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path38.join)(wtPath, line.slice(0, -1)));
38395
+ return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path39.join)(wtPath, line.slice(0, -1)));
38138
38396
  }
38139
38397
  function safeRemoveTree(path2) {
38140
- const stat4 = (0, import_node_fs41.lstatSync)(path2);
38398
+ const stat4 = (0, import_node_fs42.lstatSync)(path2);
38141
38399
  if (stat4.isSymbolicLink()) {
38142
38400
  try {
38143
- (0, import_node_fs41.rmdirSync)(path2);
38401
+ (0, import_node_fs42.rmdirSync)(path2);
38144
38402
  } catch {
38145
- (0, import_node_fs41.unlinkSync)(path2);
38403
+ (0, import_node_fs42.unlinkSync)(path2);
38146
38404
  }
38147
38405
  return;
38148
38406
  }
38149
38407
  if (stat4.isDirectory()) {
38150
- for (const entry of (0, import_node_fs41.readdirSync)(path2)) safeRemoveTree((0, import_node_path38.join)(path2, entry));
38151
- (0, import_node_fs41.rmdirSync)(path2);
38408
+ for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
38409
+ (0, import_node_fs42.rmdirSync)(path2);
38152
38410
  return;
38153
38411
  }
38154
- (0, import_node_fs41.unlinkSync)(path2);
38412
+ (0, import_node_fs42.unlinkSync)(path2);
38155
38413
  }
38156
38414
  function errorMessage(e) {
38157
38415
  return e instanceof Error ? e.message : String(e);
38158
38416
  }
38159
38417
  function resolvedOrRaw(path2) {
38160
38418
  try {
38161
- return normPath2((0, import_node_fs41.realpathSync)(path2));
38419
+ return normPath2((0, import_node_fs42.realpathSync)(path2));
38162
38420
  } catch {
38163
38421
  return normPath2(path2);
38164
38422
  }
@@ -38166,10 +38424,10 @@ function resolvedOrRaw(path2) {
38166
38424
  function unlinkEscapingReparsePoints(root, primaryRoot) {
38167
38425
  let realRoot;
38168
38426
  try {
38169
- if ((0, import_node_fs41.lstatSync)(root).isSymbolicLink()) {
38427
+ if ((0, import_node_fs42.lstatSync)(root).isSymbolicLink()) {
38170
38428
  return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
38171
38429
  }
38172
- realRoot = normPath2((0, import_node_fs41.realpathSync)(root));
38430
+ realRoot = normPath2((0, import_node_fs42.realpathSync)(root));
38173
38431
  } catch (e) {
38174
38432
  if (e.code === "ENOENT") return { ok: true, unlinked: [] };
38175
38433
  return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
@@ -38185,16 +38443,16 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
38185
38443
  const dir = stack.pop();
38186
38444
  let entries;
38187
38445
  try {
38188
- entries = (0, import_node_fs41.readdirSync)(dir, { withFileTypes: true });
38446
+ entries = (0, import_node_fs42.readdirSync)(dir, { withFileTypes: true });
38189
38447
  } catch (e) {
38190
38448
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
38191
38449
  }
38192
38450
  for (const entry of entries) {
38193
- const child2 = (0, import_node_path38.join)(dir, entry.name);
38451
+ const child2 = (0, import_node_path39.join)(dir, entry.name);
38194
38452
  if (entry.isSymbolicLink()) {
38195
38453
  let target = "";
38196
38454
  try {
38197
- target = normPath2((0, import_node_fs41.realpathSync)(child2));
38455
+ target = normPath2((0, import_node_fs42.realpathSync)(child2));
38198
38456
  } catch {
38199
38457
  target = "";
38200
38458
  }
@@ -38224,7 +38482,7 @@ async function describePreCleanFailure(wtPath, execGit, error) {
38224
38482
  const more = remaining.length > 10 ? ` (+${remaining.length - 10} more)` : "";
38225
38483
  const quote = (path2) => path2.replace(/'/g, "''");
38226
38484
  const nested = remaining.find((path2) => path2.endsWith("node_modules/") && path2 !== "node_modules/");
38227
- const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0, import_node_path38.join)(wtPath, nested.slice(0, -1)))}' -Recurse -Force` : void 0;
38485
+ const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0, import_node_path39.join)(wtPath, nested.slice(0, -1)))}' -Recurse -Force` : void 0;
38228
38486
  return { ok: false, error: `${error}; remaining ignored paths: ${shown}${more}`, ...remediation ? { remediation } : {} };
38229
38487
  }
38230
38488
  function formatWorktreeRemovalFailureDetail(options) {
@@ -38378,7 +38636,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
38378
38636
  return report;
38379
38637
  }
38380
38638
  const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
38381
- const pathExists = options.pathExists ?? import_node_fs41.existsSync;
38639
+ const pathExists = options.pathExists ?? import_node_fs42.existsSync;
38382
38640
  let afterWorktrees = [];
38383
38641
  try {
38384
38642
  afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
@@ -38518,7 +38776,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
38518
38776
  const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
38519
38777
  if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
38520
38778
  unlinkedReparsePoints.push(...preHelperGuard.unlinked);
38521
- if (pathExists((0, import_node_path38.join)(wtPath, "node_modules"))) {
38779
+ if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
38522
38780
  const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
38523
38781
  if (!nmRemoved.ok) {
38524
38782
  report.worktree = {
@@ -38685,10 +38943,10 @@ function argvWantsJson2() {
38685
38943
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
38686
38944
  }
38687
38945
  function hubRoot() {
38688
- const fromPkg = (0, import_node_path39.join)(__dirname, "..", "..");
38946
+ const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
38689
38947
  const marker = "skills/bootstrap/seeds/manifest.json";
38690
- if ((0, import_node_fs42.existsSync)((0, import_node_path39.join)(fromPkg, marker))) return fromPkg;
38691
- if ((0, import_node_fs42.existsSync)((0, import_node_path39.join)(process.cwd(), marker))) return process.cwd();
38948
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
38949
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
38692
38950
  return null;
38693
38951
  }
38694
38952
  function ciAuditDeps() {
@@ -38700,8 +38958,8 @@ function ciAuditDeps() {
38700
38958
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
38701
38959
  readSeedFile: (path2) => {
38702
38960
  if (!root) return null;
38703
- const fullPath = (0, import_node_path39.join)(root, path2);
38704
- return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
38961
+ const fullPath = (0, import_node_path40.join)(root, path2);
38962
+ return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
38705
38963
  }
38706
38964
  };
38707
38965
  }
@@ -38783,7 +39041,7 @@ function scheduleRelatedDiscovery(o) {
38783
39041
  try {
38784
39042
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
38785
39043
  if (o.repo) args.push("--repo", o.repo);
38786
- spawnDetachedSelf(args, { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
39044
+ spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
38787
39045
  } catch {
38788
39046
  }
38789
39047
  }
@@ -39344,11 +39602,11 @@ ${list}`);
39344
39602
  }
39345
39603
  });
39346
39604
  async function listCiWorkflowPaths(cwd = process.cwd()) {
39347
- const wfDir = (0, import_node_path39.join)(cwd, ".github", "workflows");
39348
- if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
39349
- return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39605
+ const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
39606
+ if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
39607
+ return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39350
39608
  try {
39351
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(wfDir, name), "utf8"));
39609
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
39352
39610
  } catch {
39353
39611
  return true;
39354
39612
  }
@@ -39536,7 +39794,49 @@ ${list}`);
39536
39794
  if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
39537
39795
  if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
39538
39796
  });
39539
- pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land 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) => {
39797
+ jsonParity(pr.command("review-verdict <number>").description("post the ZeroCI review verdict comment the merge helper requires (#6255): computes the PR diff patch-id and head sha itself, renders the v1 contract body, posts it as a PR comment. Only a PROCEED bound to the CURRENT diff lets `pr merge` / `pr land` merge").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
39798
+ if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
39799
+ const verdict = o.verdict;
39800
+ const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
39801
+ let patch;
39802
+ let head;
39803
+ try {
39804
+ [patch, head] = await Promise.all([computePrPatchId(number, o.repo), readPrHeadSha(number, o.repo)]);
39805
+ } catch (e) {
39806
+ return failGraceful(`pr review-verdict: ${e.message}`);
39807
+ }
39808
+ const body = renderReviewVerdictComment({ v: 1, patch, head, verdict, scope: o.scope, risk: o.risk, unverified: o.unverified, reviewer: o.reviewer, findings });
39809
+ let commentUrl;
39810
+ try {
39811
+ commentUrl = await postPrCommentFromFile(number, o.repo, body);
39812
+ } catch (e) {
39813
+ const err = e;
39814
+ return failGraceful(`pr review-verdict: ${(err.stderr || err.message || String(e)).trim()}`);
39815
+ }
39816
+ printLine(JSON.stringify({ number: Number(number), repo: o.repo, verdict, patch, head, commentUrl }));
39817
+ });
39818
+ async function updatePrHeadForMerge(input) {
39819
+ return updatePrHeadFromBase({
39820
+ ...input,
39821
+ git: async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
39822
+ rest: (method, path2, body) => defaultGitHubClient().rest(method, path2, { ...body ? { body } : {}, timeoutMs: GH_MUTATION_TIMEOUT_MS })
39823
+ });
39824
+ }
39825
+ async function prHeadCheckedOutHere(head, repo, explicitRepo) {
39826
+ if (await gitOut(["branch", "--show-current"]) !== head) return false;
39827
+ if (!explicitRepo || !repo) return true;
39828
+ const cwdRepo = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"]));
39829
+ return cwdRepo?.toLowerCase() === repo.split("/").slice(-2).join("/").toLowerCase();
39830
+ }
39831
+ async function prLandUpdateBranch(prNumber, repo, explicitRepo) {
39832
+ const viewed = JSON.parse((await execFileP("gh", ["pr", "view", prNumber, "--repo", repo, "--json", "headRefName,baseRefName"], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
39833
+ const localCheckedOut = await prHeadCheckedOutHere(viewed.headRefName, repo, explicitRepo);
39834
+ console.warn(`pr land: PR #${prNumber} is BEHIND ${viewed.baseRefName} \u2014 updating ${viewed.headRefName} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and re-waiting the checks once (#6263).`);
39835
+ return updatePrHeadForMerge({ prNumber, repo, head: viewed.headRefName, base: viewed.baseRefName, localCheckedOut });
39836
+ }
39837
+ class PrHeadBehindBaseError extends Error {
39838
+ }
39839
+ pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land 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").option("--without-review <reason>", "land without a review verdict (#6255): posts a `zeroci-review-waived` comment carrying the reason and actor, then proceeds").action(async (number, o) => {
39540
39840
  if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
39541
39841
  try {
39542
39842
  const parsed = parseIssueRef(number, o.repo);
@@ -39571,6 +39871,15 @@ ${list}`);
39571
39871
  return;
39572
39872
  }
39573
39873
  if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
39874
+ {
39875
+ const gateRepo = landRepoForGuard ?? await requireRepo(o.repo);
39876
+ const refusal = await requireReviewVerdict("pr land", number, gateRepo, { withoutReview: o.withoutReview });
39877
+ if (refusal) {
39878
+ console.error(refusal);
39879
+ process.exitCode = 1;
39880
+ return;
39881
+ }
39882
+ }
39574
39883
  const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
39575
39884
  resolveRepo: async (prNumber, repoOpt) => {
39576
39885
  const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
@@ -39645,6 +39954,7 @@ ${list}`);
39645
39954
  allowedClosing: landClosingGuardInput?.closing,
39646
39955
  bodyText: landSquashBody
39647
39956
  }),
39957
+ updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)),
39648
39958
  pollMerged: async (prNumber, repo, deadlineMs) => {
39649
39959
  let lastFailure;
39650
39960
  while (Date.now() < deadlineMs) {
@@ -39705,7 +40015,7 @@ ${list}`);
39705
40015
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
39706
40016
  if (result.status === "failed") process.exitCode = 1;
39707
40017
  });
39708
- 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); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").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) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").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) => {
40018
+ 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); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").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) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").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").option("--without-review <reason>", "merge without a review verdict (#6255): posts a `zeroci-review-waived` comment carrying the reason and actor, then proceeds")).action(async (number, o) => {
39709
40019
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
39710
40020
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
39711
40021
  if (o.disableAuto) {
@@ -39743,7 +40053,7 @@ ${list}`);
39743
40053
  const mergeSquashBody = squashBodyTextForMerge(
39744
40054
  closingGuardInput,
39745
40055
  method === "--squash",
39746
- o.squashBodyFile ? (0, import_node_fs42.readFileSync)(o.squashBodyFile, "utf8") : void 0
40056
+ o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
39747
40057
  );
39748
40058
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
39749
40059
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -39757,6 +40067,15 @@ ${list}`);
39757
40067
  return;
39758
40068
  }
39759
40069
  if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
40070
+ if (prMeta.state !== "MERGED") {
40071
+ const gateRepo = repoForPostCleanup ?? await requireRepo(o.repo);
40072
+ const refusal = await requireReviewVerdict("pr merge", number, gateRepo, { withoutReview: o.withoutReview });
40073
+ if (refusal) {
40074
+ console.error(refusal);
40075
+ process.exitCode = 1;
40076
+ return;
40077
+ }
40078
+ }
39760
40079
  if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
39761
40080
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
39762
40081
  const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
@@ -39775,7 +40094,7 @@ ${list}`);
39775
40094
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
39776
40095
  let foreignCheckout;
39777
40096
  if (foreignCwd) {
39778
- const sibling = (0, import_node_path39.join)((0, import_node_path39.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
40097
+ const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
39779
40098
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
39780
40099
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
39781
40100
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -39787,7 +40106,7 @@ ${list}`);
39787
40106
  }
39788
40107
  const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
39789
40108
  const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
39790
- if (o.wait) {
40109
+ const runWaitGate = async () => {
39791
40110
  console.warn(`pr merge: --wait can hold the shell up to the full ${PR_CHECKS_TIMEOUT_MS / 6e4}m budget plus the merge \u2014 run under a shell timeout above that budget or as a background/monitor task (#6027)`);
39792
40111
  const repo = await requireRepo(o.repo);
39793
40112
  const budgetMs = PR_CHECKS_TIMEOUT_MS;
@@ -39799,7 +40118,7 @@ ${list}`);
39799
40118
  if (snapshotRead.state === "failed") {
39800
40119
  console.error(`pr merge: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
39801
40120
  process.exitCode = 1;
39802
- return;
40121
+ return false;
39803
40122
  }
39804
40123
  const baseBranch = snapshotRead.snapshot.baseRef;
39805
40124
  const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
@@ -39834,9 +40153,11 @@ ${list}`);
39834
40153
  console.warn(`pr merge: --wait stopped before merge \u2014 ${wait.status}${wait.reason ? `: ${wait.reason}` : ""}${wait.detail ? ` (${wait.detail})` : ""}`);
39835
40154
  for (const line of failedChecksReceiptLines("pr merge", wait)) console.warn(line);
39836
40155
  process.exitCode = wait.status === "timeout" || wait.status === "rate-limited" ? PR_CHECKS_TIMEOUT_EXIT_CODE : 1;
39837
- return;
40156
+ return false;
39838
40157
  }
39839
- }
40158
+ return true;
40159
+ };
40160
+ if (o.wait && !await runWaitGate()) return;
39840
40161
  if (ciPolicy.policy === "no-ci") {
39841
40162
  const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
39842
40163
  if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
@@ -39852,73 +40173,90 @@ ${list}`);
39852
40173
  { allowedClosing: method === "--squash" ? closingGuardInput?.closing : void 0 }
39853
40174
  );
39854
40175
  const bodyFile = overrideBody?.path;
39855
- try {
39856
- await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
39857
- const message2 = String(e.message || "");
39858
- if (/already been merged/i.test(message2)) {
39859
- remoteNotAttemptedReason = "pr-already-merged";
39860
- return;
40176
+ let branchUpdated;
40177
+ const mergeOnce = () => execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
40178
+ const message2 = String(e.message || "");
40179
+ if (/already been merged/i.test(message2)) {
40180
+ remoteNotAttemptedReason = "pr-already-merged";
40181
+ return;
40182
+ }
40183
+ const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
40184
+ if (note) throw new Error(`gh pr merge ${number}: ${note}`);
40185
+ if (!o.auto && !branchUpdated) {
40186
+ const mergeState = prHeadBehindBase({ message: message2 }) ? "BEHIND" : (await execFileP("gh", ["pr", "view", number, ...repoArgs, "--json", "mergeStateStatus", "--jq", ".mergeStateStatus"], { timeout: GC_GH_TIMEOUT_MS4 }).catch(() => ({ stdout: "" }))).stdout.trim();
40187
+ if (prHeadBehindBase({ mergeStateStatus: mergeState })) throw new PrHeadBehindBaseError(message2);
40188
+ }
40189
+ if (isGitHubRateLimitError(e)) {
40190
+ if (o.auto) {
40191
+ throw new Error(`gh pr merge ${number}: GraphQL rate limit exhausted, and enabling auto-merge has no REST equivalent \u2014 retry without --auto to merge now via REST, or retry --auto after the pool resets. (${message2.trim()})`);
39861
40192
  }
39862
- const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
39863
- if (note) throw new Error(`gh pr merge ${number}: ${note}`);
39864
- if (isGitHubRateLimitError(e)) {
39865
- if (o.auto) {
39866
- throw new Error(`gh pr merge ${number}: GraphQL rate limit exhausted, and enabling auto-merge has no REST equivalent \u2014 retry without --auto to merge now via REST, or retry --auto after the pool resets. (${message2.trim()})`);
40193
+ if (!repoForPostCleanup) throw e;
40194
+ console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
40195
+ const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
40196
+ await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
40197
+ body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
40198
+ timeoutMs: GH_MUTATION_TIMEOUT_MS
40199
+ });
40200
+ return;
40201
+ }
40202
+ if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
40203
+ console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
40204
+ await new Promise((resolve7) => setTimeout(resolve7, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
40205
+ await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
40206
+ const m2 = String(e2.message || "");
40207
+ if (/already been merged/i.test(m2)) {
40208
+ remoteNotAttemptedReason = "pr-already-merged";
40209
+ return;
39867
40210
  }
39868
- if (!repoForPostCleanup) throw e;
39869
- console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
39870
- const commitMessage = bodyFile ? (0, import_node_fs42.readFileSync)(bodyFile, "utf8") : void 0;
39871
- await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
39872
- body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
39873
- timeoutMs: GH_MUTATION_TIMEOUT_MS
39874
- });
39875
- return;
39876
- }
39877
- if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
39878
- console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
39879
- await new Promise((resolve7) => setTimeout(resolve7, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
39880
- await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
39881
- const m2 = String(e2.message || "");
39882
- if (/already been merged/i.test(m2)) {
39883
- remoteNotAttemptedReason = "pr-already-merged";
39884
- return;
39885
- }
39886
- const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
39887
- if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
39888
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
39889
- });
39890
- return;
39891
- }
39892
- if (o.auto && mergeAutoRejectedPrAlreadyClean(message2)) {
39893
- await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
39894
- const m2 = String(e2.message || "");
39895
- if (/already been merged/i.test(m2)) {
39896
- remoteNotAttemptedReason = "pr-already-merged";
39897
- return;
39898
- }
39899
- const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
39900
- if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
39901
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
39902
- });
39903
- return;
39904
- }
39905
- if (!o.auto && basePolicyBlocksImmediateMerge(message2)) {
39906
- console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
39907
- upgradedToAuto = true;
39908
- await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
39909
- const m2 = String(e2.message || "");
39910
- if (/already been merged/i.test(m2)) {
39911
- remoteNotAttemptedReason = "pr-already-merged";
39912
- return;
39913
- }
39914
- const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
39915
- if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
39916
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
39917
- });
39918
- return;
39919
- }
39920
- if (!ghPrMergeLocalBranchDeleteWarning(message2)) throw e;
39921
- });
40211
+ const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
40212
+ if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
40213
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
40214
+ });
40215
+ return;
40216
+ }
40217
+ if (o.auto && mergeAutoRejectedPrAlreadyClean(message2)) {
40218
+ await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
40219
+ const m2 = String(e2.message || "");
40220
+ if (/already been merged/i.test(m2)) {
40221
+ remoteNotAttemptedReason = "pr-already-merged";
40222
+ return;
40223
+ }
40224
+ const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
40225
+ if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
40226
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
40227
+ });
40228
+ return;
40229
+ }
40230
+ if (!o.auto && basePolicyBlocksImmediateMerge(message2)) {
40231
+ console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
40232
+ upgradedToAuto = true;
40233
+ await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
40234
+ const m2 = String(e2.message || "");
40235
+ if (/already been merged/i.test(m2)) {
40236
+ remoteNotAttemptedReason = "pr-already-merged";
40237
+ return;
40238
+ }
40239
+ const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
40240
+ if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
40241
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
40242
+ });
40243
+ return;
40244
+ }
40245
+ if (!ghPrMergeLocalBranchDeleteWarning(message2)) throw e;
40246
+ });
40247
+ try {
40248
+ try {
40249
+ await mergeOnce();
40250
+ } catch (e) {
40251
+ if (!(e instanceof PrHeadBehindBaseError)) throw e;
40252
+ const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
40253
+ console.warn(`pr merge: GitHub refused the merge \u2014 PR #${number}'s head is BEHIND ${baseRef} \u2014 updating ${headRef} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and retrying once (#6263).`);
40254
+ branchUpdated = await updatePrHeadForMerge({ prNumber: number, repo: await requireRepo(o.repo), head: headRef, base: baseRef, localCheckedOut }).catch((e2) => {
40255
+ throw new Error(`pr merge: ${e2.message}`);
40256
+ });
40257
+ if (o.wait && !await runWaitGate()) return;
40258
+ await mergeOnce();
40259
+ }
39922
40260
  } finally {
39923
40261
  overrideBody?.cleanup();
39924
40262
  }
@@ -39953,7 +40291,8 @@ ${list}`);
39953
40291
  pr: number,
39954
40292
  branch: headRef,
39955
40293
  state: stateRead.ok ? stateRead.state : "unknown",
39956
- cleanupStatus: "skipped"
40294
+ cleanupStatus: "skipped",
40295
+ ...branchUpdated ? { branchUpdated } : {}
39957
40296
  }));
39958
40297
  console.error(`pr merge: ${gate.message}. Nothing was deleted \u2014 re-check the PR and retry.`);
39959
40298
  process.exitCode = 1;
@@ -39973,7 +40312,7 @@ ${list}`);
39973
40312
  preserveWorktree: o.preserveWorktree,
39974
40313
  gcAcknowledged: o.gc,
39975
40314
  expectedHeadOid: headRefOid,
39976
- pathExists: (p) => (0, import_node_fs42.existsSync)(p),
40315
+ pathExists: (p) => (0, import_node_fs43.existsSync)(p),
39977
40316
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
39978
40317
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
39979
40318
  // 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
@@ -40064,6 +40403,7 @@ ${list}`);
40064
40403
  ...methodField ? { method: methodField } : {},
40065
40404
  remoteBranch,
40066
40405
  housekeeping,
40406
+ ...branchUpdated ? { branchUpdated } : {},
40067
40407
  ...foreignCwd ? { foreignCwd: true, ...foreignCheckout ? { foreignCheckout } : {} } : {},
40068
40408
  ...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
40069
40409
  ...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
@@ -40094,7 +40434,7 @@ ${list}`);
40094
40434
  }
40095
40435
 
40096
40436
  // src/command-register-developer.ts
40097
- var import_node_fs47 = require("node:fs");
40437
+ var import_node_fs48 = require("node:fs");
40098
40438
 
40099
40439
  // src/whoami.ts
40100
40440
  async function resolveWhoami(deps) {
@@ -40124,7 +40464,7 @@ async function resolveWhoami(deps) {
40124
40464
  }
40125
40465
 
40126
40466
  // src/command-register-developer.ts
40127
- var import_node_path44 = require("node:path");
40467
+ var import_node_path45 = require("node:path");
40128
40468
 
40129
40469
  // src/wave-land.ts
40130
40470
  function planWaveLand(prs) {
@@ -40155,7 +40495,7 @@ async function executeWaveLand(plan, deps) {
40155
40495
  }
40156
40496
 
40157
40497
  // src/box-commands.ts
40158
- var import_node_fs43 = require("node:fs");
40498
+ var import_node_fs44 = require("node:fs");
40159
40499
 
40160
40500
  // src/box.ts
40161
40501
  var BOX_KEYS = {
@@ -40357,7 +40697,7 @@ function registerBoxCommands(program3) {
40357
40697
  return;
40358
40698
  }
40359
40699
  const wroteScript = o.ssh && o.script ? o.script : null;
40360
- if (wroteScript) (0, import_node_fs43.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
40700
+ if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
40361
40701
  if (o.json) {
40362
40702
  console.log(JSON.stringify({
40363
40703
  box: found,
@@ -40377,21 +40717,21 @@ ${SSH_RECIPE_AGENT_NOTE}`);
40377
40717
  }
40378
40718
 
40379
40719
  // src/dist-drift.ts
40380
- var import_node_child_process16 = require("node:child_process");
40720
+ var import_node_child_process17 = require("node:child_process");
40381
40721
  var import_node_crypto12 = require("node:crypto");
40382
- var import_node_fs45 = require("node:fs");
40383
- var import_node_os20 = require("node:os");
40384
- var import_node_path41 = require("node:path");
40722
+ var import_node_fs46 = require("node:fs");
40723
+ var import_node_os21 = require("node:os");
40724
+ var import_node_path42 = require("node:path");
40385
40725
 
40386
40726
  // ../scripts/distribution-digest.mjs
40387
40727
  var import_node_crypto11 = require("node:crypto");
40388
- var import_node_fs44 = require("node:fs");
40389
- var import_node_path40 = require("node:path");
40728
+ var import_node_fs45 = require("node:fs");
40729
+ var import_node_path41 = require("node:path");
40390
40730
  var slash = (value) => value.replaceAll("\\", "/");
40391
40731
  function repoPath(root, declaredPath, label) {
40392
- const absoluteRoot = (0, import_node_path40.resolve)(root);
40393
- const target = (0, import_node_path40.resolve)(root, declaredPath);
40394
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path40.sep}`)) {
40732
+ const absoluteRoot = (0, import_node_path41.resolve)(root);
40733
+ const target = (0, import_node_path41.resolve)(root, declaredPath);
40734
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
40395
40735
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
40396
40736
  }
40397
40737
  return target;
@@ -40399,7 +40739,7 @@ function repoPath(root, declaredPath, label) {
40399
40739
  function digestFiles(files) {
40400
40740
  const hash = (0, import_node_crypto11.createHash)("sha256");
40401
40741
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
40402
- const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs44.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs44.readFileSync)(file.absolute);
40742
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
40403
40743
  hash.update(file.relative, "utf8");
40404
40744
  hash.update("\0");
40405
40745
  hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
@@ -40414,8 +40754,8 @@ function digestFiles(files) {
40414
40754
  function digestPackedFiles(packageRoot, packedFiles) {
40415
40755
  return digestFiles(packedFiles.map((path2) => {
40416
40756
  const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
40417
- if (!(0, import_node_fs44.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
40418
- return { absolute, relative: slash(path2), stat: (0, import_node_fs44.lstatSync)(absolute) };
40757
+ if (!(0, import_node_fs45.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
40758
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs45.lstatSync)(absolute) };
40419
40759
  }));
40420
40760
  }
40421
40761
 
@@ -40499,13 +40839,13 @@ function renderDistDriftReceipt(receipt) {
40499
40839
  return lines2;
40500
40840
  }
40501
40841
  function readOrNull(path2) {
40502
- return (0, import_node_fs45.existsSync)(path2) ? (0, import_node_fs45.readFileSync)(path2) : null;
40842
+ return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
40503
40843
  }
40504
40844
  function walkFiles(root) {
40505
40845
  const files = [];
40506
40846
  const walk2 = (directory) => {
40507
- for (const entry of (0, import_node_fs45.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
40508
- const child2 = (0, import_node_path41.join)(directory, entry.name);
40847
+ for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
40848
+ const child2 = (0, import_node_path42.join)(directory, entry.name);
40509
40849
  if (entry.isDirectory()) walk2(child2);
40510
40850
  else files.push(child2);
40511
40851
  }
@@ -40515,14 +40855,14 @@ function walkFiles(root) {
40515
40855
  }
40516
40856
  function bomPathFor(root) {
40517
40857
  try {
40518
- const registry2 = JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path41.join)(root, "surfaces.json"), "utf8"));
40519
- return (0, import_node_path41.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
40858
+ const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
40859
+ return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
40520
40860
  } catch {
40521
- return (0, import_node_path41.join)(root, "distribution-bom.json");
40861
+ return (0, import_node_path42.join)(root, "distribution-bom.json");
40522
40862
  }
40523
40863
  }
40524
40864
  function rebuildTo(packageRoot, outDir) {
40525
- (0, import_node_child_process16.execFileSync)(process.execPath, ["build.mjs"], {
40865
+ (0, import_node_child_process17.execFileSync)(process.execPath, ["build.mjs"], {
40526
40866
  cwd: packageRoot,
40527
40867
  env: { ...process.env, MMI_DIST_OUTDIR: outDir },
40528
40868
  windowsHide: true,
@@ -40531,35 +40871,35 @@ function rebuildTo(packageRoot, outDir) {
40531
40871
  });
40532
40872
  }
40533
40873
  function runDistStatus(root) {
40534
- const stage = (0, import_node_fs45.mkdtempSync)((0, import_node_path41.join)((0, import_node_os20.tmpdir)(), "mmi-dist-drift-"));
40874
+ const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
40535
40875
  let overlayCount = 0;
40536
40876
  try {
40537
- const cliOut = (0, import_node_path41.join)(stage, "cli-dist");
40538
- const hubOut = (0, import_node_path41.join)(stage, "hub-dist");
40539
- rebuildTo((0, import_node_path41.join)(root, "cli"), cliOut);
40540
- rebuildTo((0, import_node_path41.join)(root, "updater"), hubOut);
40877
+ const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
40878
+ const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
40879
+ rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
40880
+ rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
40541
40881
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
40542
40882
  const rebuilt = (path2) => {
40543
40883
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
40544
- return spec ? readOrNull((0, import_node_path41.join)(outDirFor(spec.packageDir), spec.output)) : null;
40884
+ return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
40545
40885
  };
40546
- const committed = (path2) => readOrNull((0, import_node_path41.join)(root, path2));
40547
- const tree = (path2) => readOrNull((0, import_node_path41.join)(root, path2));
40548
- const distRoot = (0, import_node_path41.join)(root, "cli", "dist");
40549
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path41.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
40550
- const bom = JSON.parse((0, import_node_fs45.readFileSync)(bomPathFor(root), "utf8"));
40886
+ const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
40887
+ const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
40888
+ const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
40889
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
40890
+ const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
40551
40891
  const digest = (entries) => {
40552
- const overlay = (0, import_node_path41.join)(stage, `overlay-${overlayCount++}`);
40892
+ const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
40553
40893
  for (const entry of entries) {
40554
- const target = (0, import_node_path41.join)(overlay, entry.path);
40555
- (0, import_node_fs45.mkdirSync)((0, import_node_path41.dirname)(target), { recursive: true });
40556
- (0, import_node_fs45.writeFileSync)(target, entry.bytes);
40894
+ const target = (0, import_node_path42.join)(overlay, entry.path);
40895
+ (0, import_node_fs46.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
40896
+ (0, import_node_fs46.writeFileSync)(target, entry.bytes);
40557
40897
  }
40558
40898
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
40559
40899
  };
40560
40900
  return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
40561
40901
  } finally {
40562
- (0, import_node_fs45.rmSync)(stage, { recursive: true, force: true });
40902
+ (0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
40563
40903
  }
40564
40904
  }
40565
40905
 
@@ -40631,7 +40971,7 @@ function registerEdgeCommands(program3) {
40631
40971
 
40632
40972
  // src/schedules-lift-command.ts
40633
40973
  var import_promises8 = require("node:fs/promises");
40634
- var import_node_path42 = require("node:path");
40974
+ var import_node_path43 = require("node:path");
40635
40975
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
40636
40976
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
40637
40977
  var SchedulesLiftUsageError = class extends Error {
@@ -40658,7 +40998,7 @@ async function readWorkflowFiles(dir) {
40658
40998
  const files = [];
40659
40999
  for (const name of names.sort()) {
40660
41000
  if (!/\.ya?ml$/.test(name)) continue;
40661
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path42.join)(dir, name), "utf8") });
41001
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
40662
41002
  }
40663
41003
  return files;
40664
41004
  }
@@ -40740,9 +41080,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
40740
41080
  }
40741
41081
 
40742
41082
  // src/spawn-policy-core.ts
40743
- var import_node_child_process17 = require("node:child_process");
40744
- var import_node_fs46 = require("node:fs");
40745
- var import_node_path43 = require("node:path");
41083
+ var import_node_child_process18 = require("node:child_process");
41084
+ var import_node_fs47 = require("node:fs");
41085
+ var import_node_path44 = require("node:path");
40746
41086
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
40747
41087
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
40748
41088
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -40811,7 +41151,7 @@ function findViolationsInSource(raw) {
40811
41151
  return found;
40812
41152
  }
40813
41153
  function policedFiles(root) {
40814
- const r = (0, import_node_child_process17.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
41154
+ const r = (0, import_node_child_process18.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
40815
41155
  cwd: root,
40816
41156
  encoding: "utf8",
40817
41157
  windowsHide: true,
@@ -40828,7 +41168,7 @@ function runSpawnPolicy(root) {
40828
41168
  for (const file of files) {
40829
41169
  let raw;
40830
41170
  try {
40831
- raw = (0, import_node_fs46.readFileSync)((0, import_node_path43.join)(root, file), "utf8");
41171
+ raw = (0, import_node_fs47.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
40832
41172
  } catch {
40833
41173
  continue;
40834
41174
  }
@@ -40848,19 +41188,19 @@ function runSpawnPolicy(root) {
40848
41188
  function registerDeveloperCommands(program3) {
40849
41189
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
40850
41190
  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) => {
40851
- const path2 = (0, import_node_path44.join)(process.cwd(), ".gitignore");
40852
- const current = (0, import_node_fs47.existsSync)(path2) ? (0, import_node_fs47.readFileSync)(path2, "utf8") : null;
41191
+ const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
41192
+ const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
40853
41193
  const plan = planManagedGitignore(current);
40854
41194
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
40855
41195
  if (opts.json) {
40856
- if (opts.write && plan.changed) (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
41196
+ if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
40857
41197
  console.log(JSON.stringify(plan, null, 2));
40858
41198
  if (!opts.write && plan.changed) process.exitCode = 1;
40859
41199
  return;
40860
41200
  }
40861
41201
  if (opts.write) {
40862
41202
  if (plan.changed) {
40863
- (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
41203
+ (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
40864
41204
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
40865
41205
  } else {
40866
41206
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -42348,12 +42688,12 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
42348
42688
  }
42349
42689
 
42350
42690
  // src/hotfix-coverage.ts
42351
- var import_node_child_process18 = require("node:child_process");
42691
+ var import_node_child_process19 = require("node:child_process");
42352
42692
  var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
42353
42693
  function checkHotfixCoverage(options = {}) {
42354
42694
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
42355
42695
  const ack = (options.ack ?? []).filter(Boolean);
42356
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process18.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
42696
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
42357
42697
  const revList = (range) => {
42358
42698
  const out = git3(["rev-list", "--no-merges", range]).trim();
42359
42699
  return out ? out.split("\n") : [];
@@ -42421,7 +42761,7 @@ function checkHotfixCoverage(options = {}) {
42421
42761
  }
42422
42762
  function checkHotfixCarries(options) {
42423
42763
  const { cwd = process.cwd(), branch, baseRef, targets } = options;
42424
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process18.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
42764
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
42425
42765
  const isAncestor = (sha, ref) => {
42426
42766
  try {
42427
42767
  git3(["merge-base", "--is-ancestor", sha, ref]);
@@ -42453,8 +42793,8 @@ function checkHotfixCarries(options) {
42453
42793
  }
42454
42794
 
42455
42795
  // src/train-commands.ts
42456
- var import_node_fs48 = require("node:fs");
42457
- var import_node_path45 = require("node:path");
42796
+ var import_node_fs49 = require("node:fs");
42797
+ var import_node_path46 = require("node:path");
42458
42798
  var INVOKED_ARGV = process.argv.slice(2);
42459
42799
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
42460
42800
  function resolveReleaseBumpIntent(raw) {
@@ -42466,7 +42806,7 @@ function resolveReleaseBumpIntent(raw) {
42466
42806
  }
42467
42807
  function readRepoVersion() {
42468
42808
  try {
42469
- return JSON.parse((0, import_node_fs48.readFileSync)((0, import_node_path45.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
42809
+ return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
42470
42810
  } catch {
42471
42811
  return void 0;
42472
42812
  }
@@ -43210,12 +43550,12 @@ ${r.stderr ?? ""}`).catch(() => "");
43210
43550
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
43211
43551
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
43212
43552
  function envHealLockPath(home) {
43213
- return (0, import_node_path46.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43553
+ return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43214
43554
  }
43215
43555
  async function withEnvHealLock(what, run) {
43216
43556
  try {
43217
43557
  return await withFileLock(
43218
- envHealLockPath((0, import_node_os21.homedir)()),
43558
+ envHealLockPath((0, import_node_os22.homedir)()),
43219
43559
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
43220
43560
  run
43221
43561
  );
@@ -43311,7 +43651,7 @@ function mmiDoctorDeps(opts = {}) {
43311
43651
  const configRoot = surfaceConfigRoot(surface);
43312
43652
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
43313
43653
  const plan = buildPluginCachePlan(
43314
- (0, import_node_os21.homedir)(),
43654
+ (0, import_node_os22.homedir)(),
43315
43655
  running,
43316
43656
  pluginCacheFsDeps(configRoot, () => 0),
43317
43657
  { configRoot, includeStaging: surface !== "codex" }
@@ -43339,14 +43679,14 @@ function mmiDoctorDeps(opts = {}) {
43339
43679
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
43340
43680
  const installed = installedActivePluginVersion(surface);
43341
43681
  const plan = buildPluginCachePlan(
43342
- (0, import_node_os21.homedir)(),
43682
+ (0, import_node_os22.homedir)(),
43343
43683
  running,
43344
43684
  pluginCacheFsDeps(configRoot, () => 0),
43345
43685
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
43346
43686
  );
43347
43687
  const result = applyPluginCachePlan(
43348
43688
  plan,
43349
- (p) => (0, import_node_fs49.rmSync)(p, { recursive: true }),
43689
+ (p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
43350
43690
  stagingApplyFsGuard(configRoot)
43351
43691
  );
43352
43692
  return {
@@ -43381,7 +43721,7 @@ function mmiDoctorDeps(opts = {}) {
43381
43721
  // has no generated routing index would
43382
43722
  // get a permanent — demanding an artifact it never asked for.
43383
43723
  docsIndexState: (root) => {
43384
- if (!(0, import_node_fs49.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return void 0;
43724
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
43385
43725
  const real = createDocsIndexDeps(root);
43386
43726
  let docs;
43387
43727
  const listDocs = () => docs ??= real.listDocs();
@@ -43390,7 +43730,7 @@ function mmiDoctorDeps(opts = {}) {
43390
43730
  },
43391
43731
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
43392
43732
  healDocsIndex: (root) => {
43393
- if (!(0, import_node_fs49.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
43733
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
43394
43734
  const real = createDocsIndexDeps(root);
43395
43735
  let docs;
43396
43736
  const listDocs = () => docs ??= real.listDocs();
@@ -43949,7 +44289,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
43949
44289
  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`);
43950
44290
  if (o.secretsFile) {
43951
44291
  try {
43952
- vars.push(`secrets=${(0, import_node_fs49.readFileSync)(o.secretsFile, "utf8")}`);
44292
+ vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
43953
44293
  } catch (e) {
43954
44294
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
43955
44295
  }
@@ -44262,16 +44602,16 @@ function ciAuditDeps2() {
44262
44602
  // gate re-seed step is skipped gracefully rather than failing mid-run.
44263
44603
  readSeedFile: (path2) => {
44264
44604
  if (!root) return null;
44265
- const fullPath = (0, import_node_path46.join)(root, path2);
44266
- return (0, import_node_fs49.existsSync)(fullPath) ? (0, import_node_fs49.readFileSync)(fullPath, "utf8") : null;
44605
+ const fullPath = (0, import_node_path47.join)(root, path2);
44606
+ return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
44267
44607
  }
44268
44608
  };
44269
44609
  }
44270
44610
  function hubRoot2() {
44271
- const fromPkg = (0, import_node_path46.join)(__dirname, "..", "..");
44611
+ const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
44272
44612
  const marker = "skills/bootstrap/seeds/manifest.json";
44273
- if ((0, import_node_fs49.existsSync)((0, import_node_path46.join)(fromPkg, marker))) return fromPkg;
44274
- if ((0, import_node_fs49.existsSync)((0, import_node_path46.join)(process.cwd(), marker))) return process.cwd();
44613
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
44614
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
44275
44615
  return null;
44276
44616
  }
44277
44617
  registerQueryCommands(program2);
@@ -44371,10 +44711,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
44371
44711
  targets = resolution.targets;
44372
44712
  }
44373
44713
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
44374
- const fileMatrix = (0, import_node_fs49.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs49.readFileSync)("access-matrix.json", "utf8")) : {};
44714
+ const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
44375
44715
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
44376
44716
  const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
44377
- const sanctioned = (0, import_node_fs49.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs49.readFileSync)("access-matrix.json", "utf8")) : {};
44717
+ const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
44378
44718
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
44379
44719
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
44380
44720
  if (!report.ok) process.exitCode = 1;
@@ -44405,16 +44745,16 @@ function directoryBytes(path2) {
44405
44745
  let total = 0;
44406
44746
  let entries;
44407
44747
  try {
44408
- entries = (0, import_node_fs49.readdirSync)(path2, { withFileTypes: true });
44748
+ entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
44409
44749
  } catch {
44410
44750
  return 0;
44411
44751
  }
44412
44752
  for (const entry of entries) {
44413
- const child2 = (0, import_node_path46.join)(path2, entry.name);
44753
+ const child2 = (0, import_node_path47.join)(path2, entry.name);
44414
44754
  if (entry.isDirectory()) total += directoryBytes(child2);
44415
44755
  else {
44416
44756
  try {
44417
- total += (0, import_node_fs49.statSync)(child2).size;
44757
+ total += (0, import_node_fs50.statSync)(child2).size;
44418
44758
  } catch {
44419
44759
  }
44420
44760
  }
@@ -44422,25 +44762,25 @@ function directoryBytes(path2) {
44422
44762
  return total;
44423
44763
  }
44424
44764
  function listDirEntries(dir) {
44425
- return (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
44765
+ return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
44426
44766
  }
44427
44767
  function readInstalledPluginRefs(configRoot) {
44428
44768
  const p = installedPluginsPathForConfig(configRoot);
44429
- if (!(0, import_node_fs49.existsSync)(p)) return [];
44769
+ if (!(0, import_node_fs50.existsSync)(p)) return [];
44430
44770
  try {
44431
- return installedPluginPaths((0, import_node_fs49.readFileSync)(p, "utf8"));
44771
+ return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
44432
44772
  } catch {
44433
44773
  return null;
44434
44774
  }
44435
44775
  }
44436
44776
  function pluginCacheFsDeps(configRoot, dirBytes) {
44437
44777
  return {
44438
- exists: (p) => (0, import_node_fs49.existsSync)(p),
44439
- listVersionDirs: (root) => (0, import_node_fs49.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
44778
+ exists: (p) => (0, import_node_fs50.existsSync)(p),
44779
+ listVersionDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
44440
44780
  dirBytes,
44441
- listStagingDirs: (root) => (0, import_node_fs49.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
44781
+ listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
44442
44782
  try {
44443
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path46.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs49.statSync)(p).mtimeMs) };
44783
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
44444
44784
  } catch {
44445
44785
  return { name: d.name, mtimeMs: Date.now() };
44446
44786
  }
@@ -44454,10 +44794,10 @@ function stagingApplyFsGuard(configRoot) {
44454
44794
  return {
44455
44795
  referencedPaths: () => readInstalledPluginRefs(configRoot),
44456
44796
  mtimeMs: (name) => {
44457
- const p = (0, import_node_path46.join)(stagingRoot, name);
44458
- if (!(0, import_node_fs49.existsSync)(p)) return null;
44797
+ const p = (0, import_node_path47.join)(stagingRoot, name);
44798
+ if (!(0, import_node_fs50.existsSync)(p)) return null;
44459
44799
  try {
44460
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs49.statSync)(q).mtimeMs);
44800
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
44461
44801
  } catch {
44462
44802
  return null;
44463
44803
  }
@@ -44477,13 +44817,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
44477
44817
  return;
44478
44818
  }
44479
44819
  const plan = buildPluginCachePlan(
44480
- (0, import_node_os21.homedir)(),
44820
+ (0, import_node_os22.homedir)(),
44481
44821
  running,
44482
44822
  pluginCacheFsDeps(configRoot, directoryBytes),
44483
44823
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
44484
44824
  );
44485
44825
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
44486
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs49.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
44826
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs50.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
44487
44827
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
44488
44828
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
44489
44829
  else console.log(renderPluginCachePlan(plan, result));