@fragments-sdk/mcp 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,10 @@
1
1
  import {
2
2
  AutoExtractionAdapter,
3
- BLOCK_BOOST_PER_OCCURRENCE,
4
3
  BUILTIN_TOOLS,
5
4
  CORE_TOOLS,
6
- DEFAULT_ENDPOINTS,
7
5
  FragmentsJsonAdapter,
8
6
  INFRA_TOOLS,
9
- MINIMUM_SCORE_THRESHOLD,
10
- SYNONYM_MAP,
11
7
  ToolRegistry,
12
- USE_CASE_TOKEN_CATEGORIES,
13
8
  VIEWER_TOOLS,
14
9
  blockFromCompiledBlock,
15
10
  buildCapabilities,
@@ -22,14 +17,14 @@ import {
22
17
  telemetryMiddleware,
23
18
  tokensFromCompiledTokenData,
24
19
  validateSnapshot
25
- } from "./chunk-6JMX4AMO.js";
20
+ } from "./chunk-YJTMK4JY.js";
26
21
  import {
27
22
  BRAND
28
23
  } from "./chunk-4SVS3AA3.js";
29
24
  import {
30
25
  generateRulesFiles
31
- } from "./chunk-VV2PJ75X.js";
32
- import "./chunk-YSRGQDEB.js";
26
+ } from "./chunk-WDQPNHZ2.js";
27
+ import "./chunk-7D4SUZUM.js";
33
28
 
34
29
  // src/adapters/custom-json.ts
35
30
  import { readFile } from "fs/promises";
@@ -39,6 +34,7 @@ var CustomJsonAdapter = class {
39
34
  constructor(filePath) {
40
35
  this.filePath = filePath;
41
36
  }
37
+ filePath;
42
38
  name = "custom-json";
43
39
  discover() {
44
40
  return existsSync(this.filePath) ? [this.filePath] : [];
@@ -70,6 +66,7 @@ var CustomJsonAdapter = class {
70
66
  }
71
67
  const packageMap = raw.packageMap ?? {};
72
68
  const defaultPackageName = raw.defaultPackageName;
69
+ const metadata = raw.metadata;
73
70
  const components = Object.fromEntries(
74
71
  Object.entries(raw.components).map(
75
72
  ([key, fragment]) => [
@@ -102,8 +99,11 @@ var CustomJsonAdapter = class {
102
99
  performanceSummary: raw.performanceSummary
103
100
  }),
104
101
  metadata: {
105
- packageName: defaultPackageName,
106
- importPath: defaultPackageName
102
+ designSystemName: metadata?.designSystemName,
103
+ packageName: metadata?.packageName ?? defaultPackageName,
104
+ importPath: metadata?.importPath ?? defaultPackageName,
105
+ revision: metadata?.revision,
106
+ updatedAt: metadata?.updatedAt
107
107
  },
108
108
  components,
109
109
  blocks,
@@ -127,6 +127,71 @@ var CustomJsonAdapter = class {
127
127
  }
128
128
  };
129
129
 
130
+ // src/orama-index.ts
131
+ import { create, insertMultiple, search } from "@orama/orama";
132
+ var SYNONYM_MAP = {
133
+ "form": ["input", "field", "submit", "validation"],
134
+ "input": ["form", "field", "text", "entry"],
135
+ "button": ["action", "click", "submit", "trigger"],
136
+ "action": ["button", "click", "trigger"],
137
+ "submit": ["button", "form", "action", "send"],
138
+ "alert": ["notification", "message", "warning", "error", "feedback"],
139
+ "notification": ["alert", "message", "toast"],
140
+ "feedback": ["form", "comment", "review", "rating"],
141
+ "card": ["container", "panel", "box", "content"],
142
+ "toggle": ["switch", "checkbox", "boolean", "on/off"],
143
+ "switch": ["toggle", "checkbox", "boolean"],
144
+ "badge": ["tag", "label", "status", "indicator"],
145
+ "status": ["badge", "indicator", "state"],
146
+ "login": ["auth", "signin", "authentication", "form"],
147
+ "auth": ["login", "signin", "authentication"],
148
+ "chat": ["message", "conversation", "ai"],
149
+ "table": ["data", "grid", "list", "rows"],
150
+ "textarea": ["text", "input", "multiline", "area", "comment"],
151
+ "area": ["textarea", "multiline", "text"],
152
+ "landing": ["page", "hero", "marketing", "section", "layout"],
153
+ "hero": ["landing", "marketing", "banner", "headline", "section"],
154
+ "marketing": ["landing", "hero", "pricing", "testimonial", "cta"],
155
+ "cta": ["marketing", "banner", "action", "button"],
156
+ "testimonial": ["marketing", "review", "quote", "feedback"],
157
+ "layout": ["stack", "grid", "box", "container", "page"],
158
+ "page": ["layout", "landing", "section", "container"],
159
+ "section": ["hero", "feature", "testimonial", "cta", "faq"],
160
+ "pricing": ["card", "plan", "tier", "marketing"],
161
+ "plan": ["pricing", "card", "tier", "subscription"],
162
+ "dashboard": ["metrics", "stats", "chart", "card", "grid"],
163
+ "metrics": ["dashboard", "stats", "progress", "number"],
164
+ "stats": ["metrics", "dashboard", "progress", "badge"],
165
+ "chart": ["dashboard", "metrics", "data", "graph"]
166
+ };
167
+ var USE_CASE_TOKEN_CATEGORIES = {
168
+ "table": ["spacing", "borders", "surfaces", "text"],
169
+ "data": ["spacing", "borders", "surfaces"],
170
+ "grid": ["spacing", "layout"],
171
+ "form": ["spacing", "borders", "radius", "focus"],
172
+ "input": ["spacing", "borders", "radius", "focus"],
173
+ "card": ["surfaces", "shadows", "radius", "borders", "spacing"],
174
+ "button": ["colors", "radius", "spacing", "focus"],
175
+ "layout": ["spacing", "layout", "surfaces"],
176
+ "dashboard": ["spacing", "surfaces", "borders", "shadows"],
177
+ "chat": ["spacing", "surfaces", "radius", "shadows"],
178
+ "modal": ["shadows", "surfaces", "radius", "spacing"],
179
+ "dialog": ["shadows", "surfaces", "radius", "spacing"],
180
+ "navigation": ["spacing", "surfaces", "borders"],
181
+ "sidebar": ["spacing", "surfaces", "borders"],
182
+ "hero": ["spacing", "typography", "colors"],
183
+ "landing": ["spacing", "typography", "colors"],
184
+ "pricing": ["spacing", "surfaces", "borders", "radius"],
185
+ "auth": ["spacing", "borders", "radius", "focus"],
186
+ "login": ["spacing", "borders", "radius", "focus"],
187
+ "dark": ["colors", "surfaces"],
188
+ "theme": ["colors", "surfaces", "text"]
189
+ };
190
+
191
+ // src/scoring.ts
192
+ var MINIMUM_SCORE_THRESHOLD = 5;
193
+ var BLOCK_BOOST_PER_OCCURRENCE = 5;
194
+
130
195
  // src/search-config.ts
131
196
  var DEFAULT_SEARCH_CONFIG = {
132
197
  synonyms: SYNONYM_MAP,
@@ -142,7 +207,6 @@ export {
142
207
  BUILTIN_TOOLS,
143
208
  CORE_TOOLS,
144
209
  CustomJsonAdapter,
145
- DEFAULT_ENDPOINTS,
146
210
  DEFAULT_SEARCH_CONFIG,
147
211
  FragmentsJsonAdapter,
148
212
  INFRA_TOOLS,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/custom-json.ts","../src/search-config.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { mcpSnapshotSchema, type McpSnapshot } from '@fragments-sdk/core';\nimport type {\n CompiledBlock,\n CompiledFragment,\n CompiledTokenData,\n} from '@fragments-sdk/context/types';\nimport type { SerializedComponentGraph } from '@fragments-sdk/context/graph';\nimport type { DesignSystemData } from '../types.js';\nimport type { DataAdapter } from './types.js';\nimport {\n blockFromCompiledBlock,\n buildCapabilities,\n componentFromCompiledFragment,\n tokensFromCompiledTokenData,\n validateSnapshot,\n} from './snapshot-converters.js';\n\nexport class CustomJsonAdapter implements DataAdapter {\n readonly name = 'custom-json';\n\n constructor(private filePath: string) {}\n\n discover(): string[] {\n return existsSync(this.filePath) ? [this.filePath] : [];\n }\n\n async load(_projectRoot = ''): Promise<DesignSystemData> {\n if (!existsSync(this.filePath)) {\n throw new Error(`Custom data file not found: ${this.filePath}`);\n }\n\n const content = await readFile(this.filePath, 'utf-8');\n const raw = JSON.parse(content) as Record<string, unknown>;\n\n if (\n raw.schemaVersion === 1 &&\n typeof raw.sourceType === 'string' &&\n raw.components &&\n typeof raw.components === 'object'\n ) {\n const snapshot = mcpSnapshotSchema.parse(raw) as McpSnapshot;\n return {\n snapshot,\n components: snapshot.components,\n blocks: snapshot.blocks,\n tokens: snapshot.tokens,\n graph: snapshot.graph as SerializedComponentGraph | undefined,\n performanceSummary: snapshot.performanceSummary,\n packageMap: snapshot.packageMap,\n defaultPackageName: snapshot.defaultPackageName,\n capabilities: new Set(snapshot.capabilities),\n };\n }\n\n if (!raw.components || typeof raw.components !== 'object') {\n throw new Error(\n `Invalid design system data: \"components\" field is required and must be an object. ` +\n `File: ${this.filePath}`,\n );\n }\n\n const packageMap =\n (raw.packageMap as Record<string, string> | undefined) ?? {};\n const defaultPackageName = raw.defaultPackageName as string | undefined;\n const components = Object.fromEntries(\n Object.entries(raw.components as Record<string, CompiledFragment>).map(\n ([key, fragment]) => [\n key,\n componentFromCompiledFragment({\n id: key,\n fragment,\n sourceType: 'custom-json',\n packageName: packageMap[fragment.meta.name] ?? defaultPackageName,\n importPath: packageMap[fragment.meta.name] ?? defaultPackageName,\n }),\n ],\n ),\n );\n const blocks = raw.blocks\n ? Object.fromEntries(\n Object.entries(raw.blocks as Record<string, CompiledBlock>).map(\n ([key, block]) => [key, blockFromCompiledBlock(key, block)],\n ),\n )\n : undefined;\n const tokens = raw.tokens\n ? tokensFromCompiledTokenData(raw.tokens as CompiledTokenData)\n : undefined;\n const snapshot = validateSnapshot({\n schemaVersion: 1,\n sourceType: 'custom-json',\n sourceLabel: this.filePath,\n capabilities: buildCapabilities({\n components,\n blocks,\n tokens,\n graph: raw.graph,\n performanceSummary: raw.performanceSummary,\n }),\n metadata: {\n packageName: defaultPackageName,\n importPath: defaultPackageName,\n },\n components,\n blocks,\n tokens,\n graph: raw.graph as SerializedComponentGraph | undefined,\n performanceSummary:\n raw.performanceSummary as McpSnapshot['performanceSummary'],\n packageMap,\n defaultPackageName,\n });\n\n return {\n snapshot,\n components: snapshot.components,\n blocks: snapshot.blocks,\n tokens: snapshot.tokens,\n graph: snapshot.graph as SerializedComponentGraph | undefined,\n performanceSummary: snapshot.performanceSummary,\n packageMap: snapshot.packageMap,\n defaultPackageName: snapshot.defaultPackageName,\n capabilities: new Set(snapshot.capabilities),\n };\n }\n}\n","import { SYNONYM_MAP, USE_CASE_TOKEN_CATEGORIES } from './orama-index.js';\nimport { MINIMUM_SCORE_THRESHOLD, BLOCK_BOOST_PER_OCCURRENCE } from './scoring.js';\n\nexport interface SearchConfig {\n synonyms?: Record<string, string[]>;\n useCaseTokenCategories?: Record<string, string[]>;\n minimumScoreThreshold?: number;\n blockBoostPerOccurrence?: number;\n vectorSearchUrl?: string;\n vectorSearchTimeoutMs?: number;\n}\n\nexport const DEFAULT_SEARCH_CONFIG: Required<SearchConfig> = {\n synonyms: SYNONYM_MAP,\n useCaseTokenCategories: USE_CASE_TOKEN_CATEGORIES,\n minimumScoreThreshold: MINIMUM_SCORE_THRESHOLD,\n blockBoostPerOccurrence: BLOCK_BOOST_PER_OCCURRENCE,\n vectorSearchUrl: 'https://combative-jay-834.convex.site/search',\n vectorSearchTimeoutMs: 3000,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,yBAA2C;AAiB7C,IAAM,oBAAN,MAA+C;AAAA,EAGpD,YAAoB,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAF9B,OAAO;AAAA,EAIhB,WAAqB;AACnB,WAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC;AAAA,EACxD;AAAA,EAEA,MAAM,KAAK,eAAe,IAA+B;AACvD,QAAI,CAAC,WAAW,KAAK,QAAQ,GAAG;AAC9B,YAAM,IAAI,MAAM,+BAA+B,KAAK,QAAQ,EAAE;AAAA,IAChE;AAEA,UAAM,UAAU,MAAM,SAAS,KAAK,UAAU,OAAO;AACrD,UAAM,MAAM,KAAK,MAAM,OAAO;AAE9B,QACE,IAAI,kBAAkB,KACtB,OAAO,IAAI,eAAe,YAC1B,IAAI,cACJ,OAAO,IAAI,eAAe,UAC1B;AACA,YAAMA,YAAW,kBAAkB,MAAM,GAAG;AAC5C,aAAO;AAAA,QACL,UAAAA;AAAA,QACA,YAAYA,UAAS;AAAA,QACrB,QAAQA,UAAS;AAAA,QACjB,QAAQA,UAAS;AAAA,QACjB,OAAOA,UAAS;AAAA,QAChB,oBAAoBA,UAAS;AAAA,QAC7B,YAAYA,UAAS;AAAA,QACrB,oBAAoBA,UAAS;AAAA,QAC7B,cAAc,IAAI,IAAIA,UAAS,YAAY;AAAA,MAC7C;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,cAAc,OAAO,IAAI,eAAe,UAAU;AACzD,YAAM,IAAI;AAAA,QACR,2FACW,KAAK,QAAQ;AAAA,MAC1B;AAAA,IACF;AAEA,UAAM,aACH,IAAI,cAAqD,CAAC;AAC7D,UAAM,qBAAqB,IAAI;AAC/B,UAAM,aAAa,OAAO;AAAA,MACxB,OAAO,QAAQ,IAAI,UAA8C,EAAE;AAAA,QACjE,CAAC,CAAC,KAAK,QAAQ,MAAM;AAAA,UACnB;AAAA,UACA,8BAA8B;AAAA,YAC5B,IAAI;AAAA,YACJ;AAAA,YACA,YAAY;AAAA,YACZ,aAAa,WAAW,SAAS,KAAK,IAAI,KAAK;AAAA,YAC/C,YAAY,WAAW,SAAS,KAAK,IAAI,KAAK;AAAA,UAChD,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,IAAI,SACf,OAAO;AAAA,MACL,OAAO,QAAQ,IAAI,MAAuC,EAAE;AAAA,QAC1D,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,uBAAuB,KAAK,KAAK,CAAC;AAAA,MAC5D;AAAA,IACF,IACA;AACJ,UAAM,SAAS,IAAI,SACf,4BAA4B,IAAI,MAA2B,IAC3D;AACJ,UAAM,WAAW,iBAAiB;AAAA,MAChC,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,cAAc,kBAAkB;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,IAAI;AAAA,QACX,oBAAoB,IAAI;AAAA,MAC1B,CAAC;AAAA,MACD,UAAU;AAAA,QACR,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,IAAI;AAAA,MACX,oBACE,IAAI;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,YAAY,SAAS;AAAA,MACrB,QAAQ,SAAS;AAAA,MACjB,QAAQ,SAAS;AAAA,MACjB,OAAO,SAAS;AAAA,MAChB,oBAAoB,SAAS;AAAA,MAC7B,YAAY,SAAS;AAAA,MACrB,oBAAoB,SAAS;AAAA,MAC7B,cAAc,IAAI,IAAI,SAAS,YAAY;AAAA,IAC7C;AAAA,EACF;AACF;;;ACnHO,IAAM,wBAAgD;AAAA,EAC3D,UAAU;AAAA,EACV,wBAAwB;AAAA,EACxB,uBAAuB;AAAA,EACvB,yBAAyB;AAAA,EACzB,iBAAiB;AAAA,EACjB,uBAAuB;AACzB;","names":["snapshot"]}
1
+ {"version":3,"sources":["../src/adapters/custom-json.ts","../src/orama-index.ts","../src/scoring.ts","../src/search-config.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { mcpSnapshotSchema, type McpSnapshot } from '@fragments-sdk/core';\nimport type {\n CompiledBlock,\n CompiledFragment,\n CompiledTokenData,\n} from '@fragments-sdk/context/types';\nimport type { SerializedComponentGraph } from '@fragments-sdk/context/graph';\nimport type { DesignSystemData } from '../types.js';\nimport type { DataAdapter } from './types.js';\nimport {\n blockFromCompiledBlock,\n buildCapabilities,\n componentFromCompiledFragment,\n tokensFromCompiledTokenData,\n validateSnapshot,\n} from './snapshot-converters.js';\n\nexport class CustomJsonAdapter implements DataAdapter {\n readonly name = 'custom-json';\n\n constructor(private filePath: string) {}\n\n discover(): string[] {\n return existsSync(this.filePath) ? [this.filePath] : [];\n }\n\n async load(_projectRoot = ''): Promise<DesignSystemData> {\n if (!existsSync(this.filePath)) {\n throw new Error(`Custom data file not found: ${this.filePath}`);\n }\n\n const content = await readFile(this.filePath, 'utf-8');\n const raw = JSON.parse(content) as Record<string, unknown>;\n\n if (\n raw.schemaVersion === 1 &&\n typeof raw.sourceType === 'string' &&\n raw.components &&\n typeof raw.components === 'object'\n ) {\n const snapshot = mcpSnapshotSchema.parse(raw) as McpSnapshot;\n return {\n snapshot,\n components: snapshot.components,\n blocks: snapshot.blocks,\n tokens: snapshot.tokens,\n graph: snapshot.graph as SerializedComponentGraph | undefined,\n performanceSummary: snapshot.performanceSummary,\n packageMap: snapshot.packageMap,\n defaultPackageName: snapshot.defaultPackageName,\n capabilities: new Set(snapshot.capabilities),\n };\n }\n\n if (!raw.components || typeof raw.components !== 'object') {\n throw new Error(\n `Invalid design system data: \"components\" field is required and must be an object. ` +\n `File: ${this.filePath}`,\n );\n }\n\n const packageMap =\n (raw.packageMap as Record<string, string> | undefined) ?? {};\n const defaultPackageName = raw.defaultPackageName as string | undefined;\n const metadata = raw.metadata as\n | {\n designSystemName?: string;\n packageName?: string;\n importPath?: string;\n revision?: string;\n updatedAt?: string;\n }\n | undefined;\n const components = Object.fromEntries(\n Object.entries(raw.components as Record<string, CompiledFragment>).map(\n ([key, fragment]) => [\n key,\n componentFromCompiledFragment({\n id: key,\n fragment,\n sourceType: 'custom-json',\n packageName: packageMap[fragment.meta.name] ?? defaultPackageName,\n importPath: packageMap[fragment.meta.name] ?? defaultPackageName,\n }),\n ],\n ),\n );\n const blocks = raw.blocks\n ? Object.fromEntries(\n Object.entries(raw.blocks as Record<string, CompiledBlock>).map(\n ([key, block]) => [key, blockFromCompiledBlock(key, block)],\n ),\n )\n : undefined;\n const tokens = raw.tokens\n ? tokensFromCompiledTokenData(raw.tokens as CompiledTokenData)\n : undefined;\n const snapshot = validateSnapshot({\n schemaVersion: 1,\n sourceType: 'custom-json',\n sourceLabel: this.filePath,\n capabilities: buildCapabilities({\n components,\n blocks,\n tokens,\n graph: raw.graph,\n performanceSummary: raw.performanceSummary,\n }),\n metadata: {\n designSystemName: metadata?.designSystemName,\n packageName: metadata?.packageName ?? defaultPackageName,\n importPath: metadata?.importPath ?? defaultPackageName,\n revision: metadata?.revision,\n updatedAt: metadata?.updatedAt,\n },\n components,\n blocks,\n tokens,\n graph: raw.graph as SerializedComponentGraph | undefined,\n performanceSummary:\n raw.performanceSummary as McpSnapshot['performanceSummary'],\n packageMap,\n defaultPackageName,\n });\n\n return {\n snapshot,\n components: snapshot.components,\n blocks: snapshot.blocks,\n tokens: snapshot.tokens,\n graph: snapshot.graph as SerializedComponentGraph | undefined,\n performanceSummary: snapshot.performanceSummary,\n packageMap: snapshot.packageMap,\n defaultPackageName: snapshot.defaultPackageName,\n capabilities: new Set(snapshot.capabilities),\n };\n }\n}\n","/**\n * Orama-powered BM25 search indexes for components, blocks, and tokens.\n *\n * Replaces the bag-of-words keyword scorer with Orama's built-in BM25 engine,\n * which provides IDF weighting (automatically downweights common terms like\n * \"page\", \"section\", \"content\"), stemming, and field-level boosting.\n */\n\nimport { create, insertMultiple, search } from '@orama/orama';\nimport type { AnyOrama } from '@orama/orama';\nimport type { McpBlock, McpComponent, McpTokenData } from '@fragments-sdk/core';\nimport type {\n CompiledBlock,\n CompiledFragment,\n CompiledTokenData,\n} from '@fragments-sdk/context/types';\nimport type { ScoredResult, EntryKind } from './search.js';\n\n// ---------------------------------------------------------------------------\n// Synonym map (shared with search.ts — imported via re-export)\n// ---------------------------------------------------------------------------\n\nexport const SYNONYM_MAP: Record<string, string[]> = {\n 'form': ['input', 'field', 'submit', 'validation'],\n 'input': ['form', 'field', 'text', 'entry'],\n 'button': ['action', 'click', 'submit', 'trigger'],\n 'action': ['button', 'click', 'trigger'],\n 'submit': ['button', 'form', 'action', 'send'],\n 'alert': ['notification', 'message', 'warning', 'error', 'feedback'],\n 'notification': ['alert', 'message', 'toast'],\n 'feedback': ['form', 'comment', 'review', 'rating'],\n 'card': ['container', 'panel', 'box', 'content'],\n 'toggle': ['switch', 'checkbox', 'boolean', 'on/off'],\n 'switch': ['toggle', 'checkbox', 'boolean'],\n 'badge': ['tag', 'label', 'status', 'indicator'],\n 'status': ['badge', 'indicator', 'state'],\n 'login': ['auth', 'signin', 'authentication', 'form'],\n 'auth': ['login', 'signin', 'authentication'],\n 'chat': ['message', 'conversation', 'ai'],\n 'table': ['data', 'grid', 'list', 'rows'],\n 'textarea': ['text', 'input', 'multiline', 'area', 'comment'],\n 'area': ['textarea', 'multiline', 'text'],\n 'landing': ['page', 'hero', 'marketing', 'section', 'layout'],\n 'hero': ['landing', 'marketing', 'banner', 'headline', 'section'],\n 'marketing': ['landing', 'hero', 'pricing', 'testimonial', 'cta'],\n 'cta': ['marketing', 'banner', 'action', 'button'],\n 'testimonial': ['marketing', 'review', 'quote', 'feedback'],\n 'layout': ['stack', 'grid', 'box', 'container', 'page'],\n 'page': ['layout', 'landing', 'section', 'container'],\n 'section': ['hero', 'feature', 'testimonial', 'cta', 'faq'],\n 'pricing': ['card', 'plan', 'tier', 'marketing'],\n 'plan': ['pricing', 'card', 'tier', 'subscription'],\n 'dashboard': ['metrics', 'stats', 'chart', 'card', 'grid'],\n 'metrics': ['dashboard', 'stats', 'progress', 'number'],\n 'stats': ['metrics', 'dashboard', 'progress', 'badge'],\n 'chart': ['dashboard', 'metrics', 'data', 'graph'],\n};\n\n// ---------------------------------------------------------------------------\n// Query expansion via synonyms\n// ---------------------------------------------------------------------------\n\n/**\n * Expand a query string with synonym terms.\n * Returns the expanded query as a single string suitable for Orama `term`.\n */\nexport function expandQuery(query: string): string {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const expanded = new Set(terms);\n for (const term of terms) {\n const synonyms = SYNONYM_MAP[term];\n if (synonyms) {\n for (const syn of synonyms) expanded.add(syn);\n }\n }\n return Array.from(expanded).join(' ');\n}\n\n// ---------------------------------------------------------------------------\n// Shared two-pass search pattern\n// ---------------------------------------------------------------------------\n\ninterface TwoPassConfig {\n index: AnyOrama;\n query: string;\n properties: string[];\n boost: Record<string, number>;\n limit: number;\n kind: EntryKind;\n}\n\n/**\n * Two-pass BM25 search: original terms weighted 2x, synonym-expanded terms 1x.\n * Prevents synonym noise from outranking truly relevant results.\n */\nexport function twoPassSearch(config: TwoPassConfig): ScoredResult[] {\n const { index, query, properties, boost, limit, kind } = config;\n\n const baseConfig = {\n mode: 'fulltext' as const,\n properties,\n boost,\n limit,\n };\n\n const originalTermsQuery = query.toLowerCase().split(/\\s+/).filter(Boolean).join(' ');\n const expandedQuery = expandQuery(query);\n\n // First pass: original terms only (moderate threshold to filter weak matches)\n const originalResults = search(index, { term: originalTermsQuery, ...baseConfig, threshold: 0.8 });\n // Second pass: synonym-expanded (permissive — scoring handles relevance via 1x weight)\n const expandedResults = search(index, { term: expandedQuery, ...baseConfig, threshold: 1 });\n\n type Hit = { score: number; document: { name: string } };\n const origHits = (originalResults as unknown as { hits: Hit[] }).hits;\n const expHits = (expandedResults as unknown as { hits: Hit[] }).hits;\n\n // Combine scores: original terms get 2x weight\n const scoreMap = new Map<string, number>();\n for (const hit of origHits) {\n scoreMap.set(hit.document.name, (hit.score || 0) * 2);\n }\n for (const hit of expHits) {\n const name = hit.document.name;\n const existing = scoreMap.get(name) ?? 0;\n scoreMap.set(name, existing + (hit.score || 0));\n }\n\n const scored: ScoredResult[] = [];\n for (const [name, score] of scoreMap) {\n if (score > 0) {\n scored.push({ name, kind, rank: scored.length, score });\n }\n }\n\n scored.sort((a, b) => b.score - a.score);\n scored.forEach((s, i) => { s.rank = i; });\n\n return scored;\n}\n\n// ---------------------------------------------------------------------------\n// Component index\n// ---------------------------------------------------------------------------\n\nconst componentSchema = {\n name: 'string' as const,\n description: 'string' as const,\n category: 'string' as const,\n tags: 'string' as const,\n whenUsed: 'string' as const,\n patterns: 'string' as const,\n variants: 'string' as const,\n status: 'string' as const,\n};\n\nexport type ComponentIndex = AnyOrama;\n\nfunction isCompiledFragment(\n value: McpComponent | CompiledFragment,\n): value is CompiledFragment {\n return 'meta' in value;\n}\n\nfunction normalizeComponent(\n value: McpComponent | CompiledFragment,\n): McpComponent {\n if (!isCompiledFragment(value)) return value;\n return {\n id: value.filePath ?? value.meta.name,\n name: value.meta.name,\n description: value.meta.description ?? '',\n category: value.meta.category ?? 'uncategorized',\n status: value.meta.status ?? 'stable',\n tags: value.meta.tags ?? [],\n props: {},\n propsSummary:\n value.propsSummary ??\n value.contract?.propsSummary ??\n Object.entries(value.props ?? {}).map(\n ([propName, prop]) =>\n `${propName}${prop.required ? ' (required)' : ''}: ${prop.type}`,\n ),\n examples: (value.variants ?? []).map((variant) => ({\n name: variant.name,\n description: variant.description,\n code: variant.code,\n })),\n relations: (value.relations ?? []).map((relation) => ({\n componentName: relation.component,\n relationship: relation.relationship,\n note: relation.note,\n })),\n compoundChildren: [],\n guidance: {\n when: value.usage?.when ?? [],\n whenNot: value.usage?.whenNot ?? [],\n guidelines: value.usage?.guidelines ?? [],\n accessibility: value.usage?.accessibility ?? [],\n dos: value.usage?.when ?? [],\n donts: value.usage?.whenNot ?? [],\n patterns: [],\n },\n sourceType: 'fragments-json',\n sourcePath: value.sourcePath ?? value.filePath,\n metadata: {\n a11yRules: value.contract?.a11yRules ?? [],\n scenarioTags: value.contract?.scenarioTags ?? [],\n },\n };\n}\n\nexport function buildComponentIndex(\n fragments: Array<McpComponent | CompiledFragment>,\n): ComponentIndex {\n const db = create({ schema: componentSchema, language: 'english' });\n const normalized = fragments.map(normalizeComponent);\n\n const docs = normalized.map((f) => ({\n name: f.name,\n description: f.description ?? '',\n category: f.category ?? '',\n tags: (f.tags ?? []).join(' '),\n whenUsed: (f.guidance.when ?? []).join(' '),\n patterns: (f.guidance.patterns ?? [])\n .map(\n (pattern: McpComponent['guidance']['patterns'][number]) =>\n `${pattern.name} ${pattern.description || ''}`,\n )\n .join(' '),\n variants: f.examples\n .map(\n (example: McpComponent['examples'][number]) =>\n `${example.name} ${example.description || ''}`,\n )\n .join(' '),\n status: f.status ?? 'stable',\n }));\n\n insertMultiple(db, docs);\n return db;\n}\n\nexport function searchComponents(\n query: string,\n index: ComponentIndex,\n fragments: Array<McpComponent | CompiledFragment>,\n limit = 50,\n): ScoredResult[] {\n const normalized = fragments.map(normalizeComponent);\n const boostConfig = {\n mode: 'fulltext' as const,\n properties: ['name', 'whenUsed', 'description', 'patterns', 'category', 'tags', 'variants'],\n boost: {\n name: 3,\n whenUsed: 2.5,\n description: 2,\n patterns: 1.5,\n category: 1.5,\n tags: 1.5,\n variants: 1,\n },\n limit,\n };\n\n // Two-pass search: original terms weighted 2x, synonym terms 1x\n // This prevents synonym noise (\"box\", \"container\") from outranking truly relevant results\n const originalTermsList = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const originalTermsQuery = originalTermsList.join(' ');\n const expandedQuery = expandQuery(query);\n\n // First pass: original terms, moderate threshold; second pass: synonyms, permissive\n const originalResults = search(index, { term: originalTermsQuery, ...boostConfig, threshold: 0.8 });\n const expandedResults = search(index, { term: expandedQuery, ...boostConfig, threshold: 1 });\n\n type Hit = { score: number; document: { name: string } };\n const origHits = (originalResults as unknown as { hits: Hit[] }).hits;\n const expHits = (expandedResults as unknown as { hits: Hit[] }).hits;\n\n // Combine scores: original terms get 2x weight\n const scoreMap = new Map<string, number>();\n for (const hit of origHits) {\n scoreMap.set(hit.document.name, (hit.score || 0) * 2);\n }\n for (const hit of expHits) {\n const name = hit.document.name;\n const existing = scoreMap.get(name) ?? 0;\n scoreMap.set(name, existing + (hit.score || 0));\n }\n\n // Build a name→fragment lookup for status bonuses\n const fragmentMap = new Map<string, McpComponent>();\n for (const f of normalized) {\n fragmentMap.set(f.name.toLowerCase(), f);\n }\n\n // Exact name match bonus: if a component name matches an original query term exactly,\n // boost it above similar-named components (e.g., \"Button\" > \"ButtonGroup\" for \"button action\")\n const originalTermsSet = new Set(originalTermsList);\n\n // Build scored results with status + name match adjustments\n const scored: ScoredResult[] = [];\n for (const [name, rawScore] of scoreMap) {\n let score = rawScore;\n const nameLower = name.toLowerCase();\n const fragment = fragmentMap.get(nameLower);\n\n // Exact name match bonus — strong signal that this specific component was requested\n // Must be large enough to overcome BM25 score differences from compound names\n // (e.g., \"ButtonGroup\" matching more fields than \"Button\")\n if (originalTermsSet.has(nameLower)) {\n score += 25;\n }\n\n // Apply status bonus/penalty (matches old scorer)\n if (fragment) {\n if (fragment.status === 'stable') score += 5;\n else if (fragment.status === 'beta') score += 2;\n if (fragment.status === 'deprecated') score -= 25;\n }\n\n if (score > 0) {\n scored.push({ name, kind: 'component' as EntryKind, rank: scored.length, score });\n }\n }\n\n // Sort by score descending and assign ranks\n scored.sort((a, b) => b.score - a.score);\n scored.forEach((s, i) => { s.rank = i; });\n\n return scored;\n}\n\n// ---------------------------------------------------------------------------\n// Block index\n// ---------------------------------------------------------------------------\n\nconst blockSchema = {\n name: 'string' as const,\n description: 'string' as const,\n category: 'string' as const,\n tags: 'string' as const,\n components: 'string' as const,\n};\n\nexport type BlockIndex = AnyOrama;\n\nfunction normalizeBlock(value: McpBlock | CompiledBlock): McpBlock {\n if ('id' in value) return value;\n return {\n id: value.filePath ?? value.name,\n name: value.name,\n description: value.description ?? '',\n category: value.category ?? 'uncategorized',\n components: value.components ?? [],\n tags: value.tags ?? [],\n code: value.code ?? '',\n };\n}\n\nexport function buildBlockIndex(\n blocks: Array<McpBlock | CompiledBlock>,\n): BlockIndex {\n const db = create({ schema: blockSchema, language: 'english' });\n const normalized = blocks.map(normalizeBlock);\n\n const docs = normalized.map((b) => ({\n name: b.name,\n description: b.description ?? '',\n category: b.category ?? '',\n tags: (b.tags ?? []).join(' '),\n components: b.components.join(' '),\n }));\n\n insertMultiple(db, docs);\n return db;\n}\n\nexport function searchBlocks(\n query: string,\n index: BlockIndex,\n limit = 50,\n): ScoredResult[] {\n return twoPassSearch({\n index,\n query,\n properties: ['name', 'description', 'components', 'tags', 'category'],\n boost: {\n name: 3,\n description: 2,\n components: 1.5,\n tags: 1.5,\n category: 1.5,\n },\n limit,\n kind: 'block' as EntryKind,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Token index\n// ---------------------------------------------------------------------------\n\nconst tokenSchema = {\n name: 'string' as const,\n category: 'string' as const,\n description: 'string' as const,\n};\n\nexport type TokenIndex = AnyOrama;\n\nfunction normalizeTokenData(\n tokenData: McpTokenData | CompiledTokenData,\n): McpTokenData {\n if ('flat' in tokenData) return tokenData;\n const categories = Object.fromEntries(\n Object.entries(tokenData.categories).map(([category, entries]) => [\n category,\n entries.map((entry) => ({\n name: entry.name,\n category,\n value: typeof entry.value === 'string' ? entry.value : undefined,\n description: entry.description,\n })),\n ]),\n );\n return {\n prefix: tokenData.prefix,\n total: tokenData.total,\n categories,\n flat: Object.values(categories).flat(),\n };\n}\n\nexport function buildTokenIndex(\n tokenData: McpTokenData | CompiledTokenData,\n): TokenIndex {\n const db = create({ schema: tokenSchema, language: 'english' });\n const normalizedData = normalizeTokenData(tokenData);\n\n const docs: Array<{ name: string; category: string; description: string }> = [];\n for (const [cat, tokens] of Object.entries(normalizedData.categories) as Array<\n [string, McpTokenData['categories'][string]]\n >) {\n for (const token of tokens) {\n docs.push({\n name: token.name,\n category: cat,\n description: token.description ?? '',\n });\n }\n }\n\n insertMultiple(db, docs);\n return db;\n}\n\nexport function searchTokens(\n query: string,\n index: TokenIndex,\n limit = 50,\n): ScoredResult[] {\n return twoPassSearch({\n index,\n query,\n properties: ['name', 'category', 'description'],\n boost: {\n name: 2.5,\n category: 2,\n description: 1.5,\n },\n limit,\n kind: 'token' as EntryKind,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Semantic category extraction for implement tool token matching\n// ---------------------------------------------------------------------------\n\n/** Maps use-case keywords to relevant token categories */\nexport const USE_CASE_TOKEN_CATEGORIES: Record<string, string[]> = {\n 'table': ['spacing', 'borders', 'surfaces', 'text'],\n 'data': ['spacing', 'borders', 'surfaces'],\n 'grid': ['spacing', 'layout'],\n 'form': ['spacing', 'borders', 'radius', 'focus'],\n 'input': ['spacing', 'borders', 'radius', 'focus'],\n 'card': ['surfaces', 'shadows', 'radius', 'borders', 'spacing'],\n 'button': ['colors', 'radius', 'spacing', 'focus'],\n 'layout': ['spacing', 'layout', 'surfaces'],\n 'dashboard': ['spacing', 'surfaces', 'borders', 'shadows'],\n 'chat': ['spacing', 'surfaces', 'radius', 'shadows'],\n 'modal': ['shadows', 'surfaces', 'radius', 'spacing'],\n 'dialog': ['shadows', 'surfaces', 'radius', 'spacing'],\n 'navigation': ['spacing', 'surfaces', 'borders'],\n 'sidebar': ['spacing', 'surfaces', 'borders'],\n 'hero': ['spacing', 'typography', 'colors'],\n 'landing': ['spacing', 'typography', 'colors'],\n 'pricing': ['spacing', 'surfaces', 'borders', 'radius'],\n 'auth': ['spacing', 'borders', 'radius', 'focus'],\n 'login': ['spacing', 'borders', 'radius', 'focus'],\n 'dark': ['colors', 'surfaces'],\n 'theme': ['colors', 'surfaces', 'text'],\n};\n\n/**\n * Extract relevant token categories from a use-case query.\n * Returns category names that should be included in implement results.\n */\nexport function extractTokenCategories(query: string): string[] {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const categories = new Set<string>();\n\n for (const term of terms) {\n const cats = USE_CASE_TOKEN_CATEGORIES[term];\n if (cats) {\n for (const cat of cats) categories.add(cat);\n }\n }\n\n // If nothing matched, return common defaults\n if (categories.size === 0) {\n return ['spacing', 'colors', 'surfaces'];\n }\n\n return Array.from(categories);\n}\n","/**\n * Scoring utilities for MCP tool result ranking.\n *\n * Extracted as pure functions for easy testing and reuse across\n * discover, implement, and other search-driven tools.\n */\n\nimport type { ScoredResult } from './search.js';\n\n// ---------------------------------------------------------------------------\n// Confidence assignment — ratio-to-max (scale-independent)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimum absolute score floor. If the best result scores below this,\n * results are considered a no-match rather than low-confidence garbage.\n *\n * BM25 scores for a 60-component corpus typically range 10-100+ for real\n * matches (including name-match bonuses of +25 and status bonuses of +5).\n * A maxScore below 5 means no meaningful term overlap was found — only\n * status bonuses and/or minimal synonym noise.\n *\n * Note: this threshold is calibrated for BM25 keyword scores. When hybrid\n * search uses RRF fusion (premium API key), scores are in a different range\n * (~0.01-0.05) and the threshold should be bypassed via a maxScore > 1 guard.\n */\nexport const MINIMUM_SCORE_THRESHOLD = 5;\n\n/**\n * Assign a confidence level based on how a score compares to the best result.\n *\n * Uses ratio-to-max instead of absolute thresholds so the same function works\n * for both keyword scores (8-50+) and RRF scores (0.01-0.02).\n *\n * - high: score >= 70% of maxScore (\"this is almost certainly what you need\")\n * - medium: score >= 40% of maxScore (\"this could be useful\")\n * - low: score < 40% of maxScore (\"tangentially related\")\n */\nexport function assignConfidence(\n score: number,\n maxScore: number,\n): 'high' | 'medium' | 'low' {\n if (maxScore <= 0) return 'low';\n const ratio = score / maxScore;\n if (ratio >= 0.7) return 'high';\n if (ratio >= 0.4) return 'medium';\n return 'low';\n}\n\n/**\n * Check if the best score in a result set meets the minimum quality threshold.\n * Returns true if results are meaningful, false if they're noise.\n */\nexport function meetsMinimumThreshold(maxScore: number): boolean {\n return maxScore >= MINIMUM_SCORE_THRESHOLD;\n}\n\n// ---------------------------------------------------------------------------\n// Levenshtein distance — for fuzzy \"did you mean?\" suggestions\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the Levenshtein edit distance between two strings.\n * Used for suggesting closest component name matches on typos.\n */\nexport function levenshtein(a: string, b: string): number {\n const la = a.length;\n const lb = b.length;\n const dp: number[] = Array.from({ length: lb + 1 }, (_, i) => i);\n\n for (let i = 1; i <= la; i++) {\n let prev = i - 1;\n dp[0] = i;\n for (let j = 1; j <= lb; j++) {\n const temp = dp[j];\n dp[j] = a[i - 1] === b[j - 1]\n ? prev\n : 1 + Math.min(prev, dp[j], dp[j - 1]);\n prev = temp;\n }\n }\n\n return dp[lb];\n}\n\n/**\n * Find the closest matching name from a list, using Levenshtein distance.\n * Returns the closest match if it's within `maxDistance`, or null otherwise.\n */\nexport function findClosestMatch(\n input: string,\n candidates: string[],\n maxDistance = 3,\n): string | null {\n const inputLower = input.toLowerCase();\n let bestMatch: string | null = null;\n let bestDist = maxDistance + 1;\n\n for (const candidate of candidates) {\n const candidateLower = candidate.toLowerCase();\n const dist = levenshtein(inputLower, candidateLower);\n if (dist < bestDist) {\n bestDist = dist;\n bestMatch = candidate;\n } else if (dist === bestDist && bestMatch) {\n // Tie-break: prefer the candidate whose length is closer to input length\n const currentLenDiff = Math.abs(bestMatch.length - input.length);\n const newLenDiff = Math.abs(candidate.length - input.length);\n if (newLenDiff < currentLenDiff) {\n bestMatch = candidate;\n }\n }\n }\n\n return bestDist <= maxDistance ? bestMatch : null;\n}\n\n// ---------------------------------------------------------------------------\n// Block-first component ranking\n// ---------------------------------------------------------------------------\n\n/** Points added per block occurrence (e.g., Card in 3 blocks → +15) */\nexport const BLOCK_BOOST_PER_OCCURRENCE = 5;\n\n/**\n * Count how many blocks each component appears in.\n *\n * Returns a Map<lowercase component name, count>.\n * This is the core signal for block-first ranking: if Card appears in 5/5\n * marketing blocks, it's clearly more important than Sidebar which appears in 0.\n */\nexport function buildBlockComponentFrequency(\n blocks: Array<{ components: string[] }>,\n): Map<string, number> {\n const freq = new Map<string, number>();\n for (const block of blocks) {\n for (const comp of block.components) {\n const key = comp.toLowerCase();\n freq.set(key, (freq.get(key) ?? 0) + 1);\n }\n }\n return freq;\n}\n\n/**\n * Boost component search results by how frequently they appear in matching blocks.\n *\n * Mutates scores in-place and re-sorts. Components that appear in more blocks\n * get a proportionally higher boost (freq * BLOCK_BOOST_PER_OCCURRENCE).\n *\n * Returns the mutated+sorted array for convenience.\n */\nexport function boostByBlockFrequency(\n results: ScoredResult[],\n freq: Map<string, number>,\n): ScoredResult[] {\n for (const result of results) {\n const count = freq.get(result.name.toLowerCase()) ?? 0;\n if (count > 0) {\n result.score += count * BLOCK_BOOST_PER_OCCURRENCE;\n }\n }\n results.sort((a, b) => b.score - a.score);\n results.forEach((r, i) => { r.rank = i; });\n return results;\n}\n","import { SYNONYM_MAP, USE_CASE_TOKEN_CATEGORIES } from './orama-index.js';\nimport { MINIMUM_SCORE_THRESHOLD, BLOCK_BOOST_PER_OCCURRENCE } from './scoring.js';\n\nexport interface SearchConfig {\n synonyms?: Record<string, string[]>;\n useCaseTokenCategories?: Record<string, string[]>;\n minimumScoreThreshold?: number;\n blockBoostPerOccurrence?: number;\n vectorSearchUrl?: string;\n vectorSearchTimeoutMs?: number;\n}\n\nexport const DEFAULT_SEARCH_CONFIG: Required<SearchConfig> = {\n synonyms: SYNONYM_MAP,\n useCaseTokenCategories: USE_CASE_TOKEN_CATEGORIES,\n minimumScoreThreshold: MINIMUM_SCORE_THRESHOLD,\n blockBoostPerOccurrence: BLOCK_BOOST_PER_OCCURRENCE,\n vectorSearchUrl: 'https://combative-jay-834.convex.site/search',\n vectorSearchTimeoutMs: 3000,\n};\n"],"mappings":"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","names":["snapshot"]}
package/dist/init.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ generateRulesFiles
3
+ } from "./chunk-WDQPNHZ2.js";
4
+ import "./chunk-7D4SUZUM.js";
5
+
1
6
  // src/init.ts
2
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
8
  import { join, dirname, relative } from "path";
@@ -111,6 +116,16 @@ function runInit(options) {
111
116
  console.log(` ! ${target.name} \u2014 ${result}`);
112
117
  }
113
118
  }
119
+ let rulesWritten = [];
120
+ if (options.generateRules !== false) {
121
+ rulesWritten = generateRulesFiles({
122
+ data: emptyDesignSystemData(),
123
+ brandName: "Fragments",
124
+ outputDir: projectRoot,
125
+ formats: ["cursor", "claude", "copilot"],
126
+ force: false
127
+ });
128
+ }
114
129
  const hasFragmentsJson = existsSync(join(projectRoot, "fragments.json"));
115
130
  const hasTsConfig = existsSync(join(projectRoot, "tsconfig.json")) || existsSync(join(projectRoot, "tsconfig.app.json"));
116
131
  console.log();
@@ -127,12 +142,33 @@ function runInit(options) {
127
142
  } else {
128
143
  console.log(" Already configured \u2014 no changes needed.");
129
144
  }
145
+ if (rulesWritten.length > 0) {
146
+ console.log(` Wrote ${rulesWritten.length} rules file(s) for the validation loop.`);
147
+ }
130
148
  console.log();
131
149
  console.log(" Next steps:");
132
150
  console.log(" 1. Restart your editor to activate the MCP server");
133
151
  console.log(' 2. Ask your AI assistant: "What components are available?"');
152
+ console.log(" 3. After it generates UI, have it call the Fragments MCP govern tool");
134
153
  console.log();
135
154
  }
155
+ function emptyDesignSystemData() {
156
+ const capabilities = [];
157
+ return {
158
+ snapshot: {
159
+ schemaVersion: 1,
160
+ sourceType: "custom-json",
161
+ sourceLabel: "init",
162
+ capabilities,
163
+ metadata: {},
164
+ components: {},
165
+ packageMap: {}
166
+ },
167
+ components: {},
168
+ packageMap: {},
169
+ capabilities: new Set(capabilities)
170
+ };
171
+ }
136
172
  export {
137
173
  runInit
138
174
  };
package/dist/init.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/init.ts"],"sourcesContent":["/**\n * `fragments-mcp init` — write MCP config for detected AI clients.\n *\n * Auto-detects Claude Code, Cursor, VS Code, and Windsurf, then merges a\n * \"fragments\" server entry into each client's config file. Idempotent —\n * skips clients that already have the server configured.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname, relative } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type Client = \"claude\" | \"cursor\" | \"vscode\" | \"windsurf\";\n\ninterface ClientTarget {\n name: string;\n key: Client;\n /** Absolute path to the MCP config file. */\n configPath: (root: string) => string;\n /** Top-level key that holds server entries (\"mcpServers\" or \"servers\"). */\n wrapperKey: string;\n /** The server entry to write. */\n serverEntry: Record<string, unknown>;\n /** Directory whose existence signals the client is in use (optional). */\n detectDir?: (root: string) => string;\n}\n\nexport interface InitOptions {\n projectRoot: string;\n /** Explicit client list — overrides auto-detection. */\n clients?: Client[];\n}\n\n// ---------------------------------------------------------------------------\n// Client definitions\n// ---------------------------------------------------------------------------\n\nconst SERVER_ENTRY = {\n command: \"npx\",\n args: [\"-y\", \"@fragments-sdk/mcp@latest\"],\n};\n\nconst CLIENT_TARGETS: ClientTarget[] = [\n {\n name: \"Claude Code\",\n key: \"claude\",\n configPath: (root) => join(root, \".mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n // Always offered — Claude Code uses a dotfile, no directory to detect.\n },\n {\n name: \"Cursor\",\n key: \"cursor\",\n configPath: (root) => join(root, \".cursor\", \"mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n detectDir: (root) => join(root, \".cursor\"),\n },\n {\n name: \"VS Code\",\n key: \"vscode\",\n configPath: (root) => join(root, \".vscode\", \"mcp.json\"),\n wrapperKey: \"servers\",\n serverEntry: { type: \"stdio\", ...SERVER_ENTRY },\n detectDir: (root) => join(root, \".vscode\"),\n },\n {\n name: \"Windsurf\",\n key: \"windsurf\",\n configPath: (root) => join(root, \".windsurf\", \"mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n detectDir: (root) => join(root, \".windsurf\"),\n },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction hasFragmentsMcp(servers: Record<string, unknown>): boolean {\n return Object.values(servers).some((server) => {\n const s = server as { args?: string[] };\n return s.args?.some((arg: string) => arg.includes(\"@fragments-sdk/mcp\"));\n });\n}\n\ntype WriteResult = \"created\" | \"added\" | \"exists\" | string;\n\nfunction writeConfig(configPath: string, target: ClientTarget): WriteResult {\n if (existsSync(configPath)) {\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const config = JSON.parse(raw);\n const servers = config[target.wrapperKey] || {};\n\n if (servers.fragments || hasFragmentsMcp(servers)) {\n return \"exists\";\n }\n\n servers.fragments = target.serverEntry;\n config[target.wrapperKey] = servers;\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return \"added\";\n } catch {\n return \"could not parse existing config\";\n }\n }\n\n mkdirSync(dirname(configPath), { recursive: true });\n const config = { [target.wrapperKey]: { fragments: target.serverEntry } };\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return \"created\";\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nexport function runInit(options: InitOptions): void {\n const { projectRoot } = options;\n\n // Resolve targets --------------------------------------------------------\n let targets: ClientTarget[];\n\n if (options.clients?.length) {\n const valid = new Set<string>(CLIENT_TARGETS.map((t) => t.key));\n for (const c of options.clients) {\n if (!valid.has(c)) {\n console.error(`Unknown client \"${c}\". Valid: ${[...valid].join(\", \")}`);\n process.exit(1);\n }\n }\n targets = CLIENT_TARGETS.filter((t) => options.clients!.includes(t.key));\n } else {\n // Auto-detect: always include Claude Code + any editors with existing dirs\n targets = CLIENT_TARGETS.filter((t) => {\n if (t.key === \"claude\") return true;\n return t.detectDir ? existsSync(t.detectDir(projectRoot)) : false;\n });\n }\n\n if (targets.length === 0) {\n console.log(\n \"No MCP clients detected. Use --client to specify: claude, cursor, vscode, windsurf\",\n );\n process.exit(1);\n }\n\n // Write configs ----------------------------------------------------------\n console.log(\"\\nFragments MCP \\u2014 Init\\n\");\n\n let configured = 0;\n let skipped = 0;\n\n for (const target of targets) {\n const configPath = target.configPath(projectRoot);\n const rel = relative(projectRoot, configPath) || configPath;\n const result = writeConfig(configPath, target);\n\n if (result === \"created\") {\n console.log(` + ${target.name} \\u2192 ${rel} (created)`);\n configured++;\n } else if (result === \"added\") {\n console.log(` + ${target.name} \\u2192 ${rel} (updated)`);\n configured++;\n } else if (result === \"exists\") {\n console.log(` · ${target.name} \\u2014 already configured`);\n skipped++;\n } else {\n console.log(` ! ${target.name} \\u2014 ${result}`);\n }\n }\n\n // Detection mode ---------------------------------------------------------\n const hasFragmentsJson = existsSync(join(projectRoot, \"fragments.json\"));\n const hasTsConfig =\n existsSync(join(projectRoot, \"tsconfig.json\")) ||\n existsSync(join(projectRoot, \"tsconfig.app.json\"));\n\n console.log();\n if (hasFragmentsJson) {\n console.log(\" Mode: fragments.json (compiled definitions)\");\n } else if (hasTsConfig) {\n console.log(\" Mode: auto-extraction (zero-config)\");\n } else {\n console.log(\" Mode: will auto-detect on first run\");\n }\n\n // Summary ----------------------------------------------------------------\n console.log();\n if (configured > 0) {\n console.log(` Done \\u2014 configured ${configured} client(s).`);\n } else {\n console.log(\" Already configured \\u2014 no changes needed.\");\n }\n\n console.log();\n console.log(\" Next steps:\");\n console.log(\" 1. Restart your editor to activate the MCP server\");\n console.log(' 2. Ask your AI assistant: \"What components are available?\"');\n console.log();\n}\n"],"mappings":"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","names":["config"]}
1
+ {"version":3,"sources":["../src/init.ts"],"sourcesContent":["/**\n * `fragments-mcp init` — write MCP config for detected AI clients.\n *\n * Auto-detects Claude Code, Cursor, VS Code, and Windsurf, then merges a\n * \"fragments\" server entry into each client's config file. Idempotent —\n * skips clients that already have the server configured.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname, relative } from \"node:path\";\nimport { generateRulesFiles } from \"./rules.js\";\nimport type { DesignSystemData } from \"./types.js\";\nimport type { McpCapability } from \"@fragments-sdk/core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type Client = \"claude\" | \"cursor\" | \"vscode\" | \"windsurf\";\n\ninterface ClientTarget {\n name: string;\n key: Client;\n /** Absolute path to the MCP config file. */\n configPath: (root: string) => string;\n /** Top-level key that holds server entries (\"mcpServers\" or \"servers\"). */\n wrapperKey: string;\n /** The server entry to write. */\n serverEntry: Record<string, unknown>;\n /** Directory whose existence signals the client is in use (optional). */\n detectDir?: (root: string) => string;\n}\n\nexport interface InitOptions {\n projectRoot: string;\n /** Explicit client list — overrides auto-detection. */\n clients?: Client[];\n /** Write IDE rules files alongside MCP config (default: true). */\n generateRules?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Client definitions\n// ---------------------------------------------------------------------------\n\nconst SERVER_ENTRY = {\n command: \"npx\",\n args: [\"-y\", \"@fragments-sdk/mcp@latest\"],\n};\n\nconst CLIENT_TARGETS: ClientTarget[] = [\n {\n name: \"Claude Code\",\n key: \"claude\",\n configPath: (root) => join(root, \".mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n // Always offered — Claude Code uses a dotfile, no directory to detect.\n },\n {\n name: \"Cursor\",\n key: \"cursor\",\n configPath: (root) => join(root, \".cursor\", \"mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n detectDir: (root) => join(root, \".cursor\"),\n },\n {\n name: \"VS Code\",\n key: \"vscode\",\n configPath: (root) => join(root, \".vscode\", \"mcp.json\"),\n wrapperKey: \"servers\",\n serverEntry: { type: \"stdio\", ...SERVER_ENTRY },\n detectDir: (root) => join(root, \".vscode\"),\n },\n {\n name: \"Windsurf\",\n key: \"windsurf\",\n configPath: (root) => join(root, \".windsurf\", \"mcp.json\"),\n wrapperKey: \"mcpServers\",\n serverEntry: SERVER_ENTRY,\n detectDir: (root) => join(root, \".windsurf\"),\n },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction hasFragmentsMcp(servers: Record<string, unknown>): boolean {\n return Object.values(servers).some((server) => {\n const s = server as { args?: string[] };\n return s.args?.some((arg: string) => arg.includes(\"@fragments-sdk/mcp\"));\n });\n}\n\ntype WriteResult = \"created\" | \"added\" | \"exists\" | string;\n\nfunction writeConfig(configPath: string, target: ClientTarget): WriteResult {\n if (existsSync(configPath)) {\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const config = JSON.parse(raw);\n const servers = config[target.wrapperKey] || {};\n\n if (servers.fragments || hasFragmentsMcp(servers)) {\n return \"exists\";\n }\n\n servers.fragments = target.serverEntry;\n config[target.wrapperKey] = servers;\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return \"added\";\n } catch {\n return \"could not parse existing config\";\n }\n }\n\n mkdirSync(dirname(configPath), { recursive: true });\n const config = { [target.wrapperKey]: { fragments: target.serverEntry } };\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return \"created\";\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nexport function runInit(options: InitOptions): void {\n const { projectRoot } = options;\n\n // Resolve targets --------------------------------------------------------\n let targets: ClientTarget[];\n\n if (options.clients?.length) {\n const valid = new Set<string>(CLIENT_TARGETS.map((t) => t.key));\n for (const c of options.clients) {\n if (!valid.has(c)) {\n console.error(`Unknown client \"${c}\". Valid: ${[...valid].join(\", \")}`);\n process.exit(1);\n }\n }\n targets = CLIENT_TARGETS.filter((t) => options.clients!.includes(t.key));\n } else {\n // Auto-detect: always include Claude Code + any editors with existing dirs\n targets = CLIENT_TARGETS.filter((t) => {\n if (t.key === \"claude\") return true;\n return t.detectDir ? existsSync(t.detectDir(projectRoot)) : false;\n });\n }\n\n if (targets.length === 0) {\n console.log(\n \"No MCP clients detected. Use --client to specify: claude, cursor, vscode, windsurf\",\n );\n process.exit(1);\n }\n\n // Write configs ----------------------------------------------------------\n console.log(\"\\nFragments MCP \\u2014 Init\\n\");\n\n let configured = 0;\n let skipped = 0;\n\n for (const target of targets) {\n const configPath = target.configPath(projectRoot);\n const rel = relative(projectRoot, configPath) || configPath;\n const result = writeConfig(configPath, target);\n\n if (result === \"created\") {\n console.log(` + ${target.name} \\u2192 ${rel} (created)`);\n configured++;\n } else if (result === \"added\") {\n console.log(` + ${target.name} \\u2192 ${rel} (updated)`);\n configured++;\n } else if (result === \"exists\") {\n console.log(` · ${target.name} \\u2014 already configured`);\n skipped++;\n } else {\n console.log(` ! ${target.name} \\u2014 ${result}`);\n }\n }\n\n let rulesWritten: string[] = [];\n if (options.generateRules !== false) {\n rulesWritten = generateRulesFiles({\n data: emptyDesignSystemData(),\n brandName: \"Fragments\",\n outputDir: projectRoot,\n formats: [\"cursor\", \"claude\", \"copilot\"],\n force: false,\n });\n }\n\n // Detection mode ---------------------------------------------------------\n const hasFragmentsJson = existsSync(join(projectRoot, \"fragments.json\"));\n const hasTsConfig =\n existsSync(join(projectRoot, \"tsconfig.json\")) ||\n existsSync(join(projectRoot, \"tsconfig.app.json\"));\n\n console.log();\n if (hasFragmentsJson) {\n console.log(\" Mode: fragments.json (compiled definitions)\");\n } else if (hasTsConfig) {\n console.log(\" Mode: auto-extraction (zero-config)\");\n } else {\n console.log(\" Mode: will auto-detect on first run\");\n }\n\n // Summary ----------------------------------------------------------------\n console.log();\n if (configured > 0) {\n console.log(` Done \\u2014 configured ${configured} client(s).`);\n } else {\n console.log(\" Already configured \\u2014 no changes needed.\");\n }\n\n if (rulesWritten.length > 0) {\n console.log(` Wrote ${rulesWritten.length} rules file(s) for the validation loop.`);\n }\n\n console.log();\n console.log(\" Next steps:\");\n console.log(\" 1. Restart your editor to activate the MCP server\");\n console.log(' 2. Ask your AI assistant: \"What components are available?\"');\n console.log(' 3. After it generates UI, have it call the Fragments MCP govern tool');\n console.log();\n}\n\nfunction emptyDesignSystemData(): DesignSystemData {\n const capabilities: McpCapability[] = [];\n return {\n snapshot: {\n schemaVersion: 1,\n sourceType: \"custom-json\",\n sourceLabel: \"init\",\n capabilities,\n metadata: {},\n components: {},\n packageMap: {},\n },\n components: {},\n packageMap: {},\n capabilities: new Set(capabilities),\n };\n}\n"],"mappings":"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","names":["config"]}
@@ -0,0 +1,8 @@
1
+ import {
2
+ generateRulesFiles
3
+ } from "./chunk-WDQPNHZ2.js";
4
+ import "./chunk-7D4SUZUM.js";
5
+ export {
6
+ generateRulesFiles
7
+ };
8
+ //# sourceMappingURL=rules-JUZ3RABB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -6,6 +6,7 @@ import {
6
6
  __toCommonJS,
7
7
  __toESM
8
8
  } from "./chunk-VRPDT3Y6.js";
9
+ import "./chunk-7D4SUZUM.js";
9
10
 
10
11
  // ../extract/dist/sass.node-4XJK6YBF.js
11
12
  import { createRequire as __banner_createRequire } from "module";
@@ -130793,4 +130794,4 @@ immutable/dist/immutable.es.js:
130793
130794
  * SOFTWARE.
130794
130795
  *)
130795
130796
  */
130796
- //# sourceMappingURL=sass.node-4XJK6YBF-2NJM7G64.js.map
130797
+ //# sourceMappingURL=sass.node-4XJK6YBF-CPK77BO6.js.map