@mutmutco/codex-plugin 4.5.208 → 4.5.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/dist/main.cjs CHANGED
@@ -1598,13 +1598,15 @@ __export(index_exports, {
1598
1598
  commandOwnLongFlags: () => commandOwnLongFlags,
1599
1599
  envHealLockPath: () => envHealLockPath,
1600
1600
  isOrgRegisteredRepo: () => isOrgRegisteredRepo,
1601
+ normalizeBoardClaimIssueArgv: () => normalizeBoardClaimIssueArgv,
1601
1602
  parseInvalidChoiceError: () => parseInvalidChoiceError,
1602
1603
  positionalTargetForm: () => positionalTargetForm,
1603
1604
  registryClientDeps: () => registryClientDeps,
1604
1605
  repoSlug: () => repoSlug,
1605
1606
  suggestCommandPath: () => suggestCommandPath,
1606
1607
  unknownCommandCandidates: () => unknownCommandCandidates,
1607
- unknownCommandTail: () => unknownCommandTail
1608
+ unknownCommandTail: () => unknownCommandTail,
1609
+ workboardClaimRecovery: () => workboardClaimRecovery
1608
1610
  });
1609
1611
  module.exports = __toCommonJS(index_exports);
1610
1612
 
@@ -6350,6 +6352,276 @@ function boardClaimStatusFlagRecovery(flag, canonicalPath, argv) {
6350
6352
  corrected: `mmi-cli ${kept.join(" ")}`
6351
6353
  };
6352
6354
  }
6355
+ var BOARD_CLAIM_OWN_FLAGS = ["--repo", "--for", "--force", "--check", "--allow-partial", "--json"];
6356
+ function splitInlineSelectors(argv, flags) {
6357
+ const out = [];
6358
+ for (const tok of argv) {
6359
+ const eq = tok.indexOf("=");
6360
+ if (tok.startsWith("--") && eq > 0 && flags.includes(tok.slice(0, eq))) {
6361
+ out.push(tok.slice(0, eq), tok.slice(eq + 1));
6362
+ } else {
6363
+ out.push(tok);
6364
+ }
6365
+ }
6366
+ return out;
6367
+ }
6368
+ var BOARD_MOVE_OWN_FLAGS = ["--repo", "--allow-partial", "--json"];
6369
+ var BOARD_SHOW_OWN_FLAGS = ["--json", "--repo", "--allow-partial", "--out"];
6370
+ var BOARD_READ_OWN_FLAGS = [
6371
+ "--json",
6372
+ "--repo",
6373
+ "--direct",
6374
+ "--snapshot-only",
6375
+ "--min-cursor",
6376
+ "--bundle-details",
6377
+ "--bodies",
6378
+ "--allow-partial",
6379
+ "--out"
6380
+ ];
6381
+ var BOARD_OTHER_OWN_FLAGS = {
6382
+ unclaim: ["--json", "--repo", "--to-status", "--force", "--holder-dead", "--allow-partial"],
6383
+ attach: ["--repo", "--json"],
6384
+ "set-priority": ["--json", "--repo"],
6385
+ "archive-done": ["--json", "--repo", "--apply"],
6386
+ doctor: ["--json", "--repo", "--fix", "--concurrency"],
6387
+ events: ["--since", "--wait", "--repo", "--json"]
6388
+ };
6389
+ var BOARD_TARGET_PLACEHOLDER = {
6390
+ unclaim: "<issue>",
6391
+ attach: "<issues>",
6392
+ "set-priority": "<issue> <priority>"
6393
+ };
6394
+ var WORKBOARD_VERB_ALIASES = {
6395
+ claim: "claim",
6396
+ move: "move",
6397
+ status: "move",
6398
+ "set-status": "move",
6399
+ show: "show",
6400
+ view: "show",
6401
+ get: "show",
6402
+ read: "read",
6403
+ list: "read",
6404
+ ls: "read",
6405
+ items: "read",
6406
+ unclaim: "unclaim",
6407
+ release: "unclaim",
6408
+ attach: "attach",
6409
+ add: "attach",
6410
+ "set-priority": "set-priority",
6411
+ priority: "set-priority",
6412
+ "archive-done": "archive-done",
6413
+ archive: "archive-done",
6414
+ doctor: "doctor",
6415
+ events: "events"
6416
+ };
6417
+ var WORKBOARD_VALUE_FLAGS = /* @__PURE__ */ new Set([
6418
+ "--status",
6419
+ "--to-status",
6420
+ "--issue",
6421
+ "--number",
6422
+ "--repo",
6423
+ "--for",
6424
+ "--out",
6425
+ "--min-cursor",
6426
+ "--since",
6427
+ "--wait",
6428
+ "--concurrency"
6429
+ ]);
6430
+ var ISSUE_TOKEN = /^(?:\d+|[\w.-]+\/[\w.-]+#\d+|#\d+)$/;
6431
+ function scanWorkboardArgs(rest) {
6432
+ const positionals = [];
6433
+ const values = /* @__PURE__ */ new Map();
6434
+ const flags = [];
6435
+ for (let i = 0; i < rest.length; i += 1) {
6436
+ const tok = rest[i];
6437
+ if (!tok.startsWith("-")) {
6438
+ positionals.push(tok);
6439
+ continue;
6440
+ }
6441
+ const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
6442
+ const name = eq > 0 ? tok.slice(0, eq) : tok;
6443
+ let value;
6444
+ if (eq > 0) value = tok.slice(eq + 1);
6445
+ else if (WORKBOARD_VALUE_FLAGS.has(name) && rest[i + 1] !== void 0 && !rest[i + 1].startsWith("-")) {
6446
+ value = rest[i + 1];
6447
+ i += 1;
6448
+ }
6449
+ if (value !== void 0) values.set(name, [...values.get(name) ?? [], value]);
6450
+ flags.push({ name, tokens: eq > 0 ? [tok] : value !== void 0 ? [name, value] : [name] });
6451
+ }
6452
+ return {
6453
+ positionals,
6454
+ values,
6455
+ flagArgs: (ownFlags) => flags.filter((f) => ownFlags.includes(f.name)).flatMap((f) => f.tokens)
6456
+ };
6457
+ }
6458
+ var quoteArg = (v) => /\s/.test(v) ? `"${v}"` : v;
6459
+ function workboardClaimRecovery(unknownToken, argv, paths, ownFlags = BOARD_CLAIM_OWN_FLAGS) {
6460
+ if (unknownToken !== "workboard") return void 0;
6461
+ const catalog = [...paths];
6462
+ const inCatalog = (sub2) => catalog.includes(`oracle board ${sub2}`) || catalog.includes(`board ${sub2}`);
6463
+ let tail = unknownCommandTail(argv, "workboard");
6464
+ let skip = 0;
6465
+ if ((tail[0] === "item" || tail[0] === "items") && !(tail.length === 1 && tail[0] === "items")) {
6466
+ tail = tail.slice(1);
6467
+ skip = 1;
6468
+ }
6469
+ const verb = tail[0];
6470
+ let sub = verb === void 0 ? "read" : WORKBOARD_VERB_ALIASES[verb] ?? (inCatalog(verb) ? verb : void 0);
6471
+ if (sub === void 0) return void 0;
6472
+ if (sub === "claim") {
6473
+ if (!inCatalog("claim")) return void 0;
6474
+ const canonicalPath2 = "oracle board claim";
6475
+ const positionals = tail.slice(1);
6476
+ let corrected2;
6477
+ if (positionals.length > 0) {
6478
+ corrected2 = [`mmi-cli ${canonicalPath2}`, ...positionals, ...survivingFlagArgs(argv, "", ownFlags)].join(" ");
6479
+ } else {
6480
+ const normalized = splitInlineSelectors(argv, ["--issue", "--number"]);
6481
+ const usedFlag = normalized.includes("--issue") ? "--issue" : normalized.includes("--number") ? "--number" : "--issue";
6482
+ corrected2 = formatPositionalTarget(canonicalPath2, "issues", { flag: usedFlag, argv: normalized, ownFlags });
6483
+ }
6484
+ return { canonicalPath: canonicalPath2, corrected: corrected2, hint: `did you mean \`${corrected2}\`?` };
6485
+ }
6486
+ const verbAt = argv.indexOf("workboard") + 1 + skip;
6487
+ const scan = scanWorkboardArgs(argv.slice(verb === void 0 ? verbAt : verbAt + 1));
6488
+ const issueValues = [...scan.values.get("--issue") ?? [], ...scan.values.get("--number") ?? []];
6489
+ if (sub === "read" && verb !== void 0 && (scan.positionals.length > 0 || issueValues.length > 0)) sub = "show";
6490
+ if (!inCatalog(sub)) return void 0;
6491
+ const canonicalPath = `oracle board ${sub}`;
6492
+ const head = `mmi-cli ${canonicalPath}`;
6493
+ let corrected;
6494
+ if (sub === "move") {
6495
+ const flagStatus = scan.values.get("--status")?.[0] ?? scan.values.get("--to-status")?.[0];
6496
+ let status = flagStatus;
6497
+ let issues = [];
6498
+ const pos = scan.positionals;
6499
+ if (status !== void 0) {
6500
+ issues = pos;
6501
+ } else if (pos.length >= 2) {
6502
+ if (ISSUE_TOKEN.test(pos[0]) && !ISSUE_TOKEN.test(pos[pos.length - 1])) {
6503
+ status = pos[pos.length - 1];
6504
+ issues = pos.slice(0, -1);
6505
+ } else {
6506
+ status = pos[0];
6507
+ issues = pos.slice(1);
6508
+ }
6509
+ } else if (pos.length === 1) {
6510
+ if (ISSUE_TOKEN.test(pos[0])) issues = pos;
6511
+ else status = pos[0];
6512
+ }
6513
+ if (issues.length === 0) issues = issueValues;
6514
+ corrected = [
6515
+ head,
6516
+ status !== void 0 ? quoteArg(status) : "<status>",
6517
+ ...issues.length > 0 ? issues : ["<issues>"],
6518
+ ...scan.flagArgs(BOARD_MOVE_OWN_FLAGS)
6519
+ ].join(" ");
6520
+ } else if (sub === "show") {
6521
+ if (scan.positionals.length > 0) {
6522
+ corrected = [head, ...scan.positionals, ...scan.flagArgs(BOARD_SHOW_OWN_FLAGS)].join(" ");
6523
+ } else {
6524
+ const normalized = splitInlineSelectors(argv, ["--issue", "--number"]);
6525
+ const usedFlag = normalized.includes("--issue") ? "--issue" : normalized.includes("--number") ? "--number" : "--issue";
6526
+ corrected = formatPositionalTarget(canonicalPath, "issue", { flag: usedFlag, argv: normalized, ownFlags: BOARD_SHOW_OWN_FLAGS });
6527
+ }
6528
+ } else if (sub === "read") {
6529
+ corrected = [head, ...survivingFlagArgs(argv, "", BOARD_READ_OWN_FLAGS)].join(" ");
6530
+ } else {
6531
+ const declared = BOARD_OTHER_OWN_FLAGS[sub] ?? ["--json", "--repo"];
6532
+ const targets = [...issueValues, ...scan.positionals];
6533
+ const placeholder = BOARD_TARGET_PLACEHOLDER[sub];
6534
+ corrected = [
6535
+ head,
6536
+ ...targets.length > 0 ? targets.map(quoteArg) : placeholder ? [placeholder] : [],
6537
+ ...scan.flagArgs(declared)
6538
+ ].join(" ");
6539
+ }
6540
+ return { canonicalPath, corrected, hint: `did you mean \`${corrected}\`?` };
6541
+ }
6542
+ var BOARD_CLAIM_VALUE_FLAGS = /* @__PURE__ */ new Set(["--repo", "--for", "--status"]);
6543
+ var BOARD_CLAIM_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["--json", "--force", "--check", "--allow-partial", "--help", "-h"]);
6544
+ function normalizeBoardClaimIssueArgv(argv) {
6545
+ let claimAt;
6546
+ if (argv[2] === "board" && argv[3] === "claim") claimAt = 3;
6547
+ else if (argv[2] === "oracle" && argv[3] === "board" && argv[4] === "claim") claimAt = 4;
6548
+ else return false;
6549
+ let issueAt = -1;
6550
+ let issueSpan = 0;
6551
+ let value;
6552
+ for (let i = claimAt + 1; i < argv.length; i += 1) {
6553
+ const tok = argv[i];
6554
+ if (tok === "--issue" || tok.startsWith("--issue=")) {
6555
+ if (issueAt >= 0) return false;
6556
+ issueAt = i;
6557
+ if (tok === "--issue") {
6558
+ const next = argv[i + 1];
6559
+ if (next === void 0 || next === "" || next.startsWith("-")) return false;
6560
+ value = next;
6561
+ issueSpan = 2;
6562
+ i += 1;
6563
+ } else {
6564
+ value = tok.slice("--issue=".length);
6565
+ if (value === "" || value.startsWith("-")) return false;
6566
+ issueSpan = 1;
6567
+ }
6568
+ continue;
6569
+ }
6570
+ if (tok.startsWith("-")) {
6571
+ const name = tok.includes("=") ? tok.slice(0, tok.indexOf("=")) : tok;
6572
+ if (BOARD_CLAIM_VALUE_FLAGS.has(name)) {
6573
+ if (!tok.includes("=") && argv[i + 1] !== void 0 && !argv[i + 1].startsWith("-")) i += 1;
6574
+ continue;
6575
+ }
6576
+ if (BOARD_CLAIM_BOOLEAN_FLAGS.has(tok)) continue;
6577
+ return false;
6578
+ }
6579
+ return false;
6580
+ }
6581
+ if (issueAt < 0 || value === void 0) return false;
6582
+ argv.splice(issueAt, issueSpan);
6583
+ argv.splice(claimAt + 1, 0, value);
6584
+ return true;
6585
+ }
6586
+ function normalizeWorkboardClaimArgv(argv) {
6587
+ const workboardAt = argv[2] === "oracle" ? 3 : 2;
6588
+ if (argv[workboardAt] !== "workboard" || argv[workboardAt + 1] !== "claim") return false;
6589
+ const tail = argv.slice(workboardAt + 2);
6590
+ let selectorAt = -1;
6591
+ let selectorSpan = 0;
6592
+ let selected;
6593
+ let targets = 0;
6594
+ for (let i = 0; i < tail.length; i += 1) {
6595
+ const tok = tail[i];
6596
+ const eq = tok.indexOf("=");
6597
+ const name = eq > 0 ? tok.slice(0, eq) : tok;
6598
+ if (name === "--issue" || name === "--number") {
6599
+ if (selectorAt >= 0) return false;
6600
+ selectorAt = i;
6601
+ selectorSpan = eq > 0 ? 1 : 2;
6602
+ selected = eq > 0 ? tok.slice(eq + 1) : tail[++i];
6603
+ if (!selected || selected.startsWith("-")) return false;
6604
+ } else if (BOARD_CLAIM_VALUE_FLAGS.has(name)) {
6605
+ const value = eq > 0 ? tok.slice(eq + 1) : tail[++i];
6606
+ if (!value || value.startsWith("-")) return false;
6607
+ } else if (BOARD_CLAIM_BOOLEAN_FLAGS.has(tok) || tok === "--dry-run" || tok === "--validate-only") {
6608
+ continue;
6609
+ } else if (tok.startsWith("-") || !tok) {
6610
+ return false;
6611
+ } else {
6612
+ targets += 1;
6613
+ }
6614
+ }
6615
+ if (selected !== void 0) {
6616
+ if (targets > 0) return false;
6617
+ tail.splice(selectorAt, selectorSpan);
6618
+ tail.unshift(selected);
6619
+ } else if (targets === 0) {
6620
+ return false;
6621
+ }
6622
+ argv.splice(2, argv.length - 2, "oracle", "board", "claim", ...tail);
6623
+ return true;
6624
+ }
6353
6625
  function classifyParseError(plain) {
6354
6626
  if (/unknown command/i.test(plain)) return "unknown-command";
6355
6627
  if (/unknown option|missing required argument|too many arguments|argument missing|is invalid\. Allowed choices are|cannot be used with/i.test(plain)) {
@@ -15065,6 +15337,12 @@ function renderClaimReleaseBody(release) {
15065
15337
 
15066
15338
  <!-- mmi-claim-release: ${JSON.stringify(release)} -->`;
15067
15339
  }
15340
+ function formatClaimMarker(actor, ts = (/* @__PURE__ */ new Date()).toISOString()) {
15341
+ const marker = { v: 1, session: actor.session, surface: actor.surface, host: actor.host, ts };
15342
+ return `Claimed by \`${describeClaimMarker(marker)}\` at ${marker.ts}.
15343
+
15344
+ <!-- mmi-claim: ${JSON.stringify(marker)} -->`;
15345
+ }
15068
15346
  function latestClaimMarker(comments) {
15069
15347
  for (let i = comments.length - 1; i >= 0; i -= 1) {
15070
15348
  const body = comments[i].body;
@@ -15389,6 +15667,7 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
15389
15667
  `repos/${repo}/branches/${encodeURIComponent(out.branch)}`
15390
15668
  );
15391
15669
  out.branchAt = head?.commit?.commit?.committer?.date ?? void 0;
15670
+ out.branchSha = head?.commit?.sha ?? void 0;
15392
15671
  } catch {
15393
15672
  }
15394
15673
  }
@@ -15455,13 +15734,27 @@ function isSeatClaimMarker(marker, current, context = {}) {
15455
15734
  const state = context.sessionState ?? probeLocalClaimSession(marker, context.now);
15456
15735
  return state === "dead";
15457
15736
  }
15737
+ function isSameBranchClaimResume(contest, repo, landing = {}) {
15738
+ const headBranch = landing.headBranch?.replace(/^refs\/heads\//, "").replace(/^[^/:]+:/, "").trim();
15739
+ const headSha = landing.headSha?.trim();
15740
+ if (!headBranch && !headSha) return false;
15741
+ if (!contest.marker || contest.unverifiable.length > 0 || contest.live.length === 0) return false;
15742
+ const branchLine = /^live branch ([^\s]+) on /;
15743
+ const fromContest = contest.branch?.replace(/ \(head of open PR .*\)$/, "").trim();
15744
+ const fromLive = contest.live.map((line) => branchLine.exec(line)?.[1]).find(Boolean);
15745
+ const liveBranch = fromContest || fromLive;
15746
+ const matches = Boolean(headBranch && liveBranch && liveBranch === headBranch) || Boolean(headSha && contest.branchSha && contest.branchSha === headSha);
15747
+ if (!matches) return false;
15748
+ const ownBranchLine = liveBranch ? `live branch ${liveBranch} ` : "live branch ";
15749
+ return contest.live.every((line) => /^claim marker from /.test(line) || line.startsWith(ownBranchLine) && line.endsWith(` on ${repo}`));
15750
+ }
15458
15751
  function laneResumeEvidence(evidence) {
15459
15752
  if (!evidence.marker || evidence.sessionState !== "dead") return void 0;
15460
15753
  if (!isHostSessionId(evidence.marker.session)) return void 0;
15461
15754
  const artifacts = [evidence.openPr, evidence.branch ? `live branch ${evidence.branch}` : void 0].filter(Boolean);
15462
15755
  return `prior claim by lane ${describeClaimMarker(evidence.marker)} (@${evidence.markerBy}, ${formatClaimAge(evidence.markerAgeMs)} old) is verifiably not running on this host \u2014 its local session transcript is gone or stale${artifacts.length ? `; resuming over ${artifacts.join("; ")}` : ""}`;
15463
15756
  }
15464
- async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin, appClient) {
15757
+ async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin, appClient, landing) {
15465
15758
  const livenessClient = await selectLivenessClient(client, appClient);
15466
15759
  let evidence = await gatherClaimLiveness(livenessClient, item.repository, item.number, openPullsFetcher(livenessClient));
15467
15760
  if (evidence.failed.some((channel) => channel !== "session")) {
@@ -15473,10 +15766,21 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
15473
15766
  markerBy: evidence.markerBy,
15474
15767
  sessionState: evidence.sessionState
15475
15768
  });
15476
- const resume = ownership !== "mine" && ownSeat ? laneResumeEvidence(evidence) : void 0;
15769
+ let resume = ownership !== "mine" && ownSeat ? laneResumeEvidence(evidence) : void 0;
15477
15770
  const live = ownSeat ? [] : liveEvidenceLines(evidence, item.repository);
15478
15771
  const unverifiable = ownSeat ? [] : evidence.failed;
15479
- const contested = !ownSeat && !resume && (live.length > 0 || unverifiable.length > 0);
15772
+ let contested = !ownSeat && !resume && (live.length > 0 || unverifiable.length > 0);
15773
+ if (contested && landing && isSameBranchClaimResume({
15774
+ contested: true,
15775
+ live,
15776
+ unverifiable,
15777
+ marker: evidence.marker,
15778
+ branch: evidence.branch,
15779
+ branchSha: evidence.branchSha
15780
+ }, item.repository, landing)) {
15781
+ contested = false;
15782
+ resume = `same-branch resume over live branch ${evidence.branch ?? landing.headBranch}`;
15783
+ }
15480
15784
  const carry = staleWorkLines(evidence);
15481
15785
  const staleStamp = !contested && !resume && !ownSeat && evidence.marker ? `stale claim stamp ${describeClaimMarker(evidence.marker)} (idle ${formatClaimAge(claimStampStaleness(evidence.marker, { activityAt: [evidence.lastCommentAt] }).idleMs)})${carry.length ? `; continue its ${carry.join(" and ")}` : ""}` : void 0;
15482
15786
  return {
@@ -15486,7 +15790,9 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
15486
15790
  ownership,
15487
15791
  marker: evidence.marker,
15488
15792
  resume,
15489
- ...staleStamp ? { staleStamp } : {}
15793
+ ...staleStamp ? { staleStamp } : {},
15794
+ ...evidence.branch ? { branch: evidence.branch } : {},
15795
+ ...evidence.branchSha ? { branchSha: evidence.branchSha } : {}
15490
15796
  };
15491
15797
  }
15492
15798
  function laneContestParts(contest) {
@@ -15637,6 +15943,7 @@ async function prepareClaimContext(options, selectors, deps, collected, snapshot
15637
15943
  cfg,
15638
15944
  client,
15639
15945
  items: collected.items,
15946
+ selectedRefs: new Set(selectors.map((selector) => `${selector.repo}#${selector.number}`.toLowerCase())),
15640
15947
  writable: writableOrUnknown(writable),
15641
15948
  report,
15642
15949
  session: deps.session ?? describeSessionIdentity(),
@@ -15748,7 +16055,9 @@ async function claimOneBoardItem(ctx, selector, options) {
15748
16055
  return;
15749
16056
  }
15750
16057
  if (!options.force) {
15751
- const refusal = laneContestMessage(item.ref, contest, "claim");
16058
+ const candidateRefs = [...report.primary.claimable, ...report.secondary.claimable].filter((candidate) => candidate.contentType === "Issue" && candidate.state === "OPEN" && !candidate.claim?.currentlyClaimed && !ctx.selectedRefs.has(candidate.ref.toLowerCase())).map((candidate) => candidate.ref);
16059
+ const alternatives = candidateRefs.length ? `Board snapshot candidates (not guaranteed free): ${candidateRefs.join(", ")}` : "Board snapshot candidates: none in this read";
16060
+ const refusal = `${laneContestMessage(item.ref, contest, "claim")}. ${alternatives}; refresh mmi-cli oracle board read before claiming.`;
15752
16061
  throw new Error(options.bulk ? refusal : withGatedWriteChainHint(refusal));
15753
16062
  }
15754
16063
  previousHolder = displaced();
@@ -19344,10 +19653,10 @@ var rollout_plan_default = {
19344
19653
  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)."
19345
19654
  },
19346
19655
  baseline: {
19347
- version: "4.5.208",
19348
- tag: "v4.5.208",
19349
- commit: "86627b6751ed",
19350
- npm: "@mutmutco/cli@4.5.208"
19656
+ version: "4.5.210",
19657
+ tag: "v4.5.210",
19658
+ commit: "a9e5d838bc19",
19659
+ npm: "@mutmutco/cli@4.5.210"
19351
19660
  },
19352
19661
  exitCriterion: "fleet-n-of-n",
19353
19662
  hubOnlyShortcut: "forbidden",
@@ -19364,14 +19673,14 @@ var rollout_plan_default = {
19364
19673
  repo: "mutmutco/mmi-hub",
19365
19674
  role: "canary",
19366
19675
  schedule: "train",
19367
- v3Target: "v4.5.208"
19676
+ v3Target: "v4.5.210"
19368
19677
  }
19369
19678
  ],
19370
19679
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
19371
19680
  rollback: {
19372
19681
  independent: true,
19373
- mechanism: "npm dist-tag latest -> 4.5.208 and redeploy the Hub Lambda from tag v4.5.208 (86627b6751ed); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
19374
- v3Target: "v4.5.208 (@mutmutco/cli@4.5.208, tag commit 86627b6751ed \u2014 last known-good release carrying the repo-index v4-only contract)"
19682
+ mechanism: "npm dist-tag latest -> 4.5.210 and redeploy the Hub Lambda from tag v4.5.210 (a9e5d838bc19); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
19683
+ v3Target: "v4.5.210 (@mutmutco/cli@4.5.210, tag commit a9e5d838bc19 \u2014 last known-good release carrying the repo-index v4-only contract)"
19375
19684
  }
19376
19685
  },
19377
19686
  {
@@ -23875,46 +24184,45 @@ var import_node_fs30 = require("node:fs");
23875
24184
  var import_promises4 = require("node:fs/promises");
23876
24185
  var import_node_path28 = require("node:path");
23877
24186
 
23878
- // src/actions-billing-preflight.ts
23879
- var CANARY_WORKFLOW = "actions-job-start-canary.yml";
23880
- var CANARY_REPO = "mutmutco/MMI-Hub";
23881
- var ACTIONS_BILLING_BLOCK_RE = /job was not started because recent account payments have failed|account payments have failed|spending limit needs to be increased|Billing & plans/i;
23882
- var CANARY_POLLS = 20;
23883
- var CANARY_POLL_MS = 1e3;
23884
- function isActionsBillingBlockText(text) {
23885
- return ACTIONS_BILLING_BLOCK_RE.test(text);
23886
- }
23887
- function interpretActionsJobStart(input) {
23888
- const text = input.text ?? "";
23889
- if (isActionsBillingBlockText(text)) return "billing-blocked";
23890
- const jobs = input.jobs ?? [];
23891
- if (jobs.some((job) => isActionsBillingBlockText(JSON.stringify(job)))) return "billing-blocked";
23892
- if (jobs.some((job) => Array.isArray(job.steps) && job.steps.length > 0 || Boolean(job.startedAt))) {
23893
- return "started";
23894
- }
23895
- if (jobs.length > 0 && jobs.every((job) => {
23896
- const finished = job.conclusion === "failure" || job.status === "completed";
23897
- const empty = !Array.isArray(job.steps) || job.steps.length === 0;
23898
- return finished && empty && !job.startedAt;
23899
- })) {
23900
- return "never-started";
24187
+ // ../shared/runner-fallback.ts
24188
+ var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
24189
+ function classifyRunnerCost(selector) {
24190
+ const raw = selector.replace(/\s+#.*$/gm, "").trim();
24191
+ if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
24192
+ const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
24193
+ if (json) {
24194
+ try {
24195
+ const labels2 = JSON.parse(json[1]);
24196
+ return Array.isArray(labels2) && labels2.length > 0 && labels2.every((l) => typeof l === "string" && /^[\w-]+$/.test(l)) && labels2.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
24197
+ } catch {
24198
+ return "unknown";
24199
+ }
23901
24200
  }
23902
- return "pending";
23903
- }
23904
- var CANARY_ACCESS_BLOCK_RE = /HTTP 404|HTTP 403|Not Found|Resource not accessible|must have admin rights|not authorized|no identity|401/i;
23905
- function isCanaryAccessBlockText(text) {
23906
- return !isActionsBillingBlockText(text) && CANARY_ACCESS_BLOCK_RE.test(text);
24201
+ if (raw.includes("${{")) return "unknown";
24202
+ const labels = /^\[[^\[\]]+\]$/.test(raw) ? raw.slice(1, -1).split(",").map((s) => s.trim().replace(/^(['"])(.*)\1$/, "$2")) : /^(?:-\s*[^\n]+\s*)+$/.test(raw) ? raw.split("\n").map((s) => s.trim().replace(/^-\s*/, "").replace(/^(['"])(.*)\1$/, "$2")) : [raw.replace(/^(['"])(.*)\1$/, "$2")];
24203
+ return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
23907
24204
  }
23908
- function actionsCanaryUndispatchedRefusal(detail) {
23909
- return new Error(
23910
- `release refused: the hosted-job canary (#5604) was not dispatched, so a hosted job start is UNPROVEN \u2014 this is NOT a billing verdict. ${detail} The Hub dispatches ${CANARY_WORKFLOW} with its App token for a train-authorized caller (#6319): refresh the Hub session (\`gh auth login\`) and retry, and confirm \`mmi-cli oracle org access role <owner/repo>\` reports train authority. Nothing was tagged or pushed \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`
23911
- );
23912
- }
23913
- function actionsBillingRefusal(detail) {
23914
- return new Error(
23915
- `release refused: GitHub Actions cannot start a hosted job (billing/spending). ${detail} Fix Billing & plans / the org spending limit, then rerun. Do not mint a new tag. If a tag is already on origin, use \`mmi-cli devops release --retry-publish <run-id> --apply\` on that exact run \u2014 never recut.`
23916
- );
24205
+ function classifyJobRunnerCost(selector, jobBody, standardHostedLabel) {
24206
+ const direct = classifyRunnerCost(selector);
24207
+ const matrix = /^\$\{\{\s*matrix\.([\w-]+)\s*\}\}$/.exec(selector.trim());
24208
+ if (!matrix || direct === "hosted") return direct;
24209
+ const strategy = /^ strategy:\s*\n((?: {6,}[^\n]*\n?|\s*\n)*)/m.exec(jobBody)?.[1];
24210
+ const matrixBody = strategy && /^ matrix:[^\S\n]*\n((?: {8,}[^\n]*\n?|\s*\n)*)/m.exec(strategy)?.[1];
24211
+ const include = matrixBody && /^ include:[^\S\n]*\n((?: {10,}[^\n]*\n?|\s*\n)*)$/.exec(matrixBody)?.[1];
24212
+ if (!include) return "unknown";
24213
+ const rows = include.split(/^ {10}- /m).slice(1);
24214
+ if (!rows.length) return "unknown";
24215
+ const costs = rows.map((row) => {
24216
+ const match = new RegExp(`^(?: {12})?${matrix[1]}:\\s*(.+)$`, "m").exec(row);
24217
+ if (standardHostedLabel && match?.[1].trim() === standardHostedLabel) return "standard-hosted";
24218
+ return match ? classifyRunnerCost(match[1]) : "unknown";
24219
+ });
24220
+ if (costs.includes("hosted")) return "hosted";
24221
+ if (costs.includes("unknown")) return "unknown";
24222
+ return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
23917
24223
  }
24224
+
24225
+ // src/actions-billing-preflight.ts
23918
24226
  function parseJson(raw, label) {
23919
24227
  try {
23920
24228
  return JSON.parse(raw);
@@ -23922,153 +24230,30 @@ function parseJson(raw, label) {
23922
24230
  throw new Error(`${label} was not valid JSON`);
23923
24231
  }
23924
24232
  }
23925
- async function scanRepoForBillingBlock(deps, repo) {
23926
- let raw;
23927
- try {
23928
- raw = await deps.run("gh", [
23929
- "run",
23930
- "list",
23931
- "--repo",
23932
- repo,
23933
- "--limit",
23934
- "8",
23935
- "--json",
23936
- "databaseId,conclusion,status,displayTitle,url"
23937
- ]);
23938
- } catch {
23939
- return void 0;
23940
- }
23941
- const rows = parseJson(raw, `gh run list --repo ${repo}`);
23942
- if (!Array.isArray(rows)) return void 0;
23943
- for (const row of rows) {
23944
- const blob = `${row.displayTitle ?? ""} ${row.conclusion ?? ""} ${row.url ?? ""}`;
23945
- if (isActionsBillingBlockText(blob)) {
23946
- return `${repo} run ${row.databaseId ?? row.url ?? "(unknown)"} already names a billing/spending block`;
23947
- }
23948
- if (row.conclusion !== "failure" && row.status !== "completed") continue;
23949
- if (typeof row.databaseId !== "number") continue;
23950
- try {
23951
- const view = await deps.run("gh", [
23952
- "run",
23953
- "view",
23954
- String(row.databaseId),
23955
- "--repo",
23956
- repo,
23957
- "--json",
23958
- "jobs,conclusion,displayTitle,url"
23959
- ]);
23960
- const parsed = parseJson(view, `gh run view ${row.databaseId}`);
23961
- const verdict = interpretActionsJobStart({
23962
- jobs: parsed.jobs,
23963
- text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
23964
- });
23965
- if (verdict === "billing-blocked" || verdict === "never-started") {
23966
- return `${repo} run ${row.databaseId} (${parsed.url ?? row.url ?? "no url"}): hosted job never started` + (verdict === "billing-blocked" ? " (billing/spending refusal)" : " (empty steps)");
23967
- }
23968
- } catch {
23969
- }
23970
- }
23971
- return void 0;
23972
- }
23973
- async function correlateCanaryRun(deps, nonce) {
23974
- const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
23975
- let lastError = "no matching canary run";
23976
- for (let attempt = 0; attempt < 8; attempt++) {
23977
- if (attempt > 0) await sleep2(CANARY_POLL_MS);
23978
- let raw;
23979
- try {
23980
- raw = await deps.run("gh", [
23981
- "run",
23982
- "list",
23983
- "--repo",
23984
- CANARY_REPO,
23985
- "--workflow",
23986
- CANARY_WORKFLOW,
23987
- "--limit",
23988
- "10",
23989
- "--json",
23990
- "databaseId,displayTitle,status,createdAt"
23991
- ]);
23992
- } catch (e) {
23993
- lastError = e instanceof Error ? e.message : String(e);
23994
- continue;
23995
- }
23996
- const rows = parseJson(
23997
- raw,
23998
- "canary gh run list"
23999
- );
24000
- const match = rows.find((row) => (row.displayTitle ?? "").includes(nonce) && typeof row.databaseId === "number");
24001
- if (match?.databaseId) return match.databaseId;
24002
- }
24003
- throw new Error(
24004
- `could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
24005
- );
24006
- }
24007
- async function dispatchCanary(deps, targetRepo2, nonce) {
24008
- const attempts = [];
24009
- if (deps.dispatchActionsCanary) {
24010
- try {
24011
- await deps.dispatchActionsCanary({ repo: targetRepo2, nonce });
24012
- return;
24013
- } catch (e) {
24014
- attempts.push(`Hub App dispatch: ${e instanceof Error ? e.message : String(e)}`);
24015
- }
24016
- }
24017
- try {
24018
- await deps.run("gh", [
24019
- "workflow",
24020
- "run",
24021
- CANARY_WORKFLOW,
24022
- "--repo",
24023
- CANARY_REPO,
24024
- "-f",
24025
- `nonce=${nonce}`
24026
- ]);
24027
- return;
24028
- } catch (e) {
24029
- attempts.push(`gh workflow run --repo ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}`);
24030
- }
24031
- const detail = `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO} \u2014 ${attempts.join(" | ")}.`;
24032
- if (attempts.some((a) => isActionsBillingBlockText(a))) {
24033
- throw actionsBillingRefusal(`${detail} A hosted publish job may not start (the v1.54.2 class).`);
24034
- }
24035
- throw actionsCanaryUndispatchedRefusal(
24036
- attempts.every((a) => isCanaryAccessBlockText(a)) ? `${detail} Every door answered with an access refusal \u2014 the caller may read MMI-Hub but not start its workflows.` : detail
24037
- );
24038
- }
24039
- var HOSTED_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/;
24040
- var FALLBACK_EXPR_GLOBAL_RE = /\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)'\s*\|\|\s*fromJSON\('\[[^\n']+\]'\)\s*\}\}/g;
24041
- var AUTOMATIC_TRIGGERS = /* @__PURE__ */ new Set(["push", "pull_request", "pull_request_target", "release", "schedule", "workflow_run", "merge_group", "create", "deployment", "repository_dispatch", "check_run", "check_suite", "issues", "issue_comment"]);
24042
- function workflowTriggers(yaml) {
24043
- const lines2 = yaml.split(/\r?\n/);
24044
- const i = lines2.findIndex((l) => /^(?:on|"on"|'on'):(?:\s|$)/.test(l));
24045
- if (i < 0) return [];
24046
- const inline = lines2[i].replace(/^[^:]+:\s*/, "").replace(/\s+#.*$/, "").trim();
24047
- if (inline) return inline.replace(/^\[|\]$/g, "").split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
24048
- const out = [];
24049
- for (const line of lines2.slice(i + 1)) {
24050
- if (/^\S/.test(line)) break;
24051
- const m = /^ {2}([A-Za-z_]+):/.exec(line) ?? /^ {2}- ([A-Za-z_]+)\s*$/.exec(line);
24052
- if (m) out.push(m[1]);
24053
- }
24054
- return out;
24055
- }
24056
- function workflowNeedsHostedRunner(yaml) {
24057
- const body = yaml.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join("\n").replace(FALLBACK_EXPR_GLOBAL_RE, "");
24058
- return HOSTED_LABEL_RE.test(body) && workflowTriggers(yaml).some((t) => AUTOMATIC_TRIGGERS.has(t));
24233
+ function workflowNeedsHostedRunner(yaml, localWorkflows = /* @__PURE__ */ new Set()) {
24234
+ const jobs = parseWorkflowJobs(yaml.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join("\n"));
24235
+ return !jobs.length || jobs.some((job) => {
24236
+ const callee = /^ uses:\s*\.\/(.+)$/m.exec(job.body)?.[1];
24237
+ if (callee && localWorkflows.has(callee)) return false;
24238
+ const section = /^ runs-on:([^\n]*(?:\n {6,}[^\n]*)*)/m.exec(job.body)?.[1] ?? "";
24239
+ const selector = section.split("\n").map((line) => line.trim()).join("\n");
24240
+ return classifyJobRunnerCost(selector, job.body) !== "self-hosted";
24241
+ });
24059
24242
  }
24060
24243
  async function hostedWorkflowsOf(deps, repo) {
24061
24244
  const list = parseJson(
24062
24245
  await deps.run("gh", ["api", `repos/${repo}/contents/.github/workflows`]),
24063
24246
  `${repo} workflow listing`
24064
24247
  );
24065
- const hosted = [];
24248
+ if (!Array.isArray(list) || !list.some((f) => f.type === "file" && /\.ya?ml$/.test(f.path ?? ""))) throw new Error("no workflow inventory");
24249
+ const workflows = [];
24066
24250
  for (const f of list) {
24067
- if (f.type !== "file" || !f.path || !/\.ya?ml$/.test(f.path) || f.name === CANARY_WORKFLOW) continue;
24251
+ if (f.type !== "file" || !f.path || !/\.ya?ml$/.test(f.path)) continue;
24068
24252
  const raw = parseJson(await deps.run("gh", ["api", `repos/${repo}/contents/${f.path}`]), `${repo} ${f.path}`);
24069
- if (workflowNeedsHostedRunner(Buffer.from(raw.content ?? "", "base64").toString("utf8"))) hosted.push(f.path);
24253
+ workflows.push({ path: f.path, body: Buffer.from(raw.content ?? "", "base64").toString("utf8") });
24070
24254
  }
24071
- return hosted;
24255
+ const local = new Set(workflows.map((file) => file.path));
24256
+ return workflows.filter((file) => workflowNeedsHostedRunner(file.body, local)).map((file) => file.path);
24072
24257
  }
24073
24258
  async function assertActionsJobsCanStart(deps, targetRepo2) {
24074
24259
  let hosted;
@@ -24078,50 +24263,11 @@ async function assertActionsJobsCanStart(deps, targetRepo2) {
24078
24263
  hosted = void 0;
24079
24264
  }
24080
24265
  if (hosted && hosted.length === 0) {
24081
- process.stderr.write(`actions preflight: ${targetRepo2} has no automatically triggered GitHub-hosted job; hosted canary skipped (#7721)
24266
+ process.stderr.write(`actions preflight: ${targetRepo2} source selectors are self-hosted only; no canary dispatch; access/isolation/execution unproven (#8911)
24082
24267
  `);
24083
24268
  return;
24084
24269
  }
24085
- const scanned = await scanRepoForBillingBlock(deps, targetRepo2);
24086
- if (scanned) throw actionsBillingRefusal(scanned);
24087
- if (targetRepo2.toLowerCase() !== CANARY_REPO.toLowerCase()) {
24088
- const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
24089
- if (hubScan) throw actionsBillingRefusal(hubScan);
24090
- }
24091
- const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
24092
- await dispatchCanary(deps, targetRepo2, nonce);
24093
- const runId = await correlateCanaryRun(deps, nonce);
24094
- const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
24095
- let last = "pending";
24096
- let url = `https://github.com/${CANARY_REPO}/actions/runs/${runId}`;
24097
- for (let attempt = 0; attempt < CANARY_POLLS; attempt++) {
24098
- if (attempt > 0) await sleep2(CANARY_POLL_MS);
24099
- const view = await deps.run("gh", [
24100
- "run",
24101
- "view",
24102
- String(runId),
24103
- "--repo",
24104
- CANARY_REPO,
24105
- "--json",
24106
- "jobs,status,conclusion,url,displayTitle"
24107
- ]);
24108
- const parsed = parseJson(view, `canary gh run view ${runId}`);
24109
- if (parsed.url) url = parsed.url;
24110
- last = interpretActionsJobStart({
24111
- jobs: parsed.jobs,
24112
- text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
24113
- });
24114
- if (last === "started") {
24115
- await deps.run("gh", ["run", "cancel", String(runId), "--repo", CANARY_REPO]).catch(() => "");
24116
- return;
24117
- }
24118
- if (last === "billing-blocked" || last === "never-started") {
24119
- throw actionsBillingRefusal(`${url}: hosted canary ${last} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`);
24120
- }
24121
- }
24122
- throw actionsBillingRefusal(
24123
- `${url}: hosted canary stayed ${last} after ${CANARY_POLLS} polls \u2014 cannot prove a hosted job can start \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#actions-job-start-canary`
24124
- );
24270
+ throw new Error(`release refused (#8911): ${hosted ? `paid or unresolved runner path(s): ${hosted.join(", ")}` : "runner inventory unreadable; self-hosted-only selection unproven"}. Remove paid selectors/fallbacks and verify existing self-hosted access and isolation before tagging.`);
24125
24271
  }
24126
24272
 
24127
24273
  // src/train-apply-recovery.ts
@@ -30224,33 +30370,6 @@ function registryRepoKey(repo) {
30224
30370
  return repo.toLowerCase();
30225
30371
  }
30226
30372
 
30227
- // ../shared/runner-fallback.ts
30228
- var RUNNER_FALLBACK_EXPR_RE = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'((?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*))'\s*\|\|\s*fromJSON\('(\[[^\n']+\])'\)\s*\}\}$/;
30229
- var FLEET_ONLY_RUNNER_JOBS = {
30230
- "backup-health.yml:judge": "SSH backup inventory; hosted source-IP access unverified",
30231
- "browser-pass.yml:browser-pass": "owner-bounded two-lane browser capacity",
30232
- "app-publish.yml:publish": "SSH tenant publish; hosted source-IP access unverified",
30233
- "get-publish.yml:publish": "SSH installer publish; hosted source-IP access unverified",
30234
- "get-publish-desktop.yml:publish": "SSH installer publish; hosted source-IP access unverified",
30235
- "hetzner-knownhosts-pin.yml:pin": "SSH host-key probe; hosted source-IP access unverified",
30236
- "module-rc-publish.yml:publish": "SSH live-container snapshot and tenant persistent state",
30237
- "tenant-control.yml:control": "SSH tenant control; hosted source-IP access unverified",
30238
- "tenant-deploy.yml:deploy": "SSH tenant deploy; hosted source-IP access unverified",
30239
- "tenant-reconcile.yml:reconcile": "SSH tenant reconciliation; hosted source-IP access unverified"
30240
- };
30241
- function parseRunnerFallbackExpression(runsOn) {
30242
- const match = RUNNER_FALLBACK_EXPR_RE.exec(runsOn.trim());
30243
- if (!match) return void 0;
30244
- try {
30245
- const parsed = JSON.parse(match[2]);
30246
- if (!Array.isArray(parsed) || !parsed.length || !parsed.every((item) => typeof item === "string")) return void 0;
30247
- if (!parsed.some((item) => item.toLowerCase() === "self-hosted")) return void 0;
30248
- return { hosted: match[1], labels: parsed };
30249
- } catch {
30250
- return void 0;
30251
- }
30252
- }
30253
-
30254
30373
  // src/ci-audit.ts
30255
30374
  var HUB_REPO4 = "mutmutco/MMI-Hub";
30256
30375
  var HUB_GATE_CONTEXTS = ["cli", "infra", "docs"];
@@ -30310,25 +30429,28 @@ var NPM_PUBLISH_SAFE_FLOOR_SHA = "b5632c8b940d8025fdb5630e576502e1397c4552";
30310
30429
  var SHARED_ACTION_PINS = [
30311
30430
  { path: "npm-publish", floor: NPM_PUBLISH_SAFE_FLOOR_SHA }
30312
30431
  ];
30432
+ var STANDARD_HOSTED_ALLOWLIST = [
30433
+ ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"]
30434
+ ];
30313
30435
  function auditRunnerFallbacks(repo, files) {
30314
30436
  const checks = [];
30315
30437
  for (const { path: path2, body } of files) {
30316
30438
  const jobs = parseWorkflowJobs(body.split(/\r?\n/).filter((line) => !/^\s*#/.test(line)).join("\n"));
30317
30439
  for (const job of jobs) {
30318
- const runsOn = /^ runs-on:\s*(.+)$/m.exec(job.body)?.[1] ?? "";
30319
30440
  const runnerSection = /^ runs-on:([^\n]*(?:\n {6,}[^\n]*)*)/m.exec(job.body)?.[1] ?? "";
30320
- const fallback = parseRunnerFallbackExpression(runsOn);
30321
- const selfHosted = !!fallback || /self-hosted/i.test(runnerSection) || /matrix\./.test(runsOn) && /self-hosted/i.test(job.body);
30322
- if (!selfHosted) continue;
30323
- const key = `${path2.split("/").pop()}:${job.id}`;
30324
- const marked = /#\s*mmi-runner:\s*fleet-only\s*[—–-]+\s*(\S.*?)\s*$/.exec(runsOn)?.[1];
30325
- const exemption = (repo === HUB_REPO4 ? FLEET_ONLY_RUNNER_JOBS[key] : void 0) ?? marked;
30326
- const safe = !!fallback;
30441
+ const callee = /^ uses:\s*(.+)$/m.exec(job.body)?.[1];
30442
+ if (callee?.startsWith("./") && files.some((file) => file.path === callee.slice(2))) continue;
30443
+ if (callee && /^mutmutco\/MMI-Hub\/\.github\/workflows\/[\w.-]+\.ya?ml@[\w./-]+$/.test(callee)) continue;
30444
+ const standardHostedLabel = STANDARD_HOSTED_ALLOWLIST.find(
30445
+ ([allowedRepo, allowedPath, allowedJob]) => repo === allowedRepo && path2 === allowedPath && job.id === allowedJob
30446
+ )?.[3];
30447
+ const cost = classifyJobRunnerCost(runnerSection.split("\n").map((line) => line.trim()).join("\n"), job.body, standardHostedLabel);
30448
+ const ok = !callee && (cost === "self-hosted" || cost === "standard-hosted");
30327
30449
  checks.push({
30328
- ok: !!exemption || safe,
30329
- label: `runner has hosted backup (#8671): ${path2}:${job.id}`,
30330
- detail: exemption ? `fleet-only: ${exemption}` : safe ? void 0 : "self-hosted runner does not use the vars.MMI_RUNNER_FALLBACK backup expression",
30331
- remediation: exemption || safe ? void 0 : `Use runs-on: \${{ vars.MMI_RUNNER_FALLBACK == 'hosted' && 'ubuntu-latest' || fromJSON('["self-hosted","linux","x64","mmi-live"]') }} (keeping the job's original self-hosted tuple). See gh-runner-runbook.md.`
30450
+ ok,
30451
+ label: cost === "standard-hosted" ? `runner uses exact standard-hosted exception (#8912): ${path2}:${job.id}` : `runner is self-hosted only (#8911): ${path2}:${job.id}`,
30452
+ detail: callee ? `unknown callee runner policy: ${callee}` : cost === "hosted" ? "paid GitHub-hosted runner selection is forbidden" : cost === "unknown" ? "unknown runner selector; not proven self-hosted" : cost === "standard-hosted" ? "exact Hub launcher windows-latest matrix leg permitted within included Enterprise minutes; runner access and isolation not established" : "self-hosted selection only; runner access and isolation not established",
30453
+ remediation: ok ? void 0 : `Use only a verified accessible self-hosted tuple, with no hosted fallback; resolve unknown selectors/callees. Preserve checks and artifacts. See gh-runner-runbook.md (${repo}).`
30332
30454
  });
30333
30455
  }
30334
30456
  }
@@ -30553,7 +30675,7 @@ function rulesetCoversReleaseBranches(payload, track) {
30553
30675
  function releaseBranchRefsCovered(conditions, track) {
30554
30676
  const include = conditions?.ref_name?.include;
30555
30677
  if (!Array.isArray(include)) return [];
30556
- const releaseRefs = track === "full" ? ["refs/heads/rc", "refs/heads/main"] : ["refs/heads/main"];
30678
+ const releaseRefs = branchesForTrack(track).filter((branch) => branch !== "development").map((branch) => `refs/heads/${branch}`);
30557
30679
  return releaseRefs.filter((ref) => include.some((entry) => typeof entry === "string" && refPatternCovers(entry, ref)));
30558
30680
  }
30559
30681
  function unreachableReleaseContexts(input) {
@@ -30782,6 +30904,18 @@ async function auditRepoCi(repo, callerDeps) {
30782
30904
  const explicitNoCi = registryCi === "none" || registryRequiredChecks === null || Array.isArray(registryRequiredChecks) && registryRequiredChecks.length === 0;
30783
30905
  const deployableGated = repoClass === "deployable" && !explicitNoCi;
30784
30906
  const info = await restJson2(deps, `repos/${repo}`, {});
30907
+ if (info.archived === true) {
30908
+ return {
30909
+ repo,
30910
+ class: repoClass,
30911
+ mergePolicy: "wait-for-checks",
30912
+ ok: false,
30913
+ checks: [],
30914
+ explicitNoCi,
30915
+ excludedReason: "GitHub archived repository (read-only)"
30916
+ };
30917
+ }
30918
+ let releaseReadFailure;
30785
30919
  const baseBranch = repoClass === "content" ? "main" : "development";
30786
30920
  checks.push({
30787
30921
  ok: info.allow_auto_merge === true,
@@ -30984,14 +31118,29 @@ async function auditRepoCi(repo, callerDeps) {
30984
31118
  const releaseBranches = releaseBranchRefsCovered(productRuleset.conditions, resolveReleaseTrack(meta, void 0, repo));
30985
31119
  if (releaseBranches.length > 0) {
30986
31120
  const emittedByBranch = {};
31121
+ const absent = [];
31122
+ const unreadable = [];
30987
31123
  for (const ref of releaseBranches) {
30988
- emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, ref.replace("refs/heads/", ""));
31124
+ const branch = ref.replace("refs/heads/", "");
31125
+ const present = await branchPresence(deps, repo, branch);
31126
+ if (present === false) absent.push(branch);
31127
+ else if (present === void 0) unreadable.push(branch);
31128
+ else {
31129
+ emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, branch);
31130
+ if (emittedByBranch[ref] === void 0) unreadable.push(branch);
31131
+ }
30989
31132
  }
31133
+ if (unreadable.length) releaseReadFailure = `release branch reachability unreadable: [${unreadable.join(", ")}] \u2014 no emitter conclusion`;
31134
+ const presenceNote = [
31135
+ absent.length ? `absent: [${absent.join(", ")}] \u2014 no emitter requirement audited` : "",
31136
+ unreadable.length ? `unreadable: [${unreadable.join(", ")}] \u2014 cannot audit emitters` : ""
31137
+ ].filter(Boolean).join("; ");
30990
31138
  const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore: TRAIN_PR_ONLY_CONTEXTS });
30991
31139
  checks.push({
30992
31140
  ok: unreachable.length === 0,
30993
31141
  label: RELEASE_BRANCH_REACHABLE_LABEL,
30994
- detail: unreachable.length === 0 ? void 0 : unreachable.map((u) => `${u.branch.replace("refs/heads/", "")} requires [${u.unreachable.join(", ")}] but its own workflows emit none of them`).join("; ") + " \u2014 a base-" + unreachable.map((u) => u.branch.replace("refs/heads/", "")).join("/") + " PR (e.g. a hotfix) can never satisfy the gate and stays permanently BLOCKED",
31142
+ severity: unreadable.length ? "warn" : absent.length ? "info" : void 0,
31143
+ detail: unreachable.length === 0 ? presenceNote || void 0 : unreachable.map((u) => `${u.branch.replace("refs/heads/", "")} requires [${u.unreachable.join(", ")}] but its own workflows emit none of them`).join("; ") + " \u2014 a base-" + unreachable.map((u) => u.branch.replace("refs/heads/", "")).join("/") + " PR (e.g. a hotfix) can never satisfy the gate and stays permanently BLOCKED" + (presenceNote ? `; ${presenceNote}` : ""),
30995
31144
  remediation: unreachable.length === 0 ? void 0 : `Remove the unreachable context(s) from those branches in the ${PRODUCT_RULESET_NAME} ruleset conditions and ${PRODUCT_RULESET_REF}, or add the emitting workflow to the branch`
30996
31145
  });
30997
31146
  }
@@ -31047,7 +31196,7 @@ async function auditRepoCi(repo, callerDeps) {
31047
31196
  const body = await fetchFileContent(deps, repo, baseBranch, path2);
31048
31197
  if (body == null) continue;
31049
31198
  if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
31050
- if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*==\s*(['"])pull_request\1\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
31199
+ if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*(?:==\s*(['"])pull_request\1|!=\s*(['"])merge_group\2)\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
31051
31200
  if (NO_CANCEL_WAIVER.test(body)) waived.push(path2);
31052
31201
  else missing.push(path2);
31053
31202
  }
@@ -31106,34 +31255,45 @@ async function auditRepoCi(repo, callerDeps) {
31106
31255
  if (failure) {
31107
31256
  return { repo, class: repoClass, mergePolicy: policy, ok: false, checks: [], explicitNoCi, unauditedReason: failure };
31108
31257
  }
31109
- const ok = checks.every((c) => c.ok);
31110
- return { repo, class: repoClass, mergePolicy: policy, ok, checks, explicitNoCi };
31258
+ const ok = !releaseReadFailure && checks.every((c) => c.ok);
31259
+ return {
31260
+ repo,
31261
+ class: repoClass,
31262
+ mergePolicy: policy,
31263
+ ok,
31264
+ checks,
31265
+ explicitNoCi,
31266
+ ...releaseReadFailure ? { unauditedReason: releaseReadFailure } : {}
31267
+ };
31111
31268
  }
31112
31269
  var REGISTRY_UNREADABLE_DETAIL = `the registry roster read returned nothing \u2014 the fleet was NOT audited. 0 repos read from the registry; the number left unaudited is unknown because the roster is what names them. This is "cannot audit the fleet", never "the fleet is green". Under GitHub Actions the CLI needs a CI Hub session (MMI_HUB_TOKEN, minted from the job's OIDC claim via POST /auth/session-ci) \u2014 an App installation token has no /user identity and can never pass /auth/session (#3215).`;
31113
31270
  async function auditOrgCi(deps, repoFilter) {
31114
31271
  if (repoFilter) {
31115
31272
  const report = await auditRepoCi(repoFilter, deps);
31116
- return { ok: report.ok, scope: "single-repo", repos: [report] };
31273
+ return { ok: report.ok, scope: "single-repo", repos: [report], excludedCount: report.excludedReason ? 1 : 0 };
31117
31274
  }
31118
31275
  const projects = await deps.listProjects();
31119
31276
  if (!projects || projects.length === 0) {
31120
- return { ok: false, scope: "registry-unreadable", repos: [], scopeDetail: REGISTRY_UNREADABLE_DETAIL };
31277
+ return { ok: false, scope: "registry-unreadable", repos: [], excludedCount: 0, scopeDetail: REGISTRY_UNREADABLE_DETAIL };
31121
31278
  }
31122
31279
  const targets = collectRegistryRepos(projects);
31123
31280
  const repos = [];
31124
31281
  for (const repo of targets) {
31125
31282
  repos.push(await auditRepoCi(repo, deps));
31126
31283
  }
31127
- return { ok: repos.every((r) => r.ok), scope: "fleet", repos };
31284
+ const audited = repos.filter((r) => !r.excludedReason);
31285
+ return { ok: audited.length > 0 && audited.every((r) => r.ok), scope: "fleet", repos, excludedCount: repos.length - audited.length };
31128
31286
  }
31129
31287
  function renderCiAuditScopeLine(report) {
31130
31288
  if (report.scope === "registry-unreadable") {
31131
31289
  return `scope: REGISTRY UNREADABLE \u2014 repos audited: 0 \u2014 ${report.scopeDetail ?? REGISTRY_UNREADABLE_DETAIL}`;
31132
31290
  }
31291
+ const excluded = report.repos.filter((r) => r.excludedReason).length;
31292
+ const count = `repos audited: ${report.repos.length - excluded}${excluded ? ` \u2014 repos excluded: ${excluded}` : ""}`;
31133
31293
  if (report.scope === "single-repo") {
31134
- return `scope: single-repo (explicit --repo) \u2014 repos audited: ${report.repos.length} \u2014 this is NOT a fleet verdict`;
31294
+ return `scope: single-repo (explicit --repo) \u2014 ${count} \u2014 this is NOT a fleet verdict`;
31135
31295
  }
31136
- return `scope: fleet (registry roster) \u2014 repos audited: ${report.repos.length}`;
31296
+ return `scope: fleet (registry roster) \u2014 ${count}`;
31137
31297
  }
31138
31298
  function renderCiAuditMarkdown(report) {
31139
31299
  const lines2 = [
@@ -31141,16 +31301,16 @@ function renderCiAuditMarkdown(report) {
31141
31301
  "",
31142
31302
  renderCiAuditScopeLine(report),
31143
31303
  "",
31144
- `Fleet: ${report.ok ? "OK" : "GAPS"} (${report.repos.filter((r) => r.ok).length}/${report.repos.length} repos ready)`,
31304
+ `Fleet: ${report.ok ? "OK" : "GAPS"} (${report.repos.filter((r) => r.ok && !r.excludedReason).length}/${report.repos.filter((r) => !r.excludedReason).length} repos ready)`,
31145
31305
  "",
31146
31306
  "| Repo | Class | Policy | OK | Top gap |",
31147
31307
  "|------|-------|--------|----|---------|"
31148
31308
  ];
31149
31309
  for (const r of report.repos) {
31150
- const gap = r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
31151
- lines2.push(`| ${r.repo} | ${r.class} | ${r.mergePolicy} | ${r.ok ? "yes" : "no"} | ${gap} |`);
31310
+ const gap = r.excludedReason ? `EXCLUDED \u2014 ${r.excludedReason}` : r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
31311
+ lines2.push(`| ${r.repo} | ${r.class} | ${r.excludedReason ? "\u2014" : r.mergePolicy} | ${r.excludedReason ? "excluded" : r.ok ? "yes" : "no"} | ${gap} |`);
31152
31312
  }
31153
- const advisories = report.repos.flatMap((r) => r.checks.filter((c) => c.severity && c.label === PENDING_LANDING_LABEL).map((c) => `- ${c.severity === "warn" ? "WARN" : "INFO"} ${r.repo}: ${c.label} \u2014 ${c.detail ?? ""}`));
31313
+ const advisories = report.repos.flatMap((r) => r.checks.filter((c) => c.severity).map((c) => `- ${c.severity === "warn" ? "WARN" : "INFO"} ${r.repo}: ${c.label} \u2014 ${c.detail ?? ""}`));
31154
31314
  if (advisories.length) lines2.push("", ...advisories);
31155
31315
  return lines2.join("\n");
31156
31316
  }
@@ -31160,12 +31320,15 @@ function renderCiAuditText(report) {
31160
31320
  renderCiAuditScopeLine(report)
31161
31321
  ];
31162
31322
  for (const r of report.repos) {
31163
- const verdict = r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
31323
+ const verdict = r.excludedReason ? "EXCLUDED" : r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
31164
31324
  lines2.push(`
31165
- ${r.repo} (${r.class}, policy=${r.mergePolicy}) ${verdict}`);
31325
+ ${r.repo} (${r.class}, policy=${r.excludedReason ? "not audited" : r.mergePolicy}) ${verdict}`);
31326
+ if (r.excludedReason) {
31327
+ lines2.push(` EXCLUDED \u2014 ${r.excludedReason}`);
31328
+ continue;
31329
+ }
31166
31330
  if (r.unauditedReason) {
31167
31331
  lines2.push(` UNAUDITED \u2014 ${r.unauditedReason}`);
31168
- continue;
31169
31332
  }
31170
31333
  for (const c of r.checks) {
31171
31334
  lines2.push(` ${!c.ok ? "FAIL" : c.severity === "warn" ? "WARN" : c.severity === "info" ? "INFO" : "OK"} ${c.label}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
@@ -31184,6 +31347,10 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
31184
31347
  errors.push(unauditedReconcileError(report.unauditedReason));
31185
31348
  return { repo, applied, skipped, errors };
31186
31349
  }
31350
+ if (report.excludedReason) {
31351
+ skipped.push(`excluded \u2014 ${report.excludedReason}; no reconcile change made`);
31352
+ return { repo, applied, skipped, errors };
31353
+ }
31187
31354
  const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch") || c.label.startsWith("has_wiki"));
31188
31355
  const needsPatch = mergeChecks.some((c) => !c.ok);
31189
31356
  if (!needsPatch) {
@@ -31487,6 +31654,9 @@ async function applyCiReconcileRepo(repo, deps, options = {}) {
31487
31654
  return { repo, applied: [], skipped: [], errors: ["product-ruleset requires registry META requiredChecks"] };
31488
31655
  }
31489
31656
  const report = await auditRepoCi(repo, deps);
31657
+ if (report.excludedReason) {
31658
+ return { repo, applied: [], skipped: [`excluded \u2014 ${report.excludedReason}; no reconcile change made`], errors: [] };
31659
+ }
31490
31660
  if (report.unauditedReason) {
31491
31661
  return { repo, applied: [], skipped: [], errors: [unauditedReconcileError(report.unauditedReason)] };
31492
31662
  }
@@ -45899,7 +46069,7 @@ async function checkDocsIndexAtHead(opts, deps) {
45899
46069
  return {
45900
46070
  ok: false,
45901
46071
  detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there (checked ${ref} over ${relDocs.length} indexed doc${relDocs.length === 1 ? "" : "s"})`,
45902
- fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`"
46072
+ fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`; freshness is checked at the pushed commit regardless of merge-queue state, so regenerate and commit docs/index.md BEFORE `pr create`, never defer it to after merge"
45903
46073
  };
45904
46074
  }
45905
46075
 
@@ -45989,6 +46159,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
45989
46159
  if (state?.toLowerCase() === "closed") continue;
45990
46160
  const contest = await checkContest(client, { repository: repo, number }, actor, viewerLogin, deps.appClient);
45991
46161
  if (!contest.contested) continue;
46162
+ if (isSameBranchClaimResume(contest, repo, { headBranch: deps.head, headSha: deps.headSha })) {
46163
+ await client.rest("POST", `repos/${repo}/issues/${number}/comments`, { body: { body: formatClaimMarker(actor) } }).catch(() => void 0);
46164
+ continue;
46165
+ }
45992
46166
  const ref = `${repo}#${number}`;
45993
46167
  const commentsPath = `repos/${repo}/issues/${number}/comments`;
45994
46168
  const now = deps.now ?? Date.now;
@@ -48511,9 +48685,14 @@ ${list}`);
48511
48685
  if (docsCheck && !docsCheck.ok) {
48512
48686
  return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
48513
48687
  }
48514
- const claimRefusal = await prCreateClaimRefusal(body, o.repo);
48515
- if (claimRefusal) return fail(claimRefusal);
48516
48688
  const createHead = o.head ?? await gitOut(["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "");
48689
+ const shaRef = o.head ? `refs/heads/${o.head.replace(/^[^/:]+:/, "")}` : "HEAD";
48690
+ const createHeadSha = await gitOut(["rev-parse", "--verify", "--quiet", shaRef]).catch(() => "");
48691
+ const claimRefusal = await prCreateClaimRefusal(body, o.repo, {
48692
+ head: createHead || void 0,
48693
+ headSha: createHeadSha || void 0
48694
+ });
48695
+ if (claimRefusal) return fail(claimRefusal);
48517
48696
  const mergedHeadWarning = await prCreateMergedHeadWarning({ head: createHead || void 0, repo: o.repo });
48518
48697
  if (mergedHeadWarning) process.stderr.write(`${mergedHeadWarning}
48519
48698
  `);
@@ -54660,9 +54839,13 @@ function parseBrokerAssignee(raw) {
54660
54839
  var SINGLE_SHOT_TIMEOUT_MS = 6e4;
54661
54840
  var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
54662
54841
  var PR_QUEUE_QUERY = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){mergeQueueEntry{state position enqueuedAt estimatedTimeToMerge headCommit{oid}}}}}";
54842
+ function missingBrokerPrRepoMessage(subcommand, number) {
54843
+ const example = subcommand === "list" ? "mmi-cli devops read pr list --repo <owner/repo> --state open --json number,title,headRefName" : subcommand === "diff" ? `mmi-cli devops read pr diff ${number ?? "<number>"} --repo <owner/repo> --name-only` : `mmi-cli devops read pr queue ${number ?? "<number>"} --repo <owner/repo> --json`;
54844
+ return `read pr ${subcommand}: required option '--repo <owner/repo>' not specified \u2014 broker PR ${subcommand} reads do not infer the repo from a worktree's git origin; pass --repo <owner/repo> explicitly (e.g. \`${example}\`)`;
54845
+ }
54663
54846
  function buildPrQueueSpec(number, o) {
54664
54847
  const repo = parseBrokerRepo(o.repo, "--repo");
54665
- if (!repo) throw new Error("read pr queue: --repo is required");
54848
+ if (!repo) throw new Error(missingBrokerPrRepoMessage("queue", number));
54666
54849
  const [owner, name] = repo.split("/");
54667
54850
  return { program: "gh", args: ["api", "graphql", "-f", `query=${PR_QUEUE_QUERY}`, "-f", `owner=${owner}`, "-f", `name=${name}`, "-F", `number=${parseBrokerId(number, "PR number")}`], timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
54668
54851
  }
@@ -54818,7 +55001,7 @@ async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(ch
54818
55001
  return 0;
54819
55002
  } catch (e) {
54820
55003
  const err = e;
54821
- if (typeof err.stdout === "string" && err.stdout) write(redactPrivateCredential(err.stdout, env));
55004
+ if (typeof err.stdout === "string" && err.stdout) process.stderr.write(redactPrivateCredential(err.stdout, env));
54822
55005
  const code = ghExitCodeOf(e);
54823
55006
  if (code !== null) {
54824
55007
  if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
@@ -54944,13 +55127,16 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
54944
55127
  await dispatchGh(spec, "read run watch");
54945
55128
  });
54946
55129
  const pr = read.command("pr").description("pull-request reads \u2014 view/list/diff ride gh; queue uses fixed GraphQL; checks is REST-backed (deterministic closed-custody adapter; native was EMPTY only on the historical #3035 fixture)");
54947
- pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--json [fields]", "native gh JSON fields", jsonOpt).addOption(new Option("--state <state>", "open|closed|merged|all").choices(["open", "closed", "merged", "all"])).option("--base <branch>", "base branch", branchOpt).option("--head <branch>", "head branch", branchOpt).option("--label <label>", "filter by label", labelOpt).option("--search <query>", "viewer-free search", (v) => parseBrokerPrSearch(v)).option("--assignee <login>", "explicit assignee (viewer refused)", assigneeOpt).option("--limit <n>", "maximum PRs (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
55130
+ pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--json [fields]", "native gh JSON fields", jsonOpt).addOption(new Option("--state <state>", "open|closed|merged|all").choices(["open", "closed", "merged", "all"])).option("--base <branch>", "base branch", branchOpt).option("--head <branch>", "head branch", branchOpt).option("--label <label>", "filter by label", labelOpt).option("--search <query>", "viewer-free search", (v) => parseBrokerPrSearch(v)).option("--assignee <login>", "explicit assignee (viewer refused)", assigneeOpt).option("--limit <n>", "maximum PRs (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
55131
+ if (!o.repo) return fail(missingBrokerPrRepoMessage("list"));
54948
55132
  await dispatchGh(buildPrListSpec(o), "read pr list");
54949
55133
  });
54950
- pr.command("diff").description("read a bounded PR diff on the App pool").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--name-only", "only changed file names").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
55134
+ pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--name-only", "only changed file names").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
55135
+ if (!o.repo) return fail(missingBrokerPrRepoMessage("diff", number));
54951
55136
  await dispatchGh(buildPrDiffSpec(number, o), "read pr diff");
54952
55137
  });
54953
- pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--json", "machine-readable mergeQueueEntry (null when not queued)").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
55138
+ pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (required \u2014 no git-origin default)", repoOpt).option("--json", "machine-readable mergeQueueEntry (null when not queued)").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
55139
+ if (!o.repo) return fail(missingBrokerPrRepoMessage("queue", number));
54954
55140
  const spec = buildPrQueueSpec(number, o);
54955
55141
  requireBrokerApp("read pr queue");
54956
55142
  try {
@@ -55083,9 +55269,13 @@ var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
55083
55269
  var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
55084
55270
  var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
55085
55271
  var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
55272
+ var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
55086
55273
  function parseApiGetPath(rawPath) {
55087
55274
  const path2 = rawPath.trim();
55088
- const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
55275
+ const refuse = () => {
55276
+ const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
55277
+ return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${hint}`);
55278
+ };
55089
55279
  if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
55090
55280
  const q = path2.indexOf("?");
55091
55281
  const base = q === -1 ? path2 : path2.slice(0, q);
@@ -55106,7 +55296,7 @@ function parseApiGetPath(rawPath) {
55106
55296
  if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
55107
55297
  return { kind: "contents", path: path2, paginate: false };
55108
55298
  }
55109
- throw new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
55299
+ throw refuse();
55110
55300
  }
55111
55301
  var ZIP_TIMEOUT_MS = 12e4;
55112
55302
  var ZIP_MAX_BUFFER = 512 * 1024 * 1024;
@@ -58009,6 +58199,7 @@ var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, yo
58009
58199
  var lastParseErrorKind = "other";
58010
58200
  var lastUnknownCommand;
58011
58201
  var lastParseErrorConflict = false;
58202
+ var lastParseHintOwnsGuess = false;
58012
58203
  function parseInvalidChoiceError(plain) {
58013
58204
  const m = /option '([^']+)' argument '([^']*)' is invalid\. Allowed choices are ([^.]+)\./.exec(plain);
58014
58205
  if (!m) return void 0;
@@ -58077,6 +58268,12 @@ function resolveParseHint() {
58077
58268
  const parentPath = parent ? canonicalPathFor(commandPath3(parent)) ?? commandPath3(parent) : void 0;
58078
58269
  const guide = unknownCommandDomainGuide(parentPath, lastUnknownCommand);
58079
58270
  if (guide) return `(${guide.hint}. ${DISCOVERY_HINT})`;
58271
+ const claimCmd = resolveCommandFromArgv(program2, ["board", "claim"]);
58272
+ const workboard = claimCmd ? workboardClaimRecovery(lastUnknownCommand, process.argv.slice(2), allCommandPaths(), commandOwnLongFlags(claimCmd)) : void 0;
58273
+ if (workboard) {
58274
+ lastParseHintOwnsGuess = true;
58275
+ return `(${workboard.hint} ${DISCOVERY_HINT})`;
58276
+ }
58080
58277
  const candidates = unknownCommandCandidates(parent, allCommandPaths());
58081
58278
  const tail = unknownCommandTail(process.argv.slice(2), lastUnknownCommand);
58082
58279
  const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, candidates, tail) : void 0;
@@ -58135,7 +58332,7 @@ function envelopeAwareWriteErr(str2) {
58135
58332
  return;
58136
58333
  }
58137
58334
  const resolvedHint = resolveParseHint();
58138
- if (lastParseErrorKind === "unknown-command" && (resolvedHint === STALE_HINT || resolvedHint === `(${DISCOVERY_HINT})`)) {
58335
+ if (lastParseErrorKind === "unknown-command" && (lastParseHintOwnsGuess || resolvedHint === STALE_HINT || resolvedHint === `(${DISCOVERY_HINT})`)) {
58139
58336
  pendingHumanParseError = pendingHumanParseError?.replace(/\n\(Did you mean [^)]+\?\)/g, "");
58140
58337
  }
58141
58338
  const hint = str2.replace(PARSE_HINT_SENTINEL, resolvedHint);
@@ -59096,7 +59293,7 @@ ci.command("reconcile").description("audit + optionally apply merge settings and
59096
59293
  }
59097
59294
  const deps = ciAuditDeps2();
59098
59295
  const audit = await auditOrgCi(deps, o.repo);
59099
- const applyResults = o.apply ? await Promise.all(audit.repos.map((r) => applyCiReconcileRepo(r.repo, deps))) : o.parkRuleset ? await Promise.all(audit.repos.map((r) => parkProductRuleset(r.repo, deps))) : [];
59296
+ const applyResults = o.apply ? await Promise.all(audit.repos.filter((r) => !r.excludedReason).map((r) => applyCiReconcileRepo(r.repo, deps))) : o.parkRuleset ? await Promise.all(audit.repos.filter((r) => !r.excludedReason).map((r) => parkProductRuleset(r.repo, deps))) : [];
59100
59297
  const payload = { audit, apply: applyResults };
59101
59298
  if (o.json) console.log(JSON.stringify(payload, null, 2));
59102
59299
  else {
@@ -59427,7 +59624,9 @@ function printHouseRootHelp(house) {
59427
59624
  consoleIo.log(lines2.join("\n"));
59428
59625
  }
59429
59626
  resolveVerbAliasShim(process.argv);
59627
+ normalizeWorkboardClaimArgv(process.argv);
59430
59628
  resolveHouseShim(process.argv);
59629
+ normalizeBoardClaimIssueArgv(process.argv);
59431
59630
  program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGraceful(e.message));
59432
59631
  // Annotate the CommonJS export names for ESM import in node:
59433
59632
  0 && (module.exports = {
@@ -59438,11 +59637,13 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
59438
59637
  commandOwnLongFlags,
59439
59638
  envHealLockPath,
59440
59639
  isOrgRegisteredRepo,
59640
+ normalizeBoardClaimIssueArgv,
59441
59641
  parseInvalidChoiceError,
59442
59642
  positionalTargetForm,
59443
59643
  registryClientDeps,
59444
59644
  repoSlug,
59445
59645
  suggestCommandPath,
59446
59646
  unknownCommandCandidates,
59447
- unknownCommandTail
59647
+ unknownCommandTail,
59648
+ workboardClaimRecovery
59448
59649
  });