@bridge-ds/core 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { o as modeDisplayName, q as modesOf$1, u as isMultiMode, T as TIER_NAMES, w as TIER_AXIS, x as findReferenceProblems, y as evaluatePairs, z as contrastScope, A as contextLabel, S as SINGLE_MODE, B as pathId, D as flattenTokens, E as serializeTokenFile, F as removeToken, G as upsertToken, H as threeWayDiff, I as emptyTokenFile, J as parseTokenFile, K as EXT_ID, L as EXT_SCOPES, M as resolve, N as moveToken, O as effectiveScopes, P as sameScopes, Q as defaultScopes, R as readTokenAt, U as modeFile, V as defaultFile, W as DEFAULT_PROTECTED_BRANCHES, a as assertSafeBranch, X as ProtectedBranchError, C as CONFIG_FILE, l as loadConfig, Y as ConfigError, Z as DEFAULT_TOKEN_PATHS, c as collectHistory, p as packageDirCandidates, b as parseChangelog, r as releasesFromTags, m as mergeReleases, d as buildReport, _ as applyTokenEdits, $ as buildHistoryEntry, a0 as resolveBranchPreviews, a1 as tokensPath, a2 as coerceValue, a3 as toConfigJson, a4 as parseContrastChecks, a5 as checkLayeredPair, a6 as classifyRisk, a7 as formatChangeset, a8 as RISK_LABELS, a9 as parseJson, aa as serializeJson, ab as plainEqual, ac as toPlain, ad as getEntry, ae as fromPlain, af as previewLinks, ag as slugify, ah as readPreviewComment, ai as findPreviewComment, aj as previewsSettled, f as variantKeyOf } from "./component-spec-GFVEE6Fc.js";
2
- import { ak, al, am, n, k, an, ao, ap, aq, ar, as, at, au, av, aw, ax, ay, az, aA, aB, aC, aD, aE, aF, aG, aH, aI, aJ, aK, aL, aM, aN, aO, aP, aQ, j, aR, aS, aT, aU, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bg, bh, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, h, bs, i, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, g, bD, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, e, bR, bS, bT, bU, bV, bW, bX, bY, bZ, b_, b$, s, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, t, cb, cc, cd, ce, v, cf, cg, ch, ci, cj } from "./component-spec-GFVEE6Fc.js";
1
+ import { u as modeDisplayName, w as modesOf$1, x as isMultiMode, T as TIER_NAMES, y as TIER_AXIS, z as findReferenceProblems, A as evaluatePairs, B as contrastScope, D as contextLabel, S as SINGLE_MODE, E as pathId, F as flattenTokens, G as serializeTokenFile, H as removeToken, I as upsertToken, J as threeWayDiff, K as emptyTokenFile, L as parseTokenFile, M as EXT_ID, N as EXT_SCOPES, n as resolve, O as moveToken, P as effectiveScopes, Q as sameScopes, R as defaultScopes, U as readTokenAt, V as modeFile, W as defaultFile, X as DEFAULT_PROTECTED_BRANCHES, a as assertSafeBranch, Y as ProtectedBranchError, C as CONFIG_FILE, l as loadConfig, Z as ConfigError, _ as DEFAULT_TOKEN_PATHS, c as collectHistory, p as packageDirCandidates, b as parseChangelog, r as releasesFromTags, m as mergeReleases, d as buildReport, $ as applyTokenEdits, a0 as buildHistoryEntry, a1 as resolveBranchPreviews, a2 as tokensPath, a3 as coerceValue, a4 as toConfigJson, a5 as parseContrastChecks, a6 as checkLayeredPair, a7 as classifyRisk, a8 as formatChangeset, a9 as RISK_LABELS, aa as parseJson, ab as serializeJson, ac as plainEqual, ad as toPlain, ae as getEntry, af as fromPlain, ag as previewLinks, ah as slugify, ai as readPreviewComment, aj as findPreviewComment, ak as previewsSettled, f as variantKeyOf, al as canShip, am as whoCanShipSentence, an as unreadableShipTeams, g as layoutOptions, j as buildTokenSet } from "./component-spec-1z2ih4E3.js";
2
+ import { ao, ap, aq, q, o, ar, as, at, au, av, aw, ax, ay, az, aA, aB, aC, aD, aE, aF, aG, aH, aI, aJ, aK, aL, aM, aN, aO, aP, aQ, aR, aS, aT, aU, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bg, bh, k, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, h, bD, i, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, bR, bS, bT, bU, bV, bW, bX, bY, bZ, b_, b$, e, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, s, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, t, cs, ct, cu, v, cv, cw, cx, cy } from "./component-spec-1z2ih4E3.js";
3
3
  import { a as collectRefs, i as isAlias, b as aliasTarget, v as valuesEqual, p as parseColor, e as rgbaToHex, f as formatValue, s as selectTokenFiles, L as LayeredTokenSet, j as validateLayered } from "./layered-BnbmToGs.js";
4
- import { D, d, k as k2, l, c, m, n as n2, g as g2, h as h2, o, q, r, t as t2, u } from "./layered-BnbmToGs.js";
4
+ import { D, d, k as k2, l, c, m, n, g, h as h2, o as o2, q as q2, r, t as t2, u } from "./layered-BnbmToGs.js";
5
5
  const issueKey = (i2) => `${i2.code}|${i2.path ?? ""}|${i2.mode ?? ""}|${i2.code === "file-problem" ? i2.message : ""}`;
6
6
  const modeName = (mode) => mode === SINGLE_MODE ? "all modes" : modeDisplayName(mode);
7
7
  const isPalette = (t3) => t3.tier === "brand" && t3.path.startsWith("palette.");
@@ -82,23 +82,23 @@ function validateSet(set, pairs) {
82
82
  checkValue(set, t3, v2, mode, modeName(mode), issues);
83
83
  }
84
84
  }
85
- for (const o2 of set.overrides) {
86
- const mode = `${o2.brand}.${o2.theme}`;
87
- const where = `${modeDisplayName(o2.brand)} ${o2.theme} (${o2.file})`;
88
- const base = set.byPath.get(o2.path);
85
+ for (const o3 of set.overrides) {
86
+ const mode = `${o3.brand}.${o3.theme}`;
87
+ const where = `${modeDisplayName(o3.brand)} ${o3.theme} (${o3.file})`;
88
+ const base = set.byPath.get(o3.path);
89
89
  if (!base || base.tier !== "semantic") {
90
90
  issues.push({
91
91
  code: "override-orphan",
92
92
  severity: "error",
93
- path: o2.path,
93
+ path: o3.path,
94
94
  mode,
95
- message: `${o2.file} overrides ${o2.path}, which is not a semantic token. Remove the override or restore the token.`
95
+ message: `${o3.file} overrides ${o3.path}, which is not a semantic token. Remove the override or restore the token.`
96
96
  });
97
97
  continue;
98
98
  }
99
- checkValue(set, { path: o2.path, tier: "semantic", type: base.type }, o2.value, mode, where, issues);
100
- for (const ref of collectRefs(o2.value)) {
101
- if (!set.byPath.has(ref)) issues.push({ code: "broken-reference", severity: "error", path: o2.path, mode, message: `${o2.path} points to ${ref}, which does not exist (${where}).` });
99
+ checkValue(set, { path: o3.path, tier: "semantic", type: base.type }, o3.value, mode, where, issues);
100
+ for (const ref of collectRefs(o3.value)) {
101
+ if (!set.byPath.has(ref)) issues.push({ code: "broken-reference", severity: "error", path: o3.path, mode, message: `${o3.path} points to ${ref}, which does not exist (${where}).` });
102
102
  }
103
103
  }
104
104
  const axes = { brand: modesOf$1(set.layout, "brand"), theme: modesOf$1(set.layout, "semantic"), density: modesOf$1(set.layout, "density") };
@@ -141,14 +141,14 @@ function shadowedEdits(set, edits) {
141
141
  const out = [];
142
142
  for (const e2 of edits) {
143
143
  if (e2.tier !== "semantic") continue;
144
- for (const o2 of set.overrides) {
145
- if (o2.path !== e2.path || !e2.modes.includes(o2.theme)) continue;
144
+ for (const o3 of set.overrides) {
145
+ if (o3.path !== e2.path || !e2.modes.includes(o3.theme)) continue;
146
146
  out.push({
147
147
  code: "override-shadowed",
148
148
  severity: "warning",
149
149
  path: e2.path,
150
- mode: `${o2.brand}.${o2.theme}`,
151
- message: `${e2.path} is overridden for ${modeDisplayName(o2.brand)} in ${o2.theme} (${o2.file}), so this edit does not change ${modeDisplayName(o2.brand)} ${o2.theme}. Brand overrides stay code-only.`
150
+ mode: `${o3.brand}.${o3.theme}`,
151
+ message: `${e2.path} is overridden for ${modeDisplayName(o3.brand)} in ${o3.theme} (${o3.file}), so this edit does not change ${modeDisplayName(o3.brand)} ${o3.theme}. Brand overrides stay code-only.`
152
152
  });
153
153
  }
154
154
  }
@@ -180,9 +180,9 @@ function levenshtein(a, b) {
180
180
  for (let i2 = 1; i2 <= a.length; i2++) {
181
181
  let diag = prev[0];
182
182
  prev[0] = i2;
183
- for (let j2 = 1; j2 <= b.length; j2++) {
184
- const tmp = prev[j2];
185
- prev[j2] = Math.min(prev[j2] + 1, prev[j2 - 1] + 1, diag + (a[i2 - 1] === b[j2 - 1] ? 0 : 1));
183
+ for (let j = 1; j <= b.length; j++) {
184
+ const tmp = prev[j];
185
+ prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i2 - 1] === b[j - 1] ? 0 : 1));
186
186
  diag = tmp;
187
187
  }
188
188
  }
@@ -221,16 +221,16 @@ function reconcileIds(figma, git) {
221
221
  }
222
222
  }
223
223
  const suggestions = [];
224
- const unclaimedGit = [...gitById.values()].filter((g3) => !claimed.has(g3.id));
224
+ const unclaimedGit = [...gitById.values()].filter((g2) => !claimed.has(g2.id));
225
225
  for (const t3 of tokens) {
226
226
  if (gitById.has(t3.id)) continue;
227
227
  let best;
228
- for (const g3 of unclaimedGit) {
229
- if (!sameContent(t3, g3)) continue;
230
- const similarity = pathSimilarity(t3.path, g3.path);
228
+ for (const g2 of unclaimedGit) {
229
+ if (!sameContent(t3, g2)) continue;
230
+ const similarity = pathSimilarity(t3.path, g2.path);
231
231
  if (similarity < SIMILARITY_THRESHOLD) continue;
232
232
  if (!best || similarity > best.similarity) {
233
- best = { figmaId: t3.id, gitId: g3.id, fromPath: g3.path, toPath: t3.path, similarity: Math.round(similarity * 100) / 100 };
233
+ best = { figmaId: t3.id, gitId: g2.id, fromPath: g2.path, toPath: t3.path, similarity: Math.round(similarity * 100) / 100 };
234
234
  }
235
235
  }
236
236
  if (best) suggestions.push(best);
@@ -389,11 +389,11 @@ function optionsOf(edits) {
389
389
  const out = [];
390
390
  for (const e2 of edits) {
391
391
  const l2 = look(e2.token);
392
- const hit = out.find((o2) => o2.look === l2);
392
+ const hit = out.find((o3) => o3.look === l2);
393
393
  if (hit) hit.option.changes.push(e2.change);
394
394
  else out.push({ look: l2, option: { changes: [e2.change], token: e2.token } });
395
395
  }
396
- return out.map((o2) => o2.option);
396
+ return out.map((o3) => o3.option);
397
397
  }
398
398
  function combineConflicts(changes) {
399
399
  const out = [];
@@ -404,8 +404,8 @@ function combineConflicts(changes) {
404
404
  return out;
405
405
  }
406
406
  function pickedOption(c10, picks) {
407
- const n3 = picks[c10.key];
408
- return n3 === void 0 ? null : c10.options.find((o2) => o2.changes.includes(n3)) ?? null;
407
+ const n2 = picks[c10.key];
408
+ return n2 === void 0 ? null : c10.options.find((o3) => o3.changes.includes(n2)) ?? null;
409
409
  }
410
410
  const unpicked = (conflicts, picks) => conflicts.filter((c10) => !pickedOption(c10, picks));
411
411
  function combineChanges(latest, changes, picks = {}, opts = {}) {
@@ -458,16 +458,16 @@ function combineChanges(latest, changes, picks = {}, opts = {}) {
458
458
  return { files, included: on.map((c10) => c10.number), edits: groups.size, tokens, conflicts, missing, from, problems };
459
459
  }
460
460
  function encodePicks(picks) {
461
- return Object.entries(picks).filter(([, n3]) => Number.isSafeInteger(n3) && n3 > 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, n3]) => `${n3}:${key}`).join("~");
461
+ return Object.entries(picks).filter(([, n2]) => Number.isSafeInteger(n2) && n2 > 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, n2]) => `${n2}:${key}`).join("~");
462
462
  }
463
463
  function decodePicks(raw) {
464
464
  const out = {};
465
465
  for (const part of (raw ?? "").split("~")) {
466
466
  const i2 = part.indexOf(":");
467
467
  if (i2 < 1) continue;
468
- const n3 = Number(part.slice(0, i2));
468
+ const n2 = Number(part.slice(0, i2));
469
469
  const key = part.slice(i2 + 1);
470
- if (/^\d{1,7}$/.test(part.slice(0, i2)) && n3 > 0 && key.includes("#")) out[key] = n3;
470
+ if (/^\d{1,7}$/.test(part.slice(0, i2)) && n2 > 0 && key.includes("#")) out[key] = n2;
471
471
  }
472
472
  return out;
473
473
  }
@@ -595,7 +595,7 @@ function requiredCheckNames(defaultBranchRuns, pullRequestRuns) {
595
595
  const names2 = [...new Set(defaultBranchRuns.filter((r2) => !isVercel(r2) && ran(r2)).map((r2) => r2.name))];
596
596
  if (!pullRequestRuns?.length) return names2;
597
597
  const onPulls = new Set(pullRequestRuns.filter((r2) => !isVercel(r2)).map((r2) => r2.name));
598
- return names2.filter((n3) => onPulls.has(n3));
598
+ return names2.filter((n2) => onPulls.has(n2));
599
599
  }
600
600
  function refPattern(pattern) {
601
601
  const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
@@ -903,12 +903,12 @@ class GitHubClient {
903
903
  // --- History (read only) ----------------------------------------------------------------------
904
904
  /** Commits on a ref, newest first, optionally only those touching `path` (a file or directory). */
905
905
  async listCommits(opts = {}) {
906
- const q2 = new URLSearchParams();
907
- if (opts.sha) q2.set("sha", opts.sha);
908
- if (opts.path) q2.set("path", opts.path);
909
- q2.set("per_page", String(Math.min(100, opts.perPage ?? 30)));
910
- if (opts.page) q2.set("page", String(opts.page));
911
- const r2 = await this.request("GET", `${this.repoPath}/commits?${q2}`);
906
+ const q3 = new URLSearchParams();
907
+ if (opts.sha) q3.set("sha", opts.sha);
908
+ if (opts.path) q3.set("path", opts.path);
909
+ q3.set("per_page", String(Math.min(100, opts.perPage ?? 30)));
910
+ if (opts.page) q3.set("page", String(opts.page));
911
+ const r2 = await this.request("GET", `${this.repoPath}/commits?${q3}`);
912
912
  return r2.map(toCommitSummary);
913
913
  }
914
914
  /** One commit: message, author, date and parents. */
@@ -989,37 +989,37 @@ class GitHubClient {
989
989
  * Marks a draft pull request ready for review. REST cannot unset `draft`, so this is the GraphQL mutation
990
990
  * markPullRequestReadyForReview, with the same token (pull requests: write).
991
991
  */
992
- async markReadyForReview(n3) {
992
+ async markReadyForReview(n2) {
993
993
  this.assertToken("Marking a pull request ready for review");
994
- const pr = await this.request("GET", `${this.repoPath}/pulls/${n3}`);
994
+ const pr = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
995
995
  if (pr.draft === false) return;
996
- if (!pr.node_id) throw new GitHubError(422, `Pull request #${n3} has no node id: mark it ready for review on GitHub.`);
996
+ if (!pr.node_id) throw new GitHubError(422, `Pull request #${n2} has no node id: mark it ready for review on GitHub.`);
997
997
  const r2 = await this.request("POST", "/graphql", {
998
998
  query: "mutation($id: ID!) { markPullRequestReadyForReview(input: { pullRequestId: $id }) { pullRequest { number isDraft } } }",
999
999
  variables: { id: pr.node_id }
1000
1000
  });
1001
1001
  if (r2?.errors?.length) {
1002
1002
  const detail = r2.errors.map((e2) => e2.message).filter(Boolean).join(" ");
1003
- throw new GitHubError(422, `GitHub refused to mark #${n3} ready for review${detail ? `: ${detail}` : "."} Use "Ready for review" on GitHub instead.`, detail);
1003
+ throw new GitHubError(422, `GitHub refused to mark #${n2} ready for review${detail ? `: ${detail}` : "."} Use "Ready for review" on GitHub instead.`, detail);
1004
1004
  }
1005
1005
  }
1006
1006
  /**
1007
1007
  * Turns an open pull request back into a draft: the GraphQL mutation convertPullRequestToDraft (REST cannot set
1008
1008
  * `draft` either), same token. No-op when it is already a draft.
1009
1009
  */
1010
- async convertToDraft(n3) {
1010
+ async convertToDraft(n2) {
1011
1011
  this.assertToken("Turning a pull request back into a draft");
1012
- const pr = await this.request("GET", `${this.repoPath}/pulls/${n3}`);
1012
+ const pr = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
1013
1013
  if (pr.draft === true) return;
1014
- if (pr.state && pr.state !== "open") throw new GitHubError(422, `Pull request #${n3} is ${pr.state}: only an open one can go back to draft.`);
1015
- if (!pr.node_id) throw new GitHubError(422, `Pull request #${n3} has no node id: convert it to draft on GitHub.`);
1014
+ if (pr.state && pr.state !== "open") throw new GitHubError(422, `Pull request #${n2} is ${pr.state}: only an open one can go back to draft.`);
1015
+ if (!pr.node_id) throw new GitHubError(422, `Pull request #${n2} has no node id: convert it to draft on GitHub.`);
1016
1016
  const r2 = await this.request("POST", "/graphql", {
1017
1017
  query: "mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { number isDraft } } }",
1018
1018
  variables: { id: pr.node_id }
1019
1019
  });
1020
1020
  if (r2?.errors?.length) {
1021
1021
  const detail = r2.errors.map((e2) => e2.message).filter(Boolean).join(" ");
1022
- throw new GitHubError(422, `GitHub refused to turn #${n3} back into a draft${detail ? `: ${detail}` : "."} Use "Convert to draft" on GitHub instead.`, detail);
1022
+ throw new GitHubError(422, `GitHub refused to turn #${n2} back into a draft${detail ? `: ${detail}` : "."} Use "Convert to draft" on GitHub instead.`, detail);
1023
1023
  }
1024
1024
  }
1025
1025
  async createPullRequest(input) {
@@ -1068,23 +1068,23 @@ class GitHubClient {
1068
1068
  }
1069
1069
  /** Issues (not pull requests) with a label, newest first. */
1070
1070
  async listIssues(opts = {}) {
1071
- const q2 = new URLSearchParams({ state: opts.state ?? "all", per_page: String(opts.perPage ?? 50), sort: "created", direction: "desc" });
1072
- if (opts.label) q2.set("labels", opts.label);
1073
- const r2 = await this.request("GET", `${this.repoPath}/issues?${q2}`);
1071
+ const q3 = new URLSearchParams({ state: opts.state ?? "all", per_page: String(opts.perPage ?? 50), sort: "created", direction: "desc" });
1072
+ if (opts.label) q3.set("labels", opts.label);
1073
+ const r2 = await this.request("GET", `${this.repoPath}/issues?${q3}`);
1074
1074
  return r2.filter((i2) => !i2.pull_request).map(toIssue);
1075
1075
  }
1076
- async getIssue(n3) {
1077
- return toIssue(await this.request("GET", `${this.repoPath}/issues/${n3}`));
1076
+ async getIssue(n2) {
1077
+ return toIssue(await this.request("GET", `${this.repoPath}/issues/${n2}`));
1078
1078
  }
1079
1079
  /** Edits an issue: its body, or closes it with a reason ("not_planned" for a declined request). */
1080
- async updateIssue(n3, patch) {
1080
+ async updateIssue(n2, patch) {
1081
1081
  this.assertToken("Editing an issue");
1082
- return toIssue(await this.request("PATCH", `${this.repoPath}/issues/${n3}`, patch));
1082
+ return toIssue(await this.request("PATCH", `${this.repoPath}/issues/${n2}`, patch));
1083
1083
  }
1084
1084
  /** Adds people to an issue's assignees (GitHub ignores logins that cannot be assigned: the result says who is). */
1085
- async addAssignees(n3, logins) {
1085
+ async addAssignees(n2, logins) {
1086
1086
  this.assertToken("Assigning an issue");
1087
- const r2 = await this.request("POST", `${this.repoPath}/issues/${n3}/assignees`, { assignees: logins });
1087
+ const r2 = await this.request("POST", `${this.repoPath}/issues/${n2}/assignees`, { assignees: logins });
1088
1088
  return (r2.assignees ?? []).map((a) => a.login);
1089
1089
  }
1090
1090
  /** People with at least `permission` on the repository (reading the list needs push access). First 100. */
@@ -1151,8 +1151,8 @@ class GitHubClient {
1151
1151
  /** Text of one file at a ref (branch, tag or sha), or undefined when it does not exist. */
1152
1152
  async getFileText(path, ref) {
1153
1153
  try {
1154
- const q2 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1155
- const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q2}`);
1154
+ const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1155
+ const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
1156
1156
  if (r2.type !== "file") return void 0;
1157
1157
  if (r2.encoding === "base64" && r2.content !== void 0) return base64ToUtf8(r2.content);
1158
1158
  return r2.content;
@@ -1167,8 +1167,8 @@ class GitHubClient {
1167
1167
  */
1168
1168
  async getFile(path, ref) {
1169
1169
  try {
1170
- const q2 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1171
- const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q2}`);
1170
+ const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1171
+ const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
1172
1172
  if (r2.type !== "file") return void 0;
1173
1173
  const text = r2.encoding === "base64" && r2.content !== void 0 ? base64ToUtf8(r2.content) : r2.content ?? "";
1174
1174
  return { text, sha: r2.sha };
@@ -1183,8 +1183,8 @@ class GitHubClient {
1183
1183
  */
1184
1184
  async getFileBase64(path, ref) {
1185
1185
  try {
1186
- const q2 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1187
- const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q2}`);
1186
+ const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1187
+ const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
1188
1188
  if (r2.type !== "file" || r2.encoding !== "base64" || !r2.content) return void 0;
1189
1189
  return { base64: r2.content.replace(/\s+/g, ""), size: r2.size ?? 0 };
1190
1190
  } catch (err) {
@@ -1195,8 +1195,8 @@ class GitHubClient {
1195
1195
  /** Entries of one folder at a ref (contents API), or an empty list when the folder does not exist. */
1196
1196
  async listDirectory(path, ref) {
1197
1197
  try {
1198
- const q2 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1199
- const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q2}`);
1198
+ const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
1199
+ const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
1200
1200
  if (!Array.isArray(r2)) return [];
1201
1201
  return r2.map((e2) => ({ name: e2.name, path: e2.path, type: e2.type, sha: e2.sha, size: e2.size ?? 0 }));
1202
1202
  } catch (err) {
@@ -1293,25 +1293,25 @@ class GitHubClient {
1293
1293
  })
1294
1294
  );
1295
1295
  }
1296
- async getPullRequest(n3) {
1297
- const r2 = await this.request("GET", `${this.repoPath}/pulls/${n3}`);
1298
- return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null };
1296
+ async getPullRequest(n2) {
1297
+ const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
1298
+ return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null, requestedReviewers: (r2.requested_reviewers ?? []).map((x) => x.login) };
1299
1299
  }
1300
- async listPullRequestFiles(n3) {
1301
- const r2 = await this.request("GET", `${this.repoPath}/pulls/${n3}/files?per_page=100`);
1300
+ async listPullRequestFiles(n2) {
1301
+ const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}/files?per_page=100`);
1302
1302
  return r2.map((f) => ({ filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions }));
1303
1303
  }
1304
- async updatePullRequest(n3, patch) {
1305
- await this.request("PATCH", `${this.repoPath}/pulls/${n3}`, patch);
1304
+ async updatePullRequest(n2, patch) {
1305
+ await this.request("PATCH", `${this.repoPath}/pulls/${n2}`, patch);
1306
1306
  }
1307
- async listReviews(n3) {
1308
- const r2 = await this.request("GET", `${this.repoPath}/pulls/${n3}/reviews`);
1307
+ async listReviews(n2) {
1308
+ const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}/reviews`);
1309
1309
  return r2.map((x) => ({ id: x.id, user: x.user?.login, state: x.state, body: x.body ?? "", ...x.submitted_at ? { submittedAt: x.submitted_at } : {} }));
1310
1310
  }
1311
1311
  /** Submit a review. `COMMENT` works for any collaborator; approving your own PR is refused by GitHub. */
1312
- async createReview(n3, input) {
1312
+ async createReview(n2, input) {
1313
1313
  this.assertToken("Reviewing a pull request");
1314
- const r2 = await this.request("POST", `${this.repoPath}/pulls/${n3}/reviews`, input);
1314
+ const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/reviews`, input);
1315
1315
  return { id: r2.id, user: r2.user?.login, state: r2.state, body: r2.body ?? "" };
1316
1316
  }
1317
1317
  async listCheckRuns(ref) {
@@ -1372,6 +1372,100 @@ class GitHubClient {
1372
1372
  async removeLabel(issueNumber, label) {
1373
1373
  await this.request("DELETE", `${this.repoPath}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`);
1374
1374
  }
1375
+ // --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
1376
+ /** The account the token belongs to (GET /user). Needs a token. */
1377
+ async getViewer() {
1378
+ this.assertToken("Reading who you are on GitHub");
1379
+ const r2 = await this.request("GET", "/user");
1380
+ return { login: r2.login };
1381
+ }
1382
+ /** The repository's merge settings and the viewer's permissions (unknown settings count as allowed). */
1383
+ async getMergeSettings() {
1384
+ return this.request("GET", this.repoPath);
1385
+ }
1386
+ /**
1387
+ * Merges a pull request, only at `sha` (GitHub refuses when the branch moved meanwhile). The repository's branch
1388
+ * rules still apply: GitHub refuses a merge they block, whatever Bridge says.
1389
+ */
1390
+ async mergePullRequest(n2, input) {
1391
+ this.assertToken("Merging a branch");
1392
+ const r2 = await this.request("PUT", `${this.repoPath}/pulls/${n2}/merge`, {
1393
+ merge_method: input.method,
1394
+ sha: input.sha,
1395
+ ...input.commitMessage !== void 0 && input.method !== "rebase" ? { commit_message: input.commitMessage } : {}
1396
+ });
1397
+ return { sha: r2?.sha ?? "", merged: r2?.merged ?? true };
1398
+ }
1399
+ /** GitHub's "Update branch": merges the base into the pull request, only when its head is still `expectedHeadSha`. */
1400
+ async updatePullRequestBranch(n2, expectedHeadSha) {
1401
+ this.assertToken("Updating a branch from main");
1402
+ await this.request("PUT", `${this.repoPath}/pulls/${n2}/update-branch`, { expected_head_sha: expectedHeadSha });
1403
+ }
1404
+ /** Posts a comment on an issue or pull request and returns its id and link. */
1405
+ async createIssueComment(n2, body) {
1406
+ this.assertToken("Commenting");
1407
+ const r2 = await this.request("POST", `${this.repoPath}/issues/${n2}/comments`, { body });
1408
+ return { id: r2.id, url: r2.html_url };
1409
+ }
1410
+ /** Replaces the text of a comment. */
1411
+ async updateIssueComment(id, body) {
1412
+ this.assertToken("Editing a comment");
1413
+ await this.request("PATCH", `${this.repoPath}/issues/comments/${id}`, { body });
1414
+ }
1415
+ /** Submits a review on a given commit (the head the reviewer saw); the body may be empty for an approval. */
1416
+ async submitReview(n2, input) {
1417
+ this.assertToken("Reviewing a branch");
1418
+ const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/reviews`, {
1419
+ event: input.event,
1420
+ ...input.body ? { body: input.body } : {},
1421
+ ...input.commitId ? { commit_id: input.commitId } : {}
1422
+ });
1423
+ return { id: r2.id, user: r2.user?.login, state: r2.state, body: r2.body ?? "" };
1424
+ }
1425
+ /** Line comments of a pull request (review threads), oldest first. */
1426
+ async listReviewComments(n2) {
1427
+ const r2 = await this.request(
1428
+ "GET",
1429
+ `${this.repoPath}/pulls/${n2}/comments?per_page=100`
1430
+ );
1431
+ return r2.map((c10) => ({ id: c10.id, body: c10.body ?? "", author: c10.user?.login ?? "unknown", path: c10.path, line: c10.line ?? null, inReplyTo: c10.in_reply_to_id ?? null, url: c10.html_url, createdAt: c10.created_at }));
1432
+ }
1433
+ /** Asks these people to review a pull request. */
1434
+ async requestReviewers(n2, logins) {
1435
+ this.assertToken("Asking for a review");
1436
+ await this.request("POST", `${this.repoPath}/pulls/${n2}/requested_reviewers`, { reviewers: logins });
1437
+ }
1438
+ /** Takes these people off the requested reviewers. */
1439
+ async removeRequestedReviewers(n2, logins) {
1440
+ this.assertToken("Changing the requested reviewers");
1441
+ await this.request("DELETE", `${this.repoPath}/pulls/${n2}/requested_reviewers`, { reviewers: logins });
1442
+ }
1443
+ /** Replies in the thread of a line comment. */
1444
+ async replyToReviewComment(n2, commentId, body) {
1445
+ this.assertToken("Replying to a comment");
1446
+ const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/comments/${commentId}/replies`, { body });
1447
+ return { id: r2.id, url: r2.html_url };
1448
+ }
1449
+ /**
1450
+ * What `head` adds on top of `base`, with what library sync needs: the shared start (merge base), the base
1451
+ * commit and its tree, how far behind, and the files with their previous names.
1452
+ */
1453
+ async compareDetailed(base, head) {
1454
+ const r2 = await this.request("GET", `${this.repoPath}/compare/${encodeRef(base)}...${encodeRef(head)}`);
1455
+ return {
1456
+ behindBy: r2.behind_by,
1457
+ aheadBy: r2.ahead_by,
1458
+ mergeBase: r2.merge_base_commit?.sha ?? null,
1459
+ baseSha: r2.base_commit?.sha ?? null,
1460
+ baseTree: r2.base_commit?.commit?.tree?.sha ?? null,
1461
+ files: (r2.files ?? []).map((f) => ({ filename: f.filename, status: f.status, ...f.previous_filename ? { previous_filename: f.previous_filename } : {} }))
1462
+ };
1463
+ }
1464
+ /** A tree on top of `baseTree` from entries with their own file mode (blob sha, inline content, or sha null to delete). */
1465
+ async createTreeEntries(baseTree, entries) {
1466
+ const r2 = await this.request("POST", `${this.repoPath}/git/trees`, { base_tree: baseTree, tree: entries.map((e2) => ({ ...e2, type: "blob" })) }, true);
1467
+ return r2.sha;
1468
+ }
1375
1469
  }
1376
1470
  function toCommitSummary(c10) {
1377
1471
  return {
@@ -1515,7 +1609,7 @@ function importsIn(text) {
1515
1609
  for (const m2 of text.matchAll(IMPORT_RE)) out.add(m2[1] ?? m2[2]);
1516
1610
  return [...out];
1517
1611
  }
1518
- const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
1612
+ const norm$1 = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
1519
1613
  const isStory = (p) => /\.stories\.[cm]?[jt]sx?$/.test(p);
1520
1614
  const isTest = (p) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(p);
1521
1615
  const isCss = (p) => /\.(css|scss|less)$/.test(p);
@@ -1533,14 +1627,14 @@ function componentOf(from, spec, componentsDir) {
1533
1627
  return abs.slice(componentsDir.length + 1).split("/")[0] || null;
1534
1628
  }
1535
1629
  function componentForPath(path, componentsDir) {
1536
- const dir = norm(componentsDir);
1537
- const p = norm(path);
1630
+ const dir = norm$1(componentsDir);
1631
+ const p = norm$1(path);
1538
1632
  if (!p.startsWith(`${dir}/`)) return null;
1539
1633
  const rest = p.slice(dir.length + 1).split("/");
1540
1634
  return rest.length >= 2 ? rest[0] : null;
1541
1635
  }
1542
1636
  function buildUsageIndex(files, componentsDir) {
1543
- const dir = norm(componentsDir);
1637
+ const dir = norm$1(componentsDir);
1544
1638
  const modules = {};
1545
1639
  const vars = /* @__PURE__ */ new Map();
1546
1640
  const imports = /* @__PURE__ */ new Map();
@@ -1548,7 +1642,7 @@ function buildUsageIndex(files, componentsDir) {
1548
1642
  const globalVars = {};
1549
1643
  const get = (m2, k3) => m2.get(k3) ?? m2.set(k3, /* @__PURE__ */ new Set()).get(k3);
1550
1644
  for (const f of files) {
1551
- const path = norm(f.path);
1645
+ const path = norm$1(f.path);
1552
1646
  const id = componentForPath(path, dir);
1553
1647
  if (!id) {
1554
1648
  if (isCss(path)) {
@@ -1592,7 +1686,7 @@ function impactedModules(index, input) {
1592
1686
  for (const v2 of changedVars) for (const id of index.byVar[v2] ?? []) add(id, `uses ${v2}`, false);
1593
1687
  for (const f of input.files ?? []) {
1594
1688
  const id = componentForPath(f, index.componentsDir);
1595
- if (id && index.modules[id]) add(id, `file changed: ${norm(f)}`, false);
1689
+ if (id && index.modules[id]) add(id, `file changed: ${norm$1(f)}`, false);
1596
1690
  }
1597
1691
  const renderedBy = /* @__PURE__ */ new Map();
1598
1692
  for (const m2 of Object.values(index.modules)) for (const d2 of m2.imports) (renderedBy.get(d2) ?? renderedBy.set(d2, []).get(d2)).push(m2.id);
@@ -1605,7 +1699,7 @@ function impactedModules(index, input) {
1605
1699
  if (!known) queue.push(parent);
1606
1700
  }
1607
1701
  }
1608
- const globalHits = Object.entries(index.globalVars).map(([file, vs]) => ({ file, vars: vs.filter((v2) => changedVars.has(v2)) })).filter((g3) => g3.vars.length > 0);
1702
+ const globalHits = Object.entries(index.globalVars).map(([file, vs]) => ({ file, vars: vs.filter((v2) => changedVars.has(v2)) })).filter((g2) => g2.vars.length > 0);
1609
1703
  const modules = [...reasons.entries()].map(([id, r2]) => ({ id, reasons: r2.reasons, indirect: r2.indirect })).sort((a, b) => Number(a.indirect) - Number(b.indirect) || a.id.localeCompare(b.id));
1610
1704
  return { modules, globalHits };
1611
1705
  }
@@ -1708,8 +1802,8 @@ async function loadGitHubHistory(client, config, opts = {}) {
1708
1802
  const limit = opts.limit ?? 30;
1709
1803
  const backend = gitHubHistoryBackend(client, config);
1710
1804
  let listed = 0;
1711
- const counted = { ...backend, listCommits: async (o2) => {
1712
- const r2 = await backend.listCommits(o2);
1805
+ const counted = { ...backend, listCommits: async (o3) => {
1806
+ const r2 = await backend.listCommits(o3);
1713
1807
  listed = r2.length;
1714
1808
  return r2;
1715
1809
  } };
@@ -1721,10 +1815,10 @@ async function loadGitHubHistory(client, config, opts = {}) {
1721
1815
  const files = await backend.tree(head).catch(() => []);
1722
1816
  for (const dir of packageDirCandidates(config)) {
1723
1817
  const p = (f) => dir ? `${dir}/${f}` : f;
1724
- const cl = files.find((e2) => e2.path === p("CHANGELOG.md"));
1818
+ const cl2 = files.find((e2) => e2.path === p("CHANGELOG.md"));
1725
1819
  const pkg = files.find((e2) => e2.path === p("package.json"));
1726
- if (!cl && !pkg) continue;
1727
- if (cl) changelog = parseChangelog(await client.getBlobText(cl.sha));
1820
+ if (!cl2 && !pkg) continue;
1821
+ if (cl2) changelog = parseChangelog(await client.getBlobText(cl2.sha));
1728
1822
  if (pkg) {
1729
1823
  try {
1730
1824
  const json = JSON.parse(await client.getBlobText(pkg.sha));
@@ -1846,10 +1940,10 @@ function oklch(L, C, h3) {
1846
1940
  return lin.map(encode);
1847
1941
  }
1848
1942
  function toOklch(rgb) {
1849
- const [r2, g3, b] = rgb.map(decode);
1850
- const l2 = Math.cbrt(0.4122214708 * r2 + 0.5363325363 * g3 + 0.0514459929 * b);
1851
- const m2 = Math.cbrt(0.2119034982 * r2 + 0.6806995451 * g3 + 0.1073969566 * b);
1852
- const s2 = Math.cbrt(0.0883024619 * r2 + 0.2817188376 * g3 + 0.6299787005 * b);
1943
+ const [r2, g2, b] = rgb.map(decode);
1944
+ const l2 = Math.cbrt(0.4122214708 * r2 + 0.5363325363 * g2 + 0.0514459929 * b);
1945
+ const m2 = Math.cbrt(0.2119034982 * r2 + 0.6806995451 * g2 + 0.1073969566 * b);
1946
+ const s2 = Math.cbrt(0.0883024619 * r2 + 0.2817188376 * g2 + 0.6299787005 * b);
1853
1947
  const L = 0.2104542553 * l2 + 0.793617785 * m2 - 0.0040720468 * s2;
1854
1948
  const A = 1.9779984951 * l2 - 2.428592205 * m2 + 0.4505937099 * s2;
1855
1949
  const B = 0.0259040371 * l2 + 0.7827717662 * m2 - 0.808675766 * s2;
@@ -1968,8 +2062,8 @@ function rampFor(input, pinStep, kind) {
1968
2062
  const hex = String(input).trim();
1969
2063
  if (!isHex(hex)) throw new Error(`"${hex}" is not a color. Use #rrggbb or a hue in degrees (0 to 360).`);
1970
2064
  if (kind === "neutral") {
1971
- const n3 = tintedNeutral(hex);
1972
- return { ramp: n3.ramp, anchor: `${hex.toUpperCase()} (hue ${Math.round(n3.hue)})` };
2065
+ const n2 = tintedNeutral(hex);
2066
+ return { ramp: n2.ramp, anchor: `${hex.toUpperCase()} (hue ${Math.round(n2.hue)})` };
1973
2067
  }
1974
2068
  const a = anchoredRamp(hex, pinStep);
1975
2069
  const issue = a.distance > PIN_TOLERANCE ? {
@@ -2183,8 +2277,8 @@ function planNewBrand(source, input) {
2183
2277
  const src = fill(layers.override, { brand: from, [layers.overrideAxis]: v2 });
2184
2278
  const text2 = files.get(src);
2185
2279
  if (!text2) continue;
2186
- const o2 = JSON.parse(text2);
2187
- const out = { ...o2, $description: `${name} overrides on top of semantic/${v2}, copied from ${brandDisplayName(from)}.` };
2280
+ const o3 = JSON.parse(text2);
2281
+ const out = { ...o3, $description: `${name} overrides on top of semantic/${v2}, copied from ${brandDisplayName(from)}.` };
2188
2282
  const dst = fill(layers.override, { brand: slug, [layers.overrideAxis]: v2 });
2189
2283
  nextFiles.set(dst, `${JSON.stringify(out, null, 2)}
2190
2284
  `);
@@ -2233,10 +2327,10 @@ function planNewBrand(source, input) {
2233
2327
  ...created.map((f) => ({ path: tokensPath(config, f), action: "create", content: nextFiles.get(f) })),
2234
2328
  { path: CONFIG_FILE, action: "modify", content: configText }
2235
2329
  ];
2236
- const cs = config.changesets ?? (config.changeset ? { dir: ".changeset", package: config.changeset.package } : void 0);
2237
- const changeset = cs ? {
2238
- path: `${cs.dir}/bridge-brand-${slug}.md`,
2239
- content: formatChangeset(cs.package, "minor", `Add the ${name} brand (\`${slug}\`), started from ${blank ? "a blank brand" : brandDisplayName(from)}.`, `- ${created.map((f) => `\`${tokensPath(config, f)}\``).join("\n- ")}
2330
+ const cs2 = config.changesets ?? (config.changeset ? { dir: ".changeset", package: config.changeset.package } : void 0);
2331
+ const changeset = cs2 ? {
2332
+ path: `${cs2.dir}/bridge-brand-${slug}.md`,
2333
+ content: formatChangeset(cs2.package, "minor", `Add the ${name} brand (\`${slug}\`), started from ${blank ? "a blank brand" : brandDisplayName(from)}.`, `- ${created.map((f) => `\`${tokensPath(config, f)}\``).join("\n- ")}
2240
2334
  - \`${CONFIG_FILE}\`: modes.brand gains "${slug}", so the token build and the contrast gate cover it.`)
2241
2335
  } : void 0;
2242
2336
  if (changeset) planned.push({ path: changeset.path, action: "create", content: changeset.content });
@@ -2311,7 +2405,7 @@ function newBrandSection(plan) {
2311
2405
  }
2312
2406
  function composeNewBrandBody(plan, opts) {
2313
2407
  const t3 = plan.risk.tier;
2314
- const box = (n3) => `- ${n3 === t3 ? "[x]" : "[ ]"} ${RISK_LABELS[n3]}`;
2408
+ const box = (n2) => `- ${n2 === t3 ? "[x]" : "[ ]"} ${RISK_LABELS[n2]}`;
2315
2409
  const errors = plan.issues.filter((i2) => i2.level === "error");
2316
2410
  const warnings = plan.issues.filter((i2) => i2.level === "warning");
2317
2411
  return [
@@ -2519,9 +2613,9 @@ function formatDocsJson(d2) {
2519
2613
  function patchNode(old, next) {
2520
2614
  if (plainEqual(toPlain(old), next)) return old;
2521
2615
  if (old.kind === "object" && next && typeof next === "object" && !Array.isArray(next)) {
2522
- const n3 = next;
2523
- const kept = old.entries.filter((e2) => n3[e2.key] !== void 0).map((e2) => ({ key: e2.key, value: patchNode(e2.value, n3[e2.key]) }));
2524
- const added = Object.keys(n3).filter((k3) => n3[k3] !== void 0 && !getEntry(old, k3)).map((k3) => ({ key: k3, value: fromPlain(n3[k3], void 0, old.inline) }));
2616
+ const n2 = next;
2617
+ const kept = old.entries.filter((e2) => n2[e2.key] !== void 0).map((e2) => ({ key: e2.key, value: patchNode(e2.value, n2[e2.key]) }));
2618
+ const added = Object.keys(n2).filter((k3) => n2[k3] !== void 0 && !getEntry(old, k3)).map((k3) => ({ key: k3, value: fromPlain(n2[k3], void 0, old.inline) }));
2525
2619
  return { kind: "object", inline: old.inline, entries: [...kept, ...added] };
2526
2620
  }
2527
2621
  if (old.kind === "array" && Array.isArray(next)) {
@@ -2550,7 +2644,7 @@ function cleanDraftText(text) {
2550
2644
  const values = (s2) => (s2 ?? "").split(", ").map((x) => x.trim()).filter(Boolean);
2551
2645
  const article = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
2552
2646
  const andList = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
2553
- const plural = (n3, word, many = `${word}s`) => `${n3} ${n3 === 1 ? word : many}`;
2647
+ const plural$1 = (n2, word, many = `${word}s`) => `${n2} ${n2 === 1 ? word : many}`;
2554
2648
  const capital = (s2) => s2.charAt(0).toUpperCase() + s2.slice(1);
2555
2649
  const lower = (s2) => s2.charAt(0).toLowerCase() + s2.slice(1);
2556
2650
  function propShape(s2) {
@@ -2597,7 +2691,7 @@ function firstChange(component, kind, c10, all) {
2597
2691
  case "new-component": {
2598
2692
  const axes = all.filter((x) => x.field.startsWith("Variant "));
2599
2693
  const props = all.filter((x) => x.field.startsWith("Prop "));
2600
- const parts = [axes.length ? plural(axes.length, "variant axis", "variant axes") : "", props.length ? plural(props.length, "prop") : ""].filter(Boolean);
2694
+ const parts = [axes.length ? plural$1(axes.length, "variant axis", "variant axes") : "", props.length ? plural$1(props.length, "prop") : ""].filter(Boolean);
2601
2695
  return {
2602
2696
  title: `Add the ${component} component`,
2603
2697
  summary: `${component} is not in code yet.${parts.length ? ` The Figma component has ${andList(parts)}.` : ""}`
@@ -2614,7 +2708,7 @@ function firstChange(component, kind, c10, all) {
2614
2708
  if (!before.length) return { title: `Add a ${axis} variant axis`, summary: `${component} gets a new variant axis, ${axis}, with ${after.length === 1 ? "the value" : "the values"} ${andList(after)}.` };
2615
2709
  if (!after.length) return { title: `Remove the ${axis} variant axis`, summary: `${component} no longer has the ${axis} variant axis (${andList(before)}).` };
2616
2710
  if (added.length === 1 && !removed.length) return { title: `Add ${article(added[0])} ${added[0]} ${word}`, summary: `${component} gets a new ${axis} value, ${added[0]}.` };
2617
- if (added.length && !removed.length) return { title: `Add ${andList(added)} to ${axis}`, summary: `${component} gets ${plural(added.length, `new ${axis} value`)}: ${andList(added)}.` };
2711
+ if (added.length && !removed.length) return { title: `Add ${andList(added)} to ${axis}`, summary: `${component} gets ${plural$1(added.length, `new ${axis} value`)}: ${andList(added)}.` };
2618
2712
  if (removed.length === 1 && !added.length) return { title: `Remove the ${removed[0]} ${word}`, summary: `${component} no longer has the ${axis} value ${removed[0]}.` };
2619
2713
  if (removed.length && !added.length) return { title: `Remove ${andList(removed)} from ${axis}`, summary: `${component} no longer has the ${axis} values ${andList(removed)}.` };
2620
2714
  return { title: `Change the ${axis} values`, summary: `${axis} on ${component} goes from ${andList(before)} to ${andList(after)}.` };
@@ -2666,7 +2760,7 @@ function draftRequestText(component, kind, changes) {
2666
2760
  if (!changes.length && kind !== "new-component") return { title: "", summary: "" };
2667
2761
  const d2 = firstChange(name, kind, changes[0], changes);
2668
2762
  const rest = kind === "new-component" ? 0 : changes.length - 1;
2669
- const summary = rest > 0 ? `${d2.summary} ${capital(plural(rest, "other change"))} ${rest === 1 ? "is" : "are"} listed below.` : d2.summary;
2763
+ const summary = rest > 0 ? `${d2.summary} ${capital(plural$1(rest, "other change"))} ${rest === 1 ? "is" : "are"} listed below.` : d2.summary;
2670
2764
  return { title: cleanDraftText(d2.title), summary: cleanDraftText(summary) };
2671
2765
  }
2672
2766
  const DRAFT_LINE_MAX = 120;
@@ -2719,7 +2813,7 @@ function draftChangeLine(kind, changes) {
2719
2813
  if (kind === "new-component") {
2720
2814
  const axes = changes.filter((x) => x.field.startsWith("Variant ")).length;
2721
2815
  const props = changes.filter((x) => x.field.startsWith("Prop ")).length;
2722
- const parts = [axes ? plural(axes, "variant axis", "variant axes") : "", props ? plural(props, "prop") : ""].filter(Boolean);
2816
+ const parts = [axes ? plural$1(axes, "variant axis", "variant axes") : "", props ? plural$1(props, "prop") : ""].filter(Boolean);
2723
2817
  return `New component${parts.length ? ` with ${andList(parts)}` : ""}, not in code yet`;
2724
2818
  }
2725
2819
  if (!changes.length) return "";
@@ -2744,7 +2838,7 @@ function pushChangeRows(entries) {
2744
2838
  return { field: renamed ? `${before.path} -> ${after.path}` : pathOf(e2), before: show(before), after: show(after) };
2745
2839
  });
2746
2840
  }
2747
- function names(paths) {
2841
+ function names$1(paths) {
2748
2842
  if (paths.length <= 3) return andList(paths);
2749
2843
  return `${paths.length} tokens, including ${andList(paths.slice(0, 2))}`;
2750
2844
  }
@@ -2761,15 +2855,15 @@ function draftPushDescription(entries, components = []) {
2761
2855
  const modes = only ? valueModes(only) : [];
2762
2856
  if (only && fields.size === 1 && fields.has("description")) clauses.push(`updates the description of ${pathOf(only)}`);
2763
2857
  else if (only && modes.length) clauses.push(`changes ${pathOf(only)} in ${andList(modes)}`);
2764
- else clauses.push(`changes ${names(by2.changed.map(pathOf))}`);
2858
+ else clauses.push(`changes ${names$1(by2.changed.map(pathOf))}`);
2765
2859
  }
2766
- if (by2.added.length) clauses.push(`adds ${names(by2.added.map(pathOf))}`);
2860
+ if (by2.added.length) clauses.push(`adds ${names$1(by2.added.map(pathOf))}`);
2767
2861
  if (by2.renamed.length) {
2768
2862
  const r2 = by2.renamed[0];
2769
2863
  const from = (r2.head ?? r2.base)?.path;
2770
- clauses.push(by2.renamed.length === 1 && from && from !== pathOf(r2) ? `renames ${from} to ${pathOf(r2)}` : `renames ${names(by2.renamed.map(pathOf))}`);
2864
+ clauses.push(by2.renamed.length === 1 && from && from !== pathOf(r2) ? `renames ${from} to ${pathOf(r2)}` : `renames ${names$1(by2.renamed.map(pathOf))}`);
2771
2865
  }
2772
- if (by2.removed.length) clauses.push(`removes ${names(by2.removed.map(pathOf))}`);
2866
+ if (by2.removed.length) clauses.push(`removes ${names$1(by2.removed.map(pathOf))}`);
2773
2867
  const sentences2 = [];
2774
2868
  if (clauses.length) sentences2.push(`${capital(andList(clauses))}.`);
2775
2869
  const fresh = components.filter((c10) => c10.kind === "new-component").map((c10) => c10.component);
@@ -2846,10 +2940,10 @@ const cleanDocsText = (s2) => cleanDraftText(s2.replace(/[\r\n]+/g, " "));
2846
2940
  const isExample = (x) => {
2847
2941
  if (typeof x === "string") return true;
2848
2942
  if (!x || typeof x !== "object" || Array.isArray(x)) return false;
2849
- const n3 = x;
2850
- if (typeof n3.c !== "string" || !n3.c) return false;
2851
- if (n3.p !== void 0 && (!n3.p || typeof n3.p !== "object" || Array.isArray(n3.p))) return false;
2852
- return n3.children === void 0 || Array.isArray(n3.children) && n3.children.every(isExample);
2943
+ const n2 = x;
2944
+ if (typeof n2.c !== "string" || !n2.c) return false;
2945
+ if (n2.p !== void 0 && (!n2.p || typeof n2.p !== "object" || Array.isArray(n2.p))) return false;
2946
+ return n2.children === void 0 || Array.isArray(n2.children) && n2.children.every(isExample);
2853
2947
  };
2854
2948
  const textList = (x) => {
2855
2949
  if (typeof x === "string") x = [x];
@@ -2860,9 +2954,9 @@ const textList = (x) => {
2860
2954
  const guidanceList = (x, examples) => {
2861
2955
  if (!Array.isArray(x)) return void 0;
2862
2956
  const out = [];
2863
- for (const g3 of x) {
2864
- if (!g3 || typeof g3 !== "object") continue;
2865
- const r2 = g3;
2957
+ for (const g2 of x) {
2958
+ if (!g2 || typeof g2 !== "object") continue;
2959
+ const r2 = g2;
2866
2960
  const title = typeof r2.title === "string" ? cleanDocsText(r2.title) : "";
2867
2961
  const text = typeof r2.text === "string" ? cleanDocsText(r2.text) : "";
2868
2962
  if (!title || !text) continue;
@@ -2880,8 +2974,8 @@ function sanitizeDocsSections(raw, keys = PROSE_KEYS, opts = {}) {
2880
2974
  const s2 = typeof v2 === "string" ? cleanDocsText(v2) : "";
2881
2975
  if (s2) out.summary = s2;
2882
2976
  } else if (k3 === "dos" || k3 === "donts") {
2883
- const g3 = guidanceList(v2, opts.examples ?? false);
2884
- if (g3) out[k3] = g3;
2977
+ const g2 = guidanceList(v2, opts.examples ?? false);
2978
+ if (g2) out[k3] = g2;
2885
2979
  } else {
2886
2980
  const l2 = textList(v2);
2887
2981
  if (l2) out[k3] = l2;
@@ -3013,8 +3107,8 @@ function explorationNote(e2) {
3013
3107
  ].join("\n");
3014
3108
  }
3015
3109
  function explorationPrTitle(e2) {
3016
- const n3 = e2.name.trim().replace(/\.$/, "");
3017
- return `Exploration: ${n3.charAt(0).toLowerCase()}${n3.slice(1)}`;
3110
+ const n2 = e2.name.trim().replace(/\.$/, "");
3111
+ return `Exploration: ${n2.charAt(0).toLowerCase()}${n2.slice(1)}`;
3018
3112
  }
3019
3113
  function explorationPrBody(e2, ctx) {
3020
3114
  const slug = e2.slug || explorationSlug(e2.name);
@@ -3088,7 +3182,7 @@ function promotionPlan(input) {
3088
3182
  const addLabels = [`tier: ${risk.tier}`, ...risk.tier === 3 && tokens ? ["tokens"] : []];
3089
3183
  const date = (input.date ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3090
3184
  const existing = new Set(input.body ? [...input.body.matchAll(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi)].map((m2) => Number(m2[1])) : []);
3091
- const closes = [...new Set(input.requests ?? [])].filter((n3) => !existing.has(n3));
3185
+ const closes = [...new Set(input.requests ?? [])].filter((n2) => !existing.has(n2));
3092
3186
  const kept = (input.body ?? "").replace(/^> Not mergeable as is\..*$\n?\n?/m, "").replace(/### When it is done[\s\S]*?(?=<!-- |$)/, "").replace(/^## Exploration: /m, "## ").trim();
3093
3187
  const body = [
3094
3188
  `**Promoted from an exploration on ${date}.** Review it like any change: the tier below comes from the files it touches.`,
@@ -3097,7 +3191,7 @@ function promotionPlan(input) {
3097
3191
  "",
3098
3192
  `**Risk tier:** ${risk.tier}.`,
3099
3193
  ...risk.reasons.slice(0, 8).map((r2) => `- ${r2}`),
3100
- ...closes.length ? ["", ...closes.map((n3) => `Closes #${n3}`)] : []
3194
+ ...closes.length ? ["", ...closes.map((n2) => `Closes #${n2}`)] : []
3101
3195
  ].join("\n");
3102
3196
  const t3 = title.replace(/^Exploration:\s*/i, "").trim() || exploration?.name || title;
3103
3197
  return {
@@ -3208,8 +3302,8 @@ function declineReasonProblems(reason) {
3208
3302
  return [];
3209
3303
  }
3210
3304
  function declineComment(reason, by2) {
3211
- const quote = reason.trim().split("\n").map((l2) => `> ${l2}`.trimEnd()).join("\n");
3212
- return [`Declined${by2 ? ` by @${by2.replace(/^@/, "")}` : ""} in Bridge Studio.`, "", quote, "", "<sub>Closed as not planned. Reopen the issue if things change.</sub>"].join("\n");
3305
+ const quote2 = reason.trim().split("\n").map((l2) => `> ${l2}`.trimEnd()).join("\n");
3306
+ return [`Declined${by2 ? ` by @${by2.replace(/^@/, "")}` : ""} in Bridge Studio.`, "", quote2, "", "<sub>Closed as not planned. Reopen the issue if things change.</sub>"].join("\n");
3213
3307
  }
3214
3308
  function requestStatus(issue) {
3215
3309
  const prs = issue.linkedPrs ?? [];
@@ -3347,8 +3441,8 @@ function validatePendingDrafts(x) {
3347
3441
  if (p.source !== "figma" && p.source !== "studio") out.push('source must be "figma" or "studio".');
3348
3442
  if (!Array.isArray(p.items) || !p.items.length) out.push("A draft session needs at least one item.");
3349
3443
  const ids = /* @__PURE__ */ new Set();
3350
- for (const [n3, i2] of (Array.isArray(p.items) ? p.items : []).entries()) {
3351
- const at2 = `Item ${n3 + 1}`;
3444
+ for (const [n2, i2] of (Array.isArray(p.items) ? p.items : []).entries()) {
3445
+ const at2 = `Item ${n2 + 1}`;
3352
3446
  if (!i2 || typeof i2 !== "object") {
3353
3447
  out.push(`${at2} is not an object.`);
3354
3448
  continue;
@@ -3580,8 +3674,8 @@ async function createDocsDraftSession(client, input) {
3580
3674
  const DRAFT_POLL_INTERVAL_MS = 5e3;
3581
3675
  const DRAFT_POLL_TIMEOUT_MS = 10 * 6e4;
3582
3676
  class DraftPoller {
3583
- constructor(o2) {
3584
- this.o = o2;
3677
+ constructor(o3) {
3678
+ this.o = o3;
3585
3679
  }
3586
3680
  o;
3587
3681
  handle;
@@ -3759,36 +3853,36 @@ async function closeExplorationOnGitHub(gh, ref) {
3759
3853
  await gh.updatePullRequest(p.number, { state: "closed" });
3760
3854
  return { number: p.number, url: p.url, branch: p.headRef };
3761
3855
  }
3762
- async function linkRequestToExplorationOnGitHub(gh, n3, branch, opts = {}) {
3856
+ async function linkRequestToExplorationOnGitHub(gh, n2, branch, opts = {}) {
3763
3857
  if (!isExplorationBranch(branch)) throw new GitHubError(422, `"${branch}" is not an exploration branch. Exploration branches are named explore/<name>.`);
3764
- const issue = await gh.getIssue(n3);
3858
+ const issue = await gh.getIssue(n2);
3765
3859
  const body = withRequestExploration(issue.body, branch);
3766
- if (!body) throw new GitHubError(422, `#${n3} has no Bridge request block, so it cannot be linked. Add "exploration: ${branch}" to it on GitHub.`);
3767
- await gh.updateIssue(n3, { body });
3860
+ if (!body) throw new GitHubError(422, `#${n2} has no Bridge request block, so it cannot be linked. Add "exploration: ${branch}" to it on GitHub.`);
3861
+ await gh.updateIssue(n2, { body });
3768
3862
  const warnings = [];
3769
- if (!await gh.addLabels(n3, [EXPLORATION_LABEL])) warnings.push(`The ${EXPLORATION_LABEL} label could not be added to #${n3}.`);
3863
+ if (!await gh.addLabels(n2, [EXPLORATION_LABEL])) warnings.push(`The ${EXPLORATION_LABEL} label could not be added to #${n2}.`);
3770
3864
  let assignees = [];
3771
3865
  if (opts.assignee) {
3772
3866
  try {
3773
- assignees = await gh.addAssignees(n3, [opts.assignee.replace(/^@/, "")]);
3867
+ assignees = await gh.addAssignees(n2, [opts.assignee.replace(/^@/, "")]);
3774
3868
  } catch {
3775
- warnings.push(`@${opts.assignee.replace(/^@/, "")} could not be assigned to #${n3}.`);
3869
+ warnings.push(`@${opts.assignee.replace(/^@/, "")} could not be assigned to #${n2}.`);
3776
3870
  }
3777
3871
  }
3778
- return { number: n3, branch, assignees, warnings };
3872
+ return { number: n2, branch, assignees, warnings };
3779
3873
  }
3780
- async function assignRequestOnGitHub(gh, n3, login) {
3874
+ async function assignRequestOnGitHub(gh, n2, login) {
3781
3875
  const who = login.trim().replace(/^@/, "");
3782
3876
  if (!who) throw new GitHubError(422, "Pick who should work on it.");
3783
- const assignees = await gh.addAssignees(n3, [who]);
3877
+ const assignees = await gh.addAssignees(n2, [who]);
3784
3878
  if (!assignees.some((a) => a.toLowerCase() === who.toLowerCase())) throw new GitHubError(422, `GitHub did not assign @${who}: they need access to this repository.`);
3785
3879
  return assignees;
3786
3880
  }
3787
- async function declineRequestOnGitHub(gh, n3, reason, by2) {
3881
+ async function declineRequestOnGitHub(gh, n2, reason, by2) {
3788
3882
  const problems = declineReasonProblems(reason);
3789
3883
  if (problems.length) throw new GitHubError(422, problems.join(" "));
3790
- await gh.addComment(n3, declineComment(reason, by2));
3791
- await gh.updateIssue(n3, { state: "closed", state_reason: "not_planned" });
3884
+ await gh.addComment(n2, declineComment(reason, by2));
3885
+ await gh.updateIssue(n2, { state: "closed", state_reason: "not_planned" });
3792
3886
  }
3793
3887
  const FIDELITY_DEFAULTS = { scale: 2, threshold: 0.1, maxPercent: 3, sizeTolerance: 1 };
3794
3888
  const MAX_DELTA = 35215;
@@ -3805,8 +3899,8 @@ function colorDelta(a, b) {
3805
3899
  const db = a[2] - b[2];
3806
3900
  const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
3807
3901
  const i2 = dr * 0.59597799 - dg * 0.2741761 - db * 0.32180189;
3808
- const q2 = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;
3809
- return 0.5053 * y * y + 0.299 * i2 * i2 + 0.1957 * q2 * q2;
3902
+ const q3 = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;
3903
+ return 0.5053 * y * y + 0.299 * i2 * i2 + 0.1957 * q3 * q3;
3810
3904
  }
3811
3905
  function foundNear(img, x, y, c10, radius, max) {
3812
3906
  for (let dy = -radius; dy <= radius; dy++) for (let dx = -radius; dx <= radius; dx++) if (colorDelta(c10, rgbAt(img, x + dx, y + dy)) <= max) return true;
@@ -3831,16 +3925,16 @@ function compareImages(figma, code2, options = {}) {
3831
3925
  const same = colorDelta(a, b) <= m2 || foundNear(code2, x, y, a, radius, m2) && foundNear(figma, x, y, b, radius, m2);
3832
3926
  if (!same) different++;
3833
3927
  if (out) {
3834
- const o2 = (y * width + x) * 4;
3928
+ const o3 = (y * width + x) * 4;
3835
3929
  if (same) {
3836
3930
  const gray = 255 + (b[0] * 0.299 + b[1] * 0.587 + b[2] * 0.114 - 255) * 0.1;
3837
- out[o2] = out[o2 + 1] = out[o2 + 2] = gray;
3931
+ out[o3] = out[o3 + 1] = out[o3 + 2] = gray;
3838
3932
  } else {
3839
- out[o2] = 220;
3840
- out[o2 + 1] = 38;
3841
- out[o2 + 2] = 38;
3933
+ out[o3] = 220;
3934
+ out[o3 + 1] = 38;
3935
+ out[o3 + 2] = 38;
3842
3936
  }
3843
- out[o2 + 3] = 255;
3937
+ out[o3 + 3] = 255;
3844
3938
  }
3845
3939
  }
3846
3940
  }
@@ -3877,8 +3971,8 @@ function summarizeComponent(component, variants, reason) {
3877
3971
  }
3878
3972
  const sameSize = (s2) => s2.figma.width === s2.code.width && s2.figma.height === s2.code.height;
3879
3973
  function buildFidelityReport(input) {
3880
- const o2 = input.options ?? {};
3881
- const scale = o2.scale ?? FIDELITY_DEFAULTS.scale;
3974
+ const o3 = input.options ?? {};
3975
+ const scale = o3.scale ?? FIDELITY_DEFAULTS.scale;
3882
3976
  const c10 = input.components;
3883
3977
  return {
3884
3978
  version: 1,
@@ -3887,10 +3981,10 @@ function buildFidelityReport(input) {
3887
3981
  reference: input.reference,
3888
3982
  options: {
3889
3983
  scale,
3890
- threshold: o2.threshold ?? FIDELITY_DEFAULTS.threshold,
3891
- radius: Math.round(o2.radius ?? scale),
3892
- maxPercent: o2.maxPercent ?? FIDELITY_DEFAULTS.maxPercent,
3893
- sizeTolerance: o2.sizeTolerance ?? FIDELITY_DEFAULTS.sizeTolerance
3984
+ threshold: o3.threshold ?? FIDELITY_DEFAULTS.threshold,
3985
+ radius: Math.round(o3.radius ?? scale),
3986
+ maxPercent: o3.maxPercent ?? FIDELITY_DEFAULTS.maxPercent,
3987
+ sizeTolerance: o3.sizeTolerance ?? FIDELITY_DEFAULTS.sizeTolerance
3894
3988
  },
3895
3989
  components: c10,
3896
3990
  summary: {
@@ -3898,8 +3992,8 @@ function buildFidelityReport(input) {
3898
3992
  matches: c10.filter((x) => x.status === "matches").length,
3899
3993
  differs: c10.filter((x) => x.status === "differs").length,
3900
3994
  notChecked: c10.filter((x) => x.status === "not-checked").length,
3901
- variantsChecked: c10.reduce((n3, x) => n3 + x.checked, 0),
3902
- variantsSkipped: c10.reduce((n3, x) => n3 + x.skipped, 0)
3995
+ variantsChecked: c10.reduce((n2, x) => n2 + x.checked, 0),
3996
+ variantsSkipped: c10.reduce((n2, x) => n2 + x.skipped, 0)
3903
3997
  }
3904
3998
  };
3905
3999
  }
@@ -4044,32 +4138,755 @@ function planReferences(file) {
4044
4138
  (spec) => spec.variants.map((v2) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v2.props), plan: planReference(spec, v2, file) }))
4045
4139
  );
4046
4140
  }
4141
+ function parseTier(labels, body) {
4142
+ const found = [];
4143
+ for (const l2 of labels) {
4144
+ const m2 = l2.name.match(/^(?:risk[\s:-]*)?tier[\s:_-]*([123])\b/i) ?? l2.name.match(/^risk[\s:_-]*([123])$/i);
4145
+ if (m2) found.push(Number(m2[1]));
4146
+ }
4147
+ for (const m2 of (body ?? "").matchAll(/-\s*\[[xX]\]\s*\**\s*Tier\s*([123])\b/g)) found.push(Number(m2[1]));
4148
+ for (const m2 of (body ?? "").matchAll(/risk tier\W{0,6}(?:tier\s*)?([123])\b/gi)) found.push(Number(m2[1]));
4149
+ return found.length ? Math.max(...found) : null;
4150
+ }
4151
+ function pullState(p) {
4152
+ if (p.merged_at || p.merged) return "merged";
4153
+ if (p.state && p.state !== "open") return "closed";
4154
+ if (p.draft) return "draft";
4155
+ return "open";
4156
+ }
4157
+ const PULL_STATE_PILL = {
4158
+ merged: { label: "Merged", cls: "" },
4159
+ closed: { label: "Closed", cls: "" },
4160
+ draft: { label: "Draft", cls: "" },
4161
+ open: { label: "Open", cls: "" }
4162
+ };
4163
+ function openPulls(pulls) {
4164
+ return pulls.filter((p) => {
4165
+ const s2 = pullState(p);
4166
+ return s2 === "open" || s2 === "draft";
4167
+ });
4168
+ }
4169
+ const OK = /* @__PURE__ */ new Set(["success", "neutral", "skipped"]);
4170
+ function summarizeCi(runs, statuses = []) {
4171
+ const failed = [];
4172
+ const pending = [];
4173
+ let passed = 0;
4174
+ for (const r2 of runs) {
4175
+ if (r2.status !== "completed") pending.push({ name: r2.name, url: r2.html_url });
4176
+ else if (r2.conclusion && OK.has(r2.conclusion)) passed++;
4177
+ else failed.push({ name: r2.name, url: r2.html_url });
4178
+ }
4179
+ const seen = /* @__PURE__ */ new Set();
4180
+ for (const s2 of statuses) {
4181
+ if (seen.has(s2.context)) continue;
4182
+ seen.add(s2.context);
4183
+ if (s2.state === "success") passed++;
4184
+ else if (s2.state === "pending") pending.push({ name: s2.context, url: s2.target_url ?? void 0 });
4185
+ else failed.push({ name: s2.context, url: s2.target_url ?? void 0 });
4186
+ }
4187
+ const total = passed + failed.length + pending.length;
4188
+ const state = total === 0 ? "none" : failed.length ? "fail" : pending.length ? "pending" : "pass";
4189
+ return { state, total, passed, failed, pending };
4190
+ }
4191
+ function summarizeApprovals(reviews, owners, author) {
4192
+ const latest = /* @__PURE__ */ new Map();
4193
+ const sorted = [...reviews].sort((a2, b) => String(a2.submitted_at ?? "").localeCompare(String(b.submitted_at ?? "")));
4194
+ for (const r2 of sorted) {
4195
+ const login = r2.user?.login;
4196
+ if (!login || login === author) continue;
4197
+ if (r2.state === "APPROVED" || r2.state === "CHANGES_REQUESTED" || r2.state === "DISMISSED") latest.set(login, r2.state);
4198
+ }
4199
+ const approvers = [...latest].filter(([, s2]) => s2 === "APPROVED").map(([l2]) => l2);
4200
+ const changesRequested = [...latest].filter(([, s2]) => s2 === "CHANGES_REQUESTED").map(([l2]) => l2);
4201
+ const lower2 = (xs) => xs.map((x) => x.toLowerCase().replace(/^@/, ""));
4202
+ const d2 = lower2(owners.design);
4203
+ const t3 = lower2(owners.tech);
4204
+ const a = lower2(approvers);
4205
+ let design;
4206
+ let tech;
4207
+ if (d2.length || t3.length) {
4208
+ design = d2.length ? a.some((x) => d2.includes(x)) : a.length > 0;
4209
+ const soloBoth = (x) => a.length === 1 && d2.includes(x);
4210
+ tech = t3.length ? a.some((x) => t3.includes(x) && !soloBoth(x)) : a.length > 1;
4211
+ } else {
4212
+ design = a.length >= 1;
4213
+ tech = a.length >= 2;
4214
+ }
4215
+ return { approvers, changesRequested, design, tech };
4216
+ }
4217
+ const normLogin = (x) => x.trim().toLowerCase().replace(/^@/, "");
4218
+ function canOverride(login, policy) {
4219
+ if (!login || !policy?.allowed.length) return false;
4220
+ return policy.allowed.map(normLogin).includes(normLogin(login));
4221
+ }
4222
+ function mergeGate(input) {
4223
+ const blocking = [];
4224
+ const missing = [];
4225
+ const approvalReasons = [];
4226
+ const lifecycle = pullState(input);
4227
+ if (lifecycle === "merged") blocking.push("This branch is already merged.");
4228
+ else if (lifecycle === "closed") blocking.push("This branch is closed.");
4229
+ if (input.exploration && lifecycle !== "merged" && lifecycle !== "closed") blocking.push("It is an exploration: promote it to review first.");
4230
+ if (input.draft) blocking.push("It is a draft. Mark it ready for review on GitHub first.");
4231
+ if (input.ci.state === "fail") blocking.push(`Tests fail: ${input.ci.failed.map((f) => f.name).join(", ")}.`);
4232
+ if (input.ci.state === "pending") blocking.push("Tests are still running.");
4233
+ if (input.approvals.changesRequested.length) blocking.push(`Changes requested by @${input.approvals.changesRequested.join(", @")}.`);
4234
+ const tier = input.tier ?? 3;
4235
+ if (tier >= 2) {
4236
+ if (!input.approvals.design) {
4237
+ approvalReasons.push("It needs a design owner approval.");
4238
+ missing.push("design approval");
4239
+ }
4240
+ if (!input.approvals.tech) {
4241
+ approvalReasons.push("It needs a tech owner approval.");
4242
+ missing.push("tech approval");
4243
+ }
4244
+ } else if (!input.approvals.approvers.length) {
4245
+ approvalReasons.push("It needs one approval.");
4246
+ missing.push("approval");
4247
+ }
4248
+ const conflict = input.mergeable === false ? [CONFLICT_REASON] : [];
4249
+ const shipper = !input.viewer || canShip(input.viewer, input.ship);
4250
+ const ship = !shipper && lifecycle !== "merged" && lifecycle !== "closed" ? [whoCanShipSentence(input.ship), ...unreadableShipTeams(input.ship).map((t3) => t3.status === "unreadable" ? t3.message : "")] : [];
4251
+ const reasons = [...blocking, ...approvalReasons, ...ship, ...conflict];
4252
+ const allowed = reasons.length === 0;
4253
+ const overridable = !allowed && blocking.length + conflict.length + ship.length === 0 && missing.length > 0 && canOverride(input.viewer, input.override);
4254
+ return { allowed, reasons, overridable, missing, canShip: shipper };
4255
+ }
4256
+ const CONFLICT_REASON = "It conflicts with main: resolve it with Update from main on the branch.";
4257
+ const OVERRIDE_HEADING = "**Bridge owner override**";
4258
+ function overrideReasonProblem(reason, policy) {
4259
+ if (!policy.requireReason) return null;
4260
+ const n2 = reason.trim().length;
4261
+ if (n2 === 0) return `Write why this merges without the missing approvals, in at least ${policy.minReasonLength} characters.`;
4262
+ if (n2 < policy.minReasonLength) return `Write at least ${policy.minReasonLength} characters (${n2} so far), so the history says why.`;
4263
+ return null;
4264
+ }
4265
+ function overrideComment(login, reason, missing) {
4266
+ const why = reason.trim().replace(/\s+/g, " ").replace(/[.\s]+$/, "");
4267
+ return `${OVERRIDE_HEADING}
4268
+
4269
+ Owner override by @${normLogin(login)}: ${why || "no reason given"}. Missing: ${missing.join(", ")}. Merged from Bridge.`;
4270
+ }
4271
+ function overrideRefusedComment(body, message) {
4272
+ return body.replace(/ Merged from Bridge\.$/, ` Merge refused by GitHub: ${message.trim().replace(/[.\s]+$/, "")}. Not merged.`);
4273
+ }
4274
+ function parseOverrideComment(body) {
4275
+ const text = (body ?? "").trim();
4276
+ if (!text.startsWith(OVERRIDE_HEADING)) return null;
4277
+ const m2 = text.match(/Owner override by @([\w-]+): ([\s\S]*?)\. Missing: ([^.]*)\. (Merged from Bridge\.|Merge refused by GitHub)/);
4278
+ if (!m2) return null;
4279
+ return { login: m2[1], reason: m2[2], missing: m2[3].split(/,\s*/).filter(Boolean), merged: m2[4].startsWith("Merged") };
4280
+ }
4281
+ function allowedMergeMethods(repo) {
4282
+ const out = [];
4283
+ if (repo?.allow_squash_merge !== false) out.push("squash");
4284
+ if (repo?.allow_merge_commit !== false) out.push("merge");
4285
+ if (repo?.allow_rebase_merge !== false) out.push("rebase");
4286
+ return out;
4287
+ }
4288
+ function hasWriteAccess(perms) {
4289
+ return Boolean(perms && (perms.admin || perms.maintain || perms.push));
4290
+ }
4291
+ const REVIEW_STATUS_ORDER = {
4292
+ "ci-failing": 0,
4293
+ conflict: 1,
4294
+ "changes-requested": 2,
4295
+ "needs-design": 3,
4296
+ "needs-tech": 4,
4297
+ "needs-approval": 5,
4298
+ ready: 6,
4299
+ "ci-running": 7,
4300
+ checking: 8,
4301
+ unknown: 9,
4302
+ draft: 10,
4303
+ merged: 11,
4304
+ closed: 12
4305
+ };
4306
+ function reviewStatusKind(input) {
4307
+ const out = (kind) => kind;
4308
+ const lifecycle = pullState(input);
4309
+ if (lifecycle === "merged") return out("merged");
4310
+ if (lifecycle === "closed") return out("closed");
4311
+ if (lifecycle === "draft") return out("draft");
4312
+ if (input.ci?.state === "fail") return out("ci-failing");
4313
+ if (input.approvals?.changesRequested.length) return out("changes-requested");
4314
+ if (input.mergeable === false) return out("conflict");
4315
+ if (input.ci?.state === "pending") return out("ci-running");
4316
+ if (!input.ci || !input.approvals) return out("unknown");
4317
+ if (input.mergeable === null) return out("checking");
4318
+ const tier = input.tier ?? 3;
4319
+ if (tier >= 2) {
4320
+ if (!input.approvals.design) return out("needs-design");
4321
+ if (!input.approvals.tech) return out("needs-tech");
4322
+ } else if (!input.approvals.approvers.length) return out("needs-approval");
4323
+ const gate = mergeGate({ tier: input.tier, ci: input.ci, approvals: input.approvals, draft: Boolean(input.draft), mergeable: input.mergeable, state: input.state ?? "open", merged_at: input.merged_at });
4324
+ return gate.allowed ? out("ready") : out("unknown");
4325
+ }
4326
+ const BOTS = /* @__PURE__ */ new Set(["github-actions[bot]", "app/github-actions", "changeset-bot[bot]"]);
4327
+ const isReleasePull = (p) => p.head.ref.startsWith("changeset-release/");
4328
+ function isAutomationPull(p) {
4329
+ return isReleasePull(p) || BOTS.has((p.user?.login ?? "").toLowerCase());
4330
+ }
4331
+ const runningExploration = (p) => p.state === "open" && isActiveExploration(p.labels ?? []);
4332
+ function reviewQueue(pulls) {
4333
+ return openPulls(pulls).filter((p) => !isAutomationPull(p) && !runningExploration(p));
4334
+ }
4335
+ function releasePull(pulls) {
4336
+ return openPulls(pulls).find((p) => isReleasePull(p)) ?? null;
4337
+ }
4338
+ function effectiveTier(labels, body, files) {
4339
+ const labelled = parseTier(labels, body);
4340
+ if (!files) return labelled;
4341
+ const computed = classifyRisk({ files: files.map((f) => ({ path: f.filename, status: f.status })) }).tier;
4342
+ return Math.max(labelled ?? 0, computed);
4343
+ }
4344
+ const norm = (x) => x.trim().toLowerCase().replace(/^@/, "");
4345
+ function reopenEligibility(x) {
4346
+ if (x.state !== "closed" || !x.viewer) return { kind: "none" };
4347
+ const isAuthor = norm(x.viewer) === norm(x.author);
4348
+ const isOwner = x.owners.some((o3) => norm(o3) === norm(x.viewer));
4349
+ if (!isAuthor && !isOwner) return { kind: "none" };
4350
+ if (!x.canWrite && !isAuthor) return { kind: "none" };
4351
+ if (x.branch === "exists") return { kind: "reopen" };
4352
+ if (x.branch === "gone") return { kind: "gone" };
4353
+ return { kind: "none" };
4354
+ }
4355
+ const NO_CI = { state: "pass", total: 0, passed: 0, failed: [], pending: [] };
4356
+ const ROLE_OF_MISSING = { "design approval": "design", "tech approval": "tech", approval: "approval" };
4357
+ function requiredApprovals(tier) {
4358
+ return mergeGate({ tier, ci: NO_CI, approvals: { approvers: [], changesRequested: [], design: false, tech: false }, draft: false, state: "open" }).missing.map((m2) => ROLE_OF_MISSING[m2]);
4359
+ }
4360
+ function pendingApprovals(tier, approvals) {
4361
+ if (tier == null || !approvals) return [];
4362
+ return mergeGate({ tier, ci: NO_CI, approvals, draft: false, state: "open" }).missing.map((m2) => ROLE_OF_MISSING[m2]);
4363
+ }
4364
+ const uniqLogins = (xs) => xs.filter((x, i2) => xs.findIndex((y) => normLogin(y) === normLogin(x)) === i2);
4365
+ function eligibleReviewers(role, p) {
4366
+ const raw = role === "design" ? p.owners.design : role === "tech" ? p.owners.tech : uniqLogins([...p.owners.design, ...p.owners.tech]);
4367
+ const listed = raw.map((x) => x.replace(/^@/, ""));
4368
+ const approvers = (p.approvers ?? []).map(normLogin);
4369
+ const logins = listed.filter((l2) => normLogin(l2) !== normLogin(p.author) && !approvers.includes(normLogin(l2)));
4370
+ return { open: listed.length === 0, logins, listed };
4371
+ }
4372
+ function reviewersToAsk(x) {
4373
+ const roles = pendingApprovals(x.tier, x.approvals);
4374
+ const all = roles.flatMap((r2) => eligibleReviewers(r2, { owners: x.owners, author: x.author, approvers: x.approvals.approvers }).logins);
4375
+ return uniqLogins(all).filter((l2) => normLogin(l2) !== normLogin(x.viewer ?? "") && normLogin(l2) !== normLogin(x.author));
4376
+ }
4377
+ const plural = (n2, one, many = `${one}s`) => `${n2} ${n2 === 1 ? one : many}`;
4378
+ const names = (files) => files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
4379
+ function overlap(changeFiles, libraryFiles, isTokenFile) {
4380
+ const lib = new Set(names(libraryFiles));
4381
+ const both = [...new Set(names(changeFiles).filter((f) => lib.has(f)))].sort();
4382
+ return { tokenFiles: both.filter(isTokenFile), codeFiles: both.filter((f) => !isTokenFile(f)) };
4383
+ }
4384
+ const COMPARE_FILE_LIMIT = 300;
4385
+ function conflictRoute(o3, opts = {}) {
4386
+ if (opts.truncated || !o3.tokenFiles.length && !o3.codeFiles.length) return { kind: "code", alsoTokens: false, files: [] };
4387
+ if (o3.codeFiles.length) return { kind: "code", alsoTokens: o3.tokenFiles.length > 0, files: o3.codeFiles };
4388
+ return { kind: "tokens", files: o3.tokenFiles };
4389
+ }
4390
+ function dismissStaleFrom(rules) {
4391
+ const pr = (rules ?? []).filter((r2) => r2.type === "pull_request");
4392
+ if (!pr.length) return null;
4393
+ return pr.some((r2) => r2.parameters?.dismiss_stale_reviews_on_push === true);
4394
+ }
4395
+ function resolutionMessage(input) {
4396
+ const lines = input.conflicts.map((c10) => `- ${c10.path} (${c10.file}): ${choiceFor(c10, input.choices) === "theirs" ? `used the ${input.library} value` : "kept this change"}`);
4397
+ const title = input.conflicts.length ? `Bring in the latest ${input.library} and resolve ${plural(input.conflicts.length, "token conflict")}` : `Bring in the latest ${input.library}`;
4398
+ const by2 = input.who ? `
4399
+
4400
+ Resolved in Bridge by @${input.who.replace(/^@/, "")}.` : "\n\nResolved in Bridge.";
4401
+ return `${title}${lines.length ? `
4402
+
4403
+ ${lines.join("\n")}` : ""}${by2}`;
4404
+ }
4405
+ function freshness(input) {
4406
+ const behindBy = typeof input.compare?.behind_by === "number" ? input.compare.behind_by : null;
4407
+ const mergeBase = input.compare?.merge_base_commit?.sha ?? null;
4408
+ if (!input.open) return { state: "current", behindBy, mergeBase };
4409
+ if (input.mergeable === false) return { state: "conflict", behindBy, mergeBase };
4410
+ if (behindBy === null) return { state: "unknown", behindBy, mergeBase };
4411
+ return { state: behindBy > 0 ? "behind" : "current", behindBy, mergeBase };
4412
+ }
4413
+ const withoutRoot = (root, p) => root && p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
4414
+ async function treeAt(gh, sha) {
4415
+ const t3 = await gh.getTree(await gh.getCommitTree(sha));
4416
+ if (t3.truncated) throw new Error("This repository is too big for Bridge to read in one go. A developer resolves the conflict on GitHub.");
4417
+ return new Map(t3.entries.filter((e2) => e2.type === "blob").map((e2) => [e2.path, e2]));
4418
+ }
4419
+ async function textAt(gh, sha, path) {
4420
+ return await gh.readFile(path, sha) ?? "";
4421
+ }
4422
+ async function tokenFilesAt(gh, sha, root, tree) {
4423
+ const prefix = root ? `${root}/` : "";
4424
+ const paths = [...tree.keys()].filter((p) => p.startsWith(prefix) && p.endsWith(".tokens.json"));
4425
+ return Object.fromEntries(await Promise.all(paths.map(async (p) => [withoutRoot(root, p), await textAt(gh, sha, p)])));
4426
+ }
4427
+ async function configAt(gh, sha, tree) {
4428
+ if (!tree.has("bridge.config.json")) return null;
4429
+ try {
4430
+ return loadConfig(await textAt(gh, sha, "bridge.config.json"));
4431
+ } catch {
4432
+ return null;
4433
+ }
4434
+ }
4435
+ async function readLibraryConflicts(gh, compare, headSha, opts = {}) {
4436
+ const { mergeBase, baseSha: mainSha, baseTree: mainTree } = compare;
4437
+ const changeFiles = compare.files;
4438
+ if (!mergeBase || !mainSha || !mainTree) return { route: conflictRoute({ tokenFiles: [], codeFiles: [] }) };
4439
+ const library = (await gh.compareDetailed(mergeBase, mainSha)).files;
4440
+ const truncated = changeFiles.length >= COMPARE_FILE_LIMIT || library.length >= COMPARE_FILE_LIMIT;
4441
+ const guess = opts.tokensRoot ?? DEFAULT_TOKEN_PATHS.root;
4442
+ const route = conflictRoute(
4443
+ overlap(changeFiles, library, (p) => p.endsWith(".tokens.json") && p.startsWith(`${guess}/`)),
4444
+ { truncated }
4445
+ );
4446
+ if (route.kind !== "tokens") return { route };
4447
+ const [baseTree, headTree, theirsTree] = await Promise.all([treeAt(gh, mergeBase), treeAt(gh, headSha), treeAt(gh, mainSha)]);
4448
+ const config = await configAt(gh, mainSha, theirsTree) ?? await configAt(gh, mergeBase, baseTree);
4449
+ const root = config?.tokens.root ?? guess;
4450
+ const options = config ? layoutOptions(config) : {};
4451
+ const checksPath = `${root}/${config?.tokens.contrastChecks ?? DEFAULT_TOKEN_PATHS.contrastChecks}`;
4452
+ const [baseFiles, mineFiles, theirsFiles, checks] = await Promise.all([
4453
+ tokenFilesAt(gh, mergeBase, root, baseTree),
4454
+ tokenFilesAt(gh, headSha, root, headTree),
4455
+ tokenFilesAt(gh, mainSha, root, theirsTree),
4456
+ theirsTree.has(checksPath) ? textAt(gh, mainSha, checksPath) : Promise.resolve("")
4457
+ ]);
4458
+ const input = { base: baseFiles, mine: mineFiles, theirs: theirsFiles, files: route.files.map((f) => withoutRoot(root, f)) };
4459
+ return {
4460
+ route,
4461
+ tokens: {
4462
+ root,
4463
+ plan: planTokenMerge(input),
4464
+ input,
4465
+ sets: { base: buildTokenSet(baseFiles, options), mine: buildTokenSet(mineFiles, options), theirs: buildTokenSet(theirsFiles, options) },
4466
+ mineFiles,
4467
+ theirsFiles,
4468
+ pairs: parseContrastChecks(checks || opts.contrastFallback),
4469
+ options
4470
+ },
4471
+ write: { mainSha, mainTree, headSha, headTree, changeFiles }
4472
+ };
4473
+ }
4474
+ function resolvedTokenFiles(data, choices) {
4475
+ const t3 = data.tokens;
4476
+ const merged = applyTokenMerge(t3.input, choices).files;
4477
+ const all = { ...t3.theirsFiles };
4478
+ for (const f of data.write.changeFiles) {
4479
+ for (const p of [f.previous_filename, f.filename]) {
4480
+ if (!p || !p.endsWith(".tokens.json") || !p.startsWith(`${t3.root}/`)) continue;
4481
+ const rel = withoutRoot(t3.root, p);
4482
+ if (rel in merged) continue;
4483
+ if (t3.mineFiles[rel] === void 0) delete all[rel];
4484
+ else all[rel] = t3.mineFiles[rel];
4485
+ }
4486
+ }
4487
+ for (const [rel, text] of Object.entries(merged)) {
4488
+ if (text === null) delete all[rel];
4489
+ else all[rel] = text;
4490
+ }
4491
+ return { all, merged };
4492
+ }
4493
+ async function commitLibraryResolution(gh, pull, data, choices, who) {
4494
+ const t3 = data.tokens;
4495
+ const w = data.write;
4496
+ if (!t3 || !w) throw new Error("Only a token conflict can be resolved in Bridge. A developer resolves code conflicts on GitHub.");
4497
+ if (w.headSha !== pull.head.sha) throw new Error("The branch moved while you were choosing. Read it again and resolve again.");
4498
+ const { merged } = resolvedTokenFiles(data, choices);
4499
+ const shared = new Set(data.route.files);
4500
+ const tree = [];
4501
+ for (const f of w.changeFiles) {
4502
+ if (shared.has(f.filename)) continue;
4503
+ if (f.previous_filename && !shared.has(f.previous_filename)) tree.push({ path: f.previous_filename, mode: "100644", sha: null });
4504
+ if (f.status === "removed") {
4505
+ tree.push({ path: f.filename, mode: "100644", sha: null });
4506
+ continue;
4507
+ }
4508
+ const e2 = w.headTree.get(f.filename);
4509
+ if (!e2) throw new Error(`Bridge could not read ${f.filename} in the branch. Read it again and try again.`);
4510
+ tree.push({ path: e2.path, mode: e2.mode, sha: e2.sha });
4511
+ }
4512
+ for (const [rel, text] of Object.entries(merged)) {
4513
+ const path = t3.root ? `${t3.root}/${rel}` : rel;
4514
+ const mode = w.headTree.get(path)?.mode ?? "100644";
4515
+ tree.push(text === null ? { path, mode, sha: null } : { path, mode, content: text });
4516
+ }
4517
+ const newTree = await gh.createTreeEntries(w.mainTree, tree);
4518
+ const message = resolutionMessage({ library: pull.base.ref, conflicts: t3.plan.conflicts, choices, who });
4519
+ const commit = await gh.createCommit(message, newTree, [w.headSha, w.mainSha]);
4520
+ await gh.updateBranch(pull.head.ref, commit);
4521
+ return commit;
4522
+ }
4523
+ async function mergeOnGitHub(gh, input) {
4524
+ const r2 = await gh.mergePullRequest(input.number, { method: input.method ?? "squash", sha: input.headSha });
4525
+ return { sha: r2.sha };
4526
+ }
4527
+ class OverrideMergeError extends Error {
4528
+ constructor(stage, message, cause, commentUrl) {
4529
+ super(message);
4530
+ this.stage = stage;
4531
+ this.cause = cause;
4532
+ this.commentUrl = commentUrl;
4533
+ this.name = "OverrideMergeError";
4534
+ }
4535
+ stage;
4536
+ cause;
4537
+ commentUrl;
4538
+ }
4539
+ async function overrideMergeOnGitHub(gh, input) {
4540
+ const body = overrideComment(input.login, input.reason, input.missing);
4541
+ let comment;
4542
+ try {
4543
+ comment = await gh.createIssueComment(input.number, body);
4544
+ } catch (err) {
4545
+ throw new OverrideMergeError("comment", `GitHub did not take the comment, so nothing merged: ${err.message}`, err);
4546
+ }
4547
+ try {
4548
+ const r2 = await gh.mergePullRequest(input.number, { method: input.method, sha: input.headSha, commitMessage: body.replace(OVERRIDE_HEADING, "").trim() });
4549
+ return { sha: r2.sha, commentUrl: comment.url, comment: body };
4550
+ } catch (err) {
4551
+ const msg = err.message.trim().replace(/[.\s]+$/, "");
4552
+ await gh.updateIssueComment(comment.id, overrideRefusedComment(body, msg)).catch(() => void 0);
4553
+ throw new OverrideMergeError(
4554
+ "merge",
4555
+ `GitHub refused to merge it: ${msg}. Your reason stays on the branch, marked not merged. The protection of main may need an approving review or an admin bypass.`,
4556
+ err,
4557
+ comment.url
4558
+ );
4559
+ }
4560
+ }
4561
+ async function reviewOnGitHub(gh, input) {
4562
+ const text = input.body?.trim() ?? "";
4563
+ if (input.event === "REQUEST_CHANGES" && !text) throw new Error("Add a comment that says what to change.");
4564
+ if (input.event === "COMMENT" && !text) throw new Error("Write a comment first.");
4565
+ return gh.submitReview(input.number, { event: input.event, body: text || void 0, commitId: input.headSha });
4566
+ }
4567
+ async function commentOnGitHub(gh, n2, body) {
4568
+ const text = body.trim();
4569
+ if (!text) throw new Error("Write the comment first.");
4570
+ return gh.createIssueComment(n2, text);
4571
+ }
4572
+ async function askOthersToReviewOnGitHub(gh, input) {
4573
+ if (!input.others.length) throw new Error("Nobody else can give the missing approval. Review it, or ask an owner in Settings to add a reviewer.");
4574
+ await gh.requestReviewers(input.number, input.others);
4575
+ if (input.viewer && input.viewerRequested) await gh.removeRequestedReviewers(input.number, [input.viewer]).catch(() => void 0);
4576
+ return input.others;
4577
+ }
4578
+ async function closeOnGitHub(gh, n2) {
4579
+ await gh.updatePullRequest(n2, { state: "closed" });
4580
+ }
4581
+ async function reopenOnGitHub(gh, n2) {
4582
+ await gh.updatePullRequest(n2, { state: "open" });
4583
+ }
4584
+ const sameLogin = (a, b) => Boolean(a && b && a.toLowerCase().replace(/^@/, "") === b.toLowerCase().replace(/^@/, ""));
4585
+ function canAddTo(p, viewer, write) {
4586
+ if (!viewer) return false;
4587
+ return write || sameLogin(p.user?.login, viewer);
4588
+ }
4589
+ function addedComment(paths, viewer, from = "the Bridge token editor") {
4590
+ const lines = paths.slice(0, 20).map((p) => `- \`${p}\``);
4591
+ if (paths.length > 20) lines.push(`- and ${paths.length - 20} more`);
4592
+ return `${viewer ? `@${viewer} added` : "Added"} token edits from ${from}:
4593
+
4594
+ ${lines.join("\n")}`;
4595
+ }
4596
+ const JIRA_KEY = /^[A-Z][A-Z0-9_]{0,9}-[1-9]\d{0,6}$/;
4597
+ function parseJiraKey(input) {
4598
+ const t3 = (input ?? "").trim().toUpperCase();
4599
+ if (!t3) return null;
4600
+ if (!JIRA_KEY.test(t3)) throw new Error("Write the Jira ticket as its key, like DS-42, or leave it empty.");
4601
+ return t3;
4602
+ }
4603
+ const isJiraKey = (input) => JIRA_KEY.test(input.trim().toUpperCase());
4604
+ const LEADING_KEY = /^\[?([A-Z][A-Z0-9_]{0,9}-[1-9]\d{0,6})\]?[:\s]\s*/;
4605
+ function jiraKeyOf(title) {
4606
+ return LEADING_KEY.exec(title.trim())?.[1] ?? null;
4607
+ }
4608
+ const withoutJiraKey = (title) => title.trim().replace(LEADING_KEY, "");
4609
+ function titleWithJiraKey(title, key) {
4610
+ if (!key) return title;
4611
+ return `${key} ${withoutJiraKey(title)}`;
4612
+ }
4613
+ function branchWithJiraKey(branch, key) {
4614
+ if (!key || branch.includes(key)) return branch;
4615
+ const i2 = branch.lastIndexOf("/");
4616
+ return i2 < 0 ? `${key}-${branch}` : `${branch.slice(0, i2 + 1)}${key}-${branch.slice(i2 + 1)}`;
4617
+ }
4618
+ const DOCS_EDITOR_ORIGIN = {
4619
+ where: "in the Bridge docs editor",
4620
+ footer: 'Proposed with the Bridge docs editor by a person (no AI involved in this text unless the docs say "draftedBy").',
4621
+ label: "source: bridge-web"
4622
+ };
4623
+ const DOCS_MCP_ORIGIN = {
4624
+ where: "with Claude through the Bridge MCP server",
4625
+ footer: 'Proposed with the Bridge MCP server on behalf of a person. Docs Claude drafted say "draftedBy": "claude" until a reviewer accepts them.',
4626
+ label: "source: mcp"
4627
+ };
4628
+ const EM_DASH = /\u2014/;
4629
+ function validateDocs(d2) {
4630
+ const out = [];
4631
+ if (!d2.summary.trim()) out.push({ field: "summary", message: "Add a summary: one or two sentences on what it is for." });
4632
+ const lists = ["whenToUse", "whenNotToUse", "accessibility", "contentGuidelines"];
4633
+ for (const k3 of lists) (d2[k3] ?? []).forEach((x, i2) => !x.trim() && out.push({ field: `${k3}.${i2}`, message: "Fill or remove the empty item." }));
4634
+ for (const k3 of ["dos", "donts"]) d2[k3].forEach((g2, i2) => (!g2.title.trim() || !g2.text.trim()) && out.push({ field: `${k3}.${i2}`, message: "Give every item a title and a sentence." }));
4635
+ const prose = JSON.stringify([d2.summary, d2.whenToUse, d2.whenNotToUse, d2.dos, d2.donts, d2.accessibility, d2.contentGuidelines ?? [], d2.anatomy ?? {}]);
4636
+ if (EM_DASH.test(prose)) out.push({ field: "voice", message: "Replace the em dashes with a colon, a comma or two sentences." });
4637
+ return out;
4638
+ }
4639
+ function cleanDocs(d2) {
4640
+ const list = (xs) => (xs ?? []).map((x) => x.trim()).filter(Boolean);
4641
+ const guide = (xs) => xs.map((g2) => ({ ...g2, title: g2.title.trim(), text: g2.text.trim() })).filter((g2) => g2.title || g2.text);
4642
+ return {
4643
+ ...d2,
4644
+ summary: d2.summary.trim(),
4645
+ whenToUse: list(d2.whenToUse),
4646
+ whenNotToUse: list(d2.whenNotToUse),
4647
+ dos: guide(d2.dos),
4648
+ donts: guide(d2.donts),
4649
+ accessibility: list(d2.accessibility),
4650
+ contentGuidelines: list(d2.contentGuidelines),
4651
+ anatomy: Object.fromEntries(Object.entries(d2.anatomy ?? {}).filter(([k3, v2]) => k3.trim() && v2.trim()))
4652
+ };
4653
+ }
4654
+ const docsText = (d2) => formatDocsJson(cleanDocs(d2));
4655
+ const docsFileText = (d2, original) => patchDocsJson(original, cleanDocs(d2));
4656
+ const slugDate = (d2) => d2.toISOString().slice(0, 10).replace(/-/g, "");
4657
+ function docsProposal(edits, opts) {
4658
+ const origin = opts.origin ?? DOCS_EDITOR_ORIGIN;
4659
+ if (!edits.length) throw new Error("No docs edits in the draft.");
4660
+ const names2 = edits.map((e2) => e2.component);
4661
+ const list = names2.length === 1 ? names2[0] : `${names2.slice(0, -1).join(", ")} and ${names2[names2.length - 1]}`;
4662
+ const title = opts.title?.trim() || `docs(${names2.length === 1 ? edits[0].slug : "components"}): usage guidance for ${list}`;
4663
+ const slug = `${names2.length === 1 ? edits[0].slug : `${edits[0].slug}-and-${names2.length - 1}-more`}`.toLowerCase().replace(/[^a-z0-9-]+/g, "-").slice(0, 40);
4664
+ const branch = `docs/${slug}-${slugDate(opts.date)}-${opts.date.getTime().toString(36).slice(-4)}`;
4665
+ const csPath = `${opts.changesetDir ?? ".changeset"}/docs-${slug}-${opts.date.getTime().toString(36)}.md`;
4666
+ const pkg = opts.packageName ?? "@bridge-ds/react";
4667
+ const changeset = `---
4668
+ "${pkg}": patch
4669
+ ---
4670
+
4671
+ Usage guidance for ${list}.
4672
+ `;
4673
+ const files = [...edits.map((e2) => ({ path: e2.path, content: e2.after })), { path: csPath, content: changeset }];
4674
+ const risk = classifyRisk({ files: files.map((f) => ({ path: f.path, status: "modified" })) });
4675
+ const rows = edits.map((e2) => {
4676
+ let state = "edited";
4677
+ try {
4678
+ const d2 = JSON.parse(e2.after);
4679
+ const gaps = missingProse(d2);
4680
+ state = gaps.length ? `still missing: ${gaps.join(", ")}` : "every section filled";
4681
+ } catch {
4682
+ state = "not valid JSON";
4683
+ }
4684
+ return `- \`${e2.path}\` (${e2.before === null ? "new" : "changed"}, ${state})`;
4685
+ });
4686
+ const body = [
4687
+ `## Docs change`,
4688
+ "",
4689
+ `Usage guidance for ${list}, written ${origin.where}.`,
4690
+ "",
4691
+ ...rows,
4692
+ "",
4693
+ `**Risk:** ${risk.label}. Docs only: the published components do not change. Changeset: patch.`,
4694
+ "",
4695
+ opts.notes?.trim() ? `## Notes
4696
+
4697
+ ${opts.notes.trim()}
4698
+ ` : "",
4699
+ "---",
4700
+ origin.footer
4701
+ ].filter((x, i2, a) => !(x === "" && a[i2 - 1] === "")).join("\n");
4702
+ return { branch, title, body, files, risk, bump: "patch", labels: ["docs", origin.label, `tier-${risk.tier}`] };
4703
+ }
4704
+ async function proposeDocsOnGitHub(gh, edits, opts) {
4705
+ const p = docsProposal(edits, { title: opts.title, notes: opts.notes, date: opts.date ?? /* @__PURE__ */ new Date(), origin: opts.origin });
4706
+ const prTitle = titleWithJiraKey(p.title, opts.jira ?? null);
4707
+ const branch = branchWithJiraKey(p.branch, opts.jira ?? null);
4708
+ const mainSha = await gh.getBranchSha(opts.base);
4709
+ if (!mainSha) throw new Error(`Branch ${opts.base} not found.`);
4710
+ const tree = await gh.createTree(await gh.getCommitTree(mainSha), p.files.map((f) => ({ path: f.path, content: f.content })));
4711
+ const commit = await gh.createCommit(`${prTitle}
4712
+
4713
+ ${opts.commitNote ?? "Proposed with the Bridge docs editor."}`, tree, [mainSha]);
4714
+ await gh.createBranch(branch, commit);
4715
+ const pr = await gh.createPullRequest({ title: prTitle, head: branch, base: opts.base, body: p.body, draft: false });
4716
+ await gh.addLabels(pr.number, [...p.labels, ...opts.extraLabels ?? []]).catch(() => false);
4717
+ return { number: pr.number, url: pr.url, branch, title: prTitle };
4718
+ }
4719
+ async function commitFileOnGitHub(gh, input) {
4720
+ const cur = await gh.getFile(input.path, input.branch);
4721
+ if (!cur) throw new Error(`${input.path} is not on ${input.branch}. Check the component name and the branch.`);
4722
+ const content = input.next(cur.text);
4723
+ if (content === cur.text) return "";
4724
+ return gh.putFile({ path: input.path, content, branch: input.branch, message: input.message, sha: cur.sha });
4725
+ }
4726
+ const SHARE_EXPLAIN = "Product teams can install this branch in their app before it is merged, and remove it any time.";
4727
+ const packageDir = (name) => `packages/${name.replace(/^@[^/]+\//, "")}/`;
4728
+ function touchesPackages(files, packages) {
4729
+ const dirs = packages.map(packageDir);
4730
+ return files.some((f) => dirs.some((d2) => f.startsWith(d2)));
4731
+ }
4732
+ const canShare = (x) => !x.fromFork && x.packages.length > 0;
4733
+ function shareState(x) {
4734
+ if (!touchesPackages(x.files, x.packages)) return { kind: "no-packages" };
4735
+ const c10 = readPreviewComment(x.comment);
4736
+ if (x.lifecycle === "merged" || x.lifecycle === "closed") {
4737
+ const kind = x.lifecycle === "merged" ? "shipped" : "closed";
4738
+ if (!c10 || !c10.command || c10.source === "none") return { kind, stillWorks: null, command: null };
4739
+ return { kind, stillWorks: c10.source === "pkg.pr.new" ? true : c10.source === "release" ? false : null, command: c10.command };
4740
+ }
4741
+ if (!c10) return { kind: "building" };
4742
+ if (c10.sha7 && !x.headSha.startsWith(c10.sha7)) return { kind: "building" };
4743
+ if (!c10.command || c10.source === "none") return { kind: "failed" };
4744
+ return { kind: "ready", command: c10.command, source: c10.source };
4745
+ }
4746
+ const showShare = (lifecycle, s2) => lifecycle === "open" || lifecycle === "draft" || (s2.kind === "shipped" || s2.kind === "closed") && !!s2.command;
4747
+ const shareDescription = (s2) => s2.kind === "shipped" || s2.kind === "closed" ? stateSentence(s2) ?? SHARE_EXPLAIN : SHARE_EXPLAIN;
4748
+ const stableCommand = (packages) => `npm i ${packages.map((p) => `${p}@latest`).join(" ")}`;
4749
+ function stateSentence(s2) {
4750
+ switch (s2.kind) {
4751
+ case "ready":
4752
+ return null;
4753
+ case "building":
4754
+ return "The preview version is being prepared. It is usually ready a couple of minutes after each update to the branch.";
4755
+ case "failed":
4756
+ return "The preview version could not be prepared for the latest update. Update the branch to try again, or ask a developer to run it again on GitHub.";
4757
+ case "no-packages":
4758
+ return "This branch does not touch the library tokens or components, so there is no preview version to install.";
4759
+ case "shipped":
4760
+ return s2.stillWorks === true ? "This branch was merged. Its preview version still installs, and products get it with the next library release." : s2.stillWorks === false ? "This branch was merged, so its preview version was removed. Products get it with the next library release." : "This branch was merged. Products get it with the next library release.";
4761
+ case "closed":
4762
+ return s2.stillWorks === true ? "This branch was closed without merging. Its preview version still installs, but it will not reach the library." : s2.stillWorks === false ? "This branch was closed without merging, so its preview version was removed." : "This branch was closed without merging.";
4763
+ }
4764
+ }
4765
+ const quote = (t3) => `"${t3.trim().replace(/"/g, "'")}"`;
4766
+ function shareMessage(x) {
4767
+ const back = stableCommand(x.packages);
4768
+ const s2 = x.state;
4769
+ if (s2.kind === "ready") {
4770
+ const lasts = s2.source === "pkg.pr.new" ? "This preview stays available. Each update to the change makes a new one: ask me for the latest command." : "This preview is replaced on each update to the change and removed when it ships or closes: ask me for the latest command if the install fails.";
4771
+ return {
4772
+ subject: `Try the design system change ${quote(x.title)} before it ships`,
4773
+ body: [
4774
+ "Hi,",
4775
+ "",
4776
+ `Could you try the design system change ${quote(x.title)} in our product before it ships?`,
4777
+ ...x.tryUrl ? ["", `See it in the demo product first: ${x.tryUrl}`] : [],
4778
+ "",
4779
+ "Install the preview version in a staging branch (not production):",
4780
+ s2.command,
4781
+ "",
4782
+ lasts,
4783
+ "",
4784
+ "To go back to the stable version:",
4785
+ back,
4786
+ "",
4787
+ `Details for developers: ${x.url}`
4788
+ ].join("\n")
4789
+ };
4790
+ }
4791
+ if ((s2.kind === "shipped" || s2.kind === "closed") && s2.command) {
4792
+ const what = s2.kind === "shipped" ? `The design system change ${quote(x.title)} has shipped. You get it with the next library release.` : `The design system change ${quote(x.title)} was closed without shipping.`;
4793
+ const preview = s2.stillWorks === false ? "Its preview version was removed, so reinstalling it fails." : s2.stillWorks === true ? "Its preview version still installs, but it is not the released version." : "";
4794
+ return {
4795
+ subject: `Design system change ${quote(x.title)} ${s2.kind === "shipped" ? "has shipped" : "was closed"}`,
4796
+ body: ["Hi,", "", [what, preview].filter(Boolean).join(" "), "", "If you installed its preview, go back to the stable version:", back, "", `Details for developers: ${x.url}`].join("\n")
4797
+ };
4798
+ }
4799
+ return null;
4800
+ }
4801
+ const mailtoHref = (m2) => `mailto:?subject=${encodeURIComponent(m2.subject)}&body=${encodeURIComponent(m2.body.replace(/\r?\n/g, "\r\n"))}`;
4802
+ function productPreview(apps, ctx, sources) {
4803
+ if (!apps.admin || ctx.fromFork) return null;
4804
+ const admin = resolveBranchPreviews(apps, { branch: ctx.branch, pr: ctx.pr, sha: ctx.sha }, sources).find((p) => p.app === "admin");
4805
+ if (!admin?.url || admin.source === "template" || admin.state === "failed") return null;
4806
+ return admin.url;
4807
+ }
4808
+ function tryInProductUrl(preview, pr, screen) {
4809
+ const url = new URL(preview);
4810
+ url.pathname = "/";
4811
+ url.search = "";
4812
+ url.searchParams.set("bridge", String(pr));
4813
+ if (screen) {
4814
+ url.searchParams.set("tenant", screen.tenant);
4815
+ url.searchParams.set("theme", screen.theme);
4816
+ }
4817
+ url.hash = screen ? screen.route : "";
4818
+ return url.href;
4819
+ }
4820
+ const MIN_TOGETHER = 2;
4821
+ const MAX_TOGETHER = 4;
4822
+ function onlyTokens(files, tokensRoot) {
4823
+ const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
4824
+ return files.length > 0 && files.every((f) => f.startsWith(root) && f.endsWith(".tokens.json") || /contrast\.checks\.json$/.test(f) || f.startsWith(".changeset/"));
4825
+ }
4826
+ function tokenFilesOf(files, tokensRoot) {
4827
+ const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
4828
+ return files.filter((f) => f.startsWith(root) && f.endsWith(".tokens.json")).map((f) => f.slice(root.length));
4829
+ }
4830
+ const changeList = (numbers) => numbers.join(",");
4831
+ function tryTogetherUrl(admin, numbers, picks) {
4832
+ const url = new URL(admin);
4833
+ url.pathname = "/";
4834
+ url.hash = "";
4835
+ const use = encodePicks(picks);
4836
+ url.search = `?bridge=${changeList(numbers)}${use ? `&bridge-use=${encodeURIComponent(use)}` : ""}`;
4837
+ return url.href;
4838
+ }
4839
+ const COMBINE_COMPARE_LIMIT = 300;
4840
+ async function readCombineBranch(gh, pull, opts) {
4841
+ const compare = await gh.compareDetailed(opts.base, pull.headSha);
4842
+ const files = compare.files;
4843
+ const names2 = files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
4844
+ const start = compare.mergeBase;
4845
+ if (!start || files.length >= COMBINE_COMPARE_LIMIT || !onlyTokens(names2, opts.tokensRoot)) return { number: pull.number, title: pull.title, tokens: false, edits: null };
4846
+ const root = opts.tokensRoot.replace(/^\/+|\/+$/g, "");
4847
+ const tokenFiles = files.filter((f) => tokenFilesOf([f.filename], root).length);
4848
+ const source = { number: pull.number, base: {}, head: {}, files: tokenFiles.map((f) => tokenFilesOf([f.filename], root)[0]) };
4849
+ await Promise.all(
4850
+ tokenFiles.map(async (f) => {
4851
+ const rel = tokenFilesOf([f.filename], root)[0];
4852
+ source.base[rel] = f.status === "added" ? void 0 : await gh.readFile(f.previous_filename ?? f.filename, start);
4853
+ source.head[rel] = f.status === "removed" ? void 0 : await gh.readFile(f.filename, pull.headSha);
4854
+ })
4855
+ );
4856
+ return { number: pull.number, title: pull.title, tokens: true, edits: changeEdits(source) };
4857
+ }
4047
4858
  export {
4048
- ak as AAA_MIN,
4049
- al as AA_MIN,
4859
+ ao as AAA_MIN,
4860
+ ap as AA_MIN,
4050
4861
  ADMIN_ROLE_ID,
4051
4862
  ANTHROPIC_MESSAGES_URL,
4052
4863
  BELL,
4053
4864
  BRAND_FONTS,
4054
4865
  BRAND_SLUG_RE,
4055
- am as BULK_LIMIT,
4056
- n as COLOR_FIELDS,
4057
- k as COMPONENT_SPECS_VERSION,
4866
+ aq as BULK_LIMIT,
4867
+ q as COLOR_FIELDS,
4868
+ COMBINE_COMPARE_LIMIT,
4869
+ COMPARE_FILE_LIMIT,
4870
+ o as COMPONENT_SPECS_VERSION,
4058
4871
  CONFIG_FILE,
4059
- an as CONTRAST_CELL_MAX,
4060
- ao as CONTRAST_CHECKS_FILE,
4872
+ CONFLICT_REASON,
4873
+ ar as CONTRAST_CELL_MAX,
4874
+ as as CONTRAST_CHECKS_FILE,
4061
4875
  ConfigError,
4062
- ap as DEFAULT_COMPONENT_SPECS_FILE,
4063
- aq as DEFAULT_GOVERNANCE,
4064
- ar as DEFAULT_OVERRIDE,
4876
+ at as DEFAULT_COMPONENT_SPECS_FILE,
4877
+ au as DEFAULT_GOVERNANCE,
4878
+ av as DEFAULT_OVERRIDE,
4879
+ aw as DEFAULT_PRODUCTS,
4065
4880
  DEFAULT_PROTECTED_BRANCHES,
4066
- as as DEFAULT_RIGHTS,
4881
+ ax as DEFAULT_RIGHTS,
4067
4882
  DEFAULT_TOKEN_PATHS,
4068
- at as DEFAULT_VISUAL_IMPACT,
4883
+ ay as DEFAULT_VISUAL_IMPACT,
4069
4884
  DEMO_RELEASES,
4070
4885
  DOCS_CHANGE_BUMP,
4071
4886
  DOCS_DRAFT_MODEL,
4887
+ DOCS_EDITOR_ORIGIN,
4072
4888
  DOCS_KEYS,
4889
+ DOCS_MCP_ORIGIN,
4073
4890
  DOCS_SELECTION_MODES,
4074
4891
  DRAFTS_DIR,
4075
4892
  DRAFT_EXCHANGE_VERSION,
@@ -4100,91 +4917,111 @@ export {
4100
4917
  FIGMA_REFERENCE_STORY,
4101
4918
  GitHubClient,
4102
4919
  GitHubError,
4103
- au as JsonParseError,
4920
+ az as JsonParseError,
4104
4921
  LIGHTNESS,
4105
4922
  LayeredTokenSet,
4923
+ MAX_TOGETHER,
4924
+ MIN_TOGETHER,
4106
4925
  NEUTRAL_ANCHORS,
4107
- av as OWNER_GROUPS,
4926
+ OVERRIDE_HEADING,
4927
+ aA as OWNER_GROUPS,
4928
+ OverrideMergeError,
4108
4929
  PIN_TOLERANCE,
4109
- aw as PREVIEW_APP_LABELS,
4110
- ax as PREVIEW_COMMENT_MARKER,
4111
- ay as PREVIEW_PLACEHOLDERS,
4112
- az as PRIMITIVE_MODE,
4930
+ aB as PREVIEW_APP_LABELS,
4931
+ aC as PREVIEW_COMMENT_MARKER,
4932
+ aD as PREVIEW_PLACEHOLDERS,
4933
+ aE as PRIMITIVE_MODE,
4934
+ aF as PRODUCT_LABEL_PREFIX,
4113
4935
  PROSE_KEYS,
4114
4936
  PROTECTION_PARTS,
4115
4937
  PROTECTION_PART_PHRASE,
4938
+ PULL_STATE_PILL,
4116
4939
  ProtectedBranchError,
4117
4940
  RADIUS_PRESETS,
4941
+ aG as REDUCED_MOTION_NOTE,
4118
4942
  REQUESTS_BRANCH,
4119
4943
  REQUEST_KINDS,
4120
4944
  REQUEST_LABEL,
4121
4945
  REQUEST_STATUS_LABEL,
4122
- aA as RIGHTS,
4123
- aB as RIGHT_LABEL,
4946
+ REVIEW_STATUS_ORDER,
4947
+ aH as RIGHTS,
4948
+ aI as RIGHT_LABEL,
4124
4949
  RISK_LABELS,
4125
- aC as RISK_TIER,
4950
+ aJ as RISK_TIER,
4951
+ SHARE_EXPLAIN,
4126
4952
  SIMILARITY_THRESHOLD,
4127
4953
  SINGLE_MODE,
4128
- aD as SPEC_FIELDS,
4954
+ aK as SPEC_FIELDS,
4129
4955
  STEPS,
4130
4956
  TIER_AXIS,
4131
4957
  TIER_NAMES,
4132
- aE as TIER_ORDER,
4958
+ aL as TIER_ORDER,
4133
4959
  TOKEN_REJECTED_MESSAGE,
4134
4960
  TOKEN_SETTINGS_URL,
4135
- aF as TeamDirectory,
4136
- aG as UNRELEASED,
4137
- aH as VISUAL_CHECK_LABEL,
4961
+ aM as TRIGGER_LABELS,
4962
+ aN as TeamDirectory,
4963
+ aO as UNRELEASED,
4964
+ aP as VISUAL_CHECK_LABEL,
4138
4965
  acceptDocsDraft,
4139
4966
  acceptDocsText,
4140
4967
  addBrandToConfigText,
4141
- aI as affectedContrast,
4968
+ addedComment,
4969
+ aQ as affectedContrast,
4142
4970
  d as aliasOf,
4143
4971
  aliasTarget,
4972
+ allowedMergeMethods,
4144
4973
  anatomyLabel,
4145
4974
  anchoredRamp,
4146
4975
  anthropicReplyText,
4147
4976
  anthropicRequest,
4148
- aJ as appForProject,
4149
- aK as appUrl,
4977
+ aR as appForProject,
4978
+ aS as appUrl,
4150
4979
  applyConfirmedRenames,
4151
4980
  applyDocsSections,
4152
4981
  applyTokenEdits,
4153
4982
  applyTokenMerge,
4154
- aL as approverEntries,
4155
- aM as approverLists,
4983
+ aT as approverEntries,
4984
+ aU as approverLists,
4985
+ askOthersToReviewOnGitHub,
4156
4986
  assertSafeBranch,
4157
4987
  assignRequestOnGitHub,
4158
- aN as assignVersions,
4988
+ aV as assignVersions,
4159
4989
  base64ToUtf8,
4160
- aO as baseBranchOf,
4990
+ aW as baseBranchOf,
4161
4991
  bellChroma,
4162
- aP as branchName,
4992
+ aX as branchName,
4163
4993
  branchPreviewUrls,
4164
- aQ as branchSlug,
4994
+ aY as branchSlug,
4995
+ branchWithJiraKey,
4165
4996
  brandDisplayName,
4166
4997
  brandPreviewVariables,
4167
4998
  buildFidelityReport,
4168
4999
  buildHistoryEntry,
4169
5000
  buildReport,
4170
- j as buildTokenSet,
5001
+ buildTokenSet,
4171
5002
  buildUsageIndex,
4172
- aR as canShip,
5003
+ canAddTo,
5004
+ canOverride,
5005
+ canShare,
5006
+ canShip,
4173
5007
  changeEdits,
4174
- aS as changeKind,
4175
- aT as changeTable,
4176
- aU as changedModes,
4177
- aV as changesetBump,
5008
+ aZ as changeKind,
5009
+ changeList,
5010
+ a_ as changeTable,
5011
+ a$ as changedModes,
5012
+ b0 as changesetBump,
4178
5013
  checkBrandSlug,
4179
5014
  checkLayeredPair,
4180
5015
  choiceFor,
4181
5016
  classifyAuth,
4182
- aW as classifyLayered,
5017
+ b1 as classifyLayered,
4183
5018
  classifyRisk,
5019
+ cleanDocs,
4184
5020
  cleanDocsText,
4185
5021
  cleanDraftText,
4186
- aX as clearRawDeep,
5022
+ b2 as clearRawDeep,
4187
5023
  closeExplorationOnGitHub,
5024
+ closeOnGitHub,
4188
5025
  closingIssueNumbers,
4189
5026
  coerceValue,
4190
5027
  collectHistory,
@@ -4195,28 +5032,34 @@ export {
4195
5032
  combineChanges,
4196
5033
  combineConflicts,
4197
5034
  c as comboKey,
4198
- aY as commitMessage,
5035
+ commentOnGitHub,
5036
+ commitFileOnGitHub,
5037
+ commitLibraryResolution,
5038
+ b3 as commitMessage,
4199
5039
  m as compactJson,
4200
5040
  compareImages,
4201
5041
  componentForPath,
4202
- aZ as composeChangeset,
5042
+ b4 as componentMotion,
5043
+ b5 as componentsUsingMotionToken,
5044
+ b6 as composeChangeset,
4203
5045
  composeFiles,
4204
5046
  composeNewBrandBody,
4205
5047
  composePendingDrafts,
4206
- a_ as composePrBody,
4207
- a$ as composeProposalBody,
5048
+ b7 as composePrBody,
5049
+ b8 as composeProposalBody,
4208
5050
  composeRequestIssue,
4209
- b0 as composite,
4210
- b1 as contextFor,
5051
+ b9 as composite,
5052
+ conflictRoute,
5053
+ ba as contextFor,
4211
5054
  contextLabel,
4212
- b2 as contrastDelta,
4213
- b3 as contrastDeltaTable,
4214
- b4 as contrastLevel,
4215
- b5 as contrastPair,
4216
- b6 as contrastRatio,
5055
+ bb as contrastDelta,
5056
+ bc as contrastDeltaTable,
5057
+ bd as contrastLevel,
5058
+ be as contrastPair,
5059
+ bf as contrastRatio,
4217
5060
  contrastScope,
4218
- b7 as countChanges,
4219
- b8 as coverageSummary,
5061
+ bg as countChanges,
5062
+ bh as coverageSummary,
4220
5063
  createDocsDraftSession,
4221
5064
  createPendingDrafts,
4222
5065
  cssModuleClasses,
@@ -4228,22 +5071,27 @@ export {
4228
5071
  declineRequestOnGitHub,
4229
5072
  decodePicks,
4230
5073
  defaultChoiceFor,
4231
- b9 as defaultContext,
5074
+ k as defaultContext,
4232
5075
  defaultFile,
5076
+ bi as defaultProduct,
4233
5077
  defaultScopes,
4234
- ba as defaultTitle,
4235
- bb as defineConfig,
5078
+ bj as defaultTitle,
5079
+ bk as defineConfig,
4236
5080
  deletePendingDrafts,
4237
5081
  demoHistory,
4238
- bc as detectLayout,
4239
- bd as diffLayered,
5082
+ bl as detectLayout,
5083
+ bm as diffLayered,
5084
+ dismissStaleFrom,
5085
+ docsFileText,
4240
5086
  docsPrompt,
5087
+ docsProposal,
4241
5088
  docsSectionShapes,
4242
5089
  docsSectionsAnswer,
4243
5090
  docsSectionsResult,
4244
5091
  docsSelectionPrompt,
4245
5092
  docsTaskKeys,
4246
5093
  docsTaskPrompt,
5094
+ docsText,
4247
5095
  draftChangeLine,
4248
5096
  draftImagePath,
4249
5097
  draftItemBrief,
@@ -4252,14 +5100,16 @@ export {
4252
5100
  draftSessionPath,
4253
5101
  dtcgColor,
4254
5102
  effectiveScopes,
5103
+ effectiveTier,
5104
+ eligibleReviewers,
4255
5105
  emptyTokenFile,
4256
5106
  encodePicks,
4257
5107
  ensureRequestsBranch,
4258
- be as entriesFor,
4259
- bf as entryLabel,
5108
+ bn as entriesFor,
5109
+ bo as entryLabel,
4260
5110
  evaluatePairs,
4261
- bg as expandEntries,
4262
- bh as expandOwners,
5111
+ bp as expandEntries,
5112
+ bq as expandOwners,
4263
5113
  explorationBranch,
4264
5114
  explorationNote,
4265
5115
  explorationNotePath,
@@ -4277,63 +5127,71 @@ export {
4277
5127
  explorationTryItOnGitHub,
4278
5128
  fidelityComponentLine,
4279
5129
  fidelityVerdict,
4280
- bi as fieldChanges,
4281
- bj as fileModes,
4282
- bk as filterHistory,
5130
+ br as fieldChanges,
5131
+ bs as figmaCell,
5132
+ bt as fileModes,
5133
+ bu as filterHistory,
4283
5134
  findExplorationOnGitHub,
4284
5135
  findPreviewComment,
5136
+ bv as findProduct,
4285
5137
  findReferenceProblems,
4286
- bl as findToken,
4287
- bm as findVercelComment,
4288
- bn as firstParagraph,
4289
- n2 as flattenLayerFile,
5138
+ bw as findToken,
5139
+ bx as findVercelComment,
5140
+ by as firstParagraph,
5141
+ n as flattenLayerFile,
4290
5142
  flattenTokens,
4291
5143
  formatChangeset,
4292
5144
  formatDocsJson,
4293
- bo as formatNumber,
4294
- bp as formatOwnerEntry,
5145
+ bz as formatNumber,
5146
+ bA as formatOwnerEntry,
4295
5147
  formatValue,
5148
+ freshness,
4296
5149
  friendlyMessage,
4297
5150
  fromHex,
4298
5151
  fromPlain,
4299
5152
  generateRamp,
4300
- bq as getChild,
5153
+ bB as getChild,
4301
5154
  getEntry,
4302
5155
  gitHubHistoryBackend,
4303
- g2 as globToRegExp,
4304
- br as groupsPhrase,
5156
+ g as globToRegExp,
5157
+ bC as groupsPhrase,
5158
+ hasWriteAccess,
4305
5159
  h as hashSpec,
4306
5160
  h2 as hexToRgba,
4307
- bs as holdersOf,
5161
+ bD as holdersOf,
4308
5162
  i as iconComponentName,
4309
5163
  idExtensionFor,
4310
5164
  impactedModules,
4311
5165
  importsIn,
4312
- bt as indexByPath,
4313
- bu as inheritedTypeAt,
4314
- bv as installCommand,
4315
- bw as installCommandFromPreviewComment,
5166
+ bE as indexByPath,
5167
+ bF as inheritedTypeAt,
5168
+ bG as installCommand,
5169
+ bH as installCommandFromPreviewComment,
4316
5170
  isActiveExploration,
4317
5171
  isAlias,
5172
+ isAutomationPull,
4318
5173
  isDocsSelectionMode,
4319
5174
  isDraftSessionId,
4320
5175
  isExplorationBranch,
4321
5176
  isExplorationPull,
4322
- bx as isGuardrail,
5177
+ bI as isGuardrail,
4323
5178
  isHex,
5179
+ isJiraKey,
4324
5180
  isMissingPermission,
4325
5181
  isMultiMode,
4326
5182
  isPlanRefusal,
4327
5183
  isProseKey,
4328
- by as isTeamEntry,
4329
- bz as isTokenFile,
5184
+ isReleasePull,
5185
+ bJ as isTeamEntry,
5186
+ bK as isTokenFile,
4330
5187
  isTokenRejected,
4331
- bA as isVercelComment,
5188
+ bL as isVercelComment,
4332
5189
  jiraIssueFields,
4333
- bB as kindForMin,
4334
- bC as layeredImpact,
4335
- o as layeredParityIssues,
4336
- g as layoutOptions,
5190
+ jiraKeyOf,
5191
+ bM as kindForMin,
5192
+ bN as layeredImpact,
5193
+ o2 as layeredParityIssues,
5194
+ layoutOptions,
4337
5195
  levenshtein,
4338
5196
  linkRequestToExplorationOnGitHub,
4339
5197
  listBrands,
@@ -4344,17 +5202,20 @@ export {
4344
5202
  loadProtection,
4345
5203
  loadRepoFiles,
4346
5204
  loadRequiredChecks,
4347
- bD as luminance,
5205
+ bO as luminance,
5206
+ mailtoHref,
4348
5207
  markClaudeDraft,
4349
5208
  mergeDocsDraft,
4350
5209
  mergeDraftResults,
5210
+ mergeGate,
5211
+ mergeOnGitHub,
4351
5212
  mergeReleases,
4352
5213
  missingChoices,
4353
5214
  missingPermissionMessage,
4354
5215
  missingProse,
4355
5216
  modeDisplayName,
4356
5217
  modeFile,
4357
- bE as modeLabel,
5218
+ bP as modeLabel,
4358
5219
  modesOf$1 as modesOf,
4359
5220
  moveToken,
4360
5221
  nearestStep,
@@ -4363,91 +5224,124 @@ export {
4363
5224
  neutralOffset,
4364
5225
  newBrandSection,
4365
5226
  newDraftSessionId,
4366
- bF as nodesEqual,
4367
- bG as normalizeRights,
5227
+ bQ as nodesEqual,
5228
+ bR as normalizeRights,
4368
5229
  oklch,
4369
5230
  oklchToLinear,
4370
- bH as overrideKey,
4371
- bI as ownerEntryProblems,
5231
+ onlyTokens,
5232
+ openPulls,
5233
+ overlap,
5234
+ overrideComment,
5235
+ bS as overrideKey,
5236
+ overrideMergeOnGitHub,
5237
+ overrideReasonProblem,
5238
+ overrideRefusedComment,
5239
+ bT as ownerEntryProblems,
5240
+ packageDir,
4372
5241
  packageDirCandidates,
4373
- bJ as pairKey,
5242
+ bU as pairKey,
4374
5243
  parseChangelog,
4375
- bK as parseChangesetText,
5244
+ bV as parseChangesetText,
4376
5245
  parseColor,
4377
- bL as parseComponentSpecs,
5246
+ bW as parseComponentSpecs,
4378
5247
  parseContrastChecks,
4379
- q as parseDimension,
5248
+ q2 as parseDimension,
4380
5249
  parseDocsReply,
4381
5250
  parseDocsTaskAnswer,
4382
5251
  parseExplorationBody,
5252
+ parseJiraKey,
4383
5253
  parseJson,
4384
- bM as parseOwnerEntry,
5254
+ parseOverrideComment,
5255
+ bX as parseOwnerEntry,
4385
5256
  parsePendingDrafts,
4386
5257
  parseRepo,
4387
5258
  parseRequestIssue,
4388
5259
  parseStructureChange,
5260
+ parseTier,
4389
5261
  parseTokenFile,
4390
- bN as parseVercelComment,
5262
+ bY as parseVercelComment,
4391
5263
  patchDocsJson,
4392
5264
  pathId,
4393
5265
  pathSimilarity,
5266
+ pendingApprovals,
4394
5267
  permissionLabel,
4395
5268
  pickedOption,
4396
5269
  plainEqual,
4397
5270
  planNewBrand,
4398
5271
  planReference,
4399
5272
  planReferences,
4400
- bO as planRestore,
4401
- bP as planRevert,
4402
- bQ as planToJson,
5273
+ bZ as planRestore,
5274
+ b_ as planRevert,
5275
+ b$ as planToJson,
4403
5276
  planTokenMerge,
4404
5277
  e as prNumberFromSubject,
4405
5278
  prefillDocs,
4406
- bR as previewComment,
5279
+ c0 as previewComment,
4407
5280
  previewLinks,
4408
- bS as previewLinksOf,
5281
+ c1 as previewLinksOf,
4409
5282
  previewOverrides,
4410
- bT as previewUrl,
5283
+ c2 as previewUrl,
4411
5284
  previewsSettled,
4412
- bU as projectOfLabel,
5285
+ c3 as productForPlace,
5286
+ c4 as productLabel,
5287
+ c5 as productOfBranch,
5288
+ productPreview,
5289
+ c6 as productProblems,
5290
+ c7 as productsOf,
5291
+ c8 as projectOfLabel,
4413
5292
  promoteExplorationOnGitHub,
4414
5293
  promotionPlan,
5294
+ proposeDocsOnGitHub,
4415
5295
  propsTable,
4416
5296
  protectLibrary,
4417
5297
  protectRefusal,
4418
- bV as protectedBranchesOf,
5298
+ c9 as protectedBranchesOf,
4419
5299
  protectionRuleset,
4420
5300
  protectionRulesetName,
4421
5301
  protectionSentence,
4422
5302
  protectionState,
5303
+ pullState,
4423
5304
  pushChangeRows,
4424
5305
  rateLimitMessage,
5306
+ readCombineBranch,
5307
+ readLibraryConflicts,
4425
5308
  readPendingDrafts,
4426
5309
  readPreviewComment,
4427
5310
  readTokenAt,
4428
5311
  reconcileIds,
4429
5312
  referenceImagePath,
5313
+ releasePull,
4430
5314
  releasesFromTags,
4431
5315
  removeToken,
4432
5316
  removedSide,
4433
- bW as renderedColor,
5317
+ ca as renderedColor,
5318
+ reopenEligibility,
5319
+ reopenOnGitHub,
4434
5320
  requestBrief,
4435
5321
  requestImagePath,
4436
5322
  requestIssueTitle,
4437
5323
  requestStatus,
5324
+ requiredApprovals,
4438
5325
  requiredCheckNames,
4439
5326
  requiredPermission,
5327
+ resolutionMessage,
4440
5328
  resolve,
4441
5329
  resolveBranchPreviews,
5330
+ resolvedTokenFiles,
5331
+ reviewOnGitHub,
5332
+ reviewQueue,
5333
+ reviewStatusKind,
5334
+ reviewersToAsk,
4442
5335
  r as rgbaToDtcg,
4443
5336
  rgbaToHex,
4444
- bX as rightsProblems,
5337
+ cb as rightsProblems,
4445
5338
  round3,
4446
5339
  rulesSettingsUrl,
4447
5340
  rulesetTargetsDefault,
4448
- bY as runLayeredContrast,
5341
+ cc as runLayeredContrast,
5342
+ cd as sameProducts,
4449
5343
  sameScopes,
4450
- bZ as sameToken,
5344
+ ce as sameToken,
4451
5345
  sanitizeDocsReplacement,
4452
5346
  sanitizeDocsResult,
4453
5347
  sanitizeDocsSections,
@@ -4461,60 +5355,75 @@ export {
4461
5355
  serializePendingDrafts,
4462
5356
  serializeTokenFile,
4463
5357
  shadowedEdits,
4464
- b_ as shipPolicy,
5358
+ shareDescription,
5359
+ shareMessage,
5360
+ shareState,
5361
+ cf as shipPolicy,
4465
5362
  shortVariant,
4466
5363
  shortVariants,
5364
+ showShare,
4467
5365
  slugify,
4468
- b$ as snapshotUrl,
5366
+ cg as snapshotUrl,
4469
5367
  s as specHash,
5368
+ stableCommand,
4470
5369
  startExplorationOnGitHub,
5370
+ stateSentence,
5371
+ summarizeApprovals,
5372
+ summarizeCi,
4471
5373
  summarizeComponent,
4472
- c0 as summarizeTokenChange,
5374
+ ch as summarizeTokenChange,
4473
5375
  takeMine,
4474
- c1 as tarballAssetName,
4475
- c2 as tarballUrl,
4476
- c3 as teamKey,
4477
- c4 as teamKeyOf,
4478
- c5 as teamUnreadableMessage,
4479
- c6 as teamsIn,
5376
+ ci as tarballAssetName,
5377
+ cj as tarballUrl,
5378
+ ck as teamKey,
5379
+ cl as teamKeyOf,
5380
+ cm as teamUnreadableMessage,
5381
+ cn as teamsIn,
4480
5382
  threeWayDiff,
4481
5383
  tintedNeutral,
4482
- c7 as titleOf,
5384
+ co as titleOf,
5385
+ titleWithJiraKey,
4483
5386
  t2 as toAlias,
4484
- c8 as toChangeSummary,
5387
+ cp as toChangeSummary,
4485
5388
  toConfigJson,
4486
5389
  toHex,
4487
- c9 as toMap,
5390
+ cq as toMap,
4488
5391
  toOklch,
4489
5392
  toPlain,
4490
- ca as tokenBump,
5393
+ cr as tokenBump,
4491
5394
  t as tokenCssVar,
4492
- cb as tokenHistory,
4493
- cc as tokenNode,
5395
+ tokenFilesOf,
5396
+ cs as tokenHistory,
5397
+ ct as tokenNode,
4494
5398
  tokenUsage,
4495
5399
  tokensPath,
5400
+ touchesPackages,
5401
+ tryInProductUrl,
5402
+ tryTogetherUrl,
4496
5403
  unpicked,
4497
- cd as unreadableShipTeams,
4498
- ce as unreadableTeams,
5404
+ unreadableShipTeams,
5405
+ cu as unreadableTeams,
4499
5406
  updatePendingDrafts,
4500
5407
  upsertToken,
4501
5408
  utf8ToBase64,
4502
5409
  v as validateComponentSpec,
5410
+ validateDocs,
4503
5411
  validateDocsTask,
4504
5412
  validateIntegrations,
4505
5413
  validateLayered,
4506
5414
  validatePendingDrafts,
4507
5415
  validateProposal,
4508
5416
  validateSet,
4509
- cf as valueIn,
5417
+ cv as valueIn,
4510
5418
  u as valueToCss,
4511
5419
  valuesEqual,
4512
5420
  variantKeyOf,
4513
- cg as variantName,
4514
- ch as versionTimeline,
4515
- ci as visualImpactProblems,
4516
- cj as whoCanShipSentence,
5421
+ cw as variantName,
5422
+ cx as versionTimeline,
5423
+ cy as visualImpactProblems,
5424
+ whoCanShipSentence,
4517
5425
  withRequestExploration,
5426
+ withoutJiraKey,
4518
5427
  writeDraftResults
4519
5428
  };
4520
5429
  //# sourceMappingURL=index.js.map