devlens-mcp 0.3.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/.claude/settings.json +12 -0
- package/.claude/settings.local.json +17 -0
- package/INSTALLATION_GUIDE.md +354 -0
- package/QUICK_START.md +153 -0
- package/README.md +354 -0
- package/bin/cli.ts +22 -0
- package/bin/register.ts +96 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +20 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bin/register.d.ts +10 -0
- package/dist/bin/register.d.ts.map +1 -0
- package/dist/bin/register.js +92 -0
- package/dist/bin/register.js.map +1 -0
- package/dist/src/config/devlens-config.d.ts +92 -0
- package/dist/src/config/devlens-config.d.ts.map +1 -0
- package/dist/src/config/devlens-config.js +70 -0
- package/dist/src/config/devlens-config.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/metro/cdp-client.d.ts +48 -0
- package/dist/src/metro/cdp-client.d.ts.map +1 -0
- package/dist/src/metro/cdp-client.js +127 -0
- package/dist/src/metro/cdp-client.js.map +1 -0
- package/dist/src/metro/log-collector.d.ts +30 -0
- package/dist/src/metro/log-collector.d.ts.map +1 -0
- package/dist/src/metro/log-collector.js +114 -0
- package/dist/src/metro/log-collector.js.map +1 -0
- package/dist/src/metro/metro-bridge.d.ts +56 -0
- package/dist/src/metro/metro-bridge.d.ts.map +1 -0
- package/dist/src/metro/metro-bridge.js +255 -0
- package/dist/src/metro/metro-bridge.js.map +1 -0
- package/dist/src/metro/network-inspector.d.ts +34 -0
- package/dist/src/metro/network-inspector.d.ts.map +1 -0
- package/dist/src/metro/network-inspector.js +100 -0
- package/dist/src/metro/network-inspector.js.map +1 -0
- package/dist/src/platform/android/adb.d.ts +50 -0
- package/dist/src/platform/android/adb.d.ts.map +1 -0
- package/dist/src/platform/android/adb.js +137 -0
- package/dist/src/platform/android/adb.js.map +1 -0
- package/dist/src/platform/android/android-device.d.ts +21 -0
- package/dist/src/platform/android/android-device.d.ts.map +1 -0
- package/dist/src/platform/android/android-device.js +94 -0
- package/dist/src/platform/android/android-device.js.map +1 -0
- package/dist/src/platform/android/ui-automator.d.ts +17 -0
- package/dist/src/platform/android/ui-automator.d.ts.map +1 -0
- package/dist/src/platform/android/ui-automator.js +126 -0
- package/dist/src/platform/android/ui-automator.js.map +1 -0
- package/dist/src/platform/device-manager.d.ts +28 -0
- package/dist/src/platform/device-manager.d.ts.map +1 -0
- package/dist/src/platform/device-manager.js +185 -0
- package/dist/src/platform/device-manager.js.map +1 -0
- package/dist/src/platform/device.d.ts +86 -0
- package/dist/src/platform/device.d.ts.map +1 -0
- package/dist/src/platform/device.js +7 -0
- package/dist/src/platform/device.js.map +1 -0
- package/dist/src/platform/ios/accessibility.d.ts +17 -0
- package/dist/src/platform/ios/accessibility.d.ts.map +1 -0
- package/dist/src/platform/ios/accessibility.js +159 -0
- package/dist/src/platform/ios/accessibility.js.map +1 -0
- package/dist/src/platform/ios/ios-device.d.ts +22 -0
- package/dist/src/platform/ios/ios-device.d.ts.map +1 -0
- package/dist/src/platform/ios/ios-device.js +97 -0
- package/dist/src/platform/ios/ios-device.js.map +1 -0
- package/dist/src/platform/ios/simctl.d.ts +54 -0
- package/dist/src/platform/ios/simctl.d.ts.map +1 -0
- package/dist/src/platform/ios/simctl.js +192 -0
- package/dist/src/platform/ios/simctl.js.map +1 -0
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +176 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/snapshot/formatter.d.ts +18 -0
- package/dist/src/snapshot/formatter.d.ts.map +1 -0
- package/dist/src/snapshot/formatter.js +86 -0
- package/dist/src/snapshot/formatter.js.map +1 -0
- package/dist/src/snapshot/ref-registry.d.ts +67 -0
- package/dist/src/snapshot/ref-registry.d.ts.map +1 -0
- package/dist/src/snapshot/ref-registry.js +169 -0
- package/dist/src/snapshot/ref-registry.js.map +1 -0
- package/dist/src/snapshot/snapshot-differ.d.ts +57 -0
- package/dist/src/snapshot/snapshot-differ.d.ts.map +1 -0
- package/dist/src/snapshot/snapshot-differ.js +153 -0
- package/dist/src/snapshot/snapshot-differ.js.map +1 -0
- package/dist/src/tools/app-tools.d.ts +71 -0
- package/dist/src/tools/app-tools.d.ts.map +1 -0
- package/dist/src/tools/app-tools.js +97 -0
- package/dist/src/tools/app-tools.js.map +1 -0
- package/dist/src/tools/device-tools.d.ts +53 -0
- package/dist/src/tools/device-tools.d.ts.map +1 -0
- package/dist/src/tools/device-tools.js +86 -0
- package/dist/src/tools/device-tools.js.map +1 -0
- package/dist/src/tools/ds-tools.d.ts +65 -0
- package/dist/src/tools/ds-tools.d.ts.map +1 -0
- package/dist/src/tools/ds-tools.js +314 -0
- package/dist/src/tools/ds-tools.js.map +1 -0
- package/dist/src/tools/interaction-tools.d.ts +248 -0
- package/dist/src/tools/interaction-tools.d.ts.map +1 -0
- package/dist/src/tools/interaction-tools.js +391 -0
- package/dist/src/tools/interaction-tools.js.map +1 -0
- package/dist/src/tools/metro-tools.d.ts +115 -0
- package/dist/src/tools/metro-tools.d.ts.map +1 -0
- package/dist/src/tools/metro-tools.js +270 -0
- package/dist/src/tools/metro-tools.js.map +1 -0
- package/dist/src/tools/navigation-tools.d.ts +36 -0
- package/dist/src/tools/navigation-tools.d.ts.map +1 -0
- package/dist/src/tools/navigation-tools.js +60 -0
- package/dist/src/tools/navigation-tools.js.map +1 -0
- package/dist/src/tools/screenshot-tools.d.ts +298 -0
- package/dist/src/tools/screenshot-tools.d.ts.map +1 -0
- package/dist/src/tools/screenshot-tools.js +565 -0
- package/dist/src/tools/screenshot-tools.js.map +1 -0
- package/dist/src/tools/snapshot-tools.d.ts +161 -0
- package/dist/src/tools/snapshot-tools.d.ts.map +1 -0
- package/dist/src/tools/snapshot-tools.js +479 -0
- package/dist/src/tools/snapshot-tools.js.map +1 -0
- package/dist/src/utils/image-preprocess.d.ts +49 -0
- package/dist/src/utils/image-preprocess.d.ts.map +1 -0
- package/dist/src/utils/image-preprocess.js +322 -0
- package/dist/src/utils/image-preprocess.js.map +1 -0
- package/dist/src/utils/retry.d.ts +21 -0
- package/dist/src/utils/retry.d.ts.map +1 -0
- package/dist/src/utils/retry.js +33 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/visual/comparator.d.ts +51 -0
- package/dist/src/visual/comparator.d.ts.map +1 -0
- package/dist/src/visual/comparator.js +119 -0
- package/dist/src/visual/comparator.js.map +1 -0
- package/dist/src/visual/layout-analyzer.d.ts +64 -0
- package/dist/src/visual/layout-analyzer.d.ts.map +1 -0
- package/dist/src/visual/layout-analyzer.js +198 -0
- package/dist/src/visual/layout-analyzer.js.map +1 -0
- package/dist/src/visual/screenshot.d.ts +17 -0
- package/dist/src/visual/screenshot.d.ts.map +1 -0
- package/dist/src/visual/screenshot.js +39 -0
- package/dist/src/visual/screenshot.js.map +1 -0
- package/docs/figma-workflow.md +289 -0
- package/docs/setup-guide.md +360 -0
- package/docs/tool-reference.md +622 -0
- package/package.json +57 -0
- package/src/config/devlens-config.ts +76 -0
- package/src/index.ts +5 -0
- package/src/metro/cdp-client.ts +160 -0
- package/src/metro/log-collector.ts +137 -0
- package/src/metro/metro-bridge.ts +307 -0
- package/src/metro/network-inspector.ts +134 -0
- package/src/platform/android/adb.ts +200 -0
- package/src/platform/android/android-device.ts +116 -0
- package/src/platform/android/ui-automator.ts +141 -0
- package/src/platform/device-manager.ts +229 -0
- package/src/platform/device.ts +110 -0
- package/src/platform/ios/accessibility.ts +189 -0
- package/src/platform/ios/ios-device.ts +116 -0
- package/src/platform/ios/simctl.ts +244 -0
- package/src/server.ts +228 -0
- package/src/snapshot/formatter.ts +102 -0
- package/src/snapshot/ref-registry.ts +230 -0
- package/src/snapshot/snapshot-differ.ts +220 -0
- package/src/tools/app-tools.ts +111 -0
- package/src/tools/device-tools.ts +96 -0
- package/src/tools/ds-tools.ts +395 -0
- package/src/tools/interaction-tools.ts +467 -0
- package/src/tools/metro-tools.ts +320 -0
- package/src/tools/navigation-tools.ts +71 -0
- package/src/tools/screenshot-tools.ts +698 -0
- package/src/tools/snapshot-tools.ts +585 -0
- package/src/utils/image-preprocess.ts +430 -0
- package/src/utils/retry.ts +51 -0
- package/src/visual/comparator.ts +191 -0
- package/src/visual/layout-analyzer.ts +283 -0
- package/src/visual/screenshot.ts +49 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
3
|
+
import { resolve, join } from "path";
|
|
4
|
+
import type { DesignSystemConfig } from "../config/devlens-config.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Design System Context tool.
|
|
8
|
+
*
|
|
9
|
+
* When devlens.config.json is configured with a designSystem block,
|
|
10
|
+
* this tool scans the consumer project and returns:
|
|
11
|
+
* - Design token values (colors, spacing, typography, etc.)
|
|
12
|
+
* - TypeScript prop interfaces for DS components
|
|
13
|
+
* - Component usage patterns found in the codebase
|
|
14
|
+
*
|
|
15
|
+
* The AI uses this context to write DS-compliant code when fixing
|
|
16
|
+
* differences found by devlens_compare_with_figma.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface TokenEntry {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
group: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ComponentUsagePattern {
|
|
28
|
+
componentName: string;
|
|
29
|
+
importedFrom: string;
|
|
30
|
+
usageCount: number;
|
|
31
|
+
/** prop name → distinct values seen in the codebase */
|
|
32
|
+
sampleProps: Record<string, string[]>;
|
|
33
|
+
sourceFiles: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Tool Schema ─────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export const dsToolSchemas = {
|
|
39
|
+
devlens_ds_context: {
|
|
40
|
+
description:
|
|
41
|
+
"Get design system context: available DS3 components with their TypeScript prop interfaces, design token values (colors, spacing, typography), and usage patterns from the codebase. Use this before fixing code — it tells you exactly which components and tokens to use. Requires 'designSystem' to be configured in devlens.config.json (pointed to by DEVLENS_CONFIG env var).",
|
|
42
|
+
parameters: z.object({
|
|
43
|
+
includeTokens: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.default(true)
|
|
46
|
+
.describe("Include design token values (colors, spacing, typography)"),
|
|
47
|
+
includeComponents: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.default(true)
|
|
50
|
+
.describe("Scan source files for DS component usage patterns and prop value combinations"),
|
|
51
|
+
includeInterfaces: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.default(true)
|
|
54
|
+
.describe("Include TypeScript prop interface definitions for each DS component"),
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ─── Token Parsing ────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse a TypeScript constants file for exported const objects.
|
|
63
|
+
* Handles patterns like:
|
|
64
|
+
* export const FIGMA_COLORS = { KEY: '#hex', ... }
|
|
65
|
+
* export const FIGMA_SPACING = { XS: 8, ... }
|
|
66
|
+
*/
|
|
67
|
+
async function parseTokensFile(absolutePath: string): Promise<TokenEntry[]> {
|
|
68
|
+
const tokens: TokenEntry[] = [];
|
|
69
|
+
|
|
70
|
+
let content: string;
|
|
71
|
+
try {
|
|
72
|
+
content = await readFile(absolutePath, "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return tokens;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const blockRegex = /export\s+const\s+(\w+)\s*=\s*\{([^}]+)\}/g;
|
|
78
|
+
let blockMatch: RegExpExecArray | null;
|
|
79
|
+
|
|
80
|
+
while ((blockMatch = blockRegex.exec(content)) !== null) {
|
|
81
|
+
const blockName = blockMatch[1];
|
|
82
|
+
const blockBody = blockMatch[2];
|
|
83
|
+
|
|
84
|
+
const lower = blockName.toLowerCase();
|
|
85
|
+
const group = lower.includes("color") ? "colors"
|
|
86
|
+
: lower.includes("spacing") ? "spacing"
|
|
87
|
+
: lower.includes("typo") ? "typography"
|
|
88
|
+
: lower.includes("shape") ? "shapes"
|
|
89
|
+
: lower.includes("dimension") ? "dimensions"
|
|
90
|
+
: "other";
|
|
91
|
+
|
|
92
|
+
const entryRegex = /['"]?(\w+)['"]?\s*:\s*([^,\n]+)/g;
|
|
93
|
+
let entryMatch: RegExpExecArray | null;
|
|
94
|
+
|
|
95
|
+
while ((entryMatch = entryRegex.exec(blockBody)) !== null) {
|
|
96
|
+
const name = entryMatch[1].trim();
|
|
97
|
+
const raw = entryMatch[2]
|
|
98
|
+
.trim()
|
|
99
|
+
.replace(/\/\/.*$/, "") // strip trailing comments
|
|
100
|
+
.trim()
|
|
101
|
+
.replace(/,$/, "") // strip trailing comma
|
|
102
|
+
.trim()
|
|
103
|
+
.replace(/^['"]/, "") // strip leading quote
|
|
104
|
+
.replace(/['"]$/, "") // strip trailing quote
|
|
105
|
+
.replace(/\s+as\s+const$/, "") // strip "as const"
|
|
106
|
+
.trim();
|
|
107
|
+
|
|
108
|
+
if (name && raw) {
|
|
109
|
+
tokens.push({ name, value: raw, group });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return tokens;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Component Interface Reading ──────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "__tests__", "__mocks__", ".expo"]);
|
|
120
|
+
|
|
121
|
+
/** Recursively walk a directory and collect .ts/.tsx files */
|
|
122
|
+
async function collectSourceFiles(dir: string): Promise<string[]> {
|
|
123
|
+
const files: string[] = [];
|
|
124
|
+
|
|
125
|
+
async function walk(current: string): Promise<void> {
|
|
126
|
+
let entries: string[];
|
|
127
|
+
try {
|
|
128
|
+
entries = await readdir(current);
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
134
|
+
const fullPath = join(current, entry);
|
|
135
|
+
try {
|
|
136
|
+
const s = await stat(fullPath);
|
|
137
|
+
if (s.isDirectory()) {
|
|
138
|
+
await walk(fullPath);
|
|
139
|
+
} else if (entry.endsWith(".tsx") || entry.endsWith(".ts")) {
|
|
140
|
+
files.push(fullPath);
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// ignore permission errors
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await walk(dir);
|
|
149
|
+
return files;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Read component interface definitions from a DS components directory.
|
|
154
|
+
* Expects structure: componentsDir/<ComponentName>/generated/interface.ts
|
|
155
|
+
* Returns map of interface name → full TypeScript interface text.
|
|
156
|
+
*/
|
|
157
|
+
async function readComponentInterfaces(componentsDir: string): Promise<Record<string, string>> {
|
|
158
|
+
const interfaces: Record<string, string> = {};
|
|
159
|
+
|
|
160
|
+
let entries: string[];
|
|
161
|
+
try {
|
|
162
|
+
entries = await readdir(componentsDir);
|
|
163
|
+
} catch {
|
|
164
|
+
return interfaces;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
// Try common paths for generated interfaces
|
|
169
|
+
const candidates = [
|
|
170
|
+
join(componentsDir, entry, "generated", "interface.ts"),
|
|
171
|
+
join(componentsDir, entry, "interface.ts"),
|
|
172
|
+
join(componentsDir, entry, "types.ts"),
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const interfacePath of candidates) {
|
|
176
|
+
try {
|
|
177
|
+
const content = await readFile(interfacePath, "utf-8");
|
|
178
|
+
// Extract first interface name from "export interface FooProps {"
|
|
179
|
+
const nameMatch = content.match(/export\s+(?:interface|type)\s+(\w+)/);
|
|
180
|
+
if (nameMatch) {
|
|
181
|
+
interfaces[nameMatch[1]] = content.trim();
|
|
182
|
+
break; // found one for this component
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// file not found — try next candidate
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return interfaces;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── Usage Pattern Scanning ───────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Scan TSX/TS source files for DS component usages and collect prop patterns.
|
|
197
|
+
* Detects: <JDS*> components and common React Native DS component names.
|
|
198
|
+
*/
|
|
199
|
+
async function scanComponentUsages(
|
|
200
|
+
projectRoot: string
|
|
201
|
+
): Promise<ComponentUsagePattern[]> {
|
|
202
|
+
const files = await collectSourceFiles(projectRoot);
|
|
203
|
+
const usageMap = new Map<string, ComponentUsagePattern>();
|
|
204
|
+
|
|
205
|
+
// Matches JSX tags: <JDSText variant="..." /> or <Icon name="..." />
|
|
206
|
+
const componentTagRegex = /<(JDS\w+|DS3\w+|(?:Icon|Chip|Divider|JDSButton|JDSInput|JDSText)\b)\s([^>]{0,500})(?:\/?>|>)/g;
|
|
207
|
+
const propRegex = /(\w+)=["'{]([^"'}\n]{0,80})["'}]/g;
|
|
208
|
+
|
|
209
|
+
for (const filePath of files) {
|
|
210
|
+
let content: string;
|
|
211
|
+
try {
|
|
212
|
+
content = await readFile(filePath, "utf-8");
|
|
213
|
+
} catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Find import lines to identify where components come from
|
|
218
|
+
const importLines = content.match(/import[^;]+from\s+['"][^'"]+['"]/g) || [];
|
|
219
|
+
|
|
220
|
+
componentTagRegex.lastIndex = 0;
|
|
221
|
+
let tagMatch: RegExpExecArray | null;
|
|
222
|
+
|
|
223
|
+
while ((tagMatch = componentTagRegex.exec(content)) !== null) {
|
|
224
|
+
const componentName = tagMatch[1];
|
|
225
|
+
const propsStr = tagMatch[2];
|
|
226
|
+
|
|
227
|
+
// Find import source for this component
|
|
228
|
+
const importedFrom = importLines
|
|
229
|
+
.find(line => line.includes(componentName))
|
|
230
|
+
?.match(/from\s+['"]([^'"]+)['"]/)?.[1] ?? "unknown";
|
|
231
|
+
|
|
232
|
+
if (!usageMap.has(componentName)) {
|
|
233
|
+
usageMap.set(componentName, {
|
|
234
|
+
componentName,
|
|
235
|
+
importedFrom,
|
|
236
|
+
usageCount: 0,
|
|
237
|
+
sampleProps: {},
|
|
238
|
+
sourceFiles: [],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const usage = usageMap.get(componentName)!;
|
|
243
|
+
usage.usageCount++;
|
|
244
|
+
|
|
245
|
+
if (!usage.sourceFiles.includes(filePath)) {
|
|
246
|
+
usage.sourceFiles.push(filePath);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
propRegex.lastIndex = 0;
|
|
250
|
+
let propMatch: RegExpExecArray | null;
|
|
251
|
+
while ((propMatch = propRegex.exec(propsStr)) !== null) {
|
|
252
|
+
const propName = propMatch[1];
|
|
253
|
+
const propValue = propMatch[2];
|
|
254
|
+
if (!usage.sampleProps[propName]) {
|
|
255
|
+
usage.sampleProps[propName] = [];
|
|
256
|
+
}
|
|
257
|
+
if (!usage.sampleProps[propName].includes(propValue)) {
|
|
258
|
+
usage.sampleProps[propName].push(propValue);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return Array.from(usageMap.values()).sort((a, b) => b.usageCount - a.usageCount);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ─── Formatting ───────────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
function formatOutput(
|
|
270
|
+
dsName: string,
|
|
271
|
+
tokens: TokenEntry[],
|
|
272
|
+
componentInterfaces: Record<string, string>,
|
|
273
|
+
components: ComponentUsagePattern[]
|
|
274
|
+
): string {
|
|
275
|
+
const lines: string[] = [`=== Design System: ${dsName} ===`, ""];
|
|
276
|
+
|
|
277
|
+
// Tokens grouped by category
|
|
278
|
+
if (tokens.length > 0) {
|
|
279
|
+
const groups = new Map<string, TokenEntry[]>();
|
|
280
|
+
for (const t of tokens) {
|
|
281
|
+
if (!groups.has(t.group)) groups.set(t.group, []);
|
|
282
|
+
groups.get(t.group)!.push(t);
|
|
283
|
+
}
|
|
284
|
+
for (const [group, groupTokens] of groups) {
|
|
285
|
+
lines.push(`--- ${group.toUpperCase()} ---`);
|
|
286
|
+
for (const t of groupTokens) {
|
|
287
|
+
lines.push(` ${t.name}: ${t.value}`);
|
|
288
|
+
}
|
|
289
|
+
lines.push("");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Component TypeScript interfaces
|
|
294
|
+
if (Object.keys(componentInterfaces).length > 0) {
|
|
295
|
+
lines.push("--- COMPONENT INTERFACES ---");
|
|
296
|
+
for (const [, iface] of Object.entries(componentInterfaces)) {
|
|
297
|
+
lines.push("", iface);
|
|
298
|
+
}
|
|
299
|
+
lines.push("");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Usage patterns
|
|
303
|
+
if (components.length > 0) {
|
|
304
|
+
lines.push("--- COMPONENT USAGE PATTERNS ---");
|
|
305
|
+
lines.push("(props and values observed in the codebase)");
|
|
306
|
+
for (const c of components.slice(0, 20)) {
|
|
307
|
+
lines.push(`\n${c.componentName} (${c.usageCount} uses, from '${c.importedFrom}')`);
|
|
308
|
+
for (const [prop, values] of Object.entries(c.sampleProps)) {
|
|
309
|
+
lines.push(` ${prop}: ${values.slice(0, 10).join(" | ")}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return lines.join("\n");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── Handler Factory ──────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
export function createDsToolHandlers(dsConfig: DesignSystemConfig | undefined) {
|
|
320
|
+
return {
|
|
321
|
+
devlens_ds_context: async (params: {
|
|
322
|
+
includeTokens: boolean;
|
|
323
|
+
includeComponents: boolean;
|
|
324
|
+
includeInterfaces: boolean;
|
|
325
|
+
}) => {
|
|
326
|
+
if (!dsConfig) {
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: "text" as const,
|
|
331
|
+
text: [
|
|
332
|
+
"No design system configured.",
|
|
333
|
+
"",
|
|
334
|
+
"To enable DS context, create devlens.config.json in your app root:",
|
|
335
|
+
JSON.stringify(
|
|
336
|
+
{
|
|
337
|
+
designSystem: {
|
|
338
|
+
name: "jds3",
|
|
339
|
+
projectRoot: "/absolute/path/to/your-app",
|
|
340
|
+
tokensFile: "src/constants/figmaTokens.ts",
|
|
341
|
+
componentsDir: "src/jds-components",
|
|
342
|
+
componentsGlob: "src/**/*.tsx",
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
null,
|
|
346
|
+
2
|
|
347
|
+
),
|
|
348
|
+
"",
|
|
349
|
+
"Then point to it in your MCP config env block:",
|
|
350
|
+
' "DEVLENS_CONFIG": "/absolute/path/to/your-app/devlens.config.json"',
|
|
351
|
+
].join("\n"),
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
isError: true,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const absoluteProjectRoot = resolve(dsConfig.projectRoot);
|
|
359
|
+
const absoluteTokensFile = join(absoluteProjectRoot, dsConfig.tokensFile);
|
|
360
|
+
const absoluteComponentsDir = dsConfig.componentsDir
|
|
361
|
+
? join(absoluteProjectRoot, dsConfig.componentsDir)
|
|
362
|
+
: null;
|
|
363
|
+
|
|
364
|
+
// Run all analyses in parallel
|
|
365
|
+
const [tokens, componentInterfaces, components] = await Promise.all([
|
|
366
|
+
params.includeTokens
|
|
367
|
+
? parseTokensFile(absoluteTokensFile)
|
|
368
|
+
: Promise.resolve([] as TokenEntry[]),
|
|
369
|
+
params.includeInterfaces && absoluteComponentsDir
|
|
370
|
+
? readComponentInterfaces(absoluteComponentsDir)
|
|
371
|
+
: Promise.resolve({} as Record<string, string>),
|
|
372
|
+
params.includeComponents
|
|
373
|
+
? scanComponentUsages(absoluteProjectRoot)
|
|
374
|
+
: Promise.resolve([] as ComponentUsagePattern[]),
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
const summary = [
|
|
378
|
+
`${tokens.length} tokens`,
|
|
379
|
+
`${Object.keys(componentInterfaces).length} component interfaces`,
|
|
380
|
+
`${components.reduce((s, c) => s + c.usageCount, 0)} component usages across ${components.length} component types`,
|
|
381
|
+
].join(", ");
|
|
382
|
+
|
|
383
|
+
console.error(`[devlens] DS context: ${summary}`);
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: "text" as const,
|
|
389
|
+
text: formatOutput(dsConfig.name, tokens, componentInterfaces, components),
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|