@mutmutco/claude-plugin 4.6.16 → 4.6.18

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mmi",
3
3
  "displayName": "MMI",
4
- "version": "4.6.16",
4
+ "version": "4.6.18",
5
5
  "mmiCompat": "4.x",
6
6
  "description": "MMI workflow skills and org gates delivery.",
7
7
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -2998,9 +2998,9 @@ ${indent}`) + "'";
2998
2998
  return quotedString(value, ctx);
2999
2999
  }
3000
3000
  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
3001
- const literal2 = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
3001
+ const literal = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
3002
3002
  if (!value)
3003
- return literal2 ? "|\n" : ">\n";
3003
+ return literal ? "|\n" : ">\n";
3004
3004
  let chomp;
3005
3005
  let endStart;
3006
3006
  for (endStart = value.length; endStart > 0; --endStart) {
@@ -3049,7 +3049,7 @@ ${indent}`) + "'";
3049
3049
  if (onComment)
3050
3050
  onComment();
3051
3051
  }
3052
- if (!literal2) {
3052
+ if (!literal) {
3053
3053
  const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`);
3054
3054
  let literalFallback = false;
3055
3055
  const foldOptions = getFoldOptions(ctx, true);
@@ -13637,13 +13637,16 @@ function isExemptPorcelainLine(status, path2) {
13637
13637
  return isLiveSessionPath(path2);
13638
13638
  }
13639
13639
  function porcelainBlockingPaths(porcelain) {
13640
- const blocking = [];
13640
+ return porcelainBlockingEntries(porcelain).map((e) => e.path);
13641
+ }
13642
+ function porcelainBlockingEntries(porcelain) {
13643
+ const entries = [];
13641
13644
  for (const line of porcelain.split("\n")) {
13642
13645
  if (!line.trim()) continue;
13643
13646
  const { status, path: path2 } = splitPorcelainLine(line);
13644
- if (path2 !== "" && !isExemptPorcelainLine(status, path2)) blocking.push(path2);
13647
+ if (path2 !== "" && !isExemptPorcelainLine(status, path2)) entries.push({ status, path: path2 });
13645
13648
  }
13646
- return blocking;
13649
+ return entries;
13647
13650
  }
13648
13651
  function porcelainHasBlockingChanges(porcelain) {
13649
13652
  return porcelainBlockingPaths(porcelain).length > 0;
@@ -17607,6 +17610,279 @@ var REGISTRY_FETCH_TIMEOUT_MS = 8e3;
17607
17610
  init_fetch_retry();
17608
17611
  init_client_version();
17609
17612
 
17613
+ // src/hub-release-ci-authority.ts
17614
+ var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
17615
+ var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
17616
+ var HUB_RELEASE_REF = "refs/heads/development";
17617
+ var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
17618
+ var ID = /^[1-9][0-9]*$/;
17619
+ function hubReleaseCiRun(env) {
17620
+ if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
17621
+ if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
17622
+ if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
17623
+ const runId = env.GITHUB_RUN_ID ?? "";
17624
+ const runAttempt2 = env.GITHUB_RUN_ATTEMPT ?? "";
17625
+ if (!ID.test(runId) || !ID.test(runAttempt2)) return null;
17626
+ return { runId: Number(runId), runAttempt: Number(runAttempt2) };
17627
+ }
17628
+
17629
+ // ../shared/runner-fallback.ts
17630
+ var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
17631
+ var STANDARD_HOSTED_LABEL_RE = /^(?:ubuntu-(?:latest|24\.04|22\.04|24\.04-arm|22\.04-arm)|windows-(?:latest|2025|2022|11-arm)|macos-(?:latest|\d+|\d+-intel))$/i;
17632
+ var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
17633
+ function classifyRunnerCost(selector) {
17634
+ const raw = selector.replace(/\s+#.*$/gm, "").trim();
17635
+ if (STANDARD_HOSTED_LABEL_RE.test(raw.replace(/^(['"])(.*)\1$/, "$2"))) return "standard-hosted";
17636
+ if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
17637
+ const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
17638
+ if (json) {
17639
+ try {
17640
+ const labels2 = JSON.parse(json[1]);
17641
+ 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";
17642
+ } catch {
17643
+ return "unknown";
17644
+ }
17645
+ }
17646
+ if (raw.includes("${{")) return "unknown";
17647
+ 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")];
17648
+ return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
17649
+ }
17650
+ var STANDARD_HOSTED_ALLOWLIST = [
17651
+ ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
17652
+ ["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
17653
+ ];
17654
+ var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
17655
+ var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
17656
+ function runnerChoiceProduces(job, decider, output, source) {
17657
+ const needs = job.needs;
17658
+ if (!(needs === decider || Array.isArray(needs) && needs.includes(decider))) return false;
17659
+ const producer = source.jobs.get(decider);
17660
+ if (!record(producer) || !record(producer.outputs) || !Array.isArray(producer.steps)) return false;
17661
+ const step = /^\$\{\{\s*steps\.([\w-]+)\.outputs\.runs-on\s*\}\}$/.exec(String(producer.outputs[output] ?? ""))?.[1];
17662
+ const steps = producer.steps.filter((s) => record(s) && s.id === step);
17663
+ if (steps.length !== 1 || !record(steps[0])) return false;
17664
+ const { uses } = steps[0], inputs = steps[0].with ?? {};
17665
+ const pinned = /^[0-9a-f]{40}$/.test(source.blessedSha) && uses === `${RUNNER_CHOICE_ACTION}@${source.blessedSha}`;
17666
+ if (!pinned && !(source.repo === "mutmutco/MMI-Hub" && uses === "./.github/actions/runner-choice")) return false;
17667
+ const hosted = record(inputs) ? inputs.hosted ?? "ubuntu-latest" : void 0;
17668
+ return typeof hosted === "string" && STANDARD_HOSTED_LABEL_RE.test(hosted);
17669
+ }
17670
+ function standardHostedLabelFor(repo, path2, jobId) {
17671
+ if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
17672
+ return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
17673
+ }
17674
+ var record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
17675
+ function classifyJobRunnerCost(job, allowance2, source) {
17676
+ if (!record(job) || !Object.hasOwn(job, "runs-on")) return "unknown";
17677
+ const selector = job["runs-on"];
17678
+ const raw = typeof selector === "string" ? selector.trim() : Array.isArray(selector) && selector.length && selector.every((label) => typeof label === "string") ? JSON.stringify(selector) : "";
17679
+ if (!raw) return "unknown";
17680
+ const approved = (value) => Array.isArray(allowance2) && allowance2.includes(value);
17681
+ if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
17682
+ const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
17683
+ if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
17684
+ const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
17685
+ if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
17686
+ const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
17687
+ const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
17688
+ if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
17689
+ const direct = classifyRunnerCost(raw);
17690
+ const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
17691
+ if (!matrix || direct === "hosted") return direct;
17692
+ if (!Object.hasOwn(job, "strategy") || !record(job.strategy) || !Object.hasOwn(job.strategy, "matrix") || !record(job.strategy.matrix)) return "unknown";
17693
+ const axes = job.strategy.matrix;
17694
+ if (Object.hasOwn(axes, "exclude")) return "unknown";
17695
+ let alternatives;
17696
+ if (Object.hasOwn(axes, "include")) {
17697
+ if (Object.keys(axes).length !== 1 || !Array.isArray(axes.include) || !axes.include.length) return "unknown";
17698
+ alternatives = axes.include.map((row) => record(row) && Object.hasOwn(row, matrix[1]) ? row[matrix[1]] : void 0);
17699
+ } else {
17700
+ const axis = axes[matrix[1]];
17701
+ if (!Object.hasOwn(axes, matrix[1]) || !Array.isArray(axis) || !axis.length) return "unknown";
17702
+ alternatives = axis;
17703
+ }
17704
+ if (matrix[2]) alternatives = alternatives.map((row) => record(row) && Object.hasOwn(row, matrix[2]) ? row[matrix[2]] : void 0);
17705
+ const costs = alternatives.map((value) => {
17706
+ if (typeof value === "string") {
17707
+ if (value.includes("${{")) return "unknown";
17708
+ if (approved(value) || !matrix[2] && allowance2 === "windows-latest" && value === allowance2) return "standard-hosted";
17709
+ return classifyRunnerCost(value);
17710
+ }
17711
+ if (!Array.isArray(value) || !value.length || !value.every((label) => typeof label === "string" && /^[\w-]+$/.test(label))) return "unknown";
17712
+ return classifyRunnerCost(JSON.stringify(value));
17713
+ });
17714
+ if (costs.includes("hosted")) return "hosted";
17715
+ if (costs.includes("unknown")) return "unknown";
17716
+ return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
17717
+ }
17718
+
17719
+ // ../shared/github-contents.ts
17720
+ function decodeGitHubContents(content) {
17721
+ return Buffer.from(content, "base64").toString("utf8");
17722
+ }
17723
+ function encodeGitHubContents(content) {
17724
+ return Buffer.from(content, "utf8").toString("base64");
17725
+ }
17726
+
17727
+ // ../shared/workflow-runner-source.ts
17728
+ var WORKFLOW_PATH = /^\.github\/workflows\/[\w.-]+\.ya?ml$/;
17729
+ var REPO = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9_][\w.-]*$/;
17730
+ var REF = /^[\w./-]+$/;
17731
+ var MAX_FILES = 64;
17732
+ var MAX_DEPTH = 8;
17733
+ var WorkflowSourceError = class extends Error {
17734
+ /** Value-free category (safeReadCategory vocabulary) — routes/logs carry it, never a location or body. */
17735
+ category;
17736
+ constructor(location, category) {
17737
+ super(`${location}: ${category}`);
17738
+ this.category = category;
17739
+ }
17740
+ };
17741
+ function safeReadCategory(error) {
17742
+ const e = error;
17743
+ if (e?.rateLimited === true) return "rate-limited";
17744
+ if (typeof e?.status === "number" && Number.isInteger(e.status) && e.status >= 400 && e.status <= 599) return `HTTP ${e.status}`;
17745
+ if (e?.name === "TimeoutError" || e?.name === "AbortError" || e?.code === "ETIMEDOUT") return "read-timeout";
17746
+ if (e?.code === "ENOENT") return "reader-unavailable";
17747
+ if (e?.code === "ENOBUFS" || e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "reader-output-limit";
17748
+ if (error instanceof SyntaxError) return "invalid-json";
17749
+ return "read-failed";
17750
+ }
17751
+ function locationOf(file) {
17752
+ return `${file.repo}/${file.path}${file.ref ? `@${file.ref}` : ""}`;
17753
+ }
17754
+ function contentsPath(file) {
17755
+ if (!REPO.test(file.repo) || !(WORKFLOW_PATH.test(file.path) || file.path === ".github/workflows") || file.ref !== void 0 && !REF.test(file.ref)) {
17756
+ throw new WorkflowSourceError("workflow source", "invalid-location");
17757
+ }
17758
+ return `repos/${file.repo}/contents/${file.path}${file.ref ? `?ref=${encodeURIComponent(file.ref)}` : ""}`;
17759
+ }
17760
+ async function readSource(read, file) {
17761
+ const endpoint = contentsPath(file);
17762
+ try {
17763
+ return await read(endpoint);
17764
+ } catch (e) {
17765
+ throw new WorkflowSourceError(locationOf(file), safeReadCategory(e));
17766
+ }
17767
+ }
17768
+ async function readBody(read, file) {
17769
+ const value = await readSource(read, file);
17770
+ if (!value || value.encoding !== "base64" || typeof value.content !== "string" || !value.content.trim() || value.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(value.content)) {
17771
+ throw new WorkflowSourceError(locationOf(file), "invalid-content");
17772
+ }
17773
+ return { ...file, body: decodeGitHubContents(value.content) };
17774
+ }
17775
+ function resolveCallee(caller, uses) {
17776
+ if (uses.startsWith("./") && WORKFLOW_PATH.test(uses.slice(2))) return { repo: caller.repo, path: uses.slice(2), ref: caller.ref };
17777
+ const remote = /^([\w.-]+\/[\w.-]+)\/(\.github\/workflows\/[\w.-]+\.ya?ml)@([\w./-]+)$/.exec(uses);
17778
+ return remote ? { repo: remote[1], path: remote[2], ref: remote[3] } : void 0;
17779
+ }
17780
+ async function readRunnerWorkflowClosure(parse2, read, repo, ref, roots, bounds) {
17781
+ let totalBodyBytes = 0;
17782
+ const readBounded = async (file) => {
17783
+ const child2 = await readBody(read, file);
17784
+ totalBodyBytes += Buffer.byteLength(child2.body, "utf8");
17785
+ if (bounds?.maxTotalBodyBytes !== void 0 && totalBodyBytes > bounds.maxTotalBodyBytes) {
17786
+ throw new WorkflowSourceError(locationOf(child2), "source-read-limit");
17787
+ }
17788
+ return child2;
17789
+ };
17790
+ let files;
17791
+ if (roots) {
17792
+ files = roots.map((file) => ({ ...file, repo, ref }));
17793
+ } else {
17794
+ const listing = await readSource(read, { repo, ref, path: ".github/workflows" });
17795
+ if (!Array.isArray(listing)) throw new WorkflowSourceError(`${repo}/.github/workflows`, "invalid-listing");
17796
+ const paths = listing.filter((f) => f?.type === "file" && /\.ya?ml$/i.test(f?.path ?? "")).map((f) => f.path);
17797
+ if (!paths.length || paths.length > MAX_FILES) throw new WorkflowSourceError(`${repo}/.github/workflows`, "empty-or-oversized-listing");
17798
+ files = [];
17799
+ for (const path2 of paths) files.push(await readBounded({ repo, ref, path: path2 }));
17800
+ }
17801
+ if (!files.length || files.length > MAX_FILES) throw new WorkflowSourceError("workflow source", "empty-or-oversized-listing");
17802
+ for (const file of files) contentsPath(file);
17803
+ const byLocation = new Map(files.map((file) => [locationOf(file), file]));
17804
+ const active = /* @__PURE__ */ new Set();
17805
+ const completed = /* @__PURE__ */ new Set();
17806
+ async function visit2(file, depth) {
17807
+ const key = locationOf(file);
17808
+ if (active.has(key)) throw new WorkflowSourceError(key, "reusable-cycle");
17809
+ if (depth > MAX_DEPTH) throw new WorkflowSourceError(key, "reusable-depth-limit");
17810
+ if (completed.has(key)) return;
17811
+ active.add(key);
17812
+ const jobs = parse2(file.body);
17813
+ if (!jobs) throw new WorkflowSourceError(key, "invalid-workflow-yaml");
17814
+ for (const job of jobs.values()) {
17815
+ if (!Object.hasOwn(job, "uses")) continue;
17816
+ const uses = job.uses;
17817
+ if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
17818
+ const target = resolveCallee(file, uses);
17819
+ if (!target) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
17820
+ if (bounds?.calleeAllowed && !bounds.calleeAllowed(target)) throw new WorkflowSourceError(key, "unauthorized-callee");
17821
+ const targetKey = locationOf(target);
17822
+ let child2 = byLocation.get(targetKey);
17823
+ if (!child2) {
17824
+ if (byLocation.size >= MAX_FILES) throw new WorkflowSourceError(key, "workflow-read-limit");
17825
+ child2 = await readBounded(target);
17826
+ byLocation.set(targetKey, child2);
17827
+ }
17828
+ await visit2(child2, depth + 1);
17829
+ }
17830
+ active.delete(key);
17831
+ completed.add(key);
17832
+ }
17833
+ for (const file of files) await visit2(file, 0);
17834
+ return [...byLocation.values()];
17835
+ }
17836
+ function classifyRunnerWorkflows(parse2, files) {
17837
+ const known = new Map(files.map((file) => [locationOf(file), file]));
17838
+ const parsed = new Map(files.map((file) => [locationOf(file), parse2(file.body)]));
17839
+ const resolvedCache = /* @__PURE__ */ new Map();
17840
+ function resolved(file, active) {
17841
+ const key = locationOf(file);
17842
+ if (active.has(key)) return false;
17843
+ if (resolvedCache.has(key)) return resolvedCache.get(key);
17844
+ const next = new Set(active).add(key);
17845
+ const jobs = parsed.get(key);
17846
+ const result = Boolean(jobs && [...jobs.values()].every((job) => {
17847
+ if (!Object.hasOwn(job, "uses")) return true;
17848
+ const uses = job.uses;
17849
+ if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) return false;
17850
+ const target = resolveCallee(file, uses);
17851
+ const child2 = target && known.get(locationOf(target));
17852
+ return Boolean(child2 && resolved(child2, next));
17853
+ }));
17854
+ resolvedCache.set(key, result);
17855
+ return result;
17856
+ }
17857
+ return files.flatMap((file) => {
17858
+ const jobs = parsed.get(locationOf(file));
17859
+ if (!jobs) return [{ repo: file.repo, path: file.path, job: "(invalid YAML)", cost: "unknown", callee: false }];
17860
+ return [...jobs].flatMap(([id, job]) => {
17861
+ const hasCallee = Object.hasOwn(job, "uses");
17862
+ const uses = hasCallee && typeof job.uses === "string" && !Object.hasOwn(job, "runs-on") ? job.uses : void 0;
17863
+ const target = uses && resolveCallee(file, uses);
17864
+ if (target && known.has(locationOf(target)) && resolved(file, /* @__PURE__ */ new Set())) return [];
17865
+ const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id), { repo: file.repo, jobs, blessedSha: BLESSED_RUNNER_CHOICE_SHA });
17866
+ return [{ repo: file.repo, path: file.path, job: id, cost, callee: hasCallee }];
17867
+ });
17868
+ });
17869
+ }
17870
+ function runnerWorkflowFromContents(value) {
17871
+ const file = value;
17872
+ if (!file || typeof file.repo !== "string" || typeof file.path !== "string" || file.ref !== void 0 && typeof file.ref !== "string") {
17873
+ throw new WorkflowSourceError("workflow source", "invalid-content");
17874
+ }
17875
+ if (!REPO.test(file.repo) || !WORKFLOW_PATH.test(file.path) || file.ref !== void 0 && !REF.test(file.ref)) {
17876
+ throw new WorkflowSourceError("workflow source", "invalid-content");
17877
+ }
17878
+ if (file.encoding !== "base64" || typeof file.content !== "string" || !file.content.trim() || file.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(file.content)) {
17879
+ throw new WorkflowSourceError("workflow source", "invalid-content");
17880
+ }
17881
+ const body = decodeGitHubContents(file.content);
17882
+ if (!body.trim()) throw new WorkflowSourceError("workflow source", "invalid-content");
17883
+ return { repo: file.repo, path: file.path, ref: file.ref, body };
17884
+ }
17885
+
17610
17886
  // ../shared/schedule-inventory.ts
17611
17887
  function classifyLlmValue(raw) {
17612
17888
  const value = raw.trim().toLowerCase();
@@ -18669,6 +18945,23 @@ async function tenantDeploy(payload, deps) {
18669
18945
  async function releaseAnnouncement(payload, deps) {
18670
18946
  return postJson("/release-announcement", payload, deps, "POST", { noRetry: true, timeoutMs: 3e4 });
18671
18947
  }
18948
+ async function trainWorkflowSourceClosure(repo, deps) {
18949
+ const verdict = await fetchTrainAuthority(repo, deps);
18950
+ if (!verdict.ok) throw new WorkflowSourceError("workflow source", "authority-unverified");
18951
+ const delegatedTrain = verdict.authority.train === true && verdict.authority.authenticatedDelegation === true;
18952
+ if (!delegatedTrain || repo.toLowerCase() === HUB_RELEASE_REPO.toLowerCase()) return void 0;
18953
+ const SAFE_CHECK = /^[A-Za-z0-9 -]{1,64}$/;
18954
+ const res = await postJson("/train-workflow-source", { repo }, deps, "POST", { noRetry: true, timeoutMs: 3e4 });
18955
+ if (res.status === 422) {
18956
+ const body2 = res.body;
18957
+ const check = typeof body2?.check === "string" && SAFE_CHECK.test(body2.check) ? body2.check : "read-failed";
18958
+ throw new WorkflowSourceError("workflow source", check);
18959
+ }
18960
+ if (!res.ok || res.status !== 200) throw new WorkflowSourceError("workflow source", "source-endpoint-unavailable");
18961
+ const body = res.body;
18962
+ if (!body || !Array.isArray(body.files)) throw new WorkflowSourceError("workflow source", "invalid-response");
18963
+ return body.files.map(runnerWorkflowFromContents);
18964
+ }
18672
18965
  async function actionsCanary(payload, deps) {
18673
18966
  return postJson("/actions-canary", payload, deps, "POST", { noRetry: true });
18674
18967
  }
@@ -24389,14 +24682,6 @@ async function archiveClosedDoneBoardItems(options, deps = {}) {
24389
24682
  return result;
24390
24683
  }
24391
24684
 
24392
- // src/github-contents.ts
24393
- function decodeGitHubContents(content) {
24394
- return Buffer.from(content, "base64").toString("utf8");
24395
- }
24396
- function encodeGitHubContents(content) {
24397
- return Buffer.from(content, "utf8").toString("base64");
24398
- }
24399
-
24400
24685
  // src/bootstrap-ruleset.ts
24401
24686
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
24402
24687
  var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
@@ -25538,7 +25823,6 @@ var BLESSED_RUNNER_CHECK_POLICY_SHA = "67bb2012710a96d21be825c29a5d5c2b4b8001e2"
25538
25823
  var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
25539
25824
  var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
25540
25825
  var BLESSED_VERDICT_RECEIPT_SHA = "";
25541
- var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
25542
25826
  var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
25543
25827
  var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
25544
25828
  var LOCAL_USE = "./.github/actions/run-with-budget";
@@ -25705,9 +25989,9 @@ function checkGateBudget(files, opts = {}) {
25705
25989
  if (!rawMs) {
25706
25990
  failures.push(`${path2}: run-with-budget step has no max-seconds input \u2014 the budget step guards nothing`);
25707
25991
  } else {
25708
- const literal2 = /^['"]?(\d+)['"]?$/.exec(rawMs);
25709
- const seconds = literal2 ? Number(literal2[1]) : NaN;
25710
- if (!literal2 || seconds < 1) {
25992
+ const literal = /^['"]?(\d+)['"]?$/.exec(rawMs);
25993
+ const seconds = literal ? Number(literal[1]) : NaN;
25994
+ if (!literal || seconds < 1) {
25711
25995
  failures.push(`${path2}: max-seconds '${rawMs}' is not a literal positive integer`);
25712
25996
  } else if (seconds > MAX_SECONDS_CEILING) {
25713
25997
  failures.push(`${path2}: max-seconds ${seconds} exceeds the org ceiling ${MAX_SECONDS_CEILING} \u2014 a budget that generous guards nothing; measure the gate and set a real ceiling`);
@@ -27805,10 +28089,10 @@ var rollout_plan_default = {
27805
28089
  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)."
27806
28090
  },
27807
28091
  baseline: {
27808
- version: "4.6.16",
27809
- tag: "v4.6.16",
27810
- commit: "4bd6f2423939",
27811
- npm: "@mutmutco/cli@4.6.16"
28092
+ version: "4.6.18",
28093
+ tag: "v4.6.18",
28094
+ commit: "24b7f2455176",
28095
+ npm: "@mutmutco/cli@4.6.18"
27812
28096
  },
27813
28097
  exitCriterion: "fleet-n-of-n",
27814
28098
  hubOnlyShortcut: "forbidden",
@@ -27825,14 +28109,14 @@ var rollout_plan_default = {
27825
28109
  repo: "mutmutco/mmi-hub",
27826
28110
  role: "canary",
27827
28111
  schedule: "train",
27828
- v3Target: "v4.6.16"
28112
+ v3Target: "v4.6.18"
27829
28113
  }
27830
28114
  ],
27831
28115
  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.",
27832
28116
  rollback: {
27833
28117
  independent: true,
27834
- mechanism: "npm dist-tag latest -> 4.6.16 and redeploy the Hub Lambda from tag v4.6.16 (4bd6f2423939); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27835
- v3Target: "v4.6.16 (@mutmutco/cli@4.6.16, tag commit 4bd6f2423939 \u2014 last known-good release carrying the repo-index v4-only contract)"
28118
+ mechanism: "npm dist-tag latest -> 4.6.18 and redeploy the Hub Lambda from tag v4.6.18 (24b7f2455176); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
28119
+ v3Target: "v4.6.18 (@mutmutco/cli@4.6.18, tag commit 24b7f2455176 \u2014 last known-good release carrying the repo-index v4-only contract)"
27836
28120
  }
27837
28121
  },
27838
28122
  {
@@ -32387,290 +32671,100 @@ var import_node_fs31 = require("node:fs");
32387
32671
  var import_promises4 = require("node:fs/promises");
32388
32672
  var import_node_path28 = require("node:path");
32389
32673
 
32390
- // ../shared/runner-fallback.ts
32391
- var HOSTED_RUNNER_LABEL_RE = /\b(?:ubuntu|windows|macos)-(?:latest|\d[\w.-]*)\b/i;
32392
- var STANDARD_HOSTED_LABEL_RE = /^(?:ubuntu-(?:latest|24\.04|22\.04|24\.04-arm|22\.04-arm)|windows-(?:latest|2025|2022|11-arm)|macos-(?:latest|\d+|\d+-intel))$/i;
32393
- function classifyRunnerCost(selector) {
32394
- const raw = selector.replace(/\s+#.*$/gm, "").trim();
32395
- if (STANDARD_HOSTED_LABEL_RE.test(raw.replace(/^(['"])(.*)\1$/, "$2"))) return "standard-hosted";
32396
- if (HOSTED_RUNNER_LABEL_RE.test(raw)) return "hosted";
32397
- const json = /^\$\{\{\s*fromJSON\('([^']+)'\)\s*\}\}$/.exec(raw);
32398
- if (json) {
32399
- try {
32400
- const labels2 = JSON.parse(json[1]);
32401
- 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";
32402
- } catch {
32403
- return "unknown";
32404
- }
32405
- }
32406
- if (raw.includes("${{")) return "unknown";
32407
- 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")];
32408
- return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
32409
- }
32410
- var STANDARD_HOSTED_ALLOWLIST = [
32411
- ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
32412
- ["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
32413
- ];
32414
- var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
32415
- var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
32416
- function runnerChoiceProduces(job, decider, output, source) {
32417
- const needs = job.needs;
32418
- if (!(needs === decider || Array.isArray(needs) && needs.includes(decider))) return false;
32419
- const producer = source.jobs.get(decider);
32420
- if (!record(producer) || !record(producer.outputs) || !Array.isArray(producer.steps)) return false;
32421
- const step = /^\$\{\{\s*steps\.([\w-]+)\.outputs\.runs-on\s*\}\}$/.exec(String(producer.outputs[output] ?? ""))?.[1];
32422
- const steps = producer.steps.filter((s) => record(s) && s.id === step);
32423
- if (steps.length !== 1 || !record(steps[0])) return false;
32424
- const { uses } = steps[0], inputs = steps[0].with ?? {};
32425
- const pinned = /^[0-9a-f]{40}$/.test(source.blessedSha) && uses === `${RUNNER_CHOICE_ACTION}@${source.blessedSha}`;
32426
- if (!pinned && !(source.repo === "mutmutco/MMI-Hub" && uses === "./.github/actions/runner-choice")) return false;
32427
- const hosted = record(inputs) ? inputs.hosted ?? "ubuntu-latest" : void 0;
32428
- return typeof hosted === "string" && STANDARD_HOSTED_LABEL_RE.test(hosted);
32429
- }
32430
- function standardHostedLabelFor(repo, path2, jobId) {
32431
- if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
32432
- return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
32433
- }
32434
- var record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
32435
- function classifyJobRunnerCost(job, allowance2, source) {
32436
- if (!record(job) || !Object.hasOwn(job, "runs-on")) return "unknown";
32437
- const selector = job["runs-on"];
32438
- const raw = typeof selector === "string" ? selector.trim() : Array.isArray(selector) && selector.length && selector.every((label) => typeof label === "string") ? JSON.stringify(selector) : "";
32439
- if (!raw) return "unknown";
32440
- const approved = (value) => Array.isArray(allowance2) && allowance2.includes(value);
32441
- if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
32442
- const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
32443
- if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
32444
- const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
32445
- if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
32446
- const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
32447
- const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
32448
- if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
32449
- const direct = classifyRunnerCost(raw);
32450
- const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
32451
- if (!matrix || direct === "hosted") return direct;
32452
- if (!Object.hasOwn(job, "strategy") || !record(job.strategy) || !Object.hasOwn(job.strategy, "matrix") || !record(job.strategy.matrix)) return "unknown";
32453
- const axes = job.strategy.matrix;
32454
- if (Object.hasOwn(axes, "exclude")) return "unknown";
32455
- let alternatives;
32456
- if (Object.hasOwn(axes, "include")) {
32457
- if (Object.keys(axes).length !== 1 || !Array.isArray(axes.include) || !axes.include.length) return "unknown";
32458
- alternatives = axes.include.map((row) => record(row) && Object.hasOwn(row, matrix[1]) ? row[matrix[1]] : void 0);
32459
- } else {
32460
- const axis = axes[matrix[1]];
32461
- if (!Object.hasOwn(axes, matrix[1]) || !Array.isArray(axis) || !axis.length) return "unknown";
32462
- alternatives = axis;
32463
- }
32464
- if (matrix[2]) alternatives = alternatives.map((row) => record(row) && Object.hasOwn(row, matrix[2]) ? row[matrix[2]] : void 0);
32465
- const costs = alternatives.map((value) => {
32466
- if (typeof value === "string") {
32467
- if (value.includes("${{")) return "unknown";
32468
- if (approved(value) || !matrix[2] && allowance2 === "windows-latest" && value === allowance2) return "standard-hosted";
32469
- return classifyRunnerCost(value);
32470
- }
32471
- if (!Array.isArray(value) || !value.length || !value.every((label) => typeof label === "string" && /^[\w-]+$/.test(label))) return "unknown";
32472
- return classifyRunnerCost(JSON.stringify(value));
32473
- });
32474
- if (costs.includes("hosted")) return "hosted";
32475
- if (costs.includes("unknown")) return "unknown";
32476
- return costs.includes("standard-hosted") ? "standard-hosted" : "self-hosted";
32477
- }
32478
-
32479
32674
  // src/workflow-runner-yaml.ts
32480
32675
  var import_yaml = __toESM(require_dist(), 1);
32481
- var literal = (node) => !(0, import_yaml.isAlias)(node) && !((0, import_yaml.isNode)(node) && (node.tag || "anchor" in node && node.anchor));
32482
- var literalMap = (node) => (0, import_yaml.isMap)(node) && literal(node) && node.items.every(({ key }) => (0, import_yaml.isScalar)(key) && literal(key) && typeof key.value === "string" && key.value !== "<<");
32483
- function parseRunnerJobs(source) {
32484
- if (!source || source.length > 15e5) return void 0;
32485
- try {
32486
- const doc = (0, import_yaml.parseDocument)(source, { uniqueKeys: true, stringKeys: true, merge: false, prettyErrors: false });
32487
- if (doc.errors.length || doc.warnings.length) return void 0;
32488
- let invalid = false;
32489
- let nodes = 0;
32490
- (0, import_yaml.visit)(doc, (_key, _node, path2) => {
32491
- if (++nodes > 1e5 || path2.length > 64) {
32492
- invalid = true;
32493
- return import_yaml.visit.BREAK;
32494
- }
32495
- });
32496
- if (invalid || !literalMap(doc.contents)) return void 0;
32497
- const jobs = doc.contents.get("jobs", true);
32498
- if (!literalMap(jobs) || !jobs.items.length) return void 0;
32499
- const result = /* @__PURE__ */ new Map();
32500
- for (const { key, value: job } of jobs.items) {
32501
- if (!(0, import_yaml.isScalar)(key) || typeof key.value !== "string" || !literalMap(job)) return void 0;
32502
- const projected = /* @__PURE__ */ Object.create(null);
32503
- for (const field of ["runs-on", "uses", "strategy", "needs", "outputs", "steps"]) {
32504
- if (!job.has(field)) continue;
32505
- const optional = field === "needs" || field === "outputs" || field === "steps";
32506
- const value = job.get(field, true);
32507
- if (value !== null && !(0, import_yaml.isNode)(value)) {
32508
- if (optional) continue;
32509
- return void 0;
32676
+
32677
+ // ../shared/workflow-runner-yaml-core.ts
32678
+ function createParseRunnerJobs(ops) {
32679
+ const literal = (node) => !ops.isAlias(node) && !(ops.isNode(node) && (node.tag || "anchor" in node && node.anchor));
32680
+ const literalMap = (node) => ops.isMap(node) && literal(node) && node.items.every(({ key }) => ops.isScalar(key) && literal(key) && typeof key.value === "string" && key.value !== "<<");
32681
+ return (source) => {
32682
+ if (!source || source.length > 15e5) return void 0;
32683
+ try {
32684
+ const doc = ops.parseDocument(source, { uniqueKeys: true, stringKeys: true, merge: false, prettyErrors: false });
32685
+ if (doc.errors.length || doc.warnings.length) return void 0;
32686
+ let invalid = false;
32687
+ let nodes = 0;
32688
+ ops.visit(doc, (_key, _node, path2) => {
32689
+ if (++nodes > 1e5 || path2.length > 64) {
32690
+ invalid = true;
32691
+ return ops.BREAK;
32510
32692
  }
32511
- (0, import_yaml.visit)(value, (_key, node) => {
32512
- if (!literal(node) || (0, import_yaml.isPair)(node) && (0, import_yaml.isScalar)(node.key) && node.key.value === "<<") {
32513
- invalid = true;
32514
- return import_yaml.visit.BREAK;
32693
+ });
32694
+ if (invalid || !literalMap(doc.contents)) return void 0;
32695
+ const jobs = doc.contents.get("jobs", true);
32696
+ if (!literalMap(jobs) || !jobs.items.length) return void 0;
32697
+ const result = /* @__PURE__ */ new Map();
32698
+ for (const { key, value: job } of jobs.items) {
32699
+ if (!ops.isScalar(key) || typeof key.value !== "string" || !literalMap(job)) return void 0;
32700
+ const projected = /* @__PURE__ */ Object.create(null);
32701
+ for (const field of ["runs-on", "uses", "strategy", "needs", "outputs", "steps"]) {
32702
+ if (!job.has(field)) continue;
32703
+ const optional = field === "needs" || field === "outputs" || field === "steps";
32704
+ const value = job.get(field, true);
32705
+ if (value !== null && !ops.isNode(value)) {
32706
+ if (optional) continue;
32707
+ return void 0;
32515
32708
  }
32516
- });
32517
- if (invalid && optional) {
32518
- invalid = false;
32519
- continue;
32709
+ ops.visit(value, (_key, node) => {
32710
+ if (!literal(node) || ops.isPair(node) && ops.isScalar(node.key) && node.key.value === "<<") {
32711
+ invalid = true;
32712
+ return ops.BREAK;
32713
+ }
32714
+ });
32715
+ if (invalid && optional) {
32716
+ invalid = false;
32717
+ continue;
32718
+ }
32719
+ if (invalid) return void 0;
32720
+ projected[field] = value === null ? null : value.toJS(doc, { maxAliasCount: 0 });
32520
32721
  }
32521
- if (invalid) return void 0;
32522
- projected[field] = value === null ? null : value.toJS(doc, { maxAliasCount: 0 });
32722
+ result.set(key.value, projected);
32523
32723
  }
32524
- result.set(key.value, projected);
32724
+ return result;
32725
+ } catch {
32726
+ return void 0;
32525
32727
  }
32526
- return result;
32527
- } catch {
32528
- return void 0;
32529
- }
32728
+ };
32530
32729
  }
32531
32730
 
32731
+ // src/workflow-runner-yaml.ts
32732
+ var parseRunnerJobs = createParseRunnerJobs({ isAlias: import_yaml.isAlias, isMap: import_yaml.isMap, isNode: import_yaml.isNode, isPair: import_yaml.isPair, isScalar: import_yaml.isScalar, parseDocument: import_yaml.parseDocument, visit: import_yaml.visit, BREAK: import_yaml.visit.BREAK });
32733
+
32532
32734
  // src/workflow-runner-source.ts
32533
- var WORKFLOW_PATH = /^\.github\/workflows\/[\w.-]+\.ya?ml$/;
32534
- var REPO = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9_][\w.-]*$/;
32535
- var REF = /^[\w./-]+$/;
32536
- var MAX_FILES = 64;
32537
- var MAX_DEPTH = 8;
32538
- var WorkflowSourceError = class extends Error {
32539
- constructor(location, category) {
32540
- super(`${location}: ${category}`);
32541
- }
32542
- };
32543
- function safeReadCategory(error) {
32544
- const e = error;
32545
- if (e?.rateLimited === true) return "rate-limited";
32546
- if (typeof e?.status === "number" && Number.isInteger(e.status) && e.status >= 400 && e.status <= 599) return `HTTP ${e.status}`;
32547
- if (e?.name === "TimeoutError" || e?.name === "AbortError" || e?.code === "ETIMEDOUT") return "read-timeout";
32548
- if (e?.code === "ENOENT") return "reader-unavailable";
32549
- if (e?.code === "ENOBUFS" || e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "reader-output-limit";
32550
- if (error instanceof SyntaxError) return "invalid-json";
32551
- return "read-failed";
32552
- }
32553
- function locationOf(file) {
32554
- return `${file.repo}/${file.path}${file.ref ? `@${file.ref}` : ""}`;
32555
- }
32556
- function contentsPath(file) {
32557
- if (!REPO.test(file.repo) || !(WORKFLOW_PATH.test(file.path) || file.path === ".github/workflows") || file.ref !== void 0 && !REF.test(file.ref)) {
32558
- throw new WorkflowSourceError("workflow source", "invalid-location");
32559
- }
32560
- return `repos/${file.repo}/contents/${file.path}${file.ref ? `?ref=${encodeURIComponent(file.ref)}` : ""}`;
32561
- }
32562
- async function readSource(read, file) {
32563
- const endpoint = contentsPath(file);
32564
- try {
32565
- return await read(endpoint);
32566
- } catch (e) {
32567
- throw new WorkflowSourceError(locationOf(file), safeReadCategory(e));
32568
- }
32569
- }
32570
- async function readBody(read, file) {
32571
- const value = await readSource(read, file);
32572
- if (!value || value.encoding !== "base64" || typeof value.content !== "string" || !value.content.trim() || value.content.length > 15e5 || !/^[A-Za-z0-9+/=\r\n]+$/.test(value.content)) {
32573
- throw new WorkflowSourceError(locationOf(file), "invalid-content");
32574
- }
32575
- return { ...file, body: decodeGitHubContents(value.content) };
32576
- }
32577
- function resolveCallee(caller, uses) {
32578
- if (uses.startsWith("./") && WORKFLOW_PATH.test(uses.slice(2))) return { repo: caller.repo, path: uses.slice(2), ref: caller.ref };
32579
- const remote = /^([\w.-]+\/[\w.-]+)\/(\.github\/workflows\/[\w.-]+\.ya?ml)@([\w./-]+)$/.exec(uses);
32580
- return remote ? { repo: remote[1], path: remote[2], ref: remote[3] } : void 0;
32581
- }
32582
- async function readRunnerWorkflowClosure(read, repo, ref, roots) {
32583
- let files;
32584
- if (roots) {
32585
- files = roots.map((file) => ({ ...file, repo, ref }));
32586
- } else {
32587
- const listing = await readSource(read, { repo, ref, path: ".github/workflows" });
32588
- if (!Array.isArray(listing)) throw new WorkflowSourceError(`${repo}/.github/workflows`, "invalid-listing");
32589
- const paths = listing.filter((f) => f?.type === "file" && /\.ya?ml$/i.test(f?.path ?? "")).map((f) => f.path);
32590
- if (!paths.length || paths.length > MAX_FILES) throw new WorkflowSourceError(`${repo}/.github/workflows`, "empty-or-oversized-listing");
32591
- files = [];
32592
- for (const path2 of paths) files.push(await readBody(read, { repo, ref, path: path2 }));
32593
- }
32594
- if (!files.length || files.length > MAX_FILES) throw new WorkflowSourceError("workflow source", "empty-or-oversized-listing");
32595
- for (const file of files) contentsPath(file);
32596
- const byLocation = new Map(files.map((file) => [locationOf(file), file]));
32597
- const active = /* @__PURE__ */ new Set();
32598
- const completed = /* @__PURE__ */ new Set();
32599
- async function visit2(file, depth) {
32600
- const key = locationOf(file);
32601
- if (active.has(key)) throw new WorkflowSourceError(key, "reusable-cycle");
32602
- if (depth > MAX_DEPTH) throw new WorkflowSourceError(key, "reusable-depth-limit");
32603
- if (completed.has(key)) return;
32604
- active.add(key);
32605
- const jobs = parseRunnerJobs(file.body);
32606
- if (!jobs) throw new WorkflowSourceError(key, "invalid-workflow-yaml");
32607
- for (const job of jobs.values()) {
32608
- if (!Object.hasOwn(job, "uses")) continue;
32609
- const uses = job.uses;
32610
- if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
32611
- const target = resolveCallee(file, uses);
32612
- if (!target) throw new WorkflowSourceError(key, "unresolved-reusable-ref");
32613
- const targetKey = locationOf(target);
32614
- let child2 = byLocation.get(targetKey);
32615
- if (!child2) {
32616
- if (byLocation.size >= MAX_FILES) throw new WorkflowSourceError(key, "workflow-read-limit");
32617
- child2 = await readBody(read, target);
32618
- byLocation.set(targetKey, child2);
32619
- }
32620
- await visit2(child2, depth + 1);
32621
- }
32622
- active.delete(key);
32623
- completed.add(key);
32624
- }
32625
- for (const file of files) await visit2(file, 0);
32626
- return [...byLocation.values()];
32735
+ function readRunnerWorkflowClosure2(read, repo, ref, roots, bounds) {
32736
+ return readRunnerWorkflowClosure(parseRunnerJobs, read, repo, ref, roots, bounds);
32627
32737
  }
32628
- function classifyRunnerWorkflows(files) {
32629
- const known = new Map(files.map((file) => [locationOf(file), file]));
32630
- const parsed = new Map(files.map((file) => [locationOf(file), parseRunnerJobs(file.body)]));
32631
- const resolvedCache = /* @__PURE__ */ new Map();
32632
- function resolved(file, active) {
32633
- const key = locationOf(file);
32634
- if (active.has(key)) return false;
32635
- if (resolvedCache.has(key)) return resolvedCache.get(key);
32636
- const next = new Set(active).add(key);
32637
- const jobs = parsed.get(key);
32638
- const result = Boolean(jobs && [...jobs.values()].every((job) => {
32639
- if (!Object.hasOwn(job, "uses")) return true;
32640
- const uses = job.uses;
32641
- if (typeof uses !== "string" || Object.hasOwn(job, "runs-on")) return false;
32642
- const target = resolveCallee(file, uses);
32643
- const child2 = target && known.get(locationOf(target));
32644
- return Boolean(child2 && resolved(child2, next));
32645
- }));
32646
- resolvedCache.set(key, result);
32647
- return result;
32648
- }
32649
- return files.flatMap((file) => {
32650
- const jobs = parsed.get(locationOf(file));
32651
- if (!jobs) return [{ repo: file.repo, path: file.path, job: "(invalid YAML)", cost: "unknown", callee: false }];
32652
- return [...jobs].flatMap(([id, job]) => {
32653
- const hasCallee = Object.hasOwn(job, "uses");
32654
- const uses = hasCallee && typeof job.uses === "string" && !Object.hasOwn(job, "runs-on") ? job.uses : void 0;
32655
- const target = uses && resolveCallee(file, uses);
32656
- if (target && known.has(locationOf(target)) && resolved(file, /* @__PURE__ */ new Set())) return [];
32657
- const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id), { repo: file.repo, jobs, blessedSha: BLESSED_RUNNER_CHOICE_SHA });
32658
- return [{ repo: file.repo, path: file.path, job: id, cost, callee: hasCallee }];
32659
- });
32660
- });
32738
+ function classifyRunnerWorkflows2(files) {
32739
+ return classifyRunnerWorkflows(parseRunnerJobs, files);
32661
32740
  }
32662
32741
 
32663
32742
  // src/actions-billing-preflight.ts
32664
32743
  async function assertActionsJobsCanStart(deps, targetRepo2) {
32665
- const read = deps.readWorkflowContents ?? (async (path2) => JSON.parse(await deps.run("gh", ["api", path2])));
32666
32744
  let files;
32667
32745
  try {
32668
- files = await readRunnerWorkflowClosure(read, targetRepo2);
32746
+ const served = await deps.readTrainWorkflowClosure?.(targetRepo2);
32747
+ if (served && !served.some((f) => f.repo.toLowerCase() === targetRepo2.toLowerCase())) {
32748
+ throw new WorkflowSourceError("workflow source", "invalid-response");
32749
+ }
32750
+ if (served) {
32751
+ files = served;
32752
+ } else {
32753
+ const read = deps.readWorkflowContents ?? (async (path2) => JSON.parse(await deps.run("gh", ["api", path2])));
32754
+ try {
32755
+ files = await readRunnerWorkflowClosure2(read, targetRepo2);
32756
+ } catch (e) {
32757
+ const detail = e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed";
32758
+ throw new Error("release refused (#8911): workflow source inventory unreadable; " + detail + ". Resolve this source read before tagging.");
32759
+ }
32760
+ }
32669
32761
  } catch (e) {
32670
- const detail = e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed";
32671
- throw new Error("release refused (#8911): workflow source inventory unreadable; " + detail + ". Resolve this source read before tagging.");
32762
+ if (e instanceof WorkflowSourceError) {
32763
+ throw new Error("release refused (#8911): workflow source inventory unreadable; " + e.message + ". Resolve this source read before tagging.");
32764
+ }
32765
+ throw e;
32672
32766
  }
32673
- const unresolved = classifyRunnerWorkflows(files).filter((f) => f.cost !== "self-hosted" && f.cost !== "standard-hosted");
32767
+ const unresolved = classifyRunnerWorkflows2(files).filter((f) => f.cost !== "self-hosted" && f.cost !== "standard-hosted");
32674
32768
  if (unresolved.length) throw new Error("release refused (#8911): paid or unresolved runner path(s): " + unresolved.map((f) => f.repo + "/" + f.path + ":" + f.job).join(", ") + ". Standard included GitHub-hosted labels are allowed (#8977, Jerv-Hub#2459); only larger/paid runners and unresolved selectors/callees are refused. Resolve them before tagging; no paid capacity probe is permitted.");
32675
32769
  process.stderr.write("actions preflight: " + targetRepo2 + " source selectors are self-hosted or standard included GitHub-hosted (#8977); no canary dispatch; the org spending limit guards over-allowance spend (unprovable from source); access/isolation/execution unproven (#8958)\n");
32676
32770
  }
@@ -35904,18 +35998,23 @@ async function runTrainDoctor(input) {
35904
35998
  }
35905
35999
  add(healedBlock ? { code: "scratch-untracked", severity: "healed", source: "local", title: `wrote the org-managed .gitignore block for ${liveScratch.join(", ")} (#5409)`, remedy: "nothing \u2014 the live-session dirs are ignored from now on" } : { code: "scratch-untracked", severity: "warning", source: "local", title: `untracked live-session scratch: ${liveScratch.join(", ")}`, remedy: "run `mmi-cli devops train doctor --heal` (or `mmi-cli doctor --apply`) to write the org-managed .gitignore block (#5409); the train gate already exempts these (#5397)" });
35906
36000
  }
35907
- const blocking = porcelainBlockingPaths(porcelain);
35908
- const modifiedOnly = new Set(porcelainRows.filter((l) => l.startsWith(" M ")).map((l) => l.slice(3)));
36001
+ const blockingEntries = porcelainBlockingEntries(porcelain);
36002
+ const modifiedOnly = new Set(blockingEntries.filter((e) => e.status === " M").map((e) => e.path));
35909
36003
  const churn = [];
35910
- for (const path2 of blocking.filter((p) => modifiedOnly.has(p))) {
36004
+ for (const path2 of blockingEntries.map((e) => e.path).filter((p) => modifiedOnly.has(p))) {
35911
36005
  try {
35912
36006
  if (!clean2(await train.run("git", ["diff", "HEAD", "--numstat", "--", path2]))) churn.push(path2);
35913
36007
  } catch {
35914
36008
  }
35915
36009
  }
35916
- const dirty = blocking.filter((p) => !churn.includes(p));
35917
- if (dirty.length) {
35918
- add({ code: "tree-dirty-work", severity: "blocker", source: "local", title: `working tree carries tracked changes \u2014 blocking: ${dirty.join(", ")}`, remedy: "commit or stash the work, then rerun; the train refuses a dirty tree before any mutation" });
36010
+ const dirty = blockingEntries.filter((e) => !churn.includes(e.path));
36011
+ const dirtyTracked = dirty.filter((e) => e.status !== "??");
36012
+ const dirtyUntracked = dirty.filter((e) => e.status === "??");
36013
+ if (dirtyTracked.length) {
36014
+ add({ code: "tree-dirty-work", severity: "blocker", source: "local", title: `working tree carries tracked changes \u2014 blocking: ${dirtyTracked.map((e) => e.path).join(", ")}`, remedy: "commit or stash the work, then rerun; the train refuses a dirty tree before any mutation" });
36015
+ }
36016
+ if (dirtyUntracked.length) {
36017
+ add({ code: "tree-dirty-work", severity: "blocker", source: "local", title: `working tree carries untracked content \u2014 blocking: ${dirtyUntracked.map((e) => e.path).join(", ")}`, remedy: "inspect ownership first \u2014 another session's content stays untouched; commit intended new source through its owning lane; gitignore or move only verified own scratch to the gitignored repo-root tmp/, then rerun" });
35919
36018
  }
35920
36019
  if (churn.length) {
35921
36020
  const restored = [];
@@ -39318,7 +39417,7 @@ var SHARED_ACTION_PINS = [
39318
39417
  { path: "npm-publish", floor: NPM_PUBLISH_SAFE_FLOOR_SHA }
39319
39418
  ];
39320
39419
  function auditRunnerFallbacks(repo, files, closure) {
39321
- return classifyRunnerWorkflows(closure ?? files.map((file) => ({ ...file, repo }))).map((f) => ({
39420
+ return classifyRunnerWorkflows2(closure ?? files.map((file) => ({ ...file, repo }))).map((f) => ({
39322
39421
  ok: f.cost === "self-hosted" || f.cost === "standard-hosted",
39323
39422
  label: f.cost === "standard-hosted" ? `runner uses approved standard-hosted source (#8912/#8958/#8977): ${f.repo}/${f.path}:${f.job}` : `runner is self-hosted only (#8911): ${f.repo}/${f.path}:${f.job}`,
39324
39423
  detail: f.callee ? "unknown callee runner policy; source must be read at its referenced revision" : f.cost === "hosted" ? "larger/paid GitHub-hosted runner is forbidden; only standard included GitHub-hosted labels are allowed (#8977, Jerv-Hub#2459)" : f.cost === "unknown" ? "unknown runner selector; source unresolved" : f.cost === "standard-hosted" ? "standard GitHub-hosted labels within included Enterprise minutes only (#8977, Jerv-Hub#2459); the org spending limit guards over-allowance spend (unprovable from source); runner access and isolation not established" : "self-hosted selection only; runner access and isolation not established",
@@ -40381,7 +40480,7 @@ async function auditRepoCi(repo, callerDeps) {
40381
40480
  const workflows = await getBranchWorkflows();
40382
40481
  if (workflows) {
40383
40482
  try {
40384
- const closure = workflows.length ? await readRunnerWorkflowClosure((path2) => callerDeps.client.rest("GET", path2), repo, baseBranch, workflows) : [];
40483
+ const closure = workflows.length ? await readRunnerWorkflowClosure2((path2) => callerDeps.client.rest("GET", path2), repo, baseBranch, workflows) : [];
40385
40484
  checks.push(...auditRunnerFallbacks(repo, workflows, closure));
40386
40485
  } catch (e) {
40387
40486
  checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed" });
@@ -47165,7 +47264,7 @@ function sshRemoteCommand(remoteCommand) {
47165
47264
  function shellSingleQuote(value) {
47166
47265
  return `'${value.replace(/'/g, `'\\''`)}'`;
47167
47266
  }
47168
- var SSH_RECIPE_AGENT_NOTE = "# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli devops runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.";
47267
+ var SSH_RECIPE_AGENT_NOTE = '# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli devops runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.\n# For a multi-line remote body add --stdin (or --body <file>): the recipe then runs `sh -s` on the box,\n# and you stream the body over stdin \u2014 `bash "<path>" < "<body-file>"` \u2014 with no remote re-quoting (#9750).';
47169
47268
  function sshRecipeScript(box, remoteCommand) {
47170
47269
  return `#!/usr/bin/env bash
47171
47270
  ${formatSshRecipe(box, remoteCommand)}
@@ -47249,8 +47348,16 @@ function registerBoxCommands(program3) {
47249
47348
  await failGraceful(e.message);
47250
47349
  }
47251
47350
  });
47252
- box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (runs under the keyless vault wrapper and loads the key into an ssh-agent; the key is never written to a file or echoed)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, command, o) => {
47351
+ box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (runs under the keyless vault wrapper and loads the key into an ssh-agent; the key is never written to a file or echoed)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").option("--stdin", "with --ssh --script: write the recipe with remote command sh -s so a multi-line script can be piped or redirected on stdin without remote shell re-quoting (#9750)").option("--body <file>", 'with --ssh --script: same as --stdin, and name <file> in the printed run line: bash "<script>" < "<file>" (#9750)').action(async (name, command, o) => {
47253
47352
  try {
47353
+ if ((o.stdin || o.body) && (!o.ssh || !o.script)) {
47354
+ await failGraceful("runtime box get: --stdin/--body requires --ssh --script <path> (it writes an sh -s recipe that reads the remote body from stdin)");
47355
+ return;
47356
+ }
47357
+ if ((o.stdin || o.body) && command && command.length) {
47358
+ await failGraceful("runtime box get: cannot combine --stdin/--body with trailing [command...] arguments \u2014 put the commands in the stdin body file instead");
47359
+ return;
47360
+ }
47254
47361
  const { boxes, incomplete } = await fetchInventory();
47255
47362
  const result = lookupBox(name, boxes, incomplete);
47256
47363
  if (result.kind === "unknown") {
@@ -47269,16 +47376,19 @@ function registerBoxCommands(program3) {
47269
47376
  await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
47270
47377
  return;
47271
47378
  }
47272
- const remote = command && command.length ? command.join(" ") : void 0;
47379
+ const useStdin = Boolean(o.stdin || o.body);
47380
+ const remote = useStdin ? "sh -s" : command && command.length ? command.join(" ") : void 0;
47381
+ const stdinSource = o.body ? `"${o.body}"` : "<body.sh>";
47273
47382
  const wroteScript = o.ssh && o.script ? o.script : null;
47383
+ const runLine = wroteScript ? useStdin ? `bash "${wroteScript}" < ${stdinSource}` : `bash "${wroteScript}"` : void 0;
47274
47384
  if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
47275
47385
  if (o.json) {
47276
47386
  console.log(JSON.stringify({
47277
47387
  box: found,
47278
47388
  incomplete,
47279
- ...wroteScript ? { script: { path: wroteScript, run: `bash "${wroteScript}"` } } : {}
47389
+ ...wroteScript ? { script: { path: wroteScript, run: runLine, ...useStdin ? { stdin: o.body ?? true } : {} } } : {}
47280
47390
  }, null, 2));
47281
- } else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: bash "${wroteScript}"`);
47391
+ } else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: ${runLine}`);
47282
47392
  else if (o.ssh) console.log(`${formatSshRecipe(found, remote)}
47283
47393
  ${SSH_RECIPE_AGENT_NOTE}`);
47284
47394
  else console.log(formatBoxTable([found]));
@@ -54211,22 +54321,6 @@ async function deleteMergedRemoteBranch(options) {
54211
54321
  // src/command-register-train-operations.ts
54212
54322
  init_github_client();
54213
54323
 
54214
- // src/hub-release-ci-authority.ts
54215
- var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
54216
- var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
54217
- var HUB_RELEASE_REF = "refs/heads/development";
54218
- var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
54219
- var ID = /^[1-9][0-9]*$/;
54220
- function hubReleaseCiRun(env) {
54221
- if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
54222
- if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
54223
- if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
54224
- const runId = env.GITHUB_RUN_ID ?? "";
54225
- const runAttempt2 = env.GITHUB_RUN_ATTEMPT ?? "";
54226
- if (!ID.test(runId) || !ID.test(runAttempt2)) return null;
54227
- return { runId: Number(runId), runAttempt: Number(runAttempt2) };
54228
- }
54229
-
54230
54324
  // src/release-custody.ts
54231
54325
  var import_node_crypto18 = require("node:crypto");
54232
54326
  var import_node_fs41 = require("node:fs");
@@ -62659,6 +62753,9 @@ function trainApplyDeps() {
62659
62753
  return withHubReleaseCustody({
62660
62754
  // #8958: same authenticated bounded read boundary as devops api; no raw gh contents subprocess.
62661
62755
  readWorkflowContents: (path2) => defaultGitHubClient().rest("GET", path2, { timeoutMs: GH_TRAIN_TIMEOUT_MS }),
62756
+ // #9755: delegated Jerv trains require the Hub's sourceSha-pinned closure and fail closed on errors.
62757
+ // Humans and the Hub binding retain the caller-side read above; no producer token is widened.
62758
+ readTrainWorkflowClosure: async (repo) => trainWorkflowSourceClosure(repo, registryClientDeps(await loadConfig())),
62662
62759
  // #7027: every mutating train verb holds this checkout's train writer lock for its whole run.
62663
62760
  trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
62664
62761
  run: async (file, args) => {
@@ -68400,6 +68497,22 @@ function stageAllowlistConfig(project2) {
68400
68497
  const stages = cfg.stages.filter((s) => ALLOWLIST_STAGES.includes(s));
68401
68498
  return stages.length > 0 ? { key: cfg.key, stages } : null;
68402
68499
  }
68500
+ function resolveStageAllowlistTarget(repo, projects) {
68501
+ if (!projects) return { kind: "registry-unreadable", exitCode: 1, message: "org access sync: could not read the org project registry \u2014 nothing written" };
68502
+ const project2 = projects.find((p) => (p.repos ?? []).some((r) => r.toLowerCase() === repo.toLowerCase())) ?? null;
68503
+ if (!project2) return { kind: "unregistered", exitCode: 1, message: `org access sync: ${repo} is not in the org project registry \u2014 nothing written` };
68504
+ if (project2.stageAllowlist == null) {
68505
+ return {
68506
+ kind: "no-opt-in",
68507
+ exitCode: 0,
68508
+ json: { repo, optIn: null, stages: [], skipped: "no-stageAllowlist" },
68509
+ message: `org access sync: ${repo} has no stageAllowlist opt-in \u2014 nothing to rebuild (set it with \`mmi-cli oracle org project set ${repo} --var 'stageAllowlist={"key":"<KEY>","stages":["dev"]}'\`)`
68510
+ };
68511
+ }
68512
+ const config = stageAllowlistConfig(project2);
68513
+ if (!config) return { kind: "malformed", exitCode: 1, message: `org access sync: ${repo} has a malformed stageAllowlist in the registry \u2014 set it with \`mmi-cli oracle org project set ${repo} --var 'stageAllowlist={"key":"<KEY>","stages":["dev"]}'\`` };
68514
+ return { kind: "sync", exitCode: 0, config };
68515
+ }
68403
68516
  function developerAdminLogins(collaborators) {
68404
68517
  const logins = /* @__PURE__ */ new Set();
68405
68518
  for (const c of collaborators) {
@@ -72215,11 +72328,13 @@ access.command("sync [repo]").description("rebuild the stage allowlist (e.g. MM-
72215
72328
  if (!repo) return fail("org access sync: pass <owner/repo> or run inside a repo checkout");
72216
72329
  const cfg = await loadConfig();
72217
72330
  if (!cfg.sagaApiUrl) return fail("org access sync: Hub API URL not configured");
72218
- const projects = await fetchProjectsList(registryClientDeps(cfg));
72219
- if (!projects) return failGraceful("org access sync: could not read the org project registry \u2014 nothing written");
72220
- const project2 = projects.find((p) => (p.repos ?? []).some((r) => r.toLowerCase() === repo.toLowerCase())) ?? null;
72221
- const config = stageAllowlistConfig(project2);
72222
- if (!config) return failGraceful(`org access sync: ${repo} has no stageAllowlist opt-in \u2014 set it with \`mmi-cli oracle org project set ${repo} --var 'stageAllowlist={"key":"<KEY>","stages":["dev"]}'\``);
72331
+ const target = resolveStageAllowlistTarget(repo, await fetchProjectsList(registryClientDeps(cfg)));
72332
+ if (target.kind === "no-opt-in") {
72333
+ console.log(o.json ? JSON.stringify(target.json, null, 2) : target.message);
72334
+ return;
72335
+ }
72336
+ if (target.kind !== "sync") return failGraceful(target.message);
72337
+ const config = target.config;
72223
72338
  const secretsDeps = makeSecretsDeps(cfg);
72224
72339
  const client = defaultGitHubClient();
72225
72340
  const result = await syncStageAllowlist({
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.6.16",
3
+ "version": "4.6.18",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/claude-plugin",
3
- "version": "4.6.16",
3
+ "version": "4.6.18",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",