@mutmutco/cli 3.135.0 → 3.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.cjs +956 -460
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ var program = new Command();
3416
3416
 
3417
3417
  // src/index.ts
3418
3418
  var import_promises12 = require("node:fs/promises");
3419
- var import_node_fs47 = require("node:fs");
3419
+ var import_node_fs48 = require("node:fs");
3420
3420
  var import_node_child_process19 = require("node:child_process");
3421
3421
 
3422
3422
  // src/cli-shared.ts
@@ -5502,7 +5502,9 @@ function parseWorktreeOwners(text) {
5502
5502
  return parsed.entries.filter((e) => {
5503
5503
  if (!e || typeof e !== "object") return false;
5504
5504
  const entry = e;
5505
- return typeof entry.path === "string" && entry.path.length > 0 && typeof entry.branch === "string" && typeof entry.createdAt === "string" && typeof entry.lastSeenAt === "string" && isActor(entry.actor);
5505
+ const provenance = entry.provenance;
5506
+ const provenanceValid = provenance === void 0 || typeof provenance === "object" && typeof provenance.creationBaseOid === "string" && /^[0-9a-f]{40,64}$/i.test(provenance.creationBaseOid) && typeof provenance.workerBranch === "string" && provenance.workerBranch.length > 0;
5507
+ return typeof entry.path === "string" && entry.path.length > 0 && typeof entry.branch === "string" && typeof entry.createdAt === "string" && typeof entry.lastSeenAt === "string" && isActor(entry.actor) && provenanceValid;
5506
5508
  });
5507
5509
  }
5508
5510
  function serializeWorktreeOwners(entries) {
@@ -5847,6 +5849,21 @@ async function sweepDeferredWorktrees(store, deps, removalContext) {
5847
5849
  continue;
5848
5850
  }
5849
5851
  const owner = removalContext ? lookupWorktreeOwner(removalContext.primaryRoot, entry.path) : void 0;
5852
+ if (!current && deps.pathExists?.(entry.path) === false) {
5853
+ removed.push(entry.path);
5854
+ if (removalContext) {
5855
+ recordWorktreeRemoval(removalContext.primaryRoot, {
5856
+ action: "removed",
5857
+ command: removalContext.command,
5858
+ target: entry.path,
5859
+ branch: entry.branch,
5860
+ actor: removalContext.actor,
5861
+ owner,
5862
+ reason: "deferred worktree already absent from Git registration and filesystem"
5863
+ });
5864
+ }
5865
+ continue;
5866
+ }
5850
5867
  if (removalContext) {
5851
5868
  const activeRoot = removalContext.activeWorkspaceRoot ?? resolveActiveWorkspaceRoot();
5852
5869
  const activeGuard = decideActiveWorkspaceGuard(entry.path, activeRoot, process.platform, {
@@ -6400,6 +6417,8 @@ function buildGcPlan(inputs) {
6400
6417
  const preservedBranches2 = new Set(inputs.preservedBranches ?? []);
6401
6418
  const preservedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.preserved).map((w) => w.branch));
6402
6419
  const mergedIntoBase = new Set((inputs.mergedIntoBase ?? []).map((b) => b.trim()).filter(Boolean));
6420
+ const exactTreeLanded = new Map((inputs.exactTreeLanded ?? []).map((p) => [p.branch.trim(), p]));
6421
+ const exactTreeRefusals = new Map((inputs.exactTreeRefusals ?? []).map((p) => [p.branch.trim(), p.detail]));
6403
6422
  const prLookupFailures = new Map((inputs.prLookupFailures ?? []).map((f) => [f.branch.trim(), f.detail]));
6404
6423
  const skipped = [];
6405
6424
  const branches = [];
@@ -6426,8 +6445,10 @@ function buildGcPlan(inputs) {
6426
6445
  skipped.push({ branch, reason: "open-pr" });
6427
6446
  continue;
6428
6447
  }
6429
- const state = closedState(prSet);
6430
- if (!state) continue;
6448
+ const exactDelivery = exactTreeLanded.get(branch);
6449
+ const exactRefusal = exactTreeRefusals.get(branch);
6450
+ const state = closedState(prSet) ?? (exactDelivery ? { state: "EXACT_TREE", numbers: [], headOids: [] } : null);
6451
+ if (!state && !exactRefusal) continue;
6431
6452
  if (branch === inputs.currentBranch) {
6432
6453
  skipped.push({ branch, reason: "current-branch" });
6433
6454
  skipTrackingBranches.add(branch);
@@ -6444,8 +6465,14 @@ function buildGcPlan(inputs) {
6444
6465
  skipTrackingBranches.add(branch);
6445
6466
  continue;
6446
6467
  }
6468
+ if (!state) {
6469
+ skipped.push({ branch, reason: "exact-tree-proof-refused", detail: exactRefusal });
6470
+ skipTrackingBranches.add(branch);
6471
+ continue;
6472
+ }
6447
6473
  const localHead = branchHeads?.get(branch);
6448
- const containedInBase = mergedIntoBase.has(branch);
6474
+ const exactHeadStillMatches = Boolean(exactDelivery && localHead === exactDelivery.headOid);
6475
+ const containedInBase = mergedIntoBase.has(branch) || exactHeadStillMatches;
6449
6476
  if (!containedInBase && (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead)))) {
6450
6477
  const detail = worktree2?.unpushed ? worktree2.path : localHead && state.headOids.length ? `${localHead} != ${state.headOids.join("|")}` : "local branch head could not be verified against the PR head";
6451
6478
  skipped.push({ branch, reason: "unpushed-branch", detail });
@@ -6458,7 +6485,8 @@ function buildGcPlan(inputs) {
6458
6485
  prNumbers: state.numbers,
6459
6486
  worktreePath: worktree2?.path,
6460
6487
  ...state.headOids.length ? { reviewedHeadOids: state.headOids } : {},
6461
- ...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {}
6488
+ ...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {},
6489
+ ...exactHeadStillMatches ? { deliveredBy: exactDelivery.candidateOid } : {}
6462
6490
  });
6463
6491
  }
6464
6492
  const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
@@ -7060,7 +7088,8 @@ function formatGcPlan(plan, apply, auditClone) {
7060
7088
  for (const b of plan.branches) {
7061
7089
  const prs = b.prNumbers.length ? ` #${b.prNumbers.join(",#")}` : "";
7062
7090
  const wt = b.worktreePath ? ` (worktree: ${b.worktreePath})` : "";
7063
- lines.push(` - ${b.branch} (${b.prState}${prs})${wt}`);
7091
+ const authority = b.deliveredBy ? `${b.prState}; deliveredBy ${b.deliveredBy}` : `${b.prState}${prs}`;
7092
+ lines.push(` - ${b.branch} (${authority})${wt}`);
7064
7093
  }
7065
7094
  }
7066
7095
  const remoteBranches = plan.branches.filter((b) => (b.reviewedHeadOids?.length ?? 0) > 0);
@@ -7869,7 +7898,7 @@ function commandLadderHint() {
7869
7898
  }
7870
7899
 
7871
7900
  // src/index.ts
7872
- var import_node_path45 = require("node:path");
7901
+ var import_node_path46 = require("node:path");
7873
7902
 
7874
7903
  // src/merge-ci-policy.ts
7875
7904
  function resolveMergeCiPolicy(input) {
@@ -8384,6 +8413,12 @@ function loadBootstrapSeeds(manifestJson) {
8384
8413
  if (s.ownership !== "org" && s.ownership !== "repo") {
8385
8414
  throw new Error(`invalid seed ownership '${s.ownership}' for ${s.target} (must be 'org' or 'repo')`);
8386
8415
  }
8416
+ if (s.managedBlock) {
8417
+ const block = s.managedBlock;
8418
+ if (!block.source || !block.begin || !block.end || block.begin === block.end || /[\r\n]/.test(block.begin + block.end)) {
8419
+ throw new Error(`invalid managedBlock for ${s.target} (needs source and distinct single-line begin/end markers)`);
8420
+ }
8421
+ }
8387
8422
  }
8388
8423
  return {
8389
8424
  seeds,
@@ -8399,6 +8434,68 @@ function missingPlaceholders(rendered) {
8399
8434
  for (const m of rendered.matchAll(PLACEHOLDER_RE)) out.add(m[1]);
8400
8435
  return [...out];
8401
8436
  }
8437
+ function isPropagatableSeed(seed) {
8438
+ return seed.ownership === "org" && seed.source === "self" || seed.managedBlock != null;
8439
+ }
8440
+ function sourceLines(content) {
8441
+ const lines = [];
8442
+ const newline = /\r\n|\n|\r/g;
8443
+ let start = 0;
8444
+ for (const match of content.matchAll(newline)) {
8445
+ const index = match.index;
8446
+ lines.push({ start, textEnd: index, end: index + match[0].length, text: content.slice(start, index) });
8447
+ start = index + match[0].length;
8448
+ }
8449
+ lines.push({ start, textEnd: content.length, end: content.length, text: content.slice(start) });
8450
+ return lines;
8451
+ }
8452
+ function normalizeEol(content) {
8453
+ return content.replace(/\r\n|\r|\n/g, "\n");
8454
+ }
8455
+ function upsertManagedSeedBlock(current, desired, begin, end) {
8456
+ const desiredLines = sourceLines(desired);
8457
+ const desiredBegins = desiredLines.filter((line) => line.text === begin);
8458
+ const desiredEnds = desiredLines.filter((line) => line.text === end);
8459
+ if (desiredBegins.length !== 1 || desiredEnds.length !== 1 || desiredEnds[0].start <= desiredBegins[0].start) {
8460
+ return { ok: false, reason: "managed block source must contain exactly one well-ordered marker pair" };
8461
+ }
8462
+ const desiredBegin = desiredBegins[0];
8463
+ const desiredEnd = desiredEnds[0];
8464
+ if (desired.slice(0, desiredBegin.start).trim() || desired.slice(desiredEnd.end).trim()) {
8465
+ return { ok: false, reason: "managed block source must contain only the bounded block" };
8466
+ }
8467
+ const canonical = desired.slice(desiredBegin.start, desiredEnd.textEnd);
8468
+ const currentLines = sourceLines(current);
8469
+ const begins = currentLines.filter((line) => line.text === begin);
8470
+ const ends = currentLines.filter((line) => line.text === end);
8471
+ if (begins.length === 0 && ends.length === 0) {
8472
+ const eol2 = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
8473
+ const block2 = normalizeEol(canonical).replace(/\n/g, eol2);
8474
+ if (current.length === 0) return { ok: true, content: `${block2}${eol2}`, changed: true, action: "insert" };
8475
+ const separator = /(?:\r\n|\n|\r)$/.test(current) ? eol2 : `${eol2}${eol2}`;
8476
+ return { ok: true, content: `${current}${separator}${block2}${eol2}`, changed: true, action: "insert" };
8477
+ }
8478
+ if (begins.length !== 1 || ends.length !== 1) {
8479
+ return { ok: false, reason: `malformed managed block markers (begin=${begins.length}, end=${ends.length})` };
8480
+ }
8481
+ const currentBegin = begins[0];
8482
+ const currentEnd = ends[0];
8483
+ if (currentEnd.start <= currentBegin.start) {
8484
+ return { ok: false, reason: "malformed managed block markers (end precedes begin)" };
8485
+ }
8486
+ const existing = current.slice(currentBegin.start, currentEnd.textEnd);
8487
+ if (normalizeEol(existing) === normalizeEol(canonical)) {
8488
+ return { ok: true, content: current, changed: false, action: "current" };
8489
+ }
8490
+ const eol = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
8491
+ const block = normalizeEol(canonical).replace(/\n/g, eol);
8492
+ return {
8493
+ ok: true,
8494
+ content: `${current.slice(0, currentBegin.start)}${block}${current.slice(currentEnd.textEnd)}`,
8495
+ changed: true,
8496
+ action: "replace"
8497
+ };
8498
+ }
8402
8499
  async function resolveHubSeedSource(execGit, defaultBranch = "development") {
8403
8500
  let status;
8404
8501
  try {
@@ -9463,6 +9560,9 @@ function seedMatchesProjectType(seed, projectType) {
9463
9560
  return projectType != null && seed.projectTypes.includes(projectType);
9464
9561
  }
9465
9562
  function planSeedAction(seed, exists) {
9563
+ if (seed.managedBlock) {
9564
+ return exists ? { target: seed.target, action: "update", ownership: "repo", reason: "repo-owned file; Hub-managed block reconciled in place" } : { target: seed.target, action: "create", ownership: "repo", reason: "repo-owned, missing; full template created with Hub-managed block" };
9565
+ }
9466
9566
  if (seed.source === "managed-block") {
9467
9567
  return exists ? { target: seed.target, action: "update", ownership: "org", reason: "org-managed block merged in-place (repo-owned lines preserved)" } : { target: seed.target, action: "create", ownership: "org", reason: "org-managed block; .gitignore absent, created" };
9468
9568
  }
@@ -9521,6 +9621,18 @@ function resolveSeedContent(seed, vars, readFile9) {
9521
9621
  }
9522
9622
  return null;
9523
9623
  }
9624
+ function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
9625
+ if (!seed.managedBlock) {
9626
+ return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
9627
+ }
9628
+ const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
9629
+ if (base == null) return { ok: true, content: null, managed: true };
9630
+ const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
9631
+ const desired = resolveSeedContent(blockSeed, vars, readFile9);
9632
+ if (desired == null) return { ok: true, content: null, managed: true };
9633
+ const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
9634
+ return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
9635
+ }
9524
9636
  function buildRegisterPayload(repo, cls, vars, options = {}) {
9525
9637
  const parsedRepo = parseOwnerRepo(repo);
9526
9638
  const slug = parsedRepo.slug;
@@ -9752,9 +9864,9 @@ function parseVerifyBroker(stdout) {
9752
9864
  }
9753
9865
 
9754
9866
  // src/train-apply.ts
9755
- var import_node_fs22 = require("node:fs");
9867
+ var import_node_fs23 = require("node:fs");
9756
9868
  var import_promises4 = require("node:fs/promises");
9757
- var import_node_path22 = require("node:path");
9869
+ var import_node_path23 = require("node:path");
9758
9870
 
9759
9871
  // src/plugin-guard-io.ts
9760
9872
  var import_node_fs19 = require("node:fs");
@@ -11186,9 +11298,9 @@ function renderAccessReport(report) {
11186
11298
  }
11187
11299
 
11188
11300
  // src/cli-doctor-shared.ts
11189
- var import_node_fs20 = require("node:fs");
11190
- var import_node_path21 = require("node:path");
11191
11301
  var import_node_fs21 = require("node:fs");
11302
+ var import_node_path22 = require("node:path");
11303
+ var import_node_fs22 = require("node:fs");
11192
11304
 
11193
11305
  // ../infra/registry-endpoints.mjs
11194
11306
  var PROJECTS_LIST_PATH = "/projects/list";
@@ -13630,6 +13742,142 @@ async function fileReport(deps, req) {
13630
13742
  return { ok: true, body };
13631
13743
  }
13632
13744
 
13745
+ // src/worktree-delivery-proof.ts
13746
+ var import_node_fs20 = require("node:fs");
13747
+ var import_node_os9 = require("node:os");
13748
+ var import_node_path21 = require("node:path");
13749
+ var OID_RE = /^[0-9a-f]{40,64}$/i;
13750
+ var EXACT_TREE_CANDIDATE_LIMIT = 32;
13751
+ async function repointDeliveredWorktreeTransactionally(input) {
13752
+ const symbolic = (await input.git(["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "")).trim();
13753
+ if (symbolic !== input.branch) return { action: "refuse", reason: "wrong-branch", detail: symbolic || "detached HEAD" };
13754
+ const current = (await input.git(["rev-parse", "--verify", `refs/heads/${input.branch}`]).catch(() => "")).trim();
13755
+ if (current !== input.expectedWorkerOid) {
13756
+ return { action: "refuse", reason: "worker-moved", detail: `${current || "unreadable"} != ${input.expectedWorkerOid}` };
13757
+ }
13758
+ const status = await input.git(["status", "--porcelain"]).catch(() => "unreadable");
13759
+ if (status.trim()) return { action: "refuse", reason: "dirty-worktree", detail: "tracked or untracked files present" };
13760
+ try {
13761
+ await input.git(["checkout", "-B", input.branch, input.landedTipOid]);
13762
+ return { action: "repointed" };
13763
+ } catch (error) {
13764
+ return { action: "refuse", reason: "checkout-failed", detail: error instanceof Error ? error.message : String(error) };
13765
+ }
13766
+ }
13767
+ function supportsExactTreeProofGit(versionOutput) {
13768
+ const match = /\bgit version (\d+)\.(\d+)(?:\.\d+)?/i.exec(versionOutput.trim());
13769
+ if (!match) return false;
13770
+ const major = Number(match[1]);
13771
+ const minor = Number(match[2]);
13772
+ return major > 2 || major === 2 && minor >= 32;
13773
+ }
13774
+ function describeExactTreeRefusal(verdict) {
13775
+ return `${verdict.reason}${verdict.detail ? `: ${verdict.detail}` : ""}`;
13776
+ }
13777
+ function sameChangedPathSet(a, b) {
13778
+ const sorted = (value) => value.split("\0").filter(Boolean).sort().join("\0");
13779
+ return sorted(a) === sorted(b);
13780
+ }
13781
+ async function proveExactTreeDelivery(input, deps = {}) {
13782
+ const base = input.creationBaseOid?.trim();
13783
+ if (!base || !OID_RE.test(base)) return { action: "refuse", reason: "missing-provenance" };
13784
+ if (!input.recordedWorkerBranch || input.recordedWorkerBranch !== input.branch) {
13785
+ return { action: "refuse", reason: "worker-identity-mismatch" };
13786
+ }
13787
+ if (!OID_RE.test(input.workerOid)) return { action: "refuse", reason: "proof-failed", detail: "worker OID is invalid" };
13788
+ const run = deps.git ?? (async (args, env) => (await execFileP2("git", ["-C", input.repoRoot, ...args], {
13789
+ timeout: GIT_TIMEOUT_MS,
13790
+ ...env ? { env: { ...process.env, ...env } } : {}
13791
+ })).stdout);
13792
+ const isAncestor = (ancestor, descendant) => run(["merge-base", "--is-ancestor", ancestor, descendant]).then(() => true).catch(() => false);
13793
+ let version;
13794
+ try {
13795
+ version = await run(["version"]);
13796
+ } catch {
13797
+ return { action: "refuse", reason: "proof-failed", detail: "Git capability probe failed" };
13798
+ }
13799
+ if (!supportsExactTreeProofGit(version)) {
13800
+ return { action: "refuse", reason: "unsupported-git", detail: `requires Git >=2.32; found ${version.trim() || "unparseable version"}` };
13801
+ }
13802
+ try {
13803
+ const [baseType, workerType] = await Promise.all([
13804
+ run(["cat-file", "-t", base]).then((v) => v.trim()).catch(() => ""),
13805
+ run(["cat-file", "-t", input.workerOid]).then((v) => v.trim()).catch(() => "")
13806
+ ]);
13807
+ if (baseType !== "commit" || workerType !== "commit" || !await isAncestor(base, input.workerOid)) {
13808
+ return { action: "refuse", reason: "unverified-base" };
13809
+ }
13810
+ const candidates = /* @__PURE__ */ new Map();
13811
+ for (const tip of [...new Set(input.landedTips.map((v) => v.trim()).filter(Boolean))]) {
13812
+ if (!OID_RE.test(tip) || !await isAncestor(base, tip)) continue;
13813
+ const listed = await run([
13814
+ "rev-list",
13815
+ "--first-parent",
13816
+ "--parents",
13817
+ `--max-count=${EXACT_TREE_CANDIDATE_LIMIT + 1}`,
13818
+ `${base}..${tip}`
13819
+ ]);
13820
+ const rows = listed.split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
13821
+ if (rows.length > EXACT_TREE_CANDIDATE_LIMIT) {
13822
+ return {
13823
+ action: "refuse",
13824
+ reason: "candidate-limit-exceeded",
13825
+ detail: `more than ${EXACT_TREE_CANDIDATE_LIMIT} first-parent candidates since stored base`
13826
+ };
13827
+ }
13828
+ for (const row of rows) {
13829
+ const [oid, parent] = row.split(/\s+/);
13830
+ if (oid && parent && OID_RE.test(oid) && OID_RE.test(parent)) candidates.set(oid, parent);
13831
+ }
13832
+ if (candidates.size > EXACT_TREE_CANDIDATE_LIMIT) {
13833
+ return {
13834
+ action: "refuse",
13835
+ reason: "candidate-limit-exceeded",
13836
+ detail: `more than ${EXACT_TREE_CANDIDATE_LIMIT} unique first-parent candidates across landed tips`
13837
+ };
13838
+ }
13839
+ }
13840
+ if (!candidates.size) return { action: "refuse", reason: "no-candidate" };
13841
+ const workerPaths = await run(["diff", "--name-only", "-z", "--no-renames", base, input.workerOid]);
13842
+ const pathMatches = [];
13843
+ for (const [candidate, parent] of candidates) {
13844
+ const candidatePaths = await run(["diff", "--name-only", "-z", "--no-renames", parent, candidate]);
13845
+ if (sameChangedPathSet(workerPaths, candidatePaths)) pathMatches.push([candidate, parent]);
13846
+ }
13847
+ if (!pathMatches.length) return { action: "refuse", reason: "tree-mismatch" };
13848
+ const makeTempDir = deps.makeTempDir ?? (() => (0, import_node_fs20.mkdtempSync)((0, import_node_path21.join)((0, import_node_os9.tmpdir)(), "mmi-tree-proof-")));
13849
+ const writeTextFile = deps.writeTextFile ?? ((path2, text) => (0, import_node_fs20.writeFileSync)(path2, text, "utf8"));
13850
+ const removeTempDir = deps.removeTempDir ?? ((path2) => (0, import_node_fs20.rmSync)(path2, { recursive: true, force: true }));
13851
+ const root = makeTempDir();
13852
+ try {
13853
+ const synthesize = async (name, from, to) => {
13854
+ const index = (0, import_node_path21.join)(root, `${name}.index`);
13855
+ const patch = (0, import_node_path21.join)(root, `${name}.patch`);
13856
+ const env = { GIT_INDEX_FILE: index };
13857
+ await run(["read-tree", base], env);
13858
+ const delta = await run(["diff-tree", "-p", "--binary", "--full-index", "--no-renames", "--no-ext-diff", from, to]);
13859
+ writeTextFile(patch, delta);
13860
+ if (delta.length) await run(["apply", "--cached", "--binary", "--3way", patch], env);
13861
+ return (await run(["write-tree"], env)).trim();
13862
+ };
13863
+ const workerTreeOid = await synthesize("worker", base, input.workerOid);
13864
+ const matches = [];
13865
+ let candidateNumber = 0;
13866
+ for (const [candidate, parent] of pathMatches) {
13867
+ const tree = await synthesize(`candidate-${candidateNumber++}`, parent, candidate).catch(() => void 0);
13868
+ if (tree === workerTreeOid) matches.push(candidate);
13869
+ }
13870
+ if (matches.length === 1) return { action: "settle", workerTreeOid, candidateOid: matches[0] };
13871
+ if (matches.length > 1) return { action: "refuse", reason: "ambiguous-candidate" };
13872
+ return { action: "refuse", reason: "tree-mismatch" };
13873
+ } finally {
13874
+ removeTempDir(root);
13875
+ }
13876
+ } catch (error) {
13877
+ return { action: "refuse", reason: "proof-failed", detail: error instanceof Error ? error.message : String(error) };
13878
+ }
13879
+ }
13880
+
13633
13881
  // src/cli-doctor-shared.ts
13634
13882
  var GC_GH_TIMEOUT_MS = 2e4;
13635
13883
  var RUN_LIST_TIMEOUT_MS = 2e4;
@@ -13732,7 +13980,7 @@ async function localBranchHeads() {
13732
13980
  }
13733
13981
  async function currentRepoWorktreeGitRoot(repoRoot2) {
13734
13982
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
13735
- return gitCommonDir ? (0, import_node_path21.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
13983
+ return gitCommonDir ? (0, import_node_path22.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
13736
13984
  }
13737
13985
  async function worktreeBranches() {
13738
13986
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -13752,18 +14000,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
13752
14000
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
13753
14001
  if (!match?.[1]) return void 0;
13754
14002
  const raw = match[1].trim();
13755
- return (0, import_node_path21.isAbsolute)(raw) ? raw : (0, import_node_path21.resolve)(worktreePath, raw);
14003
+ return (0, import_node_path22.isAbsolute)(raw) ? raw : (0, import_node_path22.resolve)(worktreePath, raw);
13756
14004
  }
13757
14005
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
13758
14006
  if (!worktreeGitRoot) return false;
13759
14007
  try {
13760
- const entries = (0, import_node_fs21.readdirSync)(worktreeGitRoot, { withFileTypes: true });
14008
+ const entries = (0, import_node_fs22.readdirSync)(worktreeGitRoot, { withFileTypes: true });
13761
14009
  for (const ent of entries) {
13762
14010
  if (!ent.isDirectory()) continue;
13763
14011
  try {
13764
- const gitdirPath = (0, import_node_fs20.readFileSync)((0, import_node_path21.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
13765
- const resolvedGitdir = (0, import_node_path21.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path21.resolve)(worktreeGitRoot, ent.name, gitdirPath);
13766
- if (sameWorktreeMetadataPath((0, import_node_path21.dirname)(resolvedGitdir), worktreePath)) return true;
14012
+ const gitdirPath = (0, import_node_fs21.readFileSync)((0, import_node_path22.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
14013
+ const resolvedGitdir = (0, import_node_path22.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path22.resolve)(worktreeGitRoot, ent.name, gitdirPath);
14014
+ if (sameWorktreeMetadataPath((0, import_node_path22.dirname)(resolvedGitdir), worktreePath)) return true;
13767
14015
  } catch {
13768
14016
  }
13769
14017
  }
@@ -13773,7 +14021,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
13773
14021
  }
13774
14022
  function pathExistsKnown(path2) {
13775
14023
  try {
13776
- (0, import_node_fs21.statSync)(path2);
14024
+ (0, import_node_fs22.statSync)(path2);
13777
14025
  return true;
13778
14026
  } catch (e) {
13779
14027
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -13782,10 +14030,10 @@ function pathExistsKnown(path2) {
13782
14030
  }
13783
14031
  }
13784
14032
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
13785
- const gitPath = (0, import_node_path21.join)(path2, ".git");
14033
+ const gitPath = (0, import_node_path22.join)(path2, ".git");
13786
14034
  let st;
13787
14035
  try {
13788
- st = (0, import_node_fs21.lstatSync)(gitPath);
14036
+ st = (0, import_node_fs22.lstatSync)(gitPath);
13789
14037
  } catch (e) {
13790
14038
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
13791
14039
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -13802,7 +14050,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
13802
14050
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
13803
14051
  if (!st.isFile()) return { path: path2, gitType: "other" };
13804
14052
  try {
13805
- const gitFileContent = (0, import_node_fs20.readFileSync)(gitPath, "utf8");
14053
+ const gitFileContent = (0, import_node_fs21.readFileSync)(gitPath, "utf8");
13806
14054
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
13807
14055
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
13808
14056
  return {
@@ -13819,7 +14067,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
13819
14067
  }
13820
14068
  function inspectDeadWorktreeDirContent(path2) {
13821
14069
  try {
13822
- return { entries: (0, import_node_fs21.readdirSync)(path2) };
14070
+ return { entries: (0, import_node_fs22.readdirSync)(path2) };
13823
14071
  } catch (e) {
13824
14072
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
13825
14073
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -13836,11 +14084,11 @@ async function preservedBranches() {
13836
14084
  async function siblingWorktreeDirs(explicitRoot) {
13837
14085
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
13838
14086
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
13839
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path21.dirname)((0, import_node_path21.dirname)(worktreeGitRoot)) : repoRoot2;
14087
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path22.dirname)((0, import_node_path22.dirname)(worktreeGitRoot)) : repoRoot2;
13840
14088
  try {
13841
14089
  const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
13842
14090
  const agentDirs = listDirsIn(agentWorktreesRoot(primaryRepoRoot));
13843
- const repoLocalWorktrees = listDirsIn((0, import_node_path21.join)(primaryRepoRoot, ".worktrees"));
14091
+ const repoLocalWorktrees = listDirsIn((0, import_node_path22.join)(primaryRepoRoot, ".worktrees"));
13844
14092
  return [...dirs, ...agentDirs, ...repoLocalWorktrees].map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
13845
14093
  } catch {
13846
14094
  return [];
@@ -13848,18 +14096,18 @@ async function siblingWorktreeDirs(explicitRoot) {
13848
14096
  }
13849
14097
  function listDirsIn(dir) {
13850
14098
  try {
13851
- return (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path21.join)(dir, ent.name));
14099
+ return (0, import_node_fs22.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path22.join)(dir, ent.name));
13852
14100
  } catch {
13853
14101
  return [];
13854
14102
  }
13855
14103
  }
13856
14104
  function isRepoCheckoutDir(dir) {
13857
- return (0, import_node_fs21.existsSync)((0, import_node_path21.join)(dir, ".git"));
14105
+ return (0, import_node_fs22.existsSync)((0, import_node_path22.join)(dir, ".git"));
13858
14106
  }
13859
14107
  function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
13860
14108
  let rootDirs;
13861
14109
  try {
13862
- rootDirs = (0, import_node_fs21.readdirSync)(explicitRoot, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
14110
+ rootDirs = (0, import_node_fs22.readdirSync)(explicitRoot, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
13863
14111
  } catch {
13864
14112
  return explicitRoot;
13865
14113
  }
@@ -13890,6 +14138,38 @@ async function gcPlan(remote, limit, opts = {}) {
13890
14138
  ])].filter((b) => !isProtectedBranch(b)),
13891
14139
  limit
13892
14140
  );
14141
+ const primaryRoot = await primaryCheckoutRootOf(gitOut);
14142
+ const owners = primaryRoot ? readWorktreeOwners(primaryRoot) : [];
14143
+ const mergedSet = new Set(mergedIntoBase);
14144
+ const preservedSet = new Set(preserved);
14145
+ const failedLookups = new Set(failures.map((failure) => failure.branch));
14146
+ const openBranches = new Set(prs.filter((pr2) => pr2.state === "OPEN").map((pr2) => pr2.headRefName));
14147
+ const proofWorktrees = worktrees.filter((wt) => !wt.dirty && wt.branch !== current.trim() && !isProtectedBranch(wt.branch) && !preservedSet.has(wt.branch) && !mergedSet.has(wt.branch) && !failedLookups.has(wt.branch) && !openBranches.has(wt.branch));
14148
+ const trainTips = [];
14149
+ if (proofWorktrees.length) {
14150
+ for (const name of ["development", "main", "master"]) {
14151
+ const oid = (await gitOut(["rev-parse", "--verify", `${remote}/${name}`]).catch(() => "")).trim();
14152
+ if (oid) trainTips.push(oid);
14153
+ }
14154
+ }
14155
+ const headByBranch = new Map(heads.map((h) => [h.branch, h.oid]));
14156
+ const exactTreeLanded = [];
14157
+ const exactTreeRefusals = [];
14158
+ for (const wt of proofWorktrees) {
14159
+ const headOid = headByBranch.get(wt.branch);
14160
+ const owner = owners.find((entry) => sameWorktreeMetadataPath(entry.path, wt.path));
14161
+ if (!headOid || !owner || !primaryRoot) continue;
14162
+ const proof = await proveExactTreeDelivery({
14163
+ repoRoot: primaryRoot,
14164
+ branch: wt.branch,
14165
+ workerOid: headOid,
14166
+ creationBaseOid: owner.provenance?.creationBaseOid,
14167
+ recordedWorkerBranch: owner.provenance?.workerBranch,
14168
+ landedTips: trainTips
14169
+ });
14170
+ if (proof.action === "settle") exactTreeLanded.push({ branch: wt.branch, headOid, candidateOid: proof.candidateOid });
14171
+ else exactTreeRefusals.push({ branch: wt.branch, detail: describeExactTreeRefusal(proof) });
14172
+ }
13893
14173
  return buildGcPlan({
13894
14174
  localBranches,
13895
14175
  prLookupFailures: failures,
@@ -13902,6 +14182,8 @@ async function gcPlan(remote, limit, opts = {}) {
13902
14182
  remote,
13903
14183
  preservedBranches: preserved,
13904
14184
  mergedIntoBase,
14185
+ exactTreeLanded,
14186
+ exactTreeRefusals,
13905
14187
  originBranches,
13906
14188
  trainOnly: opts.trainOnly
13907
14189
  });
@@ -13938,6 +14220,11 @@ function semverRelation(a, b) {
13938
14220
  function classifyRow(row) {
13939
14221
  const findings = [];
13940
14222
  const reasons = /* @__PURE__ */ new Set();
14223
+ if (row.onboarding.status === "drift") {
14224
+ findings.push(`canonical onboarding link drift: ${row.onboarding.detail}`);
14225
+ } else if (row.onboarding.status === "unknown") {
14226
+ reasons.add(`canonical onboarding link evidence unknown: ${row.onboarding.detail}`);
14227
+ }
13941
14228
  const releaseRequired = row.track !== "trunk" && !row.classifications.includes("no-deploy-surface");
13942
14229
  const tagIsVersion = Boolean(row.release.version) && row.unknowns.some((u) => u.startsWith(NO_VERSION_LOCK_PREFIX));
13943
14230
  for (const unknown of row.unknowns) {
@@ -14200,10 +14487,10 @@ var rollout_plan_default = {
14200
14487
  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)."
14201
14488
  },
14202
14489
  baseline: {
14203
- version: "3.135.0",
14204
- tag: "v3.135.0",
14205
- commit: "3d9fbd6f4628",
14206
- npm: "@mutmutco/cli@3.135.0"
14490
+ version: "3.137.0",
14491
+ tag: "v3.137.0",
14492
+ commit: "48d8161cd121",
14493
+ npm: "@mutmutco/cli@3.137.0"
14207
14494
  },
14208
14495
  exitCriterion: "fleet-n-of-n",
14209
14496
  hubOnlyShortcut: "forbidden",
@@ -14220,14 +14507,14 @@ var rollout_plan_default = {
14220
14507
  repo: "mutmutco/mmi-hub",
14221
14508
  role: "canary",
14222
14509
  schedule: "train",
14223
- v3Target: "v3.135.0"
14510
+ v3Target: "v3.137.0"
14224
14511
  }
14225
14512
  ],
14226
14513
  rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
14227
14514
  rollback: {
14228
14515
  independent: true,
14229
- mechanism: "npm dist-tag latest -> 3.135.0 and redeploy the Hub Lambda from tag v3.135.0 (3d9fbd6f4628); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
14230
- v3Target: "v3.135.0 (@mutmutco/cli@3.135.0, tag commit 3d9fbd6f4628 \u2014 the preserved latest-v3 distribution, D6b)"
14516
+ mechanism: "npm dist-tag latest -> 3.137.0 and redeploy the Hub Lambda from tag v3.137.0 (48d8161cd121); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
14517
+ v3Target: "v3.137.0 (@mutmutco/cli@3.137.0, tag commit 48d8161cd121 \u2014 the preserved latest-v3 distribution, D6b)"
14231
14518
  }
14232
14519
  },
14233
14520
  {
@@ -15435,6 +15722,7 @@ function registerQueryCommands(program3) {
15435
15722
 
15436
15723
  // src/fleet-track-inventory.ts
15437
15724
  var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
15725
+ var CANONICAL_ONBOARDING_URL = "https://github.com/mutmutco/MMI-Hub/blob/main/docs/Architecture/agentic-dev-environment.md";
15438
15726
  function ghErrorText(e) {
15439
15727
  const err = e;
15440
15728
  return String(err?.stderr || err?.message || e).trim().replace(/\s+/g, " ");
@@ -15465,6 +15753,23 @@ async function readFleetPluginProbe(deps, repo, branch) {
15465
15753
  return isGhNotFound2(e) ? { repo, state: "absent" } : { repo, state: "unknown", unknown: `plugin surface read failed: ${ghErrorText(e)}` };
15466
15754
  }
15467
15755
  }
15756
+ async function readFleetOnboardingProbe(deps, repo, branch) {
15757
+ const [owner, name] = repo.split("/");
15758
+ const ref = branch ? `?ref=${encodeURIComponent(branch)}` : "";
15759
+ try {
15760
+ const raw = await deps.ghJson([
15761
+ "api",
15762
+ `repos/${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}/contents/README.md${ref}`
15763
+ ]);
15764
+ if (raw.encoding !== "base64" || typeof raw.content !== "string") {
15765
+ return { repo, status: "unknown", detail: "README contents read returned no base64 content" };
15766
+ }
15767
+ const readme = Buffer.from(raw.content.replace(/\s/g, ""), "base64").toString("utf8");
15768
+ return readme.includes(CANONICAL_ONBOARDING_URL) ? { repo, status: "canonical", detail: CANONICAL_ONBOARDING_URL } : { repo, status: "drift", detail: `README.md does not link ${CANONICAL_ONBOARDING_URL}` };
15769
+ } catch (e) {
15770
+ return isGhNotFound2(e) ? { repo, status: "drift", detail: `README.md is absent; expected ${CANONICAL_ONBOARDING_URL}` } : { repo, status: "unknown", detail: `onboarding README read failed: ${ghErrorText(e)}` };
15771
+ }
15772
+ }
15468
15773
  async function mapBounded2(values, limit, read) {
15469
15774
  const results = new Array(values.length);
15470
15775
  let next = 0;
@@ -15495,9 +15800,10 @@ function classificationsOf(surfaces) {
15495
15800
  if (surfaces.deploy === "absent") classes.push("no-deploy-surface");
15496
15801
  return classes;
15497
15802
  }
15498
- function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt) {
15803
+ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt, onboardingProbes = []) {
15499
15804
  const releaseByRepo = new Map(releaseProbes.map((p) => [p.repo.toLowerCase(), p]));
15500
15805
  const pluginByRepo = new Map(pluginProbes.map((p) => [p.repo.toLowerCase(), p]));
15806
+ const onboardingByRepo = new Map(onboardingProbes.map((p) => [p.repo.toLowerCase(), p]));
15501
15807
  const projectByRepo = /* @__PURE__ */ new Map();
15502
15808
  const anomalies = [];
15503
15809
  for (const project2 of projects) {
@@ -15519,11 +15825,13 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
15519
15825
  const project2 = projectByRepo.get(repo.toLowerCase());
15520
15826
  const release = releaseByRepo.get(repo.toLowerCase());
15521
15827
  const plugin = pluginByRepo.get(repo.toLowerCase());
15828
+ const onboarding = onboardingByRepo.get(repo.toLowerCase()) ?? { repo, status: "unknown", detail: "onboarding README was not probed" };
15522
15829
  const surfaces = classifySurfaces(project2, plugin);
15523
15830
  const unknowns = [...release?.unknowns ?? []];
15524
15831
  if (!release) unknowns.push("GitHub release evidence was not collected");
15525
15832
  if (plugin?.unknown) unknowns.push(plugin.unknown);
15526
15833
  if (!plugin) unknowns.push("plugin surface was not probed");
15834
+ if (onboarding.status === "unknown") unknowns.push(onboarding.detail);
15527
15835
  return {
15528
15836
  repo,
15529
15837
  slug: String(project2?.slug ?? repo.split("/")[1] ?? repo).toLowerCase(),
@@ -15535,6 +15843,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
15535
15843
  surfaces,
15536
15844
  classifications: classificationsOf(surfaces),
15537
15845
  pluginVersion: plugin?.version ?? null,
15846
+ onboarding,
15538
15847
  release: {
15539
15848
  tag: release?.releaseTag ?? null,
15540
15849
  version: release?.releasedVersion ?? null,
@@ -15547,7 +15856,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
15547
15856
  requiredChecks: Array.isArray(project2?.requiredChecks) ? project2.requiredChecks : null,
15548
15857
  exemptReason: typeof project2?.ciExemptReason === "string" ? project2.ciExemptReason : null
15549
15858
  },
15550
- source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live" },
15859
+ source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live", onboarding: "github-readme-live" },
15551
15860
  freshness: { readAt: generatedAt, releasedAt: release?.releasedAt ?? null },
15552
15861
  unknowns
15553
15862
  };
@@ -15560,6 +15869,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
15560
15869
  roster: "hub-registry-live",
15561
15870
  releases: "github-releases-live",
15562
15871
  plugin: "github-contents-live",
15872
+ onboarding: "github-readme-live",
15563
15873
  committedInventory: "never"
15564
15874
  },
15565
15875
  counts: {
@@ -15570,6 +15880,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
15570
15880
  noVault: repos.filter((r) => r.classifications.includes("no-vault")).length,
15571
15881
  noPlugin: repos.filter((r) => r.classifications.includes("no-plugin")).length,
15572
15882
  noDeploySurface: repos.filter((r) => r.classifications.includes("no-deploy-surface")).length,
15883
+ onboardingDrift: repos.filter((r) => r.onboarding.status === "drift").length,
15573
15884
  unknownRows: repos.filter((r) => r.unknowns.length > 0).length,
15574
15885
  anomalies: anomalies.length
15575
15886
  },
@@ -15601,11 +15912,12 @@ async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps())
15601
15912
  }
15602
15913
  }
15603
15914
  }
15604
- const [releaseProbes, pluginProbes] = await Promise.all([
15915
+ const [releaseProbes, pluginProbes, onboardingProbes] = await Promise.all([
15605
15916
  mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
15606
- mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
15917
+ mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase()))),
15918
+ mapBounded2(repos, 6, (repo) => readFleetOnboardingProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
15607
15919
  ]);
15608
- return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString());
15920
+ return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString(), onboardingProbes);
15609
15921
  }
15610
15922
  function formatFleetTrackInventory(report) {
15611
15923
  const { counts } = report;
@@ -15613,13 +15925,13 @@ function formatFleetTrackInventory(report) {
15613
15925
  `fleet version-track inventory \u2014 ${counts.registeredRepos} registered repositories (${counts.projects} registry projects)`,
15614
15926
  `source: live Hub registry + live GitHub releases/contents \xB7 read ${report.generatedAt} \xB7 committed inventory: never`,
15615
15927
  `tracks: ${counts.tracks.full} full \xB7 ${counts.tracks.direct} direct \xB7 ${counts.tracks.trunk} trunk`,
15616
- `classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.unknownRows} rows with unknowns`,
15928
+ `classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.onboardingDrift} onboarding-link drift \xB7 ${counts.unknownRows} rows with unknowns`,
15617
15929
  "",
15618
- "repository | track | deploy model | released | plugin | classifications"
15930
+ "repository | track | deploy model | released | plugin | onboarding | classifications"
15619
15931
  ];
15620
15932
  for (const row of report.repos) {
15621
15933
  lines.push(
15622
- `${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.classifications.join(", ") || "none"}`
15934
+ `${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.onboarding.status} | ${row.classifications.join(", ") || "none"}`
15623
15935
  );
15624
15936
  for (const unknown of row.unknowns) lines.push(` ? ${unknown}`);
15625
15937
  }
@@ -15634,7 +15946,7 @@ function formatFleetTrackInventory(report) {
15634
15946
  function registerFleetTrackInventory(program3) {
15635
15947
  const train = program3.commands.find((c) => c.name() === "train");
15636
15948
  if (!train) throw new Error("train inventory registration requires the train command group");
15637
- train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track, deploy model, CI contract, release evidence, and explicit no-board/no-vault/no-plugin/no-deploy-surface classification").option("--json", "machine-readable output (the typed D2b input shape)").action(async (o) => {
15949
+ train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track, release evidence, surface classification, and canonical Hub onboarding-link evidence").option("--json", "machine-readable output (the typed D2b input shape)").action(async (o) => {
15638
15950
  try {
15639
15951
  const report = await readFleetTrackInventory();
15640
15952
  console.log(o.json ? JSON.stringify(report, null, 2) : formatFleetTrackInventory(report));
@@ -16311,7 +16623,7 @@ function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun) {
16311
16623
  async function resolveFoldPaths(deps, model) {
16312
16624
  const helper = "scripts/release-distribution.mjs";
16313
16625
  if (model === "hub-serverless" || model === "registry-publish") {
16314
- if ((0, import_node_fs22.existsSync)(helper)) {
16626
+ if ((0, import_node_fs23.existsSync)(helper)) {
16315
16627
  let out;
16316
16628
  try {
16317
16629
  out = await deps.run("node", [helper, "changed-files"]);
@@ -16430,7 +16742,7 @@ async function restoreMainNpmPackIdentities(deps, version) {
16430
16742
  if (mainBom.version !== version) return void 0;
16431
16743
  let localBom;
16432
16744
  try {
16433
- localBom = JSON.parse((0, import_node_fs22.readFileSync)(bomPath, "utf8"));
16745
+ localBom = JSON.parse((0, import_node_fs23.readFileSync)(bomPath, "utf8"));
16434
16746
  } catch (e) {
16435
16747
  throw new Error(
16436
16748
  `version fold refused: ${bomPath} written by this fold's prepare is unreadable (${describeReadError(e)}) \u2014 cannot restore the published ${version} npm-pack identities from origin/main (#4503/#4713).`
@@ -16448,14 +16760,14 @@ async function restoreMainNpmPackIdentities(deps, version) {
16448
16760
  restored += 1;
16449
16761
  }
16450
16762
  if (restored === 0) return void 0;
16451
- (0, import_node_fs22.writeFileSync)(bomPath, `${JSON.stringify(localBom, null, 2)}
16763
+ (0, import_node_fs23.writeFileSync)(bomPath, `${JSON.stringify(localBom, null, 2)}
16452
16764
  `);
16453
16765
  return `restored ${restored} npm-pack BOM identit${restored === 1 ? "y" : "ies"} from origin/main (#4503)`;
16454
16766
  }
16455
16767
  function publishVisibilityFor(surfaceId) {
16456
16768
  let raw;
16457
16769
  try {
16458
- raw = (0, import_node_fs22.readFileSync)("surfaces.json", "utf8");
16770
+ raw = (0, import_node_fs23.readFileSync)("surfaces.json", "utf8");
16459
16771
  } catch (e) {
16460
16772
  if (e.code === "ENOENT") return "unknown";
16461
16773
  throw trainReadFailure(
@@ -16476,7 +16788,7 @@ function publishVisibilityFor(surfaceId) {
16476
16788
  }
16477
16789
  function npmPackArtifactName(packagePath) {
16478
16790
  try {
16479
- const pkg = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path22.join)(packagePath, "package.json"), "utf8"));
16791
+ const pkg = JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path23.join)(packagePath, "package.json"), "utf8"));
16480
16792
  return pkg.name || void 0;
16481
16793
  } catch {
16482
16794
  return void 0;
@@ -16485,7 +16797,7 @@ function npmPackArtifactName(packagePath) {
16485
16797
  async function refuseDivergentPublishedNpmPack(deps, version) {
16486
16798
  let localBom;
16487
16799
  try {
16488
- localBom = JSON.parse((0, import_node_fs22.readFileSync)("distribution-bom.json", "utf8"));
16800
+ localBom = JSON.parse((0, import_node_fs23.readFileSync)("distribution-bom.json", "utf8"));
16489
16801
  } catch (e) {
16490
16802
  throw new Error(
16491
16803
  `version fold refused: distribution-bom.json written by this fold's prepare is unreadable (${e instanceof Error ? e.message.split("\n")[0] : String(e)}) \u2014 cannot compare the same-PATCH ${version} npm-pack identities against published npm (#4503/#4662).`
@@ -17647,17 +17959,17 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
17647
17959
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
17648
17960
  }
17649
17961
  function readLocalGateWorkflows() {
17650
- const dir = (0, import_node_path22.join)(".github", "workflows");
17962
+ const dir = (0, import_node_path23.join)(".github", "workflows");
17651
17963
  let names;
17652
17964
  try {
17653
- names = (0, import_node_fs22.readdirSync)(dir);
17965
+ names = (0, import_node_fs23.readdirSync)(dir);
17654
17966
  } catch {
17655
17967
  return null;
17656
17968
  }
17657
17969
  const files = [];
17658
17970
  for (const name of names.filter(isGateWorkflowPath)) {
17659
17971
  try {
17660
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs22.readFileSync)((0, import_node_path22.join)(dir, name), "utf8") });
17972
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs23.readFileSync)((0, import_node_path23.join)(dir, name), "utf8") });
17661
17973
  } catch {
17662
17974
  }
17663
17975
  }
@@ -19875,17 +20187,17 @@ async function runPrLand(prNumber, options, deps) {
19875
20187
  }
19876
20188
 
19877
20189
  // src/wave-status.ts
19878
- var import_node_fs24 = require("node:fs");
20190
+ var import_node_fs25 = require("node:fs");
19879
20191
 
19880
20192
  // src/stage-runner.ts
19881
20193
  var import_node_child_process8 = require("node:child_process");
19882
- var import_node_fs23 = require("node:fs");
19883
- var import_node_path23 = require("node:path");
20194
+ var import_node_fs24 = require("node:fs");
20195
+ var import_node_path24 = require("node:path");
19884
20196
  var import_node_net = require("node:net");
19885
20197
  var import_node_util5 = require("node:util");
19886
20198
 
19887
20199
  // src/rules-sync.ts
19888
- function normalizeEol(s) {
20200
+ function normalizeEol2(s) {
19889
20201
  return s.replace(/\r\n/g, "\n");
19890
20202
  }
19891
20203
 
@@ -19938,8 +20250,8 @@ function envFileKeys(content) {
19938
20250
  return keys;
19939
20251
  }
19940
20252
  function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
19941
- const example = normalizeEol(exampleContent);
19942
- const target = normalizeEol(targetContent);
20253
+ const example = normalizeEol2(exampleContent);
20254
+ const target = normalizeEol2(targetContent);
19943
20255
  const exampleKeys = envFileKeys(example);
19944
20256
  const targetKeys = envFileKeys(target);
19945
20257
  for (const key of exampleKeys) {
@@ -20019,11 +20331,11 @@ function appendForceRecreate(up) {
20019
20331
  return `${up.trimEnd()} --force-recreate`;
20020
20332
  }
20021
20333
  function stageStatePath(cwd = process.cwd()) {
20022
- return (0, import_node_path23.join)(cwd, "tmp", "stage", "state.json");
20334
+ return (0, import_node_path24.join)(cwd, "tmp", "stage", "state.json");
20023
20335
  }
20024
20336
  function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
20025
- const dir = (0, import_node_path23.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path23.resolve)(cwd, gitCommonDir);
20026
- return (0, import_node_path23.join)(dir, "mmi", "stage", "state.json");
20337
+ const dir = (0, import_node_path24.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path24.resolve)(cwd, gitCommonDir);
20338
+ return (0, import_node_path24.join)(dir, "mmi", "stage", "state.json");
20027
20339
  }
20028
20340
  function normPath3(path2) {
20029
20341
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -20247,14 +20559,14 @@ function stageProcessEnv(stagePort, extraEnv) {
20247
20559
  }
20248
20560
  async function ensureStageRuntimeEnv(config, opts, cwd) {
20249
20561
  if (!config.ensureEnv) return;
20250
- const target = (0, import_node_path23.join)(cwd, config.ensureEnv.target);
20251
- const example = (0, import_node_path23.join)(cwd, config.ensureEnv.example);
20252
- if (!(0, import_node_fs23.existsSync)(target) && (0, import_node_fs23.existsSync)(example)) {
20253
- (0, import_node_fs23.copyFileSync)(example, target);
20254
- } else if ((0, import_node_fs23.existsSync)(target) && (0, import_node_fs23.existsSync)(example)) {
20255
- const stale = detectStaleEnvFile((0, import_node_fs23.readFileSync)(example, "utf8"), (0, import_node_fs23.readFileSync)(target, "utf8"), {
20256
- exampleMtimeMs: (0, import_node_fs23.statSync)(example).mtimeMs,
20257
- targetMtimeMs: (0, import_node_fs23.statSync)(target).mtimeMs
20562
+ const target = (0, import_node_path24.join)(cwd, config.ensureEnv.target);
20563
+ const example = (0, import_node_path24.join)(cwd, config.ensureEnv.example);
20564
+ if (!(0, import_node_fs24.existsSync)(target) && (0, import_node_fs24.existsSync)(example)) {
20565
+ (0, import_node_fs24.copyFileSync)(example, target);
20566
+ } else if ((0, import_node_fs24.existsSync)(target) && (0, import_node_fs24.existsSync)(example)) {
20567
+ const stale = detectStaleEnvFile((0, import_node_fs24.readFileSync)(example, "utf8"), (0, import_node_fs24.readFileSync)(target, "utf8"), {
20568
+ exampleMtimeMs: (0, import_node_fs24.statSync)(example).mtimeMs,
20569
+ targetMtimeMs: (0, import_node_fs24.statSync)(target).mtimeMs
20258
20570
  });
20259
20571
  if (stale) {
20260
20572
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -20262,8 +20574,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
20262
20574
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
20263
20575
  }
20264
20576
  }
20265
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs23.existsSync)(target)) {
20266
- (0, import_node_fs23.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs23.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
20577
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs24.existsSync)(target)) {
20578
+ (0, import_node_fs24.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs24.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
20267
20579
  }
20268
20580
  }
20269
20581
  async function gitText(cwd, args) {
@@ -20293,20 +20605,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
20293
20605
  return void 0;
20294
20606
  }
20295
20607
  function readState(path2) {
20296
- if (!(0, import_node_fs23.existsSync)(path2)) return null;
20608
+ if (!(0, import_node_fs24.existsSync)(path2)) return null;
20297
20609
  try {
20298
- return JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
20610
+ return JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
20299
20611
  } catch {
20300
20612
  return null;
20301
20613
  }
20302
20614
  }
20303
20615
  function mkdirFor(path2) {
20304
20616
  const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
20305
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
20617
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
20306
20618
  }
20307
20619
  function writeState(path2, state) {
20308
20620
  mkdirFor(path2);
20309
- (0, import_node_fs23.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
20621
+ (0, import_node_fs24.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
20310
20622
  }
20311
20623
  function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
20312
20624
  const reservation = {
@@ -20326,7 +20638,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
20326
20638
  await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
20327
20639
  }
20328
20640
  for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
20329
- (0, import_node_fs23.rmSync)(path2, { force: true });
20641
+ (0, import_node_fs24.rmSync)(path2, { force: true });
20330
20642
  }
20331
20643
  }
20332
20644
  async function killTree(pid) {
@@ -20484,8 +20796,8 @@ async function runStage(config = {}, opts = {}) {
20484
20796
  await ensureStageRuntimeEnv(config, opts, cwd);
20485
20797
  if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
20486
20798
  } catch (e) {
20487
- (0, import_node_fs23.rmSync)(statePath, { force: true });
20488
- if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs23.rmSync)(globalStatePath, { force: true });
20799
+ (0, import_node_fs24.rmSync)(statePath, { force: true });
20800
+ if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs24.rmSync)(globalStatePath, { force: true });
20489
20801
  throw e;
20490
20802
  }
20491
20803
  const started = await startStage(config, {
@@ -20506,9 +20818,9 @@ function parseNextFromHead(headText) {
20506
20818
  }
20507
20819
  function readStageSummary(worktreePath) {
20508
20820
  const statePath = stageStatePath(worktreePath);
20509
- if (!(0, import_node_fs24.existsSync)(statePath)) return void 0;
20821
+ if (!(0, import_node_fs25.existsSync)(statePath)) return void 0;
20510
20822
  try {
20511
- const state = JSON.parse((0, import_node_fs24.readFileSync)(statePath, "utf8"));
20823
+ const state = JSON.parse((0, import_node_fs25.readFileSync)(statePath, "utf8"));
20512
20824
  const port = typeof state.port === "number" ? state.port : void 0;
20513
20825
  if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
20514
20826
  return { port, url: typeof state.url === "string" ? state.url : void 0 };
@@ -20613,13 +20925,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
20613
20925
  }
20614
20926
 
20615
20927
  // src/index.ts
20616
- var import_node_os20 = require("node:os");
20928
+ var import_node_os21 = require("node:os");
20617
20929
 
20618
20930
  // src/board.ts
20619
20931
  var import_node_child_process9 = require("node:child_process");
20620
- var import_node_fs25 = require("node:fs");
20621
- var import_node_os9 = require("node:os");
20622
- var import_node_path24 = require("node:path");
20932
+ var import_node_fs26 = require("node:fs");
20933
+ var import_node_os10 = require("node:os");
20934
+ var import_node_path25 = require("node:path");
20623
20935
  var import_node_util6 = require("node:util");
20624
20936
 
20625
20937
  // src/board-dependency.ts
@@ -22229,7 +22541,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
22229
22541
  var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
22230
22542
  var claimSessionProbeCache = /* @__PURE__ */ new Map();
22231
22543
  function probeLocalClaimSession(marker, now = Date.now()) {
22232
- if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os9.hostname)().toLowerCase()) return void 0;
22544
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os10.hostname)().toLowerCase()) return void 0;
22233
22545
  if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
22234
22546
  const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
22235
22547
  const cached = claimSessionProbeCache.get(cacheKey);
@@ -22238,17 +22550,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
22238
22550
  claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
22239
22551
  return state;
22240
22552
  };
22241
- const root = (0, import_node_path24.join)((0, import_node_os9.homedir)(), ".claude", "projects");
22553
+ const root = (0, import_node_path25.join)((0, import_node_os10.homedir)(), ".claude", "projects");
22242
22554
  try {
22243
22555
  const wanted = `${marker.session}.jsonl`.toLowerCase();
22244
22556
  const pending = [root];
22245
22557
  while (pending.length) {
22246
22558
  const dir = pending.pop();
22247
- for (const entry of (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true })) {
22248
- const path2 = (0, import_node_path24.join)(dir, entry.name);
22559
+ for (const entry of (0, import_node_fs26.readdirSync)(dir, { withFileTypes: true })) {
22560
+ const path2 = (0, import_node_path25.join)(dir, entry.name);
22249
22561
  if (entry.isDirectory()) pending.push(path2);
22250
22562
  else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
22251
- return remember(now - (0, import_node_fs25.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
22563
+ return remember(now - (0, import_node_fs26.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
22252
22564
  }
22253
22565
  }
22254
22566
  }
@@ -22536,7 +22848,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
22536
22848
  }
22537
22849
 
22538
22850
  // src/issue-body.ts
22539
- var import_node_os10 = require("node:os");
22851
+ var import_node_os11 = require("node:os");
22540
22852
  var TextArgError = class extends Error {
22541
22853
  constructor(message, code, offendingFlag) {
22542
22854
  super(message);
@@ -22548,7 +22860,7 @@ var TextArgError = class extends Error {
22548
22860
  offendingFlag;
22549
22861
  };
22550
22862
  function emptyStdinMessage(fileFlag) {
22551
- if ((0, import_node_os10.platform)() === "win32") {
22863
+ if ((0, import_node_os11.platform)() === "win32") {
22552
22864
  return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
22553
22865
  }
22554
22866
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -22785,6 +23097,13 @@ function applyCommandTaxonomy(program3) {
22785
23097
  }
22786
23098
  program3.configureHelp({
22787
23099
  ...REQUIRED_OPTION_HELP,
23100
+ // #5014: the root help must show the CANONICAL house-prefixed term (`oracle board`, `devops pr`),
23101
+ // not the retired flat leaf name — an agent grounding on `mmi-cli help` and running the flat name
23102
+ // hits the Wave 3 refusal. `canonicalPathFor` is the identity for `core`, so core stays unprefixed.
23103
+ subcommandTerm(command) {
23104
+ const stock = Help.prototype.subcommandTerm.call(this, command);
23105
+ return `${canonicalPathFor(command.name()) ?? command.name()}${stock.slice(command.name().length)}`;
23106
+ },
22788
23107
  visibleCommands(command) {
22789
23108
  const visible = command.commands.filter((child2) => !child2._hidden);
22790
23109
  const helpCommand = command._getHelpCommand();
@@ -23149,20 +23468,20 @@ function consolidateCommandNamespaces(program3) {
23149
23468
  }
23150
23469
 
23151
23470
  // src/claude-binary-doctor.ts
23471
+ var import_node_fs28 = require("node:fs");
23472
+ var import_node_os13 = require("node:os");
23473
+ var import_node_path27 = require("node:path");
23474
+
23475
+ // src/jerv-cli-spawn.ts
23152
23476
  var import_node_fs27 = require("node:fs");
23153
23477
  var import_node_os12 = require("node:os");
23154
23478
  var import_node_path26 = require("node:path");
23155
-
23156
- // src/jerv-cli-spawn.ts
23157
- var import_node_fs26 = require("node:fs");
23158
- var import_node_os11 = require("node:os");
23159
- var import_node_path25 = require("node:path");
23160
23479
  var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
23161
23480
  var POSIX_NAMES = ["jerv-cli"];
23162
- var JERV_CLI_ENTRY = (0, import_node_path25.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
23481
+ var JERV_CLI_ENTRY = (0, import_node_path26.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
23163
23482
  function pathEnvEntries(pathEnv, platform2 = process.platform) {
23164
23483
  if (platform2 !== "win32") {
23165
- return pathEnv.split(import_node_path25.delimiter).map((e) => e.trim()).filter(Boolean);
23484
+ return pathEnv.split(import_node_path26.delimiter).map((e) => e.trim()).filter(Boolean);
23166
23485
  }
23167
23486
  if (pathEnv.includes(";")) {
23168
23487
  return pathEnv.split(";").map((e) => e.trim()).filter(Boolean);
@@ -23181,7 +23500,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
23181
23500
  if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
23182
23501
  return trimmed;
23183
23502
  }
23184
- function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
23503
+ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform) {
23185
23504
  const seen = /* @__PURE__ */ new Set();
23186
23505
  const out = [];
23187
23506
  const push = (dir) => {
@@ -23195,35 +23514,35 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.hom
23195
23514
  push(normalizeSpawnPathEntry(entry, platform2));
23196
23515
  }
23197
23516
  if (platform2 === "win32") {
23198
- if (env.APPDATA) push((0, import_node_path25.join)(env.APPDATA, "npm"));
23199
- if (env.LOCALAPPDATA) push((0, import_node_path25.join)(env.LOCALAPPDATA, "npm"));
23517
+ if (env.APPDATA) push((0, import_node_path26.join)(env.APPDATA, "npm"));
23518
+ if (env.LOCALAPPDATA) push((0, import_node_path26.join)(env.LOCALAPPDATA, "npm"));
23200
23519
  } else {
23201
- push((0, import_node_path25.join)(home, ".local", "bin"));
23520
+ push((0, import_node_path26.join)(home, ".local", "bin"));
23202
23521
  }
23203
23522
  return out;
23204
23523
  }
23205
- function jervCliCandidatePaths(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
23524
+ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform) {
23206
23525
  const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
23207
23526
  const out = [];
23208
23527
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
23209
- for (const name of names) out.push((0, import_node_path25.join)(dir, name));
23528
+ for (const name of names) out.push((0, import_node_path26.join)(dir, name));
23210
23529
  }
23211
23530
  return out;
23212
23531
  }
23213
- function resolveJervCliPath(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform, exists = import_node_fs26.existsSync) {
23532
+ function resolveJervCliPath(env = process.env, home = (0, import_node_os12.homedir)(), platform2 = process.platform, exists = import_node_fs27.existsSync) {
23214
23533
  for (const candidate of jervCliCandidatePaths(env, home, platform2)) {
23215
23534
  if (exists(candidate)) return candidate;
23216
23535
  }
23217
23536
  return void 0;
23218
23537
  }
23219
- function resolveJervCliNodeEntry(shimPath, exists = import_node_fs26.existsSync) {
23220
- const entry = (0, import_node_path25.join)((0, import_node_path25.dirname)(shimPath), JERV_CLI_ENTRY);
23538
+ function resolveJervCliNodeEntry(shimPath, exists = import_node_fs27.existsSync) {
23539
+ const entry = (0, import_node_path26.join)((0, import_node_path26.dirname)(shimPath), JERV_CLI_ENTRY);
23221
23540
  return exists(entry) ? entry : void 0;
23222
23541
  }
23223
23542
  function jervCliExecFileArgs(args, opts = {}) {
23224
23543
  const platform2 = opts.platform ?? process.platform;
23225
- const exists = opts.exists ?? import_node_fs26.existsSync;
23226
- const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os11.homedir)(), platform2, exists);
23544
+ const exists = opts.exists ?? import_node_fs27.existsSync;
23545
+ const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os12.homedir)(), platform2, exists);
23227
23546
  if (resolved) {
23228
23547
  const entry = resolveJervCliNodeEntry(resolved, exists);
23229
23548
  if (entry) {
@@ -23312,26 +23631,26 @@ function globalNodeModulesRoots(host) {
23312
23631
  out.push(dir);
23313
23632
  };
23314
23633
  const prefix = env.npm_config_prefix?.trim();
23315
- if (prefix) push(platform2 === "win32" ? (0, import_node_path26.join)(prefix, "node_modules") : (0, import_node_path26.join)(prefix, "lib", "node_modules"));
23316
- for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os12.homedir)(), platform2)) {
23317
- push((0, import_node_path26.join)(dir, "node_modules"));
23318
- push((0, import_node_path26.join)((0, import_node_path26.dirname)(dir), "lib", "node_modules"));
23634
+ if (prefix) push(platform2 === "win32" ? (0, import_node_path27.join)(prefix, "node_modules") : (0, import_node_path27.join)(prefix, "lib", "node_modules"));
23635
+ for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os13.homedir)(), platform2)) {
23636
+ push((0, import_node_path27.join)(dir, "node_modules"));
23637
+ push((0, import_node_path27.join)((0, import_node_path27.dirname)(dir), "lib", "node_modules"));
23319
23638
  }
23320
23639
  return out;
23321
23640
  }
23322
23641
  function readHead(path2) {
23323
23642
  let fd;
23324
23643
  try {
23325
- fd = (0, import_node_fs27.openSync)(path2, "r");
23644
+ fd = (0, import_node_fs28.openSync)(path2, "r");
23326
23645
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
23327
- const read = (0, import_node_fs27.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
23646
+ const read = (0, import_node_fs28.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
23328
23647
  return buffer.subarray(0, read);
23329
23648
  } catch {
23330
23649
  return void 0;
23331
23650
  } finally {
23332
23651
  if (fd !== void 0) {
23333
23652
  try {
23334
- (0, import_node_fs27.closeSync)(fd);
23653
+ (0, import_node_fs28.closeSync)(fd);
23335
23654
  } catch {
23336
23655
  }
23337
23656
  }
@@ -23339,7 +23658,7 @@ function readHead(path2) {
23339
23658
  }
23340
23659
  function fileBytes(path2) {
23341
23660
  try {
23342
- return (0, import_node_fs27.statSync)(path2).size;
23661
+ return (0, import_node_fs28.statSync)(path2).size;
23343
23662
  } catch {
23344
23663
  return void 0;
23345
23664
  }
@@ -23353,17 +23672,17 @@ function readClaudeBinaryState(host = {}) {
23353
23672
  const arch = host.arch ?? process.arch;
23354
23673
  const magic = EXECUTABLE_MAGIC[platform2];
23355
23674
  if (!magic) return void 0;
23356
- const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path26.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs27.existsSync)((0, import_node_path26.join)(dir, "package.json")));
23675
+ const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path27.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs28.existsSync)((0, import_node_path27.join)(dir, "package.json")));
23357
23676
  if (!packageRoot) return void 0;
23358
23677
  const keys = platformPackageKeys(platform2, arch);
23359
23678
  const fallbackPackage = `${PACKAGE}-${keys[0]}`;
23360
23679
  let manifest;
23361
23680
  try {
23362
- manifest = JSON.parse((0, import_node_fs27.readFileSync)((0, import_node_path26.join)(packageRoot, "package.json"), "utf8"));
23681
+ manifest = JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path27.join)(packageRoot, "package.json"), "utf8"));
23363
23682
  } catch (e) {
23364
23683
  return {
23365
23684
  state: "unreadable",
23366
- binPath: (0, import_node_path26.join)(packageRoot, "package.json"),
23685
+ binPath: (0, import_node_path27.join)(packageRoot, "package.json"),
23367
23686
  expectedMagic: magic.name,
23368
23687
  platformPackage: fallbackPackage,
23369
23688
  error: `package.json could not be read \u2014 ${e.message}`
@@ -23380,17 +23699,17 @@ function readClaudeBinaryState(host = {}) {
23380
23699
  error: "package.json declares no `bin` entry \u2014 the executed file cannot be resolved"
23381
23700
  };
23382
23701
  }
23383
- const binPath = (0, import_node_path26.join)(packageRoot, binRelative);
23384
- const binName = (0, import_node_path26.basename)(binRelative);
23702
+ const binPath = (0, import_node_path27.join)(packageRoot, binRelative);
23703
+ const binName = (0, import_node_path27.basename)(binRelative);
23385
23704
  const optional = manifest.optionalDependencies;
23386
23705
  const publishes = (name) => typeof optional === "object" && optional !== null && Object.prototype.hasOwnProperty.call(optional, name);
23387
23706
  const published = keys.map((key) => `${PACKAGE}-${key}`).filter(publishes);
23388
23707
  if (published.length === 0) return void 0;
23389
23708
  const binIn = (name) => [
23390
- (0, import_node_path26.join)(packageRoot, "node_modules", ...name.split("/"), binName),
23391
- (0, import_node_path26.join)((0, import_node_path26.dirname)((0, import_node_path26.dirname)(packageRoot)), ...name.split("/"), binName)
23709
+ (0, import_node_path27.join)(packageRoot, "node_modules", ...name.split("/"), binName),
23710
+ (0, import_node_path27.join)((0, import_node_path27.dirname)((0, import_node_path27.dirname)(packageRoot)), ...name.split("/"), binName)
23392
23711
  ];
23393
- const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs27.existsSync)(file)) })).find((c) => c.path);
23712
+ const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs28.existsSync)(file)) })).find((c) => c.path);
23394
23713
  const platformPackage = found?.name ?? published[0];
23395
23714
  let source;
23396
23715
  let sourceProblem;
@@ -23401,7 +23720,7 @@ function readClaudeBinaryState(host = {}) {
23401
23720
  } else {
23402
23721
  source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
23403
23722
  }
23404
- if (!(0, import_node_fs27.existsSync)(binPath)) {
23723
+ if (!(0, import_node_fs28.existsSync)(binPath)) {
23405
23724
  return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
23406
23725
  }
23407
23726
  const head = readHead(binPath);
@@ -23444,9 +23763,9 @@ function healClaudeBinary(host = {}, onStep) {
23444
23763
  const platform2 = host.platform ?? process.platform;
23445
23764
  const aside = `${probe.binPath}.stub-${Date.now()}`;
23446
23765
  let renamed = false;
23447
- if ((0, import_node_fs27.existsSync)(probe.binPath)) {
23766
+ if ((0, import_node_fs28.existsSync)(probe.binPath)) {
23448
23767
  try {
23449
- (0, import_node_fs27.renameSync)(probe.binPath, aside);
23768
+ (0, import_node_fs28.renameSync)(probe.binPath, aside);
23450
23769
  renamed = true;
23451
23770
  onStep?.(`renamed the stub aside: ${aside}`);
23452
23771
  } catch (e) {
@@ -23455,12 +23774,12 @@ function healClaudeBinary(host = {}, onStep) {
23455
23774
  }
23456
23775
  try {
23457
23776
  onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
23458
- (0, import_node_fs27.copyFileSync)(probe.source.path, probe.binPath);
23459
- if (platform2 !== "win32") (0, import_node_fs27.chmodSync)(probe.binPath, 493);
23777
+ (0, import_node_fs28.copyFileSync)(probe.source.path, probe.binPath);
23778
+ if (platform2 !== "win32") (0, import_node_fs28.chmodSync)(probe.binPath, 493);
23460
23779
  } catch (e) {
23461
23780
  if (renamed) {
23462
23781
  try {
23463
- (0, import_node_fs27.renameSync)(aside, probe.binPath);
23782
+ (0, import_node_fs28.renameSync)(aside, probe.binPath);
23464
23783
  } catch {
23465
23784
  return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
23466
23785
  }
@@ -23474,7 +23793,7 @@ function healClaudeBinary(host = {}, onStep) {
23474
23793
  let kept = false;
23475
23794
  if (renamed) {
23476
23795
  try {
23477
- (0, import_node_fs27.rmSync)(aside);
23796
+ (0, import_node_fs28.rmSync)(aside);
23478
23797
  } catch {
23479
23798
  kept = true;
23480
23799
  }
@@ -23952,19 +24271,19 @@ function renderVerifyBroker(input) {
23952
24271
 
23953
24272
  // src/tenant-artifact.ts
23954
24273
  var import_node_crypto4 = require("node:crypto");
23955
- var import_node_fs28 = require("node:fs");
24274
+ var import_node_fs29 = require("node:fs");
23956
24275
  var import_promises5 = require("node:fs/promises");
23957
- var import_node_path27 = require("node:path");
24276
+ var import_node_path28 = require("node:path");
23958
24277
  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}$/;
23959
24278
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
23960
24279
  async function sha256File(path2) {
23961
24280
  const hash = (0, import_node_crypto4.createHash)("sha256");
23962
- for await (const chunk of (0, import_node_fs28.createReadStream)(path2)) hash.update(chunk);
24281
+ for await (const chunk of (0, import_node_fs29.createReadStream)(path2)) hash.update(chunk);
23963
24282
  return hash.digest("hex");
23964
24283
  }
23965
24284
  async function putTenantArtifact(repo, stage, inputPath, deps) {
23966
24285
  if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
23967
- const path2 = (0, import_node_path27.resolve)(inputPath);
24286
+ const path2 = (0, import_node_path28.resolve)(inputPath);
23968
24287
  const info = await (0, import_promises5.stat)(path2);
23969
24288
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
23970
24289
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
@@ -23982,7 +24301,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23982
24301
  return [key, value];
23983
24302
  }));
23984
24303
  headers["content-length"] = String(info.size);
23985
- const stream = (0, import_node_fs28.createReadStream)(path2);
24304
+ const stream = (0, import_node_fs29.createReadStream)(path2);
23986
24305
  let uploaded;
23987
24306
  try {
23988
24307
  uploaded = await fetch(body.uploadUrl, {
@@ -25100,8 +25419,8 @@ async function announceRelease(deps, args) {
25100
25419
  // src/repo-index.ts
25101
25420
  var import_node_crypto5 = require("node:crypto");
25102
25421
  var import_node_child_process12 = require("node:child_process");
25103
- var import_node_fs29 = require("node:fs");
25104
- var import_node_path28 = require("node:path");
25422
+ var import_node_fs30 = require("node:fs");
25423
+ var import_node_path29 = require("node:path");
25105
25424
  var REPO_INDEX_SCHEMA = 1;
25106
25425
  var HARD_DENY = [
25107
25426
  /(^|\/)\.env(\.|$)/i,
@@ -25226,11 +25545,11 @@ function loadReadmeHints(cwd, candidatePaths) {
25226
25545
  }
25227
25546
  for (const rel of readmes) {
25228
25547
  if (isHardDeniedPath(rel)) continue;
25229
- const abs = (0, import_node_path28.join)(cwd, ...rel.split("/"));
25230
- if (!(0, import_node_fs29.existsSync)(abs)) continue;
25548
+ const abs = (0, import_node_path29.join)(cwd, ...rel.split("/"));
25549
+ if (!(0, import_node_fs30.existsSync)(abs)) continue;
25231
25550
  let text;
25232
25551
  try {
25233
- text = (0, import_node_fs29.readFileSync)(abs, "utf8");
25552
+ text = (0, import_node_fs30.readFileSync)(abs, "utf8");
25234
25553
  } catch {
25235
25554
  continue;
25236
25555
  }
@@ -25243,7 +25562,7 @@ function loadReadmeHints(cwd, candidatePaths) {
25243
25562
  return hints;
25244
25563
  }
25245
25564
  function toPosix(p) {
25246
- return p.split(import_node_path28.sep).join("/");
25565
+ return p.split(import_node_path29.sep).join("/");
25247
25566
  }
25248
25567
  function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync) {
25249
25568
  try {
@@ -25257,7 +25576,7 @@ function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync
25257
25576
  return [];
25258
25577
  }
25259
25578
  }
25260
- function rebuildRepoIndex(cwd, repoSlug2) {
25579
+ function rebuildRepoIndex(cwd, repoSlug3) {
25261
25580
  const candidates = listCandidatePaths(cwd).filter(isIndexablePath);
25262
25581
  const ignored = defaultIsIgnored(cwd, candidates);
25263
25582
  const readmeHints = loadReadmeHints(cwd, candidates.filter((p) => !ignored.has(p)));
@@ -25265,11 +25584,11 @@ function rebuildRepoIndex(cwd, repoSlug2) {
25265
25584
  for (const rel of candidates) {
25266
25585
  if (ignored.has(rel)) continue;
25267
25586
  if (isHardDeniedPath(rel)) continue;
25268
- const abs = (0, import_node_path28.join)(cwd, ...rel.split("/"));
25269
- if (!(0, import_node_fs29.existsSync)(abs)) continue;
25587
+ const abs = (0, import_node_path29.join)(cwd, ...rel.split("/"));
25588
+ if (!(0, import_node_fs30.existsSync)(abs)) continue;
25270
25589
  let text;
25271
25590
  try {
25272
- text = (0, import_node_fs29.readFileSync)(abs, "utf8");
25591
+ text = (0, import_node_fs30.readFileSync)(abs, "utf8");
25273
25592
  } catch {
25274
25593
  continue;
25275
25594
  }
@@ -25289,21 +25608,21 @@ function rebuildRepoIndex(cwd, repoSlug2) {
25289
25608
  entries.sort((a, b) => a.path.localeCompare(b.path));
25290
25609
  const projection = {
25291
25610
  schema: REPO_INDEX_SCHEMA,
25292
- repo: repoSlug2,
25611
+ repo: repoSlug3,
25293
25612
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
25294
25613
  entries
25295
25614
  };
25296
25615
  const store = repoIndexStorePath(cwd);
25297
- (0, import_node_fs29.mkdirSync)((0, import_node_path28.dirname)(store), { recursive: true });
25298
- (0, import_node_fs29.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
25616
+ (0, import_node_fs30.mkdirSync)((0, import_node_path29.dirname)(store), { recursive: true });
25617
+ (0, import_node_fs30.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
25299
25618
  `, "utf8");
25300
25619
  return projection;
25301
25620
  }
25302
25621
  function loadRepoIndex(cwd) {
25303
25622
  const store = repoIndexStorePath(cwd);
25304
- if (!(0, import_node_fs29.existsSync)(store)) return null;
25623
+ if (!(0, import_node_fs30.existsSync)(store)) return null;
25305
25624
  try {
25306
- const raw = JSON.parse((0, import_node_fs29.readFileSync)(store, "utf8"));
25625
+ const raw = JSON.parse((0, import_node_fs30.readFileSync)(store, "utf8"));
25307
25626
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
25308
25627
  return raw;
25309
25628
  } catch {
@@ -25375,7 +25694,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
25375
25694
  if (m?.[1]) return m[1].toLowerCase();
25376
25695
  } catch {
25377
25696
  }
25378
- return ((0, import_node_path28.basename)(cwd) || "local").toLowerCase();
25697
+ return ((0, import_node_path29.basename)(cwd) || "local").toLowerCase();
25379
25698
  }
25380
25699
 
25381
25700
  // src/repo-index-cloud-client.ts
@@ -25515,9 +25834,9 @@ async function gcRepoIndexCloud(deps) {
25515
25834
  }
25516
25835
 
25517
25836
  // src/repo-index-sync.ts
25518
- var import_node_fs30 = require("node:fs");
25519
- var import_node_os13 = require("node:os");
25520
- var import_node_path29 = require("node:path");
25837
+ var import_node_fs31 = require("node:fs");
25838
+ var import_node_os14 = require("node:os");
25839
+ var import_node_path30 = require("node:path");
25521
25840
  var import_node_child_process13 = require("node:child_process");
25522
25841
  var MAX_EMBED_BACKFILL_ROUNDS = 40;
25523
25842
  function normalizeRepo(raw) {
@@ -25561,7 +25880,7 @@ async function syncEstateRepoIndex(opts) {
25561
25880
  const failed = [];
25562
25881
  const skipped = [];
25563
25882
  for (const repo of repos) {
25564
- const dir = (0, import_node_fs30.mkdtempSync)((0, import_node_path29.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-"));
25883
+ const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path30.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
25565
25884
  try {
25566
25885
  shallowClone(repo, dir, opts.githubToken);
25567
25886
  const built = rebuildRepoIndex(dir, repo);
@@ -25611,7 +25930,7 @@ async function syncEstateRepoIndex(opts) {
25611
25930
  failed.push({ repo, error: e.message });
25612
25931
  } finally {
25613
25932
  try {
25614
- (0, import_node_fs30.rmSync)(dir, { recursive: true, force: true });
25933
+ (0, import_node_fs31.rmSync)(dir, { recursive: true, force: true });
25615
25934
  } catch {
25616
25935
  }
25617
25936
  }
@@ -25620,7 +25939,7 @@ async function syncEstateRepoIndex(opts) {
25620
25939
  }
25621
25940
 
25622
25941
  // src/repo-index-health.ts
25623
- var import_node_fs31 = require("node:fs");
25942
+ var import_node_fs32 = require("node:fs");
25624
25943
 
25625
25944
  // testdata/repo-index-golden-queries.json
25626
25945
  var repo_index_golden_queries_default = {
@@ -25662,7 +25981,7 @@ function assertGoldenSuite(raw, source) {
25662
25981
  function loadGoldenSuite(path2) {
25663
25982
  let text;
25664
25983
  try {
25665
- text = (0, import_node_fs31.readFileSync)(path2, "utf8");
25984
+ text = (0, import_node_fs32.readFileSync)(path2, "utf8");
25666
25985
  } catch (e) {
25667
25986
  throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
25668
25987
  }
@@ -25816,8 +26135,8 @@ async function runRepoIndexHealth(opts) {
25816
26135
 
25817
26136
  // src/spawn-policy-core.ts
25818
26137
  var import_node_child_process14 = require("node:child_process");
25819
- var import_node_fs32 = require("node:fs");
25820
- var import_node_path30 = require("node:path");
26138
+ var import_node_fs33 = require("node:fs");
26139
+ var import_node_path31 = require("node:path");
25821
26140
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
25822
26141
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
25823
26142
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -25903,7 +26222,7 @@ function runSpawnPolicy(root) {
25903
26222
  for (const file of files) {
25904
26223
  let raw;
25905
26224
  try {
25906
- raw = (0, import_node_fs32.readFileSync)((0, import_node_path30.join)(root, file), "utf8");
26225
+ raw = (0, import_node_fs33.readFileSync)((0, import_node_path31.join)(root, file), "utf8");
25907
26226
  } catch {
25908
26227
  continue;
25909
26228
  }
@@ -25921,8 +26240,8 @@ function runSpawnPolicy(root) {
25921
26240
 
25922
26241
  // src/test-policy-core.ts
25923
26242
  var import_node_child_process15 = require("node:child_process");
25924
- var import_node_fs33 = require("node:fs");
25925
- var import_node_path31 = require("node:path");
26243
+ var import_node_fs34 = require("node:fs");
26244
+ var import_node_path32 = require("node:path");
25926
26245
  var POLICY_FILE = "test-policy.json";
25927
26246
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
25928
26247
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -25975,7 +26294,7 @@ function isTestPath(path2) {
25975
26294
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
25976
26295
  }
25977
26296
  function loadPolicy(root, readFile9 = readFileOrNull2) {
25978
- const raw = readFile9((0, import_node_path31.join)(root, POLICY_FILE));
26297
+ const raw = readFile9((0, import_node_path32.join)(root, POLICY_FILE));
25979
26298
  if (raw == null) return { mandatory: [], declared: false };
25980
26299
  try {
25981
26300
  return { ...JSON.parse(raw), declared: true };
@@ -25985,7 +26304,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
25985
26304
  }
25986
26305
  function readFileOrNull2(path2) {
25987
26306
  try {
25988
- return (0, import_node_fs33.readFileSync)(path2, "utf8");
26307
+ return (0, import_node_fs34.readFileSync)(path2, "utf8");
25989
26308
  } catch {
25990
26309
  return null;
25991
26310
  }
@@ -26012,12 +26331,12 @@ function classify(changed, policy, present = () => false) {
26012
26331
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
26013
26332
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
26014
26333
  }
26015
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
26016
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path31.join)(root, p)));
26334
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
26335
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path32.join)(root, p)));
26017
26336
  }
26018
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
26337
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
26019
26338
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
26020
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path31.join)(root, p)));
26339
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path32.join)(root, p)));
26021
26340
  }
26022
26341
  function evaluate(changed, policy, present = () => false) {
26023
26342
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -26199,13 +26518,13 @@ function changedFilesSince(base, cwd) {
26199
26518
  }
26200
26519
  function runTestPolicy(root, deps = {}) {
26201
26520
  const policy = deps.policy ?? loadPolicy(root);
26202
- const exists = deps.exists ?? ((path2) => (0, import_node_fs33.existsSync)(path2));
26521
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs34.existsSync)(path2));
26203
26522
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
26204
26523
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
26205
26524
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
26206
26525
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
26207
26526
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
26208
- const present = (path2) => exists((0, import_node_path31.join)(root, path2));
26527
+ const present = (path2) => exists((0, import_node_path32.join)(root, path2));
26209
26528
  const removedByThisDiff = removedPaths(changed);
26210
26529
  const staleFindings = [];
26211
26530
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -26243,8 +26562,8 @@ function runTestPolicy(root, deps = {}) {
26243
26562
  }
26244
26563
 
26245
26564
  // src/project-info-sync.ts
26246
- var import_node_fs34 = require("node:fs");
26247
- var import_node_path32 = require("node:path");
26565
+ var import_node_fs35 = require("node:fs");
26566
+ var import_node_path33 = require("node:path");
26248
26567
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
26249
26568
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
26250
26569
  projectV2 { id }
@@ -26289,14 +26608,14 @@ function sharedName(entries, fallback) {
26289
26608
  }
26290
26609
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
26291
26610
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
26292
- const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
26293
- if (!(0, import_node_fs34.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
26611
+ const readmePath = (0, import_node_path33.join)(repoRoot2, "README.md");
26612
+ if (!(0, import_node_fs35.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
26294
26613
  const entries = entriesFor(project2, projects);
26295
26614
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
26296
26615
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
26297
26616
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
26298
26617
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
26299
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs34.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
26618
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs35.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
26300
26619
  const lines = [
26301
26620
  `# ${projectName}`,
26302
26621
  "",
@@ -26315,8 +26634,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
26315
26634
  const targetBase = `https://github.com/${targetRepo2}`;
26316
26635
  const targetBranch = branchFor(targetRepo2, projects);
26317
26636
  const orgDocs = [
26318
- (0, import_node_fs34.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
26319
- (0, import_node_fs34.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
26637
+ (0, import_node_fs35.existsSync)((0, import_node_path33.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
26638
+ (0, import_node_fs35.existsSync)((0, import_node_path33.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
26320
26639
  ].filter(Boolean);
26321
26640
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
26322
26641
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -27142,9 +27461,9 @@ function writeError(res) {
27142
27461
  }
27143
27462
 
27144
27463
  // src/secrets-commands.ts
27145
- var import_node_fs35 = require("node:fs");
27146
- var import_node_path33 = require("node:path");
27147
- var import_node_os14 = require("node:os");
27464
+ var import_node_fs36 = require("node:fs");
27465
+ var import_node_path34 = require("node:path");
27466
+ var import_node_os15 = require("node:os");
27148
27467
 
27149
27468
  // src/secrets-diff.ts
27150
27469
  var TIMEOUT_MS2 = 8e3;
@@ -27246,18 +27565,18 @@ function collectMap(value, previous = []) {
27246
27565
  return [...previous, value];
27247
27566
  }
27248
27567
  async function decryptRailsCredentials(input) {
27249
- const appDir = (0, import_node_path33.resolve)(input.appDir ?? process.cwd());
27568
+ const appDir = (0, import_node_path34.resolve)(input.appDir ?? process.cwd());
27250
27569
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
27251
27570
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
27252
- const credentialsPath = (0, import_node_path33.resolve)(appDir, credentialsFile);
27253
- const masterKeyPath = (0, import_node_path33.resolve)(appDir, masterKeyFile);
27571
+ const credentialsPath = (0, import_node_path34.resolve)(appDir, credentialsFile);
27572
+ const masterKeyPath = (0, import_node_path34.resolve)(appDir, masterKeyFile);
27254
27573
  const env = {
27255
27574
  ...process.env,
27256
27575
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
27257
27576
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
27258
27577
  };
27259
- if ((0, import_node_fs35.existsSync)(masterKeyPath)) {
27260
- env.RAILS_MASTER_KEY = (0, import_node_fs35.readFileSync)(masterKeyPath, "utf8").trim();
27578
+ if ((0, import_node_fs36.existsSync)(masterKeyPath)) {
27579
+ env.RAILS_MASTER_KEY = (0, import_node_fs36.readFileSync)(masterKeyPath, "utf8").trim();
27261
27580
  }
27262
27581
  const script = [
27263
27582
  'require "json"',
@@ -27267,9 +27586,9 @@ async function decryptRailsCredentials(input) {
27267
27586
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
27268
27587
  "puts JSON.generate(config.config)"
27269
27588
  ].join("\n");
27270
- const scriptDir = (0, import_node_fs35.mkdtempSync)((0, import_node_path33.join)((0, import_node_os14.tmpdir)(), "mmi-rails-decrypt-"));
27271
- const scriptPath = (0, import_node_path33.join)(scriptDir, "decrypt.rb");
27272
- (0, import_node_fs35.writeFileSync)(scriptPath, script, "utf8");
27589
+ const scriptDir = (0, import_node_fs36.mkdtempSync)((0, import_node_path34.join)((0, import_node_os15.tmpdir)(), "mmi-rails-decrypt-"));
27590
+ const scriptPath = (0, import_node_path34.join)(scriptDir, "decrypt.rb");
27591
+ (0, import_node_fs36.writeFileSync)(scriptPath, script, "utf8");
27273
27592
  try {
27274
27593
  const args = ["exec", "ruby", scriptPath];
27275
27594
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -27281,7 +27600,7 @@ async function decryptRailsCredentials(input) {
27281
27600
  });
27282
27601
  return JSON.parse(stdout);
27283
27602
  } finally {
27284
- (0, import_node_fs35.rmSync)(scriptDir, { recursive: true, force: true });
27603
+ (0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
27285
27604
  }
27286
27605
  }
27287
27606
  async function readSecretStdin() {
@@ -27371,7 +27690,7 @@ function registerSecretsCommands(program3) {
27371
27690
  let body;
27372
27691
  if (o.file) {
27373
27692
  try {
27374
- body = (0, import_node_fs35.readFileSync)((0, import_node_path33.resolve)(o.file), "utf8");
27693
+ body = (0, import_node_fs36.readFileSync)((0, import_node_path34.resolve)(o.file), "utf8");
27375
27694
  } catch (e) {
27376
27695
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
27377
27696
  }
@@ -27476,7 +27795,7 @@ function registerSecretsCommands(program3) {
27476
27795
  {
27477
27796
  ...d,
27478
27797
  decryptRailsCredentials,
27479
- removeFile: (path2) => (0, import_node_fs35.unlinkSync)((0, import_node_path33.resolve)(o.appDir ?? process.cwd(), path2))
27798
+ removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path34.resolve)(o.appDir ?? process.cwd(), path2))
27480
27799
  },
27481
27800
  {
27482
27801
  repo: o.repo,
@@ -27681,7 +28000,7 @@ function emitCliCallTelemetry(command) {
27681
28000
  }
27682
28001
 
27683
28002
  // src/box-commands.ts
27684
- var import_node_fs36 = require("node:fs");
28003
+ var import_node_fs37 = require("node:fs");
27685
28004
 
27686
28005
  // src/box.ts
27687
28006
  var BOX_KEYS = {
@@ -27884,7 +28203,7 @@ function registerBoxCommands(program3) {
27884
28203
  }
27885
28204
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
27886
28205
  else if (o.ssh && o.script) {
27887
- (0, import_node_fs36.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
28206
+ (0, import_node_fs37.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
27888
28207
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
27889
28208
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
27890
28209
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -28203,7 +28522,7 @@ function registerSchedulesCommands(program3) {
28203
28522
 
28204
28523
  // src/schedules-lift-command.ts
28205
28524
  var import_promises7 = require("node:fs/promises");
28206
- var import_node_path34 = require("node:path");
28525
+ var import_node_path35 = require("node:path");
28207
28526
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
28208
28527
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
28209
28528
  var SchedulesLiftUsageError = class extends Error {
@@ -28230,7 +28549,7 @@ async function readWorkflowFiles(dir) {
28230
28549
  const files = [];
28231
28550
  for (const name of names.sort()) {
28232
28551
  if (!/\.ya?ml$/.test(name)) continue;
28233
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises7.readFile)((0, import_node_path34.join)(dir, name), "utf8") });
28552
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises7.readFile)((0, import_node_path35.join)(dir, name), "utf8") });
28234
28553
  }
28235
28554
  return files;
28236
28555
  }
@@ -28378,9 +28697,9 @@ function registerEdgeCommands(program3) {
28378
28697
  }
28379
28698
 
28380
28699
  // src/bootstrap-commands.ts
28381
- var import_node_fs37 = require("node:fs");
28382
- var import_node_os15 = require("node:os");
28383
- var import_node_path35 = require("node:path");
28700
+ var import_node_fs38 = require("node:fs");
28701
+ var import_node_os16 = require("node:os");
28702
+ var import_node_path36 = require("node:path");
28384
28703
 
28385
28704
  // src/bootstrap-drift.ts
28386
28705
  var import_node_crypto7 = require("node:crypto");
@@ -28565,6 +28884,71 @@ function renderPropagationReport(plan) {
28565
28884
  return lines.join("\n");
28566
28885
  }
28567
28886
 
28887
+ // src/bootstrap-propagation-identity.ts
28888
+ var import_node_crypto8 = require("node:crypto");
28889
+ var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
28890
+ var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
28891
+ function safeBranchPart(value, maxLength, fallback) {
28892
+ const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
28893
+ return safe || fallback;
28894
+ }
28895
+ function repoSlug2(repo) {
28896
+ return repo.trim().split("/").pop()?.toLowerCase() || "repo";
28897
+ }
28898
+ function propagationBranch(repo, target) {
28899
+ const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
28900
+ const targetPart = safeBranchPart(target, 48, "target");
28901
+ const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
28902
+ return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
28903
+ }
28904
+ function legacyPropagationBranch(repo) {
28905
+ return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
28906
+ }
28907
+ function renderPropagationTargetMarker(target) {
28908
+ return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
28909
+ }
28910
+ function targetMarkers(body) {
28911
+ const present = body.includes(`${TARGET_MARKER_NAME}:`);
28912
+ const targets = [];
28913
+ const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
28914
+ for (const match of body.matchAll(marker)) {
28915
+ try {
28916
+ const parsed = JSON.parse(match[1]);
28917
+ if (typeof parsed === "string") targets.push(parsed);
28918
+ } catch {
28919
+ }
28920
+ }
28921
+ return { present, targets };
28922
+ }
28923
+ function canonicalProseTargets(body) {
28924
+ const targets = [];
28925
+ const command = /bootstrap propagate --target (.+?) --execute`/g;
28926
+ const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
28927
+ for (const match of body.matchAll(command)) targets.push(match[1]);
28928
+ for (const match of body.matchAll(prose)) targets.push(match[1]);
28929
+ return targets;
28930
+ }
28931
+ function propagationPrMatchesTarget(pr2, target, identity) {
28932
+ const body = pr2.body ?? "";
28933
+ const markers = targetMarkers(body);
28934
+ if (markers.present) {
28935
+ if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
28936
+ return !pr2.files?.length || pr2.files.length === 1 && pr2.files[0] === target;
28937
+ }
28938
+ if (identity === "target-specific") return false;
28939
+ const proseTargets = canonicalProseTargets(body);
28940
+ if (proseTargets.some((candidate) => candidate !== target)) return false;
28941
+ if (pr2.files?.length) {
28942
+ return pr2.files.length === 1 && pr2.files[0] === target;
28943
+ }
28944
+ return proseTargets.length > 0;
28945
+ }
28946
+ function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
28947
+ const targetSpecific = targetSpecificPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "target-specific"));
28948
+ if (targetSpecific.length) return targetSpecific;
28949
+ return legacyPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "legacy"));
28950
+ }
28951
+
28568
28952
  // src/bootstrap-rollback.ts
28569
28953
  function resolveRollbackRecord(records, repo, target) {
28570
28954
  const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
@@ -29260,13 +29644,13 @@ function registerBootstrapCommands(program3) {
29260
29644
  client: defaultGitHubClient(),
29261
29645
  projectMeta: meta,
29262
29646
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
29263
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs37.existsSync)(path2) ? (0, import_node_fs37.readFileSync)(path2, "utf8") : null,
29647
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
29264
29648
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
29265
29649
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
29266
29650
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
29267
29651
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
29268
29652
  // sanction, which is the pre-#3664 behaviour.
29269
- sanctionedAdmins: (0, import_node_fs37.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs37.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
29653
+ sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
29270
29654
  requiredGcpApis: (() => {
29271
29655
  const v = meta?.requiredGcpApis;
29272
29656
  if (Array.isArray(v)) return v;
@@ -29319,14 +29703,14 @@ function registerBootstrapCommands(program3) {
29319
29703
  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 () => {
29320
29704
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
29321
29705
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
29322
- if (!(0, import_node_fs37.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`);
29706
+ if (!(0, import_node_fs38.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`);
29323
29707
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
29324
29708
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
29325
- const manifest = loadBootstrapSeeds((0, import_node_fs37.readFileSync)(manifestPath, "utf8"));
29709
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
29326
29710
  const hubContents = /* @__PURE__ */ new Map();
29327
29711
  for (const s of manifest.seeds) {
29328
29712
  if (s.ownership !== "org" || s.source !== "self") continue;
29329
- hubContents.set(s.target, (0, import_node_fs37.existsSync)(s.target) ? (0, import_node_fs37.readFileSync)(s.target, "utf8") : null);
29713
+ hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
29330
29714
  }
29331
29715
  let targets;
29332
29716
  let classOf = (_repo) => "deployable";
@@ -29405,10 +29789,10 @@ function registerBootstrapCommands(program3) {
29405
29789
  return fail(`bootstrap apply: ${e.message}`);
29406
29790
  }
29407
29791
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
29408
- if (!(0, import_node_fs37.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`);
29792
+ if (!(0, import_node_fs38.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`);
29409
29793
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
29410
29794
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
29411
- const manifest = loadBootstrapSeeds((0, import_node_fs37.readFileSync)(manifestPath, "utf8"));
29795
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
29412
29796
  const baseBranch = o.class === "content" ? "main" : "development";
29413
29797
  const slug = parsedRepo.slug;
29414
29798
  const onlyTarget = o.only.trim();
@@ -29418,17 +29802,18 @@ function registerBootstrapCommands(program3) {
29418
29802
  return fail(`bootstrap apply: --only '${onlyTarget}' names no seed in ${manifestPath}. Declared targets:
29419
29803
  ${known}`);
29420
29804
  }
29805
+ const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
29421
29806
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
29422
- const readFile9 = (p) => (0, import_node_fs37.existsSync)(p) ? (0, import_node_fs37.readFileSync)(p, "utf8") : null;
29807
+ const readFile9 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
29423
29808
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
29424
29809
  const putSeed = async (target, content, ref, sha) => {
29425
- const tmp = (0, import_node_path35.join)((0, import_node_os15.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
29426
- (0, import_node_fs37.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
29810
+ const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
29811
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
29427
29812
  try {
29428
29813
  await gh(contentPutInputArgs(repo, target, tmp));
29429
29814
  } finally {
29430
29815
  try {
29431
- (0, import_node_fs37.unlinkSync)(tmp);
29816
+ (0, import_node_fs38.unlinkSync)(tmp);
29432
29817
  } catch {
29433
29818
  }
29434
29819
  }
@@ -29525,9 +29910,22 @@ function registerBootstrapCommands(program3) {
29525
29910
  exists = false;
29526
29911
  }
29527
29912
  const planned = planSeedAction(resolved, exists);
29528
- const isBlock = resolved.source === "managed-block";
29529
- const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile9) : null;
29530
- const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
29913
+ const isLegacyBlock = resolved.source === "managed-block";
29914
+ let content = null;
29915
+ let isManaged = isLegacyBlock || resolved.managedBlock != null;
29916
+ if (planned.action === "create" || planned.action === "update") {
29917
+ if (isLegacyBlock) {
29918
+ content = upsertManagedGitignoreBlock(remoteContent).content;
29919
+ } else {
29920
+ const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, remoteContent);
29921
+ if (!writeContent.ok) {
29922
+ return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
29923
+ }
29924
+ content = writeContent.content;
29925
+ isManaged = writeContent.managed;
29926
+ }
29927
+ }
29928
+ const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
29531
29929
  actions.push(action);
29532
29930
  const docBody = content ?? remoteContent;
29533
29931
  if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
@@ -29580,11 +29978,11 @@ function registerBootstrapCommands(program3) {
29580
29978
  "--head",
29581
29979
  seedPlan.branch,
29582
29980
  "--title",
29583
- onlyTarget ? `chore: propagate org-owned ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
29981
+ onlyTarget ? `chore: propagate ${onlyManagedBlock ? "Hub-managed block in" : "org-owned"} ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
29584
29982
  "--body",
29585
29983
  onlyTarget ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only ${onlyTarget} --execute\` (#3818).
29586
29984
 
29587
- \`${onlyTarget}\` is an org-owned seed declared in MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR brings this repo's copy to the Hub's. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
29985
+ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owned \`${onlyTarget}\`` : `The org-owned \`${onlyTarget}\` seed`} is declared by MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR reconciles that declared scope and preserves everything outside it. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
29588
29986
 
29589
29987
  \`${baseBranch}\` is protected (${seedPlan.reason}), so delivery goes via this branch + PR \u2014 a direct contents PUT 409s on a protected base.` : `Auto-opened by \`mmi-cli devops bootstrap apply --execute ${repo}\` (#2286): \`${baseBranch}\` is protected (${seedPlan.reason}), so the org-managed seed files are delivered via this branch + PR \u2014 a direct contents PUT 409s on a protected base ("N of N required status checks are expected"). Seeds are from MMI-Hub@${seedSource.sha}.`
29590
29988
  ]);
@@ -29690,23 +30088,24 @@ LIVE apply to ${repo}:
29690
30088
  ${applied.join("\n ")}`);
29691
30089
  }
29692
30090
  });
29693
- 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 ownership:org + source:self seed, e.g. .github/workflows/agent-pr.yml)").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 () => {
30091
+ 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 () => {
29694
30092
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
29695
30093
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
29696
- if (!(0, import_node_fs37.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`);
30094
+ if (!(0, import_node_fs38.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`);
29697
30095
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
29698
30096
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
29699
- const manifest = loadBootstrapSeeds((0, import_node_fs37.readFileSync)(manifestPath, "utf8"));
29700
- const propagatable = manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self");
30097
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30098
+ const propagatable = manifest.seeds.filter(isPropagatableSeed);
29701
30099
  if (!o.target) {
29702
30100
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
29703
30101
  ${propagatable.map((s) => s.target).join("\n ")}`);
29704
30102
  }
29705
30103
  const seed = propagatable.find((s) => s.target === o.target);
29706
- if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no ownership:org + source:self seed in ${manifestPath}. Propagatable targets:
30104
+ if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
29707
30105
  ${propagatable.map((s) => s.target).join("\n ")}`);
29708
- if (!(0, import_node_fs37.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
29709
- const hubContent = (0, import_node_fs37.readFileSync)(seed.target, "utf8");
30106
+ if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
30107
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
30108
+ const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
29710
30109
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
29711
30110
  const cfg = await loadConfig();
29712
30111
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -29715,9 +30114,9 @@ LIVE apply to ${repo}:
29715
30114
  }
29716
30115
  const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
29717
30116
  let independentCount = rosterRepos2.length;
29718
- if ((0, import_node_fs37.existsSync)("projects.json")) {
30117
+ if ((0, import_node_fs38.existsSync)("projects.json")) {
29719
30118
  try {
29720
- const local = JSON.parse((0, import_node_fs37.readFileSync)("projects.json", "utf8"));
30119
+ const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
29721
30120
  const localRepos = /* @__PURE__ */ new Set();
29722
30121
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
29723
30122
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -29762,11 +30161,12 @@ LIVE apply to ${repo}:
29762
30161
  });
29763
30162
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
29764
30163
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
29765
- const branchPrefix = "seed-propagate";
29766
30164
  const reads = [];
30165
+ const desiredByRepo = /* @__PURE__ */ new Map();
29767
30166
  for (const r of repos) {
29768
30167
  if (r.waiver) continue;
29769
- const baseBranch = classOf(r.repo) === "content" ? "main" : "development";
30168
+ const repoClass = classOf(r.repo);
30169
+ const baseBranch = repoClass === "content" ? "main" : "development";
29770
30170
  let content = null;
29771
30171
  try {
29772
30172
  const resp = await gh(["api", `repos/${r.repo}/contents/${enc(seed.target)}?ref=${baseBranch}`]);
@@ -29775,13 +30175,35 @@ LIVE apply to ${repo}:
29775
30175
  } catch {
29776
30176
  content = null;
29777
30177
  }
29778
- const drift = compareSeedBytes(hubContent, content);
30178
+ let desired;
30179
+ if (seed.managedBlock) {
30180
+ const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
30181
+ const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
30182
+ if (!resolved.ok) {
30183
+ return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
30184
+ }
30185
+ if (resolved.content == null) {
30186
+ return fail(`bootstrap propagate: cannot resolve the full template or managed block source for '${seed.target}' \u2014 refusing a partial fleet plan`);
30187
+ }
30188
+ desired = resolved.content;
30189
+ } else {
30190
+ desired = hubContent;
30191
+ }
30192
+ desiredByRepo.set(r.repo, desired);
30193
+ const drift = compareSeedBytes(desired, content);
29779
30194
  let pr2;
29780
30195
  try {
29781
- const branch = `${branchPrefix}-${r.slug}`;
29782
- const listed = await gh(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body", "--limit", "1"]);
29783
- const arr = JSON.parse(listed.stdout || "[]");
29784
- const p = arr[0];
30196
+ const listBranchPrs = async (branch) => {
30197
+ const listed = await gh(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files", "--limit", "100"]);
30198
+ return JSON.parse(listed.stdout || "[]");
30199
+ };
30200
+ const targetSpecific = (await listBranchPrs(propagationBranch(r.repo, seed.target))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
30201
+ let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
30202
+ if (!identified.length) {
30203
+ const legacy = (await listBranchPrs(legacyPropagationBranch(r.repo))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
30204
+ identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
30205
+ }
30206
+ const p = identified[0];
29785
30207
  if (p) {
29786
30208
  const rollup = p.statusCheckRollup ?? [];
29787
30209
  const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
@@ -29814,9 +30236,8 @@ LIVE apply to ${repo}:
29814
30236
  const headSha = seedSource.sha;
29815
30237
  for (const rec of plan.records) {
29816
30238
  if (rec.action !== "open-pr") continue;
29817
- const repoEntry = repos.find((r) => r.repo === rec.repo);
29818
30239
  const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
29819
- const branch = `${branchPrefix}-${repoEntry.slug}`;
30240
+ const branch = propagationBranch(rec.repo, seed.target);
29820
30241
  const baseRef = await gh(["api", `repos/${rec.repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"]);
29821
30242
  const baseSha = baseRef.stdout.trim();
29822
30243
  let branchExists = true;
@@ -29833,18 +30254,26 @@ LIVE apply to ${repo}:
29833
30254
  } catch {
29834
30255
  existingSha = void 0;
29835
30256
  }
29836
- const tmp = (0, import_node_path35.join)((0, import_node_os15.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
29837
- (0, import_node_fs37.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, hubContent, branch, existingSha)), "utf8");
30257
+ const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
30258
+ const desiredContent = desiredByRepo.get(rec.repo);
30259
+ if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
30260
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
29838
30261
  try {
29839
30262
  await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
29840
30263
  } finally {
29841
30264
  try {
29842
- (0, import_node_fs37.unlinkSync)(tmp);
30265
+ (0, import_node_fs38.unlinkSync)(tmp);
29843
30266
  } catch {
29844
30267
  }
29845
30268
  }
29846
- const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
29847
- const prDecision = decideSeedPrAction(JSON.parse(openPrs.stdout || "[]"));
30269
+ const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url,body,files"]);
30270
+ const listedOpenPrs = JSON.parse(openPrs.stdout || "[]");
30271
+ const identifiedOpenPrs = propagationPrCandidatesForTarget(
30272
+ seed.target,
30273
+ listedOpenPrs.map((p) => ({ ...p, files: p.files?.map((f) => f.path) })),
30274
+ []
30275
+ );
30276
+ const prDecision = decideSeedPrAction(identifiedOpenPrs);
29848
30277
  let prUrl;
29849
30278
  if (prDecision.action === "reuse") {
29850
30279
  prUrl = prDecision.url;
@@ -29859,11 +30288,12 @@ LIVE apply to ${repo}:
29859
30288
  "--head",
29860
30289
  branch,
29861
30290
  "--title",
29862
- `chore: propagate org-owned ${seed.target} from MMI-Hub (wave ${rec.wave})`,
30291
+ `chore: propagate ${seed.managedBlock ? "Hub-managed block in" : "org-owned"} ${seed.target} from MMI-Hub (wave ${rec.wave})`,
29863
30292
  "--body",
29864
30293
  `Auto-opened by \`mmi-cli devops bootstrap propagate --target ${seed.target} --execute\` (#4238), wave ${rec.wave}.
29865
30294
 
29866
- Propagates MMI-Hub@${headSha} 's copy of \`${seed.target}\` to this repo \u2014 the file this PR carries and nothing else.
30295
+ Propagates MMI-Hub@${headSha}'s ${seed.managedBlock ? "declared marker-bounded block inside repo-owned" : "org-owned copy of"} \`${seed.target}\` \u2014 the file this PR carries and nothing else; content outside a managed block remains untouched.
30296
+ ${renderPropagationTargetMarker(seed.target)}
29867
30297
 
29868
30298
  Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target} --execute\` (#4240) reverts this PR's merge commit; never re-run propagate with old bytes.`
29869
30299
  ]);
@@ -29897,30 +30327,30 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
29897
30327
  return fail(`bootstrap rollback: ${e.message}`);
29898
30328
  }
29899
30329
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
29900
- if (!(0, import_node_fs37.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)`);
30330
+ if (!(0, import_node_fs38.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)`);
29901
30331
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
29902
30332
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
29903
- const manifest = loadBootstrapSeeds((0, import_node_fs37.readFileSync)(manifestPath, "utf8"));
29904
- const propagatable = manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self");
30333
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30334
+ const propagatable = manifest.seeds.filter(isPropagatableSeed);
29905
30335
  if (!o.target) {
29906
30336
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
29907
30337
  ${propagatable.map((s) => s.target).join("\n ")}`);
29908
30338
  }
29909
30339
  const seed = propagatable.find((s) => s.target === o.target);
29910
- if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no ownership:org + source:self seed in ${manifestPath}. Propagatable (hence rollback-able) targets:
30340
+ if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable (hence rollback-able) targets:
29911
30341
  ${propagatable.map((s) => s.target).join("\n ")}`);
29912
30342
  const slug = parsedRepo.slug;
29913
30343
  const baseBranch = o.class === "content" ? "main" : "development";
29914
- const branchPrefix = "seed-propagate";
29915
- const propagateBranch = `${branchPrefix}-${slug}`;
30344
+ const targetPropagateBranch = propagationBranch(repo, seed.target);
30345
+ const legacyPropagateBranch = legacyPropagationBranch(repo);
29916
30346
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
29917
30347
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
29918
30348
  let candidates;
29919
30349
  if (o.record) {
29920
- if (!(0, import_node_fs37.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
30350
+ if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
29921
30351
  let parsed;
29922
30352
  try {
29923
- parsed = JSON.parse((0, import_node_fs37.readFileSync)(o.record, "utf8"));
30353
+ parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
29924
30354
  } catch (e) {
29925
30355
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
29926
30356
  }
@@ -29935,9 +30365,17 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
29935
30365
  } else {
29936
30366
  candidates = [];
29937
30367
  try {
29938
- const listed = await gh(["pr", "list", "--repo", repo, "--head", propagateBranch, "--base", baseBranch, "--state", "merged", "--json", "number,url,mergedAt,mergeCommit,files", "--limit", "20"]);
29939
- const arr = JSON.parse(listed.stdout || "[]");
29940
- for (const p of arr) {
30368
+ const listMergedBranchPrs = async (branch) => {
30369
+ const listed = await gh(["pr", "list", "--repo", repo, "--head", branch, "--base", baseBranch, "--state", "merged", "--json", "number,url,mergedAt,mergeCommit,body,files", "--limit", "100"]);
30370
+ return JSON.parse(listed.stdout || "[]");
30371
+ };
30372
+ const targetSpecific = (await listMergedBranchPrs(targetPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
30373
+ let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
30374
+ if (!identified.length) {
30375
+ const legacy = (await listMergedBranchPrs(legacyPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
30376
+ identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
30377
+ }
30378
+ for (const p of identified) {
29941
30379
  if (!p.mergeCommit?.oid) continue;
29942
30380
  candidates.push({
29943
30381
  repo,
@@ -29946,11 +30384,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
29946
30384
  url: p.url,
29947
30385
  mergeSha: p.mergeCommit.oid,
29948
30386
  mergedAt: p.mergedAt ?? "",
29949
- files: (p.files ?? []).map((f) => f.path)
30387
+ files: p.files ?? []
29950
30388
  });
29951
30389
  }
29952
30390
  } catch (e) {
29953
- return fail(`bootstrap rollback: could not read ${repo}'s merged ${propagateBranch} PR history: ${e.message}`);
30391
+ return fail(`bootstrap rollback: could not read ${repo}'s merged ${targetPropagateBranch} or positively matching legacy PR history: ${e.message}`);
29954
30392
  }
29955
30393
  }
29956
30394
  const plan = planRollback(repo, seed.target, slug, candidates);
@@ -29989,13 +30427,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
29989
30427
  } catch {
29990
30428
  existingSha = void 0;
29991
30429
  }
29992
- const tmp = (0, import_node_path35.join)((0, import_node_os15.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
29993
- (0, import_node_fs37.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
30430
+ const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
30431
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
29994
30432
  try {
29995
30433
  await gh(contentPutInputArgs(repo, seed.target, tmp));
29996
30434
  } finally {
29997
30435
  try {
29998
- (0, import_node_fs37.unlinkSync)(tmp);
30436
+ (0, import_node_fs38.unlinkSync)(tmp);
29999
30437
  } catch {
30000
30438
  }
30001
30439
  }
@@ -30017,12 +30455,12 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
30017
30455
  }
30018
30456
 
30019
30457
  // src/stage-commands.ts
30020
- var import_node_fs39 = require("node:fs");
30021
- var import_node_path37 = require("node:path");
30458
+ var import_node_fs40 = require("node:fs");
30459
+ var import_node_path38 = require("node:path");
30022
30460
 
30023
30461
  // src/port-registry.ts
30024
- var import_node_fs38 = require("node:fs");
30025
- var import_node_path36 = require("node:path");
30462
+ var import_node_fs39 = require("node:fs");
30463
+ var import_node_path37 = require("node:path");
30026
30464
 
30027
30465
  // ../infra/port-geometry.mjs
30028
30466
  var PORT_BLOCK = 100;
@@ -30036,8 +30474,8 @@ function nextPortBlock(registry2) {
30036
30474
  return [base, base + PORT_SPAN];
30037
30475
  }
30038
30476
  function loadPortRegistry(path2) {
30039
- if (!(0, import_node_fs38.existsSync)(path2)) return {};
30040
- const raw = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
30477
+ if (!(0, import_node_fs39.existsSync)(path2)) return {};
30478
+ const raw = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
30041
30479
  const out = {};
30042
30480
  for (const [key, value] of Object.entries(raw)) {
30043
30481
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -30051,9 +30489,9 @@ function ensurePortRange(repo, path2) {
30051
30489
  const existing = registry2[repo];
30052
30490
  if (existing) return existing;
30053
30491
  const range = nextPortBlock(registry2);
30054
- const raw = (0, import_node_fs38.existsSync)(path2) ? JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8")) : {};
30492
+ const raw = (0, import_node_fs39.existsSync)(path2) ? JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8")) : {};
30055
30493
  raw[repo] = range;
30056
- (0, import_node_fs38.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
30494
+ (0, import_node_fs39.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
30057
30495
  return range;
30058
30496
  }
30059
30497
  function portCursorSeed(registry2) {
@@ -30075,22 +30513,22 @@ function existingPortRange(repo, registry2) {
30075
30513
  return registry2[repo] ?? null;
30076
30514
  }
30077
30515
  function portRangeInfraAt(root, source) {
30078
- const registryPath = (0, import_node_path36.join)(root, "infra", "port-ranges.json");
30079
- const ddbScriptPath = (0, import_node_path36.join)(root, "infra", "port-ddb.mjs");
30080
- if (!(0, import_node_fs38.existsSync)(registryPath) || !(0, import_node_fs38.existsSync)(ddbScriptPath)) return null;
30516
+ const registryPath = (0, import_node_path37.join)(root, "infra", "port-ranges.json");
30517
+ const ddbScriptPath = (0, import_node_path37.join)(root, "infra", "port-ddb.mjs");
30518
+ if (!(0, import_node_fs39.existsSync)(registryPath) || !(0, import_node_fs39.existsSync)(ddbScriptPath)) return null;
30081
30519
  return { root, source, registryPath, ddbScriptPath };
30082
30520
  }
30083
30521
  function resolvePortRangeInfra(cwd, packageDir) {
30084
30522
  const direct = portRangeInfraAt(cwd, "cwd");
30085
30523
  if (direct) return direct;
30086
- for (let dir = cwd; ; dir = (0, import_node_path36.dirname)(dir)) {
30087
- const sibling = portRangeInfraAt((0, import_node_path36.join)(dir, "MMI-Hub"), "sibling-hub");
30524
+ for (let dir = cwd; ; dir = (0, import_node_path37.dirname)(dir)) {
30525
+ const sibling = portRangeInfraAt((0, import_node_path37.join)(dir, "MMI-Hub"), "sibling-hub");
30088
30526
  if (sibling) return sibling;
30089
- const parent = (0, import_node_path36.dirname)(dir);
30527
+ const parent = (0, import_node_path37.dirname)(dir);
30090
30528
  if (parent === dir) break;
30091
30529
  }
30092
30530
  if (packageDir) {
30093
- const pkgRoot = (0, import_node_path36.join)(packageDir, "..", "..");
30531
+ const pkgRoot = (0, import_node_path37.join)(packageDir, "..", "..");
30094
30532
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
30095
30533
  if (pkgFrom) return pkgFrom;
30096
30534
  }
@@ -30284,8 +30722,8 @@ function registerStageCommands(program3) {
30284
30722
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
30285
30723
  return decideStage({
30286
30724
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
30287
- hasCompose: (0, import_node_fs39.existsSync)((0, import_node_path37.join)(process.cwd(), "docker-compose.yml")),
30288
- hasEnvExample: (0, import_node_fs39.existsSync)((0, import_node_path37.join)(process.cwd(), ".env.example"))
30725
+ hasCompose: (0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), "docker-compose.yml")),
30726
+ hasEnvExample: (0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), ".env.example"))
30289
30727
  });
30290
30728
  }
30291
30729
  async function fetchStageVaultEnvMerge() {
@@ -30776,10 +31214,10 @@ function registerBoardCommands(program3) {
30776
31214
  }
30777
31215
 
30778
31216
  // src/merge-cleanup.ts
30779
- var import_node_fs40 = require("node:fs");
31217
+ var import_node_fs41 = require("node:fs");
30780
31218
  var import_promises9 = require("node:fs/promises");
30781
- var import_node_path39 = require("node:path");
30782
- var import_node_os16 = require("node:os");
31219
+ var import_node_path40 = require("node:path");
31220
+ var import_node_os17 = require("node:os");
30783
31221
  var import_node_child_process17 = require("node:child_process");
30784
31222
 
30785
31223
  // src/board-advance.ts
@@ -30866,7 +31304,7 @@ function boardAdvanceFailureMessage(result) {
30866
31304
 
30867
31305
  // src/deferred-registry-store.ts
30868
31306
  var import_promises8 = require("node:fs/promises");
30869
- var import_node_path38 = require("node:path");
31307
+ var import_node_path39 = require("node:path");
30870
31308
  var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
30871
31309
  async function atomicWrite(target, contents) {
30872
31310
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -30917,12 +31355,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
30917
31355
  },
30918
31356
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
30919
31357
  write: async (entries) => {
30920
- await (0, import_promises8.mkdir)((0, import_node_path38.dirname)(registryPath), { recursive: true });
31358
+ await (0, import_promises8.mkdir)((0, import_node_path39.dirname)(registryPath), { recursive: true });
30921
31359
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
30922
31360
  },
30923
31361
  // Serialized read-modify-write under the repo-wide lock (#2846).
30924
31362
  update: async (mutate) => {
30925
- await (0, import_promises8.mkdir)((0, import_node_path38.dirname)(registryPath), { recursive: true });
31363
+ await (0, import_promises8.mkdir)((0, import_node_path39.dirname)(registryPath), { recursive: true });
30926
31364
  const deadline = Date.now() + opts.maxWaitMs;
30927
31365
  for (; ; ) {
30928
31366
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -31086,17 +31524,17 @@ ${err.stderr ?? ""}`;
31086
31524
  return { step, status: `failed: ${msg}` };
31087
31525
  }
31088
31526
  }
31089
- async function bestEffortCloseMissingWorktreeLeases(leaseDir = (0, import_node_path39.join)((0, import_node_os16.homedir)(), ".jerv", "leases"), exists = import_node_fs40.existsSync) {
31527
+ async function bestEffortCloseMissingWorktreeLeases(leaseDir = (0, import_node_path40.join)((0, import_node_os17.homedir)(), ".jerv", "leases"), exists = import_node_fs41.existsSync) {
31090
31528
  let names = [];
31091
31529
  try {
31092
- names = (0, import_node_fs40.readdirSync)(leaseDir);
31530
+ names = (0, import_node_fs41.readdirSync)(leaseDir);
31093
31531
  } catch {
31094
31532
  return;
31095
31533
  }
31096
31534
  for (const name of names) {
31097
31535
  if (!name.endsWith(".json")) continue;
31098
31536
  try {
31099
- const rec = JSON.parse((0, import_node_fs40.readFileSync)((0, import_node_path39.join)(leaseDir, name), "utf8"));
31537
+ const rec = JSON.parse((0, import_node_fs41.readFileSync)((0, import_node_path40.join)(leaseDir, name), "utf8"));
31100
31538
  if (rec.kind !== "worktree" || rec.state === "closed" || typeof rec.ref !== "string" || !rec.ref.trim()) continue;
31101
31539
  if (!exists(rec.ref)) await bestEffortLeaseClose(rec.ref);
31102
31540
  } catch {
@@ -31110,7 +31548,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
31110
31548
  );
31111
31549
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
31112
31550
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
31113
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path39.dirname)((0, import_node_path39.dirname)(worktreeGitRoot)) : repoRoot2;
31551
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path40.dirname)((0, import_node_path40.dirname)(worktreeGitRoot)) : repoRoot2;
31114
31552
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
31115
31553
  const owners = readWorktreeOwners(primaryRepoRoot);
31116
31554
  const removalNow = Date.now();
@@ -31169,7 +31607,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
31169
31607
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
31170
31608
  beforeWorktrees,
31171
31609
  startingPath: branch.worktreePath,
31172
- pathExists: (p) => (0, import_node_fs40.existsSync)(p),
31610
+ pathExists: (p) => (0, import_node_fs41.existsSync)(p),
31173
31611
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
31174
31612
  teardownWorktreeStage,
31175
31613
  deferredStore,
@@ -31208,7 +31646,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
31208
31646
  let removalAttempted = false;
31209
31647
  try {
31210
31648
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, cleanupRoots, {
31211
- realpath: (path2) => (0, import_node_fs40.realpathSync)(path2)
31649
+ realpath: (path2) => (0, import_node_fs41.realpathSync)(path2)
31212
31650
  });
31213
31651
  if (!cleanupTarget.ok) {
31214
31652
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -31310,13 +31748,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
31310
31748
  const commits = JSON.parse(raw).commits ?? [];
31311
31749
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
31312
31750
  if (!body) return void 0;
31313
- const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path39.join)((0, import_node_os16.tmpdir)(), "mmi-squash-body-"));
31314
- const path2 = (0, import_node_path39.join)(dir, "body.txt");
31315
- (0, import_node_fs40.writeFileSync)(path2, `${body}
31751
+ const dir = (0, import_node_fs41.mkdtempSync)((0, import_node_path40.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
31752
+ const path2 = (0, import_node_path40.join)(dir, "body.txt");
31753
+ (0, import_node_fs41.writeFileSync)(path2, `${body}
31316
31754
  `, "utf8");
31317
31755
  return { path: path2, cleanup: () => {
31318
31756
  try {
31319
- (0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
31757
+ (0, import_node_fs41.rmSync)(dir, { recursive: true, force: true });
31320
31758
  } catch {
31321
31759
  }
31322
31760
  } };
@@ -31439,13 +31877,13 @@ var realWorktreeDirRemover = {
31439
31877
  const target = win32LongPath(p);
31440
31878
  let st;
31441
31879
  try {
31442
- st = (0, import_node_fs40.lstatSync)(target);
31880
+ st = (0, import_node_fs41.lstatSync)(target);
31443
31881
  } catch {
31444
31882
  return null;
31445
31883
  }
31446
31884
  if (st.isSymbolicLink()) return "link";
31447
31885
  try {
31448
- (0, import_node_fs40.readlinkSync)(target);
31886
+ (0, import_node_fs41.readlinkSync)(target);
31449
31887
  return "link";
31450
31888
  } catch {
31451
31889
  }
@@ -31453,7 +31891,7 @@ var realWorktreeDirRemover = {
31453
31891
  },
31454
31892
  readdir: (p) => {
31455
31893
  try {
31456
- return (0, import_node_fs40.readdirSync)(win32LongPath(p));
31894
+ return (0, import_node_fs41.readdirSync)(win32LongPath(p));
31457
31895
  } catch {
31458
31896
  return [];
31459
31897
  }
@@ -31463,9 +31901,9 @@ var realWorktreeDirRemover = {
31463
31901
  detachLink: (p) => {
31464
31902
  const target = win32LongPath(p);
31465
31903
  try {
31466
- (0, import_node_fs40.rmdirSync)(target);
31904
+ (0, import_node_fs41.rmdirSync)(target);
31467
31905
  } catch {
31468
- (0, import_node_fs40.unlinkSync)(target);
31906
+ (0, import_node_fs41.unlinkSync)(target);
31469
31907
  }
31470
31908
  },
31471
31909
  // #4904: Windows MAX_PATH aborts git worktree remove; the fallback must use \\?\ so the dir
@@ -31488,7 +31926,7 @@ function worktreeRemoveDeps(execGit) {
31488
31926
  detachReparsePoints: (worktreePath) => detachReparsePoints(worktreePath, realWorktreeDirRemover),
31489
31927
  removeWorktreeDir: async (worktreePath) => removeWorktreeTree(worktreePath, await resolvePrimaryCheckout(execGit), realWorktreeDirRemover),
31490
31928
  // #4850: verify the directory is actually gone before any caller reports completion.
31491
- pathExists: (worktreePath) => (0, import_node_fs40.existsSync)(worktreePath)
31929
+ pathExists: (worktreePath) => (0, import_node_fs41.existsSync)(worktreePath)
31492
31930
  };
31493
31931
  }
31494
31932
  async function worktreeHasStageState(worktreePath) {
@@ -31502,9 +31940,9 @@ async function worktreeHasStageState(worktreePath) {
31502
31940
  }
31503
31941
  }
31504
31942
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
31505
- if (!(0, import_node_fs40.existsSync)(statePath)) return false;
31943
+ if (!(0, import_node_fs41.existsSync)(statePath)) return false;
31506
31944
  try {
31507
- const state = JSON.parse((0, import_node_fs40.readFileSync)(statePath, "utf8"));
31945
+ const state = JSON.parse((0, import_node_fs41.readFileSync)(statePath, "utf8"));
31508
31946
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
31509
31947
  return Boolean(recordedCwd && isPathUnderDirectory2(recordedCwd, worktreePath));
31510
31948
  } catch {
@@ -31939,15 +32377,15 @@ async function checkDocsIndexAtHead(opts, deps) {
31939
32377
  }
31940
32378
 
31941
32379
  // src/worktree-lifecycle-commands.ts
31942
- var import_node_fs42 = require("node:fs");
32380
+ var import_node_fs43 = require("node:fs");
31943
32381
  var import_promises10 = require("node:fs/promises");
31944
- var import_node_os17 = require("node:os");
31945
- var import_node_path41 = require("node:path");
32382
+ var import_node_os18 = require("node:os");
32383
+ var import_node_path42 = require("node:path");
31946
32384
 
31947
32385
  // src/worktree-install-cache.ts
31948
- var import_node_crypto8 = require("node:crypto");
31949
- var import_node_fs41 = require("node:fs");
31950
- var import_node_path40 = require("node:path");
32386
+ var import_node_crypto9 = require("node:crypto");
32387
+ var import_node_fs42 = require("node:fs");
32388
+ var import_node_path41 = require("node:path");
31951
32389
  var CACHE_DIR = "worktree-install-cache";
31952
32390
  var MANIFEST = "manifest.json";
31953
32391
  var NODE_MODULES2 = "node_modules";
@@ -31960,24 +32398,24 @@ var LOCKFILE_NAMES = [
31960
32398
  "package-lock.json"
31961
32399
  ];
31962
32400
  var realWorktreeInstallCacheFs = {
31963
- exists: import_node_fs41.existsSync,
31964
- readFile: (path2) => (0, import_node_fs41.readFileSync)(path2, "utf8"),
31965
- lstat: (path2) => (0, import_node_fs41.lstatSync)(path2),
31966
- copyDir: (from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }),
32401
+ exists: import_node_fs42.existsSync,
32402
+ readFile: (path2) => (0, import_node_fs42.readFileSync)(path2, "utf8"),
32403
+ lstat: (path2) => (0, import_node_fs42.lstatSync)(path2),
32404
+ copyDir: (from, to) => (0, import_node_fs42.cpSync)(from, to, { recursive: true, force: true }),
31967
32405
  mkdirp: (path2) => {
31968
- (0, import_node_fs41.mkdirSync)(path2, { recursive: true });
32406
+ (0, import_node_fs42.mkdirSync)(path2, { recursive: true });
31969
32407
  },
31970
- writeFile: (path2, contents) => (0, import_node_fs41.writeFileSync)(path2, contents, "utf8"),
32408
+ writeFile: (path2, contents) => (0, import_node_fs42.writeFileSync)(path2, contents, "utf8"),
31971
32409
  rm: (path2) => {
31972
- (0, import_node_fs41.rmSync)(path2, { recursive: true, force: true });
32410
+ (0, import_node_fs42.rmSync)(path2, { recursive: true, force: true });
31973
32411
  }
31974
32412
  };
31975
32413
  function hashLockfileBytes(contents) {
31976
- return (0, import_node_crypto8.createHash)("sha256").update(contents).digest("hex");
32414
+ return (0, import_node_crypto9.createHash)("sha256").update(contents).digest("hex");
31977
32415
  }
31978
32416
  function resolveWorktreeInstallLockfile(packageDir, fs2 = realWorktreeInstallCacheFs) {
31979
32417
  for (const name of LOCKFILE_NAMES) {
31980
- const path2 = (0, import_node_path40.join)(packageDir, name);
32418
+ const path2 = (0, import_node_path41.join)(packageDir, name);
31981
32419
  if (!fs2.exists(path2)) continue;
31982
32420
  try {
31983
32421
  const hash = hashLockfileBytes(fs2.readFile(path2));
@@ -31992,8 +32430,8 @@ function worktreeInstallCacheEntry(primaryRoot, lockfileHash) {
31992
32430
  const root = repoRuntimeStatePath(primaryRoot, CACHE_DIR, lockfileHash);
31993
32431
  return {
31994
32432
  root,
31995
- manifestPath: (0, import_node_path40.join)(root, MANIFEST),
31996
- nodeModulesPath: (0, import_node_path40.join)(root, NODE_MODULES2)
32433
+ manifestPath: (0, import_node_path41.join)(root, MANIFEST),
32434
+ nodeModulesPath: (0, import_node_path41.join)(root, NODE_MODULES2)
31997
32435
  };
31998
32436
  }
31999
32437
  function readWorktreeInstallCacheManifest(manifestPath, fs2 = realWorktreeInstallCacheFs) {
@@ -32033,7 +32471,7 @@ function invalidateWorktreeInstallCacheEntry(entry, fs2 = realWorktreeInstallCac
32033
32471
  }
32034
32472
  }
32035
32473
  function removeMaterializedTree(packageDir, fs2) {
32036
- const dest = (0, import_node_path40.join)(packageDir, NODE_MODULES2);
32474
+ const dest = (0, import_node_path41.join)(packageDir, NODE_MODULES2);
32037
32475
  if (!fs2.exists(dest)) return;
32038
32476
  fs2.rm(dest);
32039
32477
  if (fs2.exists(dest)) {
@@ -32041,7 +32479,7 @@ function removeMaterializedTree(packageDir, fs2) {
32041
32479
  }
32042
32480
  }
32043
32481
  function materializeCachedNodeModules(entry, destPackageDir, fs2 = realWorktreeInstallCacheFs) {
32044
- const dest = (0, import_node_path40.join)(destPackageDir, NODE_MODULES2);
32482
+ const dest = (0, import_node_path41.join)(destPackageDir, NODE_MODULES2);
32045
32483
  try {
32046
32484
  if (fs2.exists(dest)) fs2.rm(dest);
32047
32485
  fs2.mkdirp(destPackageDir);
@@ -32056,7 +32494,7 @@ function materializeCachedNodeModules(entry, destPackageDir, fs2 = realWorktreeI
32056
32494
  }
32057
32495
  }
32058
32496
  async function storeWorktreeInstallCacheEntry(primaryRoot, lockfile, command, sourcePackageDir, fs2 = realWorktreeInstallCacheFs, now = Date.now()) {
32059
- const source = (0, import_node_path40.join)(sourcePackageDir, NODE_MODULES2);
32497
+ const source = (0, import_node_path41.join)(sourcePackageDir, NODE_MODULES2);
32060
32498
  if (!isMaterializableNodeModulesDir(source, fs2)) return;
32061
32499
  const entry = worktreeInstallCacheEntry(primaryRoot, lockfile.hash);
32062
32500
  const manifest = {
@@ -32340,7 +32778,7 @@ function classifyStaleLeaks(input) {
32340
32778
  var defaultOrphanDirScanDeps = {
32341
32779
  listDirs: (root) => {
32342
32780
  try {
32343
- return (0, import_node_fs42.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path41.join)(root, e.name));
32781
+ return (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path42.join)(root, e.name));
32344
32782
  } catch {
32345
32783
  return [];
32346
32784
  }
@@ -32530,13 +32968,13 @@ function registerWorktreeCommands(program3) {
32530
32968
  const detached = headBorn && !symbolicBranch;
32531
32969
  const branch = symbolicBranch || (detached ? "HEAD" : "");
32532
32970
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
32533
- const gitFile = (0, import_node_path41.join)(wtPath, ".git");
32534
- const isLinked = (0, import_node_fs42.existsSync)(gitFile) && (0, import_node_fs42.statSync)(gitFile).isFile();
32971
+ const gitFile = (0, import_node_path42.join)(wtPath, ".git");
32972
+ const isLinked = (0, import_node_fs43.existsSync)(gitFile) && (0, import_node_fs43.statSync)(gitFile).isFile();
32535
32973
  if (apply && !isLinked) {
32536
32974
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
32537
32975
  }
32538
32976
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
32539
- const primaryCheckout = commonDir ? (0, import_node_path41.dirname)(commonDir) : wtPath;
32977
+ const primaryCheckout = commonDir ? (0, import_node_path42.dirname)(commonDir) : wtPath;
32540
32978
  const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
32541
32979
  const orphan = classifyOrphanedWorktree({
32542
32980
  branch,
@@ -32796,7 +33234,7 @@ async function gatherWorktreeContext() {
32796
33234
  const porcelain = (await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
32797
33235
  const parsed = parseWorktreePorcelain(porcelain);
32798
33236
  const primaryPath = parsed[0]?.path ?? repoRoot2;
32799
- const worktrees = parsed.map((w) => ({ path: (0, import_node_path41.resolve)(toNativePath(w.path)), branch: w.branch, primary: w.path === primaryPath }));
33237
+ const worktrees = parsed.map((w) => ({ path: (0, import_node_path42.resolve)(toNativePath(w.path)), branch: w.branch, primary: w.path === primaryPath }));
32800
33238
  const branchOut = (await execFileP2("git", ["branch", "--format=%(refname:short)"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
32801
33239
  const localBranches = branchOut.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
32802
33240
  const currentBranch2 = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || void 0;
@@ -32834,17 +33272,17 @@ async function gatherWorktreeContext() {
32834
33272
  if (s) stages.push({ path: wt.path, port: s.port });
32835
33273
  }
32836
33274
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
32837
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path41.dirname)((0, import_node_path41.dirname)(worktreeGitRoot)) : repoRoot2;
33275
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path42.dirname)((0, import_node_path42.dirname)(worktreeGitRoot)) : repoRoot2;
32838
33276
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
32839
33277
  let orphanDirs = [];
32840
- if ((0, import_node_fs42.existsSync)(wtRoot)) {
33278
+ if ((0, import_node_fs43.existsSync)(wtRoot)) {
32841
33279
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
32842
33280
  ...defaultOrphanDirScanDeps,
32843
33281
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
32844
33282
  });
32845
33283
  }
32846
33284
  const repoContainer = worktreesRootOf(primaryRepoRoot);
32847
- if ((0, import_node_fs42.existsSync)(repoContainer)) {
33285
+ if ((0, import_node_fs43.existsSync)(repoContainer)) {
32848
33286
  for (const dir of defaultOrphanDirScanDeps.listDirs(repoContainer)) {
32849
33287
  if (orphanDirs.some((o) => o.path === dir)) continue;
32850
33288
  const inspected = inspectSiblingWorktreeDir(dir, worktreeGitRoot);
@@ -32868,7 +33306,7 @@ async function gatherWorktreeContext() {
32868
33306
  });
32869
33307
  const helperWorktrees = scanHelperWorktrees(primaryRepoRoot, worktreeGitRoot);
32870
33308
  const leaseRefs = readJervWorktreeLeaseRefs();
32871
- const missingLeaseRefs = leaseRefs.filter((ref) => !(0, import_node_fs42.existsSync)(ref));
33309
+ const missingLeaseRefs = leaseRefs.filter((ref) => !(0, import_node_fs43.existsSync)(ref));
32872
33310
  const remoteUrls = await gitRemoteUrls();
32873
33311
  const otherCheckouts = discoverOtherPrimaries(primaryRepoRoot).map((path2) => ({
32874
33312
  path: path2,
@@ -32898,16 +33336,16 @@ async function gatherWorktreeContext() {
32898
33336
  };
32899
33337
  }
32900
33338
  function gitConfigRemoteUrls(checkout) {
32901
- const gitPath = (0, import_node_path41.join)(checkout, ".git");
32902
- let configPath = (0, import_node_path41.join)(checkout, ".git", "config");
33339
+ const gitPath = (0, import_node_path42.join)(checkout, ".git");
33340
+ let configPath = (0, import_node_path42.join)(checkout, ".git", "config");
32903
33341
  try {
32904
- const st = (0, import_node_fs42.statSync)(gitPath);
33342
+ const st = (0, import_node_fs43.statSync)(gitPath);
32905
33343
  if (st.isFile()) return [];
32906
33344
  } catch {
32907
33345
  return [];
32908
33346
  }
32909
33347
  try {
32910
- const text = (0, import_node_fs42.readFileSync)(configPath, "utf8");
33348
+ const text = (0, import_node_fs43.readFileSync)(configPath, "utf8");
32911
33349
  return [...text.matchAll(/^\s*url\s*=\s*(.+)$/gm)].map((m) => m[1].trim());
32912
33350
  } catch {
32913
33351
  return [];
@@ -32921,26 +33359,26 @@ async function gitRemoteUrls() {
32921
33359
  }
32922
33360
  function discoverOtherPrimaries(primaryRepoRoot) {
32923
33361
  const found = /* @__PURE__ */ new Set();
32924
- const parent = (0, import_node_path41.dirname)(primaryRepoRoot);
33362
+ const parent = (0, import_node_path42.dirname)(primaryRepoRoot);
32925
33363
  try {
32926
- for (const name of (0, import_node_fs42.readdirSync)(parent)) {
32927
- const path2 = (0, import_node_path41.join)(parent, name);
33364
+ for (const name of (0, import_node_fs43.readdirSync)(parent)) {
33365
+ const path2 = (0, import_node_path42.join)(parent, name);
32928
33366
  if (path2 === primaryRepoRoot) continue;
32929
- if ((0, import_node_fs42.existsSync)((0, import_node_path41.join)(path2, ".git"))) found.add(path2);
33367
+ if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(path2, ".git"))) found.add(path2);
32930
33368
  }
32931
33369
  } catch {
32932
33370
  }
32933
- const mirror = (0, import_node_path41.join)((0, import_node_os17.homedir)(), "Projects", (0, import_node_path41.basename)(primaryRepoRoot));
32934
- if (mirror !== primaryRepoRoot && (0, import_node_fs42.existsSync)((0, import_node_path41.join)(mirror, ".git"))) found.add(mirror);
33371
+ const mirror = (0, import_node_path42.join)((0, import_node_os18.homedir)(), "Projects", (0, import_node_path42.basename)(primaryRepoRoot));
33372
+ if (mirror !== primaryRepoRoot && (0, import_node_fs43.existsSync)((0, import_node_path42.join)(mirror, ".git"))) found.add(mirror);
32935
33373
  return [...found];
32936
33374
  }
32937
- function readJervWorktreeLeaseRefs(leaseDir = (0, import_node_path41.join)((0, import_node_os17.homedir)(), ".jerv", "leases")) {
33375
+ function readJervWorktreeLeaseRefs(leaseDir = (0, import_node_path42.join)((0, import_node_os18.homedir)(), ".jerv", "leases")) {
32938
33376
  try {
32939
33377
  const refs = [];
32940
- for (const name of (0, import_node_fs42.readdirSync)(leaseDir)) {
33378
+ for (const name of (0, import_node_fs43.readdirSync)(leaseDir)) {
32941
33379
  if (!name.endsWith(".json")) continue;
32942
33380
  try {
32943
- const rec = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path41.join)(leaseDir, name), "utf8"));
33381
+ const rec = JSON.parse((0, import_node_fs43.readFileSync)((0, import_node_path42.join)(leaseDir, name), "utf8"));
32944
33382
  if (rec.kind === "worktree" && rec.state !== "closed" && typeof rec.ref === "string" && rec.ref.trim()) {
32945
33383
  refs.push(rec.ref);
32946
33384
  }
@@ -32956,9 +33394,9 @@ function scanHelperWorktrees(thisPrimary, thisWorktreeGitRoot) {
32956
33394
  const primaries = [thisPrimary, ...discoverOtherPrimaries(thisPrimary)];
32957
33395
  const out = [];
32958
33396
  for (const primary of primaries) {
32959
- const gitRoot = primary === thisPrimary ? thisWorktreeGitRoot : (0, import_node_path41.join)(primary, ".git", "worktrees");
33397
+ const gitRoot = primary === thisPrimary ? thisWorktreeGitRoot : (0, import_node_path42.join)(primary, ".git", "worktrees");
32960
33398
  for (const root of helperWorktreeRoots(primary)) {
32961
- if (!(0, import_node_fs42.existsSync)(root)) continue;
33399
+ if (!(0, import_node_fs43.existsSync)(root)) continue;
32962
33400
  for (const dir of defaultOrphanDirScanDeps.listDirs(root)) {
32963
33401
  const inspected = inspectSiblingWorktreeDir(dir, gitRoot);
32964
33402
  const classified = classifySiblingWorktreeDir(inspected);
@@ -32998,8 +33436,8 @@ ${err.stderr ?? ""}`;
32998
33436
  }
32999
33437
 
33000
33438
  // src/issue-commands.ts
33001
- var import_node_fs43 = require("node:fs");
33002
- var import_node_crypto9 = require("node:crypto");
33439
+ var import_node_fs44 = require("node:fs");
33440
+ var import_node_crypto10 = require("node:crypto");
33003
33441
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
33004
33442
  var ReparentConflictError = class extends Error {
33005
33443
  constructor(message, payload) {
@@ -33016,7 +33454,7 @@ async function editIssue(client, options, deps = {}) {
33016
33454
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
33017
33455
  const patch = {};
33018
33456
  let bodyChanged = false;
33019
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs43.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
33457
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs44.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
33020
33458
  if (options.titleFile !== void 0) {
33021
33459
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
33022
33460
  } else if (options.title !== void 0) {
@@ -33290,7 +33728,7 @@ function rowIdempotencyKey(batchKey, spec) {
33290
33728
  const identity = `${spec.type}
33291
33729
  ${spec.title.trim()}
33292
33730
  ${spec.body ?? ""}`;
33293
- const hash = (0, import_node_crypto9.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
33731
+ const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
33294
33732
  return `${batchKey}:${hash}`;
33295
33733
  }
33296
33734
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -33621,7 +34059,7 @@ function extendCreateCommand(issue2, batchAttach) {
33621
34059
  if (opts.batch) {
33622
34060
  let specs;
33623
34061
  try {
33624
- const raw = (0, import_node_fs43.readFileSync)(opts.batch, "utf8");
34062
+ const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
33625
34063
  specs = JSON.parse(raw);
33626
34064
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33627
34065
  } catch (e) {
@@ -33696,8 +34134,8 @@ ${lines}`, {
33696
34134
  }
33697
34135
 
33698
34136
  // src/train-commands.ts
33699
- var import_node_fs44 = require("node:fs");
33700
- var import_node_path42 = require("node:path");
34137
+ var import_node_fs45 = require("node:fs");
34138
+ var import_node_path43 = require("node:path");
33701
34139
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
33702
34140
  function resolveReleaseBumpIntent(raw) {
33703
34141
  const intent = typeof raw === "string" ? raw.trim() : "";
@@ -33708,7 +34146,7 @@ function resolveReleaseBumpIntent(raw) {
33708
34146
  }
33709
34147
  function readRepoVersion() {
33710
34148
  try {
33711
- return JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path42.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
34149
+ return JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path43.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
33712
34150
  } catch {
33713
34151
  return void 0;
33714
34152
  }
@@ -33865,9 +34303,9 @@ function registerDeployCommands(program3) {
33865
34303
  }
33866
34304
 
33867
34305
  // src/discovery-commands.ts
33868
- var import_node_fs45 = require("node:fs");
33869
- var import_node_os18 = require("node:os");
33870
- var import_node_path43 = require("node:path");
34306
+ var import_node_fs46 = require("node:fs");
34307
+ var import_node_os19 = require("node:os");
34308
+ var import_node_path44 = require("node:path");
33871
34309
  var GC_GH_TIMEOUT_MS3 = 2e4;
33872
34310
  async function collectStatus() {
33873
34311
  const repo = await resolveRepo();
@@ -33969,7 +34407,7 @@ function onboardPluginGate(deps) {
33969
34407
  declared,
33970
34408
  settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
33971
34409
  }).effective;
33972
- return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-updater reconcile` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-updater is the single release-gated writer" };
34410
+ return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-hub update` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-hub is the single release-gated writer" };
33973
34411
  }
33974
34412
  async function collectOnboardStatus(opts = {}) {
33975
34413
  const cfg = await loadConfig();
@@ -34051,10 +34489,10 @@ async function collectOnboardStatus(opts = {}) {
34051
34489
  else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
34052
34490
  else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
34053
34491
  }
34054
- const home = (0, import_node_os18.homedir)();
34492
+ const home = (0, import_node_os19.homedir)();
34055
34493
  const plugin = onboardPluginGate({
34056
- readKnown: () => readFileSyncSafe((0, import_node_path43.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs45.readFileSync),
34057
- readSettings: () => readFileSyncSafe((0, import_node_path43.join)(home, ".claude", "settings.json"), import_node_fs45.readFileSync)
34494
+ readKnown: () => readFileSyncSafe((0, import_node_path44.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
34495
+ readSettings: () => readFileSyncSafe((0, import_node_path44.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
34058
34496
  });
34059
34497
  return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
34060
34498
  }
@@ -36269,13 +36707,13 @@ var surfaces_default = {
36269
36707
  publishVisibility: "public"
36270
36708
  },
36271
36709
  {
36272
- id: "mmi-updater",
36710
+ id: "mmi-hub",
36273
36711
  classification: "capability",
36274
36712
  kind: "cli",
36275
36713
  ownerPath: "updater/package.json",
36276
36714
  deliveryPath: "updater/package.json",
36277
36715
  delivery: "npm",
36278
- applicability: "editor-agnostic agent surfaces",
36716
+ applicability: "editor-agnostic MMI installation and maintenance; npm package @mutmutco/hub exposing mmi-hub",
36279
36717
  versionCoordinated: true,
36280
36718
  versionPaths: [
36281
36719
  {
@@ -36292,6 +36730,34 @@ var surfaces_default = {
36292
36730
  },
36293
36731
  publishVisibility: "public"
36294
36732
  },
36733
+ {
36734
+ id: "mmi-updater-compat",
36735
+ classification: "packaging",
36736
+ kind: "cli",
36737
+ ownerPath: "packages/updater-compat/package.json",
36738
+ deliveryPath: "packages/updater-compat/package.json",
36739
+ delivery: "npm",
36740
+ applicability: "one-window deprecated @mutmutco/updater / mmi-updater wrapper delegating to the exact coordinated @mutmutco/hub version",
36741
+ versionCoordinated: true,
36742
+ versionPaths: [
36743
+ {
36744
+ path: "packages/updater-compat/package.json",
36745
+ pointer: "version"
36746
+ },
36747
+ {
36748
+ path: "packages/updater-compat/package.json",
36749
+ pointer: "dependencies.@mutmutco/hub"
36750
+ }
36751
+ ],
36752
+ additionalPaths: [
36753
+ "packages/updater-compat/dist"
36754
+ ],
36755
+ artifactIdentity: {
36756
+ kind: "npm-pack",
36757
+ packagePath: "packages/updater-compat"
36758
+ },
36759
+ publishVisibility: "public"
36760
+ },
36295
36761
  {
36296
36762
  id: "mmi-cli-lock",
36297
36763
  classification: "packaging",
@@ -36753,7 +37219,7 @@ function checkCliVersion(input, releasedNote) {
36753
37219
  warn: true,
36754
37220
  label: "mmi-cli",
36755
37221
  detail: `${report.currentVersion} \u2014 freshness UNKNOWN, the published version could not be read`,
36756
- fix: "check it directly: `mmi-cli --version` against `npm view @mutmutco/cli version`; update with `npm install -g @mutmutco/cli@<released>`",
37222
+ fix: "check actual maintenance state with `mmi-hub status`; converge with `mmi-hub update`",
36757
37223
  verbose: evidence
36758
37224
  };
36759
37225
  }
@@ -36763,7 +37229,7 @@ function checkCliVersion(input, releasedNote) {
36763
37229
  ok: false,
36764
37230
  label: "mmi-cli",
36765
37231
  detail: `${report.currentVersion} \u2192 ${report.releasedVersion}`,
36766
- fix: report.fix,
37232
+ fix: "run `mmi-hub update` to converge the CLI and every present host surface",
36767
37233
  verbose: evidence
36768
37234
  };
36769
37235
  }
@@ -36776,8 +37242,8 @@ function checkFleetDrift(probe) {
36776
37242
  id,
36777
37243
  ok: true,
36778
37244
  label,
36779
- detail: "no updater journal \u2014 no reconciler scheduled on this machine",
36780
- verbose: [`journal: ${probe.journalPath} (absent)`, "schedule one with `mmi-updater install` (@mutmutco/updater)"]
37245
+ detail: "no MMI Hub maintenance journal on this machine",
37246
+ verbose: [`journal: ${probe.journalPath} (absent)`, "bootstrap with `npm install -g @mutmutco/hub` then `mmi-hub install`"]
36781
37247
  };
36782
37248
  }
36783
37249
  if (probe.unreadable) {
@@ -36788,14 +37254,14 @@ function checkFleetDrift(probe) {
36788
37254
  warn: true,
36789
37255
  label,
36790
37256
  detail: `journal could not be read \u2014 ${probe.unreadable}`,
36791
- fix: `read ${probe.journalPath} directly, or re-register the reconciler with \`mmi-updater install\``,
37257
+ fix: `read ${probe.journalPath} directly, or repair maintenance with \`mmi-hub install\``,
36792
37258
  verbose: [`journal: ${probe.journalPath}`, `read failed: ${probe.unreadable}`]
36793
37259
  };
36794
37260
  }
36795
37261
  const evidence = [
36796
37262
  `journal: ${probe.journalPath}${probe.lastEventAt ? ` (last event ${probe.lastEventAt})` : ""}${probe.truncated ? ` \u2014 newest ${JOURNAL_TAIL_BYTES / 1024} KiB read` : ""}`,
36797
37263
  `expected: ${probe.expected ?? "(no gated release recorded yet)"}`,
36798
- ...probe.surfaces.map((s) => `${s.surface}: installed ${s.installed ?? "unknown"} \u2014 ${s.verdict}${s.detail ? ` (${s.detail})` : ""}${s.compat ? ` [compat ${s.compat}]` : ""}`),
37264
+ ...probe.surfaces.map((s) => `${s.surface}: last recorded ${s.installed ?? "unknown"} \u2014 ${s.verdict}${s.detail ? ` (${s.detail})` : ""}${s.compat ? ` [compat ${s.compat}]` : ""}`),
36799
37265
  ...probe.absent?.length ? [`absent since their last arm, skipped by the reconciler: ${probe.absent.join(", ")}`] : []
36800
37266
  ];
36801
37267
  if (!probe.expected) {
@@ -36840,7 +37306,7 @@ function checkFleetDrift(probe) {
36840
37306
  warn: true,
36841
37307
  label,
36842
37308
  detail: `${probe.expected} \u2014 ${probe.surfaces.length - unverified.length} converged, ${unverifiedNote}: no installed version recorded`,
36843
- fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-updater reconcile\` retries now; doctor never installs`,
37309
+ fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-hub update\` retries now; doctor never installs`,
36844
37310
  verbose: evidence
36845
37311
  };
36846
37312
  }
@@ -36851,7 +37317,7 @@ function checkFleetDrift(probe) {
36851
37317
  label,
36852
37318
  ...unverified.length ? { verified: false } : {},
36853
37319
  detail: `${drifted.length} of ${probe.surfaces.length} surface(s) behind ${probe.expected}: ${drifted.map((s) => `${s.surface} ${s.installed}`).join(", ")}${unverifiedNote ? `; ${unverifiedNote}` : ""}`,
36854
- fix: `the scheduled updater converges these on its next tick \u2014 \`mmi-updater reconcile\` runs one now; doctor never installs${unverified.length ? `. The unverified surface(s) recorded no version \u2014 read their reason in ${probe.journalPath}` : ""}`,
37320
+ fix: `hourly Hub maintenance converges these on its next tick \u2014 \`mmi-hub update\` runs one now; doctor never installs${unverified.length ? `. The unverified surface(s) recorded no version \u2014 read their reason in ${probe.journalPath}` : ""}`,
36855
37321
  verbose: evidence
36856
37322
  };
36857
37323
  }
@@ -37144,7 +37610,7 @@ function gcReapable(plan) {
37144
37610
  async function runDoctorClean(opts, io, deps) {
37145
37611
  const full = !opts.fast && !opts.banner && !opts.preflight;
37146
37612
  const applyEnv = full;
37147
- const applyRepo = full && opts.repoWrites !== false;
37613
+ const applyRepo = full && opts.repoWrites === true;
37148
37614
  const lane = {
37149
37615
  banner: Boolean(opts.banner),
37150
37616
  fast: Boolean(opts.fast),
@@ -37259,7 +37725,7 @@ async function runDoctorClean(opts, io, deps) {
37259
37725
  ...cli,
37260
37726
  ok: false,
37261
37727
  detail: cli.ok ? `missing commands: ${missing.join(", ")}` : cli.detail,
37262
- fix: `run \`${cliUpdateCommand(released)}\` \u2014 the CLI lacks ${missing.join(", ")} (or wait for the scheduled updater's next tick)`,
37728
+ fix: `run \`mmi-hub update\` \u2014 the CLI lacks ${missing.join(", ")} (or wait for hourly Hub maintenance)`,
37263
37729
  verbose: [...cli.verbose ?? [], `missing commands: ${missing.join(", ")}`]
37264
37730
  });
37265
37731
  }
@@ -37292,7 +37758,7 @@ async function runDoctorClean(opts, io, deps) {
37292
37758
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
37293
37759
  restartPending = true;
37294
37760
  } else {
37295
- emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor` (without --no-repo-writes) to write the org-managed .gitignore block", verbose: giEvidence });
37761
+ emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
37296
37762
  }
37297
37763
  }
37298
37764
  async function runPluginCacheRow() {
@@ -37715,7 +38181,7 @@ async function runDoctorClean(opts, io, deps) {
37715
38181
  // #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
37716
38182
  // the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
37717
38183
  detail: `${n} stale`,
37718
- fix: "run `mmi-cli doctor` (without --no-repo-writes) to reap merged branches, stale refs, and dead worktrees",
38184
+ fix: "run `mmi-cli doctor --apply` to reap merged branches, stale refs, and dead worktrees",
37719
38185
  verbose: gcEvidence
37720
38186
  });
37721
38187
  }
@@ -37733,7 +38199,7 @@ async function runDoctorClean(opts, io, deps) {
37733
38199
  restartPending = true;
37734
38200
  }
37735
38201
  } else {
37736
- emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor` (without --no-repo-writes)", verbose: scratchEvidence });
38202
+ emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
37737
38203
  }
37738
38204
  }
37739
38205
  const prefix = [
@@ -37835,17 +38301,17 @@ function parseOriginRepo(remoteUrl) {
37835
38301
  }
37836
38302
  function ghHostsConfigPath(env, platform2) {
37837
38303
  const sep3 = platform2 === "win32" ? "\\" : "/";
37838
- const join40 = (...parts) => parts.join(sep3);
38304
+ const join41 = (...parts) => parts.join(sep3);
37839
38305
  const explicit = env.GH_CONFIG_DIR?.trim();
37840
- if (explicit) return join40(explicit, "hosts.yml");
38306
+ if (explicit) return join41(explicit, "hosts.yml");
37841
38307
  if (platform2 === "win32") {
37842
38308
  const appData = (env.AppData ?? env.APPDATA)?.trim();
37843
- return appData ? join40(appData, "GitHub CLI", "hosts.yml") : void 0;
38309
+ return appData ? join41(appData, "GitHub CLI", "hosts.yml") : void 0;
37844
38310
  }
37845
38311
  const xdg = env.XDG_CONFIG_HOME?.trim();
37846
- if (xdg) return join40(xdg, "gh", "hosts.yml");
38312
+ if (xdg) return join41(xdg, "gh", "hosts.yml");
37847
38313
  const home = env.HOME?.trim();
37848
- return home ? join40(home, ".config", "gh", "hosts.yml") : void 0;
38314
+ return home ? join41(home, ".config", "gh", "hosts.yml") : void 0;
37849
38315
  }
37850
38316
  function parseGhHostsAccounts(yaml, host = "github.com") {
37851
38317
  let hostIndent = null;
@@ -37895,9 +38361,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
37895
38361
  }
37896
38362
 
37897
38363
  // src/doctor-io.ts
37898
- var import_node_fs46 = require("node:fs");
37899
- var import_node_os19 = require("node:os");
37900
- var import_node_path44 = require("node:path");
38364
+ var import_node_fs47 = require("node:fs");
38365
+ var import_node_os20 = require("node:os");
38366
+ var import_node_path45 = require("node:path");
37901
38367
  var import_node_child_process18 = require("node:child_process");
37902
38368
  var import_node_util8 = require("node:util");
37903
38369
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
@@ -37905,7 +38371,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
37905
38371
  function installedClaudePluginVersion() {
37906
38372
  try {
37907
38373
  const file = JSON.parse(
37908
- (0, import_node_fs46.readFileSync)((0, import_node_path44.join)((0, import_node_os19.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
38374
+ (0, import_node_fs47.readFileSync)((0, import_node_path45.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
37909
38375
  );
37910
38376
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
37911
38377
  if (versions.length === 0) return void 0;
@@ -37916,7 +38382,7 @@ function installedClaudePluginVersion() {
37916
38382
  }
37917
38383
  function manifestVersion(path2) {
37918
38384
  try {
37919
- const manifest = JSON.parse((0, import_node_fs46.readFileSync)(path2, "utf8"));
38385
+ const manifest = JSON.parse((0, import_node_fs47.readFileSync)(path2, "utf8"));
37920
38386
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
37921
38387
  } catch {
37922
38388
  return void 0;
@@ -37926,20 +38392,20 @@ function installedSurfacePluginVersion(surface) {
37926
38392
  const token = surfaceToken(surface);
37927
38393
  if (token === "kilo") {
37928
38394
  try {
37929
- const stamp = (0, import_node_fs46.readFileSync)((0, import_node_path44.join)((0, import_node_os19.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
38395
+ const stamp = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
37930
38396
  return stamp || void 0;
37931
38397
  } catch {
37932
38398
  return void 0;
37933
38399
  }
37934
38400
  }
37935
38401
  if (token === "cursor") {
37936
- return manifestVersion((0, import_node_path44.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
38402
+ return manifestVersion((0, import_node_path45.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
37937
38403
  }
37938
38404
  if (token === "jervcode") {
37939
38405
  return installedJervCodePackageVersion();
37940
38406
  }
37941
38407
  if (token === "kimi") {
37942
- return manifestVersion((0, import_node_path44.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
38408
+ return manifestVersion((0, import_node_path45.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
37943
38409
  }
37944
38410
  if (token === "claude") return installedClaudePluginVersion();
37945
38411
  if (token !== "codex") return void 0;
@@ -37977,13 +38443,13 @@ function worktreeRootSync() {
37977
38443
  }
37978
38444
  var gitignorePath = () => {
37979
38445
  const root = worktreeRootSync();
37980
- return root === null ? null : (0, import_node_path44.join)(root, ".gitignore");
38446
+ return root === null ? null : (0, import_node_path45.join)(root, ".gitignore");
37981
38447
  };
37982
38448
  function readGitignore() {
37983
38449
  const path2 = gitignorePath();
37984
38450
  if (path2 === null) return null;
37985
38451
  try {
37986
- return (0, import_node_fs46.readFileSync)(path2, "utf8");
38452
+ return (0, import_node_fs47.readFileSync)(path2, "utf8");
37987
38453
  } catch {
37988
38454
  return null;
37989
38455
  }
@@ -37992,7 +38458,7 @@ function writeGitignore(content) {
37992
38458
  const path2 = gitignorePath();
37993
38459
  if (path2 === null) return false;
37994
38460
  try {
37995
- (0, import_node_fs46.writeFileSync)(path2, content, "utf8");
38461
+ (0, import_node_fs47.writeFileSync)(path2, content, "utf8");
37996
38462
  return true;
37997
38463
  } catch {
37998
38464
  return false;
@@ -38011,7 +38477,7 @@ async function repoRoot() {
38011
38477
  }
38012
38478
  function hasRepoLocalWorktrees() {
38013
38479
  const root = worktreeRootSync();
38014
- return root !== null && ((0, import_node_fs46.existsSync)((0, import_node_path44.join)(root, ".worktrees")) || (0, import_node_fs46.existsSync)((0, import_node_path44.join)(root, ".claude", "worktrees")));
38480
+ return root !== null && ((0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, ".worktrees")) || (0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, ".claude", "worktrees")));
38015
38481
  }
38016
38482
 
38017
38483
  // src/cross-repo-filing-issue.ts
@@ -38117,8 +38583,8 @@ ${r.stderr ?? ""}`).catch(() => "");
38117
38583
  function ghMultiAccountCaveat(announcedLogin) {
38118
38584
  try {
38119
38585
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
38120
- if (!hostsPath || !(0, import_node_fs47.existsSync)(hostsPath)) return void 0;
38121
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs47.readFileSync)(hostsPath, "utf8")));
38586
+ if (!hostsPath || !(0, import_node_fs48.existsSync)(hostsPath)) return void 0;
38587
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs48.readFileSync)(hostsPath, "utf8")));
38122
38588
  } catch {
38123
38589
  return void 0;
38124
38590
  }
@@ -38126,12 +38592,12 @@ function ghMultiAccountCaveat(announcedLogin) {
38126
38592
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
38127
38593
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
38128
38594
  function envHealLockPath(home) {
38129
- return (0, import_node_path45.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38595
+ return (0, import_node_path46.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38130
38596
  }
38131
38597
  async function withEnvHealLock(what, run) {
38132
38598
  try {
38133
38599
  return await withFileLock(
38134
- envHealLockPath((0, import_node_os20.homedir)()),
38600
+ envHealLockPath((0, import_node_os21.homedir)()),
38135
38601
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
38136
38602
  run
38137
38603
  );
@@ -38195,9 +38661,8 @@ function mmiDoctorDeps(opts = {}) {
38195
38661
  pluginTrustState: () => codexHookTrustState(),
38196
38662
  releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
38197
38663
  releasedVersionNote: throttled ? throttled.note : void 0,
38198
- // #4954: the fleet reconciler's journal, read. `@mutmutco/updater` owns every npm install now, so
38199
- // doctor's own CLI self-update (#3272) is gone and this read is what took its place — the same
38200
- // journal `mmi-updater stamp` reports from, parsed here into one row.
38664
+ // #4954/#5023: the Hub maintenance journal is read-only last-run evidence. `@mutmutco/hub`
38665
+ // owns every version install; `mmi-hub status` separately probes actual installed state.
38201
38666
  fleetDrift: () => readFleetDrift(process.env),
38202
38667
  // #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
38203
38668
  // `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
@@ -38226,7 +38691,7 @@ function mmiDoctorDeps(opts = {}) {
38226
38691
  const configRoot = surfaceConfigRoot(surface);
38227
38692
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38228
38693
  const plan = buildPluginCachePlan(
38229
- (0, import_node_os20.homedir)(),
38694
+ (0, import_node_os21.homedir)(),
38230
38695
  running,
38231
38696
  pluginCacheFsDeps(configRoot, () => 0),
38232
38697
  { configRoot, includeStaging: surface !== "codex" }
@@ -38250,14 +38715,14 @@ function mmiDoctorDeps(opts = {}) {
38250
38715
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38251
38716
  const installed = installedActivePluginVersion(surface);
38252
38717
  const plan = buildPluginCachePlan(
38253
- (0, import_node_os20.homedir)(),
38718
+ (0, import_node_os21.homedir)(),
38254
38719
  running,
38255
38720
  pluginCacheFsDeps(configRoot, () => 0),
38256
38721
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
38257
38722
  );
38258
38723
  const result = applyPluginCachePlan(
38259
38724
  plan,
38260
- (p) => (0, import_node_fs47.rmSync)(p, { recursive: true }),
38725
+ (p) => (0, import_node_fs48.rmSync)(p, { recursive: true }),
38261
38726
  stagingApplyFsGuard(configRoot)
38262
38727
  );
38263
38728
  return {
@@ -38295,7 +38760,7 @@ function mmiDoctorDeps(opts = {}) {
38295
38760
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38296
38761
  // get a permanent — demanding an artifact it never asked for.
38297
38762
  docsIndexState: (root) => {
38298
- if (!(0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return void 0;
38763
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return void 0;
38299
38764
  const real = createDocsIndexDeps(root);
38300
38765
  let docs2;
38301
38766
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38304,7 +38769,7 @@ function mmiDoctorDeps(opts = {}) {
38304
38769
  },
38305
38770
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38306
38771
  healDocsIndex: (root) => {
38307
- if (!(0, import_node_fs47.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38772
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path46.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38308
38773
  const real = createDocsIndexDeps(root);
38309
38774
  let docs2;
38310
38775
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38670,19 +39135,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
38670
39135
  });
38671
39136
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
38672
39137
  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) => {
38673
- const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
38674
- const current = (0, import_node_fs47.existsSync)(path2) ? (0, import_node_fs47.readFileSync)(path2, "utf8") : null;
39138
+ const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
39139
+ const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
38675
39140
  const plan = planManagedGitignore(current);
38676
39141
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
38677
39142
  if (opts.json) {
38678
- if (opts.write && plan.changed) (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
39143
+ if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
38679
39144
  console.log(JSON.stringify(plan, null, 2));
38680
39145
  if (!opts.write && plan.changed) process.exitCode = 1;
38681
39146
  return;
38682
39147
  }
38683
39148
  if (opts.write) {
38684
39149
  if (plan.changed) {
38685
- (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
39150
+ (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
38686
39151
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
38687
39152
  } else {
38688
39153
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -38841,8 +39306,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
38841
39306
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
38842
39307
  let root;
38843
39308
  if (o.root !== void 0) {
38844
- root = (0, import_node_path45.resolve)(o.root);
38845
- if (!(0, import_node_fs47.existsSync)(root) || !(0, import_node_fs47.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
39309
+ root = (0, import_node_path46.resolve)(o.root);
39310
+ if (!(0, import_node_fs48.existsSync)(root) || !(0, import_node_fs48.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
38846
39311
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
38847
39312
  if (isPathUnderDirectory2(gcRepoRoot, root)) {
38848
39313
  return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
@@ -38941,7 +39406,7 @@ async function currentWorktreeRemovalContext(command, force) {
38941
39406
  };
38942
39407
  }
38943
39408
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
38944
- if (!(0, import_node_fs47.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
39409
+ if (!(0, import_node_fs48.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
38945
39410
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
38946
39411
  const registered = parseWorktreePorcelainEntries(porcelain);
38947
39412
  if (!registered.length) {
@@ -38971,26 +39436,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
38971
39436
  function acquireWorktreeSetupLock(worktreeRoot) {
38972
39437
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
38973
39438
  const take = () => {
38974
- const fd = (0, import_node_fs47.openSync)(lockPath, "wx");
39439
+ const fd = (0, import_node_fs48.openSync)(lockPath, "wx");
38975
39440
  try {
38976
- (0, import_node_fs47.writeSync)(fd, String(Date.now()));
39441
+ (0, import_node_fs48.writeSync)(fd, String(Date.now()));
38977
39442
  } finally {
38978
- (0, import_node_fs47.closeSync)(fd);
39443
+ (0, import_node_fs48.closeSync)(fd);
38979
39444
  }
38980
39445
  return () => {
38981
39446
  try {
38982
- (0, import_node_fs47.rmSync)(lockPath, { force: true });
39447
+ (0, import_node_fs48.rmSync)(lockPath, { force: true });
38983
39448
  } catch {
38984
39449
  }
38985
39450
  };
38986
39451
  };
38987
39452
  try {
38988
- (0, import_node_fs47.mkdirSync)((0, import_node_path45.dirname)(lockPath), { recursive: true });
39453
+ (0, import_node_fs48.mkdirSync)((0, import_node_path46.dirname)(lockPath), { recursive: true });
38989
39454
  return take();
38990
39455
  } catch {
38991
39456
  try {
38992
- if (Date.now() - (0, import_node_fs47.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
38993
- (0, import_node_fs47.rmSync)(lockPath, { force: true });
39457
+ if (Date.now() - (0, import_node_fs48.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
39458
+ (0, import_node_fs48.rmSync)(lockPath, { force: true });
38994
39459
  return take();
38995
39460
  }
38996
39461
  } catch {
@@ -39070,8 +39535,10 @@ withExamples(mutating(
39070
39535
  }
39071
39536
  };
39072
39537
  if (preferRemote && await revParseRef(preferRemote)) base = preferRemote;
39538
+ const resolvedBaseOid = await revParseRef(base);
39539
+ if (!resolvedBaseOid) return fail(`worktree create: could not resolve creation base '${base}' to an immutable commit`);
39073
39540
  if (!o.json) {
39074
- const baseSha = (await revParseRef(base))?.slice(0, 12) ?? "unresolved";
39541
+ const baseSha = resolvedBaseOid.slice(0, 12);
39075
39542
  const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} ? local ref)` : "";
39076
39543
  console.error(` base ${base} ${baseSha}${localOnly}`);
39077
39544
  }
@@ -39090,23 +39557,45 @@ withExamples(mutating(
39090
39557
  const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
39091
39558
  if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
39092
39559
  const head = await revParseRef(`refs/heads/${branch}`);
39093
- const baseOid = await revParseRef(base);
39094
- if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
39560
+ const baseOid = resolvedBaseOid;
39561
+ if (!head) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
39095
39562
  const canFastForward = await execFileP2(
39096
39563
  "git",
39097
39564
  ["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
39098
39565
  { timeout: GIT_TIMEOUT_MS }
39099
39566
  // io-census-allow: an unprovable ancestor probe conservatively refuses the resume below rather than fast-forwarding onto unproven history
39100
39567
  ).then(() => true).catch(() => false);
39101
- if (!canFastForward) {
39102
- return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
39568
+ if (canFastForward) {
39569
+ await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
39570
+ resumed = true;
39571
+ } else {
39572
+ const owner2 = lookupWorktreeOwner(repoRoot2, wtPath);
39573
+ const delivered = await proveExactTreeDelivery({
39574
+ repoRoot: repoRoot2,
39575
+ branch,
39576
+ workerOid: head,
39577
+ creationBaseOid: owner2?.provenance?.creationBaseOid,
39578
+ recordedWorkerBranch: owner2?.provenance?.workerBranch,
39579
+ landedTips: [baseOid]
39580
+ });
39581
+ if (delivered.action !== "settle") {
39582
+ return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}; exact-tree delivery proof refused (${describeExactTreeRefusal(delivered)})`);
39583
+ }
39584
+ const repoint = await repointDeliveredWorktreeTransactionally({
39585
+ branch,
39586
+ expectedWorkerOid: head,
39587
+ landedTipOid: baseOid,
39588
+ git: async (args) => (await execFileP2("git", ["-C", wtPath, ...args], { timeout: GIT_TIMEOUT_MS })).stdout
39589
+ });
39590
+ if (repoint.action !== "repointed") {
39591
+ return fail(`worktree create: exact-tree delivery was proven by ${delivered.candidateOid}, but transactional resume refused (${repoint.reason}${repoint.detail ? `: ${repoint.detail}` : ""})`);
39592
+ }
39593
+ resumed = true;
39103
39594
  }
39104
- await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
39105
- resumed = true;
39106
39595
  }
39107
39596
  if (!resumed) {
39108
39597
  step = `git worktree add ${wtPath}`;
39109
- const wtPathPreExisted = (0, import_node_fs47.existsSync)(wtPath);
39598
+ const wtPathPreExisted = (0, import_node_fs48.existsSync)(wtPath);
39110
39599
  const partialRemove = worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
39111
39600
  await withWorktreeAddLock(repoRoot2, () => addWorktreeRobust(wtPath, branch, base, {
39112
39601
  // #4834: `-c core.longpaths=true` rides the add command itself — a Windows worktree path
@@ -39122,7 +39611,7 @@ withExamples(mutating(
39122
39611
  },
39123
39612
  deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
39124
39613
  cleanupPartial: async () => {
39125
- if (wtPathPreExisted || !(0, import_node_fs47.existsSync)(wtPath)) return;
39614
+ if (wtPathPreExisted || !(0, import_node_fs48.existsSync)(wtPath)) return;
39126
39615
  partialRemove.detachReparsePoints(wtPath);
39127
39616
  await execFileP2("git", ["worktree", "remove", "--force", wtPath], { timeout: GIT_TIMEOUT_MS }).catch(() => partialRemove.removeWorktreeDir(wtPath).then(() => void 0));
39128
39617
  await execFileP2("git", ["worktree", "prune"], { timeout: GIT_TIMEOUT_MS }).catch(() => {
@@ -39166,7 +39655,14 @@ withExamples(mutating(
39166
39655
  }
39167
39656
  const createActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
39168
39657
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
39169
- const owner = { path: wtPath, branch, createdAt, lastSeenAt: createdAt, actor: createActor };
39658
+ const owner = {
39659
+ path: wtPath,
39660
+ branch,
39661
+ provenance: { creationBaseOid: resolvedBaseOid, workerBranch: branch },
39662
+ createdAt,
39663
+ lastSeenAt: createdAt,
39664
+ actor: createActor
39665
+ };
39170
39666
  recordWorktreeOwner(repoRoot2, owner);
39171
39667
  appendWorktreeEvent(repoRoot2, { action: "created", command: "worktree create", target: wtPath, branch, actor: owner.actor });
39172
39668
  let lease;
@@ -39903,7 +40399,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
39903
40399
  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`);
39904
40400
  if (o.secretsFile) {
39905
40401
  try {
39906
- vars.push(`secrets=${(0, import_node_fs47.readFileSync)(o.secretsFile, "utf8")}`);
40402
+ vars.push(`secrets=${(0, import_node_fs48.readFileSync)(o.secretsFile, "utf8")}`);
39907
40403
  } catch (e) {
39908
40404
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
39909
40405
  }
@@ -40671,11 +41167,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
40671
41167
  }
40672
41168
  });
40673
41169
  async function listCiWorkflowPaths(cwd = process.cwd()) {
40674
- const wfDir = (0, import_node_path45.join)(cwd, ".github", "workflows");
40675
- if (!(0, import_node_fs47.existsSync)(wfDir)) return [];
40676
- return (0, import_node_fs47.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
41170
+ const wfDir = (0, import_node_path46.join)(cwd, ".github", "workflows");
41171
+ if (!(0, import_node_fs48.existsSync)(wfDir)) return [];
41172
+ return (0, import_node_fs48.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
40677
41173
  try {
40678
- return workflowReportsPrChecks((0, import_node_fs47.readFileSync)((0, import_node_path45.join)(wfDir, name), "utf8"));
41174
+ return workflowReportsPrChecks((0, import_node_fs48.readFileSync)((0, import_node_path46.join)(wfDir, name), "utf8"));
40679
41175
  } catch {
40680
41176
  return true;
40681
41177
  }
@@ -40727,16 +41223,16 @@ function ciAuditDeps() {
40727
41223
  // gate re-seed step is skipped gracefully rather than failing mid-run.
40728
41224
  readSeedFile: (path2) => {
40729
41225
  if (!root) return null;
40730
- const fullPath = (0, import_node_path45.join)(root, path2);
40731
- return (0, import_node_fs47.existsSync)(fullPath) ? (0, import_node_fs47.readFileSync)(fullPath, "utf8") : null;
41226
+ const fullPath = (0, import_node_path46.join)(root, path2);
41227
+ return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
40732
41228
  }
40733
41229
  };
40734
41230
  }
40735
41231
  function hubRoot() {
40736
- const fromPkg = (0, import_node_path45.join)(__dirname, "..", "..");
41232
+ const fromPkg = (0, import_node_path46.join)(__dirname, "..", "..");
40737
41233
  const marker = "skills/bootstrap/seeds/manifest.json";
40738
- if ((0, import_node_fs47.existsSync)((0, import_node_path45.join)(fromPkg, marker))) return fromPkg;
40739
- if ((0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), marker))) return process.cwd();
41234
+ if ((0, import_node_fs48.existsSync)((0, import_node_path46.join)(fromPkg, marker))) return fromPkg;
41235
+ if ((0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), marker))) return process.cwd();
40740
41236
  return null;
40741
41237
  }
40742
41238
  async function waitLoopCorePool(label) {
@@ -41067,7 +41563,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41067
41563
  }
41068
41564
  if (!repoForPostCleanup) throw e;
41069
41565
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
41070
- const commitMessage = bodyFile ? (0, import_node_fs47.readFileSync)(bodyFile, "utf8") : void 0;
41566
+ const commitMessage = bodyFile ? (0, import_node_fs48.readFileSync)(bodyFile, "utf8") : void 0;
41071
41567
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
41072
41568
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
41073
41569
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -41163,7 +41659,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41163
41659
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
41164
41660
  beforeWorktrees,
41165
41661
  startingPath,
41166
- pathExists: (p) => (0, import_node_fs47.existsSync)(p),
41662
+ pathExists: (p) => (0, import_node_fs48.existsSync)(p),
41167
41663
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
41168
41664
  teardownWorktreeStage,
41169
41665
  deferredStore,
@@ -41767,26 +42263,26 @@ access.command("audit").description("audit collaborator roles + train-branch pus
41767
42263
  targets = resolution.targets;
41768
42264
  }
41769
42265
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
41770
- const fileMatrix = (0, import_node_fs47.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs47.readFileSync)("access-matrix.json", "utf8")) : {};
42266
+ const fileMatrix = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
41771
42267
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
41772
42268
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
41773
- const fileContracts = (0, import_node_fs47.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs47.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
42269
+ const fileContracts = (0, import_node_fs48.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs48.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
41774
42270
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
41775
- const sanctioned = (0, import_node_fs47.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs47.readFileSync)("access-matrix.json", "utf8")) : {};
42271
+ const sanctioned = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
41776
42272
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
41777
42273
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
41778
42274
  if (!report.ok) process.exitCode = 1;
41779
42275
  });
41780
42276
  access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
41781
42277
  var isWin2 = process.platform === "win32";
41782
- program2.command("doctor").description("heal plugin wiring and clean up repo cruft \u2014 repairs run by default (#3975); CLI/plugin updates belong to `mmi-updater` and are only REPORTED here (#4954); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes (#4199, canon); heal env drift with a plain run or --no-repo-writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals the plugin install and cleans repo cruft (gitignore block, merged branches, dead\nworktrees, aged scratch) automatically (#3975). CLI and plugin UPDATES belong to the scheduled\n`mmi-updater` (#4954); doctor reports the lag and the fleet-drift row and never installs.\n--no-repo-writes keeps the working tree untouched for train preflights; --banner/--fast/--self are\nread-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
42278
+ program2.command("doctor").description("safely heal active plugin wiring and report hygiene; repository cleanup requires explicit --apply (#5023), while version convergence belongs to `mmi-hub update`").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the canonical MMI Agentic Onboarding URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "advanced explicit lane: also apply guarded repository cleanup (gitignore, docs index, board mechanics, merged branches, dead worktrees, aged scratch)").option("--no-repo-writes", "compatibility spelling for the safe default: env/plugin repairs only, never mutate the repository").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run may heal safe machine-global plugin wiring but never mutates the repository. Run\n`mmi-cli doctor --apply` only when you explicitly want the guarded full repository cleanup lane.\nCLI and host version convergence belongs to `mmi-hub update`; doctor reports lag and last-run evidence.\n--banner/--fast/--self/--preflight are read-only lanes.\n").action(async (opts) => {
41783
42279
  if (opts.guide) {
41784
- consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
42280
+ consoleIo.log(`MMI Agentic Onboarding: ${CANONICAL_ONBOARDING_URL}`);
41785
42281
  return;
41786
42282
  }
41787
42283
  process.exitCode = await runDoctorClean(
41788
42284
  {
41789
- repoWrites: opts.repoWrites,
42285
+ repoWrites: opts.apply === true && opts.repoWrites !== false,
41790
42286
  banner: opts.banner,
41791
42287
  preflight: opts.preflight,
41792
42288
  fast: opts.fast || opts.self,
@@ -41804,16 +42300,16 @@ function directoryBytes(path2) {
41804
42300
  let total = 0;
41805
42301
  let entries;
41806
42302
  try {
41807
- entries = (0, import_node_fs47.readdirSync)(path2, { withFileTypes: true });
42303
+ entries = (0, import_node_fs48.readdirSync)(path2, { withFileTypes: true });
41808
42304
  } catch {
41809
42305
  return 0;
41810
42306
  }
41811
42307
  for (const entry of entries) {
41812
- const child2 = (0, import_node_path45.join)(path2, entry.name);
42308
+ const child2 = (0, import_node_path46.join)(path2, entry.name);
41813
42309
  if (entry.isDirectory()) total += directoryBytes(child2);
41814
42310
  else {
41815
42311
  try {
41816
- total += (0, import_node_fs47.statSync)(child2).size;
42312
+ total += (0, import_node_fs48.statSync)(child2).size;
41817
42313
  } catch {
41818
42314
  }
41819
42315
  }
@@ -41821,25 +42317,25 @@ function directoryBytes(path2) {
41821
42317
  return total;
41822
42318
  }
41823
42319
  function listDirEntries(dir) {
41824
- return (0, import_node_fs47.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
42320
+ return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41825
42321
  }
41826
42322
  function readInstalledPluginRefs(configRoot) {
41827
42323
  const p = installedPluginsPathForConfig(configRoot);
41828
- if (!(0, import_node_fs47.existsSync)(p)) return [];
42324
+ if (!(0, import_node_fs48.existsSync)(p)) return [];
41829
42325
  try {
41830
- return installedPluginPaths((0, import_node_fs47.readFileSync)(p, "utf8"));
42326
+ return installedPluginPaths((0, import_node_fs48.readFileSync)(p, "utf8"));
41831
42327
  } catch {
41832
42328
  return null;
41833
42329
  }
41834
42330
  }
41835
42331
  function pluginCacheFsDeps(configRoot, dirBytes) {
41836
42332
  return {
41837
- exists: (p) => (0, import_node_fs47.existsSync)(p),
41838
- listVersionDirs: (root) => (0, import_node_fs47.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
42333
+ exists: (p) => (0, import_node_fs48.existsSync)(p),
42334
+ listVersionDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
41839
42335
  dirBytes,
41840
- listStagingDirs: (root) => (0, import_node_fs47.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
42336
+ listStagingDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41841
42337
  try {
41842
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path45.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs47.statSync)(p).mtimeMs) };
42338
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path46.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs48.statSync)(p).mtimeMs) };
41843
42339
  } catch {
41844
42340
  return { name: d.name, mtimeMs: Date.now() };
41845
42341
  }
@@ -41853,10 +42349,10 @@ function stagingApplyFsGuard(configRoot) {
41853
42349
  return {
41854
42350
  referencedPaths: () => readInstalledPluginRefs(configRoot),
41855
42351
  mtimeMs: (name) => {
41856
- const p = (0, import_node_path45.join)(stagingRoot, name);
41857
- if (!(0, import_node_fs47.existsSync)(p)) return null;
42352
+ const p = (0, import_node_path46.join)(stagingRoot, name);
42353
+ if (!(0, import_node_fs48.existsSync)(p)) return null;
41858
42354
  try {
41859
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs47.statSync)(q).mtimeMs);
42355
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs48.statSync)(q).mtimeMs);
41860
42356
  } catch {
41861
42357
  return null;
41862
42358
  }
@@ -41876,13 +42372,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
41876
42372
  return;
41877
42373
  }
41878
42374
  const plan = buildPluginCachePlan(
41879
- (0, import_node_os20.homedir)(),
42375
+ (0, import_node_os21.homedir)(),
41880
42376
  running,
41881
42377
  pluginCacheFsDeps(configRoot, directoryBytes),
41882
42378
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
41883
42379
  );
41884
42380
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
41885
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs47.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
42381
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs48.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
41886
42382
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
41887
42383
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
41888
42384
  else console.log(renderPluginCachePlan(plan, result));