@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/README.md +33 -17
- package/dist/bin.js +7 -21
- package/dist/bin.js.map +1 -1
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/{chunk-VV2PJ75X.js → chunk-WDQPNHZ2.js} +37 -6
- package/dist/chunk-WDQPNHZ2.js.map +1 -0
- package/dist/chunk-YJTMK4JY.js +4270 -0
- package/dist/chunk-YJTMK4JY.js.map +1 -0
- package/dist/{constants-YXOTMY3I.js → constants-BLN4SSNH.js} +2 -1
- package/dist/dist-TTCI6TME.js +60962 -0
- package/dist/dist-TTCI6TME.js.map +1 -0
- package/dist/index.js +75 -11
- package/dist/index.js.map +1 -1
- package/dist/init.js +36 -0
- package/dist/init.js.map +1 -1
- package/dist/rules-JUZ3RABB.js +8 -0
- package/dist/rules-JUZ3RABB.js.map +1 -0
- package/dist/{sass.node-4XJK6YBF-2NJM7G64.js → sass.node-4XJK6YBF-CPK77BO6.js} +2 -1
- package/dist/{sass.node-4XJK6YBF-2NJM7G64.js.map → sass.node-4XJK6YBF-CPK77BO6.js.map} +1 -1
- package/dist/server.js +2 -2
- package/package.json +7 -7
- package/dist/chunk-6JMX4AMO.js +0 -4885
- package/dist/chunk-6JMX4AMO.js.map +0 -1
- package/dist/chunk-VV2PJ75X.js.map +0 -1
- package/dist/chunk-YSRGQDEB.js +0 -93
- package/dist/chunk-YSRGQDEB.js.map +0 -1
- package/dist/dist-V7D67NXS.js +0 -1093
- package/dist/dist-V7D67NXS.js.map +0 -1
- package/dist/rules-CKBRD3UL.js +0 -8
- /package/dist/{constants-YXOTMY3I.js.map → chunk-7D4SUZUM.js.map} +0 -0
- /package/dist/{rules-CKBRD3UL.js.map → constants-BLN4SSNH.js.map} +0 -0
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-
|
|
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-
|
|
32
|
-
import "./chunk-
|
|
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
|
-
|
|
106
|
-
|
|
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":"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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,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 @@
|
|
|
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-
|
|
130797
|
+
//# sourceMappingURL=sass.node-4XJK6YBF-CPK77BO6.js.map
|