@fragments-sdk/mcp 0.9.0 → 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,13 @@ import {
22
17
  telemetryMiddleware,
23
18
  tokensFromCompiledTokenData,
24
19
  validateSnapshot
25
- } from "./chunk-YSNIGHNU.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";
33
27
  import "./chunk-7D4SUZUM.js";
34
28
 
35
29
  // src/adapters/custom-json.ts
@@ -40,6 +34,7 @@ var CustomJsonAdapter = class {
40
34
  constructor(filePath) {
41
35
  this.filePath = filePath;
42
36
  }
37
+ filePath;
43
38
  name = "custom-json";
44
39
  discover() {
45
40
  return existsSync(this.filePath) ? [this.filePath] : [];
@@ -71,6 +66,7 @@ var CustomJsonAdapter = class {
71
66
  }
72
67
  const packageMap = raw.packageMap ?? {};
73
68
  const defaultPackageName = raw.defaultPackageName;
69
+ const metadata = raw.metadata;
74
70
  const components = Object.fromEntries(
75
71
  Object.entries(raw.components).map(
76
72
  ([key, fragment]) => [
@@ -103,8 +99,11 @@ var CustomJsonAdapter = class {
103
99
  performanceSummary: raw.performanceSummary
104
100
  }),
105
101
  metadata: {
106
- packageName: defaultPackageName,
107
- importPath: defaultPackageName
102
+ designSystemName: metadata?.designSystemName,
103
+ packageName: metadata?.packageName ?? defaultPackageName,
104
+ importPath: metadata?.importPath ?? defaultPackageName,
105
+ revision: metadata?.revision,
106
+ updatedAt: metadata?.updatedAt
108
107
  },
109
108
  components,
110
109
  blocks,
@@ -128,6 +127,71 @@ var CustomJsonAdapter = class {
128
127
  }
129
128
  };
130
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
+
131
195
  // src/search-config.ts
132
196
  var DEFAULT_SEARCH_CONFIG = {
133
197
  synonyms: SYNONYM_MAP,
@@ -143,7 +207,6 @@ export {
143
207
  BUILTIN_TOOLS,
144
208
  CORE_TOOLS,
145
209
  CustomJsonAdapter,
146
- DEFAULT_ENDPOINTS,
147
210
  DEFAULT_SEARCH_CONFIG,
148
211
  FragmentsJsonAdapter,
149
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":"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","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,6 @@
1
+ import {
2
+ generateRulesFiles
3
+ } from "./chunk-WDQPNHZ2.js";
1
4
  import "./chunk-7D4SUZUM.js";
2
5
 
3
6
  // src/init.ts
@@ -113,6 +116,16 @@ function runInit(options) {
113
116
  console.log(` ! ${target.name} \u2014 ${result}`);
114
117
  }
115
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
+ }
116
129
  const hasFragmentsJson = existsSync(join(projectRoot, "fragments.json"));
117
130
  const hasTsConfig = existsSync(join(projectRoot, "tsconfig.json")) || existsSync(join(projectRoot, "tsconfig.app.json"));
118
131
  console.log();
@@ -129,12 +142,33 @@ function runInit(options) {
129
142
  } else {
130
143
  console.log(" Already configured \u2014 no changes needed.");
131
144
  }
145
+ if (rulesWritten.length > 0) {
146
+ console.log(` Wrote ${rulesWritten.length} rules file(s) for the validation loop.`);
147
+ }
132
148
  console.log();
133
149
  console.log(" Next steps:");
134
150
  console.log(" 1. Restart your editor to activate the MCP server");
135
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");
136
153
  console.log();
137
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
+ }
138
172
  export {
139
173
  runInit
140
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
package/dist/server.js CHANGED
@@ -2,9 +2,8 @@ import {
2
2
  createMcpServer,
3
3
  createSandboxServer,
4
4
  startMcpServer
5
- } from "./chunk-YSNIGHNU.js";
5
+ } from "./chunk-YJTMK4JY.js";
6
6
  import "./chunk-4SVS3AA3.js";
7
- import "./chunk-YSRGQDEB.js";
8
7
  import "./chunk-7D4SUZUM.js";
9
8
  export {
10
9
  createMcpServer,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@fragments-sdk/mcp",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "license": "FSL-1.1-MIT",
5
- "description": "Standalone MCP server for Fragments design system — zero-config component discovery with semantic search",
5
+ "description": "Standalone MCP validator for Fragments design systems",
6
6
  "mcpName": "io.github.ConanMcN/fragments-mcp",
7
7
  "author": "Conan McNicholl",
8
8
  "homepage": "https://usefragments.com",
@@ -62,8 +62,9 @@
62
62
  "@modelcontextprotocol/sdk": "^1.0.0",
63
63
  "@orama/orama": "^3.1.18",
64
64
  "typescript": "^5.7.2",
65
+ "@fragments-sdk/classifier": "0.2.0",
65
66
  "@fragments-sdk/core": "3.1.0",
66
- "@fragments-sdk/context": "0.7.2"
67
+ "@fragments-sdk/context": "0.8.0"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@types/node": "^22.0.0",
@@ -71,7 +72,7 @@
71
72
  "tsup": "^8.3.5",
72
73
  "tsx": "^4.19.0",
73
74
  "vitest": "^2.1.8",
74
- "@fragments-sdk/extract": "0.1.3"
75
+ "@fragments-sdk/extract": "0.2.0"
75
76
  },
76
77
  "scripts": {
77
78
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/rules.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport type { McpComponent } from '@fragments-sdk/core';\nimport type { DesignSystemData } from './types.js';\nimport { getGuidanceWhen, getGuidanceWhenNot } from './snapshot-helpers.js';\n\nexport type RulesFormat = 'cursor' | 'claude' | 'copilot';\n\nexport interface RulesGeneratorOptions {\n data: DesignSystemData;\n brandName: string;\n outputDir: string;\n formats: RulesFormat[];\n /** Overwrite existing files (default: false — skip and warn) */\n force?: boolean;\n}\n\n/**\n * Generate IDE rules files from design system data.\n * Returns list of files written. Skips existing files unless force is true.\n */\nexport function generateRulesFiles(options: RulesGeneratorOptions): string[] {\n const { data, brandName, outputDir, formats, force = false } = options;\n const content = buildRulesContent(data, brandName);\n const files: string[] = [];\n\n for (const format of formats) {\n let filePath: string;\n let fileContent: string;\n\n switch (format) {\n case 'cursor':\n filePath = join(outputDir, '.cursorrules');\n fileContent = content;\n break;\n case 'claude':\n filePath = join(outputDir, 'CLAUDE.md');\n fileContent = `# CLAUDE.md\\n\\nThis file provides guidance to Claude Code when working with code in this repository.\\n\\n${content}`;\n break;\n case 'copilot': {\n const dir = join(outputDir, '.github');\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n filePath = join(dir, 'copilot-instructions.md');\n fileContent = content;\n break;\n }\n }\n\n if (!force && existsSync(filePath)) {\n console.error(` Skipped ${filePath} (already exists, use --force to overwrite)`);\n continue;\n }\n\n writeFileSync(filePath, fileContent, 'utf-8');\n files.push(filePath);\n }\n\n return files;\n}\n\nfunction buildRulesContent(data: DesignSystemData, brandName: string): string {\n const sections: string[] = [];\n\n // Header\n sections.push(`# ${brandName} Design System Rules\\n`);\n sections.push(`Always use ${brandName} components and design tokens when building UI. Do not use raw HTML elements, Tailwind classes, or hardcoded CSS values when a design system component or token exists.\\n`);\n\n // Component inventory\n const components: McpComponent[] = Object.values(data.components);\n if (components.length > 0) {\n sections.push('## Components\\n');\n const byCategory = new Map<string, string[]>();\n for (const c of components) {\n const cat = c.category || 'uncategorized';\n if (!byCategory.has(cat)) byCategory.set(cat, []);\n byCategory.get(cat)!.push(c.name);\n }\n for (const [cat, names] of byCategory) {\n sections.push(`### ${cat}\\n`);\n sections.push(names.map(n => `- ${n}`).join('\\n'));\n sections.push('');\n }\n }\n\n // Import pattern\n if (data.defaultPackageName) {\n sections.push('## Imports\\n');\n sections.push(`Import components from \\`${data.defaultPackageName}\\`:\\n`);\n sections.push('```typescript');\n const exampleNames = components.slice(0, 3).map(c => c.name);\n sections.push(`import { ${exampleNames.join(', ')} } from '${data.defaultPackageName}';`);\n sections.push('```\\n');\n }\n\n // Tokens\n if (data.tokens && data.tokens.total > 0) {\n sections.push('## Design Tokens\\n');\n sections.push(`Prefix: \\`${data.tokens.prefix}\\`. Use \\`var(--token-name)\\` in CSS/styles.\\n`);\n sections.push('Available categories:');\n for (const [cat, tokens] of Object.entries(data.tokens.categories) as Array<\n [string, typeof data.tokens.categories[string]]\n >) {\n sections.push(`- **${cat}** (${tokens.length} tokens)`);\n }\n sections.push('');\n }\n\n // Usage guidelines (aggregated from all components)\n const allDos: string[] = [];\n const allDonts: string[] = [];\n for (const c of components) {\n allDos.push(...getGuidanceWhen(c).slice(0, 1).map(w => `${c.name}: ${w}`));\n allDonts.push(...getGuidanceWhenNot(c).slice(0, 1).map(w => `${c.name}: ${w}`));\n }\n if (allDos.length > 0 || allDonts.length > 0) {\n sections.push('## Usage Guidelines\\n');\n if (allDos.length > 0) {\n sections.push('**Do:**');\n sections.push(allDos.slice(0, 10).map(d => `- ${d}`).join('\\n'));\n sections.push('');\n }\n if (allDonts.length > 0) {\n sections.push('**Don\\'t:**');\n sections.push(allDonts.slice(0, 10).map(d => `- ${d}`).join('\\n'));\n sections.push('');\n }\n }\n\n return sections.join('\\n');\n}\n"],"mappings":"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","names":[]}