@bridge-ds/mcp 0.8.5 → 0.10.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.
@@ -171,7 +171,7 @@ function resolveBranchPreviews(apps, ctx, sources = {}) {
171
171
  }
172
172
  return out;
173
173
  }
174
- var previewsSettled = (previews) => previews.every((p) => !!p.url && (p.state === "ready" || p.state === "failed"));
174
+ var previewsSettled = (previews) => previews.every((p) => p.state === "failed" || !!p.url && p.state === "ready");
175
175
 
176
176
  // ../core/src/teams.ts
177
177
  var LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
@@ -578,6 +578,12 @@ var CONFIG_FILE = "bridge.config.json";
578
578
  var CHANGES_MODES = ["suggest", "direct"];
579
579
  var DEFAULT_CHANGES_MODE = "suggest";
580
580
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
581
+ var TEAM_NAME_MAX = 60;
582
+ function teamName(value) {
583
+ if (typeof value !== "string") return void 0;
584
+ const v = value.trim().replace(/\s+/g, " ");
585
+ return v && v.length <= TEAM_NAME_MAX ? v : void 0;
586
+ }
581
587
  var DEFAULT_TOKEN_PATHS = {
582
588
  root: "tokens",
583
589
  primitives: "primitives",
@@ -683,6 +689,7 @@ function defineConfig(input) {
683
689
  const g = input.governance ?? {};
684
690
  const contrastFile = contrastPath ?? [root === "." ? "" : root, paths.contrastChecks].filter(Boolean).join("/");
685
691
  return {
692
+ ...teamName(input.name) ? { name: teamName(input.name) } : {},
686
693
  repo: parseRepoInput(input.repo, input.branch),
687
694
  tokens: { ...paths, include: input.tokens?.include ?? ["**/*.tokens.json"], exclude: input.tokens?.exclude ?? [], tiers, layers, ...t.usage ? { usage: trimSlashes(t.usage) } : {} },
688
695
  axes,
@@ -715,12 +722,14 @@ function defineConfig(input) {
715
722
  }
716
723
  } : {},
717
724
  ...g.rights ? { rights: normalizeRights(g.rights) } : {},
718
- ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {}
725
+ ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {},
726
+ ...g.leadsPublishTier1 === false ? { leadsPublishTier1: false } : {}
719
727
  },
720
728
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
721
729
  changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
722
730
  ...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
723
731
  ...input.library?.public === true ? { library: { public: true } } : {},
732
+ ...input.demo?.viewer?.trim() ? { demo: { viewer: input.demo.viewer.trim(), ...input.demo.role?.trim() ? { role: input.demo.role.trim() } : {} } } : {},
724
733
  ...input.audit ? { audit: { ...input.audit } } : {},
725
734
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
726
735
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -842,9 +851,11 @@ function loadConfig(source) {
842
851
  }
843
852
  problems.push(...rightsProblems(gov.rights));
844
853
  if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
854
+ if (gov.leadsPublishTier1 !== void 0 && typeof gov.leadsPublishTier1 !== "boolean") problems.push('"governance.leadsPublishTier1" must be true or false');
845
855
  }
846
856
  }
847
857
  problems.push(...visualImpactProblems(d.visualImpact));
858
+ if (d.name !== void 0 && teamName(d.name) === void 0) problems.push(`"name" must be the team's name, 1 to ${TEAM_NAME_MAX} characters`);
848
859
  if (d.changes !== void 0 && !CHANGES_MODES.includes(d.changes)) problems.push(`"changes" must be "suggest" or "direct" (got ${JSON.stringify(d.changes)})`);
849
860
  const sb = d.storybook;
850
861
  if (sb !== void 0) {
@@ -860,6 +871,14 @@ function loadConfig(source) {
860
871
  for (const k of Object.keys(lib)) if (k !== "public") problems.push(`"library.${k}" is not a known key`);
861
872
  }
862
873
  }
874
+ const demo = d.demo;
875
+ if (demo !== void 0) {
876
+ if (!isObj(demo) || !isStr(demo.viewer) || !demo.viewer.trim()) problems.push('"demo" must be { "viewer" }, for example { "viewer": "Dana", "role": "DS lead" }');
877
+ else {
878
+ if (demo.role !== void 0 && !isStr(demo.role)) problems.push('"demo.role" must be a string, for example "DS lead"');
879
+ for (const k of Object.keys(demo)) if (k !== "viewer" && k !== "role") problems.push(`"demo.${k}" is not a known key`);
880
+ }
881
+ }
863
882
  problems.push(...productProblems(d.products));
864
883
  const audit = d.audit;
865
884
  if (audit !== void 0) {
@@ -897,6 +916,7 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
897
916
  return {
898
917
  $schema: schema,
899
918
  version: 1,
919
+ ...config.name ? { name: config.name } : {},
900
920
  ...config.repo ? { repo: config.repo } : {},
901
921
  tokens: { root: config.tokens.root, include: config.tokens.include, ...config.tokens.exclude.length ? { exclude: config.tokens.exclude } : {}, tiers: config.tokens.tiers, layers: config.tokens.layers.map((l) => l.optional ? l : { files: l.files }), ...config.tokens.usage ? { usage: config.tokens.usage } : {} },
902
922
  modes: Object.fromEntries(Object.entries(config.axes).map(([k, a]) => [k, a.parity ? a : { values: a.values, default: a.default }])),
@@ -912,6 +932,7 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
912
932
  changes: changesMode(config),
913
933
  ...config.storybook ? { storybook: config.storybook } : {},
914
934
  ...config.library ? { library: config.library } : {},
935
+ ...config.demo ? { demo: config.demo } : {},
915
936
  ...config.products && !sameProducts(config.products, DEFAULT_PRODUCTS) ? { products: config.products } : {}
916
937
  };
917
938
  }
@@ -1525,6 +1546,11 @@ var RISK_LABELS = {
1525
1546
  2: "Tier 2: medium risk, design review then design + tech approval",
1526
1547
  3: "Tier 3: token change, needs design + tech approval"
1527
1548
  };
1549
+ function figmaPushTierLabel(tier, changes) {
1550
+ const waits = changes.some((c) => c.kind === "renamed" || c.kind === "removed");
1551
+ return `Tier ${tier}: token change. From Figma: design-approved, ${waits ? "waits for a developer OK (a token renamed or removed can break product code)" : "ships once the checks pass"}`;
1552
+ }
1553
+ var figmaPushNextLine = (changes) => changes.some((c) => c.kind === "renamed" || c.kind === "removed") ? "Pushed from Figma, so design-approved. A developer OKs the rename or removal before it merges." : "Pushed from Figma, so design-approved. Bridge merges it once the automatic checks pass; Undo is in History in Bridge Studio.";
1528
1554
  var BUMP_RANK = { patch: 0, minor: 1, major: 2 };
1529
1555
  var LOW = [/\.md$/, /^docs\//, /\.stories\.tsx?$/, /\.docs\.json$/, /\.test\.tsx?$/, /^\.changeset\//];
1530
1556
  var isSummary = (c) => "kind" in c;
@@ -1703,14 +1729,15 @@ _${deltas.length - limit} more pair(s) not shown._` : "";
1703
1729
  }
1704
1730
  function composeProposalBody(args) {
1705
1731
  const t = args.tier.tier;
1706
- const box = (n) => `- ${n === t ? "[x]" : "[ ]"} ${RISK_LABELS[n]}`;
1732
+ const figma = Boolean(args.fromFigma);
1733
+ const box = (n) => `- ${n === t ? "[x]" : "[ ]"} ${figma && n === t ? figmaPushTierLabel(n, args.changes) : RISK_LABELS[n]}`;
1707
1734
  const impactList = args.impact.entries.slice(0, 15).map((e) => `- \`${e.path}\`: ${swatch(e.before)} -> ${swatch(e.after)} (${e.modes.length} mode${e.modes.length > 1 ? "s" : ""})`);
1708
1735
  return [
1709
1736
  "## What changed and why",
1710
1737
  "",
1711
1738
  args.summary.trim(),
1712
1739
  "",
1713
- `Proposed through ${args.tool ?? "Bridge MCP"}${args.author ? ` by ${args.author}` : ""}. Agent-authored: a human design owner and tech owner review and merge.`,
1740
+ figma ? `Pushed from Figma through ${args.tool ?? "the Bridge Figma plugin"}${args.author ? ` by ${args.author}` : ""}. ${figmaPushNextLine(args.changes)}` : `Proposed through ${args.tool ?? "Bridge MCP"}${args.author ? ` by ${args.author}` : ""}. Agent-authored: a human design owner and tech owner review and merge.`,
1714
1741
  "",
1715
1742
  "## Risk tier",
1716
1743
  "",
@@ -1740,8 +1767,8 @@ function composeProposalBody(args) {
1740
1767
  "",
1741
1768
  "## Authored by",
1742
1769
  "",
1743
- "- [ ] Human",
1744
- "- [x] AI agent (Claude), reviewed by a human before merge",
1770
+ `- [${figma ? "x" : " "}] Human`,
1771
+ `- [${figma ? " " : "x"}] AI agent (Claude), reviewed by a human before merge`,
1745
1772
  "",
1746
1773
  "## Design review checklist",
1747
1774
  "",
@@ -1755,6 +1782,36 @@ function composeProposalBody(args) {
1755
1782
  // ../core/src/component-spec.ts
1756
1783
  var COMPONENT_SPECS_VERSION = 1;
1757
1784
  var DEFAULT_COMPONENT_SPECS_FILE = "packages/react/component-specs.json";
1785
+ var SPEC_FIELDS = [
1786
+ "fill",
1787
+ "stroke",
1788
+ "strokeWeight",
1789
+ "strokeTopWeight",
1790
+ "strokeRightWeight",
1791
+ "strokeBottomWeight",
1792
+ "strokeLeftWeight",
1793
+ "cornerRadius",
1794
+ "paddingTop",
1795
+ "paddingRight",
1796
+ "paddingBottom",
1797
+ "paddingLeft",
1798
+ "itemSpacing",
1799
+ "counterAxisSpacing",
1800
+ "width",
1801
+ "height",
1802
+ "minWidth",
1803
+ "minHeight",
1804
+ "opacity",
1805
+ "shadow",
1806
+ "textFill",
1807
+ "fontFamily",
1808
+ "fontSize",
1809
+ "fontWeight",
1810
+ "lineHeight",
1811
+ "letterSpacing",
1812
+ "textDecoration"
1813
+ ];
1814
+ var COLOR_FIELDS = /* @__PURE__ */ new Set(["fill", "stroke", "textFill"]);
1758
1815
  function coverageSummary(file) {
1759
1816
  const list2 = file.coverage ?? file.components.map((c) => ({ name: c.name, status: "generated" }));
1760
1817
  const generated = list2.filter((c) => c.status === "generated").length;
@@ -1762,6 +1819,18 @@ function coverageSummary(file) {
1762
1819
  const layout = list2.filter((c) => c.status === "layout").length;
1763
1820
  return { generated, drafted, inFigma: generated + drafted, visual: list2.length - layout, layout, total: list2.length, missing: list2.filter((c) => c.status === "unsupported") };
1764
1821
  }
1822
+ function specHash(text) {
1823
+ let h = 2166136261;
1824
+ for (let i = 0; i < text.length; i++) {
1825
+ h ^= text.charCodeAt(i);
1826
+ h = Math.imul(h, 16777619);
1827
+ }
1828
+ return (h >>> 0).toString(16).padStart(8, "0");
1829
+ }
1830
+ function hashSpec(spec) {
1831
+ const { hash: _ignored, example: _example, ...rest } = spec;
1832
+ return specHash(JSON.stringify(rest));
1833
+ }
1765
1834
  var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
1766
1835
  function parseComponentSpecs(text) {
1767
1836
  let data;
@@ -1805,7 +1874,7 @@ var STATE_PSEUDO = {
1805
1874
  var iconName = (component) => component.replace(/^Icon\//, "");
1806
1875
  var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
1807
1876
  function planReference(spec, variant, file) {
1808
- if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
1877
+ if (spec.view === "popup") return planPopup(spec, variant);
1809
1878
  const props = { ...spec.fixed ?? {} };
1810
1879
  let pseudo;
1811
1880
  for (const axis of spec.axes) {
@@ -1834,6 +1903,7 @@ function planReference(spec, variant, file) {
1834
1903
  if (part.swap && swapTo) {
1835
1904
  const slot = file?.slots?.find((s) => s.name === swapTo);
1836
1905
  if (!slot) return { reason: `${swapTo} is not in the specs` };
1906
+ if (slot.kind === "media") return { reason: `Figma shows a media placeholder (${swapTo}); code shows the image it is given, nothing to compare` };
1837
1907
  if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
1838
1908
  return slot.text.default;
1839
1909
  }
@@ -1885,10 +1955,13 @@ function planReference(spec, variant, file) {
1885
1955
  }
1886
1956
  const example = spec.example;
1887
1957
  const exampleChildren = example?.children?.length ? example.children : void 0;
1958
+ const component = exportName(example?.c ?? (spec.name.includes("/") ? spec.component ?? spec.name : spec.name));
1959
+ const parent = spec.component ? exportName(spec.component) : void 0;
1888
1960
  return {
1889
1961
  ok: true,
1890
1962
  render: {
1891
- component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
1963
+ component,
1964
+ ...parent && parent !== component ? { within: parent } : {},
1892
1965
  props: { ...example?.p ?? {}, ...props },
1893
1966
  ...exampleChildren ? { children: exampleChildren } : {},
1894
1967
  ...text !== void 0 && !exampleChildren ? { text } : {},
@@ -1899,6 +1972,28 @@ function planReference(spec, variant, file) {
1899
1972
  }
1900
1973
  };
1901
1974
  }
1975
+ function planPopup(spec, variant) {
1976
+ const root = spec.parts.find((p) => !p.parent);
1977
+ const own = {};
1978
+ for (const axis of spec.axes) {
1979
+ const value = variant.props[axis.name];
1980
+ if (!axis.prop || value === void 0) return { ok: false, reason: `${axis.name} has no matching option in code` };
1981
+ own[axis.prop] = value === "true" ? true : value === "false" ? false : value;
1982
+ }
1983
+ const className = /\.module\.css \.([\w-]+)/.exec(root?.source ?? "")?.[1];
1984
+ const component = spec.component ? exportName(spec.component) : void 0;
1985
+ const example = spec.example;
1986
+ if (!component || !example) return { ok: false, reason: "drawn open from no code example: nothing to open in code" };
1987
+ if (!className) return { ok: false, reason: "the popup has no class to find it by in code" };
1988
+ const popup = { className };
1989
+ if (exportName(example.c) === component) {
1990
+ return { ok: true, render: { component, props: { ...example.p ?? {}, ...own, defaultOpen: true }, ...example.children?.length ? { children: example.children } : {}, popup } };
1991
+ }
1992
+ return {
1993
+ ok: true,
1994
+ render: { component, props: { defaultOpen: true }, children: [{ c: `${component}Trigger`, children: ["Open"] }, { ...example, p: { ...example.p ?? {}, ...own } }], popup }
1995
+ };
1996
+ }
1902
1997
  function referenceImagePath(component, key) {
1903
1998
  const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1904
1999
  const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
@@ -2053,6 +2148,7 @@ export {
2053
2148
  CONFIG_FILE,
2054
2149
  DEFAULT_OVERRIDE,
2055
2150
  DEFAULT_TOKEN_PATHS,
2151
+ DEFAULT_GOVERNANCE,
2056
2152
  loadConfig,
2057
2153
  tokensPath,
2058
2154
  protectedBranchesOf,
@@ -2067,6 +2163,7 @@ export {
2067
2163
  parseContrastChecks,
2068
2164
  threeWayDiff,
2069
2165
  RISK_LABELS,
2166
+ isGuardrail,
2070
2167
  classifyRisk,
2071
2168
  aliasOf,
2072
2169
  globToRegExp,
@@ -2088,7 +2185,10 @@ export {
2088
2185
  ProtectedBranchError,
2089
2186
  assertSafeBranch,
2090
2187
  DEFAULT_COMPONENT_SPECS_FILE,
2188
+ SPEC_FIELDS,
2189
+ COLOR_FIELDS,
2091
2190
  coverageSummary,
2191
+ hashSpec,
2092
2192
  variantKeyOf,
2093
2193
  parseComponentSpecs,
2094
2194
  figmaCell,