@mutmutco/codex-plugin 4.5.104 → 4.5.105

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.
package/cli/dist/main.cjs CHANGED
@@ -4747,7 +4747,7 @@ function useColor() {
4747
4747
  var program = new Command();
4748
4748
 
4749
4749
  // src/command-composition.ts
4750
- var import_node_fs54 = require("node:fs");
4750
+ var import_node_fs55 = require("node:fs");
4751
4751
  init_clean_exit();
4752
4752
  init_cli_shared();
4753
4753
 
@@ -17699,10 +17699,10 @@ var rollout_plan_default = {
17699
17699
  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)."
17700
17700
  },
17701
17701
  baseline: {
17702
- version: "4.5.104",
17703
- tag: "v4.5.104",
17704
- commit: "6bf765dc986c",
17705
- npm: "@mutmutco/cli@4.5.104"
17702
+ version: "4.5.105",
17703
+ tag: "v4.5.105",
17704
+ commit: "2edfdb644608",
17705
+ npm: "@mutmutco/cli@4.5.105"
17706
17706
  },
17707
17707
  exitCriterion: "fleet-n-of-n",
17708
17708
  hubOnlyShortcut: "forbidden",
@@ -17719,14 +17719,14 @@ var rollout_plan_default = {
17719
17719
  repo: "mutmutco/mmi-hub",
17720
17720
  role: "canary",
17721
17721
  schedule: "train",
17722
- v3Target: "v4.5.104"
17722
+ v3Target: "v4.5.105"
17723
17723
  }
17724
17724
  ],
17725
17725
  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.",
17726
17726
  rollback: {
17727
17727
  independent: true,
17728
- mechanism: "npm dist-tag latest -> 4.5.104 and redeploy the Hub Lambda from tag v4.5.104 (6bf765dc986c); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17729
- v3Target: "v4.5.104 (@mutmutco/cli@4.5.104, tag commit 6bf765dc986c \u2014 last known-good release carrying the repo-index v4-only contract)"
17728
+ mechanism: "npm dist-tag latest -> 4.5.105 and redeploy the Hub Lambda from tag v4.5.105 (2edfdb644608); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17729
+ v3Target: "v4.5.105 (@mutmutco/cli@4.5.105, tag commit 2edfdb644608 \u2014 last known-good release carrying the repo-index v4-only contract)"
17730
17730
  }
17731
17731
  },
17732
17732
  {
@@ -21992,7 +21992,7 @@ function renderTenantControl(r) {
21992
21992
  }
21993
21993
 
21994
21994
  // src/train-apply-phases.ts
21995
- var import_node_fs24 = require("node:fs");
21995
+ var import_node_fs25 = require("node:fs");
21996
21996
  var import_promises4 = require("node:fs/promises");
21997
21997
  var import_node_path24 = require("node:path");
21998
21998
 
@@ -25765,6 +25765,7 @@ function formatTrainDoctor(v) {
25765
25765
 
25766
25766
  // src/release-plain-words.ts
25767
25767
  var import_promises3 = require("node:fs/promises");
25768
+ var import_node_fs24 = require("node:fs");
25768
25769
  function releaseCutByNotes(cutBy) {
25769
25770
  return `## Internal
25770
25771
  Cut by ${formatSessionIdentity(cutBy)}
@@ -25779,8 +25780,44 @@ function buildPlainWordsSection(lines2, big, heading = PLAIN_WORDS_HEADING) {
25779
25780
  if (big) body.push(PLAIN_WORDS_MARKER);
25780
25781
  return body.join("\n");
25781
25782
  }
25782
- function noChangePlainWordsLine(suiteTag) {
25783
- return suiteTag ? `No changes to this module in this release; ships with the MM-Work suite ${suiteTag}.` : "No changes to this module in this release.";
25783
+ var SUMMARY_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing";
25784
+ function validatePlainWordsSummary(content) {
25785
+ const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
25786
+ if (lines2[0]?.startsWith("No changes to this module")) throw new Error(`requires a real MM-Work title, not the no-change placeholder \u2014 see ${SUMMARY_GUIDE}`);
25787
+ if (lines2.length < 3 || lines2.length > 6) throw new Error(`requires 3\u20136 curated non-empty lines (title + 2\u20135 notes; found ${lines2.length}) \u2014 see ${SUMMARY_GUIDE}`);
25788
+ }
25789
+ function validateSuiteReleaseSummary(path2) {
25790
+ if (!path2) throw new Error(`requires --announce-summary-file <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
25791
+ let content;
25792
+ try {
25793
+ content = (0, import_node_fs24.readFileSync)(path2, "utf8");
25794
+ } catch (e) {
25795
+ throw new Error(`could not read summary file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
25796
+ }
25797
+ validatePlainWordsSummary(content);
25798
+ }
25799
+ async function validateReleaseSummaries(releases, files, releaseExists) {
25800
+ const refusals = [];
25801
+ for (const { repo, tag } of releases) {
25802
+ if (await releaseExists(repo, tag)) continue;
25803
+ try {
25804
+ validateSuiteReleaseSummary(files[repo]);
25805
+ } catch (e) {
25806
+ refusals.push(`${repo}: ${e.message}`);
25807
+ }
25808
+ }
25809
+ if (refusals.length) throw new Error(`suite release summaries refused; no member was released:
25810
+ ${refusals.join("\n")}`);
25811
+ }
25812
+ async function requireSuiteReleaseSummary(deps, path2) {
25813
+ if (!path2) throw new Error(`a new mm-work suite Release requires --announce-summary-file <path> with a title + 2\u20135 notes \u2014 see ${SUMMARY_GUIDE}`);
25814
+ let content;
25815
+ try {
25816
+ content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(path2);
25817
+ } catch (e) {
25818
+ throw new Error(`could not read --announce-summary-file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
25819
+ }
25820
+ validatePlainWordsSummary(content);
25784
25821
  }
25785
25822
  function upsertPlainWordsSection(body, section) {
25786
25823
  let rest = body;
@@ -25810,7 +25847,8 @@ function plainWordsSummaryLines(content) {
25810
25847
  async function writePlainWordsIfSuiteMember(deps, args) {
25811
25848
  if (args.meta?.suite !== "mm-work") return;
25812
25849
  try {
25813
- const lines2 = args.summaryLines && args.summaryLines.length > 0 ? args.summaryLines : [noChangePlainWordsLine(args.suiteTag)];
25850
+ if (!args.summaryLines?.length) return;
25851
+ const lines2 = args.summaryLines;
25814
25852
  const section = [
25815
25853
  buildPlainWordsSection(lines2, args.big === true),
25816
25854
  ...args.summaryLinesTr?.length ? [buildPlainWordsSection(args.summaryLinesTr, false, PLAIN_WORDS_HEADING_TR)] : []
@@ -25962,14 +26000,14 @@ function readLocalGateWorkflows() {
25962
26000
  const dir = (0, import_node_path24.join)(".github", "workflows");
25963
26001
  let names;
25964
26002
  try {
25965
- names = (0, import_node_fs24.readdirSync)(dir);
26003
+ names = (0, import_node_fs25.readdirSync)(dir);
25966
26004
  } catch {
25967
26005
  return null;
25968
26006
  }
25969
26007
  const files = [];
25970
26008
  for (const name of names.filter(isGateWorkflowPath)) {
25971
26009
  try {
25972
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs24.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
26010
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs25.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
25973
26011
  } catch {
25974
26012
  }
25975
26013
  }
@@ -25979,14 +26017,14 @@ function readLocalWorkflows() {
25979
26017
  const dir = (0, import_node_path24.join)(".github", "workflows");
25980
26018
  let names;
25981
26019
  try {
25982
- names = (0, import_node_fs24.readdirSync)(dir);
26020
+ names = (0, import_node_fs25.readdirSync)(dir);
25983
26021
  } catch {
25984
26022
  return null;
25985
26023
  }
25986
26024
  const files = [];
25987
26025
  for (const name of names.filter((n) => /\.ya?ml$/i.test(n))) {
25988
26026
  try {
25989
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs24.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
26027
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs25.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
25990
26028
  } catch {
25991
26029
  }
25992
26030
  }
@@ -26868,7 +26906,7 @@ async function activeRcandLedgerCandidate(deps, cwd) {
26868
26906
  }
26869
26907
  let prior;
26870
26908
  try {
26871
- prior = parseReleaseLedger((0, import_node_fs24.readFileSync)(path2, "utf8"));
26909
+ prior = parseReleaseLedger((0, import_node_fs25.readFileSync)(path2, "utf8"));
26872
26910
  } catch {
26873
26911
  return void 0;
26874
26912
  }
@@ -27157,6 +27195,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
27157
27195
  }
27158
27196
  const ledgerMode = persisted ? "strict" : "migration";
27159
27197
  const partialRelease = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
27198
+ if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
27160
27199
  if (!partialRelease) {
27161
27200
  try {
27162
27201
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
@@ -28031,6 +28070,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
28031
28070
  const bumpIntent = await resolveTrainBumpIntent(deps, process.env, void 0, preOneZeroInForce(meta));
28032
28071
  const branchHints = await loadReleaseTrackBranchHints(deps);
28033
28072
  const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
28073
+ if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
28034
28074
  const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
28035
28075
  if (command === "rcand") {
28036
28076
  if (directTrack) {
@@ -30741,7 +30781,7 @@ function registerBoardCommands(program3) {
30741
30781
  }
30742
30782
 
30743
30783
  // src/bootstrap-commands.ts
30744
- var import_node_fs28 = require("node:fs");
30784
+ var import_node_fs29 = require("node:fs");
30745
30785
  var import_node_os17 = require("node:os");
30746
30786
  var import_node_path28 = require("node:path");
30747
30787
  init_cli_shared();
@@ -30927,7 +30967,7 @@ init_github_client();
30927
30967
  init_cli_shared();
30928
30968
 
30929
30969
  // src/port-registry.ts
30930
- var import_node_fs25 = require("node:fs");
30970
+ var import_node_fs26 = require("node:fs");
30931
30971
  var import_node_path25 = require("node:path");
30932
30972
 
30933
30973
  // ../infra/port-geometry.mjs
@@ -30942,8 +30982,8 @@ function nextPortBlock(registry2) {
30942
30982
  return [base, base + PORT_SPAN];
30943
30983
  }
30944
30984
  function loadPortRegistry(path2) {
30945
- if (!(0, import_node_fs25.existsSync)(path2)) return {};
30946
- const raw = JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8"));
30985
+ if (!(0, import_node_fs26.existsSync)(path2)) return {};
30986
+ const raw = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
30947
30987
  const out = {};
30948
30988
  for (const [key, value] of Object.entries(raw)) {
30949
30989
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -30973,7 +31013,7 @@ function existingPortRange(repo, registry2) {
30973
31013
  function portRangeInfraAt(root, source) {
30974
31014
  const registryPath = (0, import_node_path25.join)(root, "infra", "port-ranges.json");
30975
31015
  const ddbScriptPath = (0, import_node_path25.join)(root, "infra", "port-ddb.mjs");
30976
- if (!(0, import_node_fs25.existsSync)(registryPath) || !(0, import_node_fs25.existsSync)(ddbScriptPath)) return null;
31016
+ if (!(0, import_node_fs26.existsSync)(registryPath) || !(0, import_node_fs26.existsSync)(ddbScriptPath)) return null;
30977
31017
  return { root, source, registryPath, ddbScriptPath };
30978
31018
  }
30979
31019
  function resolvePortRangeInfra(cwd, packageDir) {
@@ -31055,7 +31095,7 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
31055
31095
  }
31056
31096
 
31057
31097
  // src/box-commands.ts
31058
- var import_node_fs27 = require("node:fs");
31098
+ var import_node_fs28 = require("node:fs");
31059
31099
  var import_node_child_process10 = require("node:child_process");
31060
31100
  var import_node_os16 = require("node:os");
31061
31101
  var import_node_path27 = require("node:path");
@@ -31187,7 +31227,7 @@ function applyRemoteScript(items, date) {
31187
31227
  init_clean_exit();
31188
31228
 
31189
31229
  // src/secrets-commands.ts
31190
- var import_node_fs26 = require("node:fs");
31230
+ var import_node_fs27 = require("node:fs");
31191
31231
  var import_node_path26 = require("node:path");
31192
31232
  var import_node_os15 = require("node:os");
31193
31233
  init_cli_shared();
@@ -32349,8 +32389,8 @@ async function decryptRailsCredentials(input) {
32349
32389
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
32350
32390
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
32351
32391
  };
32352
- if ((0, import_node_fs26.existsSync)(masterKeyPath)) {
32353
- env.RAILS_MASTER_KEY = (0, import_node_fs26.readFileSync)(masterKeyPath, "utf8").trim();
32392
+ if ((0, import_node_fs27.existsSync)(masterKeyPath)) {
32393
+ env.RAILS_MASTER_KEY = (0, import_node_fs27.readFileSync)(masterKeyPath, "utf8").trim();
32354
32394
  }
32355
32395
  const script = [
32356
32396
  'require "json"',
@@ -32360,9 +32400,9 @@ async function decryptRailsCredentials(input) {
32360
32400
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
32361
32401
  "puts JSON.generate(config.config)"
32362
32402
  ].join("\n");
32363
- const scriptDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path26.join)((0, import_node_os15.tmpdir)(), "mmi-rails-decrypt-"));
32403
+ const scriptDir = (0, import_node_fs27.mkdtempSync)((0, import_node_path26.join)((0, import_node_os15.tmpdir)(), "mmi-rails-decrypt-"));
32364
32404
  const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
32365
- (0, import_node_fs26.writeFileSync)(scriptPath, script, "utf8");
32405
+ (0, import_node_fs27.writeFileSync)(scriptPath, script, "utf8");
32366
32406
  try {
32367
32407
  const args = ["exec", "ruby", scriptPath];
32368
32408
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -32374,7 +32414,7 @@ async function decryptRailsCredentials(input) {
32374
32414
  });
32375
32415
  return JSON.parse(stdout);
32376
32416
  } finally {
32377
- (0, import_node_fs26.rmSync)(scriptDir, { recursive: true, force: true });
32417
+ (0, import_node_fs27.rmSync)(scriptDir, { recursive: true, force: true });
32378
32418
  }
32379
32419
  }
32380
32420
  async function readSecretStdin() {
@@ -32464,7 +32504,7 @@ function registerSecretsCommands(program3) {
32464
32504
  let body;
32465
32505
  if (o.file) {
32466
32506
  try {
32467
- body = (0, import_node_fs26.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
32507
+ body = (0, import_node_fs27.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
32468
32508
  } catch (e) {
32469
32509
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
32470
32510
  }
@@ -32600,7 +32640,7 @@ function registerSecretsCommands(program3) {
32600
32640
  {
32601
32641
  ...d,
32602
32642
  decryptRailsCredentials,
32603
- removeFile: (path2) => (0, import_node_fs26.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
32643
+ removeFile: (path2) => (0, import_node_fs27.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
32604
32644
  },
32605
32645
  {
32606
32646
  repo: o.repo,
@@ -32648,8 +32688,6 @@ function registerSecretsCommands(program3) {
32648
32688
  var BOX_KEYS = {
32649
32689
  /** Opens every MM box INCLUDING the CI runner (`mmi-runner`). The general-purpose MM key. */
32650
32690
  mmMaster: "HETZNER_MASTER_SSH_PRIVATE_KEY",
32651
- /** Opens the OGUZ project's boxes. */
32652
- oguzMaster: "HETZNER_OGUZ_MASTER_SSH_PRIVATE_KEY",
32653
32691
  /** The deploy key. Opens MM prod/tenant boxes but NOT `mmi-runner` — a real trap: reaching for this key
32654
32692
  * to get into the CI runner fails, and the failure reads like a broken box rather than a wrong key. */
32655
32693
  mmDeploy: "HETZNER_SSH_PRIVATE_KEY"
@@ -32658,11 +32696,10 @@ var HETZNER_VAULT_SLUG = "_org";
32658
32696
  var HETZNER_NS = "hetzner";
32659
32697
  var nsKey = (key) => `${HETZNER_NS}/${key}`;
32660
32698
  var PROJECT_API_TOKEN = {
32661
- MM: nsKey("HETZNER_MM_API_TOKEN"),
32662
- OGUZ: nsKey("HETZNER_OGUZ_API_TOKEN")
32699
+ MM: nsKey("HETZNER_MM_API_TOKEN")
32663
32700
  };
32664
- function sshKeyForProject(project2) {
32665
- return project2 === "OGUZ" ? BOX_KEYS.oguzMaster : BOX_KEYS.mmMaster;
32701
+ function sshKeyForProject(_project) {
32702
+ return BOX_KEYS.mmMaster;
32666
32703
  }
32667
32704
  function vaultPath(key) {
32668
32705
  return `/mmi-future/${HETZNER_VAULT_SLUG}/${HETZNER_NS}/${key}`;
@@ -32765,7 +32802,7 @@ var HETZNER_API = "https://api.hetzner.cloud/v1/servers";
32765
32802
  var HETZNER_TIMEOUT_MS = 15e3;
32766
32803
  var PER_PAGE = 50;
32767
32804
  var MAX_PAGES = 20;
32768
- var PROJECTS = ["MM", "OGUZ"];
32805
+ var PROJECTS = ["MM"];
32769
32806
  async function fetchProjectBoxes(project2, token) {
32770
32807
  const boxes = [];
32771
32808
  let page = 1;
@@ -32859,7 +32896,7 @@ function registerBoxCommands(program3) {
32859
32896
  }
32860
32897
  const remote = command && command.length ? command.join(" ") : void 0;
32861
32898
  const wroteScript = o.ssh && o.script ? o.script : null;
32862
- if (wroteScript) (0, import_node_fs27.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
32899
+ if (wroteScript) (0, import_node_fs28.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
32863
32900
  if (o.json) {
32864
32901
  console.log(JSON.stringify({
32865
32902
  box: found,
@@ -32933,17 +32970,17 @@ function hubGitShow(ref) {
32933
32970
  return (path2) => git3(["show", `${ref}:${path2}`]);
32934
32971
  }
32935
32972
  function runOnBox(found, script) {
32936
- const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
32973
+ const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
32937
32974
  try {
32938
32975
  const recipe = (0, import_node_path27.join)(dir, "recipe.sh");
32939
- (0, import_node_fs27.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
32976
+ (0, import_node_fs28.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
32940
32977
  const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
32941
32978
  if (r.status !== 0) {
32942
32979
  throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
32943
32980
  }
32944
32981
  return r.stdout;
32945
32982
  } finally {
32946
- (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
32983
+ (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
32947
32984
  }
32948
32985
  }
32949
32986
  function formatControl(mode, box, rows) {
@@ -34578,12 +34615,12 @@ async function verifyTargetedSeedPr(repo, number, plan, target, expected, client
34578
34615
  // src/bootstrap-commands.ts
34579
34616
  var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
34580
34617
  async function gateRenderDiff(current, rendered) {
34581
- const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path28.join)((0, import_node_os17.tmpdir)(), "mmi-gate-diff-"));
34618
+ const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path28.join)((0, import_node_os17.tmpdir)(), "mmi-gate-diff-"));
34582
34619
  const before = (0, import_node_path28.join)(dir, "committed-gate.yml");
34583
34620
  const after = (0, import_node_path28.join)(dir, "registry-render.yml");
34584
34621
  try {
34585
- (0, import_node_fs28.writeFileSync)(before, current);
34586
- (0, import_node_fs28.writeFileSync)(after, rendered);
34622
+ (0, import_node_fs29.writeFileSync)(before, current);
34623
+ (0, import_node_fs29.writeFileSync)(after, rendered);
34587
34624
  try {
34588
34625
  return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
34589
34626
  } catch (e) {
@@ -34592,7 +34629,7 @@ async function gateRenderDiff(current, rendered) {
34592
34629
  throw e;
34593
34630
  }
34594
34631
  } finally {
34595
- (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
34632
+ (0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
34596
34633
  }
34597
34634
  }
34598
34635
  function bootstrapTrainDoctorDeps() {
@@ -34652,13 +34689,13 @@ function registerBootstrapCommands(program3) {
34652
34689
  })),
34653
34690
  pushAllowlistOwners: verifyOwners,
34654
34691
  pushAllowlistOwnersRead: verifyOwnersRead,
34655
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null,
34692
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null,
34656
34693
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
34657
34694
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
34658
34695
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
34659
34696
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
34660
34697
  // sanction, which is the pre-#3664 behaviour.
34661
- sanctionedAdmins: (0, import_node_fs28.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs28.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
34698
+ sanctionedAdmins: (0, import_node_fs29.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs29.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
34662
34699
  requiredGcpApis: (() => {
34663
34700
  const v = meta?.requiredGcpApis;
34664
34701
  if (Array.isArray(v)) return v;
@@ -34736,14 +34773,14 @@ function registerBootstrapCommands(program3) {
34736
34773
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
34737
34774
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
34738
34775
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
34739
- if (!(0, import_node_fs28.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
34776
+ if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
34740
34777
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
34741
34778
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
34742
- const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
34779
+ const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
34743
34780
  const hubContents = /* @__PURE__ */ new Map();
34744
34781
  for (const s of manifest.seeds) {
34745
34782
  if (s.ownership !== "org" || s.source !== "self") continue;
34746
- hubContents.set(s.target, (0, import_node_fs28.existsSync)(s.target) ? (0, import_node_fs28.readFileSync)(s.target, "utf8") : null);
34783
+ hubContents.set(s.target, (0, import_node_fs29.existsSync)(s.target) ? (0, import_node_fs29.readFileSync)(s.target, "utf8") : null);
34747
34784
  }
34748
34785
  let targets;
34749
34786
  let classOf = (_repo) => "deployable";
@@ -34951,24 +34988,24 @@ function registerBootstrapCommands(program3) {
34951
34988
  return;
34952
34989
  }
34953
34990
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
34954
- if (!(0, import_node_fs28.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
34991
+ if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
34955
34992
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
34956
34993
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
34957
- const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
34994
+ const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
34958
34995
  const baseBranch = o.class === "content" ? "main" : "development";
34959
34996
  const slug = parsedRepo.slug;
34960
34997
  const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
34961
34998
  const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
34962
- const readFile10 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
34999
+ const readFile10 = (p) => (0, import_node_fs29.existsSync)(p) ? (0, import_node_fs29.readFileSync)(p, "utf8") : null;
34963
35000
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
34964
35001
  const putSeed = async (target, content, ref, sha) => {
34965
35002
  const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
34966
- (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
35003
+ (0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
34967
35004
  try {
34968
35005
  await gh2(contentPutInputArgs(repo, target, tmp));
34969
35006
  } finally {
34970
35007
  try {
34971
- (0, import_node_fs28.unlinkSync)(tmp);
35008
+ (0, import_node_fs29.unlinkSync)(tmp);
34972
35009
  } catch {
34973
35010
  }
34974
35011
  }
@@ -35604,10 +35641,10 @@ LIVE apply to ${repo}:
35604
35641
  bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
35605
35642
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
35606
35643
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
35607
- if (!(0, import_node_fs28.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
35644
+ if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
35608
35645
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
35609
35646
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
35610
- const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
35647
+ const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
35611
35648
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
35612
35649
  if (!o.target) {
35613
35650
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -35616,9 +35653,9 @@ LIVE apply to ${repo}:
35616
35653
  const seed = propagatable.find((s) => s.target === o.target);
35617
35654
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
35618
35655
  ${propagatable.map((s) => s.target).join("\n ")}`);
35619
- if (!seed.managedBlock && !(0, import_node_fs28.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
35620
- const hubContent = seed.managedBlock ? null : (0, import_node_fs28.readFileSync)(seed.target, "utf8");
35621
- const readSeedFile2 = (path2) => (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null;
35656
+ if (!seed.managedBlock && !(0, import_node_fs29.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
35657
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs29.readFileSync)(seed.target, "utf8");
35658
+ const readSeedFile2 = (path2) => (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null;
35622
35659
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
35623
35660
  const cfg = await loadConfig();
35624
35661
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -35627,9 +35664,9 @@ LIVE apply to ${repo}:
35627
35664
  }
35628
35665
  const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
35629
35666
  let independentCount = rosterRepos.length;
35630
- if ((0, import_node_fs28.existsSync)("projects.json")) {
35667
+ if ((0, import_node_fs29.existsSync)("projects.json")) {
35631
35668
  try {
35632
- const local = JSON.parse((0, import_node_fs28.readFileSync)("projects.json", "utf8"));
35669
+ const local = JSON.parse((0, import_node_fs29.readFileSync)("projects.json", "utf8"));
35633
35670
  const localRepos = /* @__PURE__ */ new Set();
35634
35671
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
35635
35672
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -35802,12 +35839,12 @@ LIVE apply to ${repo}:
35802
35839
  const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
35803
35840
  const desiredContent = desiredByRepo.get(rec.repo);
35804
35841
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
35805
- (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
35842
+ (0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
35806
35843
  try {
35807
35844
  await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
35808
35845
  } finally {
35809
35846
  try {
35810
- (0, import_node_fs28.unlinkSync)(tmp);
35847
+ (0, import_node_fs29.unlinkSync)(tmp);
35811
35848
  } catch {
35812
35849
  }
35813
35850
  }
@@ -35876,10 +35913,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
35876
35913
  return fail(`bootstrap rollback: ${e.message}`);
35877
35914
  }
35878
35915
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
35879
- if (!(0, import_node_fs28.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
35916
+ if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
35880
35917
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
35881
35918
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
35882
- const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
35919
+ const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
35883
35920
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
35884
35921
  if (!o.target) {
35885
35922
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -35896,10 +35933,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
35896
35933
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
35897
35934
  let candidates;
35898
35935
  if (o.record) {
35899
- if (!(0, import_node_fs28.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
35936
+ if (!(0, import_node_fs29.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
35900
35937
  let parsed;
35901
35938
  try {
35902
- parsed = JSON.parse((0, import_node_fs28.readFileSync)(o.record, "utf8"));
35939
+ parsed = JSON.parse((0, import_node_fs29.readFileSync)(o.record, "utf8"));
35903
35940
  } catch (e) {
35904
35941
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
35905
35942
  }
@@ -35977,12 +36014,12 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
35977
36014
  existingSha = void 0;
35978
36015
  }
35979
36016
  const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
35980
- (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
36017
+ (0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
35981
36018
  try {
35982
36019
  await gh2(contentPutInputArgs(repo, seed.target, tmp));
35983
36020
  } finally {
35984
36021
  try {
35985
- (0, import_node_fs28.unlinkSync)(tmp);
36022
+ (0, import_node_fs29.unlinkSync)(tmp);
35986
36023
  } catch {
35987
36024
  }
35988
36025
  }
@@ -36438,7 +36475,7 @@ async function waitSuiteDev(deps, opts) {
36438
36475
  }
36439
36476
 
36440
36477
  // src/suite-train.ts
36441
- var import_node_fs29 = require("node:fs");
36478
+ var import_node_fs30 = require("node:fs");
36442
36479
  var import_node_path29 = require("node:path");
36443
36480
  var import_node_crypto14 = require("node:crypto");
36444
36481
 
@@ -36468,6 +36505,17 @@ var SCHEMA = "mmi-suite-cut/v1";
36468
36505
  var SHA2 = /^[a-f0-9]{40}$/i;
36469
36506
  var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
36470
36507
  var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
36508
+ async function reapplyRulesets(deps, record, after) {
36509
+ for (const member of record.members) {
36510
+ try {
36511
+ member.ruleset = { after, ok: true, lines: await deps.productRuleset(member.repo) };
36512
+ } catch (e) {
36513
+ member.ruleset = { after, ok: false, lines: [e.message] };
36514
+ console.warn(`${member.repo}: product ruleset not re-applied (${e.message}); run mmi-cli devops bootstrap apply ${member.repo} --only product-ruleset --execute from the MMI-Hub checkout`);
36515
+ }
36516
+ writeSuiteCut(deps.recordPath, record);
36517
+ }
36518
+ }
36471
36519
  async function kitCheck(deps, shellSha, bundles, where) {
36472
36520
  const shell2 = shellSha ? await deps.shellKit(shellSha) : void 0;
36473
36521
  const kits = await Promise.all(bundles.map(async (b) => ({ id: b.id, kit: b.url ? await deps.bundleKit(b.url) : void 0 })));
@@ -36514,7 +36562,7 @@ function suiteReleaseTag(rc) {
36514
36562
  function readSuiteCut(path2) {
36515
36563
  let raw;
36516
36564
  try {
36517
- raw = (0, import_node_fs29.readFileSync)(path2, "utf8");
36565
+ raw = (0, import_node_fs30.readFileSync)(path2, "utf8");
36518
36566
  } catch (e) {
36519
36567
  if (e.code === "ENOENT") return void 0;
36520
36568
  throw e;
@@ -36525,16 +36573,16 @@ function readSuiteCut(path2) {
36525
36573
  } catch {
36526
36574
  throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
36527
36575
  }
36528
- if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
36576
+ if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0) || m.ruleset !== void 0 && (!["rc", "main"].includes(m.ruleset.after) || typeof m.ruleset.ok !== "boolean" || !Array.isArray(m.ruleset.lines))))
36529
36577
  throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
36530
36578
  return record;
36531
36579
  }
36532
36580
  function writeSuiteCut(path2, record) {
36533
- (0, import_node_fs29.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
36581
+ (0, import_node_fs30.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
36534
36582
  const temp = `${path2}.${(0, import_node_crypto14.randomBytes)(8).toString("hex")}.tmp`;
36535
36583
  try {
36536
- (0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
36537
- (0, import_node_fs29.renameSync)(temp, path2);
36584
+ (0, import_node_fs30.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
36585
+ (0, import_node_fs30.renameSync)(temp, path2);
36538
36586
  } catch (e) {
36539
36587
  throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
36540
36588
  }
@@ -36583,25 +36631,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
36583
36631
  return tags.map((t) => `${t.repo}@${t.tag}`);
36584
36632
  }
36585
36633
  async function retireFailedLeadRcLedger(deps, ownerGo) {
36586
- if (!(0, import_node_fs29.existsSync)(deps.ledgerPath)) return void 0;
36634
+ if (!(0, import_node_fs30.existsSync)(deps.ledgerPath)) return void 0;
36587
36635
  return withLedgerLock(deps.ledgerPath, async (fence) => {
36588
- const ledger = parseReleaseLedger((0, import_node_fs29.readFileSync)(deps.ledgerPath, "utf8"));
36636
+ const ledger = parseReleaseLedger((0, import_node_fs30.readFileSync)(deps.ledgerPath, "utf8"));
36589
36637
  const states = Object.values(ledger.phases).map((p) => p.state);
36590
36638
  if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
36591
36639
  if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
36592
36640
  if ((await deps.rcHead())?.toLowerCase() === ledger.tagSha.toLowerCase()) throw new Error(`origin rc already points at ${ledger.tag}; it was promoted, so its ledger is not retired`);
36593
36641
  const archive = (0, import_node_path29.join)((0, import_node_path29.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
36594
- if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
36642
+ if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
36595
36643
  await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
36596
- await fence(() => (0, import_node_fs29.renameSync)(deps.ledgerPath, archive));
36644
+ await fence(() => (0, import_node_fs30.renameSync)(deps.ledgerPath, archive));
36597
36645
  return `${ledger.tag} (ledger archived to ${archive})`;
36598
36646
  });
36599
36647
  }
36600
36648
  function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
36601
36649
  const record = abandonableCut(recordPath, ownerGo);
36602
36650
  const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
36603
- if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
36604
- (0, import_node_fs29.renameSync)(recordPath, archive);
36651
+ if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
36652
+ (0, import_node_fs30.renameSync)(recordPath, archive);
36605
36653
  return { archive, orphanTags: record.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
36606
36654
  }
36607
36655
  async function suiteRestoreMainPins(deps) {
@@ -36667,12 +36715,12 @@ async function suiteRcand(deps, opts) {
36667
36715
  }
36668
36716
  if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
36669
36717
  const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
36670
- if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
36671
- (0, import_node_fs29.renameSync)(deps.recordPath, archive);
36718
+ if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
36719
+ (0, import_node_fs30.renameSync)(deps.recordPath, archive);
36672
36720
  record = void 0;
36673
36721
  }
36674
36722
  if (record && !record.pinPr && !record.leadRcTag && record.members.every((m) => !m.tag)) {
36675
- (0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
36723
+ (0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
36676
36724
  record = void 0;
36677
36725
  }
36678
36726
  if (!record) {
@@ -36726,7 +36774,7 @@ async function suiteRcand(deps, opts) {
36726
36774
  quiet.push(member);
36727
36775
  }
36728
36776
  if (record.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record.leadDevelopment, record.leadPreviousRc)) {
36729
- (0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
36777
+ (0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
36730
36778
  throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
36731
36779
  }
36732
36780
  for (const member of quiet) {
@@ -36744,7 +36792,7 @@ async function suiteRcand(deps, opts) {
36744
36792
  }
36745
36793
  if (refusals.length) {
36746
36794
  const untouched = record.members.every((m) => !m.tag) && !record.pinPr;
36747
- if (untouched) (0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
36795
+ if (untouched) (0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
36748
36796
  throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
36749
36797
  ${refusals.join("\n")}
36750
36798
  Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
@@ -36807,6 +36855,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
36807
36855
  record.leadRcSha = leadCut.sha;
36808
36856
  writeSuiteCut(deps.recordPath, record);
36809
36857
  }
36858
+ await reapplyRulesets(deps, record, "rc");
36810
36859
  return { plan, record };
36811
36860
  }
36812
36861
  async function suiteRelease(deps, ownerGo, big = false) {
@@ -36842,6 +36891,10 @@ async function suiteRelease(deps, ownerGo, big = false) {
36842
36891
  throw new Error(`${member.repo}: live backend changed after the cut (${member.backend} \u2192 ${live.version}); release refused`);
36843
36892
  }
36844
36893
  if ((await deps.tagSha(SUITE_REPO, record.leadRcTag))?.toLowerCase() !== record.leadRcSha?.toLowerCase()) throw new Error("MM-Work rc tag changed after the cut; release refused");
36894
+ await deps.validateReleaseSummaries([
36895
+ ...record.members.filter((m) => !m.carried && !m.released && !m.hotfixedTo).map((m) => ({ repo: m.repo, tag: m.releasedAs ?? suiteReleaseTag(m.tag) })),
36896
+ ...!record.leadReleased ? [{ repo: SUITE_REPO, tag: suiteReleaseTag(record.leadRcTag) }] : []
36897
+ ]);
36845
36898
  const publishesMain = !record.leadReleased;
36846
36899
  const refusals = [];
36847
36900
  for (const member of record.members) {
@@ -36873,6 +36926,7 @@ Each member's live release must serve <data>/main/module.js at its main pin path
36873
36926
  writeSuiteCut(deps.recordPath, record);
36874
36927
  }
36875
36928
  }
36929
+ await reapplyRulesets(deps, record, "main");
36876
36930
  if (!record.leadReleased) {
36877
36931
  await deps.release(SUITE_REPO, suiteReleaseTag(record.leadRcTag), record.leadRcSha, { big });
36878
36932
  record.leadReleased = true;
@@ -36899,7 +36953,7 @@ var hotfixRecordPath = (deps) => (0, import_node_path29.join)((0, import_node_pa
36899
36953
  function readSuiteHotfix(path2) {
36900
36954
  let raw;
36901
36955
  try {
36902
- raw = (0, import_node_fs29.readFileSync)(path2, "utf8");
36956
+ raw = (0, import_node_fs30.readFileSync)(path2, "utf8");
36903
36957
  } catch (e) {
36904
36958
  if (e.code === "ENOENT") return void 0;
36905
36959
  throw e;
@@ -36938,11 +36992,11 @@ async function suiteHotfix(deps, opts) {
36938
36992
  if (cut && !cut.leadReleased) throw new Error(`an unfinished suite cut (${cut.leadRcTag ?? "no lead tag"}) is in flight; finish it with suite release or retire it with suite abandon first`);
36939
36993
  const path2 = hotfixRecordPath(deps);
36940
36994
  let record = readSuiteHotfix(path2);
36941
- if (record?.mapped) {
36942
- (0, import_node_fs29.renameSync)(path2, `${path2}.${record.leadTag}.json`);
36943
- record = void 0;
36944
- }
36995
+ const completed = record?.mapped ? record : void 0;
36996
+ if (completed) record = void 0;
36945
36997
  if (record && record.repo !== found.repo) throw new Error(`an unfinished suite hotfix for ${record.repo} is recorded; finish it (rerun with that member) first`);
36998
+ if (!record?.tag) await deps.validateHotfixSummary();
36999
+ if (completed) (0, import_node_fs30.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
36946
37000
  const rec = record ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
36947
37001
  const save = () => writeSuiteHotfix(path2, rec);
36948
37002
  save();
@@ -37209,7 +37263,7 @@ async function runRcDataSwitch(deps, opts) {
37209
37263
  }
37210
37264
 
37211
37265
  // src/suite-train-io.ts
37212
- var import_node_fs43 = require("node:fs");
37266
+ var import_node_fs44 = require("node:fs");
37213
37267
  var import_node_path43 = require("node:path");
37214
37268
  init_cli_shared();
37215
37269
 
@@ -38271,6 +38325,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
38271
38325
  await assertConsumerBomLineage(deps, mergedSha, tag);
38272
38326
  }
38273
38327
  const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
38328
+ if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
38274
38329
  if (!releaseExists && isHubControlRepo(ctx.repo)) {
38275
38330
  if (!options.announceSummaryFile) {
38276
38331
  throw new Error("a new MMI-Hub hotfix Release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same hotfix release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing");
@@ -38886,7 +38941,7 @@ function checkHotfixCarries(options) {
38886
38941
  }
38887
38942
 
38888
38943
  // src/merge-cleanup.ts
38889
- var import_node_fs31 = require("node:fs");
38944
+ var import_node_fs32 = require("node:fs");
38890
38945
  var import_node_path31 = require("node:path");
38891
38946
  var import_node_os18 = require("node:os");
38892
38947
  init_cli_shared();
@@ -39202,7 +39257,7 @@ async function runPrLand(prNumber, options, deps) {
39202
39257
 
39203
39258
  // src/test-policy-core.ts
39204
39259
  var import_node_child_process13 = require("node:child_process");
39205
- var import_node_fs30 = require("node:fs");
39260
+ var import_node_fs31 = require("node:fs");
39206
39261
  var import_node_path30 = require("node:path");
39207
39262
 
39208
39263
  // src/test-command-policy-shared.mjs
@@ -39756,7 +39811,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
39756
39811
  }
39757
39812
  function readFileOrNull2(path2) {
39758
39813
  try {
39759
- return (0, import_node_fs30.readFileSync)(path2, "utf8");
39814
+ return (0, import_node_fs31.readFileSync)(path2, "utf8");
39760
39815
  } catch {
39761
39816
  return null;
39762
39817
  }
@@ -39784,10 +39839,10 @@ function classify(changed, policy, present = () => false) {
39784
39839
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
39785
39840
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
39786
39841
  }
39787
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
39842
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
39788
39843
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
39789
39844
  }
39790
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
39845
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
39791
39846
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
39792
39847
  return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
39793
39848
  }
@@ -39928,7 +39983,7 @@ function runTestPolicy(root, deps = {}) {
39928
39983
  }
39929
39984
  const loaded = deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
39930
39985
  const { policy, source: policySource } = loaded;
39931
- const exists = deps.exists ?? ((path2) => (0, import_node_fs30.existsSync)(path2));
39986
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
39932
39987
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
39933
39988
  const base = deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
39934
39989
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
@@ -40226,13 +40281,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
40226
40281
  return rewritten === base ? null : rewritten;
40227
40282
  }
40228
40283
  function writeSquashBodyFile(body) {
40229
- const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
40284
+ const dir = (0, import_node_fs32.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
40230
40285
  const path2 = (0, import_node_path31.join)(dir, "body.txt");
40231
- (0, import_node_fs31.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
40286
+ (0, import_node_fs32.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
40232
40287
  `, "utf8");
40233
40288
  return { path: path2, cleanup: () => {
40234
40289
  try {
40235
- (0, import_node_fs31.rmSync)(dir, { recursive: true, force: true });
40290
+ (0, import_node_fs32.rmSync)(dir, { recursive: true, force: true });
40236
40291
  } catch {
40237
40292
  }
40238
40293
  } };
@@ -40439,7 +40494,7 @@ async function deleteMergedRemoteBranch(options) {
40439
40494
  }
40440
40495
 
40441
40496
  // src/project-info-sync.ts
40442
- var import_node_fs32 = require("node:fs");
40497
+ var import_node_fs33 = require("node:fs");
40443
40498
  var import_node_path32 = require("node:path");
40444
40499
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
40445
40500
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
@@ -40488,13 +40543,13 @@ function sharedName(entries, fallback) {
40488
40543
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
40489
40544
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
40490
40545
  const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
40491
- if (!(0, import_node_fs32.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
40546
+ if (!(0, import_node_fs33.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
40492
40547
  const entries = entriesFor(project2, projects);
40493
40548
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
40494
40549
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
40495
40550
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
40496
40551
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
40497
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs32.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
40552
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs33.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
40498
40553
  const lines2 = [
40499
40554
  `# ${projectName}`,
40500
40555
  "",
@@ -40513,8 +40568,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
40513
40568
  const targetBase = `https://github.com/${targetRepo2}`;
40514
40569
  const targetBranch = branchFor(targetRepo2, projects);
40515
40570
  const orgDocs = [
40516
- (0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
40517
- (0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
40571
+ (0, import_node_fs33.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
40572
+ (0, import_node_fs33.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
40518
40573
  ].filter(Boolean);
40519
40574
  if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
40520
40575
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
@@ -40536,7 +40591,7 @@ async function syncProjectInfo(plan, client, apply) {
40536
40591
  }
40537
40592
 
40538
40593
  // src/train-commands.ts
40539
- var import_node_fs33 = require("node:fs");
40594
+ var import_node_fs34 = require("node:fs");
40540
40595
  var import_node_path33 = require("node:path");
40541
40596
  init_cli_shared();
40542
40597
  init_clean_exit();
@@ -40552,7 +40607,7 @@ function resolveReleaseBumpIntent(raw) {
40552
40607
  }
40553
40608
  function readRepoVersion() {
40554
40609
  try {
40555
- return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
40610
+ return JSON.parse((0, import_node_fs34.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
40556
40611
  } catch {
40557
40612
  return void 0;
40558
40613
  }
@@ -40917,7 +40972,7 @@ async function openSuitePinPr(deps, input) {
40917
40972
 
40918
40973
  // src/command-register-collaboration.ts
40919
40974
  var import_node_child_process17 = require("node:child_process");
40920
- var import_node_fs42 = require("node:fs");
40975
+ var import_node_fs43 = require("node:fs");
40921
40976
  var import_promises8 = require("node:fs/promises");
40922
40977
  init_clean_exit();
40923
40978
  init_cli_shared();
@@ -41051,7 +41106,7 @@ init_github_client();
41051
41106
  init_github_quota();
41052
41107
 
41053
41108
  // src/issue-commands.ts
41054
- var import_node_fs34 = require("node:fs");
41109
+ var import_node_fs35 = require("node:fs");
41055
41110
  var import_node_crypto15 = require("node:crypto");
41056
41111
  init_cli_shared();
41057
41112
  init_clean_exit();
@@ -41244,7 +41299,7 @@ async function editIssue(client, options, deps = {}) {
41244
41299
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
41245
41300
  const patch = {};
41246
41301
  let bodyChanged = false;
41247
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs34.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
41302
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs35.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
41248
41303
  if (options.titleFile !== void 0) {
41249
41304
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
41250
41305
  } else if (options.title !== void 0) {
@@ -41907,7 +41962,7 @@ function extendCreateCommand(issue, batchAttach) {
41907
41962
  if (opts.batch) {
41908
41963
  let specs;
41909
41964
  try {
41910
- const raw = (0, import_node_fs34.readFileSync)(opts.batch, "utf8");
41965
+ const raw = (0, import_node_fs35.readFileSync)(opts.batch, "utf8");
41911
41966
  specs = JSON.parse(raw);
41912
41967
  if (!Array.isArray(specs)) {
41913
41968
  const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
@@ -41984,7 +42039,7 @@ ${lines2}`, {
41984
42039
  }
41985
42040
 
41986
42041
  // src/post-merge-recon.ts
41987
- var import_node_fs35 = require("node:fs");
42042
+ var import_node_fs36 = require("node:fs");
41988
42043
  var import_node_path35 = require("node:path");
41989
42044
 
41990
42045
  // src/cross-repo-filing-issue.ts
@@ -42154,16 +42209,16 @@ function buildPostMergeReconRecovery(input) {
42154
42209
  }
42155
42210
  function writePostMergeReconRecovery(cwd, recovery) {
42156
42211
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
42157
- (0, import_node_fs35.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
42158
- (0, import_node_fs35.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
42212
+ (0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
42213
+ (0, import_node_fs36.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
42159
42214
  `, "utf8");
42160
42215
  return path2;
42161
42216
  }
42162
42217
  function clearPostMergeReconRecovery(cwd, repo, pr) {
42163
42218
  const path2 = postMergeReconStatePath(cwd, repo, pr);
42164
- if (!(0, import_node_fs35.existsSync)(path2)) return;
42219
+ if (!(0, import_node_fs36.existsSync)(path2)) return;
42165
42220
  try {
42166
- (0, import_node_fs35.unlinkSync)(path2);
42221
+ (0, import_node_fs36.unlinkSync)(path2);
42167
42222
  } catch {
42168
42223
  }
42169
42224
  }
@@ -42639,7 +42694,7 @@ function registerPrLifecycleCommands(program3) {
42639
42694
 
42640
42695
  // src/review-verdict.ts
42641
42696
  var import_node_child_process14 = require("node:child_process");
42642
- var import_node_fs36 = require("node:fs");
42697
+ var import_node_fs37 = require("node:fs");
42643
42698
  var import_node_os20 = require("node:os");
42644
42699
  var import_node_path36 = require("node:path");
42645
42700
  init_cli_shared();
@@ -42806,15 +42861,15 @@ async function readPrIssueComments(number, repo) {
42806
42861
  return comments;
42807
42862
  }
42808
42863
  async function postPrCommentFromFile(number, repo, body) {
42809
- const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
42864
+ const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
42810
42865
  const path2 = (0, import_node_path36.join)(dir, "body.md");
42811
42866
  try {
42812
- (0, import_node_fs36.writeFileSync)(path2, body, "utf8");
42867
+ (0, import_node_fs37.writeFileSync)(path2, body, "utf8");
42813
42868
  const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
42814
42869
  return stdout.trim();
42815
42870
  } finally {
42816
42871
  try {
42817
- (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true });
42872
+ (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
42818
42873
  } catch {
42819
42874
  }
42820
42875
  }
@@ -43048,17 +43103,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
43048
43103
  }
43049
43104
 
43050
43105
  // src/worktree-residue-sweep.ts
43051
- var import_node_fs41 = require("node:fs");
43106
+ var import_node_fs42 = require("node:fs");
43052
43107
  var import_node_path41 = require("node:path");
43053
43108
 
43054
43109
  // src/worktree-merge-cleanup.ts
43055
- var import_node_fs40 = require("node:fs");
43110
+ var import_node_fs41 = require("node:fs");
43056
43111
  var import_node_path40 = require("node:path");
43057
43112
  init_cli_shared();
43058
43113
 
43059
43114
  // src/stage-runner.ts
43060
43115
  var import_node_child_process16 = require("node:child_process");
43061
- var import_node_fs38 = require("node:fs");
43116
+ var import_node_fs39 = require("node:fs");
43062
43117
  var import_node_path38 = require("node:path");
43063
43118
  var import_node_net = require("node:net");
43064
43119
  var import_node_util5 = require("node:util");
@@ -43069,7 +43124,7 @@ function normalizeEol2(s) {
43069
43124
  }
43070
43125
 
43071
43126
  // src/env-detection.ts
43072
- var import_node_fs37 = require("node:fs");
43127
+ var import_node_fs38 = require("node:fs");
43073
43128
  var import_node_path37 = require("node:path");
43074
43129
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
43075
43130
  function isEnvBaseName(name) {
@@ -43080,7 +43135,7 @@ function findEnvFiles(root) {
43080
43135
  const walk2 = (dir, rel) => {
43081
43136
  let entries;
43082
43137
  try {
43083
- entries = (0, import_node_fs37.readdirSync)(dir, { withFileTypes: true });
43138
+ entries = (0, import_node_fs38.readdirSync)(dir, { withFileTypes: true });
43084
43139
  } catch {
43085
43140
  return;
43086
43141
  }
@@ -43094,7 +43149,7 @@ function findEnvFiles(root) {
43094
43149
  if (isEnvBaseName(entry.name)) {
43095
43150
  let isDir = false;
43096
43151
  try {
43097
- isDir = (0, import_node_fs37.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
43152
+ isDir = (0, import_node_fs38.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
43098
43153
  } catch {
43099
43154
  }
43100
43155
  if (!isDir) found.push(relPath);
@@ -43207,12 +43262,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
43207
43262
  function stageBash() {
43208
43263
  if (process.platform !== "win32") return "bash";
43209
43264
  const configured = process.env.SHELL;
43210
- if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs38.existsSync)(configured)) return configured;
43265
+ if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs39.existsSync)(configured)) return configured;
43211
43266
  const candidates = [
43212
43267
  (0, import_node_path38.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
43213
43268
  ...process.env.LOCALAPPDATA ? [(0, import_node_path38.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
43214
43269
  ];
43215
- const bash = candidates.find((path2) => (0, import_node_fs38.existsSync)(path2));
43270
+ const bash = candidates.find((path2) => (0, import_node_fs39.existsSync)(path2));
43216
43271
  if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
43217
43272
  return bash;
43218
43273
  }
@@ -43568,7 +43623,7 @@ function stageProcessEnv(stagePort, extraEnv) {
43568
43623
  function composeResolvesPort(cwd) {
43569
43624
  if (process.env.PORT) return true;
43570
43625
  const envFile = (0, import_node_path38.join)(cwd, ".env");
43571
- return (0, import_node_fs38.existsSync)(envFile) && envFileKeys((0, import_node_fs38.readFileSync)(envFile, "utf8")).has("PORT");
43626
+ return (0, import_node_fs39.existsSync)(envFile) && envFileKeys((0, import_node_fs39.readFileSync)(envFile, "utf8")).has("PORT");
43572
43627
  }
43573
43628
  function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
43574
43629
  const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
@@ -43583,13 +43638,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
43583
43638
  function stageComposeFiles(cwd) {
43584
43639
  const files = ["docker-compose.yml"];
43585
43640
  for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
43586
- if ((0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
43641
+ if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
43587
43642
  }
43588
43643
  return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
43589
43644
  }
43590
43645
  async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
43591
43646
  const keys = Object.keys(vaultEnvMerge ?? {});
43592
- if (!keys.length || !(0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
43647
+ if (!keys.length || !(0, import_node_fs39.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
43593
43648
  let configJson;
43594
43649
  try {
43595
43650
  const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
@@ -43606,20 +43661,20 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
43606
43661
  const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson));
43607
43662
  if (!body) return {};
43608
43663
  const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
43609
- (0, import_node_fs38.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
43610
- (0, import_node_fs38.writeFileSync)(overridePath, body, "utf8");
43664
+ (0, import_node_fs39.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
43665
+ (0, import_node_fs39.writeFileSync)(overridePath, body, "utf8");
43611
43666
  return stageComposeFileEnv(stageComposeFiles(cwd));
43612
43667
  }
43613
43668
  async function ensureStageRuntimeEnv(config, opts, cwd) {
43614
43669
  if (!config.ensureEnv) return;
43615
43670
  const target = (0, import_node_path38.join)(cwd, config.ensureEnv.target);
43616
43671
  const example = (0, import_node_path38.join)(cwd, config.ensureEnv.example);
43617
- if (!(0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
43618
- (0, import_node_fs38.copyFileSync)(example, target);
43619
- } else if ((0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
43620
- const stale = detectStaleEnvFile((0, import_node_fs38.readFileSync)(example, "utf8"), (0, import_node_fs38.readFileSync)(target, "utf8"), {
43621
- exampleMtimeMs: (0, import_node_fs38.statSync)(example).mtimeMs,
43622
- targetMtimeMs: (0, import_node_fs38.statSync)(target).mtimeMs
43672
+ if (!(0, import_node_fs39.existsSync)(target) && (0, import_node_fs39.existsSync)(example)) {
43673
+ (0, import_node_fs39.copyFileSync)(example, target);
43674
+ } else if ((0, import_node_fs39.existsSync)(target) && (0, import_node_fs39.existsSync)(example)) {
43675
+ const stale = detectStaleEnvFile((0, import_node_fs39.readFileSync)(example, "utf8"), (0, import_node_fs39.readFileSync)(target, "utf8"), {
43676
+ exampleMtimeMs: (0, import_node_fs39.statSync)(example).mtimeMs,
43677
+ targetMtimeMs: (0, import_node_fs39.statSync)(target).mtimeMs
43623
43678
  });
43624
43679
  if (stale) {
43625
43680
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -43627,8 +43682,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
43627
43682
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
43628
43683
  }
43629
43684
  }
43630
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs38.existsSync)(target)) {
43631
- (0, import_node_fs38.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs38.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
43685
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs39.existsSync)(target)) {
43686
+ (0, import_node_fs39.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs39.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
43632
43687
  }
43633
43688
  }
43634
43689
  async function gitText(cwd, args) {
@@ -43658,20 +43713,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
43658
43713
  return void 0;
43659
43714
  }
43660
43715
  function readState(path2) {
43661
- if (!(0, import_node_fs38.existsSync)(path2)) return null;
43716
+ if (!(0, import_node_fs39.existsSync)(path2)) return null;
43662
43717
  try {
43663
- return JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
43718
+ return JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
43664
43719
  } catch {
43665
43720
  return null;
43666
43721
  }
43667
43722
  }
43668
43723
  function mkdirFor(path2) {
43669
43724
  const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
43670
- (0, import_node_fs38.mkdirSync)(dir, { recursive: true });
43725
+ (0, import_node_fs39.mkdirSync)(dir, { recursive: true });
43671
43726
  }
43672
43727
  function writeState(path2, state) {
43673
43728
  mkdirFor(path2);
43674
- (0, import_node_fs38.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
43729
+ (0, import_node_fs39.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
43675
43730
  }
43676
43731
  function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
43677
43732
  const reservation = {
@@ -43700,7 +43755,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
43700
43755
  );
43701
43756
  }
43702
43757
  for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
43703
- (0, import_node_fs38.rmSync)(path2, { force: true });
43758
+ (0, import_node_fs39.rmSync)(path2, { force: true });
43704
43759
  }
43705
43760
  }
43706
43761
  async function killTree(pid) {
@@ -43947,8 +44002,8 @@ async function runStage(config = {}, opts = {}) {
43947
44002
  });
43948
44003
  }
43949
44004
  } catch (e) {
43950
- (0, import_node_fs38.rmSync)(statePath, { force: true });
43951
- if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs38.rmSync)(globalStatePath, { force: true });
44005
+ (0, import_node_fs39.rmSync)(statePath, { force: true });
44006
+ if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs39.rmSync)(globalStatePath, { force: true });
43952
44007
  throw e;
43953
44008
  }
43954
44009
  const started = await startStage(config, {
@@ -43963,7 +44018,7 @@ async function runStage(config = {}, opts = {}) {
43963
44018
  }
43964
44019
 
43965
44020
  // src/worktree-evidence-archive.ts
43966
- var import_node_fs39 = require("node:fs");
44021
+ var import_node_fs40 = require("node:fs");
43967
44022
  var import_node_path39 = require("node:path");
43968
44023
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
43969
44024
  function sanitizeArchiveSegment(value, max = 80) {
@@ -43979,17 +44034,17 @@ function resolveArtifactRunScope(env = process.env) {
43979
44034
  }
43980
44035
  function defaultIsDirectory(path2) {
43981
44036
  try {
43982
- return (0, import_node_fs39.lstatSync)(path2).isDirectory();
44037
+ return (0, import_node_fs40.lstatSync)(path2).isDirectory();
43983
44038
  } catch {
43984
44039
  return false;
43985
44040
  }
43986
44041
  }
43987
44042
  function archiveWorktreeJervArtifacts(args, deps = {}) {
43988
- const exists = deps.exists ?? import_node_fs39.existsSync;
44043
+ const exists = deps.exists ?? import_node_fs40.existsSync;
43989
44044
  const isDirectory = deps.isDirectory ?? defaultIsDirectory;
43990
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
44045
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
43991
44046
  const mkdirp = deps.mkdirp ?? ((path2) => {
43992
- (0, import_node_fs39.mkdirSync)(path2, { recursive: true });
44047
+ (0, import_node_fs40.mkdirSync)(path2, { recursive: true });
43993
44048
  });
43994
44049
  const env = deps.env ?? process.env;
43995
44050
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
@@ -44013,13 +44068,13 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
44013
44068
  }
44014
44069
  }
44015
44070
  function defaultStat(path2) {
44016
- const st = (0, import_node_fs39.statSync)(path2);
44071
+ const st = (0, import_node_fs40.statSync)(path2);
44017
44072
  return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
44018
44073
  }
44019
44074
  function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
44020
- const exists = deps.exists ?? import_node_fs39.existsSync;
44075
+ const exists = deps.exists ?? import_node_fs40.existsSync;
44021
44076
  const stat4 = deps.stat ?? defaultStat;
44022
- const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
44077
+ const readdir2 = deps.readdir ?? import_node_fs40.readdirSync;
44023
44078
  const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
44024
44079
  if (!exists(tmpRoot)) return [];
44025
44080
  const entries = [];
@@ -44052,10 +44107,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
44052
44107
  return entries;
44053
44108
  }
44054
44109
  function archiveWorktreeTmpArtifacts(args, deps = {}) {
44055
- const exists = deps.exists ?? import_node_fs39.existsSync;
44056
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
44110
+ const exists = deps.exists ?? import_node_fs40.existsSync;
44111
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
44057
44112
  const mkdirp = deps.mkdirp ?? ((path2) => {
44058
- (0, import_node_fs39.mkdirSync)(path2, { recursive: true });
44113
+ (0, import_node_fs40.mkdirSync)(path2, { recursive: true });
44059
44114
  });
44060
44115
  const env = deps.env ?? process.env;
44061
44116
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
@@ -44142,7 +44197,7 @@ function normPath2(p) {
44142
44197
  function unlinkNodeModulesJunction(wtPath) {
44143
44198
  const nm = (0, import_node_path40.join)(wtPath, "node_modules");
44144
44199
  try {
44145
- if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
44200
+ if ((0, import_node_fs41.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs41.rmdirSync)(nm);
44146
44201
  return { ok: true };
44147
44202
  } catch (e) {
44148
44203
  if (e.code === "ENOENT") return { ok: true };
@@ -44153,8 +44208,8 @@ function defaultSleep2(ms) {
44153
44208
  return new Promise((resolve7) => setTimeout(resolve7, ms));
44154
44209
  }
44155
44210
  async function removeResidueDirectory(wtPath, options = {}) {
44156
- const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs40.rmdirSync, exists = import_node_fs40.existsSync } = options;
44157
- const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs40.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
44211
+ const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs41.rmdirSync, exists = import_node_fs41.existsSync } = options;
44212
+ const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs41.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
44158
44213
  const junction = unlinkNodeModulesJunction(wtPath);
44159
44214
  if (!junction.ok) return junction;
44160
44215
  try {
@@ -44280,21 +44335,21 @@ async function listNestedIgnoredNodeModules(wtPath, execGit) {
44280
44335
  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)));
44281
44336
  }
44282
44337
  function safeRemoveTree(path2) {
44283
- const stat4 = (0, import_node_fs40.lstatSync)(path2);
44338
+ const stat4 = (0, import_node_fs41.lstatSync)(path2);
44284
44339
  if (stat4.isSymbolicLink()) {
44285
44340
  try {
44286
- (0, import_node_fs40.rmdirSync)(path2);
44341
+ (0, import_node_fs41.rmdirSync)(path2);
44287
44342
  } catch {
44288
- (0, import_node_fs40.unlinkSync)(path2);
44343
+ (0, import_node_fs41.unlinkSync)(path2);
44289
44344
  }
44290
44345
  return;
44291
44346
  }
44292
44347
  if (stat4.isDirectory()) {
44293
- for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
44294
- (0, import_node_fs40.rmdirSync)(path2);
44348
+ for (const entry of (0, import_node_fs41.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
44349
+ (0, import_node_fs41.rmdirSync)(path2);
44295
44350
  return;
44296
44351
  }
44297
- (0, import_node_fs40.unlinkSync)(path2);
44352
+ (0, import_node_fs41.unlinkSync)(path2);
44298
44353
  }
44299
44354
  async function removeWorktreeNodeModules(wtPath) {
44300
44355
  try {
@@ -44310,7 +44365,7 @@ function errorMessage(e) {
44310
44365
  }
44311
44366
  function resolvedOrRaw(path2) {
44312
44367
  try {
44313
- return normPath2((0, import_node_fs40.realpathSync)(path2));
44368
+ return normPath2((0, import_node_fs41.realpathSync)(path2));
44314
44369
  } catch {
44315
44370
  return normPath2(path2);
44316
44371
  }
@@ -44318,10 +44373,10 @@ function resolvedOrRaw(path2) {
44318
44373
  function unlinkEscapingReparsePoints(root, primaryRoot) {
44319
44374
  let realRoot;
44320
44375
  try {
44321
- if ((0, import_node_fs40.lstatSync)(root).isSymbolicLink()) {
44376
+ if ((0, import_node_fs41.lstatSync)(root).isSymbolicLink()) {
44322
44377
  return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
44323
44378
  }
44324
- realRoot = normPath2((0, import_node_fs40.realpathSync)(root));
44379
+ realRoot = normPath2((0, import_node_fs41.realpathSync)(root));
44325
44380
  } catch (e) {
44326
44381
  if (e.code === "ENOENT") return { ok: true, unlinked: [] };
44327
44382
  return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
@@ -44337,7 +44392,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
44337
44392
  const dir = stack.pop();
44338
44393
  let entries;
44339
44394
  try {
44340
- entries = (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true });
44395
+ entries = (0, import_node_fs41.readdirSync)(dir, { withFileTypes: true });
44341
44396
  } catch (e) {
44342
44397
  if (e.code === "ENOENT") continue;
44343
44398
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
@@ -44347,7 +44402,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
44347
44402
  if (entry.isSymbolicLink()) {
44348
44403
  let target = "";
44349
44404
  try {
44350
- target = normPath2((0, import_node_fs40.realpathSync)(child2));
44405
+ target = normPath2((0, import_node_fs41.realpathSync)(child2));
44351
44406
  } catch {
44352
44407
  target = "";
44353
44408
  }
@@ -44530,7 +44585,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
44530
44585
  return report;
44531
44586
  }
44532
44587
  const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
44533
- const pathExists = options.pathExists ?? import_node_fs40.existsSync;
44588
+ const pathExists = options.pathExists ?? import_node_fs41.existsSync;
44534
44589
  let afterWorktrees = [];
44535
44590
  try {
44536
44591
  afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
@@ -44817,7 +44872,7 @@ function residueLedgerPath(gitCommonDir) {
44817
44872
  }
44818
44873
  function readPaths(file, deps) {
44819
44874
  try {
44820
- const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs41.readFileSync)(f, "utf8")))(file));
44875
+ const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs42.readFileSync)(f, "utf8")))(file));
44821
44876
  return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
44822
44877
  } catch {
44823
44878
  return [];
@@ -44826,8 +44881,8 @@ function readPaths(file, deps) {
44826
44881
  function writePaths(file, paths, deps) {
44827
44882
  try {
44828
44883
  const write = deps.writeLedger ?? ((f, t) => {
44829
- (0, import_node_fs41.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
44830
- (0, import_node_fs41.writeFileSync)(f, t, "utf8");
44884
+ (0, import_node_fs42.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
44885
+ (0, import_node_fs42.writeFileSync)(f, t, "utf8");
44831
44886
  });
44832
44887
  write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
44833
44888
  `);
@@ -44841,17 +44896,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
44841
44896
  writePaths(file, paths, deps);
44842
44897
  }
44843
44898
  async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
44844
- const exists = deps.exists ?? import_node_fs41.existsSync;
44845
- const readDir = deps.readDir ?? ((p) => (0, import_node_fs41.readdirSync)(p));
44899
+ const exists = deps.exists ?? import_node_fs42.existsSync;
44900
+ const readDir = deps.readDir ?? ((p) => (0, import_node_fs42.readdirSync)(p));
44846
44901
  const isDir = deps.isDir ?? ((p) => {
44847
44902
  try {
44848
- return (0, import_node_fs41.statSync)(p).isDirectory();
44903
+ return (0, import_node_fs42.statSync)(p).isDirectory();
44849
44904
  } catch {
44850
44905
  return false;
44851
44906
  }
44852
44907
  });
44853
44908
  const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
44854
- const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs41.rmdirSync)(p));
44909
+ const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs42.rmdirSync)(p));
44855
44910
  const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
44856
44911
  const live = new Set(registered.map(norm));
44857
44912
  const primary = registered[0] ? norm(registered[0]) : void 0;
@@ -44931,8 +44986,8 @@ function argvWantsJson2() {
44931
44986
  function hubRoot() {
44932
44987
  const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
44933
44988
  const marker = "skills/bootstrap/seeds/manifest.json";
44934
- if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
44935
- if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
44989
+ if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
44990
+ if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
44936
44991
  return null;
44937
44992
  }
44938
44993
  function ciAuditDeps() {
@@ -44945,7 +45000,7 @@ function ciAuditDeps() {
44945
45000
  readSeedFile: (path2) => {
44946
45001
  if (!root) return null;
44947
45002
  const fullPath = (0, import_node_path42.join)(root, path2);
44948
- return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
45003
+ return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
44949
45004
  }
44950
45005
  };
44951
45006
  }
@@ -45709,10 +45764,10 @@ ${list}`);
45709
45764
  });
45710
45765
  async function listCiWorkflowPaths(cwd = process.cwd()) {
45711
45766
  const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
45712
- if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
45713
- return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
45767
+ if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
45768
+ return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
45714
45769
  try {
45715
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
45770
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
45716
45771
  } catch {
45717
45772
  return true;
45718
45773
  }
@@ -45917,7 +45972,7 @@ ${list}`);
45917
45972
  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) => {
45918
45973
  if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
45919
45974
  const verdict = o.verdict;
45920
- const findings = o.findingsFile ? (0, import_node_fs42.readFileSync)(o.findingsFile, "utf8") : void 0;
45975
+ const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
45921
45976
  let patch;
45922
45977
  let head;
45923
45978
  try {
@@ -46192,7 +46247,7 @@ ${list}`);
46192
46247
  const mergeSquashBody = squashBodyTextForMerge(
46193
46248
  closingGuardInput,
46194
46249
  method === "--squash",
46195
- o.squashBodyFile ? (0, import_node_fs42.readFileSync)(o.squashBodyFile, "utf8") : void 0
46250
+ o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
46196
46251
  );
46197
46252
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
46198
46253
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -46322,7 +46377,7 @@ ${list}`);
46322
46377
  }
46323
46378
  if (!repoForPostCleanup) throw e;
46324
46379
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
46325
- const commitMessage = bodyFile ? (0, import_node_fs42.readFileSync)(bodyFile, "utf8") : void 0;
46380
+ const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
46326
46381
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
46327
46382
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
46328
46383
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -46454,7 +46509,7 @@ ${list}`);
46454
46509
  preserveWorktree: o.preserveWorktree,
46455
46510
  gcAcknowledged: o.gc,
46456
46511
  expectedHeadOid: headRefOid,
46457
- pathExists: (p) => (0, import_node_fs42.existsSync)(p),
46512
+ pathExists: (p) => (0, import_node_fs43.existsSync)(p),
46458
46513
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
46459
46514
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
46460
46515
  // 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
@@ -47363,6 +47418,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
47363
47418
  const slug = applyingRepo ? slugOf(applyingRepo) : "";
47364
47419
  const isHub = applyingRepo?.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
47365
47420
  let announcesToChannel = isHub;
47421
+ let isWorkSuite = false;
47366
47422
  let releaseLanguage2 = "en";
47367
47423
  if (!isHub && slug) {
47368
47424
  let read;
@@ -47376,9 +47432,17 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
47376
47432
  `release: Hub registry read failed for ${slug} (${read.error}) \u2014 whether ${slug} announces to a project release channel is UNREAD, so the --announce-summary-file requirement is unproven. Likely transient (cold start, network, or auth blip); retry shortly.`
47377
47433
  );
47378
47434
  }
47435
+ isWorkSuite = read.project?.suite === "mm-work";
47379
47436
  announcesToChannel = Boolean(read.project?.releaseChannel?.trim());
47380
47437
  releaseLanguage2 = read.project?.releaseLanguage?.trim() || "en";
47381
47438
  }
47439
+ if (isWorkSuite) {
47440
+ try {
47441
+ await requireSuiteReleaseSummary({}, o.announceSummaryFile);
47442
+ } catch (e) {
47443
+ return fail(`release: ${e.message}`);
47444
+ }
47445
+ }
47382
47446
  if (announcesToChannel) {
47383
47447
  if (!o.announceSummaryFile) {
47384
47448
  return fail(
@@ -47682,11 +47746,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
47682
47746
  const checkout = async (repo) => {
47683
47747
  if (repo === SUITE_REPO) return root;
47684
47748
  const target = (0, import_node_path43.join)(base, repo.split("/")[1]);
47685
- if (!(0, import_node_fs43.existsSync)(target)) {
47686
- (0, import_node_fs43.mkdirSync)(base, { recursive: true });
47749
+ if (!(0, import_node_fs44.existsSync)(target)) {
47750
+ (0, import_node_fs44.mkdirSync)(base, { recursive: true });
47687
47751
  await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
47688
47752
  }
47689
- if (!(0, import_node_fs43.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
47753
+ if (!(0, import_node_fs44.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
47690
47754
  const origin = await exec("git", ["remote", "get-url", "origin"], target);
47691
47755
  if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
47692
47756
  throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
@@ -47890,6 +47954,20 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
47890
47954
  };
47891
47955
  return {
47892
47956
  recordPath,
47957
+ validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile),
47958
+ validateReleaseSummaries: (releases) => validateReleaseSummaries(
47959
+ releases,
47960
+ { ...memberSummaryFiles, [SUITE_REPO]: summaryFile },
47961
+ async (repo, tag) => {
47962
+ try {
47963
+ await gh(["release", "view", tag, "--repo", repo, "--json", "tagName"]);
47964
+ return true;
47965
+ } catch (e) {
47966
+ if (/release not found|HTTP 404|Not Found|\(404\)/i.test(e.message)) return false;
47967
+ throw e;
47968
+ }
47969
+ }
47970
+ ),
47893
47971
  registry: registry2,
47894
47972
  backend,
47895
47973
  hold,
@@ -47970,10 +48048,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
47970
48048
  },
47971
48049
  // #7738: a member hotfixes as suite-solo; the lead hotfixes under its suite-lead capability (inTrain sets it).
47972
48050
  hotfixStart: (repo, o) => inTrain(repo, async (deps) => {
48051
+ await requireSuiteReleaseSummary(deps, summaryFile);
47973
48052
  const r = await runHotfixStart(deps, { from: o.from, ack: o.ack, suiteSolo: repo !== SUITE_REPO });
47974
48053
  return { tag: r.tag };
47975
48054
  }, repo !== SUITE_REPO),
47976
- hotfixRelease: (repo, tag) => inTrain(repo, async (deps) => ({ tag: (await runHotfixRelease(deps, tag, { suiteSolo: repo !== SUITE_REPO })).tag }), repo !== SUITE_REPO),
48055
+ hotfixRelease: (repo, tag) => inTrain(repo, async (deps) => ({ tag: (await runHotfixRelease(deps, tag, { suiteSolo: repo !== SUITE_REPO, announceSummaryFile: summaryFile, announceSummaryFileTr: summaryFileTr })).tag }), repo !== SUITE_REPO),
47977
48056
  // #7738: the #7714 solo pin leg, polled until the auto-merge lands (30 s x 40 = 20 min), then the caller decides.
47978
48057
  soloPin: async (repo, tag) => {
47979
48058
  for (let i = 0; ; i++) {
@@ -47994,13 +48073,27 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
47994
48073
  const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
47995
48074
  if (parseSuiteMapping(body)) return;
47996
48075
  const dir = suiteStateDir(root);
47997
- (0, import_node_fs43.mkdirSync)(dir, { recursive: true });
48076
+ (0, import_node_fs44.mkdirSync)(dir, { recursive: true });
47998
48077
  const file = (0, import_node_path43.join)(dir, `notes-${leadTag}.md`);
47999
- (0, import_node_fs43.writeFileSync)(file, `${body}
48078
+ (0, import_node_fs44.writeFileSync)(file, `${body}
48000
48079
 
48001
48080
  ${suiteMappingBlock(leadTag, mapping)}`);
48002
48081
  await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
48003
48082
  },
48083
+ productRuleset: async (repo) => {
48084
+ const read = await fetchProjectBySlugChecked(slugOf(repo), registryClientDeps(await loadConfig()));
48085
+ if (!read.ok) throw new Error(`registry META unreadable (${read.error})`);
48086
+ if (!read.project?.requiredChecks?.length) return ["skip: no registry META requiredChecks; the seeded ruleset stays as it is"];
48087
+ const result = await applyCiReconcileRepo(repo, {
48088
+ client: defaultGitHubClient(),
48089
+ listProjects: async () => [],
48090
+ getProjectMeta: async () => read.project
48091
+ }, { productRulesetOnly: true });
48092
+ const lines2 = [...result.applied, ...result.skipped.map((s) => `skip: ${s}`)];
48093
+ lines2.forEach((line) => console.log(`${repo}: product ruleset ${line}`));
48094
+ if (result.errors.length) throw new Error(result.errors.join("; "));
48095
+ return lines2;
48096
+ },
48004
48097
  release,
48005
48098
  shippedTag,
48006
48099
  sameContent: sameContent2,
@@ -48023,7 +48116,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
48023
48116
 
48024
48117
  // src/suite-commands.ts
48025
48118
  init_cli_shared();
48026
- var import_node_fs44 = require("node:fs");
48119
+ var import_node_fs45 = require("node:fs");
48027
48120
  function parseMemberSummaryFiles(entries) {
48028
48121
  const memberSummaryFiles = {};
48029
48122
  for (const entry of entries) {
@@ -48033,12 +48126,6 @@ function parseMemberSummaryFiles(entries) {
48033
48126
  }
48034
48127
  return memberSummaryFiles;
48035
48128
  }
48036
- function validateSuiteReleaseSummary(path2) {
48037
- if (!path2) throw new Error("suite release requires --announce-summary-file before promoting any member");
48038
- const lines2 = (0, import_node_fs44.readFileSync)(path2, "utf8").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
48039
- if (lines2[0]?.startsWith("No changes to this module")) throw new Error("suite release requires a real MM-Work title, not the no-change placeholder");
48040
- if (lines2.length < 3 || lines2.length > 6) throw new Error("suite release announcement needs 3\u20136 curated lines before promoting any member");
48041
- }
48042
48129
  function suiteRcDataProdDeps() {
48043
48130
  return {
48044
48131
  fetch: async (url, init) => {
@@ -48111,7 +48198,7 @@ function registerSuiteCommands(program3) {
48111
48198
  try {
48112
48199
  const io = suiteTrainIo(root);
48113
48200
  retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
48114
- if (retired && !(0, import_node_fs44.existsSync)(io.recordPath)) {
48201
+ if (retired && !(0, import_node_fs45.existsSync)(io.recordPath)) {
48115
48202
  console.log(o.json ? JSON.stringify({ retiredLeadRc: retired }, null, 2) : `suite abandon: retired the lead's failed, never-promoted rc candidate ${retired}; marked abandoned on origin (#7854)`);
48116
48203
  return;
48117
48204
  }
@@ -48133,10 +48220,9 @@ function registerSuiteCommands(program3) {
48133
48220
  return fail(`suite abandon: ${e.message}`);
48134
48221
  }
48135
48222
  });
48136
- suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: a member's OWN plain-words summary (#7510) \u2014 never the suite lead's summary reused; a member with no entry still ships, with the coordinator-ruled no-change line", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "Turkish plain-words summary for MM-Work itself").option("--member-summary-file-tr <owner/repo=path>", "repeatable: a member's own Turkish plain-words summary", (val, prev = []) => [...prev, val], []).option("--big", "marks MM-Work's own release big for the plain-words section (#7510); a member is never marked big through this flag \u2014 it is big only via the read route's automatic minor/major detection").option("--json", "structured receipt").action(async (o) => {
48223
+ suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required title + 2\u20135 notes for each member being released; carried members need none", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "Turkish plain-words summary for MM-Work itself").option("--member-summary-file-tr <owner/repo=path>", "repeatable: a member's own Turkish plain-words summary", (val, prev = []) => [...prev, val], []).option("--big", "marks MM-Work's own release big for the plain-words section (#7510); a member is never marked big through this flag \u2014 it is big only via the read route's automatic minor/major detection").option("--json", "structured receipt").action(async (o) => {
48137
48224
  try {
48138
48225
  if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
48139
- validateSuiteReleaseSummary(o.announceSummaryFile);
48140
48226
  const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
48141
48227
  const memberSummaryFilesTr = parseMemberSummaryFiles(o.memberSummaryFileTr ?? []);
48142
48228
  const root = await leadRoot();
@@ -48157,14 +48243,14 @@ function registerSuiteCommands(program3) {
48157
48243
  return fail(`suite release: ${e.message}`);
48158
48244
  }
48159
48245
  });
48160
- suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
48246
+ suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required title + 2\u20135 notes for the member and lead hotfix Releases").option("--announce-summary-file-tr <path>", "optional Turkish plain-words notes").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
48161
48247
  try {
48162
48248
  if (!o.ownerGo && !o.dryRun) throw new Error("suite hotfix requires --owner-go (it ships to production); nothing was written. --dry-run prints the plan");
48163
48249
  const root = await leadRoot();
48164
48250
  const unlock = o.dryRun ? async () => void 0 : await acquireTrainWriterLock(root, "suite hotfix");
48165
48251
  let result;
48166
48252
  try {
48167
- result = await suiteHotfix(suiteTrainIo(root), { member, from: o.from, ack: o.ack?.split(",").map((s) => s.trim()).filter(Boolean), ownerGo: o.ownerGo, dryRun: o.dryRun });
48253
+ result = await suiteHotfix(suiteTrainIo(root, o.announceSummaryFile, {}, o.announceSummaryFileTr), { member, from: o.from, ack: o.ack?.split(",").map((s) => s.trim()).filter(Boolean), ownerGo: o.ownerGo, dryRun: o.dryRun });
48168
48254
  } finally {
48169
48255
  await unlock();
48170
48256
  }
@@ -48301,7 +48387,7 @@ function suiteStatusDeps(github, registry2) {
48301
48387
 
48302
48388
  // src/packages-adopt-export.ts
48303
48389
  var import_node_child_process18 = require("node:child_process");
48304
- var import_node_fs45 = require("node:fs");
48390
+ var import_node_fs46 = require("node:fs");
48305
48391
  var import_node_os21 = require("node:os");
48306
48392
  var import_node_path44 = require("node:path");
48307
48393
  var import_node_util6 = require("node:util");
@@ -48925,36 +49011,36 @@ async function adoptExport(opts, deps) {
48925
49011
  if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
48926
49012
  const packagePath = (0, import_node_path44.join)(opts.dir, "package.json");
48927
49013
  const lockPath = (0, import_node_path44.join)(opts.dir, "package-lock.json");
48928
- const packageText = (0, import_node_fs45.readFileSync)(packagePath, "utf8");
48929
- const lockText = (0, import_node_fs45.readFileSync)(lockPath, "utf8");
48930
- const work = (0, import_node_fs45.mkdtempSync)((0, import_node_path44.join)((0, import_node_os21.tmpdir)(), "mmi-adopt-export-"));
49014
+ const packageText = (0, import_node_fs46.readFileSync)(packagePath, "utf8");
49015
+ const lockText = (0, import_node_fs46.readFileSync)(lockPath, "utf8");
49016
+ const work = (0, import_node_fs46.mkdtempSync)((0, import_node_path44.join)((0, import_node_os21.tmpdir)(), "mmi-adopt-export-"));
48931
49017
  try {
48932
49018
  const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
48933
- (0, import_node_fs45.writeFileSync)((0, import_node_path44.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
48934
- (0, import_node_fs45.writeFileSync)((0, import_node_path44.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
48935
- (0, import_node_fs45.writeFileSync)((0, import_node_path44.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
49019
+ (0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
49020
+ (0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
49021
+ (0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
48936
49022
  const script = (0, import_node_path44.join)(work, "package_export.py");
48937
- (0, import_node_fs45.writeFileSync)(script, package_export_default);
49023
+ (0, import_node_fs46.writeFileSync)(script, package_export_default);
48938
49024
  await deps.python(script, ["verify", work, opts.ds, opts.screens]);
48939
49025
  const verified = (0, import_node_path44.join)(work, "verified");
48940
- const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path44.join)(verified, `${short3}.metadata.json`), "utf8"))]));
49026
+ const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path44.join)(verified, `${short3}.metadata.json`), "utf8"))]));
48941
49027
  const plan = planAdoption(packageText, lockText, metadata);
48942
49028
  for (const { name } of plan.result.adopted) {
48943
49029
  const short3 = PACKAGES.find((p) => p.name === name).short;
48944
49030
  await deps.npm(["cache", "add", (0, import_node_path44.join)(verified, `${short3}.tgz`)], opts.dir);
48945
49031
  }
48946
- (0, import_node_fs45.writeFileSync)(packagePath, plan.packageText);
48947
- (0, import_node_fs45.writeFileSync)(lockPath, plan.lockText);
49032
+ (0, import_node_fs46.writeFileSync)(packagePath, plan.packageText);
49033
+ (0, import_node_fs46.writeFileSync)(lockPath, plan.lockText);
48948
49034
  try {
48949
49035
  await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
48950
49036
  } catch (error) {
48951
- (0, import_node_fs45.writeFileSync)(packagePath, packageText);
48952
- (0, import_node_fs45.writeFileSync)(lockPath, lockText);
49037
+ (0, import_node_fs46.writeFileSync)(packagePath, packageText);
49038
+ (0, import_node_fs46.writeFileSync)(lockPath, lockText);
48953
49039
  throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
48954
49040
  }
48955
49041
  return plan.result;
48956
49042
  } finally {
48957
- (0, import_node_fs45.rmSync)(work, { recursive: true, force: true });
49043
+ (0, import_node_fs46.rmSync)(work, { recursive: true, force: true });
48958
49044
  }
48959
49045
  }
48960
49046
  var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process18.execFile);
@@ -49019,12 +49105,12 @@ function registerPackagesCommands(program3) {
49019
49105
  init_cli_shared();
49020
49106
 
49021
49107
  // src/wave-status.ts
49022
- var import_node_fs46 = require("node:fs");
49108
+ var import_node_fs47 = require("node:fs");
49023
49109
  function readStageSummary(worktreePath) {
49024
49110
  const statePath = stageStatePath(worktreePath);
49025
- if (!(0, import_node_fs46.existsSync)(statePath)) return void 0;
49111
+ if (!(0, import_node_fs47.existsSync)(statePath)) return void 0;
49026
49112
  try {
49027
- const state = JSON.parse((0, import_node_fs46.readFileSync)(statePath, "utf8"));
49113
+ const state = JSON.parse((0, import_node_fs47.readFileSync)(statePath, "utf8"));
49028
49114
  const port = typeof state.port === "number" ? state.port : void 0;
49029
49115
  if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
49030
49116
  return { port, url: typeof state.url === "string" ? state.url : void 0 };
@@ -49081,7 +49167,7 @@ async function collectWaveStatus(deps) {
49081
49167
  // src/discovery-commands.ts
49082
49168
  init_github_client();
49083
49169
  init_cli_shared();
49084
- var import_node_fs47 = require("node:fs");
49170
+ var import_node_fs48 = require("node:fs");
49085
49171
  var import_node_os22 = require("node:os");
49086
49172
  var import_node_path45 = require("node:path");
49087
49173
  var GC_GH_TIMEOUT_MS3 = 2e4;
@@ -49274,8 +49360,8 @@ async function collectOnboardStatus(opts = {}) {
49274
49360
  }
49275
49361
  const home = (0, import_node_os22.homedir)();
49276
49362
  const plugin = onboardPluginGate({
49277
- readKnown: () => readFileSyncSafe((0, import_node_path45.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs47.readFileSync),
49278
- readSettings: () => readFileSyncSafe((0, import_node_path45.join)(home, ".claude", "settings.json"), import_node_fs47.readFileSync)
49363
+ readKnown: () => readFileSyncSafe((0, import_node_path45.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs48.readFileSync),
49364
+ readSettings: () => readFileSyncSafe((0, import_node_path45.join)(home, ".claude", "settings.json"), import_node_fs48.readFileSync)
49279
49365
  });
49280
49366
  return {
49281
49367
  guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
@@ -52379,7 +52465,7 @@ function registerSessionReport(program3) {
52379
52465
  }
52380
52466
 
52381
52467
  // src/stage-commands.ts
52382
- var import_node_fs48 = require("node:fs");
52468
+ var import_node_fs49 = require("node:fs");
52383
52469
  var import_node_path46 = require("node:path");
52384
52470
  init_cli_shared();
52385
52471
  init_clean_exit();
@@ -52628,8 +52714,8 @@ function registerStageCommands(program3) {
52628
52714
  const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
52629
52715
  const resolution = decideStage({
52630
52716
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
52631
- hasCompose: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), "docker-compose.yml")),
52632
- hasEnvExample: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), ".env.example")),
52717
+ hasCompose: (0, import_node_fs49.existsSync)((0, import_node_path46.join)(process.cwd(), "docker-compose.yml")),
52718
+ hasEnvExample: (0, import_node_fs49.existsSync)((0, import_node_path46.join)(process.cwd(), ".env.example")),
52633
52719
  repo
52634
52720
  });
52635
52721
  if (resolution.source === "derived" && resolution.derived) {
@@ -52922,14 +53008,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
52922
53008
 
52923
53009
  // src/tenant-artifact.ts
52924
53010
  var import_node_crypto16 = require("node:crypto");
52925
- var import_node_fs49 = require("node:fs");
53011
+ var import_node_fs50 = require("node:fs");
52926
53012
  var import_promises11 = require("node:fs/promises");
52927
53013
  var import_node_path47 = require("node:path");
52928
53014
  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}$/;
52929
53015
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
52930
53016
  async function sha256File(path2) {
52931
53017
  const hash = (0, import_node_crypto16.createHash)("sha256");
52932
- for await (const chunk of (0, import_node_fs49.createReadStream)(path2)) hash.update(chunk);
53018
+ for await (const chunk of (0, import_node_fs50.createReadStream)(path2)) hash.update(chunk);
52933
53019
  return hash.digest("hex");
52934
53020
  }
52935
53021
  async function putTenantArtifact(repo, stage, inputPath, deps) {
@@ -52952,7 +53038,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
52952
53038
  return [key, value];
52953
53039
  }));
52954
53040
  headers["content-length"] = String(info.size);
52955
- const stream = (0, import_node_fs49.createReadStream)(path2);
53041
+ const stream = (0, import_node_fs50.createReadStream)(path2);
52956
53042
  let uploaded;
52957
53043
  try {
52958
53044
  uploaded = await fetch(body.uploadUrl, {
@@ -53091,7 +53177,7 @@ function renderVerifySecrets(body) {
53091
53177
  }
53092
53178
 
53093
53179
  // src/command-register-developer.ts
53094
- var import_node_fs53 = require("node:fs");
53180
+ var import_node_fs54 = require("node:fs");
53095
53181
  init_clean_exit();
53096
53182
  init_cli_shared();
53097
53183
  init_error_codes();
@@ -53159,13 +53245,13 @@ async function executeWaveLand(plan, deps) {
53159
53245
  // src/dist-drift.ts
53160
53246
  var import_node_child_process19 = require("node:child_process");
53161
53247
  var import_node_crypto18 = require("node:crypto");
53162
- var import_node_fs51 = require("node:fs");
53248
+ var import_node_fs52 = require("node:fs");
53163
53249
  var import_node_os23 = require("node:os");
53164
53250
  var import_node_path49 = require("node:path");
53165
53251
 
53166
53252
  // ../scripts/distribution-digest.mjs
53167
53253
  var import_node_crypto17 = require("node:crypto");
53168
- var import_node_fs50 = require("node:fs");
53254
+ var import_node_fs51 = require("node:fs");
53169
53255
  var import_node_path48 = require("node:path");
53170
53256
  var slash = (value) => value.replaceAll("\\", "/");
53171
53257
  function repoPath(root, declaredPath, label) {
@@ -53179,7 +53265,7 @@ function repoPath(root, declaredPath, label) {
53179
53265
  function digestFiles(files) {
53180
53266
  const hash = (0, import_node_crypto17.createHash)("sha256");
53181
53267
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
53182
- const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs50.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs50.readFileSync)(file.absolute);
53268
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs51.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs51.readFileSync)(file.absolute);
53183
53269
  hash.update(file.relative, "utf8");
53184
53270
  hash.update("\0");
53185
53271
  hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
@@ -53194,8 +53280,8 @@ function digestFiles(files) {
53194
53280
  function digestPackedFiles(packageRoot, packedFiles) {
53195
53281
  return digestFiles(packedFiles.map((path2) => {
53196
53282
  const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
53197
- if (!(0, import_node_fs50.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
53198
- return { absolute, relative: slash(path2), stat: (0, import_node_fs50.lstatSync)(absolute) };
53283
+ if (!(0, import_node_fs51.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
53284
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs51.lstatSync)(absolute) };
53199
53285
  }));
53200
53286
  }
53201
53287
 
@@ -53279,12 +53365,12 @@ function renderDistDriftReceipt(receipt) {
53279
53365
  return lines2;
53280
53366
  }
53281
53367
  function readOrNull(path2) {
53282
- return (0, import_node_fs51.existsSync)(path2) ? (0, import_node_fs51.readFileSync)(path2) : null;
53368
+ return (0, import_node_fs52.existsSync)(path2) ? (0, import_node_fs52.readFileSync)(path2) : null;
53283
53369
  }
53284
53370
  function walkFiles(root) {
53285
53371
  const files = [];
53286
53372
  const walk2 = (directory) => {
53287
- for (const entry of (0, import_node_fs51.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
53373
+ for (const entry of (0, import_node_fs52.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
53288
53374
  const child2 = (0, import_node_path49.join)(directory, entry.name);
53289
53375
  if (entry.isDirectory()) walk2(child2);
53290
53376
  else files.push(child2);
@@ -53295,7 +53381,7 @@ function walkFiles(root) {
53295
53381
  }
53296
53382
  function bomPathFor(root) {
53297
53383
  try {
53298
- const registry2 = JSON.parse((0, import_node_fs51.readFileSync)((0, import_node_path49.join)(root, "surfaces.json"), "utf8"));
53384
+ const registry2 = JSON.parse((0, import_node_fs52.readFileSync)((0, import_node_path49.join)(root, "surfaces.json"), "utf8"));
53299
53385
  return (0, import_node_path49.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
53300
53386
  } catch {
53301
53387
  return (0, import_node_path49.join)(root, "distribution-bom.json");
@@ -53311,7 +53397,7 @@ function rebuildTo(packageRoot, outDir) {
53311
53397
  });
53312
53398
  }
53313
53399
  function runDistStatus(root) {
53314
- const stage = (0, import_node_fs51.mkdtempSync)((0, import_node_path49.join)((0, import_node_os23.tmpdir)(), "mmi-dist-drift-"));
53400
+ const stage = (0, import_node_fs52.mkdtempSync)((0, import_node_path49.join)((0, import_node_os23.tmpdir)(), "mmi-dist-drift-"));
53315
53401
  let overlayCount = 0;
53316
53402
  try {
53317
53403
  const cliOut = (0, import_node_path49.join)(stage, "cli-dist");
@@ -53327,19 +53413,19 @@ function runDistStatus(root) {
53327
53413
  const tree = (path2) => readOrNull((0, import_node_path49.join)(root, path2));
53328
53414
  const distRoot = (0, import_node_path49.join)(root, "cli", "dist");
53329
53415
  const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path49.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
53330
- const bom = JSON.parse((0, import_node_fs51.readFileSync)(bomPathFor(root), "utf8"));
53416
+ const bom = JSON.parse((0, import_node_fs52.readFileSync)(bomPathFor(root), "utf8"));
53331
53417
  const digest = (entries) => {
53332
53418
  const overlay = (0, import_node_path49.join)(stage, `overlay-${overlayCount++}`);
53333
53419
  for (const entry of entries) {
53334
53420
  const target = (0, import_node_path49.join)(overlay, entry.path);
53335
- (0, import_node_fs51.mkdirSync)((0, import_node_path49.dirname)(target), { recursive: true });
53336
- (0, import_node_fs51.writeFileSync)(target, entry.bytes);
53421
+ (0, import_node_fs52.mkdirSync)((0, import_node_path49.dirname)(target), { recursive: true });
53422
+ (0, import_node_fs52.writeFileSync)(target, entry.bytes);
53337
53423
  }
53338
53424
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
53339
53425
  };
53340
53426
  return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
53341
53427
  } finally {
53342
- (0, import_node_fs51.rmSync)(stage, { recursive: true, force: true });
53428
+ (0, import_node_fs52.rmSync)(stage, { recursive: true, force: true });
53343
53429
  }
53344
53430
  }
53345
53431
 
@@ -53552,7 +53638,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
53552
53638
 
53553
53639
  // src/spawn-policy-core.ts
53554
53640
  var import_node_child_process20 = require("node:child_process");
53555
- var import_node_fs52 = require("node:fs");
53641
+ var import_node_fs53 = require("node:fs");
53556
53642
  var import_node_path51 = require("node:path");
53557
53643
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
53558
53644
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
@@ -53639,7 +53725,7 @@ function runSpawnPolicy(root) {
53639
53725
  for (const file of files) {
53640
53726
  let raw;
53641
53727
  try {
53642
- raw = (0, import_node_fs52.readFileSync)((0, import_node_path51.join)(root, file), "utf8");
53728
+ raw = (0, import_node_fs53.readFileSync)((0, import_node_path51.join)(root, file), "utf8");
53643
53729
  } catch {
53644
53730
  continue;
53645
53731
  }
@@ -53660,18 +53746,18 @@ function registerDeveloperCommands(program3) {
53660
53746
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
53661
53747
  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) => {
53662
53748
  const path2 = (0, import_node_path52.join)(process.cwd(), ".gitignore");
53663
- const current = (0, import_node_fs53.existsSync)(path2) ? (0, import_node_fs53.readFileSync)(path2, "utf8") : null;
53749
+ const current = (0, import_node_fs54.existsSync)(path2) ? (0, import_node_fs54.readFileSync)(path2, "utf8") : null;
53664
53750
  const plan = planManagedGitignore(current);
53665
53751
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
53666
53752
  if (opts.json) {
53667
- if (opts.write && plan.changed) (0, import_node_fs53.writeFileSync)(path2, plan.content, "utf8");
53753
+ if (opts.write && plan.changed) (0, import_node_fs54.writeFileSync)(path2, plan.content, "utf8");
53668
53754
  console.log(JSON.stringify(plan, null, 2));
53669
53755
  if (!opts.write && plan.changed) process.exitCode = 1;
53670
53756
  return;
53671
53757
  }
53672
53758
  if (opts.write) {
53673
53759
  if (plan.changed) {
53674
- (0, import_node_fs53.writeFileSync)(path2, plan.content, "utf8");
53760
+ (0, import_node_fs54.writeFileSync)(path2, plan.content, "utf8");
53675
53761
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
53676
53762
  } else {
53677
53763
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -54071,7 +54157,7 @@ function mmiDoctorDeps(opts = {}) {
54071
54157
  );
54072
54158
  const result = applyPluginCachePlan(
54073
54159
  plan,
54074
- (p) => (0, import_node_fs54.rmSync)(p, { recursive: true }),
54160
+ (p) => (0, import_node_fs55.rmSync)(p, { recursive: true }),
54075
54161
  stagingApplyFsGuard(configRoot)
54076
54162
  );
54077
54163
  return {
@@ -54106,7 +54192,7 @@ function mmiDoctorDeps(opts = {}) {
54106
54192
  // has no generated routing index would
54107
54193
  // get a permanent — demanding an artifact it never asked for.
54108
54194
  docsIndexState: (root) => {
54109
- if (!(0, import_node_fs54.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return void 0;
54195
+ if (!(0, import_node_fs55.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return void 0;
54110
54196
  const real = createDocsIndexDeps(root);
54111
54197
  let docs;
54112
54198
  const listDocs = () => docs ??= real.listDocs();
@@ -54115,7 +54201,7 @@ function mmiDoctorDeps(opts = {}) {
54115
54201
  },
54116
54202
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
54117
54203
  healDocsIndex: (root) => {
54118
- if (!(0, import_node_fs54.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
54204
+ if (!(0, import_node_fs55.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
54119
54205
  const real = createDocsIndexDeps(root);
54120
54206
  let docs;
54121
54207
  const listDocs = () => docs ??= real.listDocs();
@@ -54829,7 +54915,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
54829
54915
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
54830
54916
  if (o.secretsFile) {
54831
54917
  try {
54832
- vars.push(`secrets=${(0, import_node_fs54.readFileSync)(o.secretsFile, "utf8")}`);
54918
+ vars.push(`secrets=${(0, import_node_fs55.readFileSync)(o.secretsFile, "utf8")}`);
54833
54919
  } catch (e) {
54834
54920
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
54835
54921
  }
@@ -55218,15 +55304,15 @@ function ciAuditDeps2() {
55218
55304
  readSeedFile: (path2) => {
55219
55305
  if (!root) return null;
55220
55306
  const fullPath = (0, import_node_path53.join)(root, path2);
55221
- return (0, import_node_fs54.existsSync)(fullPath) ? (0, import_node_fs54.readFileSync)(fullPath, "utf8") : null;
55307
+ return (0, import_node_fs55.existsSync)(fullPath) ? (0, import_node_fs55.readFileSync)(fullPath, "utf8") : null;
55222
55308
  }
55223
55309
  };
55224
55310
  }
55225
55311
  function hubRoot2() {
55226
55312
  const fromPkg = (0, import_node_path53.join)(__dirname, "..", "..");
55227
55313
  const marker = "skills/bootstrap/seeds/manifest.json";
55228
- if ((0, import_node_fs54.existsSync)((0, import_node_path53.join)(fromPkg, marker))) return fromPkg;
55229
- if ((0, import_node_fs54.existsSync)((0, import_node_path53.join)(process.cwd(), marker))) return process.cwd();
55314
+ if ((0, import_node_fs55.existsSync)((0, import_node_path53.join)(fromPkg, marker))) return fromPkg;
55315
+ if ((0, import_node_fs55.existsSync)((0, import_node_path53.join)(process.cwd(), marker))) return process.cwd();
55230
55316
  return null;
55231
55317
  }
55232
55318
  registerQueryCommands(program2);
@@ -55352,10 +55438,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
55352
55438
  targets = resolution.targets;
55353
55439
  }
55354
55440
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
55355
- const fileMatrix = (0, import_node_fs54.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs54.readFileSync)("access-matrix.json", "utf8")) : {};
55441
+ const fileMatrix = (0, import_node_fs55.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs55.readFileSync)("access-matrix.json", "utf8")) : {};
55356
55442
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
55357
55443
  const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
55358
- const sanctioned = (0, import_node_fs54.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs54.readFileSync)("access-matrix.json", "utf8")) : {};
55444
+ const sanctioned = (0, import_node_fs55.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs55.readFileSync)("access-matrix.json", "utf8")) : {};
55359
55445
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
55360
55446
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
55361
55447
  if (!report.ok) process.exitCode = 1;
@@ -55386,7 +55472,7 @@ function directoryBytes(path2) {
55386
55472
  let total = 0;
55387
55473
  let entries;
55388
55474
  try {
55389
- entries = (0, import_node_fs54.readdirSync)(path2, { withFileTypes: true });
55475
+ entries = (0, import_node_fs55.readdirSync)(path2, { withFileTypes: true });
55390
55476
  } catch {
55391
55477
  return 0;
55392
55478
  }
@@ -55395,7 +55481,7 @@ function directoryBytes(path2) {
55395
55481
  if (entry.isDirectory()) total += directoryBytes(child2);
55396
55482
  else {
55397
55483
  try {
55398
- total += (0, import_node_fs54.statSync)(child2).size;
55484
+ total += (0, import_node_fs55.statSync)(child2).size;
55399
55485
  } catch {
55400
55486
  }
55401
55487
  }
@@ -55403,25 +55489,25 @@ function directoryBytes(path2) {
55403
55489
  return total;
55404
55490
  }
55405
55491
  function listDirEntries(dir) {
55406
- return (0, import_node_fs54.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
55492
+ return (0, import_node_fs55.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
55407
55493
  }
55408
55494
  function readInstalledPluginRefs(configRoot) {
55409
55495
  const p = installedPluginsPathForConfig(configRoot);
55410
- if (!(0, import_node_fs54.existsSync)(p)) return [];
55496
+ if (!(0, import_node_fs55.existsSync)(p)) return [];
55411
55497
  try {
55412
- return installedPluginPaths((0, import_node_fs54.readFileSync)(p, "utf8"));
55498
+ return installedPluginPaths((0, import_node_fs55.readFileSync)(p, "utf8"));
55413
55499
  } catch {
55414
55500
  return null;
55415
55501
  }
55416
55502
  }
55417
55503
  function pluginCacheFsDeps(configRoot, dirBytes) {
55418
55504
  return {
55419
- exists: (p) => (0, import_node_fs54.existsSync)(p),
55420
- listVersionDirs: (root) => (0, import_node_fs54.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
55505
+ exists: (p) => (0, import_node_fs55.existsSync)(p),
55506
+ listVersionDirs: (root) => (0, import_node_fs55.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
55421
55507
  dirBytes,
55422
- listStagingDirs: (root) => (0, import_node_fs54.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
55508
+ listStagingDirs: (root) => (0, import_node_fs55.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
55423
55509
  try {
55424
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path53.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs54.statSync)(p).mtimeMs) };
55510
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path53.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs55.statSync)(p).mtimeMs) };
55425
55511
  } catch {
55426
55512
  return { name: d.name, mtimeMs: Date.now() };
55427
55513
  }
@@ -55436,9 +55522,9 @@ function stagingApplyFsGuard(configRoot) {
55436
55522
  referencedPaths: () => readInstalledPluginRefs(configRoot),
55437
55523
  mtimeMs: (name) => {
55438
55524
  const p = (0, import_node_path53.join)(stagingRoot, name);
55439
- if (!(0, import_node_fs54.existsSync)(p)) return null;
55525
+ if (!(0, import_node_fs55.existsSync)(p)) return null;
55440
55526
  try {
55441
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs54.statSync)(q).mtimeMs);
55527
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs55.statSync)(q).mtimeMs);
55442
55528
  } catch {
55443
55529
  return null;
55444
55530
  }
@@ -55464,7 +55550,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
55464
55550
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
55465
55551
  );
55466
55552
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
55467
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs54.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
55553
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs55.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
55468
55554
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
55469
55555
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
55470
55556
  else console.log(renderPluginCachePlan(plan, result));