@sxl-studio/bridge 1.7.1 → 1.7.3

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 (197) hide show
  1. package/README.md +342 -16
  2. package/dist/agent-recipes.d.ts +781 -11
  3. package/dist/agent-recipes.js +886 -13
  4. package/dist/agent-recipes.js.map +1 -1
  5. package/dist/agent-runbook.d.ts +50 -0
  6. package/dist/agent-runbook.js +243 -0
  7. package/dist/agent-runbook.js.map +1 -0
  8. package/dist/asset-upload.d.ts +63 -0
  9. package/dist/asset-upload.js +225 -0
  10. package/dist/asset-upload.js.map +1 -0
  11. package/dist/audit-store.d.ts +15 -0
  12. package/dist/audit-store.js +100 -0
  13. package/dist/audit-store.js.map +1 -0
  14. package/dist/audit.d.ts +4 -3
  15. package/dist/audit.js +37 -4
  16. package/dist/audit.js.map +1 -1
  17. package/dist/auth.d.ts +8 -1
  18. package/dist/auth.js +41 -1
  19. package/dist/auth.js.map +1 -1
  20. package/dist/bridge-agent-workflow-validation-cli.d.ts +2 -0
  21. package/dist/bridge-agent-workflow-validation-cli.js +68 -0
  22. package/dist/bridge-agent-workflow-validation-cli.js.map +1 -0
  23. package/dist/bridge-agent-workflow-validation.d.ts +42 -0
  24. package/dist/bridge-agent-workflow-validation.js +170 -0
  25. package/dist/bridge-agent-workflow-validation.js.map +1 -0
  26. package/dist/bridge-contract-audit.d.ts +45 -0
  27. package/dist/bridge-contract-audit.js +345 -0
  28. package/dist/bridge-contract-audit.js.map +1 -0
  29. package/dist/bridge-health-cli.d.ts +2 -0
  30. package/dist/bridge-health-cli.js +115 -0
  31. package/dist/bridge-health-cli.js.map +1 -0
  32. package/dist/bridge-health.d.ts +33 -0
  33. package/dist/bridge-health.js +594 -0
  34. package/dist/bridge-health.js.map +1 -0
  35. package/dist/bridge-live-validation-cli.d.ts +2 -0
  36. package/dist/bridge-live-validation-cli.js +114 -0
  37. package/dist/bridge-live-validation-cli.js.map +1 -0
  38. package/dist/bridge-live-validation.d.ts +39 -0
  39. package/dist/bridge-live-validation.js +1141 -0
  40. package/dist/bridge-live-validation.js.map +1 -0
  41. package/dist/bridge-performance-profile.d.ts +81 -0
  42. package/dist/bridge-performance-profile.js +227 -0
  43. package/dist/bridge-performance-profile.js.map +1 -0
  44. package/dist/bridge-readiness-cli.d.ts +30 -0
  45. package/dist/bridge-readiness-cli.js +242 -0
  46. package/dist/bridge-readiness-cli.js.map +1 -0
  47. package/dist/bridge-runtime-summary.d.ts +50 -0
  48. package/dist/bridge-runtime-summary.js +112 -0
  49. package/dist/bridge-runtime-summary.js.map +1 -0
  50. package/dist/bridge-workflow-smoke-cli.d.ts +2 -0
  51. package/dist/bridge-workflow-smoke-cli.js +126 -0
  52. package/dist/bridge-workflow-smoke-cli.js.map +1 -0
  53. package/dist/bridge-workflow-smoke.d.ts +39 -0
  54. package/dist/bridge-workflow-smoke.js +431 -0
  55. package/dist/bridge-workflow-smoke.js.map +1 -0
  56. package/dist/codeconnect-suggestions.d.ts +74 -0
  57. package/dist/codeconnect-suggestions.js +398 -0
  58. package/dist/codeconnect-suggestions.js.map +1 -0
  59. package/dist/codeconnect-template.d.ts +98 -0
  60. package/dist/codeconnect-template.js +280 -0
  61. package/dist/codeconnect-template.js.map +1 -0
  62. package/dist/command-queue.d.ts +24 -2
  63. package/dist/command-queue.js +295 -25
  64. package/dist/command-queue.js.map +1 -1
  65. package/dist/command-safety.d.ts +13 -0
  66. package/dist/command-safety.js +59 -0
  67. package/dist/command-safety.js.map +1 -0
  68. package/dist/enabled-library-search.d.ts +49 -0
  69. package/dist/enabled-library-search.js +151 -0
  70. package/dist/enabled-library-search.js.map +1 -0
  71. package/dist/figma-mcp-parity.d.ts +49 -0
  72. package/dist/figma-mcp-parity.js +368 -0
  73. package/dist/figma-mcp-parity.js.map +1 -0
  74. package/dist/figma-mcp-skills-parity.d.ts +61 -0
  75. package/dist/figma-mcp-skills-parity.js +434 -0
  76. package/dist/figma-mcp-skills-parity.js.map +1 -0
  77. package/dist/figma-rest-diagnostics.d.ts +50 -0
  78. package/dist/figma-rest-diagnostics.js +314 -0
  79. package/dist/figma-rest-diagnostics.js.map +1 -0
  80. package/dist/figma-rest.d.ts +27 -0
  81. package/dist/figma-rest.js +116 -0
  82. package/dist/figma-rest.js.map +1 -0
  83. package/dist/http-api.d.ts +14 -2
  84. package/dist/http-api.js +323 -17
  85. package/dist/http-api.js.map +1 -1
  86. package/dist/index.js +25 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/mcp-factory.d.ts +6 -1
  89. package/dist/mcp-factory.js +23 -4
  90. package/dist/mcp-factory.js.map +1 -1
  91. package/dist/mcp-runtime-probe.d.ts +22 -0
  92. package/dist/mcp-runtime-probe.js +777 -0
  93. package/dist/mcp-runtime-probe.js.map +1 -0
  94. package/dist/mcp-server.d.ts +2 -1
  95. package/dist/mcp-server.js +2 -2
  96. package/dist/mcp-server.js.map +1 -1
  97. package/dist/sxl-mcp-instructions.js +97 -25
  98. package/dist/sxl-mcp-instructions.js.map +1 -1
  99. package/dist/tools/audit.d.ts +22 -6
  100. package/dist/tools/audit.js +49 -7
  101. package/dist/tools/audit.js.map +1 -1
  102. package/dist/tools/capability-matrix.d.ts +22 -0
  103. package/dist/tools/capability-matrix.js +38 -0
  104. package/dist/tools/capability-matrix.js.map +1 -0
  105. package/dist/tools/catalogue-bootstrap.d.ts +1 -0
  106. package/dist/tools/catalogue-bootstrap.js +665 -30
  107. package/dist/tools/catalogue-bootstrap.js.map +1 -1
  108. package/dist/tools/code-connect-context.d.ts +3 -0
  109. package/dist/tools/code-connect-context.js +319 -0
  110. package/dist/tools/code-connect-context.js.map +1 -0
  111. package/dist/tools/code-connect-template.d.ts +3 -0
  112. package/dist/tools/code-connect-template.js +111 -0
  113. package/dist/tools/code-connect-template.js.map +1 -0
  114. package/dist/tools/composition.js +13 -28
  115. package/dist/tools/composition.js.map +1 -1
  116. package/dist/tools/compositions-orchestration.d.ts +14 -14
  117. package/dist/tools/compositions-orchestration.js +2 -2
  118. package/dist/tools/compositions-orchestration.js.map +1 -1
  119. package/dist/tools/data.js +839 -27
  120. package/dist/tools/data.js.map +1 -1
  121. package/dist/tools/design-context.d.ts +3 -0
  122. package/dist/tools/design-context.js +197 -0
  123. package/dist/tools/design-context.js.map +1 -0
  124. package/dist/tools/destructive-confirmation.d.ts +10 -0
  125. package/dist/tools/destructive-confirmation.js +22 -0
  126. package/dist/tools/destructive-confirmation.js.map +1 -0
  127. package/dist/tools/diagnostics.js +76 -51
  128. package/dist/tools/diagnostics.js.map +1 -1
  129. package/dist/tools/figma-mcp-design.d.ts +3 -0
  130. package/dist/tools/figma-mcp-design.js +377 -0
  131. package/dist/tools/figma-mcp-design.js.map +1 -0
  132. package/dist/tools/figma-nodes.js +57 -43
  133. package/dist/tools/figma-nodes.js.map +1 -1
  134. package/dist/tools/figma-rc-extended.js +23 -6
  135. package/dist/tools/figma-rc-extended.js.map +1 -1
  136. package/dist/tools/figma-rest.d.ts +39 -0
  137. package/dist/tools/figma-rest.js +279 -0
  138. package/dist/tools/figma-rest.js.map +1 -0
  139. package/dist/tools/git.js +11 -7
  140. package/dist/tools/git.js.map +1 -1
  141. package/dist/tools/large-data.d.ts +14 -0
  142. package/dist/tools/large-data.js +189 -0
  143. package/dist/tools/large-data.js.map +1 -0
  144. package/dist/tools/meta.d.ts +6 -1
  145. package/dist/tools/meta.js +89 -11
  146. package/dist/tools/meta.js.map +1 -1
  147. package/dist/tools/metadata.d.ts +3 -0
  148. package/dist/tools/metadata.js +140 -0
  149. package/dist/tools/metadata.js.map +1 -0
  150. package/dist/tools/mockup.d.ts +15 -156
  151. package/dist/tools/mockup.js +54 -121
  152. package/dist/tools/mockup.js.map +1 -1
  153. package/dist/tools/orchestration.js +73 -45
  154. package/dist/tools/orchestration.js.map +1 -1
  155. package/dist/tools/prompts.d.ts +3 -0
  156. package/dist/tools/prompts.js +219 -0
  157. package/dist/tools/prompts.js.map +1 -0
  158. package/dist/tools/registry.d.ts +19 -1
  159. package/dist/tools/registry.js +4 -4
  160. package/dist/tools/registry.js.map +1 -1
  161. package/dist/tools/resources.d.ts +19 -2
  162. package/dist/tools/resources.js +149 -5
  163. package/dist/tools/resources.js.map +1 -1
  164. package/dist/tools/schema-contracts.d.ts +4763 -0
  165. package/dist/tools/schema-contracts.js +814 -0
  166. package/dist/tools/schema-contracts.js.map +1 -0
  167. package/dist/tools/screenshot.d.ts +3 -0
  168. package/dist/tools/screenshot.js +144 -0
  169. package/dist/tools/screenshot.js.map +1 -0
  170. package/dist/tools/shared.d.ts +11 -1
  171. package/dist/tools/shared.js +55 -2
  172. package/dist/tools/shared.js.map +1 -1
  173. package/dist/tools/styles-orchestration.d.ts +2 -2
  174. package/dist/tools/styles-orchestration.js +13 -5
  175. package/dist/tools/styles-orchestration.js.map +1 -1
  176. package/dist/tools/styles.js +22 -8
  177. package/dist/tools/styles.js.map +1 -1
  178. package/dist/tools/tokens.d.ts +31 -692
  179. package/dist/tools/tokens.js +175 -135
  180. package/dist/tools/tokens.js.map +1 -1
  181. package/dist/tools/variable-defs.d.ts +3 -0
  182. package/dist/tools/variable-defs.js +338 -0
  183. package/dist/tools/variable-defs.js.map +1 -0
  184. package/dist/tools/variables-orchestration.js +13 -5
  185. package/dist/tools/variables-orchestration.js.map +1 -1
  186. package/dist/tools/variables.js +18 -15
  187. package/dist/tools/variables.js.map +1 -1
  188. package/dist/types.d.ts +53 -0
  189. package/dist/ultimate-readiness-audit.d.ts +37 -0
  190. package/dist/ultimate-readiness-audit.js +431 -0
  191. package/dist/ultimate-readiness-audit.js.map +1 -0
  192. package/dist/workflow-planner.d.ts +57 -0
  193. package/dist/workflow-planner.js +464 -0
  194. package/dist/workflow-planner.js.map +1 -0
  195. package/dist/ws-server.js +16 -3
  196. package/dist/ws-server.js.map +1 -1
  197. package/package.json +19 -3
@@ -0,0 +1,1141 @@
1
+ import { getBridgePackageVersion } from "./bridge-version.js";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { generateCodeConnectTemplate } from "./codeconnect-template.js";
6
+ import { runMcpRuntimeProbe } from "./mcp-runtime-probe.js";
7
+ import { registerDataTools } from "./tools/data.js";
8
+ function normalizeBaseUrl(baseUrl) {
9
+ return baseUrl.replace(/\/+$/, "");
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function addFinding(findings, finding) {
15
+ findings.push(finding);
16
+ }
17
+ function statusFromFindings(findings) {
18
+ if (findings.some((finding) => finding.severity === "error"))
19
+ return "fail";
20
+ if (findings.some((finding) => finding.severity === "warning"))
21
+ return "warn";
22
+ return "pass";
23
+ }
24
+ async function fetchJson(options) {
25
+ const controller = new AbortController();
26
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
27
+ try {
28
+ const headers = {};
29
+ if (options.authToken)
30
+ headers.Authorization = `Bearer ${options.authToken}`;
31
+ if (options.body !== undefined)
32
+ headers["Content-Type"] = "application/json";
33
+ const response = await options.fetchImpl(`${options.baseUrl}${options.endpoint}`, {
34
+ method: options.method ?? "GET",
35
+ headers,
36
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
37
+ signal: controller.signal,
38
+ });
39
+ const text = await response.text();
40
+ let body = null;
41
+ if (text.trim().length > 0) {
42
+ try {
43
+ body = JSON.parse(text);
44
+ }
45
+ catch {
46
+ body = text;
47
+ }
48
+ }
49
+ return { status: response.status, body };
50
+ }
51
+ finally {
52
+ clearTimeout(timeout);
53
+ }
54
+ }
55
+ function asEnvelope(body) {
56
+ return isRecord(body) ? body : {};
57
+ }
58
+ function decodeCommandError(error) {
59
+ if (!error)
60
+ return "Command failed.";
61
+ try {
62
+ const parsed = JSON.parse(error);
63
+ if (isRecord(parsed) && typeof parsed["message"] === "string") {
64
+ return parsed["message"];
65
+ }
66
+ }
67
+ catch {
68
+ // Plain command errors are already human-readable.
69
+ }
70
+ return error;
71
+ }
72
+ async function validateHttpEndpoint(options) {
73
+ const startedAt = Date.now();
74
+ const response = await fetchJson(options);
75
+ const durationMs = Date.now() - startedAt;
76
+ if (response.status === 401) {
77
+ addFinding(options.findings, {
78
+ severity: "error",
79
+ code: "LIVE_UNAUTHORIZED",
80
+ message: "Bridge requires Authorization. Pass --auth-token or set BRIDGE_AUTH_TOKEN.",
81
+ step: options.name,
82
+ });
83
+ return { name: options.name, kind: "http", ok: false, status: "fail", durationMs };
84
+ }
85
+ if (response.status < 200 || response.status >= 300) {
86
+ addFinding(options.findings, {
87
+ severity: "error",
88
+ code: "LIVE_HTTP_FAILED",
89
+ message: `GET ${options.endpoint} returned HTTP ${response.status}.`,
90
+ step: options.name,
91
+ });
92
+ return { name: options.name, kind: "http", ok: false, status: "fail", durationMs };
93
+ }
94
+ const validation = options.validate(response.body);
95
+ if (!validation.ok) {
96
+ addFinding(options.findings, {
97
+ severity: "error",
98
+ code: validation.error ? "LIVE_HTTP_STATUS_FAILED" : "LIVE_HTTP_SHAPE_INVALID",
99
+ message: validation.error ?? `GET ${options.endpoint} did not return the expected shape.`,
100
+ step: options.name,
101
+ });
102
+ return {
103
+ name: options.name,
104
+ kind: "http",
105
+ ok: false,
106
+ status: "fail",
107
+ durationMs,
108
+ details: validation.details,
109
+ };
110
+ }
111
+ if (validation.warning) {
112
+ addFinding(options.findings, {
113
+ severity: "warning",
114
+ code: "LIVE_HTTP_WARNING",
115
+ message: validation.warning,
116
+ step: options.name,
117
+ });
118
+ }
119
+ return {
120
+ name: options.name,
121
+ kind: "http",
122
+ ok: true,
123
+ status: validation.warning ? "warn" : "pass",
124
+ durationMs,
125
+ details: validation.details,
126
+ };
127
+ }
128
+ async function validateMcpStreamableHttp(options) {
129
+ const startedAt = Date.now();
130
+ const result = await runMcpRuntimeProbe(options);
131
+ for (const warning of result.warnings) {
132
+ addFinding(options.findings, {
133
+ severity: "warning",
134
+ code: warning.code,
135
+ message: warning.message,
136
+ step: "runtime.mcpStreamableHttp",
137
+ });
138
+ }
139
+ if (!result.ok) {
140
+ addFinding(options.findings, {
141
+ severity: "error",
142
+ code: result.error?.code ?? "MCP_STREAMABLE_HTTP_FAILED",
143
+ message: result.error?.message ?? "MCP /mcp probe failed.",
144
+ step: "runtime.mcpStreamableHttp",
145
+ });
146
+ }
147
+ return {
148
+ name: "runtime.mcpStreamableHttp",
149
+ kind: "mcp",
150
+ ok: result.ok,
151
+ status: result.ok ? result.warnings.length > 0 ? "warn" : "pass" : "fail",
152
+ durationMs: Date.now() - startedAt,
153
+ details: result.details,
154
+ };
155
+ }
156
+ async function runCommand(options) {
157
+ const startedAt = Date.now();
158
+ const response = await fetchJson({
159
+ ...options,
160
+ endpoint: "/api/command",
161
+ method: "POST",
162
+ body: {
163
+ commandType: options.commandType,
164
+ payload: options.payload ?? {},
165
+ },
166
+ });
167
+ const durationMs = Date.now() - startedAt;
168
+ if (response.status === 401) {
169
+ addFinding(options.findings, {
170
+ severity: "error",
171
+ code: "LIVE_UNAUTHORIZED",
172
+ message: "Bridge requires Authorization. Pass --auth-token or set BRIDGE_AUTH_TOKEN.",
173
+ step: options.commandType,
174
+ });
175
+ return {
176
+ step: { name: options.commandType, kind: "command", commandType: options.commandType, ok: false, status: "fail", durationMs },
177
+ result: null,
178
+ };
179
+ }
180
+ if (response.status === 503) {
181
+ addFinding(options.findings, {
182
+ severity: "error",
183
+ code: "LIVE_PLUGIN_NOT_CONNECTED",
184
+ message: "No plugin is connected to Bridge.",
185
+ step: options.commandType,
186
+ });
187
+ return {
188
+ step: { name: options.commandType, kind: "command", commandType: options.commandType, ok: false, status: "fail", durationMs },
189
+ result: null,
190
+ };
191
+ }
192
+ if (response.status < 200 || response.status >= 300) {
193
+ addFinding(options.findings, {
194
+ severity: "error",
195
+ code: "LIVE_COMMAND_HTTP_FAILED",
196
+ message: `POST /api/command ${options.commandType} returned HTTP ${response.status}.`,
197
+ step: options.commandType,
198
+ });
199
+ return {
200
+ step: { name: options.commandType, kind: "command", commandType: options.commandType, ok: false, status: "fail", durationMs },
201
+ result: null,
202
+ };
203
+ }
204
+ const envelope = asEnvelope(response.body);
205
+ if (envelope.status !== "completed") {
206
+ addFinding(options.findings, {
207
+ severity: "error",
208
+ code: "LIVE_COMMAND_FAILED",
209
+ message: `${options.commandType} failed: ${decodeCommandError(envelope.error)}`,
210
+ step: options.commandType,
211
+ });
212
+ return {
213
+ step: {
214
+ name: options.commandType,
215
+ kind: "command",
216
+ commandType: options.commandType,
217
+ ok: false,
218
+ status: "fail",
219
+ durationMs: typeof envelope.durationMs === "number" ? envelope.durationMs : durationMs,
220
+ details: { commandStatus: envelope.status ?? null },
221
+ },
222
+ result: envelope.result ?? null,
223
+ };
224
+ }
225
+ const validation = options.validate?.(envelope.result);
226
+ if (validation && !validation.ok) {
227
+ addFinding(options.findings, {
228
+ severity: "error",
229
+ code: "LIVE_COMMAND_RESULT_INVALID",
230
+ message: `${options.commandType} returned an unexpected result shape.`,
231
+ step: options.commandType,
232
+ });
233
+ }
234
+ else if (validation?.warning) {
235
+ addFinding(options.findings, {
236
+ severity: "warning",
237
+ code: "LIVE_COMMAND_WARNING",
238
+ message: validation.warning,
239
+ step: options.commandType,
240
+ });
241
+ }
242
+ return {
243
+ step: {
244
+ name: options.commandType,
245
+ kind: "command",
246
+ commandType: options.commandType,
247
+ ok: validation ? validation.ok : true,
248
+ status: validation && !validation.ok ? "fail" : validation?.warning ? "warn" : "pass",
249
+ durationMs: typeof envelope.durationMs === "number" ? envelope.durationMs : durationMs,
250
+ details: validation?.details,
251
+ },
252
+ result: envelope.result ?? null,
253
+ };
254
+ }
255
+ async function runLocalStep(options) {
256
+ const startedAt = Date.now();
257
+ try {
258
+ const validation = await options.run();
259
+ if (!validation.ok) {
260
+ addFinding(options.findings, {
261
+ severity: "error",
262
+ code: "LIVE_LOCAL_CHECK_FAILED",
263
+ message: `${options.name} returned an unexpected result shape.`,
264
+ step: options.name,
265
+ });
266
+ }
267
+ else if (validation.warning) {
268
+ addFinding(options.findings, {
269
+ severity: "warning",
270
+ code: "LIVE_LOCAL_CHECK_WARNING",
271
+ message: validation.warning,
272
+ step: options.name,
273
+ });
274
+ }
275
+ return {
276
+ name: options.name,
277
+ kind: "local",
278
+ ok: validation.ok,
279
+ status: validation.ok ? validation.warning ? "warn" : "pass" : "fail",
280
+ durationMs: Date.now() - startedAt,
281
+ details: validation.details,
282
+ };
283
+ }
284
+ catch (error) {
285
+ addFinding(options.findings, {
286
+ severity: "error",
287
+ code: "LIVE_LOCAL_CHECK_ERROR",
288
+ message: `${options.name} failed: ${error instanceof Error ? error.message : String(error)}`,
289
+ step: options.name,
290
+ });
291
+ return {
292
+ name: options.name,
293
+ kind: "local",
294
+ ok: false,
295
+ status: "fail",
296
+ durationMs: Date.now() - startedAt,
297
+ };
298
+ }
299
+ }
300
+ function smokeScreenSpec(dryRun = true) {
301
+ return {
302
+ name: `SXL Bridge Live Validation ${new Date().toISOString().slice(0, 19)}`,
303
+ dryRun,
304
+ layout: { mode: "VERTICAL", itemSpacing: 8, padding: 16, width: 320 },
305
+ items: [
306
+ {
307
+ kind: "text",
308
+ name: "Validation Title",
309
+ value: "SXL Bridge live validation",
310
+ fontSize: 16,
311
+ },
312
+ { kind: "spacer", size: 8 },
313
+ ],
314
+ };
315
+ }
316
+ function smokeVariableSpec() {
317
+ const suffix = new Date().toISOString().slice(0, 10);
318
+ return {
319
+ collections: [
320
+ {
321
+ collectionName: `SXL Bridge Live Validation/${suffix}`,
322
+ modes: [{ key: "default", name: "Default" }],
323
+ variables: [
324
+ {
325
+ name: "color/live-validation",
326
+ type: "COLOR",
327
+ valuesByMode: {
328
+ default: { value: "#3366ff" },
329
+ },
330
+ },
331
+ ],
332
+ },
333
+ ],
334
+ dryRun: true,
335
+ };
336
+ }
337
+ function smokeStyleSpec() {
338
+ const suffix = new Date().toISOString().slice(0, 10);
339
+ return {
340
+ paintStyles: [
341
+ {
342
+ name: `SXL Bridge Live Validation/${suffix}/Paint`,
343
+ paints: [{ type: "SOLID", color: "#3366ff" }],
344
+ },
345
+ ],
346
+ dryRun: true,
347
+ };
348
+ }
349
+ function smokeVariableCoverageSuggestion() {
350
+ return {
351
+ nodeId: "SXL_BRIDGE_DRY_RUN_NODE",
352
+ property: "fills[0].color",
353
+ variableId: "SXL_BRIDGE_DRY_RUN_VARIABLE",
354
+ variableName: "SXL Bridge Dry Run Variable",
355
+ };
356
+ }
357
+ function smokeStyleCoverageSuggestion() {
358
+ return {
359
+ nodeId: "SXL_BRIDGE_DRY_RUN_NODE",
360
+ property: "fillStyleId · fills[0]",
361
+ styleId: "SXL_BRIDGE_DRY_RUN_STYLE",
362
+ styleName: "SXL Bridge Dry Run Style",
363
+ };
364
+ }
365
+ function toolsCatalogueValidation(requiredTools) {
366
+ return (body) => {
367
+ if (!isRecord(body) || !Array.isArray(body["tools"]))
368
+ return { ok: false };
369
+ const tools = body["tools"].filter(isRecord);
370
+ const names = new Set(tools.map((tool) => typeof tool["name"] === "string" ? tool["name"] : ""));
371
+ const missing = requiredTools.filter((name) => !names.has(name));
372
+ const dryRunTemplate = tools.find((tool) => tool["name"] === "generate_code_connect_template");
373
+ return {
374
+ ok: missing.length === 0,
375
+ details: {
376
+ toolCount: tools.length,
377
+ missing,
378
+ hasCodeConnectContext: names.has("get_context_for_code_connect"),
379
+ hasCodeConnectTemplate: names.has("generate_code_connect_template"),
380
+ templateDryRunSupported: isRecord(dryRunTemplate) ? dryRunTemplate["dryRunSupported"] === true : false,
381
+ },
382
+ };
383
+ };
384
+ }
385
+ function statusEnvelopeValidation(options) {
386
+ return (body) => {
387
+ const shapeOk = isRecord(body) &&
388
+ typeof body["ok"] === "boolean" &&
389
+ typeof body["status"] === "string" &&
390
+ Array.isArray(body[options.requiredArray]) &&
391
+ isRecord(body["summary"]);
392
+ const details = isRecord(body) && isRecord(body["summary"]) ? body["summary"] : undefined;
393
+ if (!shapeOk) {
394
+ return { ok: false, details };
395
+ }
396
+ const summaryCount = details?.[options.summaryCountKey];
397
+ const countOk = typeof summaryCount === "number" ? summaryCount > 0 : true;
398
+ if (!countOk) {
399
+ return {
400
+ ok: false,
401
+ details,
402
+ error: `GET ${options.endpoint} returned an empty ${options.summaryCountKey} summary.`,
403
+ };
404
+ }
405
+ if (body["status"] === "fail" || body["ok"] !== true) {
406
+ return {
407
+ ok: false,
408
+ details,
409
+ error: `GET ${options.endpoint} reported status=${String(body["status"])}.`,
410
+ };
411
+ }
412
+ return {
413
+ ok: true,
414
+ details,
415
+ warning: body["status"] === "warn" ? `GET ${options.endpoint} reported warnings.` : undefined,
416
+ };
417
+ };
418
+ }
419
+ function okObject(result) {
420
+ return { ok: isRecord(result) };
421
+ }
422
+ function operatorRunbookValidation(body) {
423
+ const workflows = isRecord(body) && Array.isArray(body["workflows"]) ? body["workflows"] : [];
424
+ const workflowIds = new Set(workflows
425
+ .filter(isRecord)
426
+ .map((workflow) => typeof workflow["id"] === "string" ? workflow["id"] : "")
427
+ .filter(Boolean));
428
+ const ok = isRecord(body) &&
429
+ body["id"] === "sxl-operator-runbook" &&
430
+ Array.isArray(body["startHere"]) &&
431
+ Array.isArray(body["globalSafetyRules"]) &&
432
+ workflowIds.has("draw-or-update-screen") &&
433
+ workflowIds.has("official-figma-mcp-companion");
434
+ return {
435
+ ok,
436
+ details: { workflowCount: workflows.length },
437
+ error: ok ? undefined : "GET /api/operator-runbook did not return the expected SXL operator runbook.",
438
+ };
439
+ }
440
+ function pluginStatusValidation(result) {
441
+ if (!isRecord(result))
442
+ return { ok: false };
443
+ return {
444
+ ok: typeof result["writesAllowed"] === "boolean" && typeof result["sessionActive"] === "boolean",
445
+ details: {
446
+ editorType: typeof result["editorType"] === "string" ? result["editorType"] : null,
447
+ writesAllowed: result["writesAllowed"] === true,
448
+ sessionActive: result["sessionActive"] === true,
449
+ },
450
+ };
451
+ }
452
+ function listValidation(key) {
453
+ return (result) => {
454
+ if (!isRecord(result))
455
+ return { ok: false };
456
+ const value = result[key];
457
+ return {
458
+ ok: Array.isArray(value),
459
+ details: { count: Array.isArray(value) ? value.length : null },
460
+ };
461
+ };
462
+ }
463
+ function findComponentsValidation(result) {
464
+ if (!isRecord(result))
465
+ return { ok: false };
466
+ const components = Array.isArray(result["components"]) ? result["components"] : [];
467
+ const summary = isRecord(result["summary"]) ? result["summary"] : {};
468
+ return {
469
+ ok: result["ok"] === true && Array.isArray(result["components"]) && isRecord(result["summary"]),
470
+ details: {
471
+ returned: summary["returned"] ?? components.length,
472
+ totalLocal: summary["totalLocal"] ?? null,
473
+ hasMore: summary["hasMore"] ?? null,
474
+ },
475
+ };
476
+ }
477
+ function buildDryRunValidation(result) {
478
+ if (!isRecord(result))
479
+ return { ok: false };
480
+ const summary = isRecord(result["summary"]) ? result["summary"] : {};
481
+ const errors = Array.isArray(summary["errors"]) ? summary["errors"] : [];
482
+ return {
483
+ ok: result["ok"] === true && result["dryRun"] === true && errors.length === 0,
484
+ details: {
485
+ dryRun: result["dryRun"] === true,
486
+ errors: errors.length,
487
+ totalItems: summary["totalItems"] ?? null,
488
+ },
489
+ };
490
+ }
491
+ function auditSummaryValidation(result) {
492
+ if (!isRecord(result))
493
+ return { ok: false };
494
+ return {
495
+ ok: result["ok"] === true && typeof result["scannedNodes"] === "number",
496
+ details: {
497
+ scannedNodes: result["scannedNodes"] ?? null,
498
+ totalUsages: result["totalUsages"] ?? null,
499
+ },
500
+ };
501
+ }
502
+ function coverageSummaryValidation(result) {
503
+ if (!isRecord(result))
504
+ return { ok: false };
505
+ return {
506
+ ok: typeof result["scannedNodes"] === "number" &&
507
+ typeof result["missing"] === "number" &&
508
+ isRecord(result["byProperty"]) &&
509
+ isRecord(result["byPage"]),
510
+ details: {
511
+ scope: typeof result["scope"] === "string" ? result["scope"] : null,
512
+ scannedNodes: result["scannedNodes"] ?? null,
513
+ missing: result["missing"] ?? null,
514
+ withSuggestion: result["withSuggestion"] ?? null,
515
+ truncated: result["truncated"] === true,
516
+ },
517
+ };
518
+ }
519
+ function coveragePageValidation(result) {
520
+ if (!isRecord(result))
521
+ return { ok: false };
522
+ const missingItems = Array.isArray(result["missingItems"]) ? result["missingItems"] : [];
523
+ return {
524
+ ok: typeof result["scannedNodes"] === "number" &&
525
+ typeof result["missing"] === "number" &&
526
+ Array.isArray(result["missingItems"]),
527
+ details: {
528
+ scannedNodes: result["scannedNodes"] ?? null,
529
+ missing: result["missing"] ?? null,
530
+ returned: missingItems.length,
531
+ cursor: typeof result["cursor"] === "string" ? result["cursor"] : null,
532
+ },
533
+ };
534
+ }
535
+ function dryRunApplyPreviewValidation(result) {
536
+ if (!isRecord(result))
537
+ return { ok: false };
538
+ const preview = Array.isArray(result["preview"]) ? result["preview"] : [];
539
+ const errors = Array.isArray(result["errors"]) ? result["errors"] : [];
540
+ return {
541
+ ok: result["ok"] === true && result["dryRun"] === true && preview.length > 0 && errors.length === 0,
542
+ details: {
543
+ dryRun: result["dryRun"] === true,
544
+ preview: preview.length,
545
+ skipped: Array.isArray(result["skipped"]) ? result["skipped"].length : null,
546
+ errors: errors.length,
547
+ },
548
+ };
549
+ }
550
+ function importVariableSpecDryRunValidation(result) {
551
+ if (!isRecord(result))
552
+ return { ok: false };
553
+ const totals = isRecord(result["totals"]) ? result["totals"] : {};
554
+ return {
555
+ ok: result["ok"] === true &&
556
+ result["dryRun"] === true &&
557
+ Array.isArray(result["collections"]) &&
558
+ typeof totals["variablesCreated"] === "number",
559
+ details: {
560
+ dryRun: result["dryRun"] === true,
561
+ collections: Array.isArray(result["collections"]) ? result["collections"].length : null,
562
+ variablesCreated: totals["variablesCreated"] ?? null,
563
+ errors: totals["errors"] ?? null,
564
+ },
565
+ };
566
+ }
567
+ function importStyleSpecDryRunValidation(result) {
568
+ if (!isRecord(result))
569
+ return { ok: false };
570
+ const totals = isRecord(result["totals"]) ? result["totals"] : {};
571
+ return {
572
+ ok: result["ok"] === true &&
573
+ result["dryRun"] === true &&
574
+ Array.isArray(result["paintStyles"]) &&
575
+ typeof totals["paintsCreated"] === "number",
576
+ details: {
577
+ dryRun: result["dryRun"] === true,
578
+ paintStyles: Array.isArray(result["paintStyles"]) ? result["paintStyles"].length : null,
579
+ paintsCreated: totals["paintsCreated"] ?? null,
580
+ errors: totals["errors"] ?? null,
581
+ },
582
+ };
583
+ }
584
+ function dedupeDryRunValidation(result) {
585
+ if (!isRecord(result))
586
+ return { ok: false };
587
+ return {
588
+ ok: result["ok"] === true && result["dryRun"] === true && Array.isArray(result["groups"]),
589
+ details: {
590
+ dryRun: result["dryRun"] === true,
591
+ groups: Array.isArray(result["groups"]) ? result["groups"].length : null,
592
+ mergedCount: result["mergedCount"] ?? null,
593
+ errors: Array.isArray(result["errors"]) ? result["errors"].length : null,
594
+ },
595
+ };
596
+ }
597
+ function listCompositionsValidation(result) {
598
+ if (!isRecord(result))
599
+ return { ok: false };
600
+ return {
601
+ ok: typeof result["count"] === "number" && Array.isArray(result["compositions"]),
602
+ details: {
603
+ count: result["count"] ?? null,
604
+ returned: Array.isArray(result["compositions"]) ? result["compositions"].length : null,
605
+ },
606
+ };
607
+ }
608
+ function bulkCompositionDryRunValidation(result) {
609
+ if (!isRecord(result))
610
+ return { ok: false };
611
+ const summary = isRecord(result["summary"]) ? result["summary"] : {};
612
+ const items = Array.isArray(result["items"]) ? result["items"] : [];
613
+ return {
614
+ ok: typeof result["ok"] === "boolean" && result["dryRun"] === true && isRecord(result["summary"]) && Array.isArray(result["items"]),
615
+ details: {
616
+ ok: result["ok"] === true,
617
+ dryRun: result["dryRun"] === true,
618
+ total: summary["total"] ?? null,
619
+ processed: summary["processed"] ?? null,
620
+ failed: summary["failed"] ?? null,
621
+ items: items.length,
622
+ },
623
+ };
624
+ }
625
+ function compositionDriftValidation(result) {
626
+ if (!isRecord(result))
627
+ return { ok: false };
628
+ const summary = isRecord(result["summary"]) ? result["summary"] : {};
629
+ return {
630
+ ok: result["ok"] === true && isRecord(result["summary"]) && Array.isArray(result["findings"]),
631
+ details: {
632
+ totalCompositions: summary["totalCompositions"] ?? null,
633
+ linked: summary["linked"] ?? null,
634
+ unlinked: summary["unlinked"] ?? null,
635
+ drift: summary["drift"] ?? null,
636
+ missing: summary["missing"] ?? null,
637
+ findings: Array.isArray(result["findings"]) ? result["findings"].length : null,
638
+ },
639
+ };
640
+ }
641
+ function unusedVariablesValidation(result) {
642
+ if (!isRecord(result))
643
+ return { ok: false };
644
+ return {
645
+ ok: typeof result["count"] === "number" && typeof result["scannedNodes"] === "number" && Array.isArray(result["variables"]),
646
+ details: {
647
+ count: result["count"] ?? null,
648
+ scannedNodes: result["scannedNodes"] ?? null,
649
+ returned: Array.isArray(result["variables"]) ? result["variables"].length : null,
650
+ },
651
+ };
652
+ }
653
+ function unusedStylesValidation(result) {
654
+ if (!isRecord(result))
655
+ return { ok: false };
656
+ return {
657
+ ok: typeof result["count"] === "number" && typeof result["scannedNodes"] === "number" && Array.isArray(result["styles"]),
658
+ details: {
659
+ count: result["count"] ?? null,
660
+ scannedNodes: result["scannedNodes"] ?? null,
661
+ returned: Array.isArray(result["styles"]) ? result["styles"].length : null,
662
+ },
663
+ };
664
+ }
665
+ function componentUsagePageValidation(result) {
666
+ if (!isRecord(result))
667
+ return { ok: false };
668
+ const usages = Array.isArray(result["usages"]) ? result["usages"] : [];
669
+ return {
670
+ ok: result["ok"] === true && typeof result["scannedNodes"] === "number" && Array.isArray(result["usages"]),
671
+ details: {
672
+ scannedNodes: result["scannedNodes"] ?? null,
673
+ totalUsages: result["totalUsages"] ?? null,
674
+ returned: usages.length,
675
+ cursor: typeof result["cursor"] === "string" ? result["cursor"] : null,
676
+ },
677
+ };
678
+ }
679
+ function componentPropSummaryValidation(result) {
680
+ if (!isRecord(result))
681
+ return { ok: false };
682
+ return {
683
+ ok: result["ok"] === true &&
684
+ typeof result["scannedNodes"] === "number" &&
685
+ isRecord(result["byProperty"]) &&
686
+ isRecord(result["byValue"]),
687
+ details: {
688
+ scannedNodes: result["scannedNodes"] ?? null,
689
+ totalUsages: result["totalUsages"] ?? null,
690
+ propertyCount: isRecord(result["byProperty"]) ? Object.keys(result["byProperty"]).length : null,
691
+ valueCount: isRecord(result["byValue"]) ? Object.keys(result["byValue"]).length : null,
692
+ },
693
+ };
694
+ }
695
+ function docFlowDryRunValidation(result) {
696
+ if (!isRecord(result))
697
+ return { ok: false };
698
+ const pages = Array.isArray(result["pages"]) ? result["pages"] : [];
699
+ return {
700
+ ok: result["ok"] === true && result["dryRun"] === true && typeof result["pageCount"] === "number",
701
+ details: {
702
+ dryRun: result["dryRun"] === true,
703
+ pageCount: result["pageCount"] ?? null,
704
+ returnedPages: pages.length,
705
+ flowName: typeof result["flowName"] === "string" ? result["flowName"] : null,
706
+ },
707
+ };
708
+ }
709
+ function readWritesAllowed(result) {
710
+ if (!isRecord(result))
711
+ return null;
712
+ return typeof result["writesAllowed"] === "boolean" ? result["writesAllowed"] : null;
713
+ }
714
+ async function codeConnectTemplateDryRunValidation() {
715
+ const root = mkdtempSync(path.join(tmpdir(), "sxl-bridge-live-codeconnect-"));
716
+ try {
717
+ const filePath = path.join(root, "src/components/LiveValidationButton.tsx");
718
+ mkdirSync(path.dirname(filePath), { recursive: true });
719
+ writeFileSync(filePath, "export function LiveValidationButton() { return <button />; }", "utf8");
720
+ const result = generateCodeConnectTemplate({
721
+ rootDir: root,
722
+ componentNames: ["LiveValidationButton"],
723
+ repositoryUrl: "https://example.invalid/sxl-live-validation",
724
+ writeFile: false,
725
+ });
726
+ return {
727
+ ok: result.ok === true &&
728
+ result.dryRun === true &&
729
+ result.write.requested === false &&
730
+ result.write.written === false &&
731
+ result.template.fileName === "LiveValidationButton.figma.ts" &&
732
+ result.template.content.includes("figma.code`<LiveValidationButton />`"),
733
+ details: {
734
+ dryRun: result.dryRun,
735
+ written: result.write.written,
736
+ fileName: result.template.fileName,
737
+ bytes: result.template.bytes,
738
+ },
739
+ };
740
+ }
741
+ finally {
742
+ rmSync(root, { recursive: true, force: true });
743
+ }
744
+ }
745
+ function readLocalToolJson(response) {
746
+ if (!isRecord(response))
747
+ return {};
748
+ const content = response["content"];
749
+ if (!Array.isArray(content) || !isRecord(content[0]) || typeof content[0]["text"] !== "string")
750
+ return {};
751
+ try {
752
+ const parsed = JSON.parse(content[0]["text"]);
753
+ return isRecord(parsed) ? parsed : {};
754
+ }
755
+ catch {
756
+ return {};
757
+ }
758
+ }
759
+ async function databasePayloadDryRunValidation() {
760
+ const tools = new Map();
761
+ const server = {
762
+ tool: (name, _description, _schema, handler) => {
763
+ tools.set(name, { handler });
764
+ },
765
+ };
766
+ const calls = [];
767
+ const queue = {
768
+ execute: async (commandType, payload = {}) => {
769
+ calls.push({ commandType, payload });
770
+ if (commandType === "list_datasets") {
771
+ return { commandId: "cmd-datasets", status: "completed", result: { ok: true, datasets: [{ id: "ds_live", name: "Live Dataset" }] } };
772
+ }
773
+ if (commandType === "get_dataset") {
774
+ return {
775
+ commandId: "cmd-dataset",
776
+ status: "completed",
777
+ result: { ok: true, dataset: { id: "ds_live", name: "Live Dataset", data: [{ label: "A" }] } },
778
+ };
779
+ }
780
+ if (commandType === "list_assets") {
781
+ return { commandId: "cmd-assets", status: "completed", result: { ok: true, assets: [{ id: "asset_live", name: "Live Asset" }] } };
782
+ }
783
+ if (commandType === "get_asset") {
784
+ return {
785
+ commandId: "cmd-asset",
786
+ status: "completed",
787
+ result: { ok: true, asset: { id: "asset_live", name: "Live Asset" }, content: "data:image/png;base64,AAA" },
788
+ };
789
+ }
790
+ if (commandType === "list_mappings") {
791
+ return { commandId: "cmd-mappings", status: "completed", result: { ok: true, mappings: [{ id: "map_live", name: "Live Mapping" }] } };
792
+ }
793
+ if (commandType === "get_mapping") {
794
+ return {
795
+ commandId: "cmd-mapping",
796
+ status: "completed",
797
+ result: { ok: true, mapping: { id: "map_live", name: "Live Mapping", fields: [] } },
798
+ };
799
+ }
800
+ return { commandId: `cmd-${commandType}`, status: "failed", error: `Unexpected command ${commandType}` };
801
+ },
802
+ };
803
+ registerDataTools(server, queue);
804
+ const exportTool = tools.get("export_database_payload");
805
+ const importTool = tools.get("import_database_payload");
806
+ if (!exportTool || !importTool) {
807
+ return { ok: false, details: { hasExportTool: Boolean(exportTool), hasImportTool: Boolean(importTool) } };
808
+ }
809
+ const exportBody = readLocalToolJson(await exportTool.handler({
810
+ includeAssetContent: true,
811
+ bridgeResponseMode: "full",
812
+ }));
813
+ const importBody = readLocalToolJson(await importTool.handler({
814
+ payload: exportBody,
815
+ bridgeResponseMode: "full",
816
+ }));
817
+ const writeCalls = calls.filter(({ commandType }) => /^save_|^delete_/.test(commandType));
818
+ return {
819
+ ok: exportBody["ok"] === true &&
820
+ exportBody["kind"] === "sxl-database-payload" &&
821
+ importBody["ok"] === true &&
822
+ importBody["dryRun"] === true &&
823
+ importBody["willCallPlugin"] === false &&
824
+ writeCalls.length === 0,
825
+ details: {
826
+ exportedKind: exportBody["kind"] ?? null,
827
+ exportCounts: isRecord(exportBody["counts"]) ? exportBody["counts"] : null,
828
+ importDryRun: importBody["dryRun"] === true,
829
+ importWillCallPlugin: importBody["willCallPlugin"] ?? null,
830
+ writeCalls: writeCalls.length,
831
+ },
832
+ };
833
+ }
834
+ export async function runBridgeLiveValidation(options = {}) {
835
+ const findings = [];
836
+ const steps = [];
837
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? `http://127.0.0.1:${process.env["BRIDGE_PORT"] || "37830"}`);
838
+ const suite = options.suite ?? "baseline";
839
+ const writeCanary = options.writeCanary === true;
840
+ const cleanup = options.cleanup === true;
841
+ if (cleanup && !writeCanary) {
842
+ addFinding(findings, {
843
+ severity: "error",
844
+ code: "LIVE_CLEANUP_REQUIRES_WRITE_CANARY",
845
+ message: "Live validation cleanup can only run with --write-canary because it deletes the frame created by the canary step.",
846
+ step: "writeCanary.cleanup",
847
+ });
848
+ const status = statusFromFindings(findings);
849
+ return {
850
+ ok: false,
851
+ status,
852
+ version: 1,
853
+ bridgeVersion: getBridgePackageVersion(),
854
+ generatedAt: new Date().toISOString(),
855
+ baseUrl,
856
+ suite,
857
+ writeCanary,
858
+ cleanup,
859
+ steps,
860
+ findings,
861
+ };
862
+ }
863
+ const shared = {
864
+ baseUrl,
865
+ authToken: options.authToken ?? process.env["BRIDGE_AUTH_TOKEN"],
866
+ timeoutMs: options.timeoutMs ?? 30_000,
867
+ fetchImpl: options.fetchImpl ?? fetch,
868
+ findings,
869
+ };
870
+ steps.push(await validateHttpEndpoint({
871
+ ...shared,
872
+ endpoint: "/api/status",
873
+ name: "runtime.status",
874
+ validate: (body) => {
875
+ const statusBody = isRecord(body) ? body : {};
876
+ const session = isRecord(statusBody["session"]) ? statusBody["session"] : {};
877
+ const ports = isRecord(statusBody["ports"]) ? statusBody["ports"] : {};
878
+ const connected = session["connected"] === true;
879
+ return {
880
+ ok: isRecord(body),
881
+ details: {
882
+ pluginConnected: connected,
883
+ authEnabled: statusBody["authEnabled"] === true,
884
+ bridgePort: typeof ports["http"] === "number" ? ports["http"] : null,
885
+ runtimeHealth: isRecord(statusBody["runtime"]) && typeof statusBody["runtime"]["health"] === "string"
886
+ ? statusBody["runtime"]["health"]
887
+ : null,
888
+ telemetryEvents: isRecord(statusBody["telemetry"]) && typeof statusBody["telemetry"]["eventsBuffered"] === "number"
889
+ ? statusBody["telemetry"]["eventsBuffered"]
890
+ : null,
891
+ },
892
+ warning: connected ? undefined : "Bridge is running, but no Remote Connect plugin session is connected.",
893
+ };
894
+ },
895
+ }));
896
+ steps.push(await validateMcpStreamableHttp(shared));
897
+ steps.push(await validateHttpEndpoint({
898
+ ...shared,
899
+ endpoint: "/api/ultimate-readiness",
900
+ name: "runtime.ultimateReadiness",
901
+ validate: statusEnvelopeValidation({
902
+ endpoint: "/api/ultimate-readiness",
903
+ requiredArray: "requirements",
904
+ summaryCountKey: "requirementCount",
905
+ }),
906
+ }));
907
+ steps.push(await validateHttpEndpoint({
908
+ ...shared,
909
+ endpoint: "/api/performance-profile",
910
+ name: "runtime.performanceProfile",
911
+ validate: statusEnvelopeValidation({
912
+ endpoint: "/api/performance-profile",
913
+ requiredArray: "tools",
914
+ summaryCountKey: "toolCount",
915
+ }),
916
+ }));
917
+ steps.push(await validateHttpEndpoint({
918
+ ...shared,
919
+ endpoint: "/api/operator-runbook",
920
+ name: "runtime.operatorRunbook",
921
+ validate: operatorRunbookValidation,
922
+ }));
923
+ steps.push(await validateHttpEndpoint({
924
+ ...shared,
925
+ endpoint: "/api/tools",
926
+ name: "runtime.toolsCatalogue",
927
+ validate: toolsCatalogueValidation([
928
+ "get_operator_runbook",
929
+ "get_context_for_code_connect",
930
+ "generate_code_connect_template",
931
+ "get_code_connect_suggestions",
932
+ "codeconnect_get_registry",
933
+ "codeconnect_get_global_settings",
934
+ "get_libraries",
935
+ "search_design_system",
936
+ "get_design_context",
937
+ "get_metadata",
938
+ "get_screenshot",
939
+ "get_variable_defs",
940
+ "upload_assets",
941
+ "import_variable_spec",
942
+ "import_style_spec",
943
+ "audit_variable_coverage",
944
+ "apply_coverage_suggestions",
945
+ "audit_style_coverage",
946
+ "apply_style_coverage_suggestions",
947
+ "list_compositions",
948
+ "bulk_generate_compositions",
949
+ "audit_composition_drift",
950
+ ]),
951
+ }));
952
+ const statusStep = steps.find((step) => step.name === "runtime.status");
953
+ if (!statusStep?.ok || statusStep.details?.["pluginConnected"] !== true) {
954
+ if (statusStep?.details?.["pluginConnected"] !== true) {
955
+ const authEnabled = statusStep?.details?.["authEnabled"] === true;
956
+ const bridgePort = typeof statusStep?.details?.["bridgePort"] === "number"
957
+ ? statusStep.details["bridgePort"]
958
+ : null;
959
+ const guidance = [
960
+ "Bridge is reachable, but no SXL Studio Remote Connect plugin session is connected.",
961
+ bridgePort !== null
962
+ ? `Set the plugin Remote Connect port to ${bridgePort} if Bridge is not using the default port.`
963
+ : null,
964
+ authEnabled
965
+ ? "Bridge auth is enabled; verify the plugin Remote Connect overlay token matches BRIDGE_AUTH_TOKEN."
966
+ : null,
967
+ ].filter(Boolean).join(" ");
968
+ addFinding(findings, {
969
+ severity: "error",
970
+ code: "LIVE_PLUGIN_NOT_CONNECTED",
971
+ message: guidance,
972
+ step: "runtime.status",
973
+ });
974
+ }
975
+ const status = statusFromFindings(findings);
976
+ return {
977
+ ok: false,
978
+ status,
979
+ version: 1,
980
+ bridgeVersion: getBridgePackageVersion(),
981
+ generatedAt: new Date().toISOString(),
982
+ baseUrl,
983
+ suite,
984
+ writeCanary,
985
+ cleanup,
986
+ steps,
987
+ findings,
988
+ };
989
+ }
990
+ const baselineCommands = [
991
+ { commandType: "get_plugin_status", validate: pluginStatusValidation },
992
+ { commandType: "get_selection_summary", validate: okObject },
993
+ { commandType: "list_token_files", validate: listValidation("files") },
994
+ { commandType: "get_tokens_config", validate: okObject },
995
+ { commandType: "get_variables", payload: { limit: 20 }, validate: okObject },
996
+ { commandType: "get_local_styles", validate: okObject },
997
+ { commandType: "find_components", payload: { includeProperties: true, includeRemoteInstances: false, limit: 5 }, validate: findComponentsValidation },
998
+ { commandType: "build_mockup", payload: smokeScreenSpec(), validate: buildDryRunValidation },
999
+ ];
1000
+ const allOnlyCommands = [
1001
+ { commandType: "codeconnect_get_registry", validate: okObject },
1002
+ { commandType: "codeconnect_get_global_settings", validate: okObject },
1003
+ { commandType: "codeconnect_get_selection_status", validate: okObject },
1004
+ { commandType: "get_selection_variable_defs", payload: { scope: "selection", maxNodes: 5, maxDefinitions: 20 }, validate: okObject },
1005
+ { commandType: "inspect_enabled_libraries", validate: okObject },
1006
+ { commandType: "search_enabled_library_assets", payload: { query: "button", includeVariables: false }, validate: okObject },
1007
+ { commandType: "list_datasets", validate: listValidation("datasets") },
1008
+ { commandType: "list_assets", validate: listValidation("assets") },
1009
+ { commandType: "list_mappings", validate: listValidation("mappings") },
1010
+ { commandType: "import_variable_spec", payload: { spec: smokeVariableSpec() }, validate: importVariableSpecDryRunValidation },
1011
+ { commandType: "dedupe_variables", payload: { strategy: "byName", apply: false }, validate: dedupeDryRunValidation },
1012
+ { commandType: "import_style_spec", payload: { spec: smokeStyleSpec() }, validate: importStyleSpecDryRunValidation },
1013
+ { commandType: "dedupe_styles", payload: { strategy: "byName", styleType: "ALL", apply: false }, validate: dedupeDryRunValidation },
1014
+ { commandType: "audit_variable_coverage", payload: { scope: "page", suggest: true }, validate: coverageSummaryValidation },
1015
+ { commandType: "find_variable_coverage_misses", payload: { scope: "page", suggest: true, limit: 5 }, validate: coveragePageValidation },
1016
+ { commandType: "apply_coverage_suggestions", payload: { suggestions: [smokeVariableCoverageSuggestion()], dryRun: true }, validate: dryRunApplyPreviewValidation },
1017
+ { commandType: "audit_style_coverage", payload: { scope: "page" }, validate: coverageSummaryValidation },
1018
+ { commandType: "find_style_coverage_misses", payload: { scope: "page", limit: 5 }, validate: coveragePageValidation },
1019
+ { commandType: "apply_style_coverage_suggestions", payload: { suggestions: [smokeStyleCoverageSuggestion()], dryRun: true }, validate: dryRunApplyPreviewValidation },
1020
+ { commandType: "find_unused_variables", validate: unusedVariablesValidation },
1021
+ { commandType: "find_unused_styles", validate: unusedStylesValidation },
1022
+ { commandType: "list_compositions", validate: listCompositionsValidation },
1023
+ { commandType: "bulk_generate_compositions", payload: { operation: "auto", dryRun: true }, validate: bulkCompositionDryRunValidation },
1024
+ { commandType: "audit_composition_drift", validate: compositionDriftValidation },
1025
+ { commandType: "analyze_component_usage", payload: { scope: "page" }, validate: auditSummaryValidation },
1026
+ { commandType: "find_component_usages", payload: { scope: "page", limit: 5 }, validate: componentUsagePageValidation },
1027
+ { commandType: "analyze_component_prop_usage", payload: { scope: "page" }, validate: componentPropSummaryValidation },
1028
+ { commandType: "find_component_prop_usages", payload: { scope: "page", limit: 5 }, validate: componentUsagePageValidation },
1029
+ {
1030
+ commandType: "build_doc_flow",
1031
+ payload: {
1032
+ title: "SXL Bridge Doc Builder Live Validation",
1033
+ dryRun: true,
1034
+ pages: [
1035
+ {
1036
+ title: "Overview",
1037
+ description: "Non-destructive Remote Connect Doc Builder dry-run.",
1038
+ },
1039
+ ],
1040
+ },
1041
+ validate: docFlowDryRunValidation,
1042
+ },
1043
+ ];
1044
+ let writesAllowed = null;
1045
+ for (const command of suite === "all" ? [...baselineCommands, ...allOnlyCommands] : baselineCommands) {
1046
+ const step = await runCommand({ ...shared, ...command });
1047
+ if (command.commandType === "get_plugin_status") {
1048
+ writesAllowed = readWritesAllowed(step.result);
1049
+ }
1050
+ steps.push(step.step);
1051
+ }
1052
+ if (suite === "all") {
1053
+ steps.push(await runLocalStep({
1054
+ name: "generate_code_connect_template:dryRun",
1055
+ findings,
1056
+ run: codeConnectTemplateDryRunValidation,
1057
+ }));
1058
+ steps.push(await runLocalStep({
1059
+ name: "database_payload:dryRun",
1060
+ findings,
1061
+ run: databasePayloadDryRunValidation,
1062
+ }));
1063
+ }
1064
+ if (writeCanary) {
1065
+ if (writesAllowed !== true) {
1066
+ addFinding(findings, {
1067
+ severity: "error",
1068
+ code: "LIVE_WRITE_CANARY_NOT_WRITABLE",
1069
+ message: "Live validation --write-canary requires writesAllowed=true. Run in Figma Design mode with edit access.",
1070
+ step: "get_plugin_status",
1071
+ });
1072
+ }
1073
+ else {
1074
+ const apply = await runCommand({
1075
+ ...shared,
1076
+ commandType: "build_mockup",
1077
+ payload: smokeScreenSpec(false),
1078
+ });
1079
+ let rootNodeId = null;
1080
+ if (!isRecord(apply.result) || typeof apply.result["rootNodeId"] !== "string") {
1081
+ addFinding(findings, {
1082
+ severity: "error",
1083
+ code: "LIVE_WRITE_CANARY_RESULT_INVALID",
1084
+ message: "build_mockup write canary did not return a rootNodeId.",
1085
+ step: "writeCanary.build_mockup",
1086
+ });
1087
+ apply.step.ok = false;
1088
+ apply.step.status = "fail";
1089
+ }
1090
+ else {
1091
+ rootNodeId = apply.result["rootNodeId"];
1092
+ apply.step.details = { rootNodeId };
1093
+ }
1094
+ steps.push({ ...apply.step, name: "writeCanary.build_mockup" });
1095
+ if (cleanup && rootNodeId) {
1096
+ const cleanupStep = await runCommand({
1097
+ ...shared,
1098
+ commandType: "delete_node",
1099
+ payload: {
1100
+ nodeId: rootNodeId,
1101
+ confirmDestructive: true,
1102
+ destructiveReason: "cleanup SXL Bridge live validation write canary",
1103
+ },
1104
+ });
1105
+ if (!isRecord(cleanupStep.result) || cleanupStep.result["ok"] !== true) {
1106
+ addFinding(findings, {
1107
+ severity: "error",
1108
+ code: "LIVE_WRITE_CANARY_CLEANUP_FAILED",
1109
+ message: "delete_node cleanup did not return { ok: true }.",
1110
+ step: "writeCanary.delete_node",
1111
+ });
1112
+ cleanupStep.step.ok = false;
1113
+ cleanupStep.step.status = "fail";
1114
+ }
1115
+ else {
1116
+ cleanupStep.step.details = {
1117
+ rootNodeId,
1118
+ deleted: cleanupStep.result["deleted"] === true,
1119
+ name: typeof cleanupStep.result["name"] === "string" ? cleanupStep.result["name"] : null,
1120
+ };
1121
+ }
1122
+ steps.push({ ...cleanupStep.step, name: "writeCanary.delete_node" });
1123
+ }
1124
+ }
1125
+ }
1126
+ const status = statusFromFindings(findings);
1127
+ return {
1128
+ ok: status !== "fail",
1129
+ status,
1130
+ version: 1,
1131
+ bridgeVersion: getBridgePackageVersion(),
1132
+ generatedAt: new Date().toISOString(),
1133
+ baseUrl,
1134
+ suite,
1135
+ writeCanary,
1136
+ cleanup,
1137
+ steps,
1138
+ findings,
1139
+ };
1140
+ }
1141
+ //# sourceMappingURL=bridge-live-validation.js.map