@bridge-ds/core 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +27 -1
  2. package/bridge.config.schema.json +23 -0
  3. package/dist/audit/index.js +3 -3
  4. package/dist/config.d.ts +28 -0
  5. package/dist/docs-proposal.d.ts +58 -10
  6. package/dist/figma-push-github.d.ts +148 -0
  7. package/dist/figma-push.d.ts +210 -0
  8. package/dist/github-client.d.ts +6 -1
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +1011 -2861
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/git-local.d.ts +15 -0
  13. package/dist/node/index.d.ts +1 -0
  14. package/dist/node/index.js +201 -10
  15. package/dist/node/index.js.map +1 -1
  16. package/dist/node/suggestion-apply-node.d.ts +69 -0
  17. package/dist/{pr-composer-D2SZm7cg.js → prod-branch-BdMPKffE.js} +491 -173
  18. package/dist/prod-branch-BdMPKffE.js.map +1 -0
  19. package/dist/prod-branch-github.d.ts +28 -7
  20. package/dist/prod-branch.d.ts +17 -7
  21. package/dist/{report-DY_peRy4.js → report-DOnKUw8G.js} +2 -2
  22. package/dist/{report-DY_peRy4.js.map → report-DOnKUw8G.js.map} +1 -1
  23. package/dist/suggestion-apply-DxXRGQrO.js +4820 -0
  24. package/dist/suggestion-apply-DxXRGQrO.js.map +1 -0
  25. package/dist/suggestion-apply.d.ts +150 -0
  26. package/dist/suggestion-review.d.ts +70 -0
  27. package/dist/suggestions-github.d.ts +131 -0
  28. package/dist/suggestions.d.ts +250 -0
  29. package/dist/token-proposal.d.ts +51 -0
  30. package/package.json +1 -1
  31. package/dist/component-spec-BzfBkYRD.js +0 -1122
  32. package/dist/component-spec-BzfBkYRD.js.map +0 -1
  33. package/dist/pr-composer-D2SZm7cg.js.map +0 -1
  34. package/dist/prod-branch-D5Tl87Wf.js +0 -266
  35. package/dist/prod-branch-D5Tl87Wf.js.map +0 -1
@@ -1062,8 +1062,8 @@ function groupsPhrase(groups) {
1062
1062
  return `a ${g.join(" or ")} owner`;
1063
1063
  }
1064
1064
  function whoCanShipSentence(policy) {
1065
- const names = (policy.entries ?? policy.logins).map((l) => entryLabel(l, policy.teams)).join(", ");
1066
- return `Only ${groupsPhrase(policy.groups)} can merge this${names ? ` (${names})` : ""}.`;
1065
+ const names2 = (policy.entries ?? policy.logins).map((l) => entryLabel(l, policy.teams)).join(", ");
1066
+ return `Only ${groupsPhrase(policy.groups)} can merge this${names2 ? ` (${names2})` : ""}.`;
1067
1067
  }
1068
1068
  const DEFAULT_VISUAL_IMPACT = { mode: "auto" };
1069
1069
  const VISUAL_CHECK_LABEL = "visual-check";
@@ -1175,6 +1175,8 @@ function sameProducts(a, b) {
1175
1175
  return norm2(a) === norm2(b);
1176
1176
  }
1177
1177
  const CONFIG_FILE = "bridge.config.json";
1178
+ const CHANGES_MODES = ["suggest", "direct"];
1179
+ const DEFAULT_CHANGES_MODE = "suggest";
1178
1180
  const DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
1179
1181
  const DEFAULT_TOKEN_PATHS = {
1180
1182
  root: "tokens",
@@ -1314,6 +1316,8 @@ function defineConfig(input) {
1314
1316
  ...g.rights ? { rights: normalizeRights(g.rights) } : {}
1315
1317
  },
1316
1318
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
1319
+ changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
1320
+ ...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
1317
1321
  ...input.audit ? { audit: { ...input.audit } } : {},
1318
1322
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
1319
1323
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -1436,6 +1440,13 @@ function loadConfig(source) {
1436
1440
  }
1437
1441
  }
1438
1442
  problems.push(...visualImpactProblems(d.visualImpact));
1443
+ if (d.changes !== void 0 && !CHANGES_MODES.includes(d.changes)) problems.push(`"changes" must be "suggest" or "direct" (got ${JSON.stringify(d.changes)})`);
1444
+ const sb = d.storybook;
1445
+ if (sb !== void 0) {
1446
+ if (!isObj(sb) || !isStr(sb.url)) problems.push('"storybook" must be { "url" } with the deployed Storybook address');
1447
+ else if (!/^https:\/\/[^\s/]+/.test(sb.url.trim())) problems.push('"storybook.url" must start with https://, for example https://storybook.example.com');
1448
+ else for (const k of Object.keys(sb)) if (k !== "url") problems.push(`"storybook.${k}" is not a known key`);
1449
+ }
1439
1450
  problems.push(...productProblems(d.products));
1440
1451
  const audit = d.audit;
1441
1452
  if (audit !== void 0) {
@@ -1468,6 +1479,13 @@ function loadConfig(source) {
1468
1479
  const tokensPath = (config, rel = "") => [config.tokens.root === "." ? "" : config.tokens.root, rel].filter(Boolean).join("/");
1469
1480
  const protectedBranchesOf = (config) => [.../* @__PURE__ */ new Set([...config.governance.protectedBranches, ...config.repo ? [config.repo.defaultBranch] : []])];
1470
1481
  const baseBranchOf = (config) => config.repo?.defaultBranch ?? "main";
1482
+ const changesMode = (config) => config?.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE;
1483
+ function storybookUrl(config) {
1484
+ const explicit = config?.storybook?.url?.trim();
1485
+ if (explicit) return explicit.replace(/\/+$/, "");
1486
+ const prod = config?.previews?.production?.storybook?.trim();
1487
+ return prod && /^https:\/\//.test(prod) && !/\{\w+\}/.test(prod) ? prod.replace(/\/+$/, "") : void 0;
1488
+ }
1471
1489
  function toConfigJson(config, schema = "./packages/core/bridge.config.schema.json") {
1472
1490
  return {
1473
1491
  $schema: schema,
@@ -1484,6 +1502,8 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
1484
1502
  ...config.audit ? { audit: config.audit } : {},
1485
1503
  ...config.previews ? { previews: config.previews } : {},
1486
1504
  ...config.visualImpact ? { visualImpact: config.visualImpact } : {},
1505
+ changes: changesMode(config),
1506
+ ...config.storybook ? { storybook: config.storybook } : {},
1487
1507
  ...config.products && !sameProducts(config.products, DEFAULT_PRODUCTS) ? { products: config.products } : {}
1488
1508
  };
1489
1509
  }
@@ -2193,7 +2213,7 @@ function indexByPath(tokens) {
2193
2213
  for (const t of tokens) m.set(t.path, t);
2194
2214
  return m;
2195
2215
  }
2196
- const has = (segments, ...names) => names.some((n) => segments.includes(n));
2216
+ const has = (segments, ...names2) => names2.some((n) => segments.includes(n));
2197
2217
  function defaultScopes(path, type, tier) {
2198
2218
  const s = path.split(".");
2199
2219
  switch (type) {
@@ -2376,8 +2396,8 @@ function contrastScope(set) {
2376
2396
  overrides: set.overrideIndex
2377
2397
  };
2378
2398
  }
2379
- const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2380
- const contextLabel = (r) => r.brand ? `${cap(r.brand)} ${r.theme}` : cap(r.theme);
2399
+ const cap$1 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2400
+ const contextLabel = (r) => r.brand ? `${cap$1(r.brand)} ${r.theme}` : cap$1(r.theme);
2381
2401
  function renderedColor(value) {
2382
2402
  const c = parseColor(value);
2383
2403
  const hex = value && typeof value === "object" ? value.hex : void 0;
@@ -2600,22 +2620,296 @@ ${body}
2600
2620
 
2601
2621
  ${footer}`;
2602
2622
  }
2623
+ const PIECES_MARK = "bridge-pieces";
2624
+ const NEEDED_MARK = "bridge-needed-by";
2625
+ const SECTION = "### Missing from the library";
2626
+ const NEEDED_BY_LABEL = "needed-by-product";
2627
+ const hiddenJson = (mark, data) => `<!-- ${mark} ${JSON.stringify(data).replace(/-->/g, "--\\u003e")} -->`;
2628
+ const readHidden = (mark, body) => {
2629
+ if (!body) return void 0;
2630
+ const m = new RegExp(`<!-- ${mark} (\\{[\\s\\S]*?\\}) -->`).exec(body);
2631
+ if (!m) return void 0;
2632
+ try {
2633
+ return JSON.parse(m[1]);
2634
+ } catch {
2635
+ return void 0;
2636
+ }
2637
+ };
2638
+ const KIND_WORD = { "token-change": "token change", exploration: "library exploration", request: "component request", suggestion: "suggested token change" };
2639
+ function parsePiecesRecord(body) {
2640
+ const r = readHidden(PIECES_MARK, body);
2641
+ if (!r || !Array.isArray(r.pieces) || !Array.isArray(r.links) || typeof r.checkedAt !== "string") return void 0;
2642
+ return r;
2643
+ }
2644
+ function piecesSection(r) {
2645
+ const link = (p) => r.links.find((l) => l.piece === p.id);
2646
+ const lines = r.pieces.length ? r.pieces.map((p) => {
2647
+ const l = link(p);
2648
+ return `- **${p.title}**: ${p.summary}${l ? ` ${cap(KIND_WORD[l.kind])}: #${l.number}.` : " No library branch yet."}`;
2649
+ }) : ["- Nothing: every value and component comes from the library."];
2650
+ return [SECTION, "", `Checked ${r.checkedAt.slice(0, 10)}${r.headSha ? ` on ${r.headSha.slice(0, 7)}` : ""} by Bridge.`, "", ...lines, "", hiddenJson(PIECES_MARK, r)].join("\n");
2651
+ }
2652
+ function withPiecesRecord(body, r) {
2653
+ const b = (body ?? "").trimEnd();
2654
+ const start = b.indexOf(SECTION);
2655
+ const markAt = b.indexOf(`<!-- ${PIECES_MARK} `);
2656
+ if (start >= 0 && markAt > start) {
2657
+ const end = b.indexOf("-->", markAt);
2658
+ return `${b.slice(0, start)}${piecesSection(r)}${end >= 0 ? b.slice(end + 3) : ""}`.trimEnd();
2659
+ }
2660
+ return `${b}${b ? "\n\n" : ""}${piecesSection(r)}`;
2661
+ }
2662
+ function mergePiecesRecord(prev, next) {
2663
+ const links = [...prev?.links ?? []];
2664
+ for (const l of next.links ?? []) {
2665
+ const at = links.findIndex((x) => x.piece === l.piece);
2666
+ if (at >= 0) links[at] = l;
2667
+ else links.push(l);
2668
+ }
2669
+ return { ...next, links };
2670
+ }
2671
+ function withLink(r, l) {
2672
+ return { ...r, links: [...r.links.filter((x) => x.piece !== l.piece), l] };
2673
+ }
2674
+ function neededBySection(n) {
2675
+ const who = n.author ? ` for @${n.author}` : "";
2676
+ const near = n.nearest ? ` The closest the library has today is ${n.nearest}${n.nearestToken ? ` (\`${n.nearestToken}\`)` : ""}.` : "";
2677
+ return [`**Needed by:** [${n.title}](${n.url}) (#${n.pr}) in ${n.productName}${who}.${near}`, "", hiddenJson(NEEDED_MARK, n)].join("\n");
2678
+ }
2679
+ function parseNeededBy(body) {
2680
+ const n = readHidden(NEEDED_MARK, body);
2681
+ return n && typeof n.pr === "number" && typeof n.title === "string" && typeof n.piece === "string" ? n : void 0;
2682
+ }
2683
+ function withNeededBy(body, n) {
2684
+ let b = (body ?? "").trim();
2685
+ const at = b.indexOf("**Needed by:**");
2686
+ const mark = b.indexOf(`<!-- ${NEEDED_MARK} `);
2687
+ if (at >= 0 && mark > at) {
2688
+ const end = b.indexOf("-->", mark);
2689
+ b = `${b.slice(0, at)}${b.slice(end + 3)}`.trim();
2690
+ }
2691
+ return `${neededBySection(n)}${b ? `
2692
+
2693
+ ${b}` : ""}`;
2694
+ }
2695
+ const cap = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
2696
+ const forLine = (b) => `${b.productName} needs it for "${b.title.replace(/"/g, "'")}" (#${b.pr})`;
2697
+ function libraryBranchPlan(piece, branch, opts = {}) {
2698
+ const neededBy = {
2699
+ pr: branch.pr,
2700
+ title: branch.title,
2701
+ branch: branch.branch,
2702
+ url: branch.url,
2703
+ product: branch.productKey,
2704
+ productName: branch.productName,
2705
+ ...branch.author ? { author: branch.author } : {},
2706
+ piece: piece.id,
2707
+ pieceTitle: piece.title,
2708
+ ...piece.nearest ? { nearest: piece.nearest.label, nearestToken: piece.nearest.token } : {}
2709
+ };
2710
+ const near = piece.nearest ? ` The closest the library has today is ${piece.nearest.label} (${piece.nearest.token}, ${piece.nearest.value}).` : "";
2711
+ if (piece.kind === "token" && piece.token) {
2712
+ const t = piece.token;
2713
+ const files = t.files.length ? t.files : [void 0];
2714
+ const edits = files.map((file) => ({
2715
+ op: "add",
2716
+ path: t.path,
2717
+ type: t.type,
2718
+ value: t.value,
2719
+ description: `${cap(piece.name)}: ${forLine(branch)}.${files.length > 1 ? " Same value in every theme: review it in each." : ""}`,
2720
+ ...file ? { file } : {}
2721
+ }));
2722
+ return { kind: "token-change", title: `Add ${t.path}`, summary: `${forLine(branch)}: ${piece.summary}${near}`, edits, neededBy };
2723
+ }
2724
+ if (piece.kind === "variant" && piece.component && opts.mode === "suggest") {
2725
+ const request2 = {
2726
+ component: piece.component,
2727
+ kind: "new-variant",
2728
+ title: piece.title,
2729
+ summary: `${forLine(branch)}: ${piece.summary}${near}`,
2730
+ changes: piece.values.slice(0, 6).map((v) => ({ field: v.property ?? v.category, after: v.value })),
2731
+ source: opts.source ?? "mcp",
2732
+ ...opts.requestedBy ? { requestedBy: opts.requestedBy } : {}
2733
+ };
2734
+ return { kind: "request", title: piece.title, request: request2, neededBy };
2735
+ }
2736
+ if (piece.kind === "variant" && piece.component) {
2737
+ const values = piece.values.map((v) => `${v.property ?? v.category} ${v.value}`).join(", ");
2738
+ const exploration = {
2739
+ name: piece.title,
2740
+ purpose: `${forLine(branch)}: ${/^[aeiou]/i.test(piece.name) ? "an" : "a"} ${piece.name} ${piece.aspect ?? "style"} on ${piece.component} (${values}).${near}`,
2741
+ useCase: "terminal",
2742
+ experimental: [piece.title],
2743
+ source: opts.source === "figma" ? "figma" : opts.source ?? "mcp"
2744
+ };
2745
+ const brief = [
2746
+ `Add the ${piece.name} ${piece.aspect ?? "style"} to ${piece.component} in the library, on this exploration branch.`,
2747
+ `The product branch writes it by hand: ${values}. Express it with tokens (a component or semantic token per value), add it to the component's props, docs and examples, and keep every existing ${piece.component} unchanged.`,
2748
+ piece.nearest ? `Say in the pull request why ${piece.nearest.label} is not enough, or recommend it instead.` : ""
2749
+ ].filter(Boolean).join(" ");
2750
+ return { kind: "exploration", title: piece.title, exploration, neededBy, brief };
2751
+ }
2752
+ const name = piece.component ?? cap(piece.name);
2753
+ const request = {
2754
+ component: name,
2755
+ kind: "new-component",
2756
+ title: `Add ${name}`,
2757
+ summary: `${forLine(branch)}: ${piece.summary}${near}`,
2758
+ changes: piece.values.slice(0, 6).map((v) => ({ field: v.property ?? v.category, after: v.value })),
2759
+ source: opts.source ?? "mcp",
2760
+ ...opts.requestedBy ? { requestedBy: opts.requestedBy } : {}
2761
+ };
2762
+ return { kind: "request", title: `New component: ${name}`, request, neededBy };
2763
+ }
2764
+ const STATUS_LABEL = {
2765
+ draft: "Draft",
2766
+ "in-review": "In review",
2767
+ changes: "Changes requested",
2768
+ approved: "Approved",
2769
+ shipped: "Shipped",
2770
+ closed: "Closed",
2771
+ exploring: "Being built",
2772
+ requested: "Requested",
2773
+ "in-progress": "In progress",
2774
+ done: "Done",
2775
+ declined: "Declined",
2776
+ outdated: "Outdated"
2777
+ };
2778
+ const LINK_STATUS_TONE = {
2779
+ draft: "neutral",
2780
+ "in-review": "info",
2781
+ changes: "warning",
2782
+ approved: "success",
2783
+ shipped: "success",
2784
+ closed: "neutral",
2785
+ exploring: "neutral",
2786
+ requested: "neutral",
2787
+ "in-progress": "info",
2788
+ done: "success",
2789
+ declined: "neutral",
2790
+ outdated: "warning"
2791
+ };
2792
+ const names = (people2) => people2.length <= 2 ? people2.map((p) => `@${p}`).join(" and ") : `@${people2[0]} and ${people2.length - 1} more`;
2793
+ const SUGGESTION_LINK_STATUS = {
2794
+ "needs-review": "in-review",
2795
+ "changes-requested": "changes",
2796
+ approved: "approved",
2797
+ applying: "in-progress",
2798
+ applied: "shipped",
2799
+ declined: "declined",
2800
+ outdated: "outdated"
2801
+ };
2802
+ function linkStatus(x) {
2803
+ const make = (key, people2 = [], verb = "reviews") => {
2804
+ const label = STATUS_LABEL[key];
2805
+ const tail = people2.length ? ` · ${names(people2)} ${people2.length === 1 ? verb : verb.replace(/s$/, "")}` : "";
2806
+ return { key, label, people: people2, line: `${label}${tail}` };
2807
+ };
2808
+ if (x.kind === "suggestion") {
2809
+ const key = SUGGESTION_LINK_STATUS[x.status];
2810
+ return make(key, key === "in-progress" ? [...x.assignees ?? []] : [], "builds");
2811
+ }
2812
+ if (x.kind === "request") {
2813
+ if (x.state === "closed") return make(x.stateReason === "not_planned" ? "declined" : "done");
2814
+ return x.implemented ? make("in-progress", [...x.assignees ?? []], "builds") : make("requested", [...x.assignees ?? []], "builds");
2815
+ }
2816
+ if (x.state === "merged") return make("shipped");
2817
+ if (x.state !== "open") return make("closed");
2818
+ if ((x.labels ?? []).includes("exploration")) return make("exploring");
2819
+ if (x.draft) return make("draft");
2820
+ const latest = /* @__PURE__ */ new Map();
2821
+ for (const r of x.reviews ?? []) if (r.user && r.state !== "COMMENTED") latest.set(r.user, r.state);
2822
+ const states = [...latest.values()];
2823
+ if (states.includes("CHANGES_REQUESTED")) return make("changes", [...latest].filter(([, s]) => s === "CHANGES_REQUESTED").map(([u]) => u), "asks");
2824
+ const waiting = [...x.requestedReviewers ?? []];
2825
+ if (states.includes("APPROVED") && !waiting.length) return make("approved");
2826
+ return make("in-review", waiting);
2827
+ }
2828
+ function libraryPreviewState(x) {
2829
+ if (x.kind === "suggestion") {
2830
+ if (x.lifecycle === "closed") return { key: "declined", sentence: `The library declined this suggestion.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ""}`, action: null };
2831
+ if (x.lifecycle === "merged") return x.behindMain === 0 ? { key: "switched", sentence: "Your branch uses the published version.", action: null } : { key: "published", sentence: "The library applied the suggestion. Switch your branch to the published version.", action: { kind: "switch", label: "Switch to the published version" } };
2832
+ return { key: "nothing-yet", sentence: "Nothing to preview yet: owners review the suggestion, then a developer applies it. Your branch keeps its own value until then.", action: null };
2833
+ }
2834
+ if (x.kind === "request") {
2835
+ if (x.lifecycle === "closed") return { key: "declined", sentence: `The library declined this request.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ""}`, action: null };
2836
+ if (x.lifecycle === "merged") return x.behindMain === 0 ? { key: "switched", sentence: "Your branch uses the published version.", action: null } : { key: "published", sentence: "The library built it. Switch your branch to the published version, then ask Claude to use it.", action: { kind: "switch", label: "Switch to the published version" } };
2837
+ return { key: "nothing-yet", sentence: "Nothing to preview yet: the library builds it first. Your branch keeps its own version until then.", action: null };
2838
+ }
2839
+ if (x.lifecycle === "merged") {
2840
+ if (x.behindMain === 0) return { key: "switched", sentence: "Your branch uses the published version.", action: null };
2841
+ return { key: "published", sentence: "The library published it. Switch your branch to the published version.", action: { kind: "switch", label: "Switch to the published version" } };
2842
+ }
2843
+ if (x.lifecycle === "closed") return { key: "declined", sentence: `The library closed it without shipping.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ""}`, action: null };
2844
+ if (!x.hasChanges) return { key: "nothing-yet", sentence: "The library branch has no change yet. Claude adds it there, then your branch can use its preview.", action: null };
2845
+ if (x.missing === 0) return { key: "preview", sentence: "Your branch uses a preview of the library branch. When the library publishes it, switch to the real version here in one click.", action: null };
2846
+ if (x.missing !== null && x.total !== null && x.missing < x.total) return { key: "outdated", sentence: "The library branch changed since your branch took its preview.", action: { kind: "refresh-preview", label: "Get the latest preview" } };
2847
+ return { key: "not-used", sentence: "Your branch does not use the library branch yet.", action: { kind: "use-preview", label: "Use its preview in my branch" } };
2848
+ }
2849
+ const nPieces = (n) => `${n} ${n === 1 ? "piece" : "pieces"}`;
2850
+ function productBranchSentence(r, opts = {}) {
2851
+ if (!r) return { lead: "Not checked against the library yet.", strong: "", rest: " Ask Claude what is missing from the library." };
2852
+ const n = r.pieces.length;
2853
+ if (!n) return { lead: "Built with the library.", strong: " Nothing is missing", rest: ": every value and component comes from it." };
2854
+ const linked = r.pieces.filter((p) => r.links.some((l) => l.piece === p.id));
2855
+ const openers = [...new Set(linked.map((p) => r.links.find((l) => l.piece === p.id)).map((l) => l.agent ? opts.agentName ?? "Claude AI" : l.openedBy && l.openedBy === opts.viewer ? "you" : l.openedBy ? `@${l.openedBy}` : "someone"))];
2856
+ const by = openers.length === 1 ? openers[0] : "your team";
2857
+ const was = n === 1 ? "was" : "were";
2858
+ if (linked.length === n) {
2859
+ const what = n === 1 ? "a library branch for it" : "library branches for them";
2860
+ return { lead: "Built with the library. ", strong: `${nPieces(n)} ${was} missing`, rest: `, and ${by} opened ${what}.` };
2861
+ }
2862
+ if (!linked.length) return { lead: "Built with the library. ", strong: `${nPieces(n)} ${n === 1 ? "is" : "are"} missing`, rest: n === 1 ? ": open a library branch for it." : ": open a library branch for each." };
2863
+ return { lead: "Built with the library. ", strong: `${nPieces(n)} ${was} missing`, rest: `: ${linked.length} ${linked.length === 1 ? "has" : "have"} a library branch, ${n - linked.length} still ${n - linked.length === 1 ? "needs" : "need"} one.` };
2864
+ }
2865
+ function whyItExists(n, opts = {}) {
2866
+ const who = n.author ? opts.names?.[n.author] ?? `@${n.author}` : "A product team";
2867
+ return { who, title: n.title, product: n.productName };
2868
+ }
2869
+ function nearestQuestion(n, opts = {}) {
2870
+ if (!n.nearest) return null;
2871
+ const who = n.author ? opts.names?.[n.author] ?? `@${n.author}` : "the product team";
2872
+ return `It looks a lot like "${n.nearest}". Is a new one worth it, or should ${who} use ${n.nearest}?`;
2873
+ }
2874
+ function suggestNearestComment(n) {
2875
+ if (!n.nearest) return null;
2876
+ return `Let's not add it to the library: ${n.nearest}${n.nearestToken ? ` (\`${n.nearestToken}\`)` : ""} covers it. Use it on ${n.title} (#${n.pr}) instead, then close this branch.`;
2877
+ }
2878
+ function pieceId(parts) {
2879
+ return slugify(parts.filter(Boolean).join(" "), 48);
2880
+ }
2881
+ const TONE_WORDS = ["neutral", "success", "warning", "danger", "info", "error", "critical", "accent", "brand", "primary", "secondary"];
2882
+ function tokenLabel(path) {
2883
+ const parts = path.split(".");
2884
+ const tone = parts.find((p) => TONE_WORDS.includes(p));
2885
+ if (tone) return tone;
2886
+ const meaningful = parts.filter((p) => !/^(color|bg|fg|text|border|default|base|value|\d+)$/.test(p));
2887
+ return meaningful[meaningful.length - 1] ?? parts[parts.length - 1] ?? path;
2888
+ }
2889
+ function proposedTokenPath(nearest, name, category) {
2890
+ const slug = slugify(name, 32);
2891
+ if (!nearest) return `${category === "color" ? "color" : category}.${slug}`;
2892
+ const parts = nearest.split(".");
2893
+ const toneAt = parts.findIndex((p) => TONE_WORDS.includes(p));
2894
+ if (toneAt > 0) return [...parts.slice(0, toneAt), slug, ...parts.slice(toneAt + 1)].join(".");
2895
+ return [...parts.slice(0, -1), slug].join(".");
2896
+ }
2603
2897
  export {
2604
- fromPlain as $,
2605
- effectiveScopes as A,
2606
- sameScopes as B,
2607
- defaultScopes as C,
2608
- readTokenAt as D,
2898
+ layoutOptions as $,
2899
+ formatValue as A,
2900
+ resolve as B,
2901
+ moveToken as C,
2902
+ effectiveScopes as D,
2609
2903
  EXT_ID as E,
2610
- modeFile as F,
2611
- defaultFile as G,
2612
- CONFIG_FILE as H,
2613
- loadConfig as I,
2614
- ConfigError as J,
2615
- DEFAULT_TOKEN_PATHS as K,
2616
- selectTokenFiles as L,
2617
- LayeredTokenSet as M,
2618
- resolveBranchPreviews as N,
2904
+ sameScopes as F,
2905
+ defaultScopes as G,
2906
+ readTokenAt as H,
2907
+ modeFile as I,
2908
+ defaultFile as J,
2909
+ selectTokenFiles as K,
2910
+ LayeredTokenSet as L,
2911
+ resolveBranchPreviews as M,
2912
+ loadConfig as N,
2619
2913
  tokensPath as O,
2620
2914
  toConfigJson as P,
2621
2915
  validateLayered as Q,
@@ -2623,166 +2917,190 @@ export {
2623
2917
  SINGLE_MODE as S,
2624
2918
  TIER_NAMES as T,
2625
2919
  classifyRisk as U,
2626
- RISK_LABELS as V,
2627
- parseJson as W,
2628
- serializeJson as X,
2629
- plainEqual as Y,
2630
- toPlain as Z,
2631
- getEntry as _,
2632
- modesOf as a,
2633
- entryLabel as a$,
2634
- previewLinks as a0,
2635
- slugify as a1,
2636
- readPreviewComment as a2,
2637
- findPreviewComment as a3,
2638
- previewsSettled as a4,
2639
- canShip as a5,
2640
- whoCanShipSentence as a6,
2641
- unreadableShipTeams as a7,
2642
- layoutOptions as a8,
2643
- buildTokenSet as a9,
2644
- aliasOf as aA,
2645
- appForProject as aB,
2646
- appUrl as aC,
2647
- approverEntries as aD,
2648
- approverLists as aE,
2649
- baseBranchOf as aF,
2650
- branchName as aG,
2651
- branchSlug as aH,
2652
- changeKind as aI,
2653
- changedModes as aJ,
2654
- clearRawDeep as aK,
2655
- colorToCss as aL,
2656
- colorsEqual as aM,
2657
- comboKey as aN,
2658
- commitMessage as aO,
2659
- compactJson as aP,
2660
- composePrBody as aQ,
2661
- composite as aR,
2662
- contextFor as aS,
2663
- contrastLevel as aT,
2664
- contrastPair as aU,
2665
- contrastRatio as aV,
2666
- defaultContext as aW,
2667
- defaultTitle as aX,
2668
- defineConfig as aY,
2669
- detectLayout as aZ,
2670
- entriesFor as a_,
2671
- productOfBranch as aa,
2672
- defaultProduct as ab,
2673
- AAA_MIN as ac,
2674
- AA_MIN as ad,
2675
- CONTRAST_CELL_MAX as ae,
2676
- CONTRAST_CHECKS_FILE as af,
2677
- DEFAULT_GOVERNANCE as ag,
2678
- DEFAULT_OVERRIDE as ah,
2679
- DEFAULT_PRODUCTS as ai,
2680
- DEFAULT_RIGHTS as aj,
2681
- DEFAULT_VISUAL_IMPACT as ak,
2682
- DTCG_TYPES as al,
2683
- JsonParseError as am,
2684
- OWNER_GROUPS as an,
2685
- PREVIEW_APP_LABELS as ao,
2686
- PREVIEW_COMMENT_MARKER as ap,
2687
- PREVIEW_PLACEHOLDERS as aq,
2688
- PRIMITIVE_MODE as ar,
2689
- PRODUCT_LABEL_PREFIX as as,
2690
- RIGHTS as at,
2691
- RIGHT_LABEL as au,
2692
- RISK_TIER as av,
2693
- TIER_ORDER as aw,
2694
- TeamDirectory as ax,
2695
- VISUAL_CHECK_LABEL as ay,
2696
- affectedContrast as az,
2697
- TIER_AXIS as b,
2698
- valueToCss as b$,
2699
- expandEntries as b0,
2700
- expandOwners as b1,
2701
- fieldChanges as b2,
2702
- findProduct as b3,
2703
- findToken as b4,
2704
- findVercelComment as b5,
2705
- flattenLayerFile as b6,
2706
- formatNumber as b7,
2707
- formatOwnerEntry as b8,
2708
- getChild as b9,
2709
- productForPlace as bA,
2710
- productLabel as bB,
2711
- productMarks as bC,
2712
- productProblems as bD,
2713
- productsOf as bE,
2714
- projectOfLabel as bF,
2715
- protectedBranchesOf as bG,
2716
- renderedColor as bH,
2717
- rgbaToDtcg as bI,
2718
- rightsProblems as bJ,
2719
- sameProducts as bK,
2720
- sameToken as bL,
2721
- shipPolicy as bM,
2722
- snapshotUrl as bN,
2723
- summarizeTokenChange as bO,
2724
- tarballAssetName as bP,
2725
- tarballUrl as bQ,
2726
- teamKey as bR,
2727
- teamKeyOf as bS,
2728
- teamUnreadableMessage as bT,
2729
- teamsIn as bU,
2730
- toAlias as bV,
2731
- toMap as bW,
2732
- tokenBump as bX,
2733
- tokenNode as bY,
2734
- unreadableTeams as bZ,
2735
- valueIn as b_,
2736
- globToRegExp as ba,
2737
- groupsPhrase as bb,
2738
- hexToRgba as bc,
2739
- holdersOf as bd,
2740
- indexByPath as be,
2741
- inheritedTypeAt as bf,
2742
- installCommand as bg,
2743
- installCommandFromPreviewComment as bh,
2744
- isGuardrail as bi,
2745
- isTeamEntry as bj,
2746
- isTokenFile as bk,
2747
- isVercelComment as bl,
2748
- kindForMin as bm,
2749
- layeredParityIssues as bn,
2750
- luminance as bo,
2751
- nodesEqual as bp,
2752
- normalizeRights as bq,
2753
- overrideKey as br,
2754
- ownerEntryProblems as bs,
2755
- pairKey as bt,
2756
- parseDimension as bu,
2757
- parseOwnerEntry as bv,
2758
- parseVercelComment as bw,
2759
- previewComment as bx,
2760
- previewLinksOf as by,
2761
- previewUrl as bz,
2762
- collectRefs as c,
2763
- visualImpactProblems as c0,
2764
- contrastScope as d,
2920
+ CONFIG_FILE as V,
2921
+ RISK_LABELS as W,
2922
+ previewLinks as X,
2923
+ findPreviewComment as Y,
2924
+ previewsSettled as Z,
2925
+ DEFAULT_TOKEN_PATHS as _,
2926
+ proposedTokenPath as a,
2927
+ contextFor as a$,
2928
+ buildTokenSet as a0,
2929
+ readPreviewComment as a1,
2930
+ linkStatus as a2,
2931
+ libraryPreviewState as a3,
2932
+ parsePiecesRecord as a4,
2933
+ libraryBranchPlan as a5,
2934
+ NEEDED_BY_LABEL as a6,
2935
+ withNeededBy as a7,
2936
+ withLink as a8,
2937
+ withPiecesRecord as a9,
2938
+ RIGHTS as aA,
2939
+ RIGHT_LABEL as aB,
2940
+ RISK_TIER as aC,
2941
+ TIER_ORDER as aD,
2942
+ TeamDirectory as aE,
2943
+ VISUAL_CHECK_LABEL as aF,
2944
+ affectedContrast as aG,
2945
+ aliasOf as aH,
2946
+ appForProject as aI,
2947
+ appUrl as aJ,
2948
+ approverEntries as aK,
2949
+ approverLists as aL,
2950
+ baseBranchOf as aM,
2951
+ branchName as aN,
2952
+ branchSlug as aO,
2953
+ canShip as aP,
2954
+ changeKind as aQ,
2955
+ changedModes as aR,
2956
+ changesMode as aS,
2957
+ clearRawDeep as aT,
2958
+ colorToCss as aU,
2959
+ colorsEqual as aV,
2960
+ comboKey as aW,
2961
+ commitMessage as aX,
2962
+ compactJson as aY,
2963
+ composePrBody as aZ,
2964
+ composite as a_,
2965
+ productBranchSentence as aa,
2966
+ mergePiecesRecord as ab,
2967
+ productOfBranch as ac,
2968
+ defaultProduct as ad,
2969
+ slugify as ae,
2970
+ AAA_MIN as af,
2971
+ AA_MIN as ag,
2972
+ CHANGES_MODES as ah,
2973
+ CONTRAST_CELL_MAX as ai,
2974
+ CONTRAST_CHECKS_FILE as aj,
2975
+ ConfigError as ak,
2976
+ DEFAULT_CHANGES_MODE as al,
2977
+ DEFAULT_GOVERNANCE as am,
2978
+ DEFAULT_OVERRIDE as an,
2979
+ DEFAULT_PRODUCTS as ao,
2980
+ DEFAULT_RIGHTS as ap,
2981
+ DEFAULT_VISUAL_IMPACT as aq,
2982
+ DTCG_TYPES as ar,
2983
+ JsonParseError as as,
2984
+ LINK_STATUS_TONE as at,
2985
+ OWNER_GROUPS as au,
2986
+ PREVIEW_APP_LABELS as av,
2987
+ PREVIEW_COMMENT_MARKER as aw,
2988
+ PREVIEW_PLACEHOLDERS as ax,
2989
+ PRIMITIVE_MODE as ay,
2990
+ PRODUCT_LABEL_PREFIX as az,
2991
+ modesOf as b,
2992
+ sameProducts as b$,
2993
+ contrastLevel as b0,
2994
+ contrastPair as b1,
2995
+ contrastRatio as b2,
2996
+ defaultContext as b3,
2997
+ defaultTitle as b4,
2998
+ defineConfig as b5,
2999
+ detectLayout as b6,
3000
+ entriesFor as b7,
3001
+ entryLabel as b8,
3002
+ expandEntries as b9,
3003
+ nearestQuestion as bA,
3004
+ neededBySection as bB,
3005
+ nodesEqual as bC,
3006
+ normalizeRights as bD,
3007
+ overrideKey as bE,
3008
+ ownerEntryProblems as bF,
3009
+ pairKey as bG,
3010
+ parseDimension as bH,
3011
+ parseJson as bI,
3012
+ parseNeededBy as bJ,
3013
+ parseOwnerEntry as bK,
3014
+ parseVercelComment as bL,
3015
+ piecesSection as bM,
3016
+ plainEqual as bN,
3017
+ previewComment as bO,
3018
+ previewLinksOf as bP,
3019
+ previewUrl as bQ,
3020
+ productForPlace as bR,
3021
+ productLabel as bS,
3022
+ productMarks as bT,
3023
+ productProblems as bU,
3024
+ productsOf as bV,
3025
+ projectOfLabel as bW,
3026
+ protectedBranchesOf as bX,
3027
+ renderedColor as bY,
3028
+ rgbaToDtcg as bZ,
3029
+ rightsProblems as b_,
3030
+ expandOwners as ba,
3031
+ fieldChanges as bb,
3032
+ findProduct as bc,
3033
+ findToken as bd,
3034
+ findVercelComment as be,
3035
+ flattenLayerFile as bf,
3036
+ formatNumber as bg,
3037
+ formatOwnerEntry as bh,
3038
+ fromPlain as bi,
3039
+ getChild as bj,
3040
+ getEntry as bk,
3041
+ globToRegExp as bl,
3042
+ groupsPhrase as bm,
3043
+ hexToRgba as bn,
3044
+ holdersOf as bo,
3045
+ indexByPath as bp,
3046
+ inheritedTypeAt as bq,
3047
+ installCommand as br,
3048
+ installCommandFromPreviewComment as bs,
3049
+ isGuardrail as bt,
3050
+ isTeamEntry as bu,
3051
+ isTokenFile as bv,
3052
+ isVercelComment as bw,
3053
+ kindForMin as bx,
3054
+ layeredParityIssues as by,
3055
+ luminance as bz,
3056
+ TIER_AXIS as c,
3057
+ sameToken as c0,
3058
+ serializeJson as c1,
3059
+ shipPolicy as c2,
3060
+ snapshotUrl as c3,
3061
+ storybookUrl as c4,
3062
+ suggestNearestComment as c5,
3063
+ summarizeTokenChange as c6,
3064
+ tarballAssetName as c7,
3065
+ tarballUrl as c8,
3066
+ teamKey as c9,
3067
+ teamKeyOf as ca,
3068
+ teamUnreadableMessage as cb,
3069
+ teamsIn as cc,
3070
+ toAlias as cd,
3071
+ toMap as ce,
3072
+ toPlain as cf,
3073
+ tokenBump as cg,
3074
+ tokenNode as ch,
3075
+ unreadableShipTeams as ci,
3076
+ unreadableTeams as cj,
3077
+ valueIn as ck,
3078
+ valueToCss as cl,
3079
+ visualImpactProblems as cm,
3080
+ whoCanShipSentence as cn,
3081
+ whyItExists as co,
3082
+ collectRefs as d,
2765
3083
  evaluatePairs as e,
2766
3084
  findReferenceProblems as f,
2767
- contextLabel as g,
2768
- isAlias as h,
3085
+ contrastScope as g,
3086
+ contextLabel as h,
2769
3087
  isMultiMode as i,
2770
- aliasTarget as j,
2771
- flattenTokens as k,
2772
- emptyTokenFile as l,
3088
+ isAlias as j,
3089
+ aliasTarget as k,
3090
+ pathId as l,
2773
3091
  modeDisplayName as m,
2774
- parseTokenFile as n,
2775
- EXT_SCOPES as o,
2776
- pathId as p,
2777
- parseColor as q,
3092
+ flattenTokens as n,
3093
+ threeWayDiff as o,
3094
+ pieceId as p,
3095
+ emptyTokenFile as q,
2778
3096
  removeToken as r,
2779
3097
  serializeTokenFile as s,
2780
- threeWayDiff as t,
3098
+ tokenLabel as t,
2781
3099
  upsertToken as u,
2782
3100
  valuesEqual as v,
2783
- rgbaToHex as w,
2784
- formatValue as x,
2785
- resolve as y,
2786
- moveToken as z
3101
+ parseTokenFile as w,
3102
+ EXT_SCOPES as x,
3103
+ parseColor as y,
3104
+ rgbaToHex as z
2787
3105
  };
2788
- //# sourceMappingURL=pr-composer-D2SZm7cg.js.map
3106
+ //# sourceMappingURL=prod-branch-BdMPKffE.js.map