@mutmutco/cli 4.3.20 → 4.3.22

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 +761 -502
  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");
@@ -12242,7 +12242,10 @@ async function claimOneBoardItem(ctx, selector, options) {
12242
12242
  }
12243
12243
  return;
12244
12244
  }
12245
- if (!options.force) throw new Error(withPowerShellChainHint(laneContestMessage(item.ref, contest, "claim")));
12245
+ if (!options.force) {
12246
+ const refusal = laneContestMessage(item.ref, contest, "claim");
12247
+ throw new Error(options.bulk ? refusal : withPowerShellChainHint(refusal));
12248
+ }
12246
12249
  previousHolder = displaced();
12247
12250
  };
12248
12251
  await refuseIfContested();
@@ -12313,7 +12316,7 @@ async function claimBoardIssues(options, deps = {}) {
12313
12316
  const selector = selectors[index];
12314
12317
  const ref = `${selector.repo}#${selector.number}`;
12315
12318
  try {
12316
- const result = await claimOneBoardItem(ctx, selector, options);
12319
+ const result = await claimOneBoardItem(ctx, selector, { ...options, bulk: true });
12317
12320
  results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
12318
12321
  } catch (e) {
12319
12322
  results[index] = { ref, claimed: false, reason: e.message };
@@ -12902,6 +12905,29 @@ async function waitForPrChecks(deps) {
12902
12905
 
12903
12906
  // src/bootstrap-ruleset.ts
12904
12907
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
12908
+ var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
12909
+ function reseedProductRulesetStrictness(current, seed) {
12910
+ const parse = (raw) => {
12911
+ const policy2 = JSON.parse(raw);
12912
+ 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")) {
12913
+ throw new Error("product ruleset must be a branch ruleset object with well-formed rules");
12914
+ }
12915
+ const required = policy2.rules.filter((r) => r.type === "required_status_checks");
12916
+ const parameters2 = required[0]?.parameters;
12917
+ 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") {
12918
+ throw new Error("product ruleset must carry exactly one well-formed required_status_checks rule");
12919
+ }
12920
+ return { policy: policy2, parameters: parameters2 };
12921
+ };
12922
+ const strict = parse(seed).parameters.strict_required_status_checks_policy;
12923
+ if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
12924
+ if (current === null) return seed;
12925
+ const { policy, parameters } = parse(current);
12926
+ if (parameters.strict_required_status_checks_policy === strict) return current;
12927
+ parameters.strict_required_status_checks_policy = strict;
12928
+ return `${JSON.stringify(policy, null, 2)}
12929
+ `;
12930
+ }
12905
12931
  function stripRulesetComment(raw) {
12906
12932
  const parsed = JSON.parse(raw);
12907
12933
  delete parsed._comment;
@@ -12921,14 +12947,6 @@ function rulesetStrictPolicy(ruleset) {
12921
12947
  const rules = (ruleset.rules ?? []).filter((rule) => rule.type === "required_status_checks");
12922
12948
  return rules.length > 0 && rules.every((rule) => rule.parameters?.strict_required_status_checks_policy === true);
12923
12949
  }
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
12950
  function rulesetBranchIncludes(ruleset) {
12933
12951
  const raw = ruleset.conditions?.ref_name?.include;
12934
12952
  return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
@@ -12948,8 +12966,7 @@ function patchRulesetRequiredContexts(body, contexts) {
12948
12966
  ...r,
12949
12967
  parameters: {
12950
12968
  ...r.parameters,
12951
- strict_required_status_checks_policy: true,
12952
- // #6263: org policy — see patchRulesetStrictPolicy
12969
+ strict_required_status_checks_policy: r.parameters?.strict_required_status_checks_policy ?? false,
12953
12970
  required_status_checks: sorted.map((context) => ({ context }))
12954
12971
  }
12955
12972
  };
@@ -13353,7 +13370,7 @@ function extractForwardRefs(markdown) {
13353
13370
  });
13354
13371
  return refs;
13355
13372
  }
13356
- function checkPins(root, readFile10, docs) {
13373
+ function checkPins(root, readFile9, docs) {
13357
13374
  const findings = [];
13358
13375
  for (const [doc, markdown] of Object.entries(docs)) {
13359
13376
  for (const pin of extractPins(markdown)) {
@@ -13361,7 +13378,7 @@ function checkPins(root, readFile10, docs) {
13361
13378
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
13362
13379
  continue;
13363
13380
  }
13364
- const source = readFile10((0, import_node_path12.join)(root, pin.file));
13381
+ const source = readFile9((0, import_node_path12.join)(root, pin.file));
13365
13382
  if (source == null) {
13366
13383
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
13367
13384
  continue;
@@ -13576,7 +13593,7 @@ function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_chi
13576
13593
  }
13577
13594
  }
13578
13595
  function runDocRefs(root, deps = {}) {
13579
- const readFile10 = deps.readFile ?? readFileOrNull;
13596
+ const readFile9 = deps.readFile ?? readFileOrNull;
13580
13597
  const exists = deps.exists ?? import_node_fs14.existsSync;
13581
13598
  const listDocs = deps.listDocs ?? defaultListDocs;
13582
13599
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
@@ -13585,11 +13602,11 @@ function runDocRefs(root, deps = {}) {
13585
13602
  const walked = listDocs(root);
13586
13603
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
13587
13604
  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)
13605
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
13589
13606
  );
13590
13607
  const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
13591
13608
  const findings = [
13592
- ...checkPins(root, readFile10, docs).findings,
13609
+ ...checkPins(root, readFile9, docs).findings,
13593
13610
  ...refResult.findings
13594
13611
  ];
13595
13612
  if (commandPaths == null) {
@@ -14186,22 +14203,22 @@ function labelsToPrune(orgLabelNames) {
14186
14203
  const org = new Set(orgLabelNames);
14187
14204
  return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
14188
14205
  }
14189
- function resolveSeedContent(seed, vars, readFile10) {
14190
- if (seed.source === "self") return readFile10(seed.target);
14206
+ function resolveSeedContent(seed, vars, readFile9) {
14207
+ if (seed.source === "self") return readFile9(seed.target);
14191
14208
  if (seed.source.startsWith("seed:")) {
14192
- const tmpl = readFile10(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
14209
+ const tmpl = readFile9(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
14193
14210
  return tmpl == null ? null : renderSeed(tmpl, vars);
14194
14211
  }
14195
14212
  return null;
14196
14213
  }
14197
- function resolveSeedWriteContent(seed, vars, readFile10, remoteContent) {
14214
+ function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
14198
14215
  if (!seed.managedBlock) {
14199
- return { ok: true, content: resolveSeedContent(seed, vars, readFile10), managed: seed.source === "managed-block" };
14216
+ return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
14200
14217
  }
14201
- const base = remoteContent ?? resolveSeedContent(seed, vars, readFile10);
14218
+ const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
14202
14219
  if (base == null) return { ok: true, content: null, managed: true };
14203
14220
  const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
14204
- const desired = resolveSeedContent(blockSeed, vars, readFile10);
14221
+ const desired = resolveSeedContent(blockSeed, vars, readFile9);
14205
14222
  if (desired == null) return { ok: true, content: null, managed: true };
14206
14223
  const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
14207
14224
  return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
@@ -15502,10 +15519,10 @@ var rollout_plan_default = {
15502
15519
  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
15520
  },
15504
15521
  baseline: {
15505
- version: "4.3.20",
15506
- tag: "v4.3.20",
15507
- commit: "f33217850417",
15508
- npm: "@mutmutco/cli@4.3.20"
15522
+ version: "4.3.22",
15523
+ tag: "v4.3.22",
15524
+ commit: "ab0293be4361",
15525
+ npm: "@mutmutco/cli@4.3.22"
15509
15526
  },
15510
15527
  exitCriterion: "fleet-n-of-n",
15511
15528
  hubOnlyShortcut: "forbidden",
@@ -15522,14 +15539,14 @@ var rollout_plan_default = {
15522
15539
  repo: "mutmutco/mmi-hub",
15523
15540
  role: "canary",
15524
15541
  schedule: "train",
15525
- v3Target: "v4.3.20"
15542
+ v3Target: "v4.3.22"
15526
15543
  }
15527
15544
  ],
15528
15545
  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
15546
  rollback: {
15530
15547
  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)"
15548
+ mechanism: "npm dist-tag latest -> 4.3.22 and redeploy the Hub Lambda from tag v4.3.22 (ab0293be4361); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15549
+ v3Target: "v4.3.22 (@mutmutco/cli@4.3.22, tag commit ab0293be4361 \u2014 last known-good release carrying the repo-index v4-only contract)"
15533
15550
  }
15534
15551
  },
15535
15552
  {
@@ -17897,8 +17914,29 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
17897
17914
  }
17898
17915
  return [...contexts];
17899
17916
  }
17900
- async function findAlignmentPr(deps, ctx, target) {
17901
- const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", "main", "--state", "all", "--json", "number,url,state"]));
17917
+ function alignmentBranchName(target, tag) {
17918
+ return `release-align/${target}/${tag}`;
17919
+ }
17920
+ async function pushAlignmentBranch(deps, target, tag) {
17921
+ const branch = alignmentBranchName(target, tag);
17922
+ let tree;
17923
+ try {
17924
+ tree = clean2(await deps.run("git", ["merge-tree", "--write-tree", "--no-messages", `origin/${target}`, "origin/main"])).split("\n")[0] ?? "";
17925
+ } catch {
17926
+ const files = await runMergeTreePreflight(deps, `origin/${target}`, "origin/main");
17927
+ throw new Error(
17928
+ `the ${target} alignment of ${tag} could not merge origin/main${files.length ? ` \u2014 conflicts on ${files.join(", ")}` : ""} (#6288). Reconcile it on a branch cut from origin/${target} that merges origin/main, open that against ${target}, and land it with a true merge (\`mmi-cli devops pr merge <n> --auto --merge\`) \u2014 never hand-resolve on main, and never merge ${target} into main.`
17929
+ );
17930
+ }
17931
+ if (!tree) throw new Error(`git merge-tree wrote no tree for the ${target} alignment of ${tag} (#6288)`);
17932
+ const commit = clean2(await deps.run("git", ["commit-tree", tree, "-p", `origin/${target}`, "-p", "origin/main", "-m", `chore(release): align ${target} to ${tag}`]));
17933
+ if (!commit) throw new Error(`git commit-tree wrote no commit for the ${target} alignment of ${tag} (#6288)`);
17934
+ await deps.run("git", ["push", "origin", "--delete", branch]).catch(() => void 0);
17935
+ await runGitPush(deps, ["push", "origin", `${commit}:refs/heads/${branch}`]);
17936
+ return branch;
17937
+ }
17938
+ async function findAlignmentPr(deps, ctx, target, head) {
17939
+ const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", head, "--state", "all", "--json", "number,url,state"]));
17902
17940
  if (!out) return void 0;
17903
17941
  const rows = JSON.parse(out);
17904
17942
  const row = rows.filter((r) => typeof r.number === "number" && typeof r.url === "string" && (r.state === "OPEN" || r.state === "MERGED")).sort((a, b) => b.number - a.number)[0];
@@ -17919,23 +17957,28 @@ async function rollDevelopmentForward(deps, ctx, tag) {
17919
17957
  return { status: "pushed", note: "development rolled forward to the released main (development has no required checks)" };
17920
17958
  }
17921
17959
  const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..origin/main"]));
17922
- const existing = await findAlignmentPr(deps, ctx, "development");
17960
+ const branch = alignmentBranchName("development", tag);
17961
+ const existing = await findAlignmentPr(deps, ctx, "development", branch);
17923
17962
  if (ahead === "0") {
17963
+ const merged = existing ?? await findAlignmentPr(deps, ctx, "development", "main");
17924
17964
  return {
17925
17965
  status: "aligned",
17926
17966
  alignment: "already-merged",
17927
- note: existing?.state === "MERGED" ? `alignment: already-merged \u2014 development already contains main via alignment PR #${existing.number}` : "alignment: already-merged \u2014 development already contains the released main; nothing to roll forward"
17967
+ note: merged?.state === "MERGED" ? `alignment: already-merged \u2014 development already contains main via alignment PR #${merged.number}` : "alignment: already-merged \u2014 development already contains the released main; nothing to roll forward"
17928
17968
  };
17929
17969
  }
17930
17970
  if (existing?.state === "OPEN") {
17931
17971
  return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `alignment PR already open: ${existing.url}`);
17932
17972
  }
17973
+ const superseded = await findAlignmentPr(deps, ctx, "development", "main");
17974
+ const supersededNote = superseded?.state === "OPEN" ? ` (supersedes the main-headed alignment PR #${superseded.number}, which GitHub closes as merged once this lands \u2014 #6288)` : "";
17975
+ await pushAlignmentBranch(deps, "development", tag);
17933
17976
  const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
17934
17977
 
17935
- \`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
17936
- const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
17978
+ \`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). The head is \`development\` with \`main\` merged into it, never \`main\` itself: a strict \`require branches to be up to date\` ruleset can never be satisfied by a main-headed PR, and satisfying it literally would promote every unreleased commit to \`main\` (#6288). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
17979
+ const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", branch, "--title", `chore(release): align development to ${tag}`, "--body", body]));
17937
17980
  const number = parsePrNumber(url);
17938
- return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}`);
17981
+ return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}${supersededNote}`);
17939
17982
  }
17940
17983
  var ALIGNMENT_ARM_ATTEMPTS = 3;
17941
17984
  var ALIGNMENT_ARM_BACKOFF_MS = 2e3;
@@ -17976,23 +18019,28 @@ async function alignRcForward(deps, ctx, tag) {
17976
18019
  return { status: "pushed", note: "rc aligned to the released main (rc has no required checks)" };
17977
18020
  }
17978
18021
  const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/rc..origin/main"]));
17979
- const existing = await findAlignmentPr(deps, ctx, "rc");
18022
+ const branch = alignmentBranchName("rc", tag);
18023
+ const existing = await findAlignmentPr(deps, ctx, "rc", branch);
17980
18024
  if (ahead === "0") {
18025
+ const merged = existing ?? await findAlignmentPr(deps, ctx, "rc", "main");
17981
18026
  return {
17982
18027
  status: "aligned",
17983
18028
  alignment: "already-merged",
17984
- note: existing?.state === "MERGED" ? `alignment: already-merged \u2014 rc already contains main via alignment PR #${existing.number}` : "alignment: already-merged \u2014 rc already contains the released main; nothing to align"
18029
+ note: merged?.state === "MERGED" ? `alignment: already-merged \u2014 rc already contains main via alignment PR #${merged.number}` : "alignment: already-merged \u2014 rc already contains the released main; nothing to align"
17985
18030
  };
17986
18031
  }
17987
18032
  if (existing?.state === "OPEN") {
17988
18033
  return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `rc alignment PR already open: ${existing.url}`);
17989
18034
  }
18035
+ const superseded = await findAlignmentPr(deps, ctx, "rc", "main");
18036
+ const supersededNote = superseded?.state === "OPEN" ? ` (supersedes the main-headed alignment PR #${superseded.number}, which GitHub closes as merged once this lands \u2014 #6288)` : "";
18037
+ await pushAlignmentBranch(deps, "rc", tag);
17990
18038
  const body = `Carries the ${tag} release from \`main\` back to \`rc\`.
17991
18039
 
17992
- \`rc\` requires status checks, so the release train opens this alignment PR instead of bypassing branch protection with a direct push. Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives.`;
17993
- const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "rc", "--head", "main", "--title", `chore(release): align rc to ${tag}`, "--body", body]));
18040
+ \`rc\` requires status checks, so the release train opens this alignment PR instead of bypassing branch protection with a direct push. The head is \`rc\` with \`main\` merged into it, never \`main\` itself \u2014 a strict \`require branches to be up to date\` ruleset can never be satisfied by a main-headed PR (#6288). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives.`;
18041
+ const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "rc", "--head", branch, "--title", `chore(release): align rc to ${tag}`, "--body", body]));
17994
18042
  const number = parsePrNumber(url);
17995
- return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `rc requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}`);
18043
+ return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `rc requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}${supersededNote}`);
17996
18044
  }
17997
18045
  var FLEET_FOREIGN_VERDICT_GRACE_ATTEMPTS = 3;
17998
18046
  function resolveContextState(context, checkRuns, statuses) {
@@ -21535,6 +21583,23 @@ async function runTrainDoctor(input) {
21535
21583
  derived = deriveReleasePhaseStatus(ledger);
21536
21584
  }
21537
21585
  }
21586
+ if (derived.legs.some((l) => l.phase === "alignment" && l.state === "pending")) {
21587
+ for (const target of ["development", "rc"].filter((t) => t !== "rc" || track === "full")) {
21588
+ try {
21589
+ const stale = await findAlignmentPr(train, ctx, target, "main");
21590
+ if (stale?.state !== "OPEN") continue;
21591
+ add({
21592
+ code: "alignment-pr-main-headed",
21593
+ severity: "blocker",
21594
+ source: "origin",
21595
+ title: `alignment PR #${stale.number} (\`main\` -> ${target}) is headed by \`main\` \u2014 under a strict "branches must be up to date" ruleset it can never merge (#6288)`,
21596
+ remedy: `rerun \`mmi-cli devops release --resume\`: the train rebuilds the alignment as \`release-align/${target}/<tag>\` (${target} with \`main\` merged into it), which is up to date by construction; GitHub closes #${stale.number} as merged once that lands. Never merge ${target} into \`main\`, never hand-merge on \`main\`, and never add a bypass actor to the strict rule`
21597
+ });
21598
+ } catch (e) {
21599
+ unverified(`the ${target} alignment PR head`, e);
21600
+ }
21601
+ }
21602
+ }
21538
21603
  if (derived.phaseStatus !== "phases-green") {
21539
21604
  const open2 = derived.legs.filter((l) => l.state === "pending" || l.state === "failed").map((l) => `${l.phase}=${l.state}`).join(", ");
21540
21605
  const failed = derived.phaseStatus === "phases-failed";
@@ -23527,12 +23592,12 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
23527
23592
  const contexts = sortedUnique(registryContexts ?? committedContexts);
23528
23593
  const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
23529
23594
  const committedStrict = rulesetStrictPolicy(committedPayload);
23530
- let apiPayload = patchRulesetStrictPolicy(registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts));
23595
+ let apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
23531
23596
  if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
23532
23597
  const branchIncludes = rulesetBranchIncludes(apiPayload);
23533
23598
  const authoritativeFilePayload = {
23534
23599
  ...filePayload,
23535
- ...registryContexts == null && committedStrict ? {} : { rules: apiPayload.rules },
23600
+ ...registryContexts == null ? {} : { rules: apiPayload.rules },
23536
23601
  ...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
23537
23602
  };
23538
23603
  return {
@@ -23554,7 +23619,7 @@ function sameContexts(left, right) {
23554
23619
  function resolveProductRulesetReconcilePlan(input) {
23555
23620
  const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
23556
23621
  const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
23557
- const liveNeedsStrictConvergence = input.liveStrict === false;
23622
+ const liveNeedsStrictConvergence = input.liveStrict !== void 0 && input.authorityStrict !== void 0 && input.liveStrict !== input.authorityStrict;
23558
23623
  const liveIsActive = input.liveEnforcement === "active";
23559
23624
  if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence && !liveNeedsStrictConvergence) {
23560
23625
  return { shouldActivate: false, targetEnforcement: "active" };
@@ -23765,12 +23830,12 @@ async function auditRepoCi(repo, deps) {
23765
23830
  const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
23766
23831
  const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
23767
23832
  const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
23768
- const strictAligned = authoritativeRuleset.committedStrict && liveStrict;
23833
+ const strictAligned = authoritativeRuleset.committedStrict === liveStrict;
23769
23834
  const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned && strictAligned;
23770
23835
  checks.push({
23771
23836
  ok: aligned,
23772
23837
  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}`),
23838
+ 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
23839
  remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
23775
23840
  });
23776
23841
  }
@@ -24360,6 +24425,7 @@ async function applyCiReconcileRepo(repo, deps) {
24360
24425
  liveBranchIncludes,
24361
24426
  authorityBranchIncludes: authority.branchIncludes,
24362
24427
  liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
24428
+ authorityStrict: authority.committedStrict,
24363
24429
  gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
24364
24430
  unsafeContexts: unsafe
24365
24431
  });
@@ -27167,11 +27233,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
27167
27233
  label: "product required-check ruleset enforcement active",
27168
27234
  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
27235
  });
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
27236
  const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
27176
27237
  const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
27177
27238
  checks.push({
@@ -27225,6 +27286,100 @@ function renderBootstrapVerifyReport(report) {
27225
27286
  return lines2.join("\n");
27226
27287
  }
27227
27288
 
27289
+ // src/bootstrap-seed-delivery.ts
27290
+ var import_node_crypto9 = require("node:crypto");
27291
+ var SHA = /^[a-f0-9]{40}$/;
27292
+ async function readSeedFile(repo, target, ref, client) {
27293
+ try {
27294
+ const file = await client.rest(
27295
+ "GET",
27296
+ `repos/${repo}/contents/${target.split("/").map(encodeURIComponent).join("/")}?ref=${encodeURIComponent(ref)}`
27297
+ );
27298
+ if (!file?.sha || !SHA.test(file.sha) || file.encoding !== "base64" || typeof file.content !== "string") {
27299
+ throw new Error("bootstrap apply: scoped seed file is unreadable");
27300
+ }
27301
+ return { sha: file.sha, content: decodeGitHubContents(file.content) };
27302
+ } catch (e) {
27303
+ if (e instanceof GitHubApiError && e.status === 404) return null;
27304
+ throw e;
27305
+ }
27306
+ }
27307
+ async function readTargetedSeedFiles(repo, plan, target, ref, client) {
27308
+ const base = await readSeedFile(repo, target, plan.baseSha, client);
27309
+ const candidate = ref === plan.baseSha ? base : await readSeedFile(repo, target, ref, client);
27310
+ return { base, candidate };
27311
+ }
27312
+ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, client) {
27313
+ const [rules, base] = await Promise.all([
27314
+ client.rest("GET", `repos/${repo}/rules/branches/${encodeURIComponent(baseBranch)}`),
27315
+ client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(baseBranch)}`)
27316
+ ]);
27317
+ if (!Array.isArray(rules) || rules.some((rule) => !rule || typeof rule.type !== "string" || !rule.type)) {
27318
+ throw new Error("bootstrap apply: base branch protection is unreadable");
27319
+ }
27320
+ const baseSha = base?.object?.sha;
27321
+ if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
27322
+ const identity = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
27323
+ return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
27324
+ }
27325
+ async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
27326
+ let ref;
27327
+ try {
27328
+ ref = await client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(plan.ref)}`);
27329
+ } catch (e) {
27330
+ if (allowMissing && e instanceof GitHubApiError && e.status === 404) return null;
27331
+ throw e;
27332
+ }
27333
+ const head = ref?.object?.sha;
27334
+ if (!head || !SHA.test(head)) throw new Error("bootstrap apply: scoped seed head is unreadable");
27335
+ if (head === plan.baseSha) return head;
27336
+ const diff = await client.rest("GET", `repos/${repo}/compare/${plan.baseSha}...${head}`);
27337
+ 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 ?? "")) {
27338
+ throw new Error("bootstrap apply: scoped seed branch contains changes outside the requested target or base");
27339
+ }
27340
+ return head;
27341
+ }
27342
+ async function prepareTargetedSeedBranch(repo, plan, target, client, gh) {
27343
+ const candidates = await client.rest(
27344
+ "GET",
27345
+ `repos/${repo}/pulls?state=open&head=${encodeURIComponent(`${repo.split("/")[0]}:${plan.branch}`)}&base=${encodeURIComponent(plan.baseBranch)}&per_page=2`
27346
+ );
27347
+ if (!Array.isArray(candidates) || candidates.length > 1) throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
27348
+ for (const pr of candidates) {
27349
+ if (!Number.isInteger(pr?.number) || pr.number <= 0 || pr.head?.ref !== plan.branch || pr.base?.ref !== plan.baseBranch) {
27350
+ throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
27351
+ }
27352
+ await gh(["pr", "merge", String(pr.number), "--repo", repo, "--disable-auto"]);
27353
+ const after = await client.rest("GET", `repos/${repo}/pulls/${pr.number}`);
27354
+ if (after?.auto_merge !== null) throw new Error("bootstrap apply: scoped seed PR auto-merge was not disabled");
27355
+ }
27356
+ const existing = await verifyTargetedSeedBranch(repo, plan, target, client, true);
27357
+ if (existing) return;
27358
+ try {
27359
+ await client.rest("POST", `repos/${repo}/git/refs`, { body: { ref: `refs/heads/${plan.branch}`, sha: plan.baseSha } });
27360
+ } catch (e) {
27361
+ if (!(e instanceof GitHubApiError) || e.status !== 422) throw e;
27362
+ }
27363
+ await verifyTargetedSeedBranch(repo, plan, target, client);
27364
+ }
27365
+ async function enableTargetedSeedAutoMerge(repo, number, plan, target, expected, client, gh) {
27366
+ const head = await verifyTargetedSeedBranch(repo, plan, target, client);
27367
+ const pr = await client.rest("GET", `repos/${repo}/pulls/${number}`);
27368
+ 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");
27369
+ const files = await client.rest(
27370
+ "GET",
27371
+ `repos/${repo}/pulls/${number}/files?per_page=2`
27372
+ );
27373
+ if (!Array.isArray(files) || files.length !== 1 || files[0]?.filename !== target || files[0].previous_filename !== void 0) {
27374
+ throw new Error("bootstrap apply: scoped seed PR contains changes outside the requested target");
27375
+ }
27376
+ const file = await readSeedFile(repo, target, head, client);
27377
+ if (file?.content !== expected) {
27378
+ throw new Error("bootstrap apply: scoped seed content differs from this invocation");
27379
+ }
27380
+ await gh(["pr", "merge", String(number), "--repo", repo, "--auto", "--squash", "--match-head-commit", head]);
27381
+ }
27382
+
27228
27383
  // src/bootstrap-commands.ts
27229
27384
  var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
27230
27385
  function bootstrapTrainDoctorDeps() {
@@ -27537,7 +27692,7 @@ function registerBootstrapCommands(program3) {
27537
27692
  }
27538
27693
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
27539
27694
  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;
27695
+ const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
27541
27696
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
27542
27697
  const putSeed = async (target, content, ref, sha) => {
27543
27698
  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 +27787,23 @@ function registerBootstrapCommands(program3) {
27632
27787
  });
27633
27788
  }
27634
27789
  let seedPlan = { mode: "direct", ref: baseBranch, reason: "dry-run (no protection probe)" };
27790
+ let scopedPlan;
27791
+ let scopedContent;
27635
27792
  let seededToBranch = 0;
27636
- if (o.execute) {
27793
+ if (onlyTarget) {
27794
+ scopedPlan = await targetedSeedPlan(repo, slug, onlyTarget, baseBranch, seedSource.sha, controlClient);
27795
+ seedPlan = scopedPlan;
27796
+ if (o.execute && scopedPlan.branch) {
27797
+ await prepareTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient, gh);
27798
+ }
27799
+ } else if (o.execute) {
27637
27800
  let branchRules = [];
27638
27801
  try {
27639
27802
  branchRules = JSON.parse((await gh(["api", `repos/${repo}/rules/branches/${baseBranch}`])).stdout || "[]");
27640
27803
  } catch {
27641
- branchRules = [];
27804
+ throw new Error("bootstrap apply: base branch protection is unreadable");
27642
27805
  }
27806
+ if (!Array.isArray(branchRules)) throw new Error("bootstrap apply: base branch protection is unreadable");
27643
27807
  seedPlan = planSeedDelivery(branchRules, slug, baseBranch);
27644
27808
  if (seedPlan.branch) {
27645
27809
  const headSha = (await gh(["api", `repos/${repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"])).stdout.trim();
@@ -27651,6 +27815,7 @@ function registerBootstrapCommands(program3) {
27651
27815
  }
27652
27816
  }
27653
27817
  const docsForIndex = [];
27818
+ const seedReadRef = !o.execute && scopedPlan ? scopedPlan.baseSha : seedPlan.ref;
27654
27819
  for (const seed of seedsToApply) {
27655
27820
  if (!seed.classes.includes(o.class)) continue;
27656
27821
  if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
@@ -27664,8 +27829,20 @@ function registerBootstrapCommands(program3) {
27664
27829
  let exists = false;
27665
27830
  let sha;
27666
27831
  let remoteContent = null;
27667
- try {
27668
- const r = await gh(["api", `repos/${repo}/contents/${enc(resolved.target)}?ref=${seedPlan.ref}`]);
27832
+ let baseContent = null;
27833
+ const preserveRuleset = onlyTarget === PRODUCT_RULESET_PATH;
27834
+ if (scopedPlan) {
27835
+ try {
27836
+ const files = await readTargetedSeedFiles(repo, scopedPlan, resolved.target, seedReadRef, controlClient);
27837
+ baseContent = files.base?.content ?? null;
27838
+ exists = files.candidate !== null;
27839
+ sha = files.candidate?.sha;
27840
+ remoteContent = files.candidate?.content ?? null;
27841
+ } catch (e) {
27842
+ return failGraceful(`bootstrap apply: cannot read scoped seed file; no replacement made: ${e.message}`);
27843
+ }
27844
+ } else try {
27845
+ const r = await gh(["api", `repos/${repo}/contents/${enc(resolved.target)}?ref=${seedReadRef}`]);
27669
27846
  exists = true;
27670
27847
  try {
27671
27848
  const parsed = JSON.parse(r.stdout);
@@ -27678,23 +27855,34 @@ function registerBootstrapCommands(program3) {
27678
27855
  } catch {
27679
27856
  exists = false;
27680
27857
  }
27681
- const planned = planSeedAction(resolved, exists);
27858
+ const planned = planSeedAction(resolved, scopedPlan ? baseContent !== null : exists);
27859
+ if (scopedPlan && planned.action !== "skip") planned.action = exists ? "update" : "create";
27682
27860
  const isLegacyBlock = resolved.source === "managed-block";
27683
27861
  let content = null;
27684
27862
  let isManaged = isLegacyBlock || resolved.managedBlock != null;
27863
+ const preservedContent = scopedPlan ? baseContent : remoteContent;
27685
27864
  if (planned.action === "create" || planned.action === "update") {
27686
27865
  if (isLegacyBlock) {
27687
- content = upsertManagedGitignoreBlock(remoteContent).content;
27866
+ content = upsertManagedGitignoreBlock(preservedContent).content;
27688
27867
  } else {
27689
- const writeContent = resolveSeedWriteContent(resolved, vars, readFile10, remoteContent);
27868
+ const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, preservedContent);
27690
27869
  if (!writeContent.ok) {
27691
27870
  return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
27692
27871
  }
27693
27872
  content = writeContent.content;
27873
+ if (preserveRuleset) {
27874
+ if (content === null) return fail("bootstrap apply: canonical product ruleset seed is unreadable");
27875
+ try {
27876
+ content = reseedProductRulesetStrictness(baseContent, content);
27877
+ } catch (e) {
27878
+ return fail(`bootstrap apply: product ruleset preservation refused: ${e.message}`);
27879
+ }
27880
+ }
27694
27881
  isManaged = writeContent.managed;
27695
27882
  }
27696
27883
  }
27697
27884
  const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
27885
+ if (onlyTarget && content !== null) scopedContent = content;
27698
27886
  actions.push(action);
27699
27887
  const docBody = content ?? remoteContent;
27700
27888
  if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
@@ -27729,7 +27917,8 @@ function registerBootstrapCommands(program3) {
27729
27917
  }
27730
27918
  }
27731
27919
  let seedPrUrl;
27732
- if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && seededToBranch > 0) {
27920
+ const scopedHead = o.execute && scopedPlan?.branch ? await verifyTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient) : null;
27921
+ if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && (seededToBranch > 0 || scopedContent !== void 0 && scopedHead !== null && scopedHead !== scopedPlan?.baseSha)) {
27733
27922
  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
27923
  });
27735
27924
  const openPrs = await gh(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
@@ -27758,7 +27947,12 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27758
27947
  seedPrUrl = created.url;
27759
27948
  }
27760
27949
  let autoMergeEnabled = true;
27761
- await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
27950
+ if (scopedPlan) {
27951
+ if (scopedContent === void 0) throw new Error("bootstrap apply: scoped seed content is unresolved");
27952
+ const number = Number(seedPrUrl.split("/").pop());
27953
+ if (!Number.isInteger(number) || number <= 0) throw new Error("bootstrap apply: scoped seed PR number is unreadable");
27954
+ await enableTargetedSeedAutoMerge(repo, number, scopedPlan, onlyTarget, scopedContent, controlClient, gh);
27955
+ } else await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
27762
27956
  const message2 = String(e.message ?? "");
27763
27957
  if (/already/i.test(message2)) return;
27764
27958
  if (/clean status|enablePullRequestAutoMerge/i.test(message2)) {
@@ -27823,7 +28017,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27823
28017
  }
27824
28018
  const rulesetSeed = repo.toLowerCase() === "mutmutco/mmi-hub" ? void 0 : manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
27825
28019
  if (rulesetSeed) {
27826
- const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile10);
28020
+ const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile9);
27827
28021
  if (rulesetContent) {
27828
28022
  try {
27829
28023
  const client = controlClient;
@@ -27941,7 +28135,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
27941
28135
  deployFactsRead: controlDeployFacts !== null
27942
28136
  });
27943
28137
  }
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));
28138
+ 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
28139
  else {
27946
28140
  console.log(renderSeedPlan(actions));
27947
28141
  if (controlRows.length) console.log(`
@@ -27968,7 +28162,7 @@ LIVE apply to ${repo}:
27968
28162
  ${propagatable.map((s) => s.target).join("\n ")}`);
27969
28163
  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
28164
  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;
28165
+ const readSeedFile2 = (path2) => (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null;
27972
28166
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
27973
28167
  const cfg = await loadConfig();
27974
28168
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -28045,14 +28239,14 @@ LIVE apply to ${repo}:
28045
28239
  const project2 = projects.find((p) => (p.repos ?? []).some((repo) => repo.toLowerCase() === r.repo.toLowerCase()));
28046
28240
  const track = resolveReleaseTrack(project2, void 0, r.repo);
28047
28241
  const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches);
28048
- const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28242
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
28049
28243
  if (!resolved.ok || resolved.content == null) {
28050
28244
  return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.ok ? "rendered no content" : resolved.reason} \u2014 refusing an incomplete per-repo render`);
28051
28245
  }
28052
28246
  desired = resolved.content;
28053
28247
  } else if (seed.managedBlock) {
28054
28248
  const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
28055
- const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
28249
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
28056
28250
  if (!resolved.ok) {
28057
28251
  return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
28058
28252
  }
@@ -31891,8 +32085,7 @@ var LOOP_PLAYBOOKS = {
31891
32085
  { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
31892
32086
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
31893
32087
  { 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>" },
32088
+ { label: "Land to development (waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" },
31896
32089
  { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
31897
32090
  // #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
31898
32091
  { 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 +32106,14 @@ var LOOP_PLAYBOOKS = {
31913
32106
  steps: [
31914
32107
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
31915
32108
  { 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>" }
32109
+ { label: "Land the PR (merge to development \u2014 waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" }
31917
32110
  ]
31918
32111
  },
31919
32112
  "hotfix": {
31920
32113
  title: "Hotfix",
31921
32114
  steps: [
31922
32115
  { 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" },
32116
+ { label: "Merge the main-base PR (waits for checks itself)", command: "mmi-cli devops pr merge <PR-number> --squash" },
31924
32117
  { 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
32118
  ]
31926
32119
  }
@@ -32193,7 +32386,7 @@ function parseOriginRepo(remoteUrl) {
32193
32386
 
32194
32387
  // src/issue-commands.ts
32195
32388
  var import_node_fs31 = require("node:fs");
32196
- var import_node_crypto9 = require("node:crypto");
32389
+ var import_node_crypto10 = require("node:crypto");
32197
32390
 
32198
32391
  // src/issue-body.ts
32199
32392
  var import_node_os16 = require("node:os");
@@ -32723,7 +32916,7 @@ function rowIdempotencyKey(batchKey, spec) {
32723
32916
  const identity = `${spec.type}
32724
32917
  ${spec.title.trim()}
32725
32918
  ${spec.body ?? ""}`;
32726
- const hash = (0, import_node_crypto9.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
32919
+ const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
32727
32920
  return `${batchKey}:${hash}`;
32728
32921
  }
32729
32922
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -33085,7 +33278,10 @@ function extendCreateCommand(issue, batchAttach) {
33085
33278
  try {
33086
33279
  const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
33087
33280
  specs = JSON.parse(raw);
33088
- if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33281
+ if (!Array.isArray(specs)) {
33282
+ const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
33283
+ throw new Error(`batch file must contain a JSON array, but its top level is ${top}`);
33284
+ }
33089
33285
  } catch (e) {
33090
33286
  return fail(`issue create --batch: cannot read/parse ${opts.batch}: ${e.message}`);
33091
33287
  }
@@ -35768,14 +35964,14 @@ function registerStageCommands(program3) {
35768
35964
  }
35769
35965
 
35770
35966
  // src/tenant-artifact.ts
35771
- var import_node_crypto10 = require("node:crypto");
35967
+ var import_node_crypto11 = require("node:crypto");
35772
35968
  var import_node_fs35 = require("node:fs");
35773
35969
  var import_promises6 = require("node:fs/promises");
35774
35970
  var import_node_path32 = require("node:path");
35775
35971
  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
35972
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
35777
35973
  async function sha256File(path2) {
35778
- const hash = (0, import_node_crypto10.createHash)("sha256");
35974
+ const hash = (0, import_node_crypto11.createHash)("sha256");
35779
35975
  for await (const chunk of (0, import_node_fs35.createReadStream)(path2)) hash.update(chunk);
35780
35976
  return hash.digest("hex");
35781
35977
  }
@@ -35940,7 +36136,7 @@ function renderVerifySecrets(body) {
35940
36136
  // src/command-register-collaboration.ts
35941
36137
  var import_node_child_process16 = require("node:child_process");
35942
36138
  var import_node_fs43 = require("node:fs");
35943
- var import_promises8 = require("node:fs/promises");
36139
+ var import_promises7 = require("node:fs/promises");
35944
36140
 
35945
36141
  // src/session-runtime.ts
35946
36142
  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 +36155,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
35959
36155
  }
35960
36156
 
35961
36157
  // src/command-register-collaboration.ts
35962
- var import_node_path41 = require("node:path");
36158
+ var import_node_path40 = require("node:path");
35963
36159
 
35964
36160
  // src/attach-to-project.ts
35965
36161
  function boardAttachRateLimitedReceipt(resetEpochSeconds) {
@@ -36043,10 +36239,18 @@ function prHeadBehindBase(input) {
36043
36239
  function prHeadUpdateRemedy(head, base) {
36044
36240
  return `git fetch origin ${base} && git merge origin/${base} (on ${head}), resolve the conflicts, push, then rerun \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
36045
36241
  }
36242
+ var PROTECTED_PR_HEAD_BRANCHES = ["main", "master", "development", "rc"];
36243
+ function isProtectedPrHead(head) {
36244
+ return PROTECTED_PR_HEAD_BRANCHES.includes(head);
36245
+ }
36246
+ function protectedPrHeadRemedy(head, base) {
36247
+ return `PR head \`${head}\` is a train branch \u2014 updating it from \`${base}\` would merge \`${base}\` into \`${head}\`, promoting every unreleased commit. Never do that and never hand-merge on \`${head}\`. This is the pre-#6288 alignment shape: close this PR and let the train rebuild the alignment on a \`${base}\`-based branch that merges \`${head}\` into it (\`mmi-cli devops release --resume\`), then land THAT with a true merge \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
36248
+ }
36046
36249
  var PR_HEAD_UPDATE_API_READ_RETRIES = 5;
36047
36250
  var PR_HEAD_UPDATE_API_READ_DELAY_MS = 2e3;
36048
36251
  async function updatePrHeadFromBase(input) {
36049
36252
  const remedy = prHeadUpdateRemedy(input.head, input.base);
36253
+ if (isProtectedPrHead(input.head)) throw new Error(protectedPrHeadRemedy(input.head, input.base));
36050
36254
  if (input.localCheckedOut) {
36051
36255
  const from2 = (await input.git(["rev-parse", "HEAD"])).trim();
36052
36256
  await input.git(["fetch", "origin", input.base]);
@@ -36145,6 +36349,14 @@ async function runPrLand(prNumber, options, deps) {
36145
36349
  };
36146
36350
  }
36147
36351
  }
36352
+ const queue = await deps.queueMerge?.(prNumber, repo);
36353
+ if (queue) return {
36354
+ ...base,
36355
+ queue,
36356
+ status: queue.state === "merged" ? "merged" : "failed",
36357
+ mergeStatus: queue.state === "merged" ? "merged" : "failed",
36358
+ ...queue.state === "merged" ? {} : { error: queue.detail }
36359
+ };
36148
36360
  const ciPolicy = await deps.resolveCiPolicy(repo);
36149
36361
  base.ciPolicy = ciPolicy;
36150
36362
  const checksWaitError = (checksWait) => {
@@ -36762,8 +36974,8 @@ function annotateChangeMeaning(changed, policy, read) {
36762
36974
  function isMeaningfulRow(file) {
36763
36975
  return file.meaningful !== false;
36764
36976
  }
36765
- function loadPolicy(root, readFile10 = readFileOrNull2) {
36766
- const raw = readFile10((0, import_node_path33.join)(root, POLICY_FILE));
36977
+ function loadPolicy(root, readFile9 = readFileOrNull2) {
36978
+ const raw = readFile9((0, import_node_path33.join)(root, POLICY_FILE));
36767
36979
  if (raw == null) return { mandatory: [], declared: false };
36768
36980
  return parsePolicy(raw, POLICY_FILE);
36769
36981
  }
@@ -37454,9 +37666,287 @@ async function deleteMergedRemoteBranch(options) {
37454
37666
  };
37455
37667
  }
37456
37668
 
37457
- // src/post-merge-recon.ts
37669
+ // src/review-verdict.ts
37670
+ var import_node_child_process14 = require("node:child_process");
37458
37671
  var import_node_fs38 = require("node:fs");
37672
+ var import_node_os19 = require("node:os");
37459
37673
  var import_node_path35 = require("node:path");
37674
+ var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
37675
+ var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
37676
+ function isReviewVerdict(value) {
37677
+ return typeof value === "string" && REVIEW_VERDICTS.includes(value);
37678
+ }
37679
+ function renderReviewVerdictComment(input) {
37680
+ const payload = {
37681
+ v: 1,
37682
+ patch: input.patch,
37683
+ head: input.head,
37684
+ verdict: input.verdict,
37685
+ scope: input.scope,
37686
+ risk: input.risk,
37687
+ unverified: input.unverified,
37688
+ reviewer: input.reviewer
37689
+ };
37690
+ const findings = input.findings?.trim();
37691
+ return `${REVIEW_VERDICT_MARKER}
37692
+ \`\`\`json
37693
+ ${JSON.stringify(payload, null, 2)}
37694
+ \`\`\`
37695
+ ${findings ? `
37696
+ ${findings}
37697
+ ` : ""}`;
37698
+ }
37699
+ function isReviewVerdictComment(body) {
37700
+ return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
37701
+ }
37702
+ var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
37703
+ function parseReviewVerdictComment(body) {
37704
+ if (!isReviewVerdictComment(body)) return void 0;
37705
+ const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
37706
+ const fence = FENCE_RE.exec(rest);
37707
+ if (!fence) return void 0;
37708
+ let parsed;
37709
+ try {
37710
+ parsed = JSON.parse(fence[1]);
37711
+ } catch {
37712
+ return void 0;
37713
+ }
37714
+ if (!parsed || typeof parsed !== "object") return void 0;
37715
+ const p = parsed;
37716
+ if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
37717
+ if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
37718
+ if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
37719
+ const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
37720
+ return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
37721
+ }
37722
+ function latestReviewComment(comments) {
37723
+ let latest;
37724
+ for (const c of comments) {
37725
+ if (!isReviewVerdictComment(c.body)) continue;
37726
+ if (!latest || c.createdAt > latest.createdAt || c.createdAt === latest.createdAt && (c.id ?? 0) >= (latest.id ?? 0)) latest = c;
37727
+ }
37728
+ return latest;
37729
+ }
37730
+ function evaluateReviewVerdict(comments, currentPatchId) {
37731
+ const latest = latestReviewComment(comments);
37732
+ if (!latest) return { ok: false, reason: "none" };
37733
+ const payload = parseReviewVerdictComment(latest.body);
37734
+ if (!payload) return { ok: false, reason: "malformed" };
37735
+ if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
37736
+ if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
37737
+ return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
37738
+ }
37739
+ function evaluateTrustedReviewVerdict(comments, patch, head) {
37740
+ const latest = latestReviewComment(comments);
37741
+ if (!latest) return { ok: false, reason: "none" };
37742
+ const identity = { commentId: latest.id, commentCreatedAt: latest.createdAt };
37743
+ if (latest.author?.toLowerCase() !== "jervaise") return { ...identity, ok: false, reason: "untrusted-author" };
37744
+ const evaluation = evaluateReviewVerdict([latest], patch);
37745
+ if (!evaluation.ok) return { ...identity, ok: false, reason: evaluation.reason };
37746
+ if (parseReviewVerdictComment(latest.body)?.head !== head) return { ...identity, ok: false, reason: "stale-head" };
37747
+ return { ...identity, ok: true, reason: "proceed" };
37748
+ }
37749
+ async function checkPrReview(number, repo, head, deps = {
37750
+ readHead: readPrHeadSha,
37751
+ readComments: readPrIssueComments,
37752
+ computePatch: computePrPatchId
37753
+ }) {
37754
+ const base = { repo, number, head };
37755
+ if (!/^[1-9][0-9]*$/.test(number) || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[0-9a-f]{40}$/.test(head)) {
37756
+ return { ...base, ok: false, reason: "invalid-input", detail: "expected a PR number, owner/repo and exact lowercase 40-character head SHA" };
37757
+ }
37758
+ try {
37759
+ if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
37760
+ const [comments, patch] = await Promise.all([deps.readComments(number, repo), deps.computePatch(number, repo)]);
37761
+ if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
37762
+ return { ...base, patch, ...evaluateTrustedReviewVerdict(comments, patch, head) };
37763
+ } catch (e) {
37764
+ return { ...base, ok: false, reason: "unreadable", detail: e.message };
37765
+ }
37766
+ }
37767
+ function computePrPatchId(number, repo) {
37768
+ return new Promise((resolve7, reject) => {
37769
+ const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
37770
+ const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
37771
+ let out = "";
37772
+ let ghErr = "";
37773
+ let gitErr = "";
37774
+ const timer = setTimeout(() => {
37775
+ gh.kill();
37776
+ git3.kill();
37777
+ reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
37778
+ }, GC_GH_TIMEOUT_MS4);
37779
+ gh.stdout.pipe(git3.stdin);
37780
+ gh.stderr.on("data", (d) => {
37781
+ ghErr += d.toString();
37782
+ });
37783
+ git3.stderr.on("data", (d) => {
37784
+ gitErr += d.toString();
37785
+ });
37786
+ git3.stdout.on("data", (d) => {
37787
+ out += d.toString();
37788
+ });
37789
+ gh.on("error", (e) => {
37790
+ clearTimeout(timer);
37791
+ reject(e);
37792
+ });
37793
+ git3.on("error", (e) => {
37794
+ clearTimeout(timer);
37795
+ reject(e);
37796
+ });
37797
+ let ghCode;
37798
+ let gitCode;
37799
+ const finish = () => {
37800
+ if (ghCode === void 0 || gitCode === void 0) return;
37801
+ clearTimeout(timer);
37802
+ if (ghCode !== 0) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
37803
+ if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
37804
+ const id = out.trim().split(/\s+/)[0];
37805
+ 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"})`));
37806
+ resolve7(id);
37807
+ };
37808
+ gh.on("close", (code) => {
37809
+ ghCode = code;
37810
+ finish();
37811
+ });
37812
+ git3.on("close", (code) => {
37813
+ gitCode = code;
37814
+ finish();
37815
+ });
37816
+ git3.stdin.on("error", (e) => {
37817
+ clearTimeout(timer);
37818
+ gh.kill();
37819
+ git3.kill();
37820
+ reject(e);
37821
+ });
37822
+ });
37823
+ }
37824
+ async function readPrHeadSha(number, repo) {
37825
+ const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
37826
+ const sha = stdout.trim();
37827
+ if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
37828
+ return sha;
37829
+ }
37830
+ async function readPrIssueComments(number, repo) {
37831
+ 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 });
37832
+ const comments = parseNdjsonLines(stdout);
37833
+ 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");
37834
+ return comments;
37835
+ }
37836
+ async function postPrCommentFromFile(number, repo, body) {
37837
+ const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
37838
+ const path2 = (0, import_node_path35.join)(dir, "body.md");
37839
+ try {
37840
+ (0, import_node_fs38.writeFileSync)(path2, body, "utf8");
37841
+ const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
37842
+ return stdout.trim();
37843
+ } finally {
37844
+ try {
37845
+ (0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
37846
+ } catch {
37847
+ }
37848
+ }
37849
+ }
37850
+
37851
+ // src/pr-mergify-queue.ts
37852
+ var MERGIFY_PILOT_REPO = "mutmutco/Jerv-JervCode";
37853
+ var MERGIFY_PILOT_VARIABLE = "JERV_BATCH_QUEUE_PILOT";
37854
+ var MERGIFY_READY_LABEL = "ready-to-merge";
37855
+ async function ghJson(args) {
37856
+ return JSON.parse((await execFileP("gh", ["api", ...args], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
37857
+ }
37858
+ async function usesMergifyPilot(repo, base, readVariable = async () => ghJson([`repos/${repo}/actions/variables/${MERGIFY_PILOT_VARIABLE}`])) {
37859
+ if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase() || base !== "development") return false;
37860
+ const variable = await readVariable();
37861
+ if (variable?.value === "true") return true;
37862
+ if (variable?.value === "false") return false;
37863
+ throw new Error(`Mergify pilot activation is missing or malformed on ${repo}; refusing native merge`);
37864
+ }
37865
+ function queueCheckState(checks, head) {
37866
+ 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];
37867
+ if (!check) return "refused";
37868
+ if (check.conclusion && !["success", "neutral"].includes(check.conclusion)) return "refused";
37869
+ if (["In merge queue", "Running merge queue checks"].includes(check.output?.title ?? "")) return "pending";
37870
+ return "requested";
37871
+ }
37872
+ async function requestMergifyMerge(number, repo, guardedText, expectedHead, deps = {
37873
+ readPull: () => ghJson([`repos/${repo}/pulls/${number}`]),
37874
+ readChecks: async (head) => parseNdjsonLines((await execFileP("gh", [
37875
+ "api",
37876
+ "--paginate",
37877
+ `repos/${repo}/commits/${head}/check-runs?per_page=100`,
37878
+ "--jq",
37879
+ ".check_runs[]"
37880
+ ], { timeout: GC_GH_TIMEOUT_MS4 })).stdout),
37881
+ addLabel: async () => {
37882
+ await ghJson([`repos/${repo}/issues/${number}/labels`, "--method", "POST", "-f", `labels[]=${MERGIFY_READY_LABEL}`]);
37883
+ },
37884
+ review: (head) => checkPrReview(number, repo, head),
37885
+ now: () => Date.now(),
37886
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
37887
+ }, timeoutMs = 6e5) {
37888
+ let head = expectedHead ?? "";
37889
+ let state = "refused";
37890
+ const receipt = (detail) => ({ provider: "mergify", state, head, detail });
37891
+ try {
37892
+ if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase()) return receipt("repository is outside the Mergify pilot");
37893
+ const initial = await deps.readPull();
37894
+ head = expectedHead ?? initial.head.sha;
37895
+ const sameTarget = (pr) => /^[0-9a-f]{40}$/.test(head) && pr.head.sha === head && pr.base.ref === "development";
37896
+ if (!sameTarget(initial)) return receipt("PR head or target base changed");
37897
+ if (initial.merged === true) {
37898
+ state = "merged";
37899
+ return receipt("GitHub confirms the PR merged");
37900
+ }
37901
+ const validate = (pr) => pr.state === "open" && sameTarget(pr) && `${pr.title}
37902
+ ${pr.body ?? ""}` === guardedText;
37903
+ if (!validate(initial)) return receipt("PR state, head, base or guarded title/body changed");
37904
+ const initialState = queueCheckState(await deps.readChecks(head), head);
37905
+ if (initialState === "refused") return receipt("trusted current-head Mergify check is absent or failed");
37906
+ if (!(await deps.review(head)).ok) return receipt("current trusted review verdict does not permit queue admission");
37907
+ if (!validate(await deps.readPull())) return receipt("PR changed before queue request");
37908
+ let mutationDetail = "";
37909
+ if (!initial.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
37910
+ try {
37911
+ await deps.addLabel();
37912
+ } catch {
37913
+ mutationDetail = "Label response was ambiguous; no replay was attempted. ";
37914
+ }
37915
+ }
37916
+ state = "requested";
37917
+ const deadline = deps.now() + timeoutMs;
37918
+ while (true) {
37919
+ const pr = await deps.readPull();
37920
+ if (!sameTarget(pr)) {
37921
+ state = "refused";
37922
+ return receipt("PR head or target base changed while queued");
37923
+ }
37924
+ if (pr.merged === true) {
37925
+ state = "merged";
37926
+ return receipt("GitHub confirms the PR merged");
37927
+ }
37928
+ if (!validate(pr)) {
37929
+ state = "refused";
37930
+ return receipt("PR state, head, base or guarded title/body changed while queued");
37931
+ }
37932
+ state = queueCheckState(await deps.readChecks(head), head);
37933
+ if (state === "refused") return receipt("trusted current-head Mergify check is absent or failed");
37934
+ if (!pr.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
37935
+ state = "refused";
37936
+ return receipt(`${mutationDetail}Queue request label is absent`);
37937
+ }
37938
+ 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`);
37939
+ await deps.sleep(3e4);
37940
+ }
37941
+ } catch {
37942
+ state = "refused";
37943
+ return receipt("Mergify or GitHub state is unreadable; no native merge was attempted");
37944
+ }
37945
+ }
37946
+
37947
+ // src/post-merge-recon.ts
37948
+ var import_node_fs39 = require("node:fs");
37949
+ var import_node_path36 = require("node:path");
37460
37950
 
37461
37951
  // src/cross-repo-filing-issue.ts
37462
37952
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
@@ -37622,16 +38112,16 @@ function buildPostMergeReconRecovery(input) {
37622
38112
  }
37623
38113
  function writePostMergeReconRecovery(cwd, recovery) {
37624
38114
  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)}
38115
+ (0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
38116
+ (0, import_node_fs39.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
37627
38117
  `, "utf8");
37628
38118
  return path2;
37629
38119
  }
37630
38120
  function clearPostMergeReconRecovery(cwd, repo, pr) {
37631
38121
  const path2 = postMergeReconStatePath(cwd, repo, pr);
37632
- if (!(0, import_node_fs38.existsSync)(path2)) return;
38122
+ if (!(0, import_node_fs39.existsSync)(path2)) return;
37633
38123
  try {
37634
- (0, import_node_fs38.unlinkSync)(path2);
38124
+ (0, import_node_fs39.unlinkSync)(path2);
37635
38125
  } catch {
37636
38126
  }
37637
38127
  }
@@ -37661,184 +38151,6 @@ function postMergeReconWarnings(input) {
37661
38151
  return lines2;
37662
38152
  }
37663
38153
 
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
38154
  // src/pr-create-docs-check.ts
37843
38155
  var import_node_child_process15 = require("node:child_process");
37844
38156
  var GIT_TIMEOUT_MS2 = 15e3;
@@ -37960,120 +38272,6 @@ async function checkDocsIndexAtHead(opts, deps) {
37960
38272
  };
37961
38273
  }
37962
38274
 
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
38275
  // src/pr-create-claim-guard.ts
38078
38276
  var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
38079
38277
  function withRateLimitRetry(client, seams = {}) {
@@ -38132,11 +38330,68 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
38132
38330
 
38133
38331
  // src/worktree-merge-cleanup.ts
38134
38332
  var import_node_fs42 = require("node:fs");
38135
- var import_node_path40 = require("node:path");
38333
+ var import_node_path39 = require("node:path");
38334
+
38335
+ // src/jervcode-node-modules-cleanup.ts
38336
+ var import_node_fs40 = require("node:fs");
38337
+ var import_node_os20 = require("node:os");
38338
+ var import_node_path37 = require("node:path");
38339
+ var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
38340
+ var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
38341
+ var POSIX_NAMES2 = ["jervcode"];
38342
+ var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
38343
+ function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
38344
+ const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
38345
+ const out = [];
38346
+ for (const dir of jervCliCandidateDirs(env, home, platform2)) {
38347
+ for (const name of names) out.push((0, import_node_path37.join)(dir, name));
38348
+ }
38349
+ return out;
38350
+ }
38351
+ function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
38352
+ for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
38353
+ if (exists(candidate)) return candidate;
38354
+ }
38355
+ return void 0;
38356
+ }
38357
+ function jervcodeExecFileArgs(args, opts = {}) {
38358
+ const platform2 = opts.platform ?? process.platform;
38359
+ const exists = opts.exists ?? import_node_fs40.existsSync;
38360
+ const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
38361
+ if (resolved) {
38362
+ const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
38363
+ if (exists(entry)) {
38364
+ return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
38365
+ }
38366
+ }
38367
+ const bin = resolved ?? "jervcode";
38368
+ if (platform2 === "win32") {
38369
+ return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
38370
+ }
38371
+ return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
38372
+ }
38373
+ async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
38374
+ const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
38375
+ const env = opts.env ?? process.env;
38376
+ const exec = opts.exec ?? execFileP;
38377
+ const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
38378
+ const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
38379
+ const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
38380
+ try {
38381
+ await exec(plan.file, plan.args, execOptions);
38382
+ return { ok: true };
38383
+ } catch (e) {
38384
+ const err = e;
38385
+ const base = formatJervCliSpawnFailure(err, plan, candidates);
38386
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
38387
+ const detail = stderr.split("\n")[0] || base;
38388
+ return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
38389
+ }
38390
+ }
38136
38391
 
38137
38392
  // src/worktree-evidence-archive.ts
38138
38393
  var import_node_fs41 = require("node:fs");
38139
- var import_node_path39 = require("node:path");
38394
+ var import_node_path38 = require("node:path");
38140
38395
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
38141
38396
  function sanitizeArchiveSegment(value, max = 80) {
38142
38397
  const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -38168,7 +38423,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
38168
38423
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
38169
38424
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
38170
38425
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
38171
- const source = (0, import_node_path39.join)(args.worktreePath, ".jerv");
38426
+ const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
38172
38427
  if (!exists(source)) return { status: "absent" };
38173
38428
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
38174
38429
  const runScope = resolveArtifactRunScope(env);
@@ -38176,7 +38431,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
38176
38431
  const stamp = now().toISOString().replace(/[:.]/g, "-");
38177
38432
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
38178
38433
  try {
38179
- mkdirp((0, import_node_path39.dirname)(dest));
38434
+ mkdirp((0, import_node_path38.dirname)(dest));
38180
38435
  copyDir(source, dest);
38181
38436
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
38182
38437
  return { status: "archived", path: dest, runScope };
@@ -38192,7 +38447,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
38192
38447
  const exists = deps.exists ?? import_node_fs41.existsSync;
38193
38448
  const stat4 = deps.stat ?? defaultStat;
38194
38449
  const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
38195
- const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
38450
+ const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
38196
38451
  if (!exists(tmpRoot)) return [];
38197
38452
  const entries = [];
38198
38453
  const walk2 = (dir) => {
@@ -38203,14 +38458,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
38203
38458
  return;
38204
38459
  }
38205
38460
  for (const name of names) {
38206
- const full = (0, import_node_path39.join)(dir, name);
38461
+ const full = (0, import_node_path38.join)(dir, name);
38207
38462
  let st;
38208
38463
  try {
38209
38464
  st = stat4(full);
38210
38465
  } catch {
38211
38466
  continue;
38212
38467
  }
38213
- const relPath = (0, import_node_path39.relative)(worktreePath, full).replace(/\\/g, "/");
38468
+ const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
38214
38469
  if (st.isDirectory()) {
38215
38470
  if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
38216
38471
  walk2(full);
@@ -38235,7 +38490,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
38235
38490
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
38236
38491
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
38237
38492
  const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
38238
- const source = (0, import_node_path39.join)(args.worktreePath, "tmp");
38493
+ const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
38239
38494
  if (!scanned.length) return { status: "absent" };
38240
38495
  if (!exists(source)) return { status: "absent" };
38241
38496
  const runScope = resolveArtifactRunScope(env);
@@ -38243,7 +38498,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
38243
38498
  const stamp = now().toISOString().replace(/[:.]/g, "-");
38244
38499
  const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
38245
38500
  try {
38246
- mkdirp((0, import_node_path39.dirname)(dest));
38501
+ mkdirp((0, import_node_path38.dirname)(dest));
38247
38502
  copyDir(source, dest);
38248
38503
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
38249
38504
  const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
@@ -38312,7 +38567,7 @@ function normPath2(p) {
38312
38567
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
38313
38568
  }
38314
38569
  function unlinkNodeModulesJunction(wtPath) {
38315
- const nm = (0, import_node_path40.join)(wtPath, "node_modules");
38570
+ const nm = (0, import_node_path39.join)(wtPath, "node_modules");
38316
38571
  try {
38317
38572
  if ((0, import_node_fs42.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs42.rmdirSync)(nm);
38318
38573
  return { ok: true };
@@ -38449,7 +38704,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
38449
38704
  }
38450
38705
  async function listNestedIgnoredNodeModules(wtPath, execGit) {
38451
38706
  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)));
38707
+ 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
38708
  }
38454
38709
  function safeRemoveTree(path2) {
38455
38710
  const stat4 = (0, import_node_fs42.lstatSync)(path2);
@@ -38462,7 +38717,7 @@ function safeRemoveTree(path2) {
38462
38717
  return;
38463
38718
  }
38464
38719
  if (stat4.isDirectory()) {
38465
- for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
38720
+ for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
38466
38721
  (0, import_node_fs42.rmdirSync)(path2);
38467
38722
  return;
38468
38723
  }
@@ -38505,7 +38760,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
38505
38760
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
38506
38761
  }
38507
38762
  for (const entry of entries) {
38508
- const child2 = (0, import_node_path40.join)(dir, entry.name);
38763
+ const child2 = (0, import_node_path39.join)(dir, entry.name);
38509
38764
  if (entry.isSymbolicLink()) {
38510
38765
  let target = "";
38511
38766
  try {
@@ -38539,7 +38794,7 @@ async function describePreCleanFailure(wtPath, execGit, error) {
38539
38794
  const more = remaining.length > 10 ? ` (+${remaining.length - 10} more)` : "";
38540
38795
  const quote = (path2) => path2.replace(/'/g, "''");
38541
38796
  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;
38797
+ 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
38798
  return { ok: false, error: `${error}; remaining ignored paths: ${shown}${more}`, ...remediation ? { remediation } : {} };
38544
38799
  }
38545
38800
  function formatWorktreeRemovalFailureDetail(options) {
@@ -38833,7 +39088,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
38833
39088
  const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
38834
39089
  if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
38835
39090
  unlinkedReparsePoints.push(...preHelperGuard.unlinked);
38836
- if (pathExists((0, import_node_path40.join)(wtPath, "node_modules"))) {
39091
+ if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
38837
39092
  const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
38838
39093
  if (!nmRemoved.ok) {
38839
39094
  report.worktree = {
@@ -39000,10 +39255,10 @@ function argvWantsJson2() {
39000
39255
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
39001
39256
  }
39002
39257
  function hubRoot() {
39003
- const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
39258
+ const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
39004
39259
  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();
39260
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
39261
+ if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
39007
39262
  return null;
39008
39263
  }
39009
39264
  function ciAuditDeps() {
@@ -39015,12 +39270,12 @@ function ciAuditDeps() {
39015
39270
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
39016
39271
  readSeedFile: (path2) => {
39017
39272
  if (!root) return null;
39018
- const fullPath = (0, import_node_path41.join)(root, path2);
39273
+ const fullPath = (0, import_node_path40.join)(root, path2);
39019
39274
  return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
39020
39275
  }
39021
39276
  };
39022
39277
  }
39023
- async function ghJson(args, timeout = 1e4) {
39278
+ async function ghJson2(args, timeout = 1e4) {
39024
39279
  const { stdout } = await execFileP("gh", args, { timeout });
39025
39280
  return JSON.parse(stdout);
39026
39281
  }
@@ -39166,7 +39421,7 @@ function registerCollaborationCommands(program3) {
39166
39421
  try {
39167
39422
  title = await resolveIssueTitle(
39168
39423
  { title: opts.title, titleFile: opts.titleFile },
39169
- { readFile: import_promises8.readFile, readStdin }
39424
+ { readFile: import_promises7.readFile, readStdin }
39170
39425
  );
39171
39426
  } catch (e) {
39172
39427
  return fail(
@@ -39208,8 +39463,8 @@ function registerCollaborationCommands(program3) {
39208
39463
  let surfaceFlagLabel;
39209
39464
  try {
39210
39465
  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 });
39466
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39467
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39213
39468
  if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
39214
39469
  priority = resolveCreatePriority(o.priority, "issue create");
39215
39470
  extraLabels = [...o.label ?? []];
@@ -39307,7 +39562,7 @@ function registerCollaborationCommands(program3) {
39307
39562
  async function readParentField(number, repo) {
39308
39563
  let payload;
39309
39564
  try {
39310
- payload = await ghJson(["api", `repos/${repo}/issues/${number}`]);
39565
+ payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
39311
39566
  } catch (e) {
39312
39567
  const err = e;
39313
39568
  return { parentReadError: (err.stderr || err.message || String(e)).trim() };
@@ -39339,7 +39594,7 @@ function registerCollaborationCommands(program3) {
39339
39594
  emit(data2, await readParentField(n, repo));
39340
39595
  return;
39341
39596
  }
39342
- const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
39597
+ const data = await ghJson2(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
39343
39598
  emit(data, await readParentField(n, repo));
39344
39599
  } catch (e) {
39345
39600
  const err = e;
@@ -39354,7 +39609,7 @@ function registerCollaborationCommands(program3) {
39354
39609
  const repo = await resolveRepo(o.repo);
39355
39610
  if (!repo) return fail("issue discover-related: could not resolve repo");
39356
39611
  try {
39357
- const issues = await ghJson([
39612
+ const issues = await ghJson2([
39358
39613
  "issue",
39359
39614
  "list",
39360
39615
  "--repo",
@@ -39369,7 +39624,7 @@ function registerCollaborationCommands(program3) {
39369
39624
  const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
39370
39625
  if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
39371
39626
  if (!candidates.length) return;
39372
- const viewed = await ghJson([
39627
+ const viewed = await ghJson2([
39373
39628
  "issue",
39374
39629
  "view",
39375
39630
  String(number),
@@ -39417,7 +39672,7 @@ function registerCollaborationCommands(program3) {
39417
39672
  }
39418
39673
  let body;
39419
39674
  try {
39420
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
39675
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39421
39676
  } catch (e) {
39422
39677
  return fail(`issue comment: ${e.message}`);
39423
39678
  }
@@ -39443,7 +39698,7 @@ function registerCollaborationCommands(program3) {
39443
39698
  const checked = o.off !== true;
39444
39699
  let body;
39445
39700
  try {
39446
- const viewed = await ghJson(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
39701
+ const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
39447
39702
  body = viewed.body ?? "";
39448
39703
  } catch (e) {
39449
39704
  return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
@@ -39477,8 +39732,8 @@ ${list}`);
39477
39732
  let title;
39478
39733
  const sourceRepo = o.repo ?? await resolveRepo(void 0);
39479
39734
  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 });
39735
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39736
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39482
39737
  priority = resolveCreatePriority(o.priority, "report");
39483
39738
  if (!ISSUE_TYPES.includes(o.type)) {
39484
39739
  throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
@@ -39533,8 +39788,8 @@ ${list}`);
39533
39788
  let args;
39534
39789
  try {
39535
39790
  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 });
39791
+ rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39792
+ const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39538
39793
  title = buildSkillLessonTitle(skill, rawTitle);
39539
39794
  priority = resolveCreatePriority(o.priority, "skill-lesson");
39540
39795
  body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
@@ -39545,7 +39800,7 @@ ${list}`);
39545
39800
  if (!o.force) {
39546
39801
  let openLessons = [];
39547
39802
  try {
39548
- openLessons = await ghJson([
39803
+ openLessons = await ghJson2([
39549
39804
  "issue",
39550
39805
  "list",
39551
39806
  "--repo",
@@ -39591,12 +39846,12 @@ ${list}`);
39591
39846
  console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
39592
39847
  });
39593
39848
  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) => {
39849
+ 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
39850
  let body;
39596
39851
  let title;
39597
39852
  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 });
39853
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
39854
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
39600
39855
  } catch (e) {
39601
39856
  return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
39602
39857
  }
@@ -39616,9 +39871,6 @@ ${list}`);
39616
39871
  }
39617
39872
  const claimRefusal = await prCreateClaimRefusal(body, o.repo);
39618
39873
  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
39874
  const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
39623
39875
  if (isGhCreateRateLimited(created)) {
39624
39876
  console.log(JSON.stringify(created));
@@ -39654,7 +39906,7 @@ ${list}`);
39654
39906
  console.log(JSON.stringify(data2));
39655
39907
  return;
39656
39908
  }
39657
- const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json", effective]);
39909
+ const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
39658
39910
  console.log(JSON.stringify(data));
39659
39911
  } catch (e) {
39660
39912
  const err = e;
@@ -39662,11 +39914,11 @@ ${list}`);
39662
39914
  }
39663
39915
  });
39664
39916
  async function listCiWorkflowPaths(cwd = process.cwd()) {
39665
- const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
39917
+ const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
39666
39918
  if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
39667
39919
  return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
39668
39920
  try {
39669
- return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
39921
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
39670
39922
  } catch {
39671
39923
  return true;
39672
39924
  }
@@ -39854,7 +40106,12 @@ ${list}`);
39854
40106
  if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
39855
40107
  if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
39856
40108
  });
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) => {
40109
+ 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) => {
40110
+ const result = await checkPrReview(number, o.repo, o.head);
40111
+ console.log(o.json ? JSON.stringify(result) : `pr review-check: ${result.reason}`);
40112
+ if (!result.ok) process.exitCode = 1;
40113
+ });
40114
+ 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
40115
  if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
39859
40116
  const verdict = o.verdict;
39860
40117
  const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
@@ -39896,7 +40153,7 @@ ${list}`);
39896
40153
  }
39897
40154
  class PrHeadBehindBaseError extends Error {
39898
40155
  }
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) => {
40156
+ 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
40157
  if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
39901
40158
  try {
39902
40159
  const parsed = parseIssueRef(number, o.repo);
@@ -39914,7 +40171,7 @@ ${list}`);
39914
40171
  await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
39915
40172
  async (n) => {
39916
40173
  if (!landRepoForGuard) return void 0;
39917
- const viewed = await ghJson(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
40174
+ const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
39918
40175
  return typeof viewed.state === "string" ? viewed.state : void 0;
39919
40176
  }
39920
40177
  );
@@ -39931,16 +40188,15 @@ ${list}`);
39931
40188
  return;
39932
40189
  }
39933
40190
  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
40191
  const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
40192
+ queueMerge: async (prNumber, repo) => {
40193
+ const meta = await ghJson2(["pr", "view", prNumber, "--repo", repo, "--json", "baseRefName,headRefOid"]);
40194
+ if (!await usesMergifyPilot(repo, meta.baseRefName)) return void 0;
40195
+ if (!landClosingGuardInput) throw new Error("pr land: Mergify requires a readable closing-keyword guard");
40196
+ const guard = evaluateClosingGuard(landClosingGuardInput, { force: o.force, context: "pr land", squashBodyText: landClosingGuardInput.text });
40197
+ if (guard.blocked) throw new Error(guard.message);
40198
+ return requestMergifyMerge(prNumber, repo, landClosingGuardInput.text, meta.headRefOid);
40199
+ },
39944
40200
  resolveRepo: async (prNumber, repoOpt) => {
39945
40201
  const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
39946
40202
  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 +40331,7 @@ ${list}`);
40075
40331
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
40076
40332
  if (result.status === "failed") process.exitCode = 1;
40077
40333
  });
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) => {
40334
+ 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
40335
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
40080
40336
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
40081
40337
  if (o.disableAuto) {
@@ -40097,20 +40353,24 @@ ${list}`);
40097
40353
  const headRef = prMeta.head;
40098
40354
  const baseRef = prMeta.base;
40099
40355
  const headRefOid = (prMeta.oid ?? "").trim() || void 0;
40356
+ if (!repoForPostCleanup) throw new Error("pr merge: cannot resolve target repository");
40357
+ const mergifyPilot = await usesMergifyPilot(repoForPostCleanup, baseRef);
40358
+ 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
40359
  const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
40101
40360
  const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
40102
40361
  const closingGuardInput = await withAlreadyClosedCommitTargets(
40103
40362
  await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
40104
40363
  async (n) => {
40105
40364
  if (!repoForPostCleanup) return void 0;
40106
- const viewed = await ghJson(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
40365
+ const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
40107
40366
  return typeof viewed.state === "string" ? viewed.state : void 0;
40108
40367
  }
40109
40368
  );
40110
40369
  if (o.squashBodyFile && method !== "--squash") {
40111
40370
  throw new Error("pr merge: --squash-body-file applies only to squash merges");
40112
40371
  }
40113
- const mergeSquashBody = squashBodyTextForMerge(
40372
+ if (mergifyPilot && !closingGuardInput) throw new Error("pr merge: Mergify requires a readable closing-keyword guard");
40373
+ const mergeSquashBody = mergifyPilot ? closingGuardInput.text : squashBodyTextForMerge(
40114
40374
  closingGuardInput,
40115
40375
  method === "--squash",
40116
40376
  o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
@@ -40127,15 +40387,6 @@ ${list}`);
40127
40387
  return;
40128
40388
  }
40129
40389
  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
40390
  if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
40140
40391
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
40141
40392
  const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
@@ -40154,7 +40405,7 @@ ${list}`);
40154
40405
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
40155
40406
  let foreignCheckout;
40156
40407
  if (foreignCwd) {
40157
- const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
40408
+ const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
40158
40409
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
40159
40410
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
40160
40411
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -40217,7 +40468,7 @@ ${list}`);
40217
40468
  }
40218
40469
  return true;
40219
40470
  };
40220
- if (o.wait && !await runWaitGate()) return;
40471
+ if (!mergifyPilot && o.wait && !await runWaitGate()) return;
40221
40472
  if (ciPolicy.policy === "no-ci") {
40222
40473
  const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
40223
40474
  if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
@@ -40226,7 +40477,7 @@ ${list}`);
40226
40477
  const remoteBefore = await remoteBranchExists2(headRef, { remote });
40227
40478
  let upgradedToAuto = false;
40228
40479
  let remoteNotAttemptedReason = "preserved-delayed-cleanup";
40229
- const overrideBody = mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
40480
+ const overrideBody = mergifyPilot ? void 0 : mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
40230
40481
  number,
40231
40482
  repoArgs,
40232
40483
  async (a, t) => (await execFileP("gh", a, { timeout: t })).stdout,
@@ -40306,7 +40557,15 @@ ${list}`);
40306
40557
  });
40307
40558
  try {
40308
40559
  try {
40309
- await mergeOnce();
40560
+ if (mergifyPilot) {
40561
+ const queue = await requestMergifyMerge(number, repoForPostCleanup, closingGuardInput.text, headRefOid);
40562
+ if (queue.state !== "merged") {
40563
+ console.log(JSON.stringify({ mergeStatus: "failed", pr: number, repo: repoForPostCleanup, queue }));
40564
+ process.exitCode = queue.state === "refused" ? 1 : PR_CHECKS_TIMEOUT_EXIT_CODE;
40565
+ return;
40566
+ }
40567
+ remoteNotAttemptedReason = "pr-already-merged";
40568
+ } else await mergeOnce();
40310
40569
  } catch (e) {
40311
40570
  if (!(e instanceof PrHeadBehindBaseError)) throw e;
40312
40571
  const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
@@ -40524,7 +40783,7 @@ async function resolveWhoami(deps) {
40524
40783
  }
40525
40784
 
40526
40785
  // src/command-register-developer.ts
40527
- var import_node_path46 = require("node:path");
40786
+ var import_node_path45 = require("node:path");
40528
40787
 
40529
40788
  // src/wave-land.ts
40530
40789
  function planWaveLand(prs) {
@@ -40778,26 +41037,26 @@ ${SSH_RECIPE_AGENT_NOTE}`);
40778
41037
 
40779
41038
  // src/dist-drift.ts
40780
41039
  var import_node_child_process17 = require("node:child_process");
40781
- var import_node_crypto12 = require("node:crypto");
41040
+ var import_node_crypto13 = require("node:crypto");
40782
41041
  var import_node_fs46 = require("node:fs");
40783
41042
  var import_node_os21 = require("node:os");
40784
- var import_node_path43 = require("node:path");
41043
+ var import_node_path42 = require("node:path");
40785
41044
 
40786
41045
  // ../scripts/distribution-digest.mjs
40787
- var import_node_crypto11 = require("node:crypto");
41046
+ var import_node_crypto12 = require("node:crypto");
40788
41047
  var import_node_fs45 = require("node:fs");
40789
- var import_node_path42 = require("node:path");
41048
+ var import_node_path41 = require("node:path");
40790
41049
  var slash = (value) => value.replaceAll("\\", "/");
40791
41050
  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}`)) {
41051
+ const absoluteRoot = (0, import_node_path41.resolve)(root);
41052
+ const target = (0, import_node_path41.resolve)(root, declaredPath);
41053
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
40795
41054
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
40796
41055
  }
40797
41056
  return target;
40798
41057
  }
40799
41058
  function digestFiles(files) {
40800
- const hash = (0, import_node_crypto11.createHash)("sha256");
41059
+ const hash = (0, import_node_crypto12.createHash)("sha256");
40801
41060
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
40802
41061
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
40803
41062
  hash.update(file.relative, "utf8");
@@ -40827,7 +41086,7 @@ var DIST_ARTIFACTS = [
40827
41086
  ];
40828
41087
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
40829
41088
  var ABSENT = "absent";
40830
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
41089
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto13.createHash)("sha256").update(bytes).digest("hex")}`;
40831
41090
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
40832
41091
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
40833
41092
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
@@ -40905,7 +41164,7 @@ function walkFiles(root) {
40905
41164
  const files = [];
40906
41165
  const walk2 = (directory) => {
40907
41166
  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);
41167
+ const child2 = (0, import_node_path42.join)(directory, entry.name);
40909
41168
  if (entry.isDirectory()) walk2(child2);
40910
41169
  else files.push(child2);
40911
41170
  }
@@ -40915,10 +41174,10 @@ function walkFiles(root) {
40915
41174
  }
40916
41175
  function bomPathFor(root) {
40917
41176
  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");
41177
+ const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
41178
+ return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
40920
41179
  } catch {
40921
- return (0, import_node_path43.join)(root, "distribution-bom.json");
41180
+ return (0, import_node_path42.join)(root, "distribution-bom.json");
40922
41181
  }
40923
41182
  }
40924
41183
  function rebuildTo(packageRoot, outDir) {
@@ -40931,28 +41190,28 @@ function rebuildTo(packageRoot, outDir) {
40931
41190
  });
40932
41191
  }
40933
41192
  function runDistStatus(root) {
40934
- const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
41193
+ const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
40935
41194
  let overlayCount = 0;
40936
41195
  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);
41196
+ const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
41197
+ const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
41198
+ rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
41199
+ rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
40941
41200
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
40942
41201
  const rebuilt = (path2) => {
40943
41202
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
40944
- return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
41203
+ return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
40945
41204
  };
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("\\", "/")}`);
41205
+ const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
41206
+ const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
41207
+ const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
41208
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
40950
41209
  const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
40951
41210
  const digest = (entries) => {
40952
- const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
41211
+ const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
40953
41212
  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 });
41213
+ const target = (0, import_node_path42.join)(overlay, entry.path);
41214
+ (0, import_node_fs46.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
40956
41215
  (0, import_node_fs46.writeFileSync)(target, entry.bytes);
40957
41216
  }
40958
41217
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
@@ -41030,8 +41289,8 @@ function registerEdgeCommands(program3) {
41030
41289
  }
41031
41290
 
41032
41291
  // src/schedules-lift-command.ts
41033
- var import_promises9 = require("node:fs/promises");
41034
- var import_node_path44 = require("node:path");
41292
+ var import_promises8 = require("node:fs/promises");
41293
+ var import_node_path43 = require("node:path");
41035
41294
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
41036
41295
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
41037
41296
  var SchedulesLiftUsageError = class extends Error {
@@ -41051,14 +41310,14 @@ var RegistryUnreachableError = class extends Error {
41051
41310
  async function readWorkflowFiles(dir) {
41052
41311
  let names;
41053
41312
  try {
41054
- names = await (0, import_promises9.readdir)(dir);
41313
+ names = await (0, import_promises8.readdir)(dir);
41055
41314
  } catch {
41056
41315
  return [];
41057
41316
  }
41058
41317
  const files = [];
41059
41318
  for (const name of names.sort()) {
41060
41319
  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") });
41320
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
41062
41321
  }
41063
41322
  return files;
41064
41323
  }
@@ -41142,7 +41401,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
41142
41401
  // src/spawn-policy-core.ts
41143
41402
  var import_node_child_process18 = require("node:child_process");
41144
41403
  var import_node_fs47 = require("node:fs");
41145
- var import_node_path45 = require("node:path");
41404
+ var import_node_path44 = require("node:path");
41146
41405
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
41147
41406
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
41148
41407
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -41228,7 +41487,7 @@ function runSpawnPolicy(root) {
41228
41487
  for (const file of files) {
41229
41488
  let raw;
41230
41489
  try {
41231
- raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
41490
+ raw = (0, import_node_fs47.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
41232
41491
  } catch {
41233
41492
  continue;
41234
41493
  }
@@ -41248,7 +41507,7 @@ function runSpawnPolicy(root) {
41248
41507
  function registerDeveloperCommands(program3) {
41249
41508
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
41250
41509
  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");
41510
+ const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
41252
41511
  const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
41253
41512
  const plan = planManagedGitignore(current);
41254
41513
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -41508,10 +41767,10 @@ function registerDeveloperCommands(program3) {
41508
41767
  }
41509
41768
 
41510
41769
  // src/command-register-train-operations.ts
41511
- var import_promises11 = require("node:fs/promises");
41770
+ var import_promises10 = require("node:fs/promises");
41512
41771
 
41513
41772
  // src/hotfix-apply.ts
41514
- var import_promises10 = require("node:fs/promises");
41773
+ var import_promises9 = require("node:fs/promises");
41515
41774
 
41516
41775
  // src/slack-alert.ts
41517
41776
  var SSM_REGION = "eu-central-1";
@@ -42295,7 +42554,7 @@ async function runHotfixRelease(deps, versionInput, options = {}, doctor = runTr
42295
42554
  }
42296
42555
  let lines2;
42297
42556
  try {
42298
- lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises10.readFile)(p, "utf8")))(options.announceSummaryFile));
42557
+ lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises9.readFile)(p, "utf8")))(options.announceSummaryFile));
42299
42558
  } catch (e) {
42300
42559
  throw new Error(`could not read --announce-summary-file ${options.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
42301
42560
  }
@@ -42854,7 +43113,7 @@ function checkHotfixCarries(options) {
42854
43113
 
42855
43114
  // src/train-commands.ts
42856
43115
  var import_node_fs49 = require("node:fs");
42857
- var import_node_path47 = require("node:path");
43116
+ var import_node_path46 = require("node:path");
42858
43117
  var INVOKED_ARGV = process.argv.slice(2);
42859
43118
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
42860
43119
  function resolveReleaseBumpIntent(raw) {
@@ -42866,7 +43125,7 @@ function resolveReleaseBumpIntent(raw) {
42866
43125
  }
42867
43126
  function readRepoVersion() {
42868
43127
  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;
43128
+ return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
42870
43129
  } catch {
42871
43130
  return void 0;
42872
43131
  }
@@ -43029,8 +43288,8 @@ function trainApplyDeps() {
43029
43288
  // Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
43030
43289
  announce: (args) => announceRelease({
43031
43290
  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)
43291
+ readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
43292
+ removeFile: (path2) => (0, import_promises10.unlink)(path2)
43034
43293
  }, args),
43035
43294
  // #4713 (I/O-boundary census): `null` used to mean BOTH "this project configures no edge domains"
43036
43295
  // (a real answer) and "the registry read missed" — so a release verdict printed an environments block
@@ -43416,7 +43675,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
43416
43675
  }
43417
43676
  let summaryLines;
43418
43677
  try {
43419
- summaryLines = summaryFileLines(await (0, import_promises11.readFile)(o.announceSummaryFile, "utf8"));
43678
+ summaryLines = summaryFileLines(await (0, import_promises10.readFile)(o.announceSummaryFile, "utf8"));
43420
43679
  } catch (e) {
43421
43680
  return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
43422
43681
  }
@@ -43610,7 +43869,7 @@ ${r.stderr ?? ""}`).catch(() => "");
43610
43869
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
43611
43870
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
43612
43871
  function envHealLockPath(home) {
43613
- return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43872
+ return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
43614
43873
  }
43615
43874
  async function withEnvHealLock(what, run) {
43616
43875
  try {
@@ -43781,7 +44040,7 @@ function mmiDoctorDeps(opts = {}) {
43781
44040
  // has no generated routing index would
43782
44041
  // get a permanent — demanding an artifact it never asked for.
43783
44042
  docsIndexState: (root) => {
43784
- if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
44043
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
43785
44044
  const real = createDocsIndexDeps(root);
43786
44045
  let docs;
43787
44046
  const listDocs = () => docs ??= real.listDocs();
@@ -43790,7 +44049,7 @@ function mmiDoctorDeps(opts = {}) {
43790
44049
  },
43791
44050
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
43792
44051
  healDocsIndex: (root) => {
43793
- if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
44052
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
43794
44053
  const real = createDocsIndexDeps(root);
43795
44054
  let docs;
43796
44055
  const listDocs = () => docs ??= real.listDocs();
@@ -44669,16 +44928,16 @@ function ciAuditDeps2() {
44669
44928
  // gate re-seed step is skipped gracefully rather than failing mid-run.
44670
44929
  readSeedFile: (path2) => {
44671
44930
  if (!root) return null;
44672
- const fullPath = (0, import_node_path48.join)(root, path2);
44931
+ const fullPath = (0, import_node_path47.join)(root, path2);
44673
44932
  return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
44674
44933
  }
44675
44934
  };
44676
44935
  }
44677
44936
  function hubRoot2() {
44678
- const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
44937
+ const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
44679
44938
  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();
44939
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
44940
+ if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
44682
44941
  return null;
44683
44942
  }
44684
44943
  registerQueryCommands(program2);
@@ -44817,7 +45076,7 @@ function directoryBytes(path2) {
44817
45076
  return 0;
44818
45077
  }
44819
45078
  for (const entry of entries) {
44820
- const child2 = (0, import_node_path48.join)(path2, entry.name);
45079
+ const child2 = (0, import_node_path47.join)(path2, entry.name);
44821
45080
  if (entry.isDirectory()) total += directoryBytes(child2);
44822
45081
  else {
44823
45082
  try {
@@ -44847,7 +45106,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
44847
45106
  dirBytes,
44848
45107
  listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
44849
45108
  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) };
45109
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
44851
45110
  } catch {
44852
45111
  return { name: d.name, mtimeMs: Date.now() };
44853
45112
  }
@@ -44861,7 +45120,7 @@ function stagingApplyFsGuard(configRoot) {
44861
45120
  return {
44862
45121
  referencedPaths: () => readInstalledPluginRefs(configRoot),
44863
45122
  mtimeMs: (name) => {
44864
- const p = (0, import_node_path48.join)(stagingRoot, name);
45123
+ const p = (0, import_node_path47.join)(stagingRoot, name);
44865
45124
  if (!(0, import_node_fs50.existsSync)(p)) return null;
44866
45125
  try {
44867
45126
  return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);