@fragments-sdk/mcp 0.6.2 → 0.7.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.
@@ -0,0 +1,4501 @@
1
+ import {
2
+ BRAND,
3
+ DEFAULTS
4
+ } from "./chunk-4SVS3AA3.js";
5
+ import {
6
+ componentNames,
7
+ findComponent,
8
+ findComponentByName,
9
+ getGuidanceWhen,
10
+ getGuidanceWhenNot,
11
+ listBlocks,
12
+ listComponents
13
+ } from "./chunk-YSRGQDEB.js";
14
+
15
+ // src/server.ts
16
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import {
19
+ CallToolRequestSchema,
20
+ ListToolsRequestSchema
21
+ } from "@modelcontextprotocol/sdk/types.js";
22
+ import { existsSync as existsSync9 } from "fs";
23
+ import { readFileSync as readFileSync6 } from "fs";
24
+ import { join as join8 } from "path";
25
+ import { fileURLToPath } from "url";
26
+
27
+ // src/config.ts
28
+ import { readFileSync, existsSync } from "fs";
29
+ import { join } from "path";
30
+ function loadConfigFile(projectRoot) {
31
+ const configPath = join(projectRoot, "ds-mcp.config.json");
32
+ if (existsSync(configPath)) {
33
+ try {
34
+ const content = readFileSync(configPath, "utf-8");
35
+ return JSON.parse(content);
36
+ } catch (e) {
37
+ throw new Error(`Failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
38
+ }
39
+ }
40
+ const pkgPath = join(projectRoot, "package.json");
41
+ if (existsSync(pkgPath)) {
42
+ try {
43
+ const content = readFileSync(pkgPath, "utf-8");
44
+ const pkg = JSON.parse(content);
45
+ if (pkg.dsMcp) return pkg.dsMcp;
46
+ } catch {
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ // src/server.ts
53
+ import { buildMcpTools, buildToolNames, MCP_TOOL_DEFINITIONS } from "@fragments-sdk/context/mcp-tools";
54
+
55
+ // src/orama-index.ts
56
+ import { create, insertMultiple, search } from "@orama/orama";
57
+ var SYNONYM_MAP = {
58
+ "form": ["input", "field", "submit", "validation"],
59
+ "input": ["form", "field", "text", "entry"],
60
+ "button": ["action", "click", "submit", "trigger"],
61
+ "action": ["button", "click", "trigger"],
62
+ "submit": ["button", "form", "action", "send"],
63
+ "alert": ["notification", "message", "warning", "error", "feedback"],
64
+ "notification": ["alert", "message", "toast"],
65
+ "feedback": ["form", "comment", "review", "rating"],
66
+ "card": ["container", "panel", "box", "content"],
67
+ "toggle": ["switch", "checkbox", "boolean", "on/off"],
68
+ "switch": ["toggle", "checkbox", "boolean"],
69
+ "badge": ["tag", "label", "status", "indicator"],
70
+ "status": ["badge", "indicator", "state"],
71
+ "login": ["auth", "signin", "authentication", "form"],
72
+ "auth": ["login", "signin", "authentication"],
73
+ "chat": ["message", "conversation", "ai"],
74
+ "table": ["data", "grid", "list", "rows"],
75
+ "textarea": ["text", "input", "multiline", "area", "comment"],
76
+ "area": ["textarea", "multiline", "text"],
77
+ "landing": ["page", "hero", "marketing", "section", "layout"],
78
+ "hero": ["landing", "marketing", "banner", "headline", "section"],
79
+ "marketing": ["landing", "hero", "pricing", "testimonial", "cta"],
80
+ "cta": ["marketing", "banner", "action", "button"],
81
+ "testimonial": ["marketing", "review", "quote", "feedback"],
82
+ "layout": ["stack", "grid", "box", "container", "page"],
83
+ "page": ["layout", "landing", "section", "container"],
84
+ "section": ["hero", "feature", "testimonial", "cta", "faq"],
85
+ "pricing": ["card", "plan", "tier", "marketing"],
86
+ "plan": ["pricing", "card", "tier", "subscription"],
87
+ "dashboard": ["metrics", "stats", "chart", "card", "grid"],
88
+ "metrics": ["dashboard", "stats", "progress", "number"],
89
+ "stats": ["metrics", "dashboard", "progress", "badge"],
90
+ "chart": ["dashboard", "metrics", "data", "graph"]
91
+ };
92
+ function expandQuery(query) {
93
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
94
+ const expanded = new Set(terms);
95
+ for (const term of terms) {
96
+ const synonyms = SYNONYM_MAP[term];
97
+ if (synonyms) {
98
+ for (const syn of synonyms) expanded.add(syn);
99
+ }
100
+ }
101
+ return Array.from(expanded).join(" ");
102
+ }
103
+ function twoPassSearch(config) {
104
+ const { index, query, properties, boost, limit, kind } = config;
105
+ const baseConfig = {
106
+ mode: "fulltext",
107
+ properties,
108
+ boost,
109
+ limit
110
+ };
111
+ const originalTermsQuery = query.toLowerCase().split(/\s+/).filter(Boolean).join(" ");
112
+ const expandedQuery = expandQuery(query);
113
+ const originalResults = search(index, { term: originalTermsQuery, ...baseConfig, threshold: 0.8 });
114
+ const expandedResults = search(index, { term: expandedQuery, ...baseConfig, threshold: 1 });
115
+ const origHits = originalResults.hits;
116
+ const expHits = expandedResults.hits;
117
+ const scoreMap = /* @__PURE__ */ new Map();
118
+ for (const hit of origHits) {
119
+ scoreMap.set(hit.document.name, (hit.score || 0) * 2);
120
+ }
121
+ for (const hit of expHits) {
122
+ const name = hit.document.name;
123
+ const existing = scoreMap.get(name) ?? 0;
124
+ scoreMap.set(name, existing + (hit.score || 0));
125
+ }
126
+ const scored = [];
127
+ for (const [name, score] of scoreMap) {
128
+ if (score > 0) {
129
+ scored.push({ name, kind, rank: scored.length, score });
130
+ }
131
+ }
132
+ scored.sort((a, b) => b.score - a.score);
133
+ scored.forEach((s, i) => {
134
+ s.rank = i;
135
+ });
136
+ return scored;
137
+ }
138
+ var componentSchema = {
139
+ name: "string",
140
+ description: "string",
141
+ category: "string",
142
+ tags: "string",
143
+ whenUsed: "string",
144
+ patterns: "string",
145
+ variants: "string",
146
+ status: "string"
147
+ };
148
+ function isCompiledFragment(value) {
149
+ return "meta" in value;
150
+ }
151
+ function normalizeComponent(value) {
152
+ if (!isCompiledFragment(value)) return value;
153
+ return {
154
+ id: value.filePath ?? value.meta.name,
155
+ name: value.meta.name,
156
+ description: value.meta.description ?? "",
157
+ category: value.meta.category ?? "uncategorized",
158
+ status: value.meta.status ?? "stable",
159
+ tags: value.meta.tags ?? [],
160
+ props: {},
161
+ propsSummary: value.propsSummary ?? value.contract?.propsSummary ?? Object.entries(value.props ?? {}).map(
162
+ ([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type}`
163
+ ),
164
+ examples: (value.variants ?? []).map((variant) => ({
165
+ name: variant.name,
166
+ description: variant.description,
167
+ code: variant.code
168
+ })),
169
+ relations: (value.relations ?? []).map((relation) => ({
170
+ componentName: relation.component,
171
+ relationship: relation.relationship,
172
+ note: relation.note
173
+ })),
174
+ compoundChildren: [],
175
+ guidance: {
176
+ when: value.usage?.when ?? [],
177
+ whenNot: value.usage?.whenNot ?? [],
178
+ guidelines: value.usage?.guidelines ?? [],
179
+ accessibility: value.usage?.accessibility ?? [],
180
+ dos: value.usage?.when ?? [],
181
+ donts: value.usage?.whenNot ?? [],
182
+ patterns: []
183
+ },
184
+ sourceType: "fragments-json",
185
+ sourcePath: value.sourcePath ?? value.filePath,
186
+ metadata: {
187
+ a11yRules: value.contract?.a11yRules ?? [],
188
+ scenarioTags: value.contract?.scenarioTags ?? []
189
+ }
190
+ };
191
+ }
192
+ function buildComponentIndex(fragments) {
193
+ const db = create({ schema: componentSchema, language: "english" });
194
+ const normalized = fragments.map(normalizeComponent);
195
+ const docs = normalized.map((f) => ({
196
+ name: f.name,
197
+ description: f.description ?? "",
198
+ category: f.category ?? "",
199
+ tags: (f.tags ?? []).join(" "),
200
+ whenUsed: (f.guidance.when ?? []).join(" "),
201
+ patterns: (f.guidance.patterns ?? []).map(
202
+ (pattern) => `${pattern.name} ${pattern.description || ""}`
203
+ ).join(" "),
204
+ variants: f.examples.map(
205
+ (example) => `${example.name} ${example.description || ""}`
206
+ ).join(" "),
207
+ status: f.status ?? "stable"
208
+ }));
209
+ insertMultiple(db, docs);
210
+ return db;
211
+ }
212
+ function searchComponents(query, index, fragments, limit = 50) {
213
+ const normalized = fragments.map(normalizeComponent);
214
+ const boostConfig = {
215
+ mode: "fulltext",
216
+ properties: ["name", "whenUsed", "description", "patterns", "category", "tags", "variants"],
217
+ boost: {
218
+ name: 3,
219
+ whenUsed: 2.5,
220
+ description: 2,
221
+ patterns: 1.5,
222
+ category: 1.5,
223
+ tags: 1.5,
224
+ variants: 1
225
+ },
226
+ limit
227
+ };
228
+ const originalTermsList = query.toLowerCase().split(/\s+/).filter(Boolean);
229
+ const originalTermsQuery = originalTermsList.join(" ");
230
+ const expandedQuery = expandQuery(query);
231
+ const originalResults = search(index, { term: originalTermsQuery, ...boostConfig, threshold: 0.8 });
232
+ const expandedResults = search(index, { term: expandedQuery, ...boostConfig, threshold: 1 });
233
+ const origHits = originalResults.hits;
234
+ const expHits = expandedResults.hits;
235
+ const scoreMap = /* @__PURE__ */ new Map();
236
+ for (const hit of origHits) {
237
+ scoreMap.set(hit.document.name, (hit.score || 0) * 2);
238
+ }
239
+ for (const hit of expHits) {
240
+ const name = hit.document.name;
241
+ const existing = scoreMap.get(name) ?? 0;
242
+ scoreMap.set(name, existing + (hit.score || 0));
243
+ }
244
+ const fragmentMap = /* @__PURE__ */ new Map();
245
+ for (const f of normalized) {
246
+ fragmentMap.set(f.name.toLowerCase(), f);
247
+ }
248
+ const originalTermsSet = new Set(originalTermsList);
249
+ const scored = [];
250
+ for (const [name, rawScore] of scoreMap) {
251
+ let score = rawScore;
252
+ const nameLower = name.toLowerCase();
253
+ const fragment = fragmentMap.get(nameLower);
254
+ if (originalTermsSet.has(nameLower)) {
255
+ score += 25;
256
+ }
257
+ if (fragment) {
258
+ if (fragment.status === "stable") score += 5;
259
+ else if (fragment.status === "beta") score += 2;
260
+ if (fragment.status === "deprecated") score -= 25;
261
+ }
262
+ if (score > 0) {
263
+ scored.push({ name, kind: "component", rank: scored.length, score });
264
+ }
265
+ }
266
+ scored.sort((a, b) => b.score - a.score);
267
+ scored.forEach((s, i) => {
268
+ s.rank = i;
269
+ });
270
+ return scored;
271
+ }
272
+ var blockSchema = {
273
+ name: "string",
274
+ description: "string",
275
+ category: "string",
276
+ tags: "string",
277
+ components: "string"
278
+ };
279
+ function normalizeBlock(value) {
280
+ if ("id" in value) return value;
281
+ return {
282
+ id: value.filePath ?? value.name,
283
+ name: value.name,
284
+ description: value.description ?? "",
285
+ category: value.category ?? "uncategorized",
286
+ components: value.components ?? [],
287
+ tags: value.tags ?? [],
288
+ code: value.code ?? ""
289
+ };
290
+ }
291
+ function buildBlockIndex(blocks) {
292
+ const db = create({ schema: blockSchema, language: "english" });
293
+ const normalized = blocks.map(normalizeBlock);
294
+ const docs = normalized.map((b) => ({
295
+ name: b.name,
296
+ description: b.description ?? "",
297
+ category: b.category ?? "",
298
+ tags: (b.tags ?? []).join(" "),
299
+ components: b.components.join(" ")
300
+ }));
301
+ insertMultiple(db, docs);
302
+ return db;
303
+ }
304
+ function searchBlocks(query, index, limit = 50) {
305
+ return twoPassSearch({
306
+ index,
307
+ query,
308
+ properties: ["name", "description", "components", "tags", "category"],
309
+ boost: {
310
+ name: 3,
311
+ description: 2,
312
+ components: 1.5,
313
+ tags: 1.5,
314
+ category: 1.5
315
+ },
316
+ limit,
317
+ kind: "block"
318
+ });
319
+ }
320
+ var tokenSchema = {
321
+ name: "string",
322
+ category: "string",
323
+ description: "string"
324
+ };
325
+ function normalizeTokenData(tokenData) {
326
+ if ("flat" in tokenData) return tokenData;
327
+ const categories = Object.fromEntries(
328
+ Object.entries(tokenData.categories).map(([category, entries]) => [
329
+ category,
330
+ entries.map((entry) => ({
331
+ name: entry.name,
332
+ category,
333
+ value: typeof entry.value === "string" ? entry.value : void 0,
334
+ description: entry.description
335
+ }))
336
+ ])
337
+ );
338
+ return {
339
+ prefix: tokenData.prefix,
340
+ total: tokenData.total,
341
+ categories,
342
+ flat: Object.values(categories).flat()
343
+ };
344
+ }
345
+ function buildTokenIndex(tokenData) {
346
+ const db = create({ schema: tokenSchema, language: "english" });
347
+ const normalizedData = normalizeTokenData(tokenData);
348
+ const docs = [];
349
+ for (const [cat, tokens] of Object.entries(normalizedData.categories)) {
350
+ for (const token of tokens) {
351
+ docs.push({
352
+ name: token.name,
353
+ category: cat,
354
+ description: token.description ?? ""
355
+ });
356
+ }
357
+ }
358
+ insertMultiple(db, docs);
359
+ return db;
360
+ }
361
+ function searchTokens(query, index, limit = 50) {
362
+ return twoPassSearch({
363
+ index,
364
+ query,
365
+ properties: ["name", "category", "description"],
366
+ boost: {
367
+ name: 2.5,
368
+ category: 2,
369
+ description: 1.5
370
+ },
371
+ limit,
372
+ kind: "token"
373
+ });
374
+ }
375
+ var USE_CASE_TOKEN_CATEGORIES = {
376
+ "table": ["spacing", "borders", "surfaces", "text"],
377
+ "data": ["spacing", "borders", "surfaces"],
378
+ "grid": ["spacing", "layout"],
379
+ "form": ["spacing", "borders", "radius", "focus"],
380
+ "input": ["spacing", "borders", "radius", "focus"],
381
+ "card": ["surfaces", "shadows", "radius", "borders", "spacing"],
382
+ "button": ["colors", "radius", "spacing", "focus"],
383
+ "layout": ["spacing", "layout", "surfaces"],
384
+ "dashboard": ["spacing", "surfaces", "borders", "shadows"],
385
+ "chat": ["spacing", "surfaces", "radius", "shadows"],
386
+ "modal": ["shadows", "surfaces", "radius", "spacing"],
387
+ "dialog": ["shadows", "surfaces", "radius", "spacing"],
388
+ "navigation": ["spacing", "surfaces", "borders"],
389
+ "sidebar": ["spacing", "surfaces", "borders"],
390
+ "hero": ["spacing", "typography", "colors"],
391
+ "landing": ["spacing", "typography", "colors"],
392
+ "pricing": ["spacing", "surfaces", "borders", "radius"],
393
+ "auth": ["spacing", "borders", "radius", "focus"],
394
+ "login": ["spacing", "borders", "radius", "focus"],
395
+ "dark": ["colors", "surfaces"],
396
+ "theme": ["colors", "surfaces", "text"]
397
+ };
398
+ function extractTokenCategories(query) {
399
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
400
+ const categories = /* @__PURE__ */ new Set();
401
+ for (const term of terms) {
402
+ const cats = USE_CASE_TOKEN_CATEGORIES[term];
403
+ if (cats) {
404
+ for (const cat of cats) categories.add(cat);
405
+ }
406
+ }
407
+ if (categories.size === 0) {
408
+ return ["spacing", "colors", "surfaces"];
409
+ }
410
+ return Array.from(categories);
411
+ }
412
+
413
+ // src/version.ts
414
+ import { readFileSync as readFileSync2 } from "fs";
415
+ function readPackageVersion() {
416
+ try {
417
+ const raw = readFileSync2(new URL("../package.json", import.meta.url), "utf-8");
418
+ const pkg = JSON.parse(raw);
419
+ return pkg.version ?? "0.0.0";
420
+ } catch {
421
+ return "0.0.0";
422
+ }
423
+ }
424
+ var MCP_SERVER_VERSION = readPackageVersion();
425
+
426
+ // src/search.ts
427
+ var CONVEX_SEARCH_URL = "https://combative-jay-834.convex.site/search";
428
+ var CONVEX_TIMEOUT_MS = 3e3;
429
+ async function searchConvex(query, apiKey, limit = 10, kind) {
430
+ try {
431
+ const controller = new AbortController();
432
+ const timeout = setTimeout(() => controller.abort(), CONVEX_TIMEOUT_MS);
433
+ const response = await fetch(CONVEX_SEARCH_URL, {
434
+ method: "POST",
435
+ headers: {
436
+ "Content-Type": "application/json",
437
+ "Authorization": `Bearer ${apiKey}`
438
+ },
439
+ body: JSON.stringify({ query, limit, ...kind && { kind } }),
440
+ signal: controller.signal
441
+ });
442
+ clearTimeout(timeout);
443
+ if (!response.ok) {
444
+ return [];
445
+ }
446
+ const data = await response.json();
447
+ return data.results.map((r, i) => ({
448
+ name: r.name,
449
+ kind: r.kind ?? "component",
450
+ rank: i,
451
+ score: r.score
452
+ }));
453
+ } catch {
454
+ return [];
455
+ }
456
+ }
457
+ function keywordScoreComponents(query, fragments, componentIndex) {
458
+ const index = componentIndex ?? buildComponentIndex(fragments);
459
+ return searchComponents(query, index, fragments);
460
+ }
461
+ function keywordScoreBlocks(query, blocks, blockIndex) {
462
+ const index = blockIndex ?? buildBlockIndex(blocks);
463
+ return searchBlocks(query, index);
464
+ }
465
+ function keywordScoreTokens(query, tokenData, tokenIndex) {
466
+ const index = tokenIndex ?? buildTokenIndex(tokenData);
467
+ return searchTokens(query, index);
468
+ }
469
+ function reciprocalRankFusion(resultSets, k = 60) {
470
+ const scoreMap = /* @__PURE__ */ new Map();
471
+ for (const { results } of resultSets) {
472
+ for (let rank = 0; rank < results.length; rank++) {
473
+ const result = results[rank];
474
+ const key = `${result.kind}:${result.name}`;
475
+ const rrfScore = 1 / (k + rank + 1);
476
+ const existing = scoreMap.get(key);
477
+ if (existing) {
478
+ existing.score += rrfScore;
479
+ } else {
480
+ scoreMap.set(key, { score: rrfScore, kind: result.kind, name: result.name });
481
+ }
482
+ }
483
+ }
484
+ const fused = [];
485
+ for (const [, { score, kind, name }] of scoreMap) {
486
+ fused.push({ name, kind, rank: 0, score });
487
+ }
488
+ fused.sort((a, b) => b.score - a.score);
489
+ fused.forEach((r, i) => {
490
+ r.rank = i;
491
+ });
492
+ return fused;
493
+ }
494
+ async function hybridSearch(query, data, limit = 10, kind, apiKey) {
495
+ const keywordResults = [];
496
+ if (!kind || kind === "component") {
497
+ keywordResults.push(...keywordScoreComponents(query, data.fragments, data.componentIndex));
498
+ }
499
+ if ((!kind || kind === "block") && data.blocks) {
500
+ keywordResults.push(...keywordScoreBlocks(query, data.blocks, data.blockIndex));
501
+ }
502
+ if ((!kind || kind === "token") && data.tokenData) {
503
+ keywordResults.push(...keywordScoreTokens(query, data.tokenData, data.tokenIndex));
504
+ }
505
+ keywordResults.sort((a, b) => b.score - a.score);
506
+ keywordResults.forEach((r, i) => {
507
+ r.rank = i;
508
+ });
509
+ if (!apiKey) {
510
+ return keywordResults.slice(0, limit);
511
+ }
512
+ const vectorResults = await searchConvex(query, apiKey, limit, kind);
513
+ if (vectorResults.length === 0) {
514
+ return keywordResults.slice(0, limit);
515
+ }
516
+ const graphBoostResults = [];
517
+ if (data.graph) {
518
+ try {
519
+ const { ComponentGraphEngine: ComponentGraphEngine2, deserializeGraph: deserializeGraph2 } = await import("@fragments-sdk/context/graph");
520
+ const graph = deserializeGraph2(data.graph);
521
+ const engine = new ComponentGraphEngine2(graph);
522
+ const topComponents = [...keywordResults, ...vectorResults].filter((r) => r.kind === "component").slice(0, 5);
523
+ const neighborSet = /* @__PURE__ */ new Set();
524
+ for (const result of topComponents) {
525
+ const neighbors = engine.neighbors(result.name, 1);
526
+ for (const n of neighbors.neighbors) {
527
+ if (!neighborSet.has(n.component)) {
528
+ neighborSet.add(n.component);
529
+ graphBoostResults.push({
530
+ name: n.component,
531
+ kind: "component",
532
+ rank: graphBoostResults.length,
533
+ score: 1
534
+ // Will be normalized through RRF
535
+ });
536
+ }
537
+ }
538
+ }
539
+ } catch {
540
+ }
541
+ }
542
+ const resultSets = [
543
+ { label: "vector", results: vectorResults },
544
+ { label: "keyword", results: keywordResults }
545
+ ];
546
+ if (graphBoostResults.length > 0) {
547
+ resultSets.push({ label: "graph", results: graphBoostResults });
548
+ }
549
+ const fused = reciprocalRankFusion(resultSets);
550
+ return fused.slice(0, limit);
551
+ }
552
+
553
+ // src/scoring.ts
554
+ var MINIMUM_SCORE_THRESHOLD = 5;
555
+ function assignConfidence(score, maxScore) {
556
+ if (maxScore <= 0) return "low";
557
+ const ratio = score / maxScore;
558
+ if (ratio >= 0.7) return "high";
559
+ if (ratio >= 0.4) return "medium";
560
+ return "low";
561
+ }
562
+ function meetsMinimumThreshold(maxScore) {
563
+ return maxScore >= MINIMUM_SCORE_THRESHOLD;
564
+ }
565
+ function levenshtein(a, b) {
566
+ const la = a.length;
567
+ const lb = b.length;
568
+ const dp = Array.from({ length: lb + 1 }, (_, i) => i);
569
+ for (let i = 1; i <= la; i++) {
570
+ let prev = i - 1;
571
+ dp[0] = i;
572
+ for (let j = 1; j <= lb; j++) {
573
+ const temp = dp[j];
574
+ dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
575
+ prev = temp;
576
+ }
577
+ }
578
+ return dp[lb];
579
+ }
580
+ function findClosestMatch(input, candidates, maxDistance = 3) {
581
+ const inputLower = input.toLowerCase();
582
+ let bestMatch = null;
583
+ let bestDist = maxDistance + 1;
584
+ for (const candidate of candidates) {
585
+ const candidateLower = candidate.toLowerCase();
586
+ const dist = levenshtein(inputLower, candidateLower);
587
+ if (dist < bestDist) {
588
+ bestDist = dist;
589
+ bestMatch = candidate;
590
+ } else if (dist === bestDist && bestMatch) {
591
+ const currentLenDiff = Math.abs(bestMatch.length - input.length);
592
+ const newLenDiff = Math.abs(candidate.length - input.length);
593
+ if (newLenDiff < currentLenDiff) {
594
+ bestMatch = candidate;
595
+ }
596
+ }
597
+ }
598
+ return bestDist <= maxDistance ? bestMatch : null;
599
+ }
600
+ var BLOCK_BOOST_PER_OCCURRENCE = 5;
601
+ function buildBlockComponentFrequency(blocks) {
602
+ const freq = /* @__PURE__ */ new Map();
603
+ for (const block of blocks) {
604
+ for (const comp of block.components) {
605
+ const key = comp.toLowerCase();
606
+ freq.set(key, (freq.get(key) ?? 0) + 1);
607
+ }
608
+ }
609
+ return freq;
610
+ }
611
+ function boostByBlockFrequency(results, freq) {
612
+ for (const result of results) {
613
+ const count = freq.get(result.name.toLowerCase()) ?? 0;
614
+ if (count > 0) {
615
+ result.score += count * BLOCK_BOOST_PER_OCCURRENCE;
616
+ }
617
+ }
618
+ results.sort((a, b) => b.score - a.score);
619
+ results.forEach((r, i) => {
620
+ r.rank = i;
621
+ });
622
+ return results;
623
+ }
624
+
625
+ // src/server-helpers.ts
626
+ function normalizeFilter(value) {
627
+ const normalized = value?.trim().toLowerCase();
628
+ return normalized && normalized.length > 0 ? normalized : void 0;
629
+ }
630
+ function categoryMatches(category, categoryFilter) {
631
+ if (!categoryFilter) return true;
632
+ return normalizeFilter(category) === categoryFilter;
633
+ }
634
+ function buildLocalSearchData(data, indexes) {
635
+ const isLegacy = "fragments" in data;
636
+ const allFragments = Object.values(
637
+ isLegacy ? data.fragments : data.components
638
+ );
639
+ const allBlocks = Object.values(
640
+ isLegacy ? data.blocks ?? data.recipes ?? {} : data.blocks ?? {}
641
+ );
642
+ const tokens = isLegacy ? data.tokens : data.tokens;
643
+ const graph = isLegacy ? data.graph : data.graph;
644
+ const localData = {
645
+ fragments: allFragments,
646
+ blocks: allBlocks,
647
+ tokenData: tokens,
648
+ graph,
649
+ componentIndex: indexes.componentIndex ?? void 0,
650
+ blockIndex: indexes.blockIndex ?? void 0,
651
+ tokenIndex: indexes.tokenIndex ?? void 0
652
+ };
653
+ return { allFragments, allBlocks, localData };
654
+ }
655
+ async function buildImportStatements(components, resolvePackageName) {
656
+ const grouped = /* @__PURE__ */ new Map();
657
+ const uniqueComponents = [...new Set(components.filter(Boolean))];
658
+ const resolvedPackages = await Promise.all(
659
+ uniqueComponents.map(async (component) => ({
660
+ component,
661
+ packageName: await resolvePackageName(component)
662
+ }))
663
+ );
664
+ for (const { component, packageName } of resolvedPackages) {
665
+ const existing = grouped.get(packageName);
666
+ if (!existing) {
667
+ grouped.set(packageName, [component]);
668
+ continue;
669
+ }
670
+ existing.push(component);
671
+ }
672
+ return Array.from(grouped.entries()).map(
673
+ ([packageName, componentNames2]) => `import { ${componentNames2.join(", ")} } from '${packageName}';`
674
+ );
675
+ }
676
+ function limitTokensPerCategory(categories, limit) {
677
+ if (limit === void 0) {
678
+ return {
679
+ categories,
680
+ total: Object.values(categories).reduce((sum, tokens) => sum + tokens.length, 0)
681
+ };
682
+ }
683
+ const limited = {};
684
+ let total = 0;
685
+ for (const [category, tokens] of Object.entries(categories)) {
686
+ const sliced = tokens.slice(0, limit);
687
+ if (sliced.length === 0) continue;
688
+ limited[category] = sliced;
689
+ total += sliced.length;
690
+ }
691
+ return { categories: limited, total };
692
+ }
693
+
694
+ // src/search-helpers.ts
695
+ var STOP_WORDS = /* @__PURE__ */ new Set([
696
+ "a",
697
+ "an",
698
+ "and",
699
+ "bar",
700
+ "build",
701
+ "button",
702
+ "for",
703
+ "form",
704
+ "i",
705
+ "login",
706
+ "me",
707
+ "need",
708
+ "of",
709
+ "or",
710
+ "the",
711
+ "to",
712
+ "use",
713
+ "with"
714
+ ]);
715
+ function normalizeTerms(value) {
716
+ return value.toLowerCase().split(/[^a-z0-9]+/g).filter((term) => term.length > 1 && !STOP_WORDS.has(term));
717
+ }
718
+ function hasDirectQueryOverlap(query, component) {
719
+ const queryTerms = normalizeTerms(query);
720
+ if (queryTerms.length === 0) return true;
721
+ const haystack = new Set(
722
+ normalizeTerms(
723
+ [
724
+ component.name,
725
+ component.description,
726
+ component.category,
727
+ component.tags.join(" "),
728
+ getGuidanceWhen(component).join(" "),
729
+ getGuidanceWhenNot(component).join(" "),
730
+ component.propsSummary.join(" ")
731
+ ].join(" ")
732
+ )
733
+ );
734
+ return queryTerms.some((term) => haystack.has(term));
735
+ }
736
+ function getRankingBonus(component) {
737
+ let bonus = 0;
738
+ if (component.isCanonical) bonus += 30;
739
+ if (component.tier === "core") bonus += 20;
740
+ if (component.status === "stable") bonus += 5;
741
+ return bonus;
742
+ }
743
+
744
+ // src/tools/discover.ts
745
+ function renderContextMarkdown(args) {
746
+ const lines = ["# Design System Context", ""];
747
+ for (const component of args.components) {
748
+ lines.push(`## ${component.name}`);
749
+ if (component.description) {
750
+ lines.push(component.description);
751
+ }
752
+ lines.push(
753
+ `- Category: ${component.category}`,
754
+ `- Status: ${component.status}`
755
+ );
756
+ if (component.propsSummary.length > 0) {
757
+ lines.push(`- Props: ${component.propsSummary.join(", ")}`);
758
+ }
759
+ const when = getGuidanceWhen(component);
760
+ if (when.length > 0) {
761
+ lines.push(`- Use when: ${when.slice(0, args.compact ? 1 : 3).join("; ")}`);
762
+ }
763
+ const whenNot = getGuidanceWhenNot(component);
764
+ if (whenNot.length > 0) {
765
+ lines.push(
766
+ `- Avoid when: ${whenNot.slice(0, args.compact ? 1 : 2).join("; ")}`
767
+ );
768
+ }
769
+ if (args.includeRelations && component.relations.length > 0) {
770
+ lines.push(
771
+ `- Related: ${component.relations.slice(0, 5).map(
772
+ (relation) => `${relation.componentName} (${relation.relationship})`
773
+ ).join(", ")}`
774
+ );
775
+ }
776
+ if (args.includeCode && component.examples[0]?.code) {
777
+ lines.push("", "```tsx", component.examples[0].code, "```");
778
+ }
779
+ lines.push("");
780
+ }
781
+ if (args.blocks.length > 0) {
782
+ lines.push("## Blocks", "");
783
+ for (const block of args.blocks) {
784
+ lines.push(`- ${block.name}: ${block.description}`);
785
+ }
786
+ lines.push("");
787
+ }
788
+ return lines.join("\n").trim();
789
+ }
790
+ function renderContextJson(args) {
791
+ return JSON.stringify({
792
+ components: args.components.map((component) => ({
793
+ name: component.name,
794
+ description: component.description,
795
+ category: component.category,
796
+ status: component.status,
797
+ propsSummary: component.propsSummary,
798
+ when: getGuidanceWhen(component),
799
+ whenNot: getGuidanceWhenNot(component),
800
+ ...args.includeRelations && { relations: component.relations },
801
+ ...args.includeCode && {
802
+ examples: component.examples.filter((example) => Boolean(example.code)).slice(0, 2)
803
+ }
804
+ })),
805
+ blocks: args.blocks
806
+ });
807
+ }
808
+ var discoverHandler = async (args, ctx) => {
809
+ const data = ctx.data;
810
+ const snapshotComponents = listComponents(data.snapshot);
811
+ const componentsByName = new Map(
812
+ snapshotComponents.map((component) => [
813
+ component.name.toLowerCase(),
814
+ component
815
+ ])
816
+ );
817
+ const useCase = args?.useCase ?? void 0;
818
+ const componentForAlts = args?.component ?? void 0;
819
+ const category = normalizeFilter(args?.category);
820
+ const search2 = args?.search?.toLowerCase() ?? void 0;
821
+ const status = args?.status ?? void 0;
822
+ const format = args?.format ?? "markdown";
823
+ const compact = args?.compact ?? false;
824
+ const includeCode = args?.includeCode ?? false;
825
+ const includeRelations = args?.includeRelations ?? false;
826
+ const depth = args?.depth ?? "quick";
827
+ const suggestLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : 10;
828
+ const listLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : void 0;
829
+ const verbosity = args?.verbosity ?? (compact ? "compact" : "standard");
830
+ const allSnapshotComponents = listComponents(data.snapshot);
831
+ if (!useCase && !componentForAlts && (args?.format || includeCode || includeRelations)) {
832
+ let components2 = allSnapshotComponents;
833
+ const allBlocks = listBlocks(data.snapshot);
834
+ if (category) {
835
+ components2 = components2.filter(
836
+ (component) => categoryMatches(component.category, category)
837
+ );
838
+ }
839
+ if (search2) {
840
+ const scored = keywordScoreComponents(
841
+ search2,
842
+ components2,
843
+ ctx.indexes.componentIndex ?? void 0
844
+ );
845
+ const allowedNames = new Set(components2.map((component) => component.name));
846
+ const sortedNames = scored.filter((result) => allowedNames.has(result.name)).map((result) => result.name.toLowerCase());
847
+ components2 = components2.filter((component) => sortedNames.includes(component.name.toLowerCase())).sort(
848
+ (a, b) => sortedNames.indexOf(a.name.toLowerCase()) - sortedNames.indexOf(b.name.toLowerCase())
849
+ );
850
+ }
851
+ if (status) {
852
+ components2 = components2.filter((component) => component.status === status);
853
+ }
854
+ const blocks = allBlocks.map((block) => ({
855
+ name: block.name,
856
+ description: block.description
857
+ }));
858
+ const ctxContent = format === "json" ? renderContextJson({
859
+ components: components2,
860
+ blocks,
861
+ includeCode: includeCode || verbosity === "full",
862
+ includeRelations
863
+ }) : renderContextMarkdown({
864
+ components: components2,
865
+ blocks,
866
+ includeCode: includeCode || verbosity === "full",
867
+ includeRelations,
868
+ compact: verbosity === "compact"
869
+ });
870
+ return {
871
+ content: [{ type: "text", text: ctxContent }],
872
+ _meta: {
873
+ componentCount: components2.length,
874
+ blockCount: blocks.length
875
+ }
876
+ };
877
+ }
878
+ if (useCase) {
879
+ const { allFragments, allBlocks, localData } = buildLocalSearchData(
880
+ {
881
+ components: data.components,
882
+ blocks: data.blocks,
883
+ tokens: data.tokens,
884
+ graph: data.graph
885
+ },
886
+ {
887
+ componentIndex: ctx.indexes.componentIndex,
888
+ blockIndex: ctx.indexes.blockIndex,
889
+ tokenIndex: ctx.indexes.tokenIndex
890
+ }
891
+ );
892
+ const context = args?.context?.toLowerCase() ?? "";
893
+ const fullQuery = context ? `${useCase} ${context}` : useCase;
894
+ const searchResults = await hybridSearch(
895
+ fullQuery,
896
+ localData,
897
+ suggestLimit,
898
+ "component",
899
+ ctx.config.searchApiKey
900
+ );
901
+ const filteredSearchResults = searchResults.filter((result) => {
902
+ const component = componentsByName.get(result.name.toLowerCase());
903
+ if (!component) return false;
904
+ if (category && !categoryMatches(component.category, category)) return false;
905
+ if (status && component.status !== status) return false;
906
+ result.score += getRankingBonus(component);
907
+ return true;
908
+ });
909
+ const blockMatches = keywordScoreBlocks(
910
+ fullQuery,
911
+ allBlocks,
912
+ ctx.indexes.blockIndex ?? void 0
913
+ ).slice(0, 5);
914
+ if (blockMatches.length > 0) {
915
+ const matchedBlocks = blockMatches.map(
916
+ (match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
917
+ ).filter(Boolean);
918
+ const blockFreq = buildBlockComponentFrequency(matchedBlocks);
919
+ boostByBlockFrequency(filteredSearchResults, blockFreq);
920
+ }
921
+ const maxScore = filteredSearchResults.length > 0 ? filteredSearchResults[0].score : 0;
922
+ const scored = filteredSearchResults.map((result) => {
923
+ const component = componentsByName.get(result.name.toLowerCase());
924
+ if (!component) return null;
925
+ return {
926
+ component: component.name,
927
+ category: component.category,
928
+ description: component.description,
929
+ confidence: assignConfidence(result.score, maxScore),
930
+ reasons: [`Matched via hybrid search (score: ${result.score.toFixed(4)})`],
931
+ usage: {
932
+ when: getGuidanceWhen(component).slice(0, 3),
933
+ whenNot: getGuidanceWhenNot(component).slice(0, 2)
934
+ },
935
+ publicRef: component.publicRef,
936
+ componentKey: component.id,
937
+ tier: component.tier,
938
+ isCanonical: component.isCanonical ?? false,
939
+ sourcePath: component.sourcePath,
940
+ exampleCount: component.examples.length,
941
+ status: component.status
942
+ };
943
+ }).filter(Boolean);
944
+ const suggestions = [];
945
+ const categoryCount = {};
946
+ for (const item of scored) {
947
+ if (!item) continue;
948
+ const cat = item.category || "uncategorized";
949
+ const count = categoryCount[cat] || 0;
950
+ if (count < 2 || suggestions.length < 3) {
951
+ suggestions.push(item);
952
+ categoryCount[cat] = count + 1;
953
+ if (suggestions.length >= suggestLimit) break;
954
+ }
955
+ }
956
+ const compositionHint = suggestions.length >= 2 ? `These components work well together. For example, ${suggestions[0].component} can be combined with ${suggestions.slice(1, 3).map((item) => item.component).join(" and ")}.` : void 0;
957
+ const useCaseLower = useCase.toLowerCase();
958
+ const styleKeywords = [
959
+ "color",
960
+ "spacing",
961
+ "padding",
962
+ "margin",
963
+ "font",
964
+ "border",
965
+ "radius",
966
+ "shadow",
967
+ "variable",
968
+ "token",
969
+ "css",
970
+ "theme",
971
+ "dark mode",
972
+ "background",
973
+ "hover"
974
+ ];
975
+ const isStyleQuery = styleKeywords.some(
976
+ (keyword) => useCaseLower.includes(keyword)
977
+ );
978
+ const noMatch = suggestions.length === 0 || !suggestions.some((item) => {
979
+ const component = componentsByName.get(item.component.toLowerCase());
980
+ return component ? hasDirectQueryOverlap(useCase, component) : false;
981
+ });
982
+ const belowThreshold = !noMatch && maxScore > 1 && !meetsMinimumThreshold(maxScore);
983
+ const weakMatch = !noMatch && (belowThreshold || suggestions.every((item) => item.confidence === "low"));
984
+ let recommendation;
985
+ let nextStep;
986
+ if (noMatch) {
987
+ recommendation = isStyleQuery ? `No matching components found. Your query seems styling-related \u2014 try ${ctx.toolNames.tokens} to find tokens.` : `No matching components found. Try different keywords or browse all components with ${ctx.toolNames.discover}.`;
988
+ nextStep = isStyleQuery ? `Use ${ctx.toolNames.tokens}(search: "${useCaseLower.split(/\s+/)[0]}") to find tokens.` : void 0;
989
+ } else if (weakMatch) {
990
+ recommendation = `Weak matches only \u2014 ${suggestions[0].component} might work but confidence is low.${isStyleQuery ? ` If you need tokens, try ${ctx.toolNames.tokens}.` : ""}`;
991
+ nextStep = `Use ${ctx.toolNames.inspect}("${suggestions[0].component}") to check if it fits, or try broader search terms.`;
992
+ } else {
993
+ recommendation = `Best match: ${suggestions[0].component} (${suggestions[0].confidence} confidence) - ${suggestions[0].description}`;
994
+ nextStep = `Use ${ctx.toolNames.inspect}("${suggestions[0].component}") for full details.`;
995
+ }
996
+ const tokenHint = isStyleQuery && !noMatch ? `Your query includes styling terms. For tokens, also try ${ctx.toolNames.tokens}(search: "${useCaseLower.split(/\s+/)[0]}").` : void 0;
997
+ const blockNames = blockMatches.map(
998
+ (match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
999
+ ).filter(Boolean).slice(0, 3).map((block) => block.name);
1000
+ const blockHint = blockNames.length > 0 ? `Related blocks: ${blockNames.join(", ")}. Use ${ctx.toolNames.blocks}(search: "${useCase}") for ready-to-use patterns.` : void 0;
1001
+ let fullBlocks;
1002
+ let fullTokens;
1003
+ let fullImports;
1004
+ if (depth === "full" && !noMatch) {
1005
+ const tokenData = ctx.data.tokens;
1006
+ const [blockSearchResults, tokenSearchResults] = await Promise.all([
1007
+ hybridSearch(fullQuery, localData, 5, "block", ctx.config.searchApiKey),
1008
+ tokenData ? hybridSearch(fullQuery, localData, 10, "token", ctx.config.searchApiKey) : Promise.resolve([])
1009
+ ]);
1010
+ const topBlockScore = blockSearchResults.length > 0 ? blockSearchResults[0].score : 0;
1011
+ const relevantBlockResults = blockSearchResults.filter(
1012
+ (result) => result.score >= topBlockScore * 0.3
1013
+ );
1014
+ if (relevantBlockResults.length > 0) {
1015
+ fullBlocks = (await Promise.all(
1016
+ relevantBlockResults.slice(0, 5).map(async (result) => {
1017
+ const block = allBlocks.find(
1018
+ (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1019
+ );
1020
+ if (!block) return null;
1021
+ const imports = await buildImportStatements(
1022
+ block.components,
1023
+ async (componentName) => ctx.resolvePackageName(componentName)
1024
+ );
1025
+ const codeLines = block.code.split("\n");
1026
+ const code = codeLines.length > 30 ? `${codeLines.slice(0, 20).join("\n")}
1027
+ // ... truncated (${codeLines.length} lines total)` : block.code;
1028
+ return {
1029
+ name: block.name,
1030
+ description: block.description,
1031
+ components: block.components,
1032
+ code,
1033
+ imports
1034
+ };
1035
+ })
1036
+ )).filter(Boolean);
1037
+ }
1038
+ if (tokenSearchResults.length > 0 && tokenData) {
1039
+ fullTokens = {};
1040
+ const tokensByName = /* @__PURE__ */ new Map();
1041
+ for (const [cat, tokens] of Object.entries(tokenData.categories)) {
1042
+ for (const token of tokens) {
1043
+ tokensByName.set(token.name, cat);
1044
+ }
1045
+ }
1046
+ for (const result of tokenSearchResults) {
1047
+ const cat = tokensByName.get(result.name);
1048
+ if (cat) {
1049
+ if (!fullTokens[cat]) fullTokens[cat] = [];
1050
+ fullTokens[cat].push(result.name);
1051
+ }
1052
+ }
1053
+ if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
1054
+ }
1055
+ if (!fullTokens && tokenData) {
1056
+ const categories = extractTokenCategories(fullQuery);
1057
+ fullTokens = {};
1058
+ for (const cat of categories) {
1059
+ const tokens = tokenData.categories[cat];
1060
+ if (tokens && tokens.length > 0) {
1061
+ fullTokens[cat] = tokens.slice(0, 5).map((token) => token.name);
1062
+ }
1063
+ }
1064
+ if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
1065
+ }
1066
+ if (suggestions.length > 0) {
1067
+ fullImports = {};
1068
+ for (const item of suggestions) {
1069
+ if (!item) continue;
1070
+ const pkgName = ctx.resolvePackageName(item.component);
1071
+ fullImports[item.component] = `import { ${item.component} } from '${pkgName}';`;
1072
+ }
1073
+ }
1074
+ }
1075
+ const suggestResponse = verbosity === "compact" ? {
1076
+ useCase,
1077
+ suggestions: suggestions.map((item) => ({
1078
+ component: item.component,
1079
+ description: item.description,
1080
+ confidence: item.confidence
1081
+ })),
1082
+ recommendation
1083
+ } : {
1084
+ useCase,
1085
+ context: context || void 0,
1086
+ suggestions: depth === "full" ? suggestions.map((item) => ({
1087
+ ...item,
1088
+ import: fullImports?.[item.component]
1089
+ })) : suggestions,
1090
+ noMatch,
1091
+ weakMatch,
1092
+ recommendation,
1093
+ compositionHint,
1094
+ ...tokenHint && { tokenHint },
1095
+ ...blockHint && { blockHint },
1096
+ nextStep,
1097
+ ...fullBlocks && fullBlocks.length > 0 && { blocks: fullBlocks },
1098
+ ...fullTokens && { tokens: fullTokens }
1099
+ };
1100
+ return {
1101
+ content: [
1102
+ {
1103
+ type: "text",
1104
+ text: JSON.stringify(suggestResponse)
1105
+ }
1106
+ ]
1107
+ };
1108
+ }
1109
+ if (componentForAlts) {
1110
+ const component = findComponent(data.snapshot, componentForAlts);
1111
+ if (!component) {
1112
+ const closest = findClosestMatch(
1113
+ componentForAlts,
1114
+ componentNames(data.snapshot)
1115
+ );
1116
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
1117
+ return {
1118
+ content: [
1119
+ {
1120
+ type: "text",
1121
+ text: JSON.stringify({
1122
+ error: `Component "${componentForAlts}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
1123
+ })
1124
+ }
1125
+ ],
1126
+ isError: true
1127
+ };
1128
+ }
1129
+ const relations = component.relations;
1130
+ const referencedBy = allSnapshotComponents.map((entry) => {
1131
+ const relation = entry.relations.find(
1132
+ (candidate) => candidate.componentName.toLowerCase() === component.name.toLowerCase()
1133
+ );
1134
+ if (!relation) return null;
1135
+ return {
1136
+ component: entry.name,
1137
+ relationship: relation.relationship,
1138
+ note: relation.note
1139
+ };
1140
+ }).filter(Boolean);
1141
+ const sameCategory = allSnapshotComponents.filter(
1142
+ (entry) => entry.category === component.category && entry.name.toLowerCase() !== component.name.toLowerCase()
1143
+ ).map((entry) => ({
1144
+ component: entry.name,
1145
+ description: entry.description
1146
+ }));
1147
+ return {
1148
+ content: [
1149
+ {
1150
+ type: "text",
1151
+ text: JSON.stringify({
1152
+ component: component.name,
1153
+ category: component.category,
1154
+ directRelations: relations,
1155
+ referencedBy,
1156
+ sameCategory,
1157
+ suggestion: relations.find(
1158
+ (relation) => relation.relationship === "alternative"
1159
+ ) ? `Consider ${relations.find((relation) => relation.relationship === "alternative")?.componentName}: ${relations.find((relation) => relation.relationship === "alternative")?.note}` : void 0
1160
+ })
1161
+ }
1162
+ ]
1163
+ };
1164
+ }
1165
+ let filteredComponents = allSnapshotComponents.filter((component) => {
1166
+ if (category && !categoryMatches(component.category, category)) return false;
1167
+ if (status && (component.status ?? "stable") !== status) return false;
1168
+ return true;
1169
+ });
1170
+ if (search2) {
1171
+ const scored = keywordScoreComponents(
1172
+ search2,
1173
+ filteredComponents,
1174
+ ctx.indexes.componentIndex ?? void 0
1175
+ ).map((result) => {
1176
+ const component = filteredComponents.find(
1177
+ (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1178
+ );
1179
+ if (!component) return null;
1180
+ return {
1181
+ component,
1182
+ score: result.score + getRankingBonus(component)
1183
+ };
1184
+ }).filter(Boolean);
1185
+ filteredComponents = scored.sort((a, b) => b.score - a.score).map((entry) => entry.component);
1186
+ } else {
1187
+ filteredComponents = filteredComponents.sort((a, b) => {
1188
+ const bonusDiff = getRankingBonus(b) - getRankingBonus(a);
1189
+ if (bonusDiff !== 0) return bonusDiff;
1190
+ return a.name.localeCompare(b.name);
1191
+ });
1192
+ }
1193
+ const limitedComponents = listLimit === void 0 ? filteredComponents : filteredComponents.slice(0, listLimit);
1194
+ const components = limitedComponents.map((component) => {
1195
+ if (verbosity === "compact") {
1196
+ return {
1197
+ name: component.name,
1198
+ category: component.category,
1199
+ publicRef: component.publicRef,
1200
+ componentKey: component.id,
1201
+ tier: component.tier,
1202
+ isCanonical: component.isCanonical ?? false,
1203
+ ...component.propsSummary.length > 0 && {
1204
+ propsSummary: component.propsSummary
1205
+ }
1206
+ };
1207
+ }
1208
+ return {
1209
+ name: component.name,
1210
+ category: component.category,
1211
+ description: component.description,
1212
+ status: component.status ?? "stable",
1213
+ publicRef: component.publicRef,
1214
+ componentKey: component.id,
1215
+ tier: component.tier,
1216
+ isCanonical: component.isCanonical ?? false,
1217
+ sourcePath: component.sourcePath,
1218
+ exampleCount: component.examples.length,
1219
+ tags: component.tags,
1220
+ ...(includeCode || verbosity === "full") && component.examples[0]?.code ? {
1221
+ example: component.examples[0].code
1222
+ } : {}
1223
+ };
1224
+ });
1225
+ return {
1226
+ content: [
1227
+ {
1228
+ type: "text",
1229
+ text: JSON.stringify({
1230
+ total: filteredComponents.length,
1231
+ returned: components.length,
1232
+ components,
1233
+ categories: [...new Set(components.map((component) => component.category))],
1234
+ hint: components.length === 0 ? "No components found. Try broader search terms or check available categories." : components.length > 5 ? `Use ${ctx.toolNames.discover} with useCase for recommendations, or ${ctx.toolNames.inspect} for details on a specific component.` : void 0
1235
+ })
1236
+ }
1237
+ ]
1238
+ };
1239
+ };
1240
+
1241
+ // src/tools/inspect.ts
1242
+ import { promises as fs } from "fs";
1243
+ import { existsSync as existsSync2 } from "fs";
1244
+ import { join as join2 } from "path";
1245
+
1246
+ // src/utils.ts
1247
+ function projectFields(obj, fields) {
1248
+ if (!fields || fields.length === 0) {
1249
+ return obj;
1250
+ }
1251
+ const result = {};
1252
+ for (const field of fields) {
1253
+ const parts = field.split(".");
1254
+ let source = obj;
1255
+ let target = result;
1256
+ for (let i = 0; i < parts.length; i++) {
1257
+ const part = parts[i];
1258
+ const isLast = i === parts.length - 1;
1259
+ if (source === null || source === void 0 || typeof source !== "object") {
1260
+ break;
1261
+ }
1262
+ const sourceObj = source;
1263
+ const value = sourceObj[part];
1264
+ if (isLast) {
1265
+ target[part] = value;
1266
+ } else {
1267
+ if (!(part in target)) {
1268
+ target[part] = {};
1269
+ }
1270
+ target = target[part];
1271
+ source = value;
1272
+ }
1273
+ }
1274
+ }
1275
+ return result;
1276
+ }
1277
+
1278
+ // src/tools/inspect.ts
1279
+ async function getSourceCode(component, projectRoot) {
1280
+ const sourcePath = component.sourcePath;
1281
+ if (!sourcePath) return void 0;
1282
+ const fullPath = join2(projectRoot, sourcePath);
1283
+ if (!existsSync2(fullPath)) return { path: sourcePath, code: null };
1284
+ try {
1285
+ const code = await fs.readFile(fullPath, "utf-8");
1286
+ return { path: sourcePath, code };
1287
+ } catch {
1288
+ return { path: sourcePath, code: null };
1289
+ }
1290
+ }
1291
+ var inspectHandler = async (args, ctx) => {
1292
+ const componentName = args?.component;
1293
+ const fields = args?.fields;
1294
+ const exampleName = args?.variant ?? void 0;
1295
+ const maxExamples = args?.maxExamples;
1296
+ const maxLines = args?.maxLines;
1297
+ const verbosity = args?.verbosity ?? "standard";
1298
+ if (!componentName) {
1299
+ return {
1300
+ content: [
1301
+ {
1302
+ type: "text",
1303
+ text: JSON.stringify({ error: "component is required" })
1304
+ }
1305
+ ],
1306
+ isError: true
1307
+ };
1308
+ }
1309
+ const component = findComponent(ctx.data.snapshot, componentName);
1310
+ if (!component) {
1311
+ const closest = findClosestMatch(
1312
+ componentName,
1313
+ componentNames(ctx.data.snapshot)
1314
+ );
1315
+ const suggestion = closest ? ` Did you mean "${closest}"? Use ${ctx.toolNames.inspect}("${closest}") to inspect it.` : "";
1316
+ return {
1317
+ content: [
1318
+ {
1319
+ type: "text",
1320
+ text: JSON.stringify({
1321
+ error: `Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
1322
+ })
1323
+ }
1324
+ ],
1325
+ isError: true
1326
+ };
1327
+ }
1328
+ const pkgName = ctx.resolvePackageName(component.name);
1329
+ let examples = component.examples;
1330
+ if (exampleName) {
1331
+ const query = exampleName.toLowerCase();
1332
+ let filtered = examples.filter((example) => example.name.toLowerCase() === query);
1333
+ if (filtered.length === 0) {
1334
+ filtered = examples.filter(
1335
+ (example) => example.name.toLowerCase().startsWith(query)
1336
+ );
1337
+ }
1338
+ if (filtered.length === 0) {
1339
+ filtered = examples.filter(
1340
+ (example) => example.name.toLowerCase().includes(query)
1341
+ );
1342
+ }
1343
+ if (filtered.length > 0) {
1344
+ examples = filtered;
1345
+ } else {
1346
+ return {
1347
+ content: [
1348
+ {
1349
+ type: "text",
1350
+ text: JSON.stringify({
1351
+ error: `Example "${exampleName}" not found for ${componentName}. Available: ${component.examples.map((example) => example.name).join(", ")}`
1352
+ })
1353
+ }
1354
+ ],
1355
+ isError: true
1356
+ };
1357
+ }
1358
+ }
1359
+ if (maxExamples && maxExamples > 0) {
1360
+ examples = examples.slice(0, maxExamples);
1361
+ }
1362
+ const truncateCode = (code) => {
1363
+ if (!maxLines || maxLines <= 0) {
1364
+ return { code, truncated: false, remainingLines: 0 };
1365
+ }
1366
+ const lines = code.split("\n");
1367
+ if (lines.length <= maxLines) {
1368
+ return { code, truncated: false, remainingLines: 0 };
1369
+ }
1370
+ return {
1371
+ code: lines.slice(0, maxLines).join("\n"),
1372
+ truncated: true,
1373
+ remainingLines: lines.length - maxLines
1374
+ };
1375
+ };
1376
+ const renderedExamples = examples.map((example) => ({
1377
+ ...example.code ? truncateCode(example.code) : { truncated: false, remainingLines: 0 },
1378
+ variant: example.name,
1379
+ description: example.description,
1380
+ code: example.code ? truncateCode(example.code).code : `<${component.name} />`,
1381
+ ...example.code ? {} : {
1382
+ note: "No code example provided. Refer to props for customization."
1383
+ }
1384
+ }));
1385
+ const propsReference = Object.entries(component.props ?? {}).map(
1386
+ ([propName, prop]) => ({
1387
+ name: propName,
1388
+ type: prop.type,
1389
+ required: prop.required,
1390
+ default: prop.default,
1391
+ description: prop.description,
1392
+ values: prop.values
1393
+ })
1394
+ );
1395
+ const propConstraints = Object.entries(component.props ?? {}).filter(
1396
+ ([, prop]) => Boolean(prop.constraints && prop.constraints.length > 0)
1397
+ ).map(([propName, prop]) => ({
1398
+ prop: propName,
1399
+ constraints: prop.constraints
1400
+ }));
1401
+ const fullResult = {
1402
+ meta: {
1403
+ id: component.id,
1404
+ name: component.name,
1405
+ description: component.description,
1406
+ category: component.category,
1407
+ status: component.status,
1408
+ publicRef: component.publicRef,
1409
+ publicSlug: component.publicSlug,
1410
+ isCanonical: component.isCanonical ?? false,
1411
+ tier: component.tier
1412
+ },
1413
+ props: propsReference,
1414
+ examples: {
1415
+ import: `import { ${component.name} } from '${pkgName}';`,
1416
+ code: renderedExamples
1417
+ },
1418
+ relations: component.relations,
1419
+ compoundChildren: component.compoundChildren,
1420
+ guidance: {
1421
+ when: getGuidanceWhen(component),
1422
+ whenNot: getGuidanceWhenNot(component),
1423
+ guidelines: component.guidance.guidelines,
1424
+ accessibility: component.guidance.accessibility,
1425
+ usageGuidance: component.guidance.usageGuidance,
1426
+ dos: component.guidance.dos,
1427
+ donts: component.guidance.donts,
1428
+ patterns: component.guidance.patterns,
1429
+ propConstraints,
1430
+ alternatives: component.relations?.filter((relation) => relation.relationship === "alternative").map((relation) => ({
1431
+ component: relation.componentName,
1432
+ note: relation.note
1433
+ })) ?? []
1434
+ },
1435
+ metadata: component.metadata,
1436
+ source: await getSourceCode(component, ctx.config.projectRoot)
1437
+ };
1438
+ const aliasMap = { usage: "guidance" };
1439
+ const resolvedFields = fields?.map((field) => {
1440
+ const parts = field.split(".");
1441
+ if (aliasMap[parts[0]]) parts[0] = aliasMap[parts[0]];
1442
+ return parts.join(".");
1443
+ });
1444
+ let result;
1445
+ if (verbosity === "compact" && !resolvedFields?.length) {
1446
+ result = {
1447
+ meta: fullResult.meta,
1448
+ propsSummary: component.propsSummary,
1449
+ metadata: component.metadata
1450
+ };
1451
+ } else if (verbosity === "full") {
1452
+ result = resolvedFields && resolvedFields.length > 0 ? projectFields(fullResult, resolvedFields) : fullResult;
1453
+ } else if (resolvedFields && resolvedFields.length > 0) {
1454
+ result = projectFields(fullResult, resolvedFields);
1455
+ } else {
1456
+ const { source: _source, ...withoutSource } = fullResult;
1457
+ result = withoutSource;
1458
+ }
1459
+ return {
1460
+ content: [{ type: "text", text: JSON.stringify(result) }]
1461
+ };
1462
+ };
1463
+
1464
+ // src/tools/blocks.ts
1465
+ var blocksHandler = async (args, ctx) => {
1466
+ const blockName = args?.name;
1467
+ const search2 = args?.search?.toLowerCase() ?? void 0;
1468
+ const component = args?.component?.toLowerCase() ?? void 0;
1469
+ const category = args?.category?.toLowerCase() ?? void 0;
1470
+ const blocksLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : void 0;
1471
+ const allBlocks = listBlocks(ctx.data.snapshot);
1472
+ if (allBlocks.length === 0) {
1473
+ return {
1474
+ content: [{
1475
+ type: "text",
1476
+ text: JSON.stringify({
1477
+ total: 0,
1478
+ blocks: [],
1479
+ hint: `No composition blocks found. Blocks are reusable patterns showing how components wire together (e.g., "Login Form", "Settings Page"). Create .block.ts files and run \`${BRAND.cliCommand} build\`.`
1480
+ })
1481
+ }]
1482
+ };
1483
+ }
1484
+ let filtered = allBlocks;
1485
+ if (blockName) {
1486
+ filtered = filtered.filter(
1487
+ (b) => b.name.toLowerCase() === blockName.toLowerCase()
1488
+ );
1489
+ if (filtered.length === 0) {
1490
+ const allBlockNames = allBlocks.map((b) => b.name);
1491
+ const closest = findClosestMatch(blockName, allBlockNames);
1492
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
1493
+ throw new Error(`Block "${blockName}" not found.${suggestion} Use ${ctx.toolNames.blocks} to see available blocks.`);
1494
+ }
1495
+ }
1496
+ if (search2) {
1497
+ if (ctx.indexes.blockIndex) {
1498
+ const ranked = searchBlocks(search2, ctx.indexes.blockIndex, 50);
1499
+ const rankedNames = new Set(ranked.map((r) => r.name.toLowerCase()));
1500
+ filtered = filtered.filter((b) => rankedNames.has(b.name.toLowerCase()));
1501
+ filtered.sort((a, b) => {
1502
+ const aIdx = ranked.findIndex((r) => r.name.toLowerCase() === a.name.toLowerCase());
1503
+ const bIdx = ranked.findIndex((r) => r.name.toLowerCase() === b.name.toLowerCase());
1504
+ return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
1505
+ });
1506
+ } else {
1507
+ filtered = filtered.filter((b) => {
1508
+ const haystack = [
1509
+ b.name,
1510
+ b.description,
1511
+ ...b.tags ?? [],
1512
+ ...b.components,
1513
+ b.category
1514
+ ].join(" ").toLowerCase();
1515
+ return haystack.includes(search2);
1516
+ });
1517
+ }
1518
+ }
1519
+ if (component) {
1520
+ filtered = filtered.filter(
1521
+ (b) => b.components.some((c) => c.toLowerCase() === component)
1522
+ );
1523
+ }
1524
+ if (category) {
1525
+ filtered = filtered.filter(
1526
+ (b) => b.category.toLowerCase() === category
1527
+ );
1528
+ }
1529
+ const blocksUseIcons = filtered.some(
1530
+ (b) => b.components.some((c) => c === "Icon") || b.code && /\bIcon\b/.test(b.code)
1531
+ );
1532
+ const iconHint = blocksUseIcons ? "Icon components in block code are from @phosphor-icons/react. Import them as: import { IconName } from '@phosphor-icons/react';" : void 0;
1533
+ if (blocksLimit !== void 0) {
1534
+ filtered = filtered.slice(0, blocksLimit);
1535
+ }
1536
+ const verbosity = args?.verbosity ?? "standard";
1537
+ const blocksWithImports = await Promise.all(filtered.map(async (b) => {
1538
+ const imports = await buildImportStatements(
1539
+ b.components,
1540
+ async (componentName) => ctx.resolvePackageName(componentName)
1541
+ );
1542
+ const base = {
1543
+ name: b.name,
1544
+ description: b.description,
1545
+ category: b.category,
1546
+ components: b.components,
1547
+ tags: b.tags,
1548
+ import: imports.join("\n"),
1549
+ imports
1550
+ };
1551
+ if (verbosity === "compact") return base;
1552
+ if (verbosity === "full") return { ...base, code: b.code };
1553
+ const codeLines = b.code.split("\n");
1554
+ const code = codeLines.length > 30 ? codeLines.slice(0, 20).join("\n") + "\n// ... truncated (" + codeLines.length + " lines total)" : b.code;
1555
+ return { ...base, code };
1556
+ }));
1557
+ return {
1558
+ content: [{
1559
+ type: "text",
1560
+ text: JSON.stringify({
1561
+ total: blocksWithImports.length,
1562
+ blocks: blocksWithImports,
1563
+ ...iconHint && { iconHint },
1564
+ ...blocksWithImports.length === 0 && allBlocks.length > 0 && {
1565
+ hint: "No blocks matching your query. Try broader search terms."
1566
+ }
1567
+ })
1568
+ }]
1569
+ };
1570
+ };
1571
+
1572
+ // src/tools/tokens.ts
1573
+ var TOKEN_CATEGORY_ALIASES = {
1574
+ colors: ["color", "colors", "accent", "background", "foreground", "theme"],
1575
+ spacing: ["spacing", "space", "spaces", "padding", "margin", "gap", "inset"],
1576
+ typography: ["typography", "type", "font", "fonts", "letter", "line-height"],
1577
+ surfaces: ["surface", "surfaces", "canvas"],
1578
+ shadows: ["shadow", "shadows", "elevation"],
1579
+ radius: ["radius", "radii", "corner", "corners", "rounded", "rounding"],
1580
+ borders: ["border", "borders", "stroke", "outline"],
1581
+ text: ["text", "copy", "content"],
1582
+ focus: ["focus", "ring", "focus-ring"],
1583
+ layout: ["layout", "container", "grid", "breakpoint"],
1584
+ code: ["code"],
1585
+ "component-sizing": ["component-sizing", "sizing", "size", "sizes"]
1586
+ };
1587
+ function normalizeCategoryValue(value) {
1588
+ const normalized = value?.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1589
+ return normalized && normalized.length > 0 ? normalized : void 0;
1590
+ }
1591
+ function resolveCategoryKeys(categories, requestedCategory) {
1592
+ const normalized = normalizeCategoryValue(requestedCategory);
1593
+ if (!normalized) {
1594
+ return Object.keys(categories);
1595
+ }
1596
+ const keys = Object.keys(categories);
1597
+ const exactMatches = keys.filter((key) => normalizeCategoryValue(key) === normalized);
1598
+ if (exactMatches.length > 0) {
1599
+ return exactMatches;
1600
+ }
1601
+ const canonical = Object.entries(TOKEN_CATEGORY_ALIASES).find(
1602
+ ([categoryName, aliases]) => categoryName === normalized || aliases.includes(normalized)
1603
+ );
1604
+ if (canonical) {
1605
+ const aliases = [canonical[0], ...canonical[1]];
1606
+ const aliasMatches = keys.filter((key) => {
1607
+ const normalizedKey = normalizeCategoryValue(key) ?? "";
1608
+ return aliases.some((alias) => normalizedKey.includes(alias));
1609
+ });
1610
+ if (aliasMatches.length > 0) {
1611
+ return aliasMatches;
1612
+ }
1613
+ }
1614
+ return keys.filter((key) => (normalizeCategoryValue(key) ?? "").includes(normalized));
1615
+ }
1616
+ function canonicalizeCategory(category, tokens) {
1617
+ const normalizedCategory = normalizeCategoryValue(category);
1618
+ const candidates = [
1619
+ normalizedCategory,
1620
+ ...tokens.flatMap((token) => [
1621
+ normalizeCategoryValue(token.category),
1622
+ normalizeCategoryValue(token.path?.[0]),
1623
+ normalizeCategoryValue(token.name.split(/[.:/-]/)[0])
1624
+ ])
1625
+ ].filter(Boolean);
1626
+ for (const candidate of candidates) {
1627
+ for (const [canonical, aliases] of Object.entries(TOKEN_CATEGORY_ALIASES)) {
1628
+ if (candidate === canonical || aliases.some(
1629
+ (alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
1630
+ )) {
1631
+ return canonical;
1632
+ }
1633
+ }
1634
+ }
1635
+ return normalizedCategory || "other";
1636
+ }
1637
+ function normalizeTokenCategories(categories) {
1638
+ const normalized = {};
1639
+ for (const [category, tokens] of Object.entries(categories)) {
1640
+ const canonical = canonicalizeCategory(category, tokens);
1641
+ if (!normalized[canonical]) normalized[canonical] = [];
1642
+ normalized[canonical].push(
1643
+ ...tokens.map((token) => ({
1644
+ ...token,
1645
+ category: canonical
1646
+ }))
1647
+ );
1648
+ }
1649
+ for (const tokens of Object.values(normalized)) {
1650
+ tokens.sort((a, b) => a.name.localeCompare(b.name));
1651
+ }
1652
+ return normalized;
1653
+ }
1654
+ var tokensHandler = async (args, ctx) => {
1655
+ const category = args?.category;
1656
+ const search2 = args?.search?.toLowerCase() ?? void 0;
1657
+ const tokensLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 100) : search2 ? 25 : void 0;
1658
+ const tokenData = ctx.data.tokens;
1659
+ if (!tokenData || tokenData.total === 0) {
1660
+ return {
1661
+ content: [{
1662
+ type: "text",
1663
+ text: JSON.stringify({
1664
+ total: 0,
1665
+ categories: {},
1666
+ hint: `No design tokens found. Add a tokens.include pattern to your ${BRAND.configFile} and run \`${BRAND.cliCommand} build\`.`
1667
+ })
1668
+ }]
1669
+ };
1670
+ }
1671
+ const normalizedCategories = normalizeTokenCategories(tokenData.categories);
1672
+ let filteredCategories = {};
1673
+ let filteredTotal = 0;
1674
+ const resolvedCategoryKeys = resolveCategoryKeys(normalizedCategories, category);
1675
+ const friendlyCategories = Object.keys(TOKEN_CATEGORY_ALIASES);
1676
+ const searchMatchesCategory = search2 ? resolveCategoryKeys(normalizedCategories, search2) : [];
1677
+ for (const [cat, tokens] of Object.entries(normalizedCategories)) {
1678
+ if (category && !resolvedCategoryKeys.includes(cat)) continue;
1679
+ let filtered = tokens;
1680
+ if (search2) {
1681
+ if (searchMatchesCategory.includes(cat)) {
1682
+ filtered = tokens;
1683
+ } else {
1684
+ filtered = tokens.filter(
1685
+ (t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || (normalizeCategoryValue(cat) ?? "").includes(search2) || t.value && t.value.toLowerCase().includes(search2) || t.path && t.path.join(" ").toLowerCase().includes(search2)
1686
+ );
1687
+ }
1688
+ }
1689
+ if (filtered.length > 0) {
1690
+ filteredCategories[cat] = filtered;
1691
+ filteredTotal += filtered.length;
1692
+ }
1693
+ }
1694
+ if (tokensLimit !== void 0) {
1695
+ const limited = limitTokensPerCategory(filteredCategories, tokensLimit);
1696
+ filteredCategories = limited.categories;
1697
+ filteredTotal = limited.total;
1698
+ }
1699
+ let hint;
1700
+ if (filteredTotal === 0) {
1701
+ if (category && search2) {
1702
+ const categoryTotal = resolvedCategoryKeys.reduce(
1703
+ (sum, key) => sum + (normalizedCategories[key]?.length ?? 0),
1704
+ 0
1705
+ );
1706
+ hint = categoryTotal > 0 ? `No tokens matching "${search2}" in category "${category}" (${categoryTotal} tokens in this category). Try a broader search or remove the search term.` : `Category "${category}" not found. Try categories like: ${friendlyCategories.join(", ")}.`;
1707
+ } else if (search2) {
1708
+ hint = `No tokens matching "${search2}". Try categories like: ${friendlyCategories.join(", ")}.`;
1709
+ } else if (category) {
1710
+ hint = `Category "${category}" not found. Try categories like: ${friendlyCategories.join(", ")}.`;
1711
+ }
1712
+ } else if (!category && !search2) {
1713
+ hint = `Use var(--token-name) in your CSS/styles. Filter by category or search to narrow results.`;
1714
+ }
1715
+ return {
1716
+ content: [{
1717
+ type: "text",
1718
+ text: JSON.stringify({
1719
+ prefix: tokenData.prefix,
1720
+ total: filteredTotal,
1721
+ totalAvailable: tokenData.total,
1722
+ categories: filteredCategories,
1723
+ ...hint && { hint },
1724
+ ...!category && !search2 && {
1725
+ availableCategories: Object.entries(normalizedCategories).map(([cat, tokens]) => ({
1726
+ category: cat,
1727
+ count: tokens.length
1728
+ }))
1729
+ }
1730
+ })
1731
+ }]
1732
+ };
1733
+ };
1734
+
1735
+ // src/service.ts
1736
+ var DEFAULT_ENDPOINTS = {
1737
+ render: "/fragments/render",
1738
+ compare: "/fragments/compare",
1739
+ fix: "/fragments/fix",
1740
+ a11y: "/fragments/a11y"
1741
+ };
1742
+ async function renderComponent(viewerUrl, request, endpoints) {
1743
+ const renderUrl = `${viewerUrl}${endpoints?.render ?? DEFAULT_ENDPOINTS.render}`;
1744
+ const response = await fetch(renderUrl, {
1745
+ method: "POST",
1746
+ headers: { "Content-Type": "application/json" },
1747
+ body: JSON.stringify({
1748
+ component: request.component,
1749
+ props: request.props ?? {},
1750
+ variant: request.variant,
1751
+ viewport: request.viewport ?? { width: 800, height: 600 }
1752
+ })
1753
+ });
1754
+ return await response.json();
1755
+ }
1756
+ async function compareComponent(viewerUrl, request, endpoints) {
1757
+ const compareUrl = `${viewerUrl}${endpoints?.compare ?? DEFAULT_ENDPOINTS.compare}`;
1758
+ const response = await fetch(compareUrl, {
1759
+ method: "POST",
1760
+ headers: { "Content-Type": "application/json" },
1761
+ body: JSON.stringify(request)
1762
+ });
1763
+ return await response.json();
1764
+ }
1765
+ async function fixComponent(viewerUrl, request, endpoints) {
1766
+ const fixUrl = `${viewerUrl}${endpoints?.fix ?? DEFAULT_ENDPOINTS.fix}`;
1767
+ const response = await fetch(fixUrl, {
1768
+ method: "POST",
1769
+ headers: { "Content-Type": "application/json" },
1770
+ body: JSON.stringify(request)
1771
+ });
1772
+ return await response.json();
1773
+ }
1774
+ async function auditComponent(viewerUrl, request, endpoints) {
1775
+ const a11yUrl = `${viewerUrl}${endpoints?.a11y ?? DEFAULT_ENDPOINTS.a11y}`;
1776
+ const response = await fetch(a11yUrl, {
1777
+ method: "POST",
1778
+ headers: { "Content-Type": "application/json" },
1779
+ body: JSON.stringify({
1780
+ component: request.component,
1781
+ variant: request.variant,
1782
+ standard: request.standard,
1783
+ includeFixPatches: request.includeFixPatches
1784
+ })
1785
+ });
1786
+ const raw = await response.json();
1787
+ if (raw.error) {
1788
+ return {
1789
+ component: request.component,
1790
+ results: [],
1791
+ score: 0,
1792
+ aaPercent: 0,
1793
+ aaaPercent: 0,
1794
+ passed: false,
1795
+ standard: request.standard ?? "AA",
1796
+ error: raw.error
1797
+ };
1798
+ }
1799
+ const results = raw.results ?? [];
1800
+ const standard = request.standard ?? "AA";
1801
+ let totalCritical = 0;
1802
+ let totalSerious = 0;
1803
+ let totalModerate = 0;
1804
+ let totalMinor = 0;
1805
+ for (const r of results) {
1806
+ totalCritical += r.summary.critical;
1807
+ totalSerious += r.summary.serious;
1808
+ totalModerate += r.summary.moderate;
1809
+ totalMinor += r.summary.minor;
1810
+ }
1811
+ const deductions = totalCritical * 10 + totalSerious * 5 + totalModerate * 2 + totalMinor * 1;
1812
+ const score = Math.max(0, 100 - deductions);
1813
+ const variantCount = results.length;
1814
+ const aaPassCount = results.filter((r) => {
1815
+ const critical = r.summary.critical;
1816
+ const serious = r.summary.serious;
1817
+ return critical === 0 && serious === 0;
1818
+ }).length;
1819
+ const aaaPassCount = results.filter((r) => {
1820
+ const total = r.summary.critical + r.summary.serious + r.summary.moderate + r.summary.minor;
1821
+ return total === 0;
1822
+ }).length;
1823
+ const totalPasses = results.reduce((sum, r) => sum + r.passes, 0);
1824
+ const totalViolations = totalCritical + totalSerious + totalModerate + totalMinor;
1825
+ const emptyAudit = results.length > 0 && totalPasses === 0 && totalViolations === 0;
1826
+ const aaPercent = variantCount > 0 ? Math.round(aaPassCount / variantCount * 100) : 100;
1827
+ const aaaPercent = variantCount > 0 ? Math.round(aaaPassCount / variantCount * 100) : 100;
1828
+ const aaPass = !emptyAudit && totalCritical === 0 && totalSerious === 0;
1829
+ const aaaPass = !emptyAudit && totalViolations === 0;
1830
+ const passed = standard === "AAA" ? aaaPass : aaPass;
1831
+ return {
1832
+ component: request.component,
1833
+ results,
1834
+ score: emptyAudit ? 0 : score,
1835
+ aaPercent: emptyAudit ? 0 : aaPercent,
1836
+ aaaPercent: emptyAudit ? 0 : aaaPercent,
1837
+ ...emptyAudit && { emptyAudit },
1838
+ passed,
1839
+ standard
1840
+ };
1841
+ }
1842
+
1843
+ // src/tools/render.ts
1844
+ var NO_VIEWER_MSG = "This tool requires a running dev server. Add --viewer-url http://localhost:PORT to this server's config args. Start the viewer with 'fragments dev' in your component library directory.";
1845
+ var renderHandler = async (args, ctx) => {
1846
+ const componentName = args?.component;
1847
+ const variantName = args?.variant;
1848
+ const props = args?.props ?? {};
1849
+ const viewport = args?.viewport;
1850
+ const figmaUrl = args?.figmaUrl;
1851
+ const threshold = args?.threshold ?? (figmaUrl ? 1 : ctx.config.threshold ?? DEFAULTS.diffThreshold);
1852
+ if (!componentName) {
1853
+ return {
1854
+ content: [{ type: "text", text: "Error: component name is required" }],
1855
+ isError: true
1856
+ };
1857
+ }
1858
+ {
1859
+ const component = findComponentByName(ctx.data.snapshot, componentName);
1860
+ if (!component) {
1861
+ const allNames = componentNames(ctx.data.snapshot);
1862
+ const closest = findClosestMatch(componentName, allNames);
1863
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
1864
+ throw new Error(`Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`);
1865
+ }
1866
+ }
1867
+ const viewerUrl = ctx.config.viewerUrl;
1868
+ if (!viewerUrl) {
1869
+ return {
1870
+ content: [{
1871
+ type: "text",
1872
+ text: NO_VIEWER_MSG
1873
+ }],
1874
+ isError: true
1875
+ };
1876
+ }
1877
+ if (figmaUrl) {
1878
+ try {
1879
+ const result = await compareComponent(viewerUrl, {
1880
+ component: componentName,
1881
+ variant: variantName,
1882
+ props,
1883
+ figmaUrl,
1884
+ threshold
1885
+ }, ctx.config.fileConfig?.endpoints);
1886
+ if (result.error) {
1887
+ return {
1888
+ content: [{
1889
+ type: "text",
1890
+ text: `Compare error: ${result.error}${result.suggestion ? `
1891
+ Suggestion: ${result.suggestion}` : ""}`
1892
+ }],
1893
+ isError: true
1894
+ };
1895
+ }
1896
+ const content = [];
1897
+ const summaryText = result.match ? `MATCH: ${componentName} matches Figma design (${result.diffPercentage}% diff, threshold: ${result.threshold}%)` : `MISMATCH: ${componentName} differs from Figma design by ${result.diffPercentage}% (threshold: ${result.threshold}%)`;
1898
+ content.push({ type: "text", text: summaryText });
1899
+ if (result.diff && !result.match) {
1900
+ content.push({
1901
+ type: "image",
1902
+ data: result.diff.replace("data:image/png;base64,", ""),
1903
+ mimeType: "image/png"
1904
+ });
1905
+ content.push({
1906
+ type: "text",
1907
+ text: `Diff image above shows visual differences (red highlights). Changed regions: ${result.changedRegions?.length ?? 0}`
1908
+ });
1909
+ }
1910
+ content.push({
1911
+ type: "text",
1912
+ text: JSON.stringify({
1913
+ match: result.match,
1914
+ diffPercentage: result.diffPercentage,
1915
+ threshold: result.threshold,
1916
+ figmaUrl: result.figmaUrl,
1917
+ changedRegions: result.changedRegions
1918
+ })
1919
+ });
1920
+ return { content };
1921
+ } catch (error) {
1922
+ return {
1923
+ content: [{
1924
+ type: "text",
1925
+ text: `Failed to compare component: ${error instanceof Error ? error.message : "Unknown error"}. Make sure the Fragments dev server is running and FIGMA_ACCESS_TOKEN is set.`
1926
+ }],
1927
+ isError: true
1928
+ };
1929
+ }
1930
+ }
1931
+ try {
1932
+ const result = await renderComponent(viewerUrl, {
1933
+ component: componentName,
1934
+ props,
1935
+ variant: variantName,
1936
+ viewport: viewport ?? { width: 800, height: 600 }
1937
+ }, ctx.config.fileConfig?.endpoints);
1938
+ if (result.error) {
1939
+ return {
1940
+ content: [{ type: "text", text: `Render error: ${result.error}` }],
1941
+ isError: true
1942
+ };
1943
+ }
1944
+ return {
1945
+ content: [
1946
+ {
1947
+ type: "image",
1948
+ data: result.screenshot.replace("data:image/png;base64,", ""),
1949
+ mimeType: "image/png"
1950
+ },
1951
+ {
1952
+ type: "text",
1953
+ text: `Successfully rendered ${componentName}${variantName ? ` (variant: "${variantName}")` : ""}${Object.keys(props).length > 0 ? ` with props: ${JSON.stringify(props)}` : ""}`
1954
+ }
1955
+ ]
1956
+ };
1957
+ } catch (error) {
1958
+ return {
1959
+ content: [{
1960
+ type: "text",
1961
+ text: `Failed to render component: ${error instanceof Error ? error.message : "Unknown error"}. Make sure the Fragments dev server is running.`
1962
+ }],
1963
+ isError: true
1964
+ };
1965
+ }
1966
+ };
1967
+
1968
+ // src/tools/fix.ts
1969
+ var NO_VIEWER_MSG2 = "This tool requires a running dev server. Add --viewer-url http://localhost:PORT to this server's config args. Start the viewer with 'fragments dev' in your component library directory.";
1970
+ var fixHandler = async (args, ctx) => {
1971
+ const componentName = args?.component;
1972
+ const variantName = args?.variant ?? void 0;
1973
+ const fixType = args?.fixType ?? "all";
1974
+ if (!componentName) {
1975
+ throw new Error("component is required");
1976
+ }
1977
+ const fragment = findComponentByName(ctx.data.snapshot, componentName);
1978
+ if (!fragment) {
1979
+ const allNames = componentNames(ctx.data.snapshot);
1980
+ const closest = findClosestMatch(componentName, allNames);
1981
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
1982
+ throw new Error(`Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`);
1983
+ }
1984
+ const viewerUrl = ctx.config.viewerUrl;
1985
+ if (!viewerUrl) {
1986
+ return {
1987
+ content: [{
1988
+ type: "text",
1989
+ text: NO_VIEWER_MSG2
1990
+ }],
1991
+ isError: true
1992
+ };
1993
+ }
1994
+ try {
1995
+ const result = await fixComponent(viewerUrl, {
1996
+ component: componentName,
1997
+ variant: variantName,
1998
+ fixType
1999
+ }, ctx.config.fileConfig?.endpoints);
2000
+ if (result.error) {
2001
+ return {
2002
+ content: [{
2003
+ type: "text",
2004
+ text: `Fix generation error: ${result.error}`
2005
+ }],
2006
+ isError: true
2007
+ };
2008
+ }
2009
+ return {
2010
+ content: [{
2011
+ type: "text",
2012
+ text: JSON.stringify({
2013
+ component: componentName,
2014
+ variant: variantName ?? "all",
2015
+ fixType,
2016
+ patches: result.patches,
2017
+ summary: result.summary,
2018
+ patchCount: result.patches.length,
2019
+ nextStep: result.patches.length > 0 ? `Apply patches using your editor or \`patch\` command, then run ${ctx.toolNames.render} to confirm fixes.` : void 0
2020
+ })
2021
+ }]
2022
+ };
2023
+ } catch (error) {
2024
+ return {
2025
+ content: [{
2026
+ type: "text",
2027
+ text: `Failed to generate fixes: ${error instanceof Error ? error.message : "Unknown error"}. Make sure the Fragments dev server is running.`
2028
+ }],
2029
+ isError: true
2030
+ };
2031
+ }
2032
+ };
2033
+
2034
+ // src/tools/a11y.ts
2035
+ var NO_VIEWER_MSG3 = "This tool requires a running dev server. Add --viewer-url http://localhost:PORT to this server's config args. Start the viewer with 'fragments dev' in your component library directory.";
2036
+ var a11yHandler = async (args, ctx) => {
2037
+ const componentName = args?.component;
2038
+ const variantName = args?.variant ?? void 0;
2039
+ const standard = args?.standard ?? "AA";
2040
+ const includeFixPatches = args?.includeFixPatches ?? false;
2041
+ if (!componentName) {
2042
+ throw new Error("component is required");
2043
+ }
2044
+ {
2045
+ const fragment = findComponentByName(ctx.data.snapshot, componentName);
2046
+ if (!fragment) {
2047
+ const allNames = componentNames(ctx.data.snapshot);
2048
+ const closest = findClosestMatch(componentName, allNames);
2049
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2050
+ throw new Error(`Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`);
2051
+ }
2052
+ }
2053
+ const viewerUrl = ctx.config.viewerUrl;
2054
+ if (!viewerUrl) {
2055
+ return {
2056
+ content: [{
2057
+ type: "text",
2058
+ text: NO_VIEWER_MSG3
2059
+ }],
2060
+ isError: true
2061
+ };
2062
+ }
2063
+ try {
2064
+ const result = await auditComponent(viewerUrl, {
2065
+ component: componentName,
2066
+ variant: variantName,
2067
+ standard,
2068
+ includeFixPatches
2069
+ }, ctx.config.fileConfig?.endpoints);
2070
+ if (result.error) {
2071
+ return {
2072
+ content: [{
2073
+ type: "text",
2074
+ text: `A11y audit error: ${result.error}`
2075
+ }],
2076
+ isError: true
2077
+ };
2078
+ }
2079
+ let nextStep;
2080
+ if (result.emptyAudit) {
2081
+ nextStep = `No testable elements found for ${variantName ? `variant "${variantName}"` : componentName}. The variant may not exist or renders no accessible content. Check available variants with ${ctx.toolNames.inspect}("${componentName}").`;
2082
+ } else if (result.passed) {
2083
+ nextStep = 'All accessibility checks passed. Consider running with standard: "AAA" for enhanced compliance.';
2084
+ } else {
2085
+ nextStep = `Fix the violations above, then re-run ${ctx.toolNames.a11y} to verify. Use ${ctx.toolNames.fix} for automated fixes.`;
2086
+ }
2087
+ return {
2088
+ content: [{
2089
+ type: "text",
2090
+ text: JSON.stringify({
2091
+ component: componentName,
2092
+ variant: variantName ?? "all",
2093
+ standard,
2094
+ totalViolations: result.results.reduce((sum, r) => sum + r.summary.total, 0),
2095
+ variantsPassingAA: `${result.aaPercent}%`,
2096
+ variantsPassingAAA: `${result.aaaPercent}%`,
2097
+ passed: result.passed,
2098
+ ...result.emptyAudit && { emptyAudit: true },
2099
+ results: result.results,
2100
+ nextStep
2101
+ })
2102
+ }]
2103
+ };
2104
+ } catch (error) {
2105
+ return {
2106
+ content: [{
2107
+ type: "text",
2108
+ text: `Failed to audit component: ${error instanceof Error ? error.message : "Unknown error"}. Make sure the Fragments dev server is running.`
2109
+ }],
2110
+ isError: true
2111
+ };
2112
+ }
2113
+ };
2114
+
2115
+ // src/graph-handler.ts
2116
+ import {
2117
+ ComponentGraphEngine,
2118
+ deserializeGraph
2119
+ } from "@fragments-sdk/context/graph";
2120
+ function handleGraphTool(args, serializedGraph, blocks, componentNames2) {
2121
+ if (!serializedGraph) {
2122
+ return {
2123
+ text: JSON.stringify({
2124
+ error: "No graph data available. Run `fragments build` to generate the component graph.",
2125
+ hint: "The graph is built automatically during `fragments build` and embedded in fragments.json."
2126
+ }),
2127
+ isError: true
2128
+ };
2129
+ }
2130
+ const graph = deserializeGraph(serializedGraph);
2131
+ const blockData = blocks ? Object.fromEntries(
2132
+ Object.entries(blocks).map(([k, v]) => [k, { components: v.components }])
2133
+ ) : void 0;
2134
+ const engine = new ComponentGraphEngine(graph, blockData);
2135
+ const edgeTypes = args.edgeTypes;
2136
+ switch (args.mode) {
2137
+ case "health": {
2138
+ const health = engine.getHealth();
2139
+ const blockCount = blocks ? Object.keys(blocks).length : 0;
2140
+ return {
2141
+ text: JSON.stringify({
2142
+ mode: "health",
2143
+ ...health,
2144
+ ...health.compositionCoverage === 0 && blockCount === 0 && {
2145
+ compositionNote: "No composition blocks defined yet \u2014 compositionCoverage will increase as blocks are added"
2146
+ },
2147
+ summary: `${health.nodeCount} components, ${health.edgeCount} edges, ${health.connectedComponents.length} island(s), ${health.orphans.length} orphan(s), ${health.compositionCoverage}% in blocks`
2148
+ })
2149
+ };
2150
+ }
2151
+ case "dependencies": {
2152
+ if (!args.component) {
2153
+ return { text: JSON.stringify({ error: "component is required for dependencies mode" }), isError: true };
2154
+ }
2155
+ if (!engine.hasNode(args.component)) {
2156
+ const closest = componentNames2 ? findClosestMatch(args.component, componentNames2) : null;
2157
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2158
+ return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2159
+ }
2160
+ const deps = engine.dependencies(args.component, edgeTypes);
2161
+ return {
2162
+ text: JSON.stringify({
2163
+ mode: "dependencies",
2164
+ component: args.component,
2165
+ count: deps.length,
2166
+ dependencies: deps.map((e) => ({
2167
+ component: e.target,
2168
+ type: e.type,
2169
+ weight: e.weight,
2170
+ note: e.note,
2171
+ provenance: e.provenance
2172
+ }))
2173
+ })
2174
+ };
2175
+ }
2176
+ case "dependents": {
2177
+ if (!args.component) {
2178
+ return { text: JSON.stringify({ error: "component is required for dependents mode" }), isError: true };
2179
+ }
2180
+ if (!engine.hasNode(args.component)) {
2181
+ const closest = componentNames2 ? findClosestMatch(args.component, componentNames2) : null;
2182
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2183
+ return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2184
+ }
2185
+ const deps = engine.dependents(args.component, edgeTypes);
2186
+ return {
2187
+ text: JSON.stringify({
2188
+ mode: "dependents",
2189
+ component: args.component,
2190
+ count: deps.length,
2191
+ dependents: deps.map((e) => ({
2192
+ component: e.source,
2193
+ type: e.type,
2194
+ weight: e.weight,
2195
+ note: e.note,
2196
+ provenance: e.provenance
2197
+ }))
2198
+ })
2199
+ };
2200
+ }
2201
+ case "impact": {
2202
+ if (!args.component) {
2203
+ return { text: JSON.stringify({ error: "component is required for impact mode" }), isError: true };
2204
+ }
2205
+ if (!engine.hasNode(args.component)) {
2206
+ const closest = componentNames2 ? findClosestMatch(args.component, componentNames2) : null;
2207
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2208
+ return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2209
+ }
2210
+ const result = engine.impact(args.component, args.maxDepth ?? 3);
2211
+ return {
2212
+ text: JSON.stringify({
2213
+ mode: "impact",
2214
+ ...result,
2215
+ summary: `Changing ${args.component} affects ${result.totalAffected} component(s) and ${result.affectedBlocks.length} block(s)`
2216
+ })
2217
+ };
2218
+ }
2219
+ case "path": {
2220
+ if (!args.component || !args.target) {
2221
+ return { text: JSON.stringify({ error: "component and target are required for path mode" }), isError: true };
2222
+ }
2223
+ const result = engine.path(args.component, args.target);
2224
+ return {
2225
+ text: JSON.stringify({
2226
+ mode: "path",
2227
+ from: args.component,
2228
+ to: args.target,
2229
+ ...result,
2230
+ edges: result.edges.map((e) => ({
2231
+ source: e.source,
2232
+ target: e.target,
2233
+ type: e.type
2234
+ }))
2235
+ })
2236
+ };
2237
+ }
2238
+ case "composition": {
2239
+ if (!args.component) {
2240
+ return { text: JSON.stringify({ error: "component is required for composition mode" }), isError: true };
2241
+ }
2242
+ if (!engine.hasNode(args.component)) {
2243
+ const closest = componentNames2 ? findClosestMatch(args.component, componentNames2) : null;
2244
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2245
+ return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2246
+ }
2247
+ const tree = engine.composition(args.component);
2248
+ return {
2249
+ text: JSON.stringify({
2250
+ mode: "composition",
2251
+ ...tree
2252
+ })
2253
+ };
2254
+ }
2255
+ case "alternatives": {
2256
+ if (!args.component) {
2257
+ return { text: JSON.stringify({ error: "component is required for alternatives mode" }), isError: true };
2258
+ }
2259
+ if (!engine.hasNode(args.component)) {
2260
+ const closest = componentNames2 ? findClosestMatch(args.component, componentNames2) : null;
2261
+ const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2262
+ return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2263
+ }
2264
+ const alts = engine.alternatives(args.component);
2265
+ return {
2266
+ text: JSON.stringify({
2267
+ mode: "alternatives",
2268
+ component: args.component,
2269
+ count: alts.length,
2270
+ alternatives: alts
2271
+ })
2272
+ };
2273
+ }
2274
+ case "islands": {
2275
+ const islands = engine.islands();
2276
+ return {
2277
+ text: JSON.stringify({
2278
+ mode: "islands",
2279
+ count: islands.length,
2280
+ islands: islands.map((island, i) => ({
2281
+ id: i + 1,
2282
+ size: island.length,
2283
+ components: island
2284
+ }))
2285
+ })
2286
+ };
2287
+ }
2288
+ default:
2289
+ return {
2290
+ text: JSON.stringify({
2291
+ error: `Unknown mode: "${args.mode}". Valid modes: dependencies, dependents, impact, path, composition, alternatives, islands, health`
2292
+ }),
2293
+ isError: true
2294
+ };
2295
+ }
2296
+ }
2297
+
2298
+ // src/tools/graph.ts
2299
+ var graphHandler = async (args, ctx) => {
2300
+ const graphArgs = {
2301
+ mode: args?.mode ?? "health",
2302
+ component: args?.component,
2303
+ target: args?.target,
2304
+ edgeTypes: args?.edgeTypes,
2305
+ maxDepth: args?.maxDepth
2306
+ };
2307
+ const data = ctx.data;
2308
+ const allNames = componentNames(data.snapshot);
2309
+ const result = handleGraphTool(
2310
+ graphArgs,
2311
+ data.graph,
2312
+ data.blocks,
2313
+ allNames
2314
+ );
2315
+ if (result.isError) {
2316
+ return {
2317
+ content: [{ type: "text", text: result.text }],
2318
+ isError: true
2319
+ };
2320
+ }
2321
+ return {
2322
+ content: [{ type: "text", text: result.text }]
2323
+ };
2324
+ };
2325
+
2326
+ // src/tools/perf.ts
2327
+ var perfHandler = async (args, ctx) => {
2328
+ const componentName = args?.component ?? void 0;
2329
+ const sort = args?.sort ?? "size";
2330
+ const filter = args?.filter ?? void 0;
2331
+ let entries = Object.values(ctx.data.components).filter((component) => component.performance).map((component) => ({
2332
+ name: component.name,
2333
+ ...component.performance
2334
+ }));
2335
+ if (entries.length === 0) {
2336
+ return {
2337
+ content: [
2338
+ {
2339
+ type: "text",
2340
+ text: JSON.stringify({
2341
+ total: 0,
2342
+ components: [],
2343
+ hint: `No performance data found. Run \`${BRAND.cliCommand} perf\` first to measure bundle sizes.`
2344
+ })
2345
+ }
2346
+ ]
2347
+ };
2348
+ }
2349
+ if (componentName) {
2350
+ entries = entries.filter(
2351
+ (entry) => entry.name.toLowerCase() === componentName.toLowerCase()
2352
+ );
2353
+ if (entries.length === 0) {
2354
+ throw new Error(
2355
+ `No performance data for "${componentName}". Run \`${BRAND.cliCommand} perf --component ${componentName}\` first.`
2356
+ );
2357
+ }
2358
+ }
2359
+ if (filter) {
2360
+ if (filter === "over-budget") {
2361
+ entries = entries.filter((entry) => entry.overBudget);
2362
+ } else {
2363
+ entries = entries.filter((entry) => entry.complexity === filter);
2364
+ }
2365
+ }
2366
+ switch (sort) {
2367
+ case "name":
2368
+ entries.sort((a, b) => a.name.localeCompare(b.name));
2369
+ break;
2370
+ case "budget":
2371
+ entries.sort((a, b) => b.budgetPercent - a.budgetPercent);
2372
+ break;
2373
+ case "size":
2374
+ default:
2375
+ entries.sort((a, b) => b.bundleSize - a.bundleSize);
2376
+ break;
2377
+ }
2378
+ return {
2379
+ content: [
2380
+ {
2381
+ type: "text",
2382
+ text: JSON.stringify({
2383
+ total: entries.length,
2384
+ summary: ctx.data.performanceSummary ?? void 0,
2385
+ components: entries
2386
+ })
2387
+ }
2388
+ ]
2389
+ };
2390
+ };
2391
+
2392
+ // src/tools/govern.ts
2393
+ import {
2394
+ handleGovernTool,
2395
+ formatVerdict,
2396
+ universal,
2397
+ fragments as fragmentsPreset
2398
+ } from "@fragments-sdk/govern";
2399
+ var governHandler = async (args, ctx) => {
2400
+ const spec = args?.spec;
2401
+ if (!spec || typeof spec !== "object") {
2402
+ return {
2403
+ content: [
2404
+ {
2405
+ type: "text",
2406
+ text: JSON.stringify({
2407
+ error: "spec is required and must be an object with { nodes: [{ id, type, props, children }] }"
2408
+ })
2409
+ }
2410
+ ],
2411
+ isError: true
2412
+ };
2413
+ }
2414
+ const policyOverrides = args?.policy;
2415
+ const format = args?.format ?? "json";
2416
+ const allowedComponents = Object.values(ctx.data.components).map(
2417
+ (component) => component.name
2418
+ );
2419
+ const tokenPrefix = ctx.data.tokens?.prefix;
2420
+ const basePolicy = tokenPrefix === "fui-" ? { rules: fragmentsPreset().rules } : { rules: universal().rules };
2421
+ basePolicy.rules["components/allow"] = {
2422
+ enabled: true,
2423
+ severity: "serious",
2424
+ options: {
2425
+ components: allowedComponents
2426
+ }
2427
+ };
2428
+ const engineOptions = ctx.data.tokens ? { tokenData: ctx.data.tokens } : void 0;
2429
+ const input = {
2430
+ spec,
2431
+ policy: policyOverrides,
2432
+ format
2433
+ };
2434
+ try {
2435
+ const verdict = await handleGovernTool(input, basePolicy, engineOptions);
2436
+ const text = format === "summary" ? formatVerdict(verdict, "summary") : JSON.stringify(verdict);
2437
+ return {
2438
+ content: [{ type: "text", text }],
2439
+ _meta: {
2440
+ score: verdict.score,
2441
+ passed: verdict.passed,
2442
+ violationCount: verdict.results.reduce(
2443
+ (sum, r) => sum + r.violations.length,
2444
+ 0
2445
+ )
2446
+ }
2447
+ };
2448
+ } catch (error) {
2449
+ const message = error instanceof Error ? error.message : String(error);
2450
+ const isSpecError = message.includes("Expected") || message.includes("Required");
2451
+ return {
2452
+ content: [
2453
+ {
2454
+ type: "text",
2455
+ text: JSON.stringify({
2456
+ error: isSpecError ? `Invalid spec format: ${message}. Expected: { nodes: [{ id: string, type: string, props: object, children?: string[] }] }` : message
2457
+ })
2458
+ }
2459
+ ],
2460
+ isError: true
2461
+ };
2462
+ }
2463
+ };
2464
+
2465
+ // src/tools/generate-ui.ts
2466
+ var generateUiHandler = async (args, ctx) => {
2467
+ const prompt = args?.prompt;
2468
+ if (!prompt) {
2469
+ throw new Error("prompt is required");
2470
+ }
2471
+ const currentTree = args?.currentTree;
2472
+ const playgroundUrl = ctx.config.playgroundUrl ?? "https://usefragments.com";
2473
+ const response = await fetch(`${playgroundUrl}/api/playground/generate`, {
2474
+ method: "POST",
2475
+ headers: { "Content-Type": "application/json" },
2476
+ body: JSON.stringify({
2477
+ prompt,
2478
+ ...currentTree && { currentSpec: currentTree }
2479
+ })
2480
+ });
2481
+ if (!response.ok) {
2482
+ const errorBody = await response.text();
2483
+ throw new Error(`Playground API error (${response.status}): ${errorBody}`);
2484
+ }
2485
+ const text = await response.text();
2486
+ return {
2487
+ content: [{
2488
+ type: "text",
2489
+ text
2490
+ }]
2491
+ };
2492
+ };
2493
+
2494
+ // src/tools/index.ts
2495
+ var CORE_TOOLS = {
2496
+ discover: discoverHandler,
2497
+ inspect: inspectHandler,
2498
+ blocks: blocksHandler,
2499
+ tokens: tokensHandler,
2500
+ graph: graphHandler,
2501
+ perf: perfHandler,
2502
+ govern: governHandler
2503
+ };
2504
+ var VIEWER_TOOLS = {
2505
+ render: renderHandler,
2506
+ fix: fixHandler,
2507
+ a11y: a11yHandler
2508
+ };
2509
+ var INFRA_TOOLS = {
2510
+ generate_ui: generateUiHandler
2511
+ };
2512
+ var BUILTIN_TOOLS = {
2513
+ ...CORE_TOOLS,
2514
+ ...VIEWER_TOOLS,
2515
+ ...INFRA_TOOLS
2516
+ };
2517
+ var TOOL_CAPABILITIES = {
2518
+ discover: ["components"],
2519
+ inspect: ["components"],
2520
+ blocks: ["blocks"],
2521
+ tokens: ["tokens"],
2522
+ graph: ["graph"],
2523
+ perf: ["performance"],
2524
+ render: ["components"],
2525
+ fix: ["components"],
2526
+ a11y: ["components"]
2527
+ };
2528
+
2529
+ // src/registry.ts
2530
+ var ToolRegistry = class {
2531
+ tools = /* @__PURE__ */ new Map();
2532
+ prefix;
2533
+ onChanged;
2534
+ constructor(prefix, opts) {
2535
+ this.prefix = prefix;
2536
+ this.onChanged = opts?.onChanged;
2537
+ }
2538
+ /** Register a single tool */
2539
+ register(key, handler, definition, availability = "always", requiredCapabilities = []) {
2540
+ this.tools.set(key, {
2541
+ key,
2542
+ handler,
2543
+ definition,
2544
+ availability,
2545
+ requiredCapabilities
2546
+ });
2547
+ this.onChanged?.();
2548
+ }
2549
+ /** Unregister a tool by key */
2550
+ unregister(key) {
2551
+ if (this.tools.delete(key)) {
2552
+ this.onChanged?.();
2553
+ }
2554
+ }
2555
+ /** Bulk register built-in tools with availability metadata */
2556
+ registerBuiltins(toolSets, definitions, capabilityByKey = {}) {
2557
+ const defMap = new Map(definitions.map((d) => [d.key, d]));
2558
+ for (const [key, handler] of Object.entries(toolSets.core)) {
2559
+ this.tools.set(key, {
2560
+ key,
2561
+ handler,
2562
+ definition: defMap.get(key),
2563
+ availability: "always",
2564
+ requiredCapabilities: capabilityByKey[key] ?? []
2565
+ });
2566
+ }
2567
+ for (const [key, handler] of Object.entries(toolSets.viewer)) {
2568
+ this.tools.set(key, {
2569
+ key,
2570
+ handler,
2571
+ definition: defMap.get(key),
2572
+ availability: "viewer",
2573
+ requiredCapabilities: capabilityByKey[key] ?? []
2574
+ });
2575
+ }
2576
+ for (const [key, handler] of Object.entries(toolSets.infra)) {
2577
+ this.tools.set(key, {
2578
+ key,
2579
+ handler,
2580
+ definition: defMap.get(key),
2581
+ availability: "playground",
2582
+ requiredCapabilities: capabilityByKey[key] ?? []
2583
+ });
2584
+ }
2585
+ }
2586
+ /** Get handler for a tool (by unprefixed key or prefixed name) */
2587
+ getHandler(nameOrKey) {
2588
+ const direct = this.tools.get(nameOrKey);
2589
+ if (direct) return direct.handler;
2590
+ if (!this.prefix) return void 0;
2591
+ const prefixStr = this.prefix + "_";
2592
+ if (nameOrKey.startsWith(prefixStr)) {
2593
+ const key = nameOrKey.slice(prefixStr.length);
2594
+ return this.tools.get(key)?.handler;
2595
+ }
2596
+ return void 0;
2597
+ }
2598
+ /** Resolve unprefixed key from a potentially prefixed tool name */
2599
+ resolveKey(name) {
2600
+ if (!this.prefix) return name;
2601
+ const prefixStr = this.prefix + "_";
2602
+ return name.startsWith(prefixStr) ? name.slice(prefixStr.length) : name;
2603
+ }
2604
+ /** List available tools as MCP Tool[] based on current availability context */
2605
+ listTools(ctx, allToolSchemas) {
2606
+ const availableKeys = /* @__PURE__ */ new Set();
2607
+ for (const [key, tool] of this.tools) {
2608
+ const hasCapabilities = tool.requiredCapabilities.every(
2609
+ (capability) => ctx.capabilities.has(capability)
2610
+ );
2611
+ if (!hasCapabilities) continue;
2612
+ if (tool.availability === "always") {
2613
+ availableKeys.add(key);
2614
+ } else if (tool.availability === "viewer" && ctx.hasViewer) {
2615
+ availableKeys.add(key);
2616
+ } else if (tool.availability === "playground" && ctx.hasPlayground) {
2617
+ availableKeys.add(key);
2618
+ }
2619
+ }
2620
+ if (!this.prefix) {
2621
+ return allToolSchemas.filter((t) => availableKeys.has(t.name));
2622
+ }
2623
+ const prefixStr = this.prefix + "_";
2624
+ return allToolSchemas.filter((t) => {
2625
+ const key = t.name.startsWith(prefixStr) ? t.name.slice(prefixStr.length) : t.name;
2626
+ return availableKeys.has(key);
2627
+ });
2628
+ }
2629
+ /** Execute a tool by name, dispatching to its handler */
2630
+ async execute(name, args, ctx) {
2631
+ const key = this.resolveKey(name);
2632
+ const registered = this.tools.get(key);
2633
+ if (!registered) {
2634
+ throw new Error(`Unknown tool: ${name}`);
2635
+ }
2636
+ return registered.handler(args, ctx);
2637
+ }
2638
+ /** Get the number of registered tools */
2639
+ get size() {
2640
+ return this.tools.size;
2641
+ }
2642
+ /** Get all registered keys */
2643
+ keys() {
2644
+ return Array.from(this.tools.keys());
2645
+ }
2646
+ };
2647
+
2648
+ // src/middleware.ts
2649
+ function executeWithMiddleware(middlewares, mCtx, handler) {
2650
+ const chain = [...middlewares].reverse().reduce(
2651
+ (next, mw) => () => mw(mCtx, next),
2652
+ handler
2653
+ );
2654
+ return chain();
2655
+ }
2656
+ function telemetryMiddleware(logger) {
2657
+ return async (mCtx, next) => {
2658
+ const start = Date.now();
2659
+ const result = await next();
2660
+ logger(`${mCtx.toolKey}: ${Date.now() - start}ms`);
2661
+ return result;
2662
+ };
2663
+ }
2664
+
2665
+ // src/source-selection.ts
2666
+ import { existsSync as existsSync8 } from "fs";
2667
+ import { join as join7 } from "path";
2668
+
2669
+ // src/adapters/fragments-json.ts
2670
+ import { readFile } from "fs/promises";
2671
+
2672
+ // src/discovery.ts
2673
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
2674
+ import { join as join3, dirname, resolve } from "path";
2675
+ import { createRequire } from "module";
2676
+ function resolveWorkspaceGlob(baseDir, pattern) {
2677
+ const parts = pattern.split("/");
2678
+ let dirs = [baseDir];
2679
+ for (const part of parts) {
2680
+ if (part === "**") continue;
2681
+ const next = [];
2682
+ for (const d of dirs) {
2683
+ if (part === "*") {
2684
+ try {
2685
+ for (const entry of readdirSync(d, { withFileTypes: true })) {
2686
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2687
+ next.push(join3(d, entry.name));
2688
+ }
2689
+ }
2690
+ } catch {
2691
+ }
2692
+ } else {
2693
+ const candidate = join3(d, part);
2694
+ if (existsSync3(candidate)) next.push(candidate);
2695
+ }
2696
+ }
2697
+ dirs = next;
2698
+ }
2699
+ return dirs;
2700
+ }
2701
+ function getWorkspaceDirs(rootDir) {
2702
+ const dirs = [];
2703
+ const rootPkgPath = join3(rootDir, "package.json");
2704
+ if (existsSync3(rootPkgPath)) {
2705
+ try {
2706
+ const rootPkg = JSON.parse(readFileSync3(rootPkgPath, "utf-8"));
2707
+ const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages;
2708
+ if (Array.isArray(workspaces)) {
2709
+ for (const pattern of workspaces) {
2710
+ dirs.push(...resolveWorkspaceGlob(rootDir, pattern));
2711
+ }
2712
+ return dirs;
2713
+ }
2714
+ } catch {
2715
+ }
2716
+ }
2717
+ const pnpmWsPath = join3(rootDir, "pnpm-workspace.yaml");
2718
+ if (existsSync3(pnpmWsPath)) {
2719
+ try {
2720
+ const content = readFileSync3(pnpmWsPath, "utf-8");
2721
+ const lines = content.split("\n");
2722
+ let inPackages = false;
2723
+ for (const line of lines) {
2724
+ if (/^packages\s*:/.test(line)) {
2725
+ inPackages = true;
2726
+ continue;
2727
+ }
2728
+ if (inPackages) {
2729
+ const match = line.match(/^\s+-\s+['"]?([^'"#\n]+)['"]?/);
2730
+ if (match) {
2731
+ dirs.push(...resolveWorkspaceGlob(rootDir, match[1].trim()));
2732
+ } else if (/^\S/.test(line) && line.trim()) {
2733
+ break;
2734
+ }
2735
+ }
2736
+ }
2737
+ } catch {
2738
+ }
2739
+ }
2740
+ return dirs;
2741
+ }
2742
+ function resolveDepPackageJson(localRequire, depName) {
2743
+ try {
2744
+ return localRequire.resolve(`${depName}/package.json`);
2745
+ } catch {
2746
+ }
2747
+ try {
2748
+ const mainPath = localRequire.resolve(depName);
2749
+ let dir = dirname(mainPath);
2750
+ while (true) {
2751
+ const candidate = join3(dir, "package.json");
2752
+ if (existsSync3(candidate)) {
2753
+ const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
2754
+ if (pkg.name === depName) return candidate;
2755
+ }
2756
+ const parent = dirname(dir);
2757
+ if (parent === dir) break;
2758
+ dir = parent;
2759
+ }
2760
+ } catch {
2761
+ }
2762
+ return null;
2763
+ }
2764
+ function findFragmentsInDeps(dir, found, depField) {
2765
+ const pkgJsonPath = join3(dir, "package.json");
2766
+ if (!existsSync3(pkgJsonPath)) return;
2767
+ try {
2768
+ const pkgJson = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
2769
+ const allDeps = {
2770
+ ...pkgJson.dependencies,
2771
+ ...pkgJson.devDependencies
2772
+ };
2773
+ const localRequire = createRequire(join3(dir, "noop.js"));
2774
+ for (const depName of Object.keys(allDeps)) {
2775
+ try {
2776
+ const depPkgPath = resolveDepPackageJson(localRequire, depName);
2777
+ if (!depPkgPath) continue;
2778
+ const depPkg = JSON.parse(readFileSync3(depPkgPath, "utf-8"));
2779
+ if (depPkg[depField]) {
2780
+ const fragmentsPath = join3(dirname(depPkgPath), depPkg[depField]);
2781
+ if (existsSync3(fragmentsPath) && !found.includes(fragmentsPath)) {
2782
+ found.push(fragmentsPath);
2783
+ }
2784
+ }
2785
+ } catch {
2786
+ }
2787
+ }
2788
+ } catch {
2789
+ }
2790
+ }
2791
+ function findDesignSystemJson(startDir, outFile, depField) {
2792
+ const found = [];
2793
+ const resolvedStart = resolve(startDir);
2794
+ let dir = resolvedStart;
2795
+ while (true) {
2796
+ const candidate = join3(dir, outFile);
2797
+ if (existsSync3(candidate)) {
2798
+ found.push(candidate);
2799
+ break;
2800
+ }
2801
+ const parent = dirname(dir);
2802
+ if (parent === dir) break;
2803
+ dir = parent;
2804
+ }
2805
+ findFragmentsInDeps(resolvedStart, found, depField);
2806
+ if (found.length === 0 || existsSync3(join3(resolvedStart, "pnpm-workspace.yaml"))) {
2807
+ const workspaceDirs = getWorkspaceDirs(resolvedStart);
2808
+ for (const wsDir of workspaceDirs) {
2809
+ findFragmentsInDeps(wsDir, found, depField);
2810
+ }
2811
+ }
2812
+ return found;
2813
+ }
2814
+ function findFragmentsJson(startDir) {
2815
+ return findDesignSystemJson(startDir, BRAND.outFile, "fragments");
2816
+ }
2817
+ function findBundleManifest(startDir) {
2818
+ const found = [];
2819
+ let dir = resolve(startDir);
2820
+ while (true) {
2821
+ const candidate = join3(dir, BRAND.dataDir, BRAND.manifestFile);
2822
+ if (existsSync3(candidate)) {
2823
+ found.push(candidate);
2824
+ break;
2825
+ }
2826
+ const parent = dirname(dir);
2827
+ if (parent === dir) break;
2828
+ dir = parent;
2829
+ }
2830
+ return found;
2831
+ }
2832
+
2833
+ // src/adapters/snapshot-converters.ts
2834
+ import { mcpSnapshotSchema } from "@fragments-sdk/core";
2835
+ function slugify(value) {
2836
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
2837
+ }
2838
+ function buildComponentId(args) {
2839
+ const base = args.filePath ?? args.packageName ?? args.name;
2840
+ return `${slugify(args.name)}:${base}`;
2841
+ }
2842
+ function valueToString(value) {
2843
+ if (value === void 0) return void 0;
2844
+ if (typeof value === "string") return value;
2845
+ if (typeof value === "number" || typeof value === "boolean" || value === null) {
2846
+ return String(value);
2847
+ }
2848
+ try {
2849
+ return JSON.stringify(value);
2850
+ } catch {
2851
+ return void 0;
2852
+ }
2853
+ }
2854
+ function buildCompoundChildren(contract, ai) {
2855
+ const contractChildren = Object.entries(contract?.compoundChildren ?? {});
2856
+ if (contractChildren.length > 0) {
2857
+ return contractChildren.map(([childName, child]) => ({
2858
+ name: childName,
2859
+ description: child.description,
2860
+ required: child.required,
2861
+ accepts: child.accepts,
2862
+ visibility: "public"
2863
+ }));
2864
+ }
2865
+ const subs = ai?.subComponents;
2866
+ if (!subs || subs.length === 0) return [];
2867
+ const requiredSet = new Set(ai?.requiredChildren ?? []);
2868
+ return subs.map((name) => ({
2869
+ name,
2870
+ required: requiredSet.has(name) || void 0,
2871
+ visibility: "public"
2872
+ }));
2873
+ }
2874
+ function componentFromCompiledFragment(args) {
2875
+ const { fragment, sourceType } = args;
2876
+ const name = fragment.meta.name;
2877
+ const description = fragment.meta.description ?? "";
2878
+ const usage = fragment.usage ?? { when: [], whenNot: [] };
2879
+ const contract = fragment.contract;
2880
+ const ai = fragment.ai;
2881
+ const guidancePatterns = (fragment._cloudPatterns ?? []).map((pattern) => ({
2882
+ name: pattern.name ?? "Pattern",
2883
+ description: pattern.description
2884
+ }));
2885
+ return {
2886
+ id: args.id ?? buildComponentId({
2887
+ name,
2888
+ filePath: fragment.sourcePath ?? fragment.filePath,
2889
+ packageName: args.packageName
2890
+ }),
2891
+ name,
2892
+ description,
2893
+ category: fragment.meta.category ?? "uncategorized",
2894
+ status: fragment.meta.status ?? "stable",
2895
+ tags: fragment.meta.tags ?? [],
2896
+ props: Object.fromEntries(
2897
+ Object.entries(fragment.props ?? {}).map(([propName, prop]) => [
2898
+ propName,
2899
+ {
2900
+ type: prop.type,
2901
+ description: prop.description ?? "",
2902
+ required: prop.required ?? false,
2903
+ default: prop.default,
2904
+ values: prop.values ? [...prop.values] : void 0,
2905
+ constraints: prop.constraints ? [...prop.constraints] : void 0
2906
+ }
2907
+ ])
2908
+ ),
2909
+ propsSummary: fragment.propsSummary ?? contract?.propsSummary ?? Object.entries(fragment.props ?? {}).map(
2910
+ ([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type}`
2911
+ ),
2912
+ examples: (fragment.variants ?? []).map((variant) => ({
2913
+ name: variant.name,
2914
+ description: variant.description,
2915
+ code: variant.code,
2916
+ kind: "example"
2917
+ })),
2918
+ relations: (fragment.relations ?? []).map((relation) => ({
2919
+ componentName: relation.component,
2920
+ relationship: relation.relationship,
2921
+ note: relation.note
2922
+ })),
2923
+ compoundChildren: buildCompoundChildren(contract, ai),
2924
+ guidance: {
2925
+ when: usage.when ?? [],
2926
+ whenNot: usage.whenNot ?? [],
2927
+ guidelines: usage.guidelines ?? [],
2928
+ accessibility: usage.accessibility ?? [],
2929
+ dos: usage.when ?? [],
2930
+ donts: usage.whenNot ?? [],
2931
+ patterns: guidancePatterns.length > 0 ? guidancePatterns : (ai?.commonPatterns ?? []).map((pattern) => ({
2932
+ name: pattern
2933
+ }))
2934
+ },
2935
+ sourceType,
2936
+ sourcePath: fragment.sourcePath ?? fragment.filePath,
2937
+ packageName: args.packageName,
2938
+ importPath: args.importPath ?? args.packageName,
2939
+ performance: fragment.performance ? {
2940
+ bundleSize: fragment.performance.bundleSize,
2941
+ rawSize: fragment.performance.rawSize,
2942
+ complexity: fragment.performance.complexity,
2943
+ budgetPercent: fragment.performance.budgetPercent,
2944
+ overBudget: fragment.performance.overBudget,
2945
+ measuredAt: fragment.performance.measuredAt,
2946
+ imports: fragment.performance.imports?.map((entry) => ({
2947
+ path: entry.path,
2948
+ bytes: entry.bytes,
2949
+ percent: entry.percent
2950
+ }))
2951
+ } : void 0,
2952
+ metadata: {
2953
+ a11yRules: contract?.a11yRules ?? [],
2954
+ scenarioTags: contract?.scenarioTags ?? []
2955
+ }
2956
+ };
2957
+ }
2958
+ function blockFromCompiledBlock(key, block) {
2959
+ return {
2960
+ id: key,
2961
+ name: block.name,
2962
+ description: block.description ?? "",
2963
+ category: block.category ?? "uncategorized",
2964
+ components: block.components ?? [],
2965
+ tags: block.tags ?? [],
2966
+ code: block.code ?? ""
2967
+ };
2968
+ }
2969
+ function tokensFromCompiledTokenData(tokens) {
2970
+ const flat = [];
2971
+ const categories = {};
2972
+ for (const [category, entries] of Object.entries(tokens.categories)) {
2973
+ const normalized = entries.map((entry) => ({
2974
+ name: entry.name,
2975
+ category,
2976
+ value: valueToString(entry.value),
2977
+ description: entry.description
2978
+ }));
2979
+ categories[category] = normalized;
2980
+ flat.push(...normalized);
2981
+ }
2982
+ return {
2983
+ prefix: tokens.prefix,
2984
+ total: tokens.total,
2985
+ categories,
2986
+ flat
2987
+ };
2988
+ }
2989
+ function buildCapabilities(args) {
2990
+ const capabilities = /* @__PURE__ */ new Set();
2991
+ if (Object.keys(args.components).length > 0) {
2992
+ capabilities.add("components");
2993
+ }
2994
+ if (args.tokens && args.tokens.total > 0) {
2995
+ capabilities.add("tokens");
2996
+ }
2997
+ if (args.blocks && Object.keys(args.blocks).length > 0) {
2998
+ capabilities.add("blocks");
2999
+ }
3000
+ if (args.graph) {
3001
+ capabilities.add("graph");
3002
+ }
3003
+ const hasPerformance = Boolean(args.performanceSummary) || Object.values(args.components).some((component) => Boolean(component.performance));
3004
+ if (hasPerformance) {
3005
+ capabilities.add("performance");
3006
+ }
3007
+ return Array.from(capabilities);
3008
+ }
3009
+ function validateSnapshot(snapshot) {
3010
+ return mcpSnapshotSchema.parse(snapshot);
3011
+ }
3012
+
3013
+ // src/adapters/fragments-json.ts
3014
+ var FragmentsJsonAdapter = class {
3015
+ name = "fragments-json";
3016
+ discover(startDir) {
3017
+ return findFragmentsJson(startDir);
3018
+ }
3019
+ async load(projectRoot) {
3020
+ const paths = this.discover(projectRoot);
3021
+ if (paths.length === 0) {
3022
+ throw new Error(
3023
+ `No ${BRAND.outFile} found. Searched ${projectRoot} and package.json dependencies.
3024
+
3025
+ Fix: Add a project-level MCP config so the server runs from your workspace root:
3026
+
3027
+ Cursor: .cursor/mcp.json
3028
+ VS Code: .vscode/mcp.json
3029
+ Claude: claude mcp add ${BRAND.nameLower} -- npx @fragments-sdk/mcp
3030
+ Windsurf: .windsurf/mcp.json
3031
+
3032
+ Or pass --project-root: npx @fragments-sdk/mcp -p /path/to/project
3033
+
3034
+ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
3035
+ );
3036
+ }
3037
+ const content = await readFile(paths[0], "utf-8");
3038
+ const primary = JSON.parse(content);
3039
+ if (!primary.blocks && primary.recipes) {
3040
+ primary.blocks = primary.recipes;
3041
+ }
3042
+ const packageMap = {};
3043
+ if (primary.packageName) {
3044
+ for (const name of Object.keys(primary.fragments)) {
3045
+ packageMap[name] = primary.packageName;
3046
+ }
3047
+ }
3048
+ for (let i = 1; i < paths.length; i++) {
3049
+ const extra = JSON.parse(await readFile(paths[i], "utf-8"));
3050
+ if (extra.packageName) {
3051
+ for (const name of Object.keys(extra.fragments)) {
3052
+ packageMap[name] = extra.packageName;
3053
+ }
3054
+ }
3055
+ Object.assign(primary.fragments, extra.fragments);
3056
+ const extraBlocks = extra.blocks ?? extra.recipes;
3057
+ if (extraBlocks) {
3058
+ primary.blocks = { ...primary.blocks, ...extraBlocks };
3059
+ }
3060
+ }
3061
+ const components = Object.fromEntries(
3062
+ Object.entries(primary.fragments).map(([key, fragment]) => [
3063
+ key,
3064
+ componentFromCompiledFragment({
3065
+ id: key,
3066
+ fragment,
3067
+ sourceType: "fragments-json",
3068
+ packageName: packageMap[fragment.meta.name] ?? primary.packageName,
3069
+ importPath: packageMap[fragment.meta.name] ?? primary.packageName
3070
+ })
3071
+ ])
3072
+ );
3073
+ const blocks = primary.blocks ? Object.fromEntries(
3074
+ Object.entries(primary.blocks).map(([key, block]) => [
3075
+ key,
3076
+ blockFromCompiledBlock(key, block)
3077
+ ])
3078
+ ) : void 0;
3079
+ const tokens = primary.tokens ? tokensFromCompiledTokenData(primary.tokens) : void 0;
3080
+ const snapshot = validateSnapshot({
3081
+ schemaVersion: 1,
3082
+ sourceType: "fragments-json",
3083
+ sourceLabel: BRAND.outFile,
3084
+ capabilities: buildCapabilities({
3085
+ components,
3086
+ blocks,
3087
+ tokens,
3088
+ graph: primary.graph,
3089
+ performanceSummary: primary.performanceSummary
3090
+ }),
3091
+ metadata: {
3092
+ designSystemName: primary.packageName,
3093
+ packageName: primary.packageName,
3094
+ importPath: primary.packageName
3095
+ },
3096
+ components,
3097
+ blocks,
3098
+ tokens,
3099
+ graph: primary.graph,
3100
+ performanceSummary: primary.performanceSummary,
3101
+ packageMap,
3102
+ defaultPackageName: primary.packageName
3103
+ });
3104
+ return {
3105
+ snapshot,
3106
+ components: snapshot.components,
3107
+ blocks: snapshot.blocks,
3108
+ tokens: snapshot.tokens,
3109
+ graph: primary.graph,
3110
+ performanceSummary: primary.performanceSummary,
3111
+ packageMap: snapshot.packageMap,
3112
+ defaultPackageName: snapshot.defaultPackageName,
3113
+ capabilities: new Set(snapshot.capabilities)
3114
+ };
3115
+ }
3116
+ };
3117
+
3118
+ // src/adapters/auto-extract.ts
3119
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3120
+ import { join as join6, relative, sep } from "path";
3121
+
3122
+ // src/adapters/discover-components.ts
3123
+ import { readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
3124
+ import { join as join4, extname, basename } from "path";
3125
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
3126
+ "node_modules",
3127
+ "dist",
3128
+ "build",
3129
+ ".next",
3130
+ ".nuxt",
3131
+ "coverage",
3132
+ "__tests__",
3133
+ "__mocks__",
3134
+ ".git",
3135
+ ".cache",
3136
+ ".turbo",
3137
+ "out"
3138
+ ]);
3139
+ var EXCLUDED_PATTERNS = [
3140
+ /\.test\./,
3141
+ /\.spec\./,
3142
+ /\.stories\./,
3143
+ /\.story\./,
3144
+ /\.fragment\./,
3145
+ /\.d\.ts$/,
3146
+ /\.config\./,
3147
+ /\.mock\./,
3148
+ /\.fixture\./
3149
+ ];
3150
+ function discoverComponentFiles(projectRoot) {
3151
+ const results = [];
3152
+ const seen = /* @__PURE__ */ new Set();
3153
+ const scanDirs = [
3154
+ "src/components",
3155
+ "components",
3156
+ "lib/components",
3157
+ "src/ui",
3158
+ "lib/ui",
3159
+ "packages"
3160
+ ].map((d) => join4(projectRoot, d)).filter((d) => existsSync4(d));
3161
+ if (scanDirs.length === 0) {
3162
+ const srcDir = join4(projectRoot, "src");
3163
+ if (existsSync4(srcDir)) scanDirs.push(srcDir);
3164
+ }
3165
+ for (const dir of scanDirs) {
3166
+ walkDir(dir, results, seen);
3167
+ }
3168
+ return results;
3169
+ }
3170
+ function walkDir(dir, results, seen, depth = 0) {
3171
+ if (depth > 6) return;
3172
+ let entries;
3173
+ try {
3174
+ entries = readdirSync2(dir, { withFileTypes: true });
3175
+ } catch {
3176
+ return;
3177
+ }
3178
+ for (const entry of entries) {
3179
+ if (entry.name.startsWith(".")) continue;
3180
+ if (entry.isDirectory()) {
3181
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
3182
+ walkDir(join4(dir, entry.name), results, seen, depth + 1);
3183
+ continue;
3184
+ }
3185
+ if (!entry.isFile()) continue;
3186
+ const ext = extname(entry.name);
3187
+ if (ext !== ".tsx" && ext !== ".jsx") continue;
3188
+ if (EXCLUDED_PATTERNS.some((p) => p.test(entry.name))) continue;
3189
+ const filePath = join4(dir, entry.name);
3190
+ if (seen.has(filePath)) continue;
3191
+ seen.add(filePath);
3192
+ const name = inferComponentName(entry.name, dir);
3193
+ if (name) {
3194
+ results.push({ filePath, componentName: name });
3195
+ }
3196
+ }
3197
+ }
3198
+ function inferComponentName(fileName, dirPath) {
3199
+ const withoutExt = fileName.replace(/\.(tsx|jsx)$/, "");
3200
+ if (withoutExt === "index") {
3201
+ return basename(dirPath);
3202
+ }
3203
+ return withoutExt;
3204
+ }
3205
+
3206
+ // src/adapters/scan-tokens.ts
3207
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
3208
+ import { join as join5, extname as extname2 } from "path";
3209
+ function scanTokens(projectRoot) {
3210
+ const cssFiles = discoverCssFiles(projectRoot);
3211
+ if (cssFiles.length === 0) return void 0;
3212
+ const allTokens = [];
3213
+ let prefix = "";
3214
+ for (const filePath of cssFiles) {
3215
+ try {
3216
+ const content = readFileSync4(filePath, "utf-8");
3217
+ const tokens = extractCustomProperties(content);
3218
+ allTokens.push(...tokens);
3219
+ } catch {
3220
+ continue;
3221
+ }
3222
+ }
3223
+ if (allTokens.length === 0) return void 0;
3224
+ prefix = detectPrefix(allTokens.map((t) => t.name));
3225
+ const categories = {};
3226
+ for (const token of allTokens) {
3227
+ const category = inferTokenCategory(token.name);
3228
+ if (!categories[category]) categories[category] = [];
3229
+ if (!categories[category].some((t) => t.name === token.name)) {
3230
+ categories[category].push(token);
3231
+ }
3232
+ }
3233
+ return {
3234
+ prefix,
3235
+ total: Object.values(categories).reduce((sum, arr) => sum + arr.length, 0),
3236
+ categories
3237
+ };
3238
+ }
3239
+ function discoverCssFiles(projectRoot) {
3240
+ const files = [];
3241
+ const searchDirs = [
3242
+ "src",
3243
+ "styles",
3244
+ "css",
3245
+ "app"
3246
+ ].map((d) => join5(projectRoot, d)).filter((d) => existsSync5(d));
3247
+ searchDirs.push(projectRoot);
3248
+ for (const dir of searchDirs) {
3249
+ try {
3250
+ const entries = readdirSync3(dir, { withFileTypes: true });
3251
+ for (const entry of entries) {
3252
+ if (!entry.isFile()) continue;
3253
+ const ext = extname2(entry.name);
3254
+ if (ext === ".css" || ext === ".scss") {
3255
+ files.push(join5(dir, entry.name));
3256
+ }
3257
+ }
3258
+ } catch {
3259
+ continue;
3260
+ }
3261
+ }
3262
+ const srcDir = join5(projectRoot, "src");
3263
+ if (existsSync5(srcDir)) {
3264
+ try {
3265
+ for (const subEntry of readdirSync3(srcDir, { withFileTypes: true })) {
3266
+ if (subEntry.isDirectory() && ["styles", "css", "theme", "tokens"].includes(subEntry.name)) {
3267
+ const subDir = join5(srcDir, subEntry.name);
3268
+ for (const file of readdirSync3(subDir, { withFileTypes: true })) {
3269
+ if (file.isFile() && (file.name.endsWith(".css") || file.name.endsWith(".scss"))) {
3270
+ files.push(join5(subDir, file.name));
3271
+ }
3272
+ }
3273
+ }
3274
+ }
3275
+ } catch {
3276
+ }
3277
+ }
3278
+ return [...new Set(files)];
3279
+ }
3280
+ function extractCustomProperties(content) {
3281
+ const tokens = [];
3282
+ let inRelevantBlock = false;
3283
+ let braceDepth = 0;
3284
+ const relevantSelectors = [":root", ".dark", "[data-theme", "@theme"];
3285
+ const lines = content.split("\n");
3286
+ for (const line of lines) {
3287
+ const trimmed = line.trim();
3288
+ if (!inRelevantBlock && braceDepth === 0) {
3289
+ if (relevantSelectors.some((sel) => trimmed.startsWith(sel) || trimmed.includes(sel))) {
3290
+ if (trimmed.includes("{")) {
3291
+ inRelevantBlock = true;
3292
+ braceDepth = 1;
3293
+ }
3294
+ }
3295
+ }
3296
+ if (inRelevantBlock) {
3297
+ for (const ch of trimmed) {
3298
+ if (ch === "{") braceDepth++;
3299
+ else if (ch === "}") {
3300
+ braceDepth--;
3301
+ if (braceDepth <= 0) {
3302
+ inRelevantBlock = false;
3303
+ braceDepth = 0;
3304
+ }
3305
+ }
3306
+ }
3307
+ }
3308
+ if (!inRelevantBlock && braceDepth === 0) continue;
3309
+ const match = trimmed.match(/^(--[\w-]+)\s*:\s*(.+?)\s*;/);
3310
+ if (match) {
3311
+ const [, name, value] = match;
3312
+ const commentMatch = value.match(/\/\*\s*(.+?)\s*\*\//);
3313
+ const cleanValue = value.replace(/\/\*.*?\*\//, "").trim();
3314
+ tokens.push({
3315
+ name,
3316
+ value: cleanValue,
3317
+ description: commentMatch?.[1]
3318
+ });
3319
+ }
3320
+ }
3321
+ return tokens;
3322
+ }
3323
+ function detectPrefix(names) {
3324
+ if (names.length === 0) return "--";
3325
+ const stripped = names.map((n) => n.slice(2));
3326
+ let prefix = "";
3327
+ const first = stripped[0];
3328
+ for (let i = 0; i < first.length; i++) {
3329
+ const ch = first[i];
3330
+ if (stripped.every((n) => n[i] === ch)) {
3331
+ prefix += ch;
3332
+ } else {
3333
+ break;
3334
+ }
3335
+ }
3336
+ const lastDash = prefix.lastIndexOf("-");
3337
+ if (lastDash > 0) {
3338
+ return "--" + prefix.slice(0, lastDash + 1);
3339
+ }
3340
+ return "--";
3341
+ }
3342
+ function inferTokenCategory(name) {
3343
+ const n = name.toLowerCase();
3344
+ if (n.includes("color") || n.includes("background") || n.includes("foreground") || n.includes("primary") || n.includes("secondary") || n.includes("accent") || n.includes("muted") || n.includes("destructive") || n.includes("popover") || n.includes("card") && !n.includes("card-") || n.includes("chart")) {
3345
+ return "colors";
3346
+ }
3347
+ if (n.includes("font") || n.includes("text") || n.includes("letter") || n.includes("line-height")) {
3348
+ return "typography";
3349
+ }
3350
+ if (n.includes("space") || n.includes("gap") || n.includes("padding") || n.includes("margin")) {
3351
+ return "spacing";
3352
+ }
3353
+ if (n.includes("radius")) {
3354
+ return "radius";
3355
+ }
3356
+ if (n.includes("shadow")) {
3357
+ return "shadows";
3358
+ }
3359
+ if (n.includes("border")) {
3360
+ return "borders";
3361
+ }
3362
+ if (n.includes("ring")) {
3363
+ return "focus";
3364
+ }
3365
+ if (n.includes("sidebar")) {
3366
+ return "sidebar";
3367
+ }
3368
+ if (n.includes("input")) {
3369
+ return "forms";
3370
+ }
3371
+ return "other";
3372
+ }
3373
+
3374
+ // src/adapters/auto-extract.ts
3375
+ var AutoExtractionAdapter = class {
3376
+ name = "auto-extract";
3377
+ discover(startDir) {
3378
+ return discoverComponentFiles(startDir).map((f) => f.filePath);
3379
+ }
3380
+ async load(projectRoot) {
3381
+ let extractMod;
3382
+ try {
3383
+ extractMod = await loadExtractorModule();
3384
+ } catch (e) {
3385
+ throw new Error(
3386
+ "Auto-extraction requires @fragments-sdk/extract and TypeScript.\n\nIf you see this error, the MCP server may not be installed correctly.\nAlternative: pre-build your design system with `npx fragments build`"
3387
+ );
3388
+ }
3389
+ const tsconfigPath = findTsConfig(projectRoot);
3390
+ const extractor = extractMod.createComponentExtractor(tsconfigPath ?? void 0);
3391
+ try {
3392
+ const discovered = discoverComponentFiles(projectRoot);
3393
+ if (discovered.length === 0) {
3394
+ throw new Error(
3395
+ `No component files found in ${projectRoot}.
3396
+ Searched: src/components/, components/, lib/components/, src/ui/
3397
+
3398
+ If your components are elsewhere, create a fragments.json with:
3399
+ npx fragments build`
3400
+ );
3401
+ }
3402
+ const components = {};
3403
+ const packageMap = /* @__PURE__ */ new Map();
3404
+ const defaultPackageName = readPackageName(projectRoot);
3405
+ let extractedCount = 0;
3406
+ const fileToComponents = /* @__PURE__ */ new Map();
3407
+ for (const { filePath, componentName } of discovered) {
3408
+ try {
3409
+ const metas = extractor.extractAll(filePath);
3410
+ for (const meta of metas) {
3411
+ const fragment = mapToCompiledFragment(meta, filePath, projectRoot);
3412
+ components[meta.name] = fragment;
3413
+ if (defaultPackageName) {
3414
+ packageMap.set(meta.name, defaultPackageName);
3415
+ }
3416
+ extractedCount++;
3417
+ const relPath = relative(projectRoot, filePath);
3418
+ if (!fileToComponents.has(relPath)) fileToComponents.set(relPath, []);
3419
+ fileToComponents.get(relPath).push(meta.name);
3420
+ }
3421
+ if (metas.length === 0) {
3422
+ const meta = extractor.extract(filePath, componentName);
3423
+ if (meta) {
3424
+ const fragment = mapToCompiledFragment(meta, filePath, projectRoot);
3425
+ components[meta.name] = fragment;
3426
+ if (defaultPackageName) {
3427
+ packageMap.set(meta.name, defaultPackageName);
3428
+ }
3429
+ extractedCount++;
3430
+ const relPath = relative(projectRoot, filePath);
3431
+ if (!fileToComponents.has(relPath)) fileToComponents.set(relPath, []);
3432
+ fileToComponents.get(relPath).push(meta.name);
3433
+ }
3434
+ }
3435
+ } catch {
3436
+ continue;
3437
+ }
3438
+ }
3439
+ inferRelations(components, fileToComponents);
3440
+ console.error(`[fragments-mcp] Extracted ${extractedCount} components from ${discovered.length} files.`);
3441
+ if (extractedCount === 0) {
3442
+ throw new Error(
3443
+ `Found ${discovered.length} component files but could not extract any props.
3444
+ This usually means TypeScript cannot parse the files.
3445
+ Check that your tsconfig.json includes the component directories.`
3446
+ );
3447
+ }
3448
+ const tokens = scanTokens(projectRoot);
3449
+ if (tokens) {
3450
+ console.error(`[fragments-mcp] Found ${tokens.total} design tokens across ${Object.keys(tokens.categories).length} categories.`);
3451
+ }
3452
+ const snapshotComponents = Object.fromEntries(
3453
+ Object.entries(components).map(([key, fragment]) => [
3454
+ key,
3455
+ componentFromCompiledFragment({
3456
+ id: key,
3457
+ fragment,
3458
+ sourceType: "auto-extract",
3459
+ packageName: packageMap.get(fragment.meta.name) ?? defaultPackageName,
3460
+ importPath: packageMap.get(fragment.meta.name) ?? defaultPackageName
3461
+ })
3462
+ ])
3463
+ );
3464
+ const snapshotTokens = tokens ? tokensFromCompiledTokenData(tokens) : void 0;
3465
+ const packageMapRecord = Object.fromEntries(packageMap.entries());
3466
+ const snapshot = validateSnapshot({
3467
+ schemaVersion: 1,
3468
+ sourceType: "auto-extract",
3469
+ sourceLabel: "Auto-extracted source files",
3470
+ capabilities: buildCapabilities({
3471
+ components: snapshotComponents,
3472
+ tokens: snapshotTokens
3473
+ }),
3474
+ metadata: {
3475
+ packageName: defaultPackageName,
3476
+ importPath: defaultPackageName
3477
+ },
3478
+ components: snapshotComponents,
3479
+ tokens: snapshotTokens,
3480
+ packageMap: packageMapRecord,
3481
+ defaultPackageName
3482
+ });
3483
+ return {
3484
+ snapshot,
3485
+ components: snapshot.components,
3486
+ blocks: snapshot.blocks,
3487
+ tokens: snapshot.tokens,
3488
+ graph: void 0,
3489
+ performanceSummary: void 0,
3490
+ packageMap: snapshot.packageMap,
3491
+ defaultPackageName: snapshot.defaultPackageName,
3492
+ capabilities: new Set(snapshot.capabilities)
3493
+ };
3494
+ } finally {
3495
+ extractor.dispose();
3496
+ }
3497
+ }
3498
+ };
3499
+ var extractorModulePromise = null;
3500
+ async function loadExtractorModule() {
3501
+ if (!extractorModulePromise) {
3502
+ extractorModulePromise = import("./dist-V7D67NXS.js");
3503
+ }
3504
+ return extractorModulePromise;
3505
+ }
3506
+ var UNIVERSAL_INHERITED = /* @__PURE__ */ new Set(["children", "className", "id", "disabled"]);
3507
+ var FORM_INPUT_INHERITED = /* @__PURE__ */ new Set([
3508
+ "placeholder",
3509
+ "value",
3510
+ "defaultValue",
3511
+ "onChange",
3512
+ "name",
3513
+ "required",
3514
+ "autoFocus",
3515
+ "autoComplete",
3516
+ "checked",
3517
+ "defaultChecked",
3518
+ "type"
3519
+ ]);
3520
+ var LABEL_INHERITED = /* @__PURE__ */ new Set(["htmlFor"]);
3521
+ var INPUT_LIKE_NAMES = /input|select|textarea|checkbox|radio|switch|slider|combobox/i;
3522
+ var LABEL_LIKE_NAMES = /label/i;
3523
+ function getRelevantInheritedProps(componentName) {
3524
+ const allowed = new Set(UNIVERSAL_INHERITED);
3525
+ if (INPUT_LIKE_NAMES.test(componentName)) {
3526
+ for (const p of FORM_INPUT_INHERITED) allowed.add(p);
3527
+ }
3528
+ if (LABEL_LIKE_NAMES.test(componentName)) {
3529
+ for (const p of LABEL_INHERITED) allowed.add(p);
3530
+ }
3531
+ return allowed;
3532
+ }
3533
+ function mapToCompiledFragment(meta, filePath, projectRoot) {
3534
+ const relevantInherited = getRelevantInheritedProps(meta.name);
3535
+ const props = {};
3536
+ for (const [name, propMeta] of Object.entries(meta.props)) {
3537
+ if (propMeta.source === "inherited" && !relevantInherited.has(name)) continue;
3538
+ props[name] = {
3539
+ type: propMeta.type,
3540
+ description: propMeta.description ?? "",
3541
+ required: propMeta.required,
3542
+ ...propMeta.values && { values: propMeta.values },
3543
+ ...propMeta.default !== void 0 && { default: propMeta.default }
3544
+ };
3545
+ }
3546
+ const relativePath = relative(projectRoot, filePath);
3547
+ const category = inferCategory(relativePath);
3548
+ const importPath = buildImportPath(relativePath);
3549
+ const description = buildDescription(meta, props);
3550
+ const propsSummary = buildPropsSummary(props);
3551
+ const codeExample = buildCodeExample(meta.name, props);
3552
+ const fragmentMeta = {
3553
+ name: meta.name,
3554
+ description,
3555
+ category
3556
+ };
3557
+ return {
3558
+ filePath: relativePath,
3559
+ meta: fragmentMeta,
3560
+ usage: {
3561
+ when: [`Use ${meta.name} when you need a ${category} ${meta.name.toLowerCase()} element.`],
3562
+ whenNot: [],
3563
+ guidelines: importPath ? [`import { ${meta.name} } from "${importPath}"`] : []
3564
+ },
3565
+ props,
3566
+ propsSummary,
3567
+ variants: codeExample ? [{ name: "Default", description: `Basic ${meta.name} usage`, code: codeExample }] : [],
3568
+ sourcePath: relativePath,
3569
+ exportName: meta.name,
3570
+ _generated: {
3571
+ source: "extracted",
3572
+ verified: false,
3573
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3574
+ },
3575
+ ...meta.composition && {
3576
+ ai: {
3577
+ compositionPattern: meta.composition.pattern
3578
+ }
3579
+ }
3580
+ };
3581
+ }
3582
+ function buildImportPath(relativePath) {
3583
+ const withoutExt = relativePath.replace(/\.(tsx|jsx|ts|js)$/, "");
3584
+ if (withoutExt.startsWith(`src${sep}`)) {
3585
+ return "@/" + withoutExt.slice(4).split(sep).join("/");
3586
+ }
3587
+ return "./" + withoutExt.split(sep).join("/");
3588
+ }
3589
+ function buildDescription(meta, localProps) {
3590
+ if (meta.description && meta.description !== meta.name && !meta.description.endsWith(" component")) {
3591
+ return meta.description;
3592
+ }
3593
+ const propEntries = Object.entries(localProps);
3594
+ if (propEntries.length === 0) {
3595
+ return `${meta.name} component`;
3596
+ }
3597
+ const details = [];
3598
+ for (const [name, prop] of propEntries) {
3599
+ if (prop.values && prop.values.length > 0) {
3600
+ details.push(`${prop.values.length} ${name} options`);
3601
+ }
3602
+ }
3603
+ if (details.length > 0) {
3604
+ return `${meta.name} component with ${details.join(" and ")}.`;
3605
+ }
3606
+ return `${meta.name} component with ${propEntries.length} configurable prop${propEntries.length === 1 ? "" : "s"}.`;
3607
+ }
3608
+ function buildPropsSummary(props) {
3609
+ const summaries = [];
3610
+ for (const [name, prop] of Object.entries(props)) {
3611
+ let summary = `${name}`;
3612
+ if (prop.values && prop.values.length > 0) {
3613
+ summary += `: ${prop.values.map((v) => `"${v}"`).join(" | ")}`;
3614
+ } else {
3615
+ summary += `: ${prop.type}`;
3616
+ }
3617
+ if (prop.default !== void 0) {
3618
+ summary += ` (default: ${JSON.stringify(prop.default)})`;
3619
+ }
3620
+ if (prop.required) {
3621
+ summary += " (required)";
3622
+ }
3623
+ summaries.push(summary);
3624
+ }
3625
+ return summaries;
3626
+ }
3627
+ function buildCodeExample(name, props) {
3628
+ const propsStr = [];
3629
+ for (const [pName, prop] of Object.entries(props)) {
3630
+ if (pName === "children" || pName === "asChild" || pName === "className") continue;
3631
+ if (pName === "onChange" || pName === "onSubmit" || pName === "value" || pName === "defaultValue" || pName === "checked" || pName === "defaultChecked" || pName === "autoFocus" || pName === "autoComplete" || pName === "required" || pName === "disabled" || pName === "name") continue;
3632
+ if (prop.default !== void 0) {
3633
+ if (prop.values && prop.values.length > 1) {
3634
+ const nonDefault = prop.values.find((v) => v !== String(prop.default));
3635
+ if (nonDefault) {
3636
+ propsStr.push(`${pName}="${nonDefault}"`);
3637
+ continue;
3638
+ }
3639
+ }
3640
+ }
3641
+ if (pName === "placeholder" && INPUT_LIKE_NAMES.test(name)) {
3642
+ propsStr.push(`placeholder="Enter ${name.toLowerCase()}..."`);
3643
+ continue;
3644
+ }
3645
+ if (pName === "htmlFor" && LABEL_LIKE_NAMES.test(name)) {
3646
+ propsStr.push(`htmlFor="field-id"`);
3647
+ continue;
3648
+ }
3649
+ if (pName === "id" || pName === "className" || pName === "children") continue;
3650
+ if (prop.required) {
3651
+ if (prop.values && prop.values.length > 0) {
3652
+ propsStr.push(`${pName}="${prop.values[0]}"`);
3653
+ } else if (prop.type === "string") {
3654
+ propsStr.push(`${pName}="..."`);
3655
+ }
3656
+ }
3657
+ }
3658
+ const propsJsx = propsStr.length > 0 ? " " + propsStr.join(" ") : "";
3659
+ const nameLower = name.toLowerCase();
3660
+ const selfClosing = nameLower.includes("input") || nameLower.includes("separator") || nameLower.includes("slider") || nameLower.includes("switch");
3661
+ if (selfClosing) {
3662
+ return `<${name}${propsJsx} />`;
3663
+ }
3664
+ return `<${name}${propsJsx}>${name}</${name}>`;
3665
+ }
3666
+ function buildCompositionExample(root, subs) {
3667
+ const header = subs.filter((s) => s.includes("Header"));
3668
+ const title = subs.filter((s) => s.includes("Title"));
3669
+ const description = subs.filter((s) => s.includes("Description"));
3670
+ const content = subs.filter((s) => s.includes("Content") || s.includes("Body"));
3671
+ const footer = subs.filter((s) => s.includes("Footer"));
3672
+ const action = subs.filter((s) => s.includes("Action"));
3673
+ const other = subs.filter(
3674
+ (s) => !header.includes(s) && !title.includes(s) && !description.includes(s) && !content.includes(s) && !footer.includes(s) && !action.includes(s)
3675
+ );
3676
+ const lines = [`<${root}>`];
3677
+ if (header.length > 0) {
3678
+ lines.push(` <${header[0]}>`);
3679
+ for (const t of title) lines.push(` <${t}>Title</${t}>`);
3680
+ for (const d of description) lines.push(` <${d}>Description</${d}>`);
3681
+ lines.push(` </${header[0]}>`);
3682
+ } else {
3683
+ for (const t of title) lines.push(` <${t}>Title</${t}>`);
3684
+ for (const d of description) lines.push(` <${d}>Description</${d}>`);
3685
+ }
3686
+ for (const c of content) lines.push(` <${c}>Content</${c}>`);
3687
+ for (const o of other) lines.push(` <${o}>...</${o}>`);
3688
+ if (footer.length > 0) {
3689
+ lines.push(` <${footer[0]}>`);
3690
+ for (const a of action) lines.push(` <${a}>Action</${a}>`);
3691
+ lines.push(` </${footer[0]}>`);
3692
+ } else {
3693
+ for (const a of action) lines.push(` <${a}>Action</${a}>`);
3694
+ }
3695
+ lines.push(`</${root}>`);
3696
+ return lines.join("\n");
3697
+ }
3698
+ function inferRelations(components, fileToComponents) {
3699
+ for (const [_file, names] of fileToComponents) {
3700
+ if (names.length <= 1) continue;
3701
+ const sorted = [...names].sort((a, b) => a.length - b.length);
3702
+ const root = sorted[0];
3703
+ const subs = sorted.slice(1).filter((n) => n.startsWith(root));
3704
+ if (subs.length === 0) continue;
3705
+ const rootComp = components[root];
3706
+ if (rootComp) {
3707
+ rootComp.ai = {
3708
+ ...rootComp.ai,
3709
+ compositionPattern: "compound",
3710
+ subComponents: subs
3711
+ };
3712
+ rootComp.relations = subs.map((sub) => ({
3713
+ component: sub,
3714
+ relationship: "parent-of",
3715
+ note: `${sub} is a sub-component of ${root}`
3716
+ }));
3717
+ const compositionCode = buildCompositionExample(root, subs);
3718
+ rootComp.variants = [
3719
+ ...rootComp.variants ?? [],
3720
+ { name: "Composition", description: `${root} with all sub-components`, code: compositionCode }
3721
+ ];
3722
+ }
3723
+ const rootImportPath = rootComp?.usage?.guidelines?.[0];
3724
+ for (const sub of subs) {
3725
+ const subComp = components[sub];
3726
+ if (subComp) {
3727
+ subComp.relations = [{
3728
+ component: root,
3729
+ relationship: "child-of",
3730
+ note: `Use inside <${root}>`
3731
+ }];
3732
+ if (rootImportPath) {
3733
+ const allNames = [root, ...subs].join(", ");
3734
+ const fromPath = rootImportPath.match(/from\s+"([^"]+)"/)?.[1] ?? "";
3735
+ if (fromPath) {
3736
+ subComp.usage = {
3737
+ ...subComp.usage,
3738
+ guidelines: [`import { ${allNames} } from "${fromPath}"`]
3739
+ };
3740
+ }
3741
+ }
3742
+ }
3743
+ }
3744
+ if (rootComp && rootComp.usage?.guidelines?.[0]) {
3745
+ const fromPath = rootComp.usage.guidelines[0].match(/from\s+"([^"]+)"/)?.[1] ?? "";
3746
+ if (fromPath) {
3747
+ const allNames = [root, ...subs].join(", ");
3748
+ rootComp.usage.guidelines = [`import { ${allNames} } from "${fromPath}"`];
3749
+ }
3750
+ }
3751
+ }
3752
+ }
3753
+ function inferCategory(relativePath) {
3754
+ const parts = relativePath.split(sep);
3755
+ const componentsIdx = parts.findIndex(
3756
+ (p) => p === "components" || p === "ui"
3757
+ );
3758
+ if (componentsIdx >= 0 && componentsIdx + 1 < parts.length - 1) {
3759
+ const nextPart = parts[componentsIdx + 1];
3760
+ if (/^[A-Z]/.test(nextPart)) return parts[componentsIdx] || "uncategorized";
3761
+ return nextPart;
3762
+ }
3763
+ return "uncategorized";
3764
+ }
3765
+ function findTsConfig(projectRoot) {
3766
+ const candidates = ["tsconfig.json", "tsconfig.app.json"];
3767
+ for (const name of candidates) {
3768
+ const p = join6(projectRoot, name);
3769
+ if (existsSync6(p)) return p;
3770
+ }
3771
+ return null;
3772
+ }
3773
+ function readPackageName(projectRoot) {
3774
+ try {
3775
+ const pkgPath = join6(projectRoot, "package.json");
3776
+ if (!existsSync6(pkgPath)) return void 0;
3777
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
3778
+ return pkg.name;
3779
+ } catch {
3780
+ return void 0;
3781
+ }
3782
+ }
3783
+
3784
+ // src/adapters/cloud-catalog.ts
3785
+ var DEFAULT_CLOUD_URL = "https://app.usefragments.com/api/catalog";
3786
+ var TOKEN_CATEGORY_ALIASES2 = {
3787
+ color: ["color", "colors", "accent", "background", "foreground", "danger", "brand"],
3788
+ spacing: ["spacing", "space", "padding", "margin", "gap", "inset"],
3789
+ typography: ["typography", "font", "text", "copy", "line-height", "letter"],
3790
+ border: ["border", "borders", "stroke", "outline"],
3791
+ radius: ["radius", "radii", "corner", "corners", "rounded"],
3792
+ shadow: ["shadow", "shadows", "elevation"],
3793
+ layout: ["layout", "grid", "container", "breakpoint"],
3794
+ focus: ["focus", "ring", "focus-ring"],
3795
+ surface: ["surface", "surfaces", "canvas", "card", "background"]
3796
+ };
3797
+ function normalizeCatalogUrl(url) {
3798
+ if (!url) return DEFAULT_CLOUD_URL;
3799
+ if (url.endsWith("/api/catalog")) return url;
3800
+ return `${url.replace(/\/+$/, "")}/api/catalog`;
3801
+ }
3802
+ function normalizeValue(value) {
3803
+ return value?.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim() ?? "";
3804
+ }
3805
+ function canonicalizeTokenCategory(token) {
3806
+ const candidates = [
3807
+ token.category,
3808
+ token.path?.[0],
3809
+ token.name.split(/[.:/-]/)[0]
3810
+ ].map(normalizeValue).filter(Boolean);
3811
+ for (const candidate of candidates) {
3812
+ for (const [canonical, aliases] of Object.entries(
3813
+ TOKEN_CATEGORY_ALIASES2
3814
+ )) {
3815
+ if (candidate === canonical || aliases.some(
3816
+ (alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
3817
+ )) {
3818
+ return canonical;
3819
+ }
3820
+ }
3821
+ }
3822
+ return candidates[0] || "other";
3823
+ }
3824
+ function groupTokens(flat) {
3825
+ const categories = {};
3826
+ const normalizedFlat = (flat ?? []).map((token) => {
3827
+ const category = canonicalizeTokenCategory(token);
3828
+ const normalized = {
3829
+ name: token.name,
3830
+ category,
3831
+ value: token.value,
3832
+ description: token.description,
3833
+ path: token.path,
3834
+ type: token.type
3835
+ };
3836
+ if (!categories[category]) {
3837
+ categories[category] = [];
3838
+ }
3839
+ categories[category].push(normalized);
3840
+ return normalized;
3841
+ });
3842
+ return {
3843
+ prefix: "",
3844
+ total: normalizedFlat.length,
3845
+ categories,
3846
+ flat: normalizedFlat
3847
+ };
3848
+ }
3849
+ function mapComponent(component, designSystem) {
3850
+ return {
3851
+ id: component.componentKey,
3852
+ name: component.name,
3853
+ description: component.description ?? "",
3854
+ category: component.category ?? "uncategorized",
3855
+ status: component.status ?? "stable",
3856
+ tags: [],
3857
+ props: Object.fromEntries(
3858
+ Object.entries(component.props ?? {}).map(([propName, prop]) => [
3859
+ propName,
3860
+ {
3861
+ type: prop.type ?? "unknown",
3862
+ description: prop.description ?? "",
3863
+ required: prop.required ?? false,
3864
+ default: prop.default,
3865
+ values: prop.values
3866
+ }
3867
+ ])
3868
+ ),
3869
+ propsSummary: Object.entries(component.props ?? {}).map(
3870
+ ([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type ?? "unknown"}`
3871
+ ),
3872
+ examples: (component.examples ?? []).map((example) => ({
3873
+ name: example.title ?? "Example",
3874
+ description: example.kind,
3875
+ code: example.code,
3876
+ kind: example.kind
3877
+ })),
3878
+ relations: (component.relations ?? []).map((relation) => ({
3879
+ componentName: relation.component ?? relation.componentKey ?? "Unknown",
3880
+ componentId: relation.componentKey,
3881
+ relationship: relation.relationship ?? relation.type ?? "related",
3882
+ note: relation.note,
3883
+ description: relation.description
3884
+ })),
3885
+ compoundChildren: (component.compoundChildren ?? []).filter((child) => child.subcomponentVisibility !== "internal").map((child) => ({
3886
+ name: child.name,
3887
+ componentId: child.componentKey,
3888
+ description: child.description,
3889
+ required: child.required,
3890
+ accepts: child.accepts,
3891
+ visibility: child.subcomponentVisibility === "internal" ? "internal" : "public"
3892
+ })),
3893
+ guidance: {
3894
+ when: component.usageGuidance ? [component.usageGuidance] : [],
3895
+ whenNot: component.donts ?? [],
3896
+ guidelines: component.usageGuidance ? [component.usageGuidance] : [],
3897
+ accessibility: [],
3898
+ usageGuidance: component.usageGuidance,
3899
+ dos: component.dos ?? [],
3900
+ donts: component.donts ?? [],
3901
+ patterns: []
3902
+ },
3903
+ sourceType: "cloud",
3904
+ sourcePath: component.sourcePath,
3905
+ sourceRepoFullName: component.sourceRepoFullName ?? void 0,
3906
+ packageName: designSystem?.packageName ?? void 0,
3907
+ importPath: designSystem?.importPath ?? designSystem?.packageName ?? void 0,
3908
+ publicRef: component.publicRef,
3909
+ publicSlug: component.publicSlug ?? null,
3910
+ isCanonical: component.isCanonical ?? false,
3911
+ tier: component.tier,
3912
+ parentComponentId: component.parentComponentKey,
3913
+ parentComponentName: component.parentComponentName,
3914
+ metadata: {
3915
+ a11yRules: [],
3916
+ scenarioTags: []
3917
+ }
3918
+ };
3919
+ }
3920
+ var CloudCatalogAdapter = class {
3921
+ constructor(options) {
3922
+ this.options = options;
3923
+ }
3924
+ name = "cloud";
3925
+ async load(_projectRoot) {
3926
+ const response = await fetch(normalizeCatalogUrl(this.options.url), {
3927
+ headers: {
3928
+ "X-API-Key": this.options.apiKey
3929
+ }
3930
+ });
3931
+ if (!response.ok) {
3932
+ throw new Error(
3933
+ `Failed to load Cloud catalog (${response.status} ${response.statusText}).`
3934
+ );
3935
+ }
3936
+ const raw = await response.json();
3937
+ const components = Object.fromEntries(
3938
+ (raw.components ?? []).map((component) => [
3939
+ component.componentKey,
3940
+ mapComponent(component, raw.designSystem)
3941
+ ])
3942
+ );
3943
+ const tokens = raw.tokens?.flat ? groupTokens(raw.tokens.flat) : void 0;
3944
+ const packageName = raw.designSystem?.packageName ?? void 0;
3945
+ const importPath = raw.designSystem?.importPath ?? raw.designSystem?.packageName ?? void 0;
3946
+ const packageMap = packageName ? Object.fromEntries(
3947
+ Object.values(components).map((component) => [
3948
+ component.name,
3949
+ packageName
3950
+ ])
3951
+ ) : {};
3952
+ const snapshot = validateSnapshot({
3953
+ schemaVersion: 1,
3954
+ sourceType: "cloud",
3955
+ sourceLabel: "Fragments Cloud catalog",
3956
+ capabilities: buildCapabilities({
3957
+ components,
3958
+ tokens
3959
+ }),
3960
+ metadata: {
3961
+ designSystemName: raw.designSystem?.name ?? raw.org.name,
3962
+ packageName,
3963
+ importPath,
3964
+ revision: raw.revision,
3965
+ updatedAt: typeof raw.updatedAt === "number" ? new Date(raw.updatedAt).toISOString() : void 0
3966
+ },
3967
+ components,
3968
+ tokens,
3969
+ packageMap,
3970
+ defaultPackageName: packageName
3971
+ });
3972
+ const hydratedComponents = Object.fromEntries(
3973
+ Object.entries(snapshot.components).map(([componentId, component]) => [
3974
+ componentId,
3975
+ {
3976
+ ...component,
3977
+ isCanonical: components[componentId]?.isCanonical ?? false
3978
+ }
3979
+ ])
3980
+ );
3981
+ return {
3982
+ snapshot: {
3983
+ ...snapshot,
3984
+ components: hydratedComponents
3985
+ },
3986
+ components: hydratedComponents,
3987
+ blocks: snapshot.blocks,
3988
+ tokens: snapshot.tokens,
3989
+ graph: void 0,
3990
+ performanceSummary: void 0,
3991
+ packageMap: snapshot.packageMap,
3992
+ defaultPackageName: snapshot.defaultPackageName,
3993
+ capabilities: new Set(snapshot.capabilities)
3994
+ };
3995
+ }
3996
+ };
3997
+
3998
+ // src/adapters/bundle.ts
3999
+ import { existsSync as existsSync7 } from "fs";
4000
+ import { readFile as readFile2 } from "fs/promises";
4001
+ import { dirname as dirname3, resolve as resolve2 } from "path";
4002
+ import {
4003
+ bundleComponentShardSchema,
4004
+ bundleManifestSchema,
4005
+ bundleTokenFileSchema
4006
+ } from "@fragments-sdk/core";
4007
+ async function readJsonFile(path, parser, label) {
4008
+ const content = await readFile2(path, "utf-8");
4009
+ try {
4010
+ return parser.parse(JSON.parse(content));
4011
+ } catch (error) {
4012
+ throw new Error(
4013
+ `Invalid ${label} at ${path}: ${error instanceof Error ? error.message : String(error)}`
4014
+ );
4015
+ }
4016
+ }
4017
+ function normalizeProps(props) {
4018
+ return Object.fromEntries(
4019
+ Object.entries(props ?? {}).map(([propName, value]) => {
4020
+ const prop = value && typeof value === "object" ? value : {};
4021
+ return [
4022
+ propName,
4023
+ {
4024
+ type: typeof prop.type === "string" && prop.type.length > 0 ? prop.type : "unknown",
4025
+ description: typeof prop.description === "string" ? prop.description : "",
4026
+ required: Boolean(prop.required),
4027
+ default: prop.default,
4028
+ values: Array.isArray(prop.values) ? prop.values.filter((entry) => typeof entry === "string") : void 0,
4029
+ constraints: Array.isArray(prop.constraints) ? prop.constraints.filter(
4030
+ (entry) => typeof entry === "string"
4031
+ ) : void 0
4032
+ }
4033
+ ];
4034
+ })
4035
+ );
4036
+ }
4037
+ function normalizeExamples(examples) {
4038
+ return examples.map((example) => {
4039
+ const record = example && typeof example === "object" ? example : {};
4040
+ return {
4041
+ name: typeof record.title === "string" && record.title || typeof record.name === "string" && record.name || "Example",
4042
+ description: typeof record.description === "string" ? record.description : void 0,
4043
+ code: typeof record.code === "string" ? record.code : void 0,
4044
+ kind: typeof record.kind === "string" ? record.kind : void 0
4045
+ };
4046
+ });
4047
+ }
4048
+ function normalizeRelations(relations, fallback) {
4049
+ if (relations.length > 0) {
4050
+ return relations.map((relation) => {
4051
+ const record = relation && typeof relation === "object" ? relation : {};
4052
+ return {
4053
+ componentName: typeof record.component === "string" && record.component || typeof record.name === "string" && record.name || typeof record.componentName === "string" && record.componentName || typeof record.componentId === "string" && record.componentId || "Unknown",
4054
+ componentId: typeof record.componentId === "string" ? record.componentId : void 0,
4055
+ relationship: typeof record.relationship === "string" && record.relationship || typeof record.type === "string" && record.type || "related",
4056
+ note: typeof record.note === "string" ? record.note : void 0,
4057
+ description: typeof record.description === "string" ? record.description : void 0
4058
+ };
4059
+ });
4060
+ }
4061
+ return fallback.map((relation) => ({
4062
+ componentName: relation.name,
4063
+ componentId: relation.componentId,
4064
+ relationship: relation.type
4065
+ }));
4066
+ }
4067
+ function buildComponent(manifest, entry, shard) {
4068
+ const sourcePackage = manifest.designSystem.importPath ?? manifest.designSystem.packageName ?? void 0;
4069
+ const component = shard.component;
4070
+ const componentRecord = component;
4071
+ const props = normalizeProps(component.props);
4072
+ return {
4073
+ id: shard.componentId,
4074
+ name: component.name,
4075
+ description: component.description,
4076
+ category: component.category,
4077
+ status: component.status,
4078
+ tags: [],
4079
+ props,
4080
+ propsSummary: Object.entries(props).map(
4081
+ ([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type}`
4082
+ ),
4083
+ examples: normalizeExamples(component.examples),
4084
+ relations: normalizeRelations(component.relations, entry.relations),
4085
+ compoundChildren: entry.compoundChildren.map((child) => ({
4086
+ name: child.name,
4087
+ componentId: child.componentId,
4088
+ visibility: "public"
4089
+ })),
4090
+ guidance: {
4091
+ when: component.usageGuidance ? [component.usageGuidance] : [],
4092
+ whenNot: component.donts,
4093
+ guidelines: component.usageGuidance ? [component.usageGuidance] : [],
4094
+ accessibility: [],
4095
+ usageGuidance: component.usageGuidance || void 0,
4096
+ dos: component.dos,
4097
+ donts: component.donts,
4098
+ patterns: []
4099
+ },
4100
+ sourceType: "bundle",
4101
+ sourcePath: component.sourcePath,
4102
+ sourceRepoFullName: component.sourceRepoFullName,
4103
+ packageName: manifest.designSystem.packageName ?? void 0,
4104
+ importPath: sourcePackage,
4105
+ publicRef: component.publicRef,
4106
+ publicSlug: component.publicSlug,
4107
+ isCanonical: componentRecord.isCanonical ?? component.tier === "core",
4108
+ tier: component.tier,
4109
+ parentComponentName: component.parentComponentName,
4110
+ metadata: {
4111
+ a11yRules: [],
4112
+ scenarioTags: []
4113
+ }
4114
+ };
4115
+ }
4116
+ var BundleAdapter = class {
4117
+ name = "bundle";
4118
+ discover(startDir) {
4119
+ return findBundleManifest(startDir);
4120
+ }
4121
+ async load(projectRoot) {
4122
+ const manifests = this.discover(projectRoot);
4123
+ if (manifests.length === 0) {
4124
+ throw new Error(
4125
+ `No ${BRAND.dataDir}/${BRAND.manifestFile} found. Run \`${BRAND.cliCommand} context install --cloud\` or commit a Fragments bundle into your workspace.`
4126
+ );
4127
+ }
4128
+ const manifestPath = manifests[0];
4129
+ const manifest = await readJsonFile(
4130
+ manifestPath,
4131
+ bundleManifestSchema,
4132
+ "bundle manifest"
4133
+ );
4134
+ const bundleDir = dirname3(manifestPath);
4135
+ const repoRoot = dirname3(bundleDir);
4136
+ const tokensPath = resolve2(bundleDir, "tokens.json");
4137
+ const tokensFile = existsSync7(tokensPath) ? await readJsonFile(tokensPath, bundleTokenFileSchema, "bundle tokens") : void 0;
4138
+ const components = Object.fromEntries(
4139
+ await Promise.all(
4140
+ Object.values(manifest.components).map(async (entry) => {
4141
+ const shardPath = resolve2(repoRoot, entry.file);
4142
+ const shard = await readJsonFile(
4143
+ shardPath,
4144
+ bundleComponentShardSchema,
4145
+ `bundle component shard (${entry.name})`
4146
+ );
4147
+ return [entry.componentId, buildComponent(manifest, entry, shard)];
4148
+ })
4149
+ )
4150
+ );
4151
+ const packageName = manifest.designSystem.importPath ?? manifest.designSystem.packageName ?? void 0;
4152
+ const packageMap = packageName ? Object.fromEntries(
4153
+ Object.values(components).map((component) => [component.name, packageName])
4154
+ ) : {};
4155
+ const tokens = tokensFile ? {
4156
+ prefix: "",
4157
+ total: tokensFile.flat.length,
4158
+ categories: Object.fromEntries(
4159
+ Object.entries(tokensFile.categories).map(([category, value]) => [
4160
+ category,
4161
+ value.tokens.map((token) => ({
4162
+ name: token.name,
4163
+ category: token.category,
4164
+ value: token.value,
4165
+ description: token.description,
4166
+ path: token.path,
4167
+ type: token.type
4168
+ }))
4169
+ ])
4170
+ ),
4171
+ flat: tokensFile.flat.map((token) => ({
4172
+ name: token.name,
4173
+ category: token.category,
4174
+ value: token.value,
4175
+ description: token.description,
4176
+ path: token.path,
4177
+ type: token.type
4178
+ }))
4179
+ } : void 0;
4180
+ const snapshot = validateSnapshot({
4181
+ schemaVersion: 1,
4182
+ sourceType: "bundle",
4183
+ sourceLabel: `${BRAND.dataDir}/${BRAND.manifestFile}`,
4184
+ capabilities: buildCapabilities({
4185
+ components,
4186
+ tokens
4187
+ }),
4188
+ metadata: {
4189
+ designSystemName: manifest.designSystem.name,
4190
+ packageName: manifest.designSystem.packageName ?? void 0,
4191
+ importPath: manifest.designSystem.importPath ?? void 0,
4192
+ revision: manifest.catalogRevision,
4193
+ updatedAt: manifest.catalogUpdatedAt
4194
+ },
4195
+ components,
4196
+ tokens,
4197
+ packageMap,
4198
+ defaultPackageName: packageName
4199
+ });
4200
+ const hydratedComponents = Object.fromEntries(
4201
+ Object.entries(snapshot.components).map(([componentId, component]) => [
4202
+ componentId,
4203
+ {
4204
+ ...component,
4205
+ isCanonical: components[componentId]?.isCanonical ?? component.tier === "core"
4206
+ }
4207
+ ])
4208
+ );
4209
+ return {
4210
+ snapshot: {
4211
+ ...snapshot,
4212
+ components: hydratedComponents
4213
+ },
4214
+ components: hydratedComponents,
4215
+ blocks: snapshot.blocks,
4216
+ tokens: snapshot.tokens,
4217
+ graph: snapshot.graph,
4218
+ performanceSummary: snapshot.performanceSummary,
4219
+ packageMap: snapshot.packageMap,
4220
+ defaultPackageName: snapshot.defaultPackageName,
4221
+ capabilities: new Set(snapshot.capabilities)
4222
+ };
4223
+ }
4224
+ };
4225
+
4226
+ // src/source-selection.ts
4227
+ function resolveCloudApiKey(config, fileConfig) {
4228
+ return config.cloudApiKey ?? fileConfig?.cloud?.apiKey ?? process.env.FRAGMENTS_API_KEY;
4229
+ }
4230
+ function resolveCloudUrl(fileConfig) {
4231
+ return fileConfig?.cloud?.url ?? process.env.FRAGMENTS_CLOUD_URL;
4232
+ }
4233
+ function hasTsProject(projectRoot) {
4234
+ return existsSync8(join7(projectRoot, "tsconfig.json")) || existsSync8(join7(projectRoot, "tsconfig.app.json"));
4235
+ }
4236
+ function resolveDataAdapter(config, fileConfig) {
4237
+ const source = config.source ?? fileConfig?.source ?? "auto";
4238
+ const fragmentsJsonPaths = findFragmentsJson(config.projectRoot);
4239
+ const bundleManifestPaths = findBundleManifest(config.projectRoot);
4240
+ const cloudApiKey = resolveCloudApiKey(config, fileConfig);
4241
+ const cloudUrl = resolveCloudUrl(fileConfig);
4242
+ switch (source) {
4243
+ case "fragments-json":
4244
+ return { adapter: new FragmentsJsonAdapter(), mode: "fragments-json" };
4245
+ case "cloud":
4246
+ if (!cloudApiKey) {
4247
+ throw new Error(
4248
+ "Cloud source requires a Cloud API key. Set FRAGMENTS_API_KEY or pass cloudApiKey."
4249
+ );
4250
+ }
4251
+ return {
4252
+ adapter: new CloudCatalogAdapter({ apiKey: cloudApiKey, url: cloudUrl }),
4253
+ mode: "cloud"
4254
+ };
4255
+ case "bundle":
4256
+ return { adapter: new BundleAdapter(), mode: "bundle" };
4257
+ case "extract":
4258
+ return { adapter: new AutoExtractionAdapter(), mode: "extract" };
4259
+ case "auto":
4260
+ default:
4261
+ if (fragmentsJsonPaths.length > 0) {
4262
+ return {
4263
+ adapter: new FragmentsJsonAdapter(),
4264
+ mode: "fragments-json"
4265
+ };
4266
+ }
4267
+ if (cloudApiKey) {
4268
+ return {
4269
+ adapter: new CloudCatalogAdapter({
4270
+ apiKey: cloudApiKey,
4271
+ url: cloudUrl
4272
+ }),
4273
+ mode: "cloud"
4274
+ };
4275
+ }
4276
+ if (bundleManifestPaths.length > 0) {
4277
+ return {
4278
+ adapter: new BundleAdapter(),
4279
+ mode: "bundle"
4280
+ };
4281
+ }
4282
+ if (hasTsProject(config.projectRoot)) {
4283
+ return { adapter: new AutoExtractionAdapter(), mode: "extract" };
4284
+ }
4285
+ return {
4286
+ adapter: new FragmentsJsonAdapter(),
4287
+ mode: "fragments-json"
4288
+ };
4289
+ }
4290
+ }
4291
+ function resolveSearchApiKey(config, fileConfig) {
4292
+ return config.searchApiKey ?? config.apiKey ?? fileConfig?.vectorSearch?.apiKey;
4293
+ }
4294
+
4295
+ // src/server.ts
4296
+ var TOOL_NAMES = buildToolNames();
4297
+ var TOOLS = buildMcpTools();
4298
+ var TOOL_DEFINITION_BY_KEY = new Map(
4299
+ MCP_TOOL_DEFINITIONS.map((definition) => [definition.key, definition])
4300
+ );
4301
+ function createMcpServer(config) {
4302
+ const server = new Server(
4303
+ {
4304
+ name: `${BRAND.nameLower}-mcp`,
4305
+ version: MCP_SERVER_VERSION
4306
+ },
4307
+ {
4308
+ capabilities: {
4309
+ tools: { listChanged: true }
4310
+ }
4311
+ }
4312
+ );
4313
+ const registry = new ToolRegistry("", {
4314
+ onChanged: () => {
4315
+ server.notification({ method: "notifications/tools/list_changed", params: {} });
4316
+ }
4317
+ });
4318
+ registry.registerBuiltins(
4319
+ { core: CORE_TOOLS, viewer: VIEWER_TOOLS, infra: INFRA_TOOLS },
4320
+ MCP_TOOL_DEFINITIONS,
4321
+ TOOL_CAPABILITIES
4322
+ );
4323
+ const fileConfig = config.fileConfig ?? loadConfigFile(config.projectRoot) ?? void 0;
4324
+ const mergedConfig = {
4325
+ ...fileConfig ? { ...config, fileConfig } : config,
4326
+ searchApiKey: resolveSearchApiKey(config, fileConfig)
4327
+ };
4328
+ const adapter = config.adapter ?? resolveDataAdapter(mergedConfig, fileConfig).adapter;
4329
+ config.onRegistry?.(registry);
4330
+ if (fileConfig?.tools?.exclude) {
4331
+ for (const key of fileConfig.tools.exclude) {
4332
+ registry.unregister(key);
4333
+ }
4334
+ }
4335
+ let cachedData = null;
4336
+ let loadDataPromise = null;
4337
+ let resolvedRoot = null;
4338
+ let resolveProjectRootPromise = null;
4339
+ let componentIndex = null;
4340
+ let blockIndex = null;
4341
+ let tokenIndex = null;
4342
+ async function resolveProjectRoot() {
4343
+ if (resolvedRoot) return resolvedRoot;
4344
+ if (resolveProjectRootPromise) return resolveProjectRootPromise;
4345
+ resolveProjectRootPromise = (async () => {
4346
+ try {
4347
+ const result = await server.listRoots();
4348
+ if (result.roots?.length > 0) {
4349
+ const rootUri = result.roots[0].uri;
4350
+ resolvedRoot = fileURLToPath(rootUri);
4351
+ return resolvedRoot;
4352
+ }
4353
+ } catch {
4354
+ }
4355
+ resolvedRoot = config.projectRoot;
4356
+ return resolvedRoot;
4357
+ })();
4358
+ try {
4359
+ return await resolveProjectRootPromise;
4360
+ } finally {
4361
+ resolveProjectRootPromise = null;
4362
+ }
4363
+ }
4364
+ async function loadData() {
4365
+ if (cachedData) return cachedData;
4366
+ if (loadDataPromise) return loadDataPromise;
4367
+ loadDataPromise = (async () => {
4368
+ const projectRoot = await resolveProjectRoot();
4369
+ const loaded = await adapter.load(projectRoot);
4370
+ const allFragments = Object.values(loaded.components);
4371
+ const allBlocks = Object.values(loaded.blocks ?? {});
4372
+ componentIndex = buildComponentIndex(allFragments);
4373
+ blockIndex = allBlocks.length > 0 ? buildBlockIndex(allBlocks) : null;
4374
+ tokenIndex = loaded.tokens && loaded.tokens.total > 0 ? buildTokenIndex(loaded.tokens) : null;
4375
+ cachedData = loaded;
4376
+ return loaded;
4377
+ })();
4378
+ try {
4379
+ return await loadDataPromise;
4380
+ } finally {
4381
+ loadDataPromise = null;
4382
+ }
4383
+ }
4384
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
4385
+ const data = await loadData();
4386
+ return {
4387
+ tools: registry.listTools(
4388
+ {
4389
+ hasViewer: !!config.viewerUrl,
4390
+ hasPlayground: !!(config.playgroundUrl ?? fileConfig?.playgroundUrl),
4391
+ capabilities: data.capabilities
4392
+ },
4393
+ TOOLS
4394
+ )
4395
+ };
4396
+ });
4397
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4398
+ const { name, arguments: args } = request.params;
4399
+ const data = await loadData();
4400
+ const toolContext = {
4401
+ data,
4402
+ config: mergedConfig,
4403
+ indexes: { componentIndex, blockIndex, tokenIndex },
4404
+ resolvePackageName: (name2) => {
4405
+ if (name2) {
4406
+ const pkg = data.packageMap[name2];
4407
+ if (pkg) return pkg;
4408
+ }
4409
+ if (data.defaultPackageName) return data.defaultPackageName;
4410
+ const root = resolvedRoot ?? config.projectRoot;
4411
+ const packageJsonPath = join8(root, "package.json");
4412
+ if (existsSync9(packageJsonPath)) {
4413
+ try {
4414
+ const content = readFileSync6(packageJsonPath, "utf-8");
4415
+ const pkg = JSON.parse(content);
4416
+ if (pkg.name) {
4417
+ return pkg.name;
4418
+ }
4419
+ } catch {
4420
+ }
4421
+ }
4422
+ return "your-component-library";
4423
+ },
4424
+ toolNames: TOOL_NAMES
4425
+ };
4426
+ try {
4427
+ const toolKey = registry.resolveKey(name);
4428
+ const definition = TOOL_DEFINITION_BY_KEY.get(toolKey);
4429
+ const argumentKeys = Object.keys(args ?? {});
4430
+ const allowedKeys = new Set(Object.keys(definition?.params ?? {}));
4431
+ const unknownKeys = definition ? argumentKeys.filter((key) => !allowedKeys.has(key)) : [];
4432
+ if (unknownKeys.length > 0) {
4433
+ return {
4434
+ content: [
4435
+ {
4436
+ type: "text",
4437
+ text: JSON.stringify({
4438
+ error: `Unknown argument(s) for ${toolKey}: ${unknownKeys.join(", ")}`
4439
+ })
4440
+ }
4441
+ ],
4442
+ isError: true
4443
+ };
4444
+ }
4445
+ const mCtx = {
4446
+ toolName: name,
4447
+ toolKey,
4448
+ args: args ?? {},
4449
+ ctx: toolContext
4450
+ };
4451
+ return await executeWithMiddleware(
4452
+ config.middleware ?? [],
4453
+ mCtx,
4454
+ () => registry.execute(name, args ?? {}, toolContext)
4455
+ );
4456
+ } catch (error) {
4457
+ return {
4458
+ content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) }],
4459
+ isError: true
4460
+ };
4461
+ }
4462
+ });
4463
+ return server;
4464
+ }
4465
+ async function startMcpServer(config) {
4466
+ const server = createMcpServer(config);
4467
+ const transport = new StdioServerTransport();
4468
+ await server.connect(transport);
4469
+ }
4470
+ function createSandboxServer() {
4471
+ return createMcpServer({ projectRoot: process.cwd() });
4472
+ }
4473
+
4474
+ export {
4475
+ loadConfigFile,
4476
+ SYNONYM_MAP,
4477
+ USE_CASE_TOKEN_CATEGORIES,
4478
+ MINIMUM_SCORE_THRESHOLD,
4479
+ BLOCK_BOOST_PER_OCCURRENCE,
4480
+ DEFAULT_ENDPOINTS,
4481
+ CORE_TOOLS,
4482
+ VIEWER_TOOLS,
4483
+ INFRA_TOOLS,
4484
+ BUILTIN_TOOLS,
4485
+ ToolRegistry,
4486
+ executeWithMiddleware,
4487
+ telemetryMiddleware,
4488
+ componentFromCompiledFragment,
4489
+ blockFromCompiledBlock,
4490
+ tokensFromCompiledTokenData,
4491
+ buildCapabilities,
4492
+ validateSnapshot,
4493
+ FragmentsJsonAdapter,
4494
+ AutoExtractionAdapter,
4495
+ resolveDataAdapter,
4496
+ resolveSearchApiKey,
4497
+ createMcpServer,
4498
+ startMcpServer,
4499
+ createSandboxServer
4500
+ };
4501
+ //# sourceMappingURL=chunk-HGGAXLRO.js.map