@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.
@@ -1,13 +1,17 @@
1
1
  import {
2
+ FIGMA_REFERENCE_STORY,
2
3
  LayeredTokenSet,
3
4
  comboKey,
4
5
  diffLayered,
5
6
  git,
6
7
  listFilesAt,
7
8
  loadConfig,
9
+ parseComponentSpecs,
10
+ planReferences,
11
+ referenceImagePath,
8
12
  selectTokenFiles,
9
13
  showFile
10
- } from "./chunk-K3HY24MZ.js";
14
+ } from "./chunk-QTO4EP6V.js";
11
15
 
12
16
  // ../visual/src/run.ts
13
17
  import { existsSync as existsSync3 } from "node:fs";
@@ -123,148 +127,6 @@ function impactedModules(index, input) {
123
127
  return { modules, globalHits };
124
128
  }
125
129
 
126
- // ../core/src/component-spec.ts
127
- var COMPONENT_SPECS_VERSION = 1;
128
- var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
129
- function parseComponentSpecs(text) {
130
- let data;
131
- try {
132
- data = JSON.parse(text);
133
- } catch {
134
- throw new Error("component-specs.json is not valid JSON.");
135
- }
136
- const f = data;
137
- if (!f || typeof f !== "object" || !Array.isArray(f.components)) throw new Error('component-specs.json has no "components" list.');
138
- if (f.version !== COMPONENT_SPECS_VERSION) throw new Error(`component-specs.json is version ${String(f.version)}; this tool reads version ${COMPONENT_SPECS_VERSION}.`);
139
- return f;
140
- }
141
-
142
- // ../core/src/fidelity-references.ts
143
- var FIGMA_REFERENCES_DIR = "figma-references";
144
- var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
145
- var FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
146
- var FIGMA_FILL_WIDTH_TOKEN = "size.80";
147
- var STATE_PSEUDO = {
148
- default: "rest",
149
- rest: "rest",
150
- enabled: "rest",
151
- hover: "hover",
152
- hovered: "hover",
153
- pressed: "active",
154
- active: "active",
155
- focus: "focus-visible",
156
- focused: "focus-visible",
157
- "focus visible": "focus-visible",
158
- disabled: "disabled"
159
- };
160
- var iconName = (component) => component.replace(/^Icon\//, "");
161
- var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
162
- function planReference(spec, variant, file) {
163
- if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
164
- const props = { ...spec.fixed ?? {} };
165
- let pseudo;
166
- for (const axis of spec.axes) {
167
- const value = variant.props[axis.name];
168
- if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
169
- if (axis.prop) {
170
- props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
171
- continue;
172
- }
173
- if (axis.name === "Checked") {
174
- props.checked = value === "true";
175
- if (value === "mixed") props.indeterminate = true;
176
- continue;
177
- }
178
- if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
179
- const s = STATE_PSEUDO[value.toLowerCase()];
180
- if (!s) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
181
- if (s === "disabled") props.disabled = true;
182
- else if (s !== "rest") pseudo = s;
183
- }
184
- const root = spec.parts.find((p) => !p.parent);
185
- if (!root) return { ok: false, reason: "the spec has no root layer" };
186
- const children = (id) => spec.parts.filter((p) => p.parent === id);
187
- const textOf = (part) => {
188
- const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
189
- if (part.swap && swapTo) {
190
- const slot = file?.slots?.find((s) => s.name === swapTo);
191
- if (!slot) return { reason: `${swapTo} is not in the specs` };
192
- if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
193
- return slot.text.default;
194
- }
195
- const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
196
- return typeof prop?.default === "string" ? prop.default : part.name;
197
- };
198
- let text;
199
- const icons = {};
200
- for (const prop of spec.properties) {
201
- if (prop.type === "TEXT") {
202
- const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
203
- if (hidden && prop.prop) continue;
204
- if (prop.prop === "children") text = String(prop.default);
205
- else if (prop.prop) props[prop.prop] = String(prop.default);
206
- continue;
207
- }
208
- const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
209
- if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
210
- }
211
- for (const part of spec.parts) {
212
- if (part.kind !== "icon" || !part.swap) continue;
213
- const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
214
- if (!shown) continue;
215
- if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
216
- icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
217
- }
218
- let parts;
219
- const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
220
- const frames = children(root.id).filter((p) => p.kind === "frame");
221
- 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)));
222
- if (compound) {
223
- parts = [];
224
- for (const f of frames) {
225
- const t = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
226
- const value = textOf(t);
227
- if (typeof value !== "string") return { ok: false, reason: value.reason };
228
- parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
229
- }
230
- }
231
- for (const p of loose) {
232
- if (p.name === "Value") continue;
233
- const part = spec.parts.find((x) => x.text === p.name);
234
- if (compound && part && part.parent !== root.id) continue;
235
- if (part && /placeholder$/.test(part.id)) {
236
- props.placeholder = String(p.default);
237
- continue;
238
- }
239
- return { ok: false, reason: `the ${p.name} text has no matching option in code` };
240
- }
241
- const example = spec.example;
242
- const exampleChildren = example?.children?.length ? example.children : void 0;
243
- return {
244
- ok: true,
245
- render: {
246
- component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
247
- props: { ...example?.p ?? {}, ...props },
248
- ...exampleChildren ? { children: exampleChildren } : {},
249
- ...text !== void 0 && !exampleChildren ? { text } : {},
250
- ...parts && !exampleChildren ? { parts } : {},
251
- ...Object.keys(icons).length ? { icons } : {},
252
- ...pseudo ? { pseudo } : {},
253
- ...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
254
- }
255
- };
256
- }
257
- function referenceImagePath(component, key) {
258
- const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
259
- const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
260
- return `${slug(component)}/${file || "default"}.png`;
261
- }
262
- function planReferences(file) {
263
- return file.components.flatMap(
264
- (spec) => spec.variants.map((v) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v.props), plan: planReference(spec, v, file) }))
265
- );
266
- }
267
-
268
130
  // ../visual/src/capture.ts
269
131
  import { mkdir, writeFile } from "node:fs/promises";
270
132
  import { dirname } from "node:path";
@@ -722,9 +584,9 @@ function selectImpact(input) {
722
584
  app
723
585
  };
724
586
  }
725
- function storyId(title, exportName2) {
587
+ function storyId(title, exportName) {
726
588
  const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
727
- const startCase = exportName2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
589
+ const startCase = exportName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
728
590
  return `${sanitize(title)}--${sanitize(startCase)}`;
729
591
  }
730
592
  function storiesFromSource(files) {
@@ -0,0 +1,113 @@
1
+ import type { BridgeContext } from './context.js';
2
+ /**
3
+ * Save a component's guidance (docs.json) the way the Library edit mode does: merged into the docs on the branch,
4
+ * cleaned, validated (a summary, no empty items, no em dash), then either one new branch sent for review (Tier 1,
5
+ * patch changeset, the Jira key first in its name) or one commit on an open branch (`pr`).
6
+ */
7
+ export declare function saveDocs(ctx: BridgeContext, a: {
8
+ component: string;
9
+ docs: Record<string, unknown>;
10
+ pr?: number | string | undefined;
11
+ title?: string | undefined;
12
+ notes?: string | undefined;
13
+ jira?: string | undefined;
14
+ product?: string | undefined;
15
+ dryRun?: boolean | undefined;
16
+ }): Promise<{
17
+ status: "dry-run";
18
+ pr: number;
19
+ path: string;
20
+ content: string;
21
+ said?: undefined;
22
+ url?: undefined;
23
+ title?: undefined;
24
+ product?: undefined;
25
+ } | {
26
+ status: "preview";
27
+ pr: number;
28
+ path: string;
29
+ content: string;
30
+ said: string;
31
+ url?: undefined;
32
+ title?: undefined;
33
+ product?: undefined;
34
+ } | {
35
+ said: string;
36
+ continued?: boolean | undefined;
37
+ status: "unchanged" | "committed";
38
+ pr: number;
39
+ path: string;
40
+ commit: string | null;
41
+ content?: undefined;
42
+ url?: undefined;
43
+ title?: undefined;
44
+ product?: undefined;
45
+ } | {
46
+ status: "unchanged";
47
+ path: string;
48
+ said: string;
49
+ pr?: undefined;
50
+ content?: undefined;
51
+ url?: undefined;
52
+ title?: undefined;
53
+ product?: undefined;
54
+ } | {
55
+ said?: string | undefined;
56
+ status: "preview" | "dry-run";
57
+ path: string;
58
+ content: string;
59
+ product: string;
60
+ pr?: undefined;
61
+ url?: undefined;
62
+ title?: undefined;
63
+ } | {
64
+ status: "sent-for-review";
65
+ pr: number;
66
+ url: string;
67
+ title: string;
68
+ path: string;
69
+ product: string;
70
+ said: string;
71
+ content?: undefined;
72
+ }>;
73
+ /** The settings a designer reads in Studio Settings, safe to show: roles, who ships, the owner override, protection, products, previews. */
74
+ export declare function getSettings(ctx: BridgeContext): Promise<{
75
+ repo: string | null;
76
+ main: string;
77
+ roles: {
78
+ teamsNotRead?: string[] | undefined;
79
+ designApproval: string[];
80
+ techApproval: string[];
81
+ ship: string;
82
+ };
83
+ ownerOverride: {
84
+ on: boolean;
85
+ who: string[];
86
+ reason: string;
87
+ };
88
+ protection: string;
89
+ products: {
90
+ key: string;
91
+ name: string;
92
+ default: boolean;
93
+ }[];
94
+ previews: string[];
95
+ visualImpact: import("@bridge-ds/core").VisualImpactMode;
96
+ note: string;
97
+ }>;
98
+ /** Which components are in Figma (Studio's "In Figma" column), and whether code images exist for the fidelity check. */
99
+ export declare function figmaCoverage(ctx: BridgeContext, a?: {
100
+ component?: string | undefined;
101
+ }): Promise<{
102
+ components: never[];
103
+ summary: string;
104
+ fidelity?: undefined;
105
+ } | {
106
+ summary: string;
107
+ components: {
108
+ why?: string | undefined;
109
+ name: string;
110
+ inFigma: string;
111
+ }[];
112
+ fidelity: string;
113
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/mcp",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Bridge MCP server: lets Claude (Claude Code, Claude Desktop or any MCP client) read, check and propose changes to a DTCG design system repo through pull requests.",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "build:package": "node ../../scripts/build-packages.mjs mcp"
55
55
  },
56
56
  "dependencies": {
57
- "@bridge-ds/core": "^0.6.0",
58
- "@bridge-ds/visual": "^0.3.0",
57
+ "@bridge-ds/core": "^0.8.0",
58
+ "@bridge-ds/visual": "^0.3.2",
59
59
  "@modelcontextprotocol/sdk": "1.31.0",
60
60
  "typescript": "5.9.3",
61
61
  "zod": "3.25.76"