@mutmutco/cli 4.3.20 → 4.3.21

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 +684 -487
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5227,7 +5227,7 @@ function unknownCommandDomainGuide(parentPath, token) {
5227
5227
 
5228
5228
  // src/command-composition.ts
5229
5229
  var import_node_os22 = require("node:os");
5230
- var import_node_path48 = require("node:path");
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");
@@ -12902,6 +12902,29 @@ async function waitForPrChecks(deps) {
12902
12902
 
12903
12903
  // src/bootstrap-ruleset.ts
12904
12904
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
12905
+ var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
12906
+ function reseedProductRulesetStrictness(current, seed) {
12907
+ const parse = (raw) => {
12908
+ const policy2 = JSON.parse(raw);
12909
+ if (!policy2 || typeof policy2 !== "object" || Array.isArray(policy2) || policy2.name !== PRODUCT_RULESET_NAME || policy2.target !== "branch" || !Array.isArray(policy2.rules) || policy2.rules.some((r) => !r || typeof r !== "object" || Array.isArray(r) || typeof r.type !== "string")) {
12910
+ throw new Error("product ruleset must be a branch ruleset object with well-formed rules");
12911
+ }
12912
+ const required = policy2.rules.filter((r) => r.type === "required_status_checks");
12913
+ const parameters2 = required[0]?.parameters;
12914
+ if (required.length !== 1 || !parameters2 || typeof parameters2 !== "object" || Array.isArray(parameters2) || !Array.isArray(parameters2.required_status_checks) || !parameters2.required_status_checks.length || parameters2.required_status_checks.some((c) => !c || typeof c !== "object" || Array.isArray(c) || typeof c.context !== "string" || !c.context.trim()) || parameters2.strict_required_status_checks_policy !== void 0 && typeof parameters2.strict_required_status_checks_policy !== "boolean") {
12915
+ throw new Error("product ruleset must carry exactly one well-formed required_status_checks rule");
12916
+ }
12917
+ return { policy: policy2, parameters: parameters2 };
12918
+ };
12919
+ const strict = parse(seed).parameters.strict_required_status_checks_policy;
12920
+ if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
12921
+ if (current === null) return seed;
12922
+ const { policy, parameters } = parse(current);
12923
+ if (parameters.strict_required_status_checks_policy === strict) return current;
12924
+ parameters.strict_required_status_checks_policy = strict;
12925
+ return `${JSON.stringify(policy, null, 2)}
12926
+ `;
12927
+ }
12905
12928
  function stripRulesetComment(raw) {
12906
12929
  const parsed = JSON.parse(raw);
12907
12930
  delete parsed._comment;
@@ -12921,14 +12944,6 @@ function rulesetStrictPolicy(ruleset) {
12921
12944
  const rules = (ruleset.rules ?? []).filter((rule) => rule.type === "required_status_checks");
12922
12945
  return rules.length > 0 && rules.every((rule) => rule.parameters?.strict_required_status_checks_policy === true);
12923
12946
  }
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
- }
12932
12947
  function rulesetBranchIncludes(ruleset) {
12933
12948
  const raw = ruleset.conditions?.ref_name?.include;
12934
12949
  return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
@@ -12948,8 +12963,7 @@ function patchRulesetRequiredContexts(body, contexts) {
12948
12963
  ...r,
12949
12964
  parameters: {
12950
12965
  ...r.parameters,
12951
- strict_required_status_checks_policy: true,
12952
- // #6263: org policy — see patchRulesetStrictPolicy
12966
+ strict_required_status_checks_policy: r.parameters?.strict_required_status_checks_policy ?? false,
12953
12967
  required_status_checks: sorted.map((context) => ({ context }))
12954
12968
  }
12955
12969
  };
@@ -13353,7 +13367,7 @@ function extractForwardRefs(markdown) {
13353
13367
  });
13354
13368
  return refs;
13355
13369
  }
13356
- function checkPins(root, readFile10, docs) {
13370
+ function checkPins(root, readFile9, docs) {
13357
13371
  const findings = [];
13358
13372
  for (const [doc, markdown] of Object.entries(docs)) {
13359
13373
  for (const pin of extractPins(markdown)) {
@@ -13361,7 +13375,7 @@ function checkPins(root, readFile10, docs) {
13361
13375
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
13362
13376
  continue;
13363
13377
  }
13364
- const source = readFile10((0, import_node_path12.join)(root, pin.file));
13378
+ const source = readFile9((0, import_node_path12.join)(root, pin.file));
13365
13379
  if (source == null) {
13366
13380
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
13367
13381
  continue;
@@ -13576,7 +13590,7 @@ function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_chi
13576
13590
  }
13577
13591
  }
13578
13592
  function runDocRefs(root, deps = {}) {
13579
- const readFile10 = deps.readFile ?? readFileOrNull;
13593
+ const readFile9 = deps.readFile ?? readFileOrNull;
13580
13594
  const exists = deps.exists ?? import_node_fs14.existsSync;
13581
13595
  const listDocs = deps.listDocs ?? defaultListDocs;
13582
13596
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
@@ -13585,11 +13599,11 @@ function runDocRefs(root, deps = {}) {
13585
13599
  const walked = listDocs(root);
13586
13600
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
13587
13601
  const docs = Object.fromEntries(
13588
- walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
13602
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
13589
13603
  );
13590
13604
  const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
13591
13605
  const findings = [
13592
- ...checkPins(root, readFile10, docs).findings,
13606
+ ...checkPins(root, readFile9, docs).findings,
13593
13607
  ...refResult.findings
13594
13608
  ];
13595
13609
  if (commandPaths == null) {
@@ -14186,22 +14200,22 @@ function labelsToPrune(orgLabelNames) {
14186
14200
  const org = new Set(orgLabelNames);
14187
14201
  return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
14188
14202
  }
14189
- function resolveSeedContent(seed, vars, readFile10) {
14190
- if (seed.source === "self") return readFile10(seed.target);
14203
+ function resolveSeedContent(seed, vars, readFile9) {
14204
+ if (seed.source === "self") return readFile9(seed.target);
14191
14205
  if (seed.source.startsWith("seed:")) {
14192
- const tmpl = readFile10(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
14206
+ const tmpl = readFile9(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
14193
14207
  return tmpl == null ? null : renderSeed(tmpl, vars);
14194
14208
  }
14195
14209
  return null;
14196
14210
  }
14197
- function resolveSeedWriteContent(seed, vars, readFile10, remoteContent) {
14211
+ function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
14198
14212
  if (!seed.managedBlock) {
14199
- return { ok: true, content: resolveSeedContent(seed, vars, readFile10), managed: seed.source === "managed-block" };
14213
+ return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
14200
14214
  }
14201
- const base = remoteContent ?? resolveSeedContent(seed, vars, readFile10);
14215
+ const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
14202
14216
  if (base == null) return { ok: true, content: null, managed: true };
14203
14217
  const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
14204
- const desired = resolveSeedContent(blockSeed, vars, readFile10);
14218
+ const desired = resolveSeedContent(blockSeed, vars, readFile9);
14205
14219
  if (desired == null) return { ok: true, content: null, managed: true };
14206
14220
  const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
14207
14221
  return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
@@ -15502,10 +15516,10 @@ var rollout_plan_default = {
15502
15516
  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)."
15503
15517
  },
15504
15518
  baseline: {
15505
- version: "4.3.20",
15506
- tag: "v4.3.20",
15507
- commit: "f33217850417",
15508
- npm: "@mutmutco/cli@4.3.20"
15519
+ version: "4.3.21",
15520
+ tag: "v4.3.21",
15521
+ commit: "f55fc2217cd3",
15522
+ npm: "@mutmutco/cli@4.3.21"
15509
15523
  },
15510
15524
  exitCriterion: "fleet-n-of-n",
15511
15525
  hubOnlyShortcut: "forbidden",
@@ -15522,14 +15536,14 @@ var rollout_plan_default = {
15522
15536
  repo: "mutmutco/mmi-hub",
15523
15537
  role: "canary",
15524
15538
  schedule: "train",
15525
- v3Target: "v4.3.20"
15539
+ v3Target: "v4.3.21"
15526
15540
  }
15527
15541
  ],
15528
15542
  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.",
15529
15543
  rollback: {
15530
15544
  independent: true,
15531
- mechanism: "npm dist-tag latest -> 4.3.20 and redeploy the Hub Lambda from tag v4.3.20 (f33217850417); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15532
- v3Target: "v4.3.20 (@mutmutco/cli@4.3.20, tag commit f33217850417 \u2014 last known-good release carrying the repo-index v4-only contract)"
15545
+ mechanism: "npm dist-tag latest -> 4.3.21 and redeploy the Hub Lambda from tag v4.3.21 (f55fc2217cd3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15546
+ v3Target: "v4.3.21 (@mutmutco/cli@4.3.21, tag commit f55fc2217cd3 \u2014 last known-good release carrying the repo-index v4-only contract)"
15533
15547
  }
15534
15548
  },
15535
15549
  {
@@ -23527,12 +23541,12 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
23527
23541
  const contexts = sortedUnique(registryContexts ?? committedContexts);
23528
23542
  const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
23529
23543
  const committedStrict = rulesetStrictPolicy(committedPayload);
23530
- let apiPayload = patchRulesetStrictPolicy(registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts));
23544
+ let apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
23531
23545
  if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
23532
23546
  const branchIncludes = rulesetBranchIncludes(apiPayload);
23533
23547
  const authoritativeFilePayload = {
23534
23548
  ...filePayload,
23535
- ...registryContexts == null && committedStrict ? {} : { rules: apiPayload.rules },
23549
+ ...registryContexts == null ? {} : { rules: apiPayload.rules },
23536
23550
  ...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
23537
23551
  };
23538
23552
  return {
@@ -23554,7 +23568,7 @@ function sameContexts(left, right) {
23554
23568
  function resolveProductRulesetReconcilePlan(input) {
23555
23569
  const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
23556
23570
  const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
23557
- const liveNeedsStrictConvergence = input.liveStrict === false;
23571
+ const liveNeedsStrictConvergence = input.liveStrict !== void 0 && input.authorityStrict !== void 0 && input.liveStrict !== input.authorityStrict;
23558
23572
  const liveIsActive = input.liveEnforcement === "active";
23559
23573
  if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence && !liveNeedsStrictConvergence) {
23560
23574
  return { shouldActivate: false, targetEnforcement: "active" };
@@ -23765,12 +23779,12 @@ async function auditRepoCi(repo, deps) {
23765
23779
  const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
23766
23780
  const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
23767
23781
  const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
23768
- const strictAligned = authoritativeRuleset.committedStrict && liveStrict;
23782
+ const strictAligned = authoritativeRuleset.committedStrict === liveStrict;
23769
23783
  const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned && strictAligned;
23770
23784
  checks.push({
23771
23785
  ok: aligned,
23772
23786
  label: RULESET_REFERENCE_MATCH_LABEL,
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}`),
23787
+ 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 differs from committed policy \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`),
23774
23788
  remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
23775
23789
  });
23776
23790
  }
@@ -24360,6 +24374,7 @@ async function applyCiReconcileRepo(repo, deps) {
24360
24374
  liveBranchIncludes,
24361
24375
  authorityBranchIncludes: authority.branchIncludes,
24362
24376
  liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
24377
+ authorityStrict: authority.committedStrict,
24363
24378
  gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
24364
24379
  unsafeContexts: unsafe
24365
24380
  });
@@ -27167,11 +27182,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
27167
27182
  label: "product required-check ruleset enforcement active",
27168
27183
  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
27169
27184
  });
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
- });
27175
27185
  const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
27176
27186
  const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
27177
27187
  checks.push({
@@ -27225,6 +27235,100 @@ function renderBootstrapVerifyReport(report) {
27225
27235
  return lines2.join("\n");
27226
27236
  }
27227
27237
 
27238
+ // src/bootstrap-seed-delivery.ts
27239
+ var import_node_crypto9 = require("node:crypto");
27240
+ var SHA = /^[a-f0-9]{40}$/;
27241
+ async function readSeedFile(repo, target, ref, client) {
27242
+ try {
27243
+ const file = await client.rest(
27244
+ "GET",
27245
+ `repos/${repo}/contents/${target.split("/").map(encodeURIComponent).join("/")}?ref=${encodeURIComponent(ref)}`
27246
+ );
27247
+ if (!file?.sha || !SHA.test(file.sha) || file.encoding !== "base64" || typeof file.content !== "string") {
27248
+ throw new Error("bootstrap apply: scoped seed file is unreadable");
27249
+ }
27250
+ return { sha: file.sha, content: decodeGitHubContents(file.content) };
27251
+ } catch (e) {
27252
+ if (e instanceof GitHubApiError && e.status === 404) return null;
27253
+ throw e;
27254
+ }
27255
+ }
27256
+ async function readTargetedSeedFiles(repo, plan, target, ref, client) {
27257
+ const base = await readSeedFile(repo, target, plan.baseSha, client);
27258
+ const candidate = ref === plan.baseSha ? base : await readSeedFile(repo, target, ref, client);
27259
+ return { base, candidate };
27260
+ }
27261
+ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, client) {
27262
+ const [rules, base] = await Promise.all([
27263
+ client.rest("GET", `repos/${repo}/rules/branches/${encodeURIComponent(baseBranch)}`),
27264
+ client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(baseBranch)}`)
27265
+ ]);
27266
+ if (!Array.isArray(rules) || rules.some((rule) => !rule || typeof rule.type !== "string" || !rule.type)) {
27267
+ throw new Error("bootstrap apply: base branch protection is unreadable");
27268
+ }
27269
+ const baseSha = base?.object?.sha;
27270
+ if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
27271
+ const identity = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
27272
+ return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
27273
+ }
27274
+ async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
27275
+ let ref;
27276
+ try {
27277
+ ref = await client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(plan.ref)}`);
27278
+ } catch (e) {
27279
+ if (allowMissing && e instanceof GitHubApiError && e.status === 404) return null;
27280
+ throw e;
27281
+ }
27282
+ const head = ref?.object?.sha;
27283
+ if (!head || !SHA.test(head)) throw new Error("bootstrap apply: scoped seed head is unreadable");
27284
+ if (head === plan.baseSha) return head;
27285
+ const diff = await client.rest("GET", `repos/${repo}/compare/${plan.baseSha}...${head}`);
27286
+ if (diff?.status !== "ahead" || diff.merge_base_commit?.sha !== plan.baseSha || !Array.isArray(diff.files) || diff.files.length !== 1 || diff.files[0].filename !== target || diff.files[0].previous_filename !== void 0 || !["added", "modified"].includes(diff.files[0].status ?? "")) {
27287
+ throw new Error("bootstrap apply: scoped seed branch contains changes outside the requested target or base");
27288
+ }
27289
+ return head;
27290
+ }
27291
+ async function prepareTargetedSeedBranch(repo, plan, target, client, gh) {
27292
+ const candidates = await client.rest(
27293
+ "GET",
27294
+ `repos/${repo}/pulls?state=open&head=${encodeURIComponent(`${repo.split("/")[0]}:${plan.branch}`)}&base=${encodeURIComponent(plan.baseBranch)}&per_page=2`
27295
+ );
27296
+ if (!Array.isArray(candidates) || candidates.length > 1) throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
27297
+ for (const pr of candidates) {
27298
+ if (!Number.isInteger(pr?.number) || pr.number <= 0 || pr.head?.ref !== plan.branch || pr.base?.ref !== plan.baseBranch) {
27299
+ throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
27300
+ }
27301
+ await gh(["pr", "merge", String(pr.number), "--repo", repo, "--disable-auto"]);
27302
+ const after = await client.rest("GET", `repos/${repo}/pulls/${pr.number}`);
27303
+ if (after?.auto_merge !== null) throw new Error("bootstrap apply: scoped seed PR auto-merge was not disabled");
27304
+ }
27305
+ const existing = await verifyTargetedSeedBranch(repo, plan, target, client, true);
27306
+ if (existing) return;
27307
+ try {
27308
+ await client.rest("POST", `repos/${repo}/git/refs`, { body: { ref: `refs/heads/${plan.branch}`, sha: plan.baseSha } });
27309
+ } catch (e) {
27310
+ if (!(e instanceof GitHubApiError) || e.status !== 422) throw e;
27311
+ }
27312
+ await verifyTargetedSeedBranch(repo, plan, target, client);
27313
+ }
27314
+ async function enableTargetedSeedAutoMerge(repo, number, plan, target, expected, client, gh) {
27315
+ const head = await verifyTargetedSeedBranch(repo, plan, target, client);
27316
+ const pr = await client.rest("GET", `repos/${repo}/pulls/${number}`);
27317
+ if (pr?.state !== "open" || pr.head?.ref !== plan.branch || pr.head?.sha !== head || pr.head.repo?.full_name?.toLowerCase() !== repo.toLowerCase() || pr.base?.ref !== plan.baseBranch || pr.base.sha !== plan.baseSha || pr.base.repo?.full_name?.toLowerCase() !== repo.toLowerCase() || pr.changed_files !== 1) throw new Error("bootstrap apply: scoped seed PR identity or diff changed");
27318
+ const files = await client.rest(
27319
+ "GET",
27320
+ `repos/${repo}/pulls/${number}/files?per_page=2`
27321
+ );
27322
+ if (!Array.isArray(files) || files.length !== 1 || files[0]?.filename !== target || files[0].previous_filename !== void 0) {
27323
+ throw new Error("bootstrap apply: scoped seed PR contains changes outside the requested target");
27324
+ }
27325
+ const file = await readSeedFile(repo, target, head, client);
27326
+ if (file?.content !== expected) {
27327
+ throw new Error("bootstrap apply: scoped seed content differs from this invocation");
27328
+ }
27329
+ await gh(["pr", "merge", String(number), "--repo", repo, "--auto", "--squash", "--match-head-commit", head]);
27330
+ }
27331
+
27228
27332
  // src/bootstrap-commands.ts
27229
27333
  var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
27230
27334
  function bootstrapTrainDoctorDeps() {
@@ -27537,7 +27641,7 @@ function registerBootstrapCommands(program3) {
27537
27641
  }
27538
27642
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
27539
27643
  const gh = async (args) => execFileP("gh", args, { timeout: 2e4 });
27540
- const readFile10 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
27644
+ const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
27541
27645
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
27542
27646
  const putSeed = async (target, content, ref, sha) => {
27543
27647
  const tmp = (0, import_node_path25.join)((0, import_node_os14.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
@@ -27632,14 +27736,23 @@ function registerBootstrapCommands(program3) {
27632
27736
  });
27633
27737
  }
27634
27738
  let seedPlan = { mode: "direct", ref: baseBranch, reason: "dry-run (no protection probe)" };
27739
+ let scopedPlan;
27740
+ let scopedContent;
27635
27741
  let seededToBranch = 0;
27636
- if (o.execute) {
27742
+ if (onlyTarget) {
27743
+ scopedPlan = await targetedSeedPlan(repo, slug, onlyTarget, baseBranch, seedSource.sha, controlClient);
27744
+ seedPlan = scopedPlan;
27745
+ if (o.execute && scopedPlan.branch) {
27746
+ await prepareTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient, gh);
27747
+ }
27748
+ } else if (o.execute) {
27637
27749
  let branchRules = [];
27638
27750
  try {
27639
27751
  branchRules = JSON.parse((await gh(["api", `repos/${repo}/rules/branches/${baseBranch}`])).stdout || "[]");
27640
27752
  } catch {
27641
- branchRules = [];
27753
+ throw new Error("bootstrap apply: base branch protection is unreadable");
27642
27754
  }
27755
+ if (!Array.isArray(branchRules)) throw new Error("bootstrap apply: base branch protection is unreadable");
27643
27756
  seedPlan = planSeedDelivery(branchRules, slug, baseBranch);
27644
27757
  if (seedPlan.branch) {
27645
27758
  const headSha = (await gh(["api", `repos/${repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"])).stdout.trim();
@@ -27651,6 +27764,7 @@ function registerBootstrapCommands(program3) {
27651
27764
  }
27652
27765
  }
27653
27766
  const docsForIndex = [];
27767
+ const seedReadRef = !o.execute && scopedPlan ? scopedPlan.baseSha : seedPlan.ref;
27654
27768
  for (const seed of seedsToApply) {
27655
27769
  if (!seed.classes.includes(o.class)) continue;
27656
27770
  if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
@@ -27664,8 +27778,20 @@ function registerBootstrapCommands(program3) {
27664
27778
  let exists = false;
27665
27779
  let sha;
27666
27780
  let remoteContent = null;
27667
- try {
27668
- const r = await gh(["api", `repos/${repo}/contents/${enc(resolved.target)}?ref=${seedPlan.ref}`]);
27781
+ let baseContent = null;
27782
+ const preserveRuleset = onlyTarget === PRODUCT_RULESET_PATH;
27783
+ if (scopedPlan) {
27784
+ try {
27785
+ const files = await readTargetedSeedFiles(repo, scopedPlan, resolved.target, seedReadRef, controlClient);
27786
+ baseContent = files.base?.content ?? null;
27787
+ exists = files.candidate !== null;
27788
+ sha = files.candidate?.sha;
27789
+ remoteContent = files.candidate?.content ?? null;
27790
+ } catch (e) {
27791
+ return failGraceful(`bootstrap apply: cannot read scoped seed file; no replacement made: ${e.message}`);
27792
+ }
27793
+ } else try {
27794
+ const r = await gh(["api", `repos/${repo}/contents/${enc(resolved.target)}?ref=${seedReadRef}`]);
27669
27795
  exists = true;
27670
27796
  try {
27671
27797
  const parsed = JSON.parse(r.stdout);
@@ -27678,23 +27804,34 @@ function registerBootstrapCommands(program3) {
27678
27804
  } catch {
27679
27805
  exists = false;
27680
27806
  }
27681
- const planned = planSeedAction(resolved, exists);
27807
+ const planned = planSeedAction(resolved, scopedPlan ? baseContent !== null : exists);
27808
+ if (scopedPlan && planned.action !== "skip") planned.action = exists ? "update" : "create";
27682
27809
  const isLegacyBlock = resolved.source === "managed-block";
27683
27810
  let content = null;
27684
27811
  let isManaged = isLegacyBlock || resolved.managedBlock != null;
27812
+ const preservedContent = scopedPlan ? baseContent : remoteContent;
27685
27813
  if (planned.action === "create" || planned.action === "update") {
27686
27814
  if (isLegacyBlock) {
27687
- content = upsertManagedGitignoreBlock(remoteContent).content;
27815
+ content = upsertManagedGitignoreBlock(preservedContent).content;
27688
27816
  } else {
27689
- const writeContent = resolveSeedWriteContent(resolved, vars, readFile10, remoteContent);
27817
+ const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, preservedContent);
27690
27818
  if (!writeContent.ok) {
27691
27819
  return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
27692
27820
  }
27693
27821
  content = writeContent.content;
27822
+ if (preserveRuleset) {
27823
+ if (content === null) return fail("bootstrap apply: canonical product ruleset seed is unreadable");
27824
+ try {
27825
+ content = reseedProductRulesetStrictness(baseContent, content);
27826
+ } catch (e) {
27827
+ return fail(`bootstrap apply: product ruleset preservation refused: ${e.message}`);
27828
+ }
27829
+ }
27694
27830
  isManaged = writeContent.managed;
27695
27831
  }
27696
27832
  }
27697
27833
  const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
27834
+ if (onlyTarget && content !== null) scopedContent = content;
27698
27835
  actions.push(action);
27699
27836
  const docBody = content ?? remoteContent;
27700
27837
  if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
@@ -27729,7 +27866,8 @@ function registerBootstrapCommands(program3) {
27729
27866
  }
27730
27867
  }
27731
27868
  let seedPrUrl;
27732
- if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && seededToBranch > 0) {
27869
+ const scopedHead = o.execute && scopedPlan?.branch ? await verifyTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient) : null;
27870
+ if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && (seededToBranch > 0 || scopedContent !== void 0 && scopedHead !== null && scopedHead !== scopedPlan?.baseSha)) {
27733
27871
  await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-F", `delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`]).catch(() => {
27734
27872
  });
27735
27873
  const openPrs = await gh(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
@@ -27758,7 +27896,12 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27758
27896
  seedPrUrl = created.url;
27759
27897
  }
27760
27898
  let autoMergeEnabled = true;
27761
- await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
27899
+ if (scopedPlan) {
27900
+ if (scopedContent === void 0) throw new Error("bootstrap apply: scoped seed content is unresolved");
27901
+ const number = Number(seedPrUrl.split("/").pop());
27902
+ if (!Number.isInteger(number) || number <= 0) throw new Error("bootstrap apply: scoped seed PR number is unreadable");
27903
+ await enableTargetedSeedAutoMerge(repo, number, scopedPlan, onlyTarget, scopedContent, controlClient, gh);
27904
+ } else await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
27762
27905
  const message2 = String(e.message ?? "");
27763
27906
  if (/already/i.test(message2)) return;
27764
27907
  if (/clean status|enablePullRequestAutoMerge/i.test(message2)) {
@@ -27823,7 +27966,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27823
27966
  }
27824
27967
  const rulesetSeed = repo.toLowerCase() === "mutmutco/mmi-hub" ? void 0 : manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
27825
27968
  if (rulesetSeed) {
27826
- const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile10);
27969
+ const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile9);
27827
27970
  if (rulesetContent) {
27828
27971
  try {
27829
27972
  const client = controlClient;
@@ -27941,7 +28084,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27941
28084
  deployFactsRead: controlDeployFacts !== null
27942
28085
  });
27943
28086
  }
27944
- if (o.json) console.log(JSON.stringify({ repo, class: o.class, sourceSha: seedSource.sha, only: onlyTarget || null, execute: o.execute, seedDelivery: seedPlan.mode, seedPrUrl, actions, controlPlane: controlRows, applied, ddbWrites }, null, 2));
28087
+ if (o.json) console.log(JSON.stringify({ repo, class: o.class, sourceSha: seedSource.sha, only: onlyTarget || null, execute: o.execute, seedDelivery: seedPlan.mode, seedBranch: seedPlan.branch, seedBaseSha: scopedPlan?.baseSha, seedPrUrl, actions, controlPlane: controlRows, applied, ddbWrites }, null, 2));
27945
28088
  else {
27946
28089
  console.log(renderSeedPlan(actions));
27947
28090
  if (controlRows.length) console.log(`
@@ -27968,7 +28111,7 @@ LIVE apply to ${repo}:
27968
28111
  ${propagatable.map((s) => s.target).join("\n ")}`);
27969
28112
  if (!seed.managedBlock && !(0, import_node_fs26.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
27970
28113
  const hubContent = seed.managedBlock ? null : (0, import_node_fs26.readFileSync)(seed.target, "utf8");
27971
- const readSeedFile = (path2) => (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null;
28114
+ const readSeedFile2 = (path2) => (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null;
27972
28115
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
27973
28116
  const cfg = await loadConfig();
27974
28117
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -28045,14 +28188,14 @@ LIVE apply to ${repo}:
28045
28188
  const project2 = projects.find((p) => (p.repos ?? []).some((repo) => repo.toLowerCase() === r.repo.toLowerCase()));
28046
28189
  const track = resolveReleaseTrack(project2, void 0, r.repo);
28047
28190
  const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches);
28048
- const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28191
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
28049
28192
  if (!resolved.ok || resolved.content == null) {
28050
28193
  return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.ok ? "rendered no content" : resolved.reason} \u2014 refusing an incomplete per-repo render`);
28051
28194
  }
28052
28195
  desired = resolved.content;
28053
28196
  } else if (seed.managedBlock) {
28054
28197
  const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
28055
- const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28198
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
28056
28199
  if (!resolved.ok) {
28057
28200
  return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
28058
28201
  }
@@ -31891,8 +32034,7 @@ var LOOP_PLAYBOOKS = {
31891
32034
  { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
31892
32035
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
31893
32036
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
31894
- { label: "Post the review verdict (pr merge / pr land refuse an unreviewed PR, #6255)", command: 'mmi-cli devops pr review-verdict <PR-number> --repo <owner/repo> --verdict PROCEED --scope "<one line>" --risk "<one line>" --reviewer <seat-or-model-id>' },
31895
- { label: "Land to development (waits for checks itself and demands the review verdict)", command: "mmi-cli devops pr land <PR-number>" },
32037
+ { label: "Land to development (waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" },
31896
32038
  { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
31897
32039
  // #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
31898
32040
  { label: "Learning reports are fire-and-forget (file, then return to the current task \u2014 never claim/poll/duplicate the learning issue)", command: 'mmi-cli learning report --title "<one-line>" --body "<what hurt>"' }
@@ -31913,14 +32055,14 @@ var LOOP_PLAYBOOKS = {
31913
32055
  steps: [
31914
32056
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
31915
32057
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
31916
- { label: "Land the PR (merge to development \u2014 waits for checks itself and demands the review verdict)", command: "mmi-cli devops pr land <PR-number>" }
32058
+ { label: "Land the PR (merge to development \u2014 waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" }
31917
32059
  ]
31918
32060
  },
31919
32061
  "hotfix": {
31920
32062
  title: "Hotfix",
31921
32063
  steps: [
31922
32064
  { label: "Create the main-base hotfix PR from every already-merged fix this cycle carries", command: "mmi-cli devops hotfix start --from <pr#|sha>[,<pr#|sha>...]" },
31923
- { label: "Merge the main-base PR (waits for checks itself and demands the review verdict)", command: "mmi-cli devops pr merge <PR-number> --squash" },
32065
+ { label: "Merge the main-base PR (waits for checks itself)", command: "mmi-cli devops pr merge <PR-number> --squash" },
31924
32066
  { label: "After the PR is merged, run the gated release", command: "mmi-cli devops hotfix release <vX.Y.Z> --carries <pr#|sha>[,<pr#|sha>...]" }
31925
32067
  ]
31926
32068
  }
@@ -32193,7 +32335,7 @@ function parseOriginRepo(remoteUrl) {
32193
32335
 
32194
32336
  // src/issue-commands.ts
32195
32337
  var import_node_fs31 = require("node:fs");
32196
- var import_node_crypto9 = require("node:crypto");
32338
+ var import_node_crypto10 = require("node:crypto");
32197
32339
 
32198
32340
  // src/issue-body.ts
32199
32341
  var import_node_os16 = require("node:os");
@@ -32723,7 +32865,7 @@ function rowIdempotencyKey(batchKey, spec) {
32723
32865
  const identity = `${spec.type}
32724
32866
  ${spec.title.trim()}
32725
32867
  ${spec.body ?? ""}`;
32726
- const hash = (0, import_node_crypto9.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
32868
+ const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
32727
32869
  return `${batchKey}:${hash}`;
32728
32870
  }
32729
32871
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -35768,14 +35910,14 @@ function registerStageCommands(program3) {
35768
35910
  }
35769
35911
 
35770
35912
  // src/tenant-artifact.ts
35771
- var import_node_crypto10 = require("node:crypto");
35913
+ var import_node_crypto11 = require("node:crypto");
35772
35914
  var import_node_fs35 = require("node:fs");
35773
35915
  var import_promises6 = require("node:fs/promises");
35774
35916
  var import_node_path32 = require("node:path");
35775
35917
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
35776
35918
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
35777
35919
  async function sha256File(path2) {
35778
- const hash = (0, import_node_crypto10.createHash)("sha256");
35920
+ const hash = (0, import_node_crypto11.createHash)("sha256");
35779
35921
  for await (const chunk of (0, import_node_fs35.createReadStream)(path2)) hash.update(chunk);
35780
35922
  return hash.digest("hex");
35781
35923
  }
@@ -35940,7 +36082,7 @@ function renderVerifySecrets(body) {
35940
36082
  // src/command-register-collaboration.ts
35941
36083
  var import_node_child_process16 = require("node:child_process");
35942
36084
  var import_node_fs43 = require("node:fs");
35943
- var import_promises8 = require("node:fs/promises");
36085
+ var import_promises7 = require("node:fs/promises");
35944
36086
 
35945
36087
  // src/session-runtime.ts
35946
36088
  var WIN32_TRAMPOLINE = "const{spawn}=require('node:child_process');const a=JSON.parse(process.argv[1]);const c=spawn(a.cmd,a.args,{stdio:'ignore',windowsHide:true,cwd:a.cwd});c.on('exit',x=>process.exit(x??0));c.on('error',()=>process.exit(1));";
@@ -35959,7 +36101,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
35959
36101
  }
35960
36102
 
35961
36103
  // src/command-register-collaboration.ts
35962
- var import_node_path41 = require("node:path");
36104
+ var import_node_path40 = require("node:path");
35963
36105
 
35964
36106
  // src/attach-to-project.ts
35965
36107
  function boardAttachRateLimitedReceipt(resetEpochSeconds) {
@@ -36145,6 +36287,14 @@ async function runPrLand(prNumber, options, deps) {
36145
36287
  };
36146
36288
  }
36147
36289
  }
36290
+ const queue = await deps.queueMerge?.(prNumber, repo);
36291
+ if (queue) return {
36292
+ ...base,
36293
+ queue,
36294
+ status: queue.state === "merged" ? "merged" : "failed",
36295
+ mergeStatus: queue.state === "merged" ? "merged" : "failed",
36296
+ ...queue.state === "merged" ? {} : { error: queue.detail }
36297
+ };
36148
36298
  const ciPolicy = await deps.resolveCiPolicy(repo);
36149
36299
  base.ciPolicy = ciPolicy;
36150
36300
  const checksWaitError = (checksWait) => {
@@ -36762,8 +36912,8 @@ function annotateChangeMeaning(changed, policy, read) {
36762
36912
  function isMeaningfulRow(file) {
36763
36913
  return file.meaningful !== false;
36764
36914
  }
36765
- function loadPolicy(root, readFile10 = readFileOrNull2) {
36766
- const raw = readFile10((0, import_node_path33.join)(root, POLICY_FILE));
36915
+ function loadPolicy(root, readFile9 = readFileOrNull2) {
36916
+ const raw = readFile9((0, import_node_path33.join)(root, POLICY_FILE));
36767
36917
  if (raw == null) return { mandatory: [], declared: false };
36768
36918
  return parsePolicy(raw, POLICY_FILE);
36769
36919
  }
@@ -37454,9 +37604,287 @@ async function deleteMergedRemoteBranch(options) {
37454
37604
  };
37455
37605
  }
37456
37606
 
37457
- // src/post-merge-recon.ts
37607
+ // src/review-verdict.ts
37608
+ var import_node_child_process14 = require("node:child_process");
37458
37609
  var import_node_fs38 = require("node:fs");
37610
+ var import_node_os19 = require("node:os");
37459
37611
  var import_node_path35 = require("node:path");
37612
+ var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
37613
+ var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
37614
+ function isReviewVerdict(value) {
37615
+ return typeof value === "string" && REVIEW_VERDICTS.includes(value);
37616
+ }
37617
+ function renderReviewVerdictComment(input) {
37618
+ const payload = {
37619
+ v: 1,
37620
+ patch: input.patch,
37621
+ head: input.head,
37622
+ verdict: input.verdict,
37623
+ scope: input.scope,
37624
+ risk: input.risk,
37625
+ unverified: input.unverified,
37626
+ reviewer: input.reviewer
37627
+ };
37628
+ const findings = input.findings?.trim();
37629
+ return `${REVIEW_VERDICT_MARKER}
37630
+ \`\`\`json
37631
+ ${JSON.stringify(payload, null, 2)}
37632
+ \`\`\`
37633
+ ${findings ? `
37634
+ ${findings}
37635
+ ` : ""}`;
37636
+ }
37637
+ function isReviewVerdictComment(body) {
37638
+ return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
37639
+ }
37640
+ var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
37641
+ function parseReviewVerdictComment(body) {
37642
+ if (!isReviewVerdictComment(body)) return void 0;
37643
+ const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
37644
+ const fence = FENCE_RE.exec(rest);
37645
+ if (!fence) return void 0;
37646
+ let parsed;
37647
+ try {
37648
+ parsed = JSON.parse(fence[1]);
37649
+ } catch {
37650
+ return void 0;
37651
+ }
37652
+ if (!parsed || typeof parsed !== "object") return void 0;
37653
+ const p = parsed;
37654
+ if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
37655
+ if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
37656
+ if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
37657
+ const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
37658
+ return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
37659
+ }
37660
+ function latestReviewComment(comments) {
37661
+ let latest;
37662
+ for (const c of comments) {
37663
+ if (!isReviewVerdictComment(c.body)) continue;
37664
+ if (!latest || c.createdAt > latest.createdAt || c.createdAt === latest.createdAt && (c.id ?? 0) >= (latest.id ?? 0)) latest = c;
37665
+ }
37666
+ return latest;
37667
+ }
37668
+ function evaluateReviewVerdict(comments, currentPatchId) {
37669
+ const latest = latestReviewComment(comments);
37670
+ if (!latest) return { ok: false, reason: "none" };
37671
+ const payload = parseReviewVerdictComment(latest.body);
37672
+ if (!payload) return { ok: false, reason: "malformed" };
37673
+ if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
37674
+ if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
37675
+ return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
37676
+ }
37677
+ function evaluateTrustedReviewVerdict(comments, patch, head) {
37678
+ const latest = latestReviewComment(comments);
37679
+ if (!latest) return { ok: false, reason: "none" };
37680
+ const identity = { commentId: latest.id, commentCreatedAt: latest.createdAt };
37681
+ if (latest.author?.toLowerCase() !== "jervaise") return { ...identity, ok: false, reason: "untrusted-author" };
37682
+ const evaluation = evaluateReviewVerdict([latest], patch);
37683
+ if (!evaluation.ok) return { ...identity, ok: false, reason: evaluation.reason };
37684
+ if (parseReviewVerdictComment(latest.body)?.head !== head) return { ...identity, ok: false, reason: "stale-head" };
37685
+ return { ...identity, ok: true, reason: "proceed" };
37686
+ }
37687
+ async function checkPrReview(number, repo, head, deps = {
37688
+ readHead: readPrHeadSha,
37689
+ readComments: readPrIssueComments,
37690
+ computePatch: computePrPatchId
37691
+ }) {
37692
+ const base = { repo, number, head };
37693
+ if (!/^[1-9][0-9]*$/.test(number) || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[0-9a-f]{40}$/.test(head)) {
37694
+ return { ...base, ok: false, reason: "invalid-input", detail: "expected a PR number, owner/repo and exact lowercase 40-character head SHA" };
37695
+ }
37696
+ try {
37697
+ if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
37698
+ const [comments, patch] = await Promise.all([deps.readComments(number, repo), deps.computePatch(number, repo)]);
37699
+ if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
37700
+ return { ...base, patch, ...evaluateTrustedReviewVerdict(comments, patch, head) };
37701
+ } catch (e) {
37702
+ return { ...base, ok: false, reason: "unreadable", detail: e.message };
37703
+ }
37704
+ }
37705
+ function computePrPatchId(number, repo) {
37706
+ return new Promise((resolve7, reject) => {
37707
+ const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
37708
+ const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
37709
+ let out = "";
37710
+ let ghErr = "";
37711
+ let gitErr = "";
37712
+ const timer = setTimeout(() => {
37713
+ gh.kill();
37714
+ git3.kill();
37715
+ reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
37716
+ }, GC_GH_TIMEOUT_MS4);
37717
+ gh.stdout.pipe(git3.stdin);
37718
+ gh.stderr.on("data", (d) => {
37719
+ ghErr += d.toString();
37720
+ });
37721
+ git3.stderr.on("data", (d) => {
37722
+ gitErr += d.toString();
37723
+ });
37724
+ git3.stdout.on("data", (d) => {
37725
+ out += d.toString();
37726
+ });
37727
+ gh.on("error", (e) => {
37728
+ clearTimeout(timer);
37729
+ reject(e);
37730
+ });
37731
+ git3.on("error", (e) => {
37732
+ clearTimeout(timer);
37733
+ reject(e);
37734
+ });
37735
+ let ghCode;
37736
+ let gitCode;
37737
+ const finish = () => {
37738
+ if (ghCode === void 0 || gitCode === void 0) return;
37739
+ clearTimeout(timer);
37740
+ if (ghCode !== 0) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
37741
+ if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
37742
+ const id = out.trim().split(/\s+/)[0];
37743
+ 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"})`));
37744
+ resolve7(id);
37745
+ };
37746
+ gh.on("close", (code) => {
37747
+ ghCode = code;
37748
+ finish();
37749
+ });
37750
+ git3.on("close", (code) => {
37751
+ gitCode = code;
37752
+ finish();
37753
+ });
37754
+ git3.stdin.on("error", (e) => {
37755
+ clearTimeout(timer);
37756
+ gh.kill();
37757
+ git3.kill();
37758
+ reject(e);
37759
+ });
37760
+ });
37761
+ }
37762
+ async function readPrHeadSha(number, repo) {
37763
+ const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
37764
+ const sha = stdout.trim();
37765
+ if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
37766
+ return sha;
37767
+ }
37768
+ async function readPrIssueComments(number, repo) {
37769
+ const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {id, body, createdAt: .created_at, author: .user.login}"], { timeout: GC_GH_TIMEOUT_MS4 });
37770
+ const comments = parseNdjsonLines(stdout);
37771
+ if (comments.some((c) => !c || !Number.isSafeInteger(c.id) || c.id <= 0 || typeof c.body !== "string" || typeof c.createdAt !== "string" || !Number.isFinite(Date.parse(c.createdAt)) || c.author !== null && typeof c.author !== "string")) throw new Error("unreadable PR comment identity");
37772
+ return comments;
37773
+ }
37774
+ async function postPrCommentFromFile(number, repo, body) {
37775
+ const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
37776
+ const path2 = (0, import_node_path35.join)(dir, "body.md");
37777
+ try {
37778
+ (0, import_node_fs38.writeFileSync)(path2, body, "utf8");
37779
+ const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
37780
+ return stdout.trim();
37781
+ } finally {
37782
+ try {
37783
+ (0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
37784
+ } catch {
37785
+ }
37786
+ }
37787
+ }
37788
+
37789
+ // src/pr-mergify-queue.ts
37790
+ var MERGIFY_PILOT_REPO = "mutmutco/Jerv-JervCode";
37791
+ var MERGIFY_PILOT_VARIABLE = "JERV_BATCH_QUEUE_PILOT";
37792
+ var MERGIFY_READY_LABEL = "ready-to-merge";
37793
+ async function ghJson(args) {
37794
+ return JSON.parse((await execFileP("gh", ["api", ...args], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
37795
+ }
37796
+ async function usesMergifyPilot(repo, base, readVariable = async () => ghJson([`repos/${repo}/actions/variables/${MERGIFY_PILOT_VARIABLE}`])) {
37797
+ if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase() || base !== "development") return false;
37798
+ const variable = await readVariable();
37799
+ if (variable?.value === "true") return true;
37800
+ if (variable?.value === "false") return false;
37801
+ throw new Error(`Mergify pilot activation is missing or malformed on ${repo}; refusing native merge`);
37802
+ }
37803
+ function queueCheckState(checks, head) {
37804
+ const check = checks.filter((c) => c.name === "Mergify Merge Queue" && c.head_sha === head && c.app?.id === 10562 && c.app.slug === "mergify").sort((a, b) => b.id - a.id)[0];
37805
+ if (!check) return "refused";
37806
+ if (check.conclusion && !["success", "neutral"].includes(check.conclusion)) return "refused";
37807
+ if (["In merge queue", "Running merge queue checks"].includes(check.output?.title ?? "")) return "pending";
37808
+ return "requested";
37809
+ }
37810
+ async function requestMergifyMerge(number, repo, guardedText, expectedHead, deps = {
37811
+ readPull: () => ghJson([`repos/${repo}/pulls/${number}`]),
37812
+ readChecks: async (head) => parseNdjsonLines((await execFileP("gh", [
37813
+ "api",
37814
+ "--paginate",
37815
+ `repos/${repo}/commits/${head}/check-runs?per_page=100`,
37816
+ "--jq",
37817
+ ".check_runs[]"
37818
+ ], { timeout: GC_GH_TIMEOUT_MS4 })).stdout),
37819
+ addLabel: async () => {
37820
+ await ghJson([`repos/${repo}/issues/${number}/labels`, "--method", "POST", "-f", `labels[]=${MERGIFY_READY_LABEL}`]);
37821
+ },
37822
+ review: (head) => checkPrReview(number, repo, head),
37823
+ now: () => Date.now(),
37824
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
37825
+ }, timeoutMs = 6e5) {
37826
+ let head = expectedHead ?? "";
37827
+ let state = "refused";
37828
+ const receipt = (detail) => ({ provider: "mergify", state, head, detail });
37829
+ try {
37830
+ if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase()) return receipt("repository is outside the Mergify pilot");
37831
+ const initial = await deps.readPull();
37832
+ head = expectedHead ?? initial.head.sha;
37833
+ const sameTarget = (pr) => /^[0-9a-f]{40}$/.test(head) && pr.head.sha === head && pr.base.ref === "development";
37834
+ if (!sameTarget(initial)) return receipt("PR head or target base changed");
37835
+ if (initial.merged === true) {
37836
+ state = "merged";
37837
+ return receipt("GitHub confirms the PR merged");
37838
+ }
37839
+ const validate = (pr) => pr.state === "open" && sameTarget(pr) && `${pr.title}
37840
+ ${pr.body ?? ""}` === guardedText;
37841
+ if (!validate(initial)) return receipt("PR state, head, base or guarded title/body changed");
37842
+ const initialState = queueCheckState(await deps.readChecks(head), head);
37843
+ if (initialState === "refused") return receipt("trusted current-head Mergify check is absent or failed");
37844
+ if (!(await deps.review(head)).ok) return receipt("current trusted review verdict does not permit queue admission");
37845
+ if (!validate(await deps.readPull())) return receipt("PR changed before queue request");
37846
+ let mutationDetail = "";
37847
+ if (!initial.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
37848
+ try {
37849
+ await deps.addLabel();
37850
+ } catch {
37851
+ mutationDetail = "Label response was ambiguous; no replay was attempted. ";
37852
+ }
37853
+ }
37854
+ state = "requested";
37855
+ const deadline = deps.now() + timeoutMs;
37856
+ while (true) {
37857
+ const pr = await deps.readPull();
37858
+ if (!sameTarget(pr)) {
37859
+ state = "refused";
37860
+ return receipt("PR head or target base changed while queued");
37861
+ }
37862
+ if (pr.merged === true) {
37863
+ state = "merged";
37864
+ return receipt("GitHub confirms the PR merged");
37865
+ }
37866
+ if (!validate(pr)) {
37867
+ state = "refused";
37868
+ return receipt("PR state, head, base or guarded title/body changed while queued");
37869
+ }
37870
+ state = queueCheckState(await deps.readChecks(head), head);
37871
+ if (state === "refused") return receipt("trusted current-head Mergify check is absent or failed");
37872
+ if (!pr.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
37873
+ state = "refused";
37874
+ return receipt(`${mutationDetail}Queue request label is absent`);
37875
+ }
37876
+ if (deps.now() >= deadline) return receipt(`${mutationDetail}${state === "pending" ? "Mergify confirms queue admission" : "Queue requested; Mergify has not confirmed admission"}; PR has not merged within the wait window`);
37877
+ await deps.sleep(3e4);
37878
+ }
37879
+ } catch {
37880
+ state = "refused";
37881
+ return receipt("Mergify or GitHub state is unreadable; no native merge was attempted");
37882
+ }
37883
+ }
37884
+
37885
+ // src/post-merge-recon.ts
37886
+ var import_node_fs39 = require("node:fs");
37887
+ var import_node_path36 = require("node:path");
37460
37888
 
37461
37889
  // src/cross-repo-filing-issue.ts
37462
37890
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
@@ -37622,16 +38050,16 @@ function buildPostMergeReconRecovery(input) {
37622
38050
  }
37623
38051
  function writePostMergeReconRecovery(cwd, recovery) {
37624
38052
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
37625
- (0, import_node_fs38.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
37626
- (0, import_node_fs38.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
38053
+ (0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
38054
+ (0, import_node_fs39.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
37627
38055
  `, "utf8");
37628
38056
  return path2;
37629
38057
  }
37630
38058
  function clearPostMergeReconRecovery(cwd, repo, pr) {
37631
38059
  const path2 = postMergeReconStatePath(cwd, repo, pr);
37632
- if (!(0, import_node_fs38.existsSync)(path2)) return;
38060
+ if (!(0, import_node_fs39.existsSync)(path2)) return;
37633
38061
  try {
37634
- (0, import_node_fs38.unlinkSync)(path2);
38062
+ (0, import_node_fs39.unlinkSync)(path2);
37635
38063
  } catch {
37636
38064
  }
37637
38065
  }
@@ -37661,184 +38089,6 @@ function postMergeReconWarnings(input) {
37661
38089
  return lines2;
37662
38090
  }
37663
38091
 
37664
- // src/review-verdict.ts
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
38092
  // src/pr-create-docs-check.ts
37843
38093
  var import_node_child_process15 = require("node:child_process");
37844
38094
  var GIT_TIMEOUT_MS2 = 15e3;
@@ -37960,120 +38210,6 @@ async function checkDocsIndexAtHead(opts, deps) {
37960
38210
  };
37961
38211
  }
37962
38212
 
37963
- // src/pr-create-zeroci.ts
37964
- var import_promises7 = require("node:fs/promises");
37965
- var import_node_path38 = require("node:path");
37966
-
37967
- // src/jervcode-node-modules-cleanup.ts
37968
- var import_node_fs40 = require("node:fs");
37969
- var import_node_os20 = require("node:os");
37970
- var import_node_path37 = require("node:path");
37971
- var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
37972
- var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
37973
- var POSIX_NAMES2 = ["jervcode"];
37974
- var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
37975
- function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
37976
- const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
37977
- const out = [];
37978
- for (const dir of jervCliCandidateDirs(env, home, platform2)) {
37979
- for (const name of names) out.push((0, import_node_path37.join)(dir, name));
37980
- }
37981
- return out;
37982
- }
37983
- function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
37984
- for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
37985
- if (exists(candidate)) return candidate;
37986
- }
37987
- return void 0;
37988
- }
37989
- function jervcodeExecFileArgs(args, opts = {}) {
37990
- const platform2 = opts.platform ?? process.platform;
37991
- const exists = opts.exists ?? import_node_fs40.existsSync;
37992
- const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
37993
- if (resolved) {
37994
- const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
37995
- if (exists(entry)) {
37996
- return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
37997
- }
37998
- }
37999
- const bin = resolved ?? "jervcode";
38000
- if (platform2 === "win32") {
38001
- return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
38002
- }
38003
- return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
38004
- }
38005
- async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
38006
- const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
38007
- const env = opts.env ?? process.env;
38008
- const exec = opts.exec ?? execFileP;
38009
- const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
38010
- const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
38011
- const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
38012
- try {
38013
- await exec(plan.file, plan.args, execOptions);
38014
- return { ok: true };
38015
- } catch (e) {
38016
- const err = e;
38017
- const base = formatJervCliSpawnFailure(err, plan, candidates);
38018
- const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
38019
- const detail = stderr.split("\n")[0] || base;
38020
- return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
38021
- }
38022
- }
38023
-
38024
- // src/pr-create-zeroci.ts
38025
- var GATE_WORKFLOW_PATH = ".github/workflows/gate.yml";
38026
- var ZEROCI_MINT_MARKER = "zeroci-mint: seat";
38027
- var ZEROCI_MINT_TIMEOUT_MS = 18e5;
38028
- var ZEROCI_MINT_MAX_BUFFER = 32 * 1024 * 1024;
38029
- function createPrCreateZeroCiDeps() {
38030
- const git3 = createPrCreateDocsIndexDeps();
38031
- return {
38032
- worktreeRoot: git3.worktreeRoot,
38033
- originRepo: git3.originRepo,
38034
- readGateWorkflow: async (root) => {
38035
- try {
38036
- return await (0, import_promises7.readFile)((0, import_node_path38.join)(root, GATE_WORKFLOW_PATH), "utf8");
38037
- } catch {
38038
- return void 0;
38039
- }
38040
- },
38041
- mint: async (root) => {
38042
- const plan = jervcodeExecFileArgs(["zeroci-receipt", "--from-gate"]);
38043
- try {
38044
- await execFileHard(plan.file, plan.args, {
38045
- cwd: root,
38046
- timeout: ZEROCI_MINT_TIMEOUT_MS,
38047
- maxBuffer: ZEROCI_MINT_MAX_BUFFER,
38048
- step: "jervcode zeroci-receipt --from-gate"
38049
- });
38050
- } catch (e) {
38051
- const err = e;
38052
- const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
38053
- throw new Error(stderr.split("\n").filter(Boolean).pop() || formatJervCliSpawnFailure(err, plan, jervcodeCandidatePaths()));
38054
- }
38055
- }
38056
- };
38057
- }
38058
- async function mintZeroCiReceipt(opts, deps) {
38059
- if (opts.zeroci === false) return void 0;
38060
- const root = await deps.worktreeRoot();
38061
- if (!root) return void 0;
38062
- if (opts.repo) {
38063
- const origin = await deps.originRepo(root);
38064
- if (!origin || origin.toLowerCase() !== opts.repo.toLowerCase()) return void 0;
38065
- }
38066
- const gate = await deps.readGateWorkflow(root);
38067
- if (!gate || !gate.includes("zeroci-verify")) return void 0;
38068
- if (!gate.includes(ZEROCI_MINT_MARKER)) return void 0;
38069
- try {
38070
- await deps.mint(root);
38071
- return void 0;
38072
- } catch (e) {
38073
- return `pr create: WARNING \u2014 ZeroCI receipt not minted, opening the PR anyway (the gate runs in full): ${e.message}`;
38074
- }
38075
- }
38076
-
38077
38213
  // src/pr-create-claim-guard.ts
38078
38214
  var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
38079
38215
  function withRateLimitRetry(client, seams = {}) {
@@ -38132,11 +38268,68 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
38132
38268
 
38133
38269
  // src/worktree-merge-cleanup.ts
38134
38270
  var import_node_fs42 = require("node:fs");
38135
- var import_node_path40 = require("node:path");
38271
+ var import_node_path39 = require("node:path");
38272
+
38273
+ // src/jervcode-node-modules-cleanup.ts
38274
+ var import_node_fs40 = require("node:fs");
38275
+ var import_node_os20 = require("node:os");
38276
+ var import_node_path37 = require("node:path");
38277
+ var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
38278
+ var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
38279
+ var POSIX_NAMES2 = ["jervcode"];
38280
+ var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
38281
+ function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
38282
+ const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
38283
+ const out = [];
38284
+ for (const dir of jervCliCandidateDirs(env, home, platform2)) {
38285
+ for (const name of names) out.push((0, import_node_path37.join)(dir, name));
38286
+ }
38287
+ return out;
38288
+ }
38289
+ function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
38290
+ for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
38291
+ if (exists(candidate)) return candidate;
38292
+ }
38293
+ return void 0;
38294
+ }
38295
+ function jervcodeExecFileArgs(args, opts = {}) {
38296
+ const platform2 = opts.platform ?? process.platform;
38297
+ const exists = opts.exists ?? import_node_fs40.existsSync;
38298
+ const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
38299
+ if (resolved) {
38300
+ const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
38301
+ if (exists(entry)) {
38302
+ return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
38303
+ }
38304
+ }
38305
+ const bin = resolved ?? "jervcode";
38306
+ if (platform2 === "win32") {
38307
+ return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
38308
+ }
38309
+ return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
38310
+ }
38311
+ async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
38312
+ const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
38313
+ const env = opts.env ?? process.env;
38314
+ const exec = opts.exec ?? execFileP;
38315
+ const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
38316
+ const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
38317
+ const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
38318
+ try {
38319
+ await exec(plan.file, plan.args, execOptions);
38320
+ return { ok: true };
38321
+ } catch (e) {
38322
+ const err = e;
38323
+ const base = formatJervCliSpawnFailure(err, plan, candidates);
38324
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
38325
+ const detail = stderr.split("\n")[0] || base;
38326
+ return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
38327
+ }
38328
+ }
38136
38329
 
38137
38330
  // src/worktree-evidence-archive.ts
38138
38331
  var import_node_fs41 = require("node:fs");
38139
- var import_node_path39 = require("node:path");
38332
+ var import_node_path38 = require("node:path");
38140
38333
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
38141
38334
  function sanitizeArchiveSegment(value, max = 80) {
38142
38335
  const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -38168,7 +38361,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
38168
38361
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
38169
38362
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
38170
38363
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
38171
- const source = (0, import_node_path39.join)(args.worktreePath, ".jerv");
38364
+ const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
38172
38365
  if (!exists(source)) return { status: "absent" };
38173
38366
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
38174
38367
  const runScope = resolveArtifactRunScope(env);
@@ -38176,7 +38369,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
38176
38369
  const stamp = now().toISOString().replace(/[:.]/g, "-");
38177
38370
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
38178
38371
  try {
38179
- mkdirp((0, import_node_path39.dirname)(dest));
38372
+ mkdirp((0, import_node_path38.dirname)(dest));
38180
38373
  copyDir(source, dest);
38181
38374
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
38182
38375
  return { status: "archived", path: dest, runScope };
@@ -38192,7 +38385,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
38192
38385
  const exists = deps.exists ?? import_node_fs41.existsSync;
38193
38386
  const stat4 = deps.stat ?? defaultStat;
38194
38387
  const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
38195
- const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
38388
+ const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
38196
38389
  if (!exists(tmpRoot)) return [];
38197
38390
  const entries = [];
38198
38391
  const walk2 = (dir) => {
@@ -38203,14 +38396,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
38203
38396
  return;
38204
38397
  }
38205
38398
  for (const name of names) {
38206
- const full = (0, import_node_path39.join)(dir, name);
38399
+ const full = (0, import_node_path38.join)(dir, name);
38207
38400
  let st;
38208
38401
  try {
38209
38402
  st = stat4(full);
38210
38403
  } catch {
38211
38404
  continue;
38212
38405
  }
38213
- const relPath = (0, import_node_path39.relative)(worktreePath, full).replace(/\\/g, "/");
38406
+ const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
38214
38407
  if (st.isDirectory()) {
38215
38408
  if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
38216
38409
  walk2(full);
@@ -38235,7 +38428,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
38235
38428
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
38236
38429
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
38237
38430
  const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
38238
- const source = (0, import_node_path39.join)(args.worktreePath, "tmp");
38431
+ const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
38239
38432
  if (!scanned.length) return { status: "absent" };
38240
38433
  if (!exists(source)) return { status: "absent" };
38241
38434
  const runScope = resolveArtifactRunScope(env);
@@ -38243,7 +38436,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
38243
38436
  const stamp = now().toISOString().replace(/[:.]/g, "-");
38244
38437
  const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
38245
38438
  try {
38246
- mkdirp((0, import_node_path39.dirname)(dest));
38439
+ mkdirp((0, import_node_path38.dirname)(dest));
38247
38440
  copyDir(source, dest);
38248
38441
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
38249
38442
  const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
@@ -38312,7 +38505,7 @@ function normPath2(p) {
38312
38505
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
38313
38506
  }
38314
38507
  function unlinkNodeModulesJunction(wtPath) {
38315
- const nm = (0, import_node_path40.join)(wtPath, "node_modules");
38508
+ const nm = (0, import_node_path39.join)(wtPath, "node_modules");
38316
38509
  try {
38317
38510
  if ((0, import_node_fs42.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs42.rmdirSync)(nm);
38318
38511
  return { ok: true };
@@ -38449,7 +38642,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
38449
38642
  }
38450
38643
  async function listNestedIgnoredNodeModules(wtPath, execGit) {
38451
38644
  const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
38452
- return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path40.join)(wtPath, line.slice(0, -1)));
38645
+ 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)));
38453
38646
  }
38454
38647
  function safeRemoveTree(path2) {
38455
38648
  const stat4 = (0, import_node_fs42.lstatSync)(path2);
@@ -38462,7 +38655,7 @@ function safeRemoveTree(path2) {
38462
38655
  return;
38463
38656
  }
38464
38657
  if (stat4.isDirectory()) {
38465
- for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
38658
+ for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
38466
38659
  (0, import_node_fs42.rmdirSync)(path2);
38467
38660
  return;
38468
38661
  }
@@ -38505,7 +38698,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
38505
38698
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
38506
38699
  }
38507
38700
  for (const entry of entries) {
38508
- const child2 = (0, import_node_path40.join)(dir, entry.name);
38701
+ const child2 = (0, import_node_path39.join)(dir, entry.name);
38509
38702
  if (entry.isSymbolicLink()) {
38510
38703
  let target = "";
38511
38704
  try {
@@ -38539,7 +38732,7 @@ async function describePreCleanFailure(wtPath, execGit, error) {
38539
38732
  const more = remaining.length > 10 ? ` (+${remaining.length - 10} more)` : "";
38540
38733
  const quote = (path2) => path2.replace(/'/g, "''");
38541
38734
  const nested = remaining.find((path2) => path2.endsWith("node_modules/") && path2 !== "node_modules/");
38542
- const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0, import_node_path40.join)(wtPath, nested.slice(0, -1)))}' -Recurse -Force` : void 0;
38735
+ 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;
38543
38736
  return { ok: false, error: `${error}; remaining ignored paths: ${shown}${more}`, ...remediation ? { remediation } : {} };
38544
38737
  }
38545
38738
  function formatWorktreeRemovalFailureDetail(options) {
@@ -38833,7 +39026,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
38833
39026
  const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
38834
39027
  if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
38835
39028
  unlinkedReparsePoints.push(...preHelperGuard.unlinked);
38836
- if (pathExists((0, import_node_path40.join)(wtPath, "node_modules"))) {
39029
+ if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
38837
39030
  const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
38838
39031
  if (!nmRemoved.ok) {
38839
39032
  report.worktree = {
@@ -39000,10 +39193,10 @@ function argvWantsJson2() {
39000
39193
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
39001
39194
  }
39002
39195
  function hubRoot() {
39003
- const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
39196
+ const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
39004
39197
  const marker = "skills/bootstrap/seeds/manifest.json";
39005
- if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(fromPkg, marker))) return fromPkg;
39006
- if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(process.cwd(), marker))) return process.cwd();
39198
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
39199
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
39007
39200
  return null;
39008
39201
  }
39009
39202
  function ciAuditDeps() {
@@ -39015,12 +39208,12 @@ function ciAuditDeps() {
39015
39208
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
39016
39209
  readSeedFile: (path2) => {
39017
39210
  if (!root) return null;
39018
- const fullPath = (0, import_node_path41.join)(root, path2);
39211
+ const fullPath = (0, import_node_path40.join)(root, path2);
39019
39212
  return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
39020
39213
  }
39021
39214
  };
39022
39215
  }
39023
- async function ghJson(args, timeout = 1e4) {
39216
+ async function ghJson2(args, timeout = 1e4) {
39024
39217
  const { stdout } = await execFileP("gh", args, { timeout });
39025
39218
  return JSON.parse(stdout);
39026
39219
  }
@@ -39166,7 +39359,7 @@ function registerCollaborationCommands(program3) {
39166
39359
  try {
39167
39360
  title = await resolveIssueTitle(
39168
39361
  { title: opts.title, titleFile: opts.titleFile },
39169
- { readFile: import_promises8.readFile, readStdin }
39362
+ { readFile: import_promises7.readFile, readStdin }
39170
39363
  );
39171
39364
  } catch (e) {
39172
39365
  return fail(
@@ -39208,8 +39401,8 @@ function registerCollaborationCommands(program3) {
39208
39401
  let surfaceFlagLabel;
39209
39402
  try {
39210
39403
  issueType = resolveCreateType(o.type, "issue create", o.label);
39211
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
39212
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39404
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39405
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39213
39406
  if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
39214
39407
  priority = resolveCreatePriority(o.priority, "issue create");
39215
39408
  extraLabels = [...o.label ?? []];
@@ -39307,7 +39500,7 @@ function registerCollaborationCommands(program3) {
39307
39500
  async function readParentField(number, repo) {
39308
39501
  let payload;
39309
39502
  try {
39310
- payload = await ghJson(["api", `repos/${repo}/issues/${number}`]);
39503
+ payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
39311
39504
  } catch (e) {
39312
39505
  const err = e;
39313
39506
  return { parentReadError: (err.stderr || err.message || String(e)).trim() };
@@ -39339,7 +39532,7 @@ function registerCollaborationCommands(program3) {
39339
39532
  emit(data2, await readParentField(n, repo));
39340
39533
  return;
39341
39534
  }
39342
- const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
39535
+ const data = await ghJson2(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
39343
39536
  emit(data, await readParentField(n, repo));
39344
39537
  } catch (e) {
39345
39538
  const err = e;
@@ -39354,7 +39547,7 @@ function registerCollaborationCommands(program3) {
39354
39547
  const repo = await resolveRepo(o.repo);
39355
39548
  if (!repo) return fail("issue discover-related: could not resolve repo");
39356
39549
  try {
39357
- const issues = await ghJson([
39550
+ const issues = await ghJson2([
39358
39551
  "issue",
39359
39552
  "list",
39360
39553
  "--repo",
@@ -39369,7 +39562,7 @@ function registerCollaborationCommands(program3) {
39369
39562
  const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
39370
39563
  if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
39371
39564
  if (!candidates.length) return;
39372
- const viewed = await ghJson([
39565
+ const viewed = await ghJson2([
39373
39566
  "issue",
39374
39567
  "view",
39375
39568
  String(number),
@@ -39417,7 +39610,7 @@ function registerCollaborationCommands(program3) {
39417
39610
  }
39418
39611
  let body;
39419
39612
  try {
39420
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39613
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39421
39614
  } catch (e) {
39422
39615
  return fail(`issue comment: ${e.message}`);
39423
39616
  }
@@ -39443,7 +39636,7 @@ function registerCollaborationCommands(program3) {
39443
39636
  const checked = o.off !== true;
39444
39637
  let body;
39445
39638
  try {
39446
- const viewed = await ghJson(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
39639
+ const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
39447
39640
  body = viewed.body ?? "";
39448
39641
  } catch (e) {
39449
39642
  return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
@@ -39477,8 +39670,8 @@ ${list}`);
39477
39670
  let title;
39478
39671
  const sourceRepo = o.repo ?? await resolveRepo(void 0);
39479
39672
  try {
39480
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
39481
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39673
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39674
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39482
39675
  priority = resolveCreatePriority(o.priority, "report");
39483
39676
  if (!ISSUE_TYPES.includes(o.type)) {
39484
39677
  throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
@@ -39533,8 +39726,8 @@ ${list}`);
39533
39726
  let args;
39534
39727
  try {
39535
39728
  skill = assertSkillName(o.skill);
39536
- rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39537
- const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
39729
+ rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39730
+ const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39538
39731
  title = buildSkillLessonTitle(skill, rawTitle);
39539
39732
  priority = resolveCreatePriority(o.priority, "skill-lesson");
39540
39733
  body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
@@ -39545,7 +39738,7 @@ ${list}`);
39545
39738
  if (!o.force) {
39546
39739
  let openLessons = [];
39547
39740
  try {
39548
- openLessons = await ghJson([
39741
+ openLessons = await ghJson2([
39549
39742
  "issue",
39550
39743
  "list",
39551
39744
  "--repo",
@@ -39591,12 +39784,12 @@ ${list}`);
39591
39784
  console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
39592
39785
  });
39593
39786
  const pr = program3.command("pr").description("pull requests \u2014 reliable create with structured output");
39594
- withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--no-zeroci", "skip minting the ZeroCI receipt. By default pr create mints it first, which runs the gate commands locally \u2014 pr create then takes as long as this seat needs for its own gate run, and the CI gate skips them (#6284)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
39787
+ withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--no-zeroci", "compatibility option; pr create does not mint receipts").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
39595
39788
  let body;
39596
39789
  let title;
39597
39790
  try {
39598
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
39599
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39791
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39792
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39600
39793
  } catch (e) {
39601
39794
  return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
39602
39795
  }
@@ -39616,9 +39809,6 @@ ${list}`);
39616
39809
  }
39617
39810
  const claimRefusal = await prCreateClaimRefusal(body, o.repo);
39618
39811
  if (claimRefusal) return fail(claimRefusal);
39619
- const zeroCiWarn = await mintZeroCiReceipt({ repo: o.repo, zeroci: o.zeroci }, createPrCreateZeroCiDeps());
39620
- if (zeroCiWarn) process.stderr.write(`${zeroCiWarn}
39621
- `);
39622
39812
  const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
39623
39813
  if (isGhCreateRateLimited(created)) {
39624
39814
  console.log(JSON.stringify(created));
@@ -39654,7 +39844,7 @@ ${list}`);
39654
39844
  console.log(JSON.stringify(data2));
39655
39845
  return;
39656
39846
  }
39657
- const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json", effective]);
39847
+ const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
39658
39848
  console.log(JSON.stringify(data));
39659
39849
  } catch (e) {
39660
39850
  const err = e;
@@ -39662,11 +39852,11 @@ ${list}`);
39662
39852
  }
39663
39853
  });
39664
39854
  async function listCiWorkflowPaths(cwd = process.cwd()) {
39665
- const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
39855
+ const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
39666
39856
  if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
39667
39857
  return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39668
39858
  try {
39669
- return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
39859
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
39670
39860
  } catch {
39671
39861
  return true;
39672
39862
  }
@@ -39854,7 +40044,12 @@ ${list}`);
39854
40044
  if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
39855
40045
  if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
39856
40046
  });
39857
- 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) => {
40047
+ pr.command("review-check <number>").description("read-only trusted review verdict check for an exact PR head").requiredOption("--repo <owner/repo>", "target repository").requiredOption("--head <sha>", "exact expected head SHA").option("--json", "machine-readable output").action(async (number, o) => {
40048
+ const result = await checkPrReview(number, o.repo, o.head);
40049
+ console.log(o.json ? JSON.stringify(result) : `pr review-check: ${result.reason}`);
40050
+ if (!result.ok) process.exitCode = 1;
40051
+ });
40052
+ jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").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) => {
39858
40053
  if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
39859
40054
  const verdict = o.verdict;
39860
40055
  const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
@@ -39896,7 +40091,7 @@ ${list}`);
39896
40091
  }
39897
40092
  class PrHeadBehindBaseError extends Error {
39898
40093
  }
39899
- 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) => {
40094
+ 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>", "compatibility option; shared merge helpers do not require a review verdict").action(async (number, o) => {
39900
40095
  if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
39901
40096
  try {
39902
40097
  const parsed = parseIssueRef(number, o.repo);
@@ -39914,7 +40109,7 @@ ${list}`);
39914
40109
  await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
39915
40110
  async (n) => {
39916
40111
  if (!landRepoForGuard) return void 0;
39917
- const viewed = await ghJson(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
40112
+ const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
39918
40113
  return typeof viewed.state === "string" ? viewed.state : void 0;
39919
40114
  }
39920
40115
  );
@@ -39931,16 +40126,15 @@ ${list}`);
39931
40126
  return;
39932
40127
  }
39933
40128
  if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
39934
- {
39935
- const gateRepo = landRepoForGuard ?? await requireRepo(o.repo);
39936
- const refusal = await requireReviewVerdict("pr land", number, gateRepo, { withoutReview: o.withoutReview });
39937
- if (refusal) {
39938
- console.error(refusal);
39939
- process.exitCode = 1;
39940
- return;
39941
- }
39942
- }
39943
40129
  const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
40130
+ queueMerge: async (prNumber, repo) => {
40131
+ const meta = await ghJson2(["pr", "view", prNumber, "--repo", repo, "--json", "baseRefName,headRefOid"]);
40132
+ if (!await usesMergifyPilot(repo, meta.baseRefName)) return void 0;
40133
+ if (!landClosingGuardInput) throw new Error("pr land: Mergify requires a readable closing-keyword guard");
40134
+ const guard = evaluateClosingGuard(landClosingGuardInput, { force: o.force, context: "pr land", squashBodyText: landClosingGuardInput.text });
40135
+ if (guard.blocked) throw new Error(guard.message);
40136
+ return requestMergifyMerge(prNumber, repo, landClosingGuardInput.text, meta.headRefOid);
40137
+ },
39944
40138
  resolveRepo: async (prNumber, repoOpt) => {
39945
40139
  const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
39946
40140
  const viewed = (await execFileP("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS4 })).stdout.trim();
@@ -40075,7 +40269,7 @@ ${list}`);
40075
40269
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
40076
40270
  if (result.status === "failed") process.exitCode = 1;
40077
40271
  });
40078
- 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) => {
40272
+ 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>", "compatibility option; shared merge helpers do not require a review verdict")).action(async (number, o) => {
40079
40273
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
40080
40274
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
40081
40275
  if (o.disableAuto) {
@@ -40097,20 +40291,24 @@ ${list}`);
40097
40291
  const headRef = prMeta.head;
40098
40292
  const baseRef = prMeta.base;
40099
40293
  const headRefOid = (prMeta.oid ?? "").trim() || void 0;
40294
+ if (!repoForPostCleanup) throw new Error("pr merge: cannot resolve target repository");
40295
+ const mergifyPilot = await usesMergifyPilot(repoForPostCleanup, baseRef);
40296
+ if (mergifyPilot && (method !== "--squash" || o.squashBodyFile)) throw new Error("pr merge: Mergify pilot uses the protected squash title/body policy; custom merge methods and body files are unsupported");
40100
40297
  const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
40101
40298
  const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
40102
40299
  const closingGuardInput = await withAlreadyClosedCommitTargets(
40103
40300
  await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
40104
40301
  async (n) => {
40105
40302
  if (!repoForPostCleanup) return void 0;
40106
- const viewed = await ghJson(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
40303
+ const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
40107
40304
  return typeof viewed.state === "string" ? viewed.state : void 0;
40108
40305
  }
40109
40306
  );
40110
40307
  if (o.squashBodyFile && method !== "--squash") {
40111
40308
  throw new Error("pr merge: --squash-body-file applies only to squash merges");
40112
40309
  }
40113
- const mergeSquashBody = squashBodyTextForMerge(
40310
+ if (mergifyPilot && !closingGuardInput) throw new Error("pr merge: Mergify requires a readable closing-keyword guard");
40311
+ const mergeSquashBody = mergifyPilot ? closingGuardInput.text : squashBodyTextForMerge(
40114
40312
  closingGuardInput,
40115
40313
  method === "--squash",
40116
40314
  o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
@@ -40127,15 +40325,6 @@ ${list}`);
40127
40325
  return;
40128
40326
  }
40129
40327
  if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
40130
- if (prMeta.state !== "MERGED") {
40131
- const gateRepo = repoForPostCleanup ?? await requireRepo(o.repo);
40132
- const refusal = await requireReviewVerdict("pr merge", number, gateRepo, { withoutReview: o.withoutReview });
40133
- if (refusal) {
40134
- console.error(refusal);
40135
- process.exitCode = 1;
40136
- return;
40137
- }
40138
- }
40139
40328
  if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
40140
40329
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
40141
40330
  const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
@@ -40154,7 +40343,7 @@ ${list}`);
40154
40343
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
40155
40344
  let foreignCheckout;
40156
40345
  if (foreignCwd) {
40157
- const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
40346
+ const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
40158
40347
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
40159
40348
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
40160
40349
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -40217,7 +40406,7 @@ ${list}`);
40217
40406
  }
40218
40407
  return true;
40219
40408
  };
40220
- if (o.wait && !await runWaitGate()) return;
40409
+ if (!mergifyPilot && o.wait && !await runWaitGate()) return;
40221
40410
  if (ciPolicy.policy === "no-ci") {
40222
40411
  const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
40223
40412
  if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
@@ -40226,7 +40415,7 @@ ${list}`);
40226
40415
  const remoteBefore = await remoteBranchExists2(headRef, { remote });
40227
40416
  let upgradedToAuto = false;
40228
40417
  let remoteNotAttemptedReason = "preserved-delayed-cleanup";
40229
- const overrideBody = mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
40418
+ const overrideBody = mergifyPilot ? void 0 : mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
40230
40419
  number,
40231
40420
  repoArgs,
40232
40421
  async (a, t) => (await execFileP("gh", a, { timeout: t })).stdout,
@@ -40306,7 +40495,15 @@ ${list}`);
40306
40495
  });
40307
40496
  try {
40308
40497
  try {
40309
- await mergeOnce();
40498
+ if (mergifyPilot) {
40499
+ const queue = await requestMergifyMerge(number, repoForPostCleanup, closingGuardInput.text, headRefOid);
40500
+ if (queue.state !== "merged") {
40501
+ console.log(JSON.stringify({ mergeStatus: "failed", pr: number, repo: repoForPostCleanup, queue }));
40502
+ process.exitCode = queue.state === "refused" ? 1 : PR_CHECKS_TIMEOUT_EXIT_CODE;
40503
+ return;
40504
+ }
40505
+ remoteNotAttemptedReason = "pr-already-merged";
40506
+ } else await mergeOnce();
40310
40507
  } catch (e) {
40311
40508
  if (!(e instanceof PrHeadBehindBaseError)) throw e;
40312
40509
  const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
@@ -40524,7 +40721,7 @@ async function resolveWhoami(deps) {
40524
40721
  }
40525
40722
 
40526
40723
  // src/command-register-developer.ts
40527
- var import_node_path46 = require("node:path");
40724
+ var import_node_path45 = require("node:path");
40528
40725
 
40529
40726
  // src/wave-land.ts
40530
40727
  function planWaveLand(prs) {
@@ -40778,26 +40975,26 @@ ${SSH_RECIPE_AGENT_NOTE}`);
40778
40975
 
40779
40976
  // src/dist-drift.ts
40780
40977
  var import_node_child_process17 = require("node:child_process");
40781
- var import_node_crypto12 = require("node:crypto");
40978
+ var import_node_crypto13 = require("node:crypto");
40782
40979
  var import_node_fs46 = require("node:fs");
40783
40980
  var import_node_os21 = require("node:os");
40784
- var import_node_path43 = require("node:path");
40981
+ var import_node_path42 = require("node:path");
40785
40982
 
40786
40983
  // ../scripts/distribution-digest.mjs
40787
- var import_node_crypto11 = require("node:crypto");
40984
+ var import_node_crypto12 = require("node:crypto");
40788
40985
  var import_node_fs45 = require("node:fs");
40789
- var import_node_path42 = require("node:path");
40986
+ var import_node_path41 = require("node:path");
40790
40987
  var slash = (value) => value.replaceAll("\\", "/");
40791
40988
  function repoPath(root, declaredPath, label) {
40792
- const absoluteRoot = (0, import_node_path42.resolve)(root);
40793
- const target = (0, import_node_path42.resolve)(root, declaredPath);
40794
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path42.sep}`)) {
40989
+ const absoluteRoot = (0, import_node_path41.resolve)(root);
40990
+ const target = (0, import_node_path41.resolve)(root, declaredPath);
40991
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
40795
40992
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
40796
40993
  }
40797
40994
  return target;
40798
40995
  }
40799
40996
  function digestFiles(files) {
40800
- const hash = (0, import_node_crypto11.createHash)("sha256");
40997
+ const hash = (0, import_node_crypto12.createHash)("sha256");
40801
40998
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
40802
40999
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
40803
41000
  hash.update(file.relative, "utf8");
@@ -40827,7 +41024,7 @@ var DIST_ARTIFACTS = [
40827
41024
  ];
40828
41025
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
40829
41026
  var ABSENT = "absent";
40830
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
41027
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto13.createHash)("sha256").update(bytes).digest("hex")}`;
40831
41028
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
40832
41029
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
40833
41030
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
@@ -40905,7 +41102,7 @@ function walkFiles(root) {
40905
41102
  const files = [];
40906
41103
  const walk2 = (directory) => {
40907
41104
  for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
40908
- const child2 = (0, import_node_path43.join)(directory, entry.name);
41105
+ const child2 = (0, import_node_path42.join)(directory, entry.name);
40909
41106
  if (entry.isDirectory()) walk2(child2);
40910
41107
  else files.push(child2);
40911
41108
  }
@@ -40915,10 +41112,10 @@ function walkFiles(root) {
40915
41112
  }
40916
41113
  function bomPathFor(root) {
40917
41114
  try {
40918
- const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path43.join)(root, "surfaces.json"), "utf8"));
40919
- return (0, import_node_path43.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
41115
+ const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
41116
+ return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
40920
41117
  } catch {
40921
- return (0, import_node_path43.join)(root, "distribution-bom.json");
41118
+ return (0, import_node_path42.join)(root, "distribution-bom.json");
40922
41119
  }
40923
41120
  }
40924
41121
  function rebuildTo(packageRoot, outDir) {
@@ -40931,28 +41128,28 @@ function rebuildTo(packageRoot, outDir) {
40931
41128
  });
40932
41129
  }
40933
41130
  function runDistStatus(root) {
40934
- const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
41131
+ const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
40935
41132
  let overlayCount = 0;
40936
41133
  try {
40937
- const cliOut = (0, import_node_path43.join)(stage, "cli-dist");
40938
- const hubOut = (0, import_node_path43.join)(stage, "hub-dist");
40939
- rebuildTo((0, import_node_path43.join)(root, "cli"), cliOut);
40940
- rebuildTo((0, import_node_path43.join)(root, "updater"), hubOut);
41134
+ const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
41135
+ const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
41136
+ rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
41137
+ rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
40941
41138
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
40942
41139
  const rebuilt = (path2) => {
40943
41140
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
40944
- return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
41141
+ return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
40945
41142
  };
40946
- const committed = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
40947
- const tree = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
40948
- const distRoot = (0, import_node_path43.join)(root, "cli", "dist");
40949
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path43.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
41143
+ const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
41144
+ const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
41145
+ const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
41146
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
40950
41147
  const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
40951
41148
  const digest = (entries) => {
40952
- const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
41149
+ const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
40953
41150
  for (const entry of entries) {
40954
- const target = (0, import_node_path43.join)(overlay, entry.path);
40955
- (0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(target), { recursive: true });
41151
+ const target = (0, import_node_path42.join)(overlay, entry.path);
41152
+ (0, import_node_fs46.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
40956
41153
  (0, import_node_fs46.writeFileSync)(target, entry.bytes);
40957
41154
  }
40958
41155
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
@@ -41030,8 +41227,8 @@ function registerEdgeCommands(program3) {
41030
41227
  }
41031
41228
 
41032
41229
  // src/schedules-lift-command.ts
41033
- var import_promises9 = require("node:fs/promises");
41034
- var import_node_path44 = require("node:path");
41230
+ var import_promises8 = require("node:fs/promises");
41231
+ var import_node_path43 = require("node:path");
41035
41232
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
41036
41233
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
41037
41234
  var SchedulesLiftUsageError = class extends Error {
@@ -41051,14 +41248,14 @@ var RegistryUnreachableError = class extends Error {
41051
41248
  async function readWorkflowFiles(dir) {
41052
41249
  let names;
41053
41250
  try {
41054
- names = await (0, import_promises9.readdir)(dir);
41251
+ names = await (0, import_promises8.readdir)(dir);
41055
41252
  } catch {
41056
41253
  return [];
41057
41254
  }
41058
41255
  const files = [];
41059
41256
  for (const name of names.sort()) {
41060
41257
  if (!/\.ya?ml$/.test(name)) continue;
41061
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises9.readFile)((0, import_node_path44.join)(dir, name), "utf8") });
41258
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
41062
41259
  }
41063
41260
  return files;
41064
41261
  }
@@ -41142,7 +41339,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
41142
41339
  // src/spawn-policy-core.ts
41143
41340
  var import_node_child_process18 = require("node:child_process");
41144
41341
  var import_node_fs47 = require("node:fs");
41145
- var import_node_path45 = require("node:path");
41342
+ var import_node_path44 = require("node:path");
41146
41343
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
41147
41344
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
41148
41345
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -41228,7 +41425,7 @@ function runSpawnPolicy(root) {
41228
41425
  for (const file of files) {
41229
41426
  let raw;
41230
41427
  try {
41231
- raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
41428
+ raw = (0, import_node_fs47.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
41232
41429
  } catch {
41233
41430
  continue;
41234
41431
  }
@@ -41248,7 +41445,7 @@ function runSpawnPolicy(root) {
41248
41445
  function registerDeveloperCommands(program3) {
41249
41446
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
41250
41447
  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) => {
41251
- const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
41448
+ const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
41252
41449
  const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
41253
41450
  const plan = planManagedGitignore(current);
41254
41451
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -41508,10 +41705,10 @@ function registerDeveloperCommands(program3) {
41508
41705
  }
41509
41706
 
41510
41707
  // src/command-register-train-operations.ts
41511
- var import_promises11 = require("node:fs/promises");
41708
+ var import_promises10 = require("node:fs/promises");
41512
41709
 
41513
41710
  // src/hotfix-apply.ts
41514
- var import_promises10 = require("node:fs/promises");
41711
+ var import_promises9 = require("node:fs/promises");
41515
41712
 
41516
41713
  // src/slack-alert.ts
41517
41714
  var SSM_REGION = "eu-central-1";
@@ -42295,7 +42492,7 @@ async function runHotfixRelease(deps, versionInput, options = {}, doctor = runTr
42295
42492
  }
42296
42493
  let lines2;
42297
42494
  try {
42298
- lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises10.readFile)(p, "utf8")))(options.announceSummaryFile));
42495
+ lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises9.readFile)(p, "utf8")))(options.announceSummaryFile));
42299
42496
  } catch (e) {
42300
42497
  throw new Error(`could not read --announce-summary-file ${options.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
42301
42498
  }
@@ -42854,7 +43051,7 @@ function checkHotfixCarries(options) {
42854
43051
 
42855
43052
  // src/train-commands.ts
42856
43053
  var import_node_fs49 = require("node:fs");
42857
- var import_node_path47 = require("node:path");
43054
+ var import_node_path46 = require("node:path");
42858
43055
  var INVOKED_ARGV = process.argv.slice(2);
42859
43056
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
42860
43057
  function resolveReleaseBumpIntent(raw) {
@@ -42866,7 +43063,7 @@ function resolveReleaseBumpIntent(raw) {
42866
43063
  }
42867
43064
  function readRepoVersion() {
42868
43065
  try {
42869
- return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path47.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
43066
+ return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
42870
43067
  } catch {
42871
43068
  return void 0;
42872
43069
  }
@@ -43029,8 +43226,8 @@ function trainApplyDeps() {
43029
43226
  // Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
43030
43227
  announce: (args) => announceRelease({
43031
43228
  run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
43032
- readFile: (path2) => (0, import_promises11.readFile)(path2, "utf8"),
43033
- removeFile: (path2) => (0, import_promises11.unlink)(path2)
43229
+ readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
43230
+ removeFile: (path2) => (0, import_promises10.unlink)(path2)
43034
43231
  }, args),
43035
43232
  // #4713 (I/O-boundary census): `null` used to mean BOTH "this project configures no edge domains"
43036
43233
  // (a real answer) and "the registry read missed" — so a release verdict printed an environments block
@@ -43416,7 +43613,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
43416
43613
  }
43417
43614
  let summaryLines;
43418
43615
  try {
43419
- summaryLines = summaryFileLines(await (0, import_promises11.readFile)(o.announceSummaryFile, "utf8"));
43616
+ summaryLines = summaryFileLines(await (0, import_promises10.readFile)(o.announceSummaryFile, "utf8"));
43420
43617
  } catch (e) {
43421
43618
  return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
43422
43619
  }
@@ -43610,7 +43807,7 @@ ${r.stderr ?? ""}`).catch(() => "");
43610
43807
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
43611
43808
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
43612
43809
  function envHealLockPath(home) {
43613
- return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43810
+ return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43614
43811
  }
43615
43812
  async function withEnvHealLock(what, run) {
43616
43813
  try {
@@ -43781,7 +43978,7 @@ function mmiDoctorDeps(opts = {}) {
43781
43978
  // has no generated routing index would
43782
43979
  // get a permanent — demanding an artifact it never asked for.
43783
43980
  docsIndexState: (root) => {
43784
- if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
43981
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
43785
43982
  const real = createDocsIndexDeps(root);
43786
43983
  let docs;
43787
43984
  const listDocs = () => docs ??= real.listDocs();
@@ -43790,7 +43987,7 @@ function mmiDoctorDeps(opts = {}) {
43790
43987
  },
43791
43988
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
43792
43989
  healDocsIndex: (root) => {
43793
- if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
43990
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
43794
43991
  const real = createDocsIndexDeps(root);
43795
43992
  let docs;
43796
43993
  const listDocs = () => docs ??= real.listDocs();
@@ -44669,16 +44866,16 @@ function ciAuditDeps2() {
44669
44866
  // gate re-seed step is skipped gracefully rather than failing mid-run.
44670
44867
  readSeedFile: (path2) => {
44671
44868
  if (!root) return null;
44672
- const fullPath = (0, import_node_path48.join)(root, path2);
44869
+ const fullPath = (0, import_node_path47.join)(root, path2);
44673
44870
  return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
44674
44871
  }
44675
44872
  };
44676
44873
  }
44677
44874
  function hubRoot2() {
44678
- const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
44875
+ const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
44679
44876
  const marker = "skills/bootstrap/seeds/manifest.json";
44680
- if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
44681
- if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
44877
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
44878
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
44682
44879
  return null;
44683
44880
  }
44684
44881
  registerQueryCommands(program2);
@@ -44817,7 +45014,7 @@ function directoryBytes(path2) {
44817
45014
  return 0;
44818
45015
  }
44819
45016
  for (const entry of entries) {
44820
- const child2 = (0, import_node_path48.join)(path2, entry.name);
45017
+ const child2 = (0, import_node_path47.join)(path2, entry.name);
44821
45018
  if (entry.isDirectory()) total += directoryBytes(child2);
44822
45019
  else {
44823
45020
  try {
@@ -44847,7 +45044,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
44847
45044
  dirBytes,
44848
45045
  listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
44849
45046
  try {
44850
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path48.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
45047
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
44851
45048
  } catch {
44852
45049
  return { name: d.name, mtimeMs: Date.now() };
44853
45050
  }
@@ -44861,7 +45058,7 @@ function stagingApplyFsGuard(configRoot) {
44861
45058
  return {
44862
45059
  referencedPaths: () => readInstalledPluginRefs(configRoot),
44863
45060
  mtimeMs: (name) => {
44864
- const p = (0, import_node_path48.join)(stagingRoot, name);
45061
+ const p = (0, import_node_path47.join)(stagingRoot, name);
44865
45062
  if (!(0, import_node_fs50.existsSync)(p)) return null;
44866
45063
  try {
44867
45064
  return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);