@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/README.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# @fragments-sdk/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Source-available.** This package is distributed publicly on npm under FSL-1.1-MIT; source stays private. Contribute by filing an issue at https://github.com/fragments-sdk/fragments/issues.
|
|
4
|
+
|
|
5
|
+
Standalone MCP server for Fragments. It helps AI coding assistants validate generated UI against your design system: check candidate specs, repair safe governance failures, suggest design tokens before hardcoded CSS is written, and triage real Fragments Cloud findings.
|
|
6
|
+
|
|
7
|
+
The public MCP surface is intentionally small: `govern`, `validate_and_fix`, `tokens.suggest`, `findings_list`, `findings_summary`, `findings_for_file`, and `swap_to_canonical`.
|
|
4
8
|
|
|
5
9
|
## Setup
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
The recommended setup writes both MCP config and IDE rules that teach agents the validator loop:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @fragments-sdk/mcp@latest init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You can also add a **project-level** MCP config to your workspace root manually. This ensures the server runs from your project directory and auto-discovers `fragments.json` via `node_modules`.
|
|
8
18
|
|
|
9
19
|
### Cursor
|
|
10
20
|
|
|
@@ -67,32 +77,38 @@ The server auto-discovers `fragments.json` through two layers:
|
|
|
67
77
|
|
|
68
78
|
Once the project root is known, the server walks up from it, scans `package.json` dependencies for packages with a `"fragments"` field, and resolves through `node_modules` (supporting pnpm, Yarn, and npm).
|
|
69
79
|
|
|
80
|
+
## Validator Loop
|
|
81
|
+
|
|
82
|
+
Ask your AI assistant to follow this loop whenever it generates or edits JSX, class names, style props, or CSS:
|
|
83
|
+
|
|
84
|
+
1. Draft the UI spec or code change.
|
|
85
|
+
2. Call `govern({ spec })`.
|
|
86
|
+
3. If the result asks for revision, call `validate_and_fix({ spec, applyFixes: true })`.
|
|
87
|
+
4. Apply deterministic repairs and run `govern` again.
|
|
88
|
+
5. Before writing hardcoded CSS values, call `tokens.suggest({ property, value })` and use the returned `cssValue` when present.
|
|
89
|
+
|
|
70
90
|
## Tools
|
|
71
91
|
|
|
72
92
|
| Tool | Description |
|
|
73
93
|
|------|-------------|
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `graph` | Query the component relationship graph (dependencies, impact, composition, health) |
|
|
82
|
-
| `a11y` | Run an accessibility audit on a component (requires dev server) |
|
|
83
|
-
|
|
84
|
-
## How search works
|
|
94
|
+
| `govern` | Validate an AI-generated UI spec and return violations/fix guidance |
|
|
95
|
+
| `validate_and_fix` | Deterministically repair safe governance failures and return a fixed spec |
|
|
96
|
+
| `tokens.suggest` | Suggest the right design token for a CSS property and optional raw value |
|
|
97
|
+
| `findings_list` | List Cloud governance findings for agent planning |
|
|
98
|
+
| `findings_summary` | Summarize Cloud findings by severity, category, rule, and file |
|
|
99
|
+
| `findings_for_file` | Retrieve open Cloud findings for the file an agent is editing |
|
|
100
|
+
| `swap_to_canonical` | Suggest swaps from raw HTML (`<button>`, `<input>`, `<dialog>`, …) to the project's canonical components when a mapping is at confidence `auto` or `overridden` |
|
|
85
101
|
|
|
86
|
-
|
|
102
|
+
## How Token Suggestions Work
|
|
87
103
|
|
|
88
|
-
|
|
104
|
+
Validator tools run locally against your `fragments.json` or against a Cloud catalog when configured. Token suggestions use property-family-aware matching so color properties receive color tokens, spacing properties receive spacing tokens, and unsupported properties return an honest `noSuggestion` result.
|
|
89
105
|
|
|
90
106
|
## Options
|
|
91
107
|
|
|
92
108
|
```
|
|
93
109
|
-p, --project-root <path> Project root (default: cwd)
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
--cloud-api-key <key> Cloud API key for org-scoped catalog and findings
|
|
111
|
+
--generate-rules Generate IDE rules files and exit
|
|
96
112
|
```
|
|
97
113
|
|
|
98
114
|
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=a02beb71-df11-498d-8e1f-39de2e64ce5b" />
|
package/dist/bin.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
resolveDataAdapter,
|
|
5
5
|
resolveSearchApiKey,
|
|
6
6
|
startMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YJTMK4JY.js";
|
|
8
8
|
import "./chunk-4SVS3AA3.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-7D4SUZUM.js";
|
|
10
10
|
|
|
11
11
|
// src/bin.ts
|
|
12
12
|
var args = process.argv.slice(2);
|
|
@@ -45,8 +45,6 @@ Examples:
|
|
|
45
45
|
process.exit(0);
|
|
46
46
|
}
|
|
47
47
|
var projectRoot = process.cwd();
|
|
48
|
-
var viewerUrl;
|
|
49
|
-
var apiKey;
|
|
50
48
|
var cloudApiKey;
|
|
51
49
|
var source = "auto";
|
|
52
50
|
var generateRules = false;
|
|
@@ -56,12 +54,6 @@ for (let i = 0; i < args.length; i++) {
|
|
|
56
54
|
const arg = args[i];
|
|
57
55
|
if (arg === "--project-root" || arg === "-p") {
|
|
58
56
|
projectRoot = args[++i] ?? projectRoot;
|
|
59
|
-
} else if (arg === "--viewer-url" || arg === "-u") {
|
|
60
|
-
viewerUrl = args[++i];
|
|
61
|
-
} else if (arg === "--api-key" || arg === "-k") {
|
|
62
|
-
apiKey = args[++i];
|
|
63
|
-
} else if (arg === "--search-api-key") {
|
|
64
|
-
apiKey = args[++i];
|
|
65
57
|
} else if (arg === "--cloud-api-key") {
|
|
66
58
|
cloudApiKey = args[++i];
|
|
67
59
|
} else if (arg === "--source") {
|
|
@@ -85,16 +77,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
85
77
|
Usage: fragments-mcp [command] [options]
|
|
86
78
|
|
|
87
79
|
Standalone MCP server for design system intelligence.
|
|
88
|
-
Provides
|
|
80
|
+
Provides UI validation, deterministic repair, token suggestions, and Cloud findings.
|
|
89
81
|
|
|
90
82
|
Commands:
|
|
91
83
|
init Write MCP config for your AI editor(s)
|
|
92
84
|
|
|
93
85
|
Options:
|
|
94
86
|
-p, --project-root <path> Project root directory (default: cwd)
|
|
95
|
-
-u, --viewer-url <url> Viewer URL for render/fix/a11y tools (optional)
|
|
96
|
-
-k, --api-key <key> Premium API key for semantic vector search (optional)
|
|
97
|
-
--search-api-key <key> Explicit premium vector search key (same as --api-key)
|
|
98
87
|
--cloud-api-key <key> Cloud API key for org-scoped catalog access
|
|
99
88
|
--source <mode> Source override: auto, fragments-json, cloud, bundle, extract
|
|
100
89
|
--generate-rules Generate IDE rules files and exit
|
|
@@ -111,8 +100,7 @@ Quick start:
|
|
|
111
100
|
var fileConfig = loadConfigFile(projectRoot) ?? void 0;
|
|
112
101
|
var serverConfig = {
|
|
113
102
|
projectRoot,
|
|
114
|
-
|
|
115
|
-
searchApiKey: resolveSearchApiKey({ projectRoot, apiKey }, fileConfig),
|
|
103
|
+
searchApiKey: resolveSearchApiKey({ projectRoot }, fileConfig),
|
|
116
104
|
cloudApiKey,
|
|
117
105
|
source,
|
|
118
106
|
fileConfig
|
|
@@ -125,7 +113,7 @@ if (mode === "extract") {
|
|
|
125
113
|
}
|
|
126
114
|
if (generateRules) {
|
|
127
115
|
(async () => {
|
|
128
|
-
const { generateRulesFiles } = await import("./rules-
|
|
116
|
+
const { generateRulesFiles } = await import("./rules-JUZ3RABB.js");
|
|
129
117
|
const defaultFormats = ["cursor", "claude", "copilot"];
|
|
130
118
|
const formats = rulesFormats ? rulesFormats.split(",").map((f) => f.trim()) : defaultFormats;
|
|
131
119
|
const validFormats = /* @__PURE__ */ new Set(["cursor", "claude", "copilot"]);
|
|
@@ -136,7 +124,7 @@ if (generateRules) {
|
|
|
136
124
|
}
|
|
137
125
|
}
|
|
138
126
|
const data = await adapter.load(projectRoot);
|
|
139
|
-
const { BRAND } = await import("./constants-
|
|
127
|
+
const { BRAND } = await import("./constants-BLN4SSNH.js");
|
|
140
128
|
const brandName = fileConfig?.brand?.name ?? BRAND.name;
|
|
141
129
|
const files = generateRulesFiles({
|
|
142
130
|
data,
|
|
@@ -161,9 +149,7 @@ if (generateRules) {
|
|
|
161
149
|
} else {
|
|
162
150
|
startMcpServer({
|
|
163
151
|
projectRoot,
|
|
164
|
-
|
|
165
|
-
apiKey,
|
|
166
|
-
searchApiKey: resolveSearchApiKey({ projectRoot, apiKey }, fileConfig),
|
|
152
|
+
searchApiKey: resolveSearchApiKey({ projectRoot }, fileConfig),
|
|
167
153
|
cloudApiKey,
|
|
168
154
|
source,
|
|
169
155
|
fileConfig,
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { startMcpServer } from './server.js';\nimport { loadConfigFile } from './config.js';\nimport { resolveDataAdapter, resolveSearchApiKey } from './source-selection.js';\n\n// Parse command line arguments\nconst args = process.argv.slice(2);\n\n// ---------------------------------------------------------------------------\n// Subcommand: init\n// ---------------------------------------------------------------------------\nif (args[0] === 'init') {\n const { runInit } = await import('./init.js');\n const initArgs = args.slice(1);\n\n let projectRoot = process.cwd();\n let clients: string[] | undefined;\n\n for (let i = 0; i < initArgs.length; i++) {\n const arg = initArgs[i];\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = initArgs[++i] ?? projectRoot;\n } else if (arg === '--client' || arg === '-c') {\n clients = (initArgs[++i] ?? '').split(',').map((s) => s.trim());\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp init [options]\n\nWrite MCP server config for your AI editor(s).\nAuto-detects Claude Code, Cursor, VS Code, and Windsurf.\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -c, --client <list> Comma-separated clients: claude,cursor,vscode,windsurf\n (default: auto-detect)\n -h, --help Show this help message\n\nExamples:\n npx @fragments-sdk/mcp init\n npx @fragments-sdk/mcp init --client cursor,claude\n`);\n process.exit(0);\n }\n }\n\n runInit({ projectRoot, clients: clients as import('./init.js').Client[] });\n process.exit(0);\n}\n\n// ---------------------------------------------------------------------------\n// Main: MCP server mode\n// ---------------------------------------------------------------------------\nlet projectRoot = process.cwd();\nlet viewerUrl: string | undefined;\nlet apiKey: string | undefined;\nlet cloudApiKey: string | undefined;\nlet source: 'auto' | 'fragments-json' | 'cloud' | 'bundle' | 'extract' =\n 'auto';\nlet generateRules = false;\nlet rulesFormats: string | undefined;\nlet force = false;\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = args[++i] ?? projectRoot;\n } else if (arg === '--viewer-url' || arg === '-u') {\n viewerUrl = args[++i];\n } else if (arg === '--api-key' || arg === '-k') {\n apiKey = args[++i];\n } else if (arg === '--search-api-key') {\n apiKey = args[++i];\n } else if (arg === '--cloud-api-key') {\n cloudApiKey = args[++i];\n } else if (arg === '--source') {\n const next = args[++i];\n if (\n next === 'auto' ||\n next === 'fragments-json' ||\n next === 'cloud' ||\n next === 'bundle' ||\n next === 'extract'\n ) {\n source = next;\n } else {\n console.error(\n `Invalid source: \"${next}\". Valid values: auto, fragments-json, cloud, bundle, extract`,\n );\n process.exit(1);\n }\n } else if (arg === '--generate-rules') {\n generateRules = true;\n } else if (arg === '--formats') {\n rulesFormats = args[++i];\n } else if (arg === '--force') {\n force = true;\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp [command] [options]\n\nStandalone MCP server for design system intelligence.\nProvides component discovery, search, and governance — no CLI/Playwright needed.\n\nCommands:\n init Write MCP config for your AI editor(s)\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -u, --viewer-url <url> Viewer URL for render/fix/a11y tools (optional)\n -k, --api-key <key> Premium API key for semantic vector search (optional)\n --search-api-key <key> Explicit premium vector search key (same as --api-key)\n --cloud-api-key <key> Cloud API key for org-scoped catalog access\n --source <mode> Source override: auto, fragments-json, cloud, bundle, extract\n --generate-rules Generate IDE rules files and exit\n --formats <list> Comma-separated formats: cursor,claude,copilot (default: all)\n --force Overwrite existing rules files\n -h, --help Show this help message\n\nQuick start:\n npx @fragments-sdk/mcp init\n`);\n process.exit(0);\n }\n}\n\nconst fileConfig = loadConfigFile(projectRoot) ?? undefined;\nconst serverConfig = {\n projectRoot,\n viewerUrl,\n searchApiKey: resolveSearchApiKey({ projectRoot, apiKey }, fileConfig),\n cloudApiKey,\n source,\n fileConfig,\n};\nconst { adapter, mode } = resolveDataAdapter(serverConfig, fileConfig);\n\nif (mode === 'extract') {\n console.error(\n '[fragments-mcp] No fragments.json found — auto-extracting from source.',\n );\n}\n\n// --generate-rules: generate IDE rules and exit\nif (generateRules) {\n (async () => {\n const { generateRulesFiles } = await import('./rules.js');\n type RulesFormat = 'cursor' | 'claude' | 'copilot';\n\n const defaultFormats: RulesFormat[] = ['cursor', 'claude', 'copilot'];\n const formats: RulesFormat[] = rulesFormats\n ? (rulesFormats.split(',').map(f => f.trim()) as RulesFormat[])\n : defaultFormats;\n\n // Validate formats\n const validFormats = new Set(['cursor', 'claude', 'copilot']);\n for (const fmt of formats) {\n if (!validFormats.has(fmt)) {\n console.error(`Invalid format: \"${fmt}\". Valid formats: cursor, claude, copilot`);\n process.exit(1);\n }\n }\n\n // Load data using the same adapter\n const data = await adapter.load(projectRoot);\n\n // Resolve brand name from config\n const { BRAND } = await import('./constants.js');\n const brandName = fileConfig?.brand?.name ?? BRAND.name;\n\n // Generate rules\n const files = generateRulesFiles({\n data,\n brandName,\n outputDir: projectRoot,\n formats,\n force,\n });\n\n if (files.length === 0) {\n console.log('No rules files generated.');\n } else {\n console.log(`Generated ${files.length} rules file(s):`);\n for (const file of files) {\n console.log(` ${file}`);\n }\n }\n\n process.exit(0);\n })().catch((error) => {\n console.error('Failed to generate rules:', error instanceof Error ? error.message : error);\n process.exit(1);\n });\n} else {\n // Default: start MCP server\n startMcpServer({\n projectRoot,\n viewerUrl,\n apiKey,\n searchApiKey: resolveSearchApiKey({ projectRoot, apiKey }, fileConfig),\n cloudApiKey,\n source,\n fileConfig,\n adapter,\n }).catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["projectRoot"]}
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { startMcpServer } from './server.js';\nimport { loadConfigFile } from './config.js';\nimport { resolveDataAdapter, resolveSearchApiKey } from './source-selection.js';\n\n// Parse command line arguments\nconst args = process.argv.slice(2);\n\n// ---------------------------------------------------------------------------\n// Subcommand: init\n// ---------------------------------------------------------------------------\nif (args[0] === 'init') {\n const { runInit } = await import('./init.js');\n const initArgs = args.slice(1);\n\n let projectRoot = process.cwd();\n let clients: string[] | undefined;\n\n for (let i = 0; i < initArgs.length; i++) {\n const arg = initArgs[i];\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = initArgs[++i] ?? projectRoot;\n } else if (arg === '--client' || arg === '-c') {\n clients = (initArgs[++i] ?? '').split(',').map((s) => s.trim());\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp init [options]\n\nWrite MCP server config for your AI editor(s).\nAuto-detects Claude Code, Cursor, VS Code, and Windsurf.\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -c, --client <list> Comma-separated clients: claude,cursor,vscode,windsurf\n (default: auto-detect)\n -h, --help Show this help message\n\nExamples:\n npx @fragments-sdk/mcp init\n npx @fragments-sdk/mcp init --client cursor,claude\n`);\n process.exit(0);\n }\n }\n\n runInit({ projectRoot, clients: clients as import('./init.js').Client[] });\n process.exit(0);\n}\n\n// ---------------------------------------------------------------------------\n// Main: MCP server mode\n// ---------------------------------------------------------------------------\nlet projectRoot = process.cwd();\nlet cloudApiKey: string | undefined;\nlet source: 'auto' | 'fragments-json' | 'cloud' | 'bundle' | 'extract' =\n 'auto';\nlet generateRules = false;\nlet rulesFormats: string | undefined;\nlet force = false;\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = args[++i] ?? projectRoot;\n } else if (arg === '--cloud-api-key') {\n cloudApiKey = args[++i];\n } else if (arg === '--source') {\n const next = args[++i];\n if (\n next === 'auto' ||\n next === 'fragments-json' ||\n next === 'cloud' ||\n next === 'bundle' ||\n next === 'extract'\n ) {\n source = next;\n } else {\n console.error(\n `Invalid source: \"${next}\". Valid values: auto, fragments-json, cloud, bundle, extract`,\n );\n process.exit(1);\n }\n } else if (arg === '--generate-rules') {\n generateRules = true;\n } else if (arg === '--formats') {\n rulesFormats = args[++i];\n } else if (arg === '--force') {\n force = true;\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp [command] [options]\n\nStandalone MCP server for design system intelligence.\nProvides UI validation, deterministic repair, token suggestions, and Cloud findings.\n\nCommands:\n init Write MCP config for your AI editor(s)\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n --cloud-api-key <key> Cloud API key for org-scoped catalog access\n --source <mode> Source override: auto, fragments-json, cloud, bundle, extract\n --generate-rules Generate IDE rules files and exit\n --formats <list> Comma-separated formats: cursor,claude,copilot (default: all)\n --force Overwrite existing rules files\n -h, --help Show this help message\n\nQuick start:\n npx @fragments-sdk/mcp init\n`);\n process.exit(0);\n }\n}\n\nconst fileConfig = loadConfigFile(projectRoot) ?? undefined;\nconst serverConfig = {\n projectRoot,\n searchApiKey: resolveSearchApiKey({ projectRoot }, fileConfig),\n cloudApiKey,\n source,\n fileConfig,\n};\nconst { adapter, mode } = resolveDataAdapter(serverConfig, fileConfig);\n\nif (mode === 'extract') {\n console.error(\n '[fragments-mcp] No fragments.json found — auto-extracting from source.',\n );\n}\n\n// --generate-rules: generate IDE rules and exit\nif (generateRules) {\n (async () => {\n const { generateRulesFiles } = await import('./rules.js');\n type RulesFormat = 'cursor' | 'claude' | 'copilot';\n\n const defaultFormats: RulesFormat[] = ['cursor', 'claude', 'copilot'];\n const formats: RulesFormat[] = rulesFormats\n ? (rulesFormats.split(',').map(f => f.trim()) as RulesFormat[])\n : defaultFormats;\n\n // Validate formats\n const validFormats = new Set(['cursor', 'claude', 'copilot']);\n for (const fmt of formats) {\n if (!validFormats.has(fmt)) {\n console.error(`Invalid format: \"${fmt}\". Valid formats: cursor, claude, copilot`);\n process.exit(1);\n }\n }\n\n // Load data using the same adapter\n const data = await adapter.load(projectRoot);\n\n // Resolve brand name from config\n const { BRAND } = await import('./constants.js');\n const brandName = fileConfig?.brand?.name ?? BRAND.name;\n\n // Generate rules\n const files = generateRulesFiles({\n data,\n brandName,\n outputDir: projectRoot,\n formats,\n force,\n });\n\n if (files.length === 0) {\n console.log('No rules files generated.');\n } else {\n console.log(`Generated ${files.length} rules file(s):`);\n for (const file of files) {\n console.log(` ${file}`);\n }\n }\n\n process.exit(0);\n })().catch((error) => {\n console.error('Failed to generate rules:', error instanceof Error ? error.message : error);\n process.exit(1);\n });\n} else {\n // Default: start MCP server\n startMcpServer({\n projectRoot,\n searchApiKey: resolveSearchApiKey({ projectRoot }, fileConfig),\n cloudApiKey,\n source,\n fileConfig,\n adapter,\n }).catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["projectRoot"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
__require,
|
|
35
|
+
__commonJS,
|
|
36
|
+
__toESM
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-7D4SUZUM.js.map
|
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getGuidanceWhen,
|
|
3
|
-
getGuidanceWhenNot
|
|
4
|
-
} from "./chunk-YSRGQDEB.js";
|
|
5
|
-
|
|
6
1
|
// src/rules.ts
|
|
7
2
|
import { join } from "path";
|
|
8
3
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
4
|
+
|
|
5
|
+
// src/snapshot-helpers.ts
|
|
6
|
+
function getGuidanceWhen(component) {
|
|
7
|
+
if (component.guidance.when.length > 0) {
|
|
8
|
+
return component.guidance.when;
|
|
9
|
+
}
|
|
10
|
+
if (component.guidance.usageGuidance) {
|
|
11
|
+
return [component.guidance.usageGuidance];
|
|
12
|
+
}
|
|
13
|
+
if (component.guidance.dos.length > 0) {
|
|
14
|
+
return component.guidance.dos;
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
function getGuidanceWhenNot(component) {
|
|
19
|
+
if (component.guidance.whenNot.length > 0) {
|
|
20
|
+
return component.guidance.whenNot;
|
|
21
|
+
}
|
|
22
|
+
if (component.guidance.donts.length > 0) {
|
|
23
|
+
return component.guidance.donts;
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/rules.ts
|
|
9
29
|
function generateRulesFiles(options) {
|
|
10
30
|
const { data, brandName, outputDir, formats, force = false } = options;
|
|
11
31
|
const content = buildRulesContent(data, brandName);
|
|
@@ -49,6 +69,17 @@ function buildRulesContent(data, brandName) {
|
|
|
49
69
|
`);
|
|
50
70
|
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.
|
|
51
71
|
`);
|
|
72
|
+
sections.push("## Agent Validation Loop\n");
|
|
73
|
+
sections.push("After generating or editing JSX, component props, class names, or CSS, call the Fragments MCP `govern` tool with the candidate UI spec before treating the work as complete.");
|
|
74
|
+
sections.push("If `govern` returns violations or a `nextAction` that asks for revision, call `validate_and_fix` with the same spec, apply any deterministic repair it returns, then call `govern` again.");
|
|
75
|
+
sections.push("Before writing a hardcoded CSS value, call `tokens.suggest` with the CSS property and value. Use the returned `cssValue` when a recommendation is present; keep the raw value only when the tool returns `noSuggestion: true`.\n");
|
|
76
|
+
sections.push("Example loop:");
|
|
77
|
+
sections.push("```text");
|
|
78
|
+
sections.push("1. Draft the UI spec for the component or screen.");
|
|
79
|
+
sections.push("2. govern({ spec })");
|
|
80
|
+
sections.push("3. If it fails: validate_and_fix({ spec, applyFixes: true })");
|
|
81
|
+
sections.push("4. Re-run govern({ spec: fixedSpec }) before finalizing.");
|
|
82
|
+
sections.push("```\n");
|
|
52
83
|
const components = Object.values(data.components);
|
|
53
84
|
if (components.length > 0) {
|
|
54
85
|
sections.push("## Components\n");
|
|
@@ -109,4 +140,4 @@ function buildRulesContent(data, brandName) {
|
|
|
109
140
|
export {
|
|
110
141
|
generateRulesFiles
|
|
111
142
|
};
|
|
112
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-WDQPNHZ2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rules.ts","../src/snapshot-helpers.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 sections.push('## Agent Validation Loop\\n');\n sections.push('After generating or editing JSX, component props, class names, or CSS, call the Fragments MCP `govern` tool with the candidate UI spec before treating the work as complete.');\n sections.push('If `govern` returns violations or a `nextAction` that asks for revision, call `validate_and_fix` with the same spec, apply any deterministic repair it returns, then call `govern` again.');\n sections.push('Before writing a hardcoded CSS value, call `tokens.suggest` with the CSS property and value. Use the returned `cssValue` when a recommendation is present; keep the raw value only when the tool returns `noSuggestion: true`.\\n');\n sections.push('Example loop:');\n sections.push('```text');\n sections.push('1. Draft the UI spec for the component or screen.');\n sections.push('2. govern({ spec })');\n sections.push('3. If it fails: validate_and_fix({ spec, applyFixes: true })');\n sections.push('4. Re-run govern({ spec: fixedSpec }) before finalizing.');\n sections.push('```\\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","import type { McpBlock, McpComponent, McpSnapshot } from '@fragments-sdk/core';\n\nexport type ResolvedMcpComponent = McpComponent & {\n isCanonical?: boolean;\n};\n\nexport function listComponents(\n snapshot: Pick<McpSnapshot, 'components'>,\n): ResolvedMcpComponent[] {\n return Object.values(snapshot.components);\n}\n\nexport function componentNames(snapshot: Pick<McpSnapshot, 'components'>): string[] {\n return listComponents(snapshot).map((component) => component.name);\n}\n\nexport function listBlocks(snapshot: Pick<McpSnapshot, 'blocks'>): McpBlock[] {\n return Object.values(snapshot.blocks ?? {});\n}\n\nexport function findComponentByName(\n snapshot: Pick<McpSnapshot, 'components'>,\n name: string,\n): ResolvedMcpComponent | undefined {\n const query = name.toLowerCase();\n return listComponents(snapshot).find(\n (component) => component.name.toLowerCase() === query,\n );\n}\n\nfunction normalizeComponentRef(value: string): string {\n return value.trim().toLowerCase();\n}\n\nfunction buildCompoundComponent(\n parent: ResolvedMcpComponent,\n childName: string,\n): ResolvedMcpComponent | undefined {\n const child = parent.compoundChildren.find(\n (entry) => normalizeComponentRef(entry.name) === normalizeComponentRef(childName),\n );\n if (!child) return undefined;\n\n const fullName = child.name.includes('.')\n ? child.name\n : `${parent.name}.${child.name}`;\n\n return {\n ...parent,\n id: child.componentId ?? `${parent.id}::${fullName}`,\n name: fullName,\n description: child.description ?? '',\n props: {},\n propsSummary: [],\n examples: [],\n relations: [],\n compoundChildren: [],\n publicRef: child.componentId ?? fullName,\n publicSlug: null,\n parentComponentId: parent.id,\n parentComponentName: parent.name,\n };\n}\n\nexport function findComponent(\n snapshot: Pick<McpSnapshot, 'components'>,\n reference: string,\n): ResolvedMcpComponent | undefined {\n const query = normalizeComponentRef(reference);\n const components = listComponents(snapshot);\n\n const exact = components.find((component) => {\n return (\n normalizeComponentRef(component.name) === query ||\n normalizeComponentRef(component.id) === query ||\n normalizeComponentRef(component.publicRef ?? '') === query\n );\n });\n if (exact) return exact;\n\n if (reference.includes('.')) {\n const [parentName, ...childParts] = reference.split('.');\n const childName = childParts.join('.');\n const parent = components.find((component) => {\n return (\n normalizeComponentRef(component.name) === normalizeComponentRef(parentName) ||\n normalizeComponentRef(component.publicRef ?? '') ===\n normalizeComponentRef(parentName) ||\n normalizeComponentRef(component.id) === normalizeComponentRef(parentName)\n );\n });\n\n if (parent) {\n const child = buildCompoundComponent(parent, childName);\n if (child) return child;\n }\n }\n\n return undefined;\n}\n\nexport function getGuidanceWhen(component: ResolvedMcpComponent): string[] {\n if (component.guidance.when.length > 0) {\n return component.guidance.when;\n }\n if (component.guidance.usageGuidance) {\n return [component.guidance.usageGuidance];\n }\n if (component.guidance.dos.length > 0) {\n return component.guidance.dos;\n }\n return [];\n}\n\nexport function getGuidanceWhenNot(component: ResolvedMcpComponent): string[] {\n if (component.guidance.whenNot.length > 0) {\n return component.guidance.whenNot;\n }\n if (component.guidance.donts.length > 0) {\n return component.guidance.donts;\n }\n return [];\n}\n"],"mappings":"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","names":[]}
|