@bridge-ds/mcp 0.3.3 → 0.4.1

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.
@@ -391,6 +391,7 @@ var TeamDirectory = class {
391
391
  var OWNER_GROUPS = ["design", "tech"];
392
392
  var RIGHTS = ["approveDesign", "approveTech", "ship"];
393
393
  var DEFAULT_RIGHTS = { approveDesign: ["design"], approveTech: ["tech"], ship: ["tech"] };
394
+ var norm2 = (x) => x.trim().toLowerCase().replace(/^@/, "");
394
395
  var uniq = (xs) => [...new Set(xs)];
395
396
  function normalizeRights(input) {
396
397
  const pick = (r) => {
@@ -426,6 +427,16 @@ function shipPolicy(owners, rights, teams) {
426
427
  const logins = expandEntries(entries, teams);
427
428
  return { logins, groups: [...rights.ship], entries, ...teams ? { teams } : {} };
428
429
  }
430
+ function unreadableShipTeams(policy) {
431
+ if (!policy?.teams) return [];
432
+ const keys = new Set((policy.entries ?? []).map((e) => e.trim().toLowerCase().replace(/^@?/, "@")));
433
+ return unreadableTeams(policy.teams).filter((t) => keys.has(t.key));
434
+ }
435
+ function canShip(login, policy) {
436
+ if (!policy) return true;
437
+ if (!login || login.includes("/")) return false;
438
+ return policy.logins.map(norm2).includes(norm2(login));
439
+ }
429
440
  function groupsPhrase(groups) {
430
441
  const g = OWNER_GROUPS.filter((x) => groups.includes(x));
431
442
  if (g.length === 1) return `the ${g[0]} owner`;
@@ -446,6 +457,105 @@ function visualImpactProblems(raw) {
446
457
  return out;
447
458
  }
448
459
 
460
+ // ../core/src/products.ts
461
+ var DEFAULT_PRODUCTS = [
462
+ { key: "admin", name: "Admin demo", path: "apps/admin", framework: "react" },
463
+ { key: "library", name: "Library", default: true }
464
+ ];
465
+ var PRODUCT_LABEL_PREFIX = "product: ";
466
+ var productLabel = (key) => `${PRODUCT_LABEL_PREFIX}${key}`;
467
+ function productMarks(products, p) {
468
+ if (p.key === defaultProduct(products).key) return { labels: [], line: "" };
469
+ return { labels: [productLabel(p.key)], line: `**Product:** ${p.name}` };
470
+ }
471
+ function productsOf(config) {
472
+ const list2 = config?.products;
473
+ return Array.isArray(list2) && list2.length ? list2 : DEFAULT_PRODUCTS;
474
+ }
475
+ function defaultProduct(products) {
476
+ const list2 = products.length ? products : DEFAULT_PRODUCTS;
477
+ return list2.find((p) => p.default) ?? list2[list2.length - 1];
478
+ }
479
+ function findProduct(products, keyOrName) {
480
+ const k = (keyOrName ?? "").trim().toLowerCase();
481
+ if (!k) return void 0;
482
+ return products.find((p) => p.key.toLowerCase() === k) ?? products.find((p) => p.name.toLowerCase() === k);
483
+ }
484
+ var cleanPath = (p) => p.trim().replace(/^\.?\/+|\/+$/g, "");
485
+ function productOfBranch(products, branch) {
486
+ const fallback = defaultProduct(products);
487
+ for (const l of branch.labels ?? []) {
488
+ const name = typeof l === "string" ? l : l?.name;
489
+ if (typeof name !== "string" || !name.toLowerCase().startsWith(PRODUCT_LABEL_PREFIX)) continue;
490
+ const hit = findProduct(products, name.slice(PRODUCT_LABEL_PREFIX.length));
491
+ if (hit) return hit;
492
+ }
493
+ const files = branch.files ?? [];
494
+ for (const p of products) {
495
+ if (p === fallback || !p.path) continue;
496
+ const prefix = `${cleanPath(p.path)}/`;
497
+ if (prefix !== "/" && files.some((f) => f.startsWith(prefix))) return p;
498
+ }
499
+ return fallback;
500
+ }
501
+ function productForPlace(products, place) {
502
+ const repo = place.repo?.trim().toLowerCase();
503
+ if (repo && repo !== place.designSystemRepo?.trim().toLowerCase()) {
504
+ const hit = products.find((p) => p.repo?.toLowerCase() === repo);
505
+ if (hit) return { product: hit, how: "repo" };
506
+ }
507
+ const rel = cleanPath(place.relPath ?? "");
508
+ if (rel && rel !== ".") {
509
+ let best;
510
+ for (const p of products) {
511
+ if (!p.path) continue;
512
+ const dir = cleanPath(p.path);
513
+ if (dir && (rel === dir || rel.startsWith(`${dir}/`)) && (!best || dir.length > cleanPath(best.path).length)) best = p;
514
+ }
515
+ if (best) return { product: best, how: "path" };
516
+ }
517
+ return { product: defaultProduct(products), how: "default" };
518
+ }
519
+ var KEY = /^[a-z0-9][a-z0-9-]*$/;
520
+ var REPO = /^[\w.-]+\/[\w.-]+$/;
521
+ var FIELDS = ["key", "name", "repo", "path", "preview", "framework", "default"];
522
+ function productProblems(raw) {
523
+ if (raw === void 0) return [];
524
+ if (!Array.isArray(raw)) return ['"products" must be a list of { "key", "name" } objects'];
525
+ if (!raw.length) return ['"products" must list at least one product, or be left out to use the defaults'];
526
+ const problems = [];
527
+ const seen = /* @__PURE__ */ new Set();
528
+ let defaults = 0;
529
+ raw.forEach((p, i) => {
530
+ const at = `"products[${i}]`;
531
+ if (!p || typeof p !== "object" || Array.isArray(p)) {
532
+ problems.push(`${at}" must be an object with a "key" and a "name"`);
533
+ return;
534
+ }
535
+ const o = p;
536
+ for (const k of Object.keys(o)) if (!FIELDS.includes(k)) problems.push(`${at}.${k}" is not a known field (${FIELDS.join(", ")})`);
537
+ if (typeof o.key !== "string" || !KEY.test(o.key)) problems.push(`${at}.key" must be lowercase letters, digits and dashes, such as "admin"`);
538
+ else if (seen.has(o.key)) problems.push(`${at}.key" ("${o.key}") is used twice: give each product its own key`);
539
+ else seen.add(o.key);
540
+ if (typeof o.name !== "string" || !o.name.trim()) problems.push(`${at}.name" must be the name people read, such as "Admin demo"`);
541
+ if (o.repo !== void 0 && (typeof o.repo !== "string" || !REPO.test(o.repo))) problems.push(`${at}.repo" must be "owner/name"`);
542
+ if (o.path !== void 0) {
543
+ const bad = typeof o.path !== "string" || !o.path.trim() || o.path.startsWith("/") || o.path.split("/").includes("..");
544
+ if (bad) problems.push(`${at}.path" must be a folder relative to the repository root, without a leading "/" or "..", such as "apps/admin"`);
545
+ }
546
+ if (o.preview !== void 0 && (typeof o.preview !== "string" || !/^https?:\/\//.test(o.preview))) problems.push(`${at}.preview" must be an http(s) URL template, such as "https://app-git-{branch}.vercel.app"`);
547
+ if (o.framework !== void 0 && (typeof o.framework !== "string" || !o.framework.trim())) problems.push(`${at}.framework" must be a name such as "react", "vue" or "other"`);
548
+ if (o.default !== void 0 && typeof o.default !== "boolean") problems.push(`${at}.default" must be true or false`);
549
+ if (o.default === true) defaults++;
550
+ });
551
+ if (defaults > 1) problems.push('"products" marks more than one product as default: keep "default": true on the library only');
552
+ return problems;
553
+ }
554
+ function sameProducts(a, b) {
555
+ const norm3 = (l) => JSON.stringify(l.map((p) => FIELDS.map((f) => p[f] ?? null)));
556
+ return norm3(a) === norm3(b);
557
+ }
558
+
449
559
  // ../core/src/config.ts
450
560
  var CONFIG_FILE = "bridge.config.json";
451
561
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
@@ -586,6 +696,7 @@ function defineConfig(input) {
586
696
  } : {},
587
697
  ...g.rights ? { rights: normalizeRights(g.rights) } : {}
588
698
  },
699
+ products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
589
700
  ...input.audit ? { audit: { ...input.audit } } : {},
590
701
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
591
702
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -708,6 +819,7 @@ function loadConfig(source) {
708
819
  }
709
820
  }
710
821
  problems.push(...visualImpactProblems(d.visualImpact));
822
+ problems.push(...productProblems(d.products));
711
823
  const audit = d.audit;
712
824
  if (audit !== void 0) {
713
825
  if (!isObj(audit)) problems.push('"audit" must be an object');
@@ -754,7 +866,8 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
754
866
  governance: config.governance,
755
867
  ...config.audit ? { audit: config.audit } : {},
756
868
  ...config.previews ? { previews: config.previews } : {},
757
- ...config.visualImpact ? { visualImpact: config.visualImpact } : {}
869
+ ...config.visualImpact ? { visualImpact: config.visualImpact } : {},
870
+ ...config.products && !sameProducts(config.products, DEFAULT_PRODUCTS) ? { products: config.products } : {}
758
871
  };
759
872
  }
760
873
 
@@ -1317,6 +1430,49 @@ function fieldChanges(a, b) {
1317
1430
  if (!sameScopes(sa, sb)) out.push({ field: "scopes", before: sa, after: sb });
1318
1431
  return out;
1319
1432
  }
1433
+ function sameToken(a, b) {
1434
+ if (!a || !b) return a === b;
1435
+ return fieldChanges(a, b).length === 0;
1436
+ }
1437
+ function sideChange(from, to) {
1438
+ if (!from && !to) return void 0;
1439
+ if (!from) return { kind: "added", fields: [] };
1440
+ if (!to) return { kind: "removed", fields: [] };
1441
+ const fields = fieldChanges(from, to);
1442
+ if (!fields.length) return void 0;
1443
+ return { kind: fields.some((f) => f.field === "path") ? "renamed" : "changed", fields };
1444
+ }
1445
+ function threeWayDiff(base, figma, head) {
1446
+ const ids = /* @__PURE__ */ new Set([...base?.keys() ?? [], ...figma.keys(), ...head.keys()]);
1447
+ const result = { local: [], remote: [], conflicts: [], unchanged: 0 };
1448
+ for (const id of [...ids].sort()) {
1449
+ const b = base?.get(id);
1450
+ const f = figma.get(id);
1451
+ const h = head.get(id);
1452
+ const local = sideChange(b, f);
1453
+ const remote = sideChange(b, h);
1454
+ if (!local && !remote || f && h && sameToken(f, h) || !f && !h) {
1455
+ result.unchanged++;
1456
+ continue;
1457
+ }
1458
+ const entry = { id, path: (f ?? h ?? b).path, base: b, figma: f, head: h, local, remote, conflict: false };
1459
+ if (local && !remote) {
1460
+ entry.path = (f ?? b).path;
1461
+ result.local.push(entry);
1462
+ } else if (remote && !local) {
1463
+ entry.path = (h ?? b).path;
1464
+ result.remote.push(entry);
1465
+ } else {
1466
+ entry.conflict = true;
1467
+ result.conflicts.push(entry);
1468
+ }
1469
+ }
1470
+ const byPath = (a, b) => a.path.localeCompare(b.path);
1471
+ result.local.sort(byPath);
1472
+ result.remote.sort(byPath);
1473
+ result.conflicts.sort(byPath);
1474
+ return result;
1475
+ }
1320
1476
 
1321
1477
  // ../core/src/risk.ts
1322
1478
  var RISK_LABELS = {
@@ -1551,6 +1707,164 @@ function composeProposalBody(args) {
1551
1707
  ].join("\n");
1552
1708
  }
1553
1709
 
1710
+ // ../core/src/component-spec.ts
1711
+ var COMPONENT_SPECS_VERSION = 1;
1712
+ var DEFAULT_COMPONENT_SPECS_FILE = "packages/react/component-specs.json";
1713
+ function coverageSummary(file) {
1714
+ const list2 = file.coverage ?? file.components.map((c) => ({ name: c.name, status: "generated" }));
1715
+ const generated = list2.filter((c) => c.status === "generated").length;
1716
+ const drafted = list2.filter((c) => c.status === "drafted").length;
1717
+ const layout = list2.filter((c) => c.status === "layout").length;
1718
+ return { generated, drafted, inFigma: generated + drafted, visual: list2.length - layout, layout, total: list2.length, missing: list2.filter((c) => c.status === "unsupported") };
1719
+ }
1720
+ var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
1721
+ function parseComponentSpecs(text) {
1722
+ let data;
1723
+ try {
1724
+ data = JSON.parse(text);
1725
+ } catch {
1726
+ throw new Error("component-specs.json is not valid JSON.");
1727
+ }
1728
+ const f = data;
1729
+ if (!f || typeof f !== "object" || !Array.isArray(f.components)) throw new Error('component-specs.json has no "components" list.');
1730
+ if (f.version !== COMPONENT_SPECS_VERSION) throw new Error(`component-specs.json is version ${String(f.version)}; this tool reads version ${COMPONENT_SPECS_VERSION}.`);
1731
+ return f;
1732
+ }
1733
+ function figmaCell(entry) {
1734
+ if (!entry) return void 0;
1735
+ if (entry.status === "generated") return { label: "Yes" };
1736
+ if (entry.status === "drafted") return { label: "Drafted", title: "Drafted with Claude where the code cannot be read on its own: in Figma, check it against the code." };
1737
+ if (entry.status === "layout") return { label: "Auto layout", title: "A layout helper: nothing to draw, use auto layout in Figma." };
1738
+ return { label: "Not yet", ...entry.reason ? { title: `Not supported yet: ${entry.reason}` } : {} };
1739
+ }
1740
+
1741
+ // ../core/src/fidelity-references.ts
1742
+ var FIGMA_REFERENCES_BRANCH = "bridge-visual";
1743
+ var FIGMA_REFERENCES_DIR = "figma-references";
1744
+ var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
1745
+ var FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
1746
+ var FIGMA_FILL_WIDTH_TOKEN = "size.80";
1747
+ var STATE_PSEUDO = {
1748
+ default: "rest",
1749
+ rest: "rest",
1750
+ enabled: "rest",
1751
+ hover: "hover",
1752
+ hovered: "hover",
1753
+ pressed: "active",
1754
+ active: "active",
1755
+ focus: "focus-visible",
1756
+ focused: "focus-visible",
1757
+ "focus visible": "focus-visible",
1758
+ disabled: "disabled"
1759
+ };
1760
+ var iconName = (component) => component.replace(/^Icon\//, "");
1761
+ var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
1762
+ function planReference(spec, variant, file) {
1763
+ if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
1764
+ const props = { ...spec.fixed ?? {} };
1765
+ let pseudo;
1766
+ for (const axis of spec.axes) {
1767
+ const value = variant.props[axis.name];
1768
+ if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
1769
+ if (axis.prop) {
1770
+ props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
1771
+ continue;
1772
+ }
1773
+ if (axis.name === "Checked") {
1774
+ props.checked = value === "true";
1775
+ if (value === "mixed") props.indeterminate = true;
1776
+ continue;
1777
+ }
1778
+ if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
1779
+ const s = STATE_PSEUDO[value.toLowerCase()];
1780
+ if (!s) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
1781
+ if (s === "disabled") props.disabled = true;
1782
+ else if (s !== "rest") pseudo = s;
1783
+ }
1784
+ const root = spec.parts.find((p) => !p.parent);
1785
+ if (!root) return { ok: false, reason: "the spec has no root layer" };
1786
+ const children = (id) => spec.parts.filter((p) => p.parent === id);
1787
+ const textOf = (part) => {
1788
+ const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
1789
+ if (part.swap && swapTo) {
1790
+ const slot = file?.slots?.find((s) => s.name === swapTo);
1791
+ if (!slot) return { reason: `${swapTo} is not in the specs` };
1792
+ if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
1793
+ return slot.text.default;
1794
+ }
1795
+ const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
1796
+ return typeof prop?.default === "string" ? prop.default : part.name;
1797
+ };
1798
+ let text;
1799
+ const icons = {};
1800
+ for (const prop of spec.properties) {
1801
+ if (prop.type === "TEXT") {
1802
+ const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
1803
+ if (hidden && prop.prop) continue;
1804
+ if (prop.prop === "children") text = String(prop.default);
1805
+ else if (prop.prop) props[prop.prop] = String(prop.default);
1806
+ continue;
1807
+ }
1808
+ const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
1809
+ if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
1810
+ }
1811
+ for (const part of spec.parts) {
1812
+ if (part.kind !== "icon" || !part.swap) continue;
1813
+ const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
1814
+ if (!shown) continue;
1815
+ if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
1816
+ icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
1817
+ }
1818
+ let parts;
1819
+ const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
1820
+ const frames = children(root.id).filter((p) => p.kind === "frame");
1821
+ const compound = text === void 0 && frames.length > 0 && frames.every((f) => spec.parts.some((p) => p.parent === f.id && p.kind === "text" && (p.swap || p.text === f.name)));
1822
+ if (compound) {
1823
+ parts = [];
1824
+ for (const f of frames) {
1825
+ const t = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
1826
+ const value = textOf(t);
1827
+ if (typeof value !== "string") return { ok: false, reason: value.reason };
1828
+ parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
1829
+ }
1830
+ }
1831
+ for (const p of loose) {
1832
+ if (p.name === "Value") continue;
1833
+ const part = spec.parts.find((x) => x.text === p.name);
1834
+ if (compound && part && part.parent !== root.id) continue;
1835
+ if (part && /placeholder$/.test(part.id)) {
1836
+ props.placeholder = String(p.default);
1837
+ continue;
1838
+ }
1839
+ return { ok: false, reason: `the ${p.name} text has no matching option in code` };
1840
+ }
1841
+ const example = spec.example;
1842
+ const exampleChildren = example?.children?.length ? example.children : void 0;
1843
+ return {
1844
+ ok: true,
1845
+ render: {
1846
+ component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
1847
+ props: { ...example?.p ?? {}, ...props },
1848
+ ...exampleChildren ? { children: exampleChildren } : {},
1849
+ ...text !== void 0 && !exampleChildren ? { text } : {},
1850
+ ...parts && !exampleChildren ? { parts } : {},
1851
+ ...Object.keys(icons).length ? { icons } : {},
1852
+ ...pseudo ? { pseudo } : {},
1853
+ ...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
1854
+ }
1855
+ };
1856
+ }
1857
+ function referenceImagePath(component, key) {
1858
+ const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1859
+ const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
1860
+ return `${slug(component)}/${file || "default"}.png`;
1861
+ }
1862
+ function planReferences(file) {
1863
+ return file.components.flatMap(
1864
+ (spec) => spec.variants.map((v) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v.props), plan: planReference(spec, v, file) }))
1865
+ );
1866
+ }
1867
+
1554
1868
  // ../core/src/node/git-local.ts
1555
1869
  import { execFile } from "node:child_process";
1556
1870
  import { mkdtemp, rm } from "node:fs/promises";
@@ -1661,6 +1975,7 @@ async function identityEnv(root) {
1661
1975
 
1662
1976
  export {
1663
1977
  __export,
1978
+ previewUrl,
1664
1979
  previewLinks,
1665
1980
  readPreviewComment,
1666
1981
  findPreviewComment,
@@ -1673,8 +1988,18 @@ export {
1673
1988
  DEFAULT_RIGHTS,
1674
1989
  approverEntries,
1675
1990
  shipPolicy,
1991
+ unreadableShipTeams,
1992
+ canShip,
1676
1993
  whoCanShipSentence,
1994
+ productMarks,
1995
+ productsOf,
1996
+ defaultProduct,
1997
+ findProduct,
1998
+ productOfBranch,
1999
+ productForPlace,
1677
2000
  CONFIG_FILE,
2001
+ DEFAULT_OVERRIDE,
2002
+ DEFAULT_TOKEN_PATHS,
1678
2003
  loadConfig,
1679
2004
  tokensPath,
1680
2005
  protectedBranchesOf,
@@ -1686,6 +2011,7 @@ export {
1686
2011
  renderedColor,
1687
2012
  contrastPair,
1688
2013
  parseContrastChecks,
2014
+ threeWayDiff,
1689
2015
  RISK_LABELS,
1690
2016
  classifyRisk,
1691
2017
  aliasOf,
@@ -1707,6 +2033,15 @@ export {
1707
2033
  DEFAULT_PROTECTED_BRANCHES,
1708
2034
  ProtectedBranchError,
1709
2035
  assertSafeBranch,
2036
+ DEFAULT_COMPONENT_SPECS_FILE,
2037
+ coverageSummary,
2038
+ parseComponentSpecs,
2039
+ figmaCell,
2040
+ FIGMA_REFERENCES_BRANCH,
2041
+ FIGMA_REFERENCES_MANIFEST,
2042
+ FIGMA_REFERENCE_STORY,
2043
+ referenceImagePath,
2044
+ planReferences,
1710
2045
  git,
1711
2046
  isGitRepo,
1712
2047
  currentBranch,
@@ -11,6 +11,8 @@ export interface StartExplorationInput {
11
11
  /** Where it is started from (the MCP tool says mcp, the CLI says cli), and the AI agent behind it when known. */
12
12
  source?: ExplorationSource | undefined;
13
13
  agent?: string | undefined;
14
+ /** The product it is tried in (key or name, bridge.config.json "products"). Default: the product of the current folder or repo. */
15
+ product?: string | undefined;
14
16
  dryRun?: boolean | undefined;
15
17
  }
16
18
  /**
@@ -20,6 +22,7 @@ export interface StartExplorationInput {
20
22
  */
21
23
  export declare function startExploration(ctx: BridgeContext, a: StartExplorationInput): Promise<{
22
24
  branch: string;
25
+ product: string;
23
26
  slug: string;
24
27
  base: string;
25
28
  title: string;
@@ -46,6 +49,7 @@ export declare function startExploration(ctx: BridgeContext, a: StartExploration
46
49
  }[];
47
50
  next: string;
48
51
  branch: string;
52
+ product: string;
49
53
  slug: string;
50
54
  base: string;
51
55
  title: string;
@@ -60,6 +64,7 @@ export declare function startExploration(ctx: BridgeContext, a: StartExploration
60
64
  commit: string;
61
65
  next: string;
62
66
  branch: string;
67
+ product: string;
63
68
  slug: string;
64
69
  base: string;
65
70
  title: string;
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  VERSION,
4
4
  optionsFromArgv,
5
5
  serveStdio
6
- } from "./chunk-7HYTZVRW.js";
7
- import "./chunk-K3HY24MZ.js";
6
+ } from "./chunk-MXMMPO7F.js";
7
+ import "./chunk-QTO4EP6V.js";
8
8
 
9
9
  // src/index.ts
10
10
  var argv = process.argv.slice(2);
package/dist/lib.d.ts CHANGED
@@ -11,6 +11,8 @@ export * as brand from './brand.js';
11
11
  export * as requests from './requests.js';
12
12
  export * as explorations from './explorations.js';
13
13
  export * as drafts from './drafts.js';
14
+ export * as branches from './branches.js';
15
+ export * as studio from './studio.js';
14
16
  export { DOCS_DRAFT_MODEL, anthropicReplyText, anthropicRequest, formatDocsJson, mergeDocsDraft, missingProse, parseDocsReply } from '@bridge-ds/core';
15
17
  export interface ServeOptions extends ContextOptions {
16
18
  }
package/dist/lib.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  anthropicReplyText,
9
9
  anthropicRequest,
10
10
  auditSnippet,
11
+ branches_exports,
11
12
  brand_exports,
12
13
  core,
13
14
  createServer,
@@ -22,13 +23,14 @@ import {
22
23
  optionsFromArgv,
23
24
  parseDocsReply,
24
25
  requests_exports,
25
- serveStdio
26
- } from "./chunk-7HYTZVRW.js";
26
+ serveStdio,
27
+ studio_exports
28
+ } from "./chunk-MXMMPO7F.js";
27
29
  import {
28
30
  CONFIG_FILE,
29
31
  loadConfig,
30
32
  toConfigJson
31
- } from "./chunk-K3HY24MZ.js";
33
+ } from "./chunk-QTO4EP6V.js";
32
34
  export {
33
35
  BridgeContext,
34
36
  CONFIG_FILE,
@@ -40,6 +42,7 @@ export {
40
42
  anthropicReplyText,
41
43
  anthropicRequest,
42
44
  auditSnippet,
45
+ branches_exports as branches,
43
46
  brand_exports as brand,
44
47
  core,
45
48
  createServer,
@@ -56,5 +59,6 @@ export {
56
59
  parseDocsReply,
57
60
  requests_exports as requests,
58
61
  serveStdio,
62
+ studio_exports as studio,
59
63
  toConfigJson
60
64
  };
package/dist/ops.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type ScaffoldInput } from './components.js';
2
- import type { BridgeContext } from './context.js';
3
2
  import { type BridgeConfig, type GitHubClient } from '@bridge-ds/core';
4
3
  import { type Combo, type PairInput, type TokenEdit, type TokenSet } from './core-adapter.js';
4
+ import { BridgeContext } from './context.js';
5
5
  /** Accepts `color.text.muted`, `--color-text-muted` or `var(--color-text-muted)`. */
6
6
  export declare function normalizeTokenPath(set: TokenSet, input: string): string;
7
7
  export declare function getConfig(ctx: BridgeContext): Promise<{
@@ -274,7 +274,75 @@ export declare function proposeTokenChange(ctx: BridgeContext, a: {
274
274
  summary?: string;
275
275
  dryRun?: boolean;
276
276
  branch?: string | undefined;
277
+ pr?: number | string | undefined;
278
+ jira?: string | undefined;
279
+ product?: string | undefined;
277
280
  }): Promise<{
281
+ status: string;
282
+ pr: number;
283
+ message: string;
284
+ } | {
285
+ hint: string;
286
+ pr: number;
287
+ branch: string;
288
+ tier: 1 | 2 | 3;
289
+ changes: {
290
+ kind: "added" | "changed" | "renamed" | "removed";
291
+ path: string;
292
+ file: string;
293
+ before: string | undefined;
294
+ after: string | undefined;
295
+ }[];
296
+ contrast: {
297
+ moved: number;
298
+ newFailures: import("@bridge-ds/core").ContrastDelta[];
299
+ };
300
+ notes: string[];
301
+ status: string;
302
+ why: string;
303
+ newErrors: import("@bridge-ds/core").LayerIssue[];
304
+ message?: undefined;
305
+ } | {
306
+ files: string[];
307
+ pr: number;
308
+ branch: string;
309
+ tier: 1 | 2 | 3;
310
+ changes: {
311
+ kind: "added" | "changed" | "renamed" | "removed";
312
+ path: string;
313
+ file: string;
314
+ before: string | undefined;
315
+ after: string | undefined;
316
+ }[];
317
+ contrast: {
318
+ moved: number;
319
+ newFailures: import("@bridge-ds/core").ContrastDelta[];
320
+ };
321
+ notes: string[];
322
+ status: string;
323
+ message?: undefined;
324
+ } | {
325
+ commit: string;
326
+ url: string;
327
+ said: string;
328
+ pr: number;
329
+ branch: string;
330
+ tier: 1 | 2 | 3;
331
+ changes: {
332
+ kind: "added" | "changed" | "renamed" | "removed";
333
+ path: string;
334
+ file: string;
335
+ before: string | undefined;
336
+ after: string | undefined;
337
+ }[];
338
+ contrast: {
339
+ moved: number;
340
+ newFailures: import("@bridge-ds/core").ContrastDelta[];
341
+ };
342
+ notes: string[];
343
+ status: string;
344
+ message?: undefined;
345
+ } | {
278
346
  status: string;
279
347
  message: string;
280
348
  } | {
@@ -501,6 +569,7 @@ export declare function proposeTokenChange(ctx: BridgeContext, a: {
501
569
  url: string;
502
570
  labels: string[];
503
571
  draft: boolean;
572
+ product: string;
504
573
  files: string[];
505
574
  prBody: string;
506
575
  title: string;
@@ -68,6 +68,8 @@ export interface CreateRequestInput {
68
68
  source?: RequestSource | undefined;
69
69
  /** Exploration branch to implement it on (`explore/<slug>`), instead of a pull request to main. */
70
70
  exploration?: string | undefined;
71
+ /** The product it is for (key or name, bridge.config.json "products"). Default: the product of the current folder or repo. */
72
+ product?: string | undefined;
71
73
  dryRun?: boolean | undefined;
72
74
  }
73
75
  export declare function createRequest(ctx: BridgeContext, a: CreateRequestInput): Promise<{