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.
Files changed (175) hide show
  1. package/.claude/settings.json +12 -0
  2. package/.claude/settings.local.json +17 -0
  3. package/INSTALLATION_GUIDE.md +354 -0
  4. package/QUICK_START.md +153 -0
  5. package/README.md +354 -0
  6. package/bin/cli.ts +22 -0
  7. package/bin/register.ts +96 -0
  8. package/dist/bin/cli.d.ts +3 -0
  9. package/dist/bin/cli.d.ts.map +1 -0
  10. package/dist/bin/cli.js +20 -0
  11. package/dist/bin/cli.js.map +1 -0
  12. package/dist/bin/register.d.ts +10 -0
  13. package/dist/bin/register.d.ts.map +1 -0
  14. package/dist/bin/register.js +92 -0
  15. package/dist/bin/register.js.map +1 -0
  16. package/dist/src/config/devlens-config.d.ts +92 -0
  17. package/dist/src/config/devlens-config.d.ts.map +1 -0
  18. package/dist/src/config/devlens-config.js +70 -0
  19. package/dist/src/config/devlens-config.js.map +1 -0
  20. package/dist/src/index.d.ts +35 -0
  21. package/dist/src/index.d.ts.map +1 -0
  22. package/dist/src/index.js +8 -0
  23. package/dist/src/index.js.map +1 -0
  24. package/dist/src/metro/cdp-client.d.ts +48 -0
  25. package/dist/src/metro/cdp-client.d.ts.map +1 -0
  26. package/dist/src/metro/cdp-client.js +127 -0
  27. package/dist/src/metro/cdp-client.js.map +1 -0
  28. package/dist/src/metro/log-collector.d.ts +30 -0
  29. package/dist/src/metro/log-collector.d.ts.map +1 -0
  30. package/dist/src/metro/log-collector.js +114 -0
  31. package/dist/src/metro/log-collector.js.map +1 -0
  32. package/dist/src/metro/metro-bridge.d.ts +56 -0
  33. package/dist/src/metro/metro-bridge.d.ts.map +1 -0
  34. package/dist/src/metro/metro-bridge.js +255 -0
  35. package/dist/src/metro/metro-bridge.js.map +1 -0
  36. package/dist/src/metro/network-inspector.d.ts +34 -0
  37. package/dist/src/metro/network-inspector.d.ts.map +1 -0
  38. package/dist/src/metro/network-inspector.js +100 -0
  39. package/dist/src/metro/network-inspector.js.map +1 -0
  40. package/dist/src/platform/android/adb.d.ts +50 -0
  41. package/dist/src/platform/android/adb.d.ts.map +1 -0
  42. package/dist/src/platform/android/adb.js +137 -0
  43. package/dist/src/platform/android/adb.js.map +1 -0
  44. package/dist/src/platform/android/android-device.d.ts +21 -0
  45. package/dist/src/platform/android/android-device.d.ts.map +1 -0
  46. package/dist/src/platform/android/android-device.js +94 -0
  47. package/dist/src/platform/android/android-device.js.map +1 -0
  48. package/dist/src/platform/android/ui-automator.d.ts +17 -0
  49. package/dist/src/platform/android/ui-automator.d.ts.map +1 -0
  50. package/dist/src/platform/android/ui-automator.js +126 -0
  51. package/dist/src/platform/android/ui-automator.js.map +1 -0
  52. package/dist/src/platform/device-manager.d.ts +28 -0
  53. package/dist/src/platform/device-manager.d.ts.map +1 -0
  54. package/dist/src/platform/device-manager.js +185 -0
  55. package/dist/src/platform/device-manager.js.map +1 -0
  56. package/dist/src/platform/device.d.ts +86 -0
  57. package/dist/src/platform/device.d.ts.map +1 -0
  58. package/dist/src/platform/device.js +7 -0
  59. package/dist/src/platform/device.js.map +1 -0
  60. package/dist/src/platform/ios/accessibility.d.ts +17 -0
  61. package/dist/src/platform/ios/accessibility.d.ts.map +1 -0
  62. package/dist/src/platform/ios/accessibility.js +159 -0
  63. package/dist/src/platform/ios/accessibility.js.map +1 -0
  64. package/dist/src/platform/ios/ios-device.d.ts +22 -0
  65. package/dist/src/platform/ios/ios-device.d.ts.map +1 -0
  66. package/dist/src/platform/ios/ios-device.js +97 -0
  67. package/dist/src/platform/ios/ios-device.js.map +1 -0
  68. package/dist/src/platform/ios/simctl.d.ts +54 -0
  69. package/dist/src/platform/ios/simctl.d.ts.map +1 -0
  70. package/dist/src/platform/ios/simctl.js +192 -0
  71. package/dist/src/platform/ios/simctl.js.map +1 -0
  72. package/dist/src/server.d.ts +3 -0
  73. package/dist/src/server.d.ts.map +1 -0
  74. package/dist/src/server.js +176 -0
  75. package/dist/src/server.js.map +1 -0
  76. package/dist/src/snapshot/formatter.d.ts +18 -0
  77. package/dist/src/snapshot/formatter.d.ts.map +1 -0
  78. package/dist/src/snapshot/formatter.js +86 -0
  79. package/dist/src/snapshot/formatter.js.map +1 -0
  80. package/dist/src/snapshot/ref-registry.d.ts +67 -0
  81. package/dist/src/snapshot/ref-registry.d.ts.map +1 -0
  82. package/dist/src/snapshot/ref-registry.js +169 -0
  83. package/dist/src/snapshot/ref-registry.js.map +1 -0
  84. package/dist/src/snapshot/snapshot-differ.d.ts +57 -0
  85. package/dist/src/snapshot/snapshot-differ.d.ts.map +1 -0
  86. package/dist/src/snapshot/snapshot-differ.js +153 -0
  87. package/dist/src/snapshot/snapshot-differ.js.map +1 -0
  88. package/dist/src/tools/app-tools.d.ts +71 -0
  89. package/dist/src/tools/app-tools.d.ts.map +1 -0
  90. package/dist/src/tools/app-tools.js +97 -0
  91. package/dist/src/tools/app-tools.js.map +1 -0
  92. package/dist/src/tools/device-tools.d.ts +53 -0
  93. package/dist/src/tools/device-tools.d.ts.map +1 -0
  94. package/dist/src/tools/device-tools.js +86 -0
  95. package/dist/src/tools/device-tools.js.map +1 -0
  96. package/dist/src/tools/ds-tools.d.ts +65 -0
  97. package/dist/src/tools/ds-tools.d.ts.map +1 -0
  98. package/dist/src/tools/ds-tools.js +314 -0
  99. package/dist/src/tools/ds-tools.js.map +1 -0
  100. package/dist/src/tools/interaction-tools.d.ts +248 -0
  101. package/dist/src/tools/interaction-tools.d.ts.map +1 -0
  102. package/dist/src/tools/interaction-tools.js +391 -0
  103. package/dist/src/tools/interaction-tools.js.map +1 -0
  104. package/dist/src/tools/metro-tools.d.ts +115 -0
  105. package/dist/src/tools/metro-tools.d.ts.map +1 -0
  106. package/dist/src/tools/metro-tools.js +270 -0
  107. package/dist/src/tools/metro-tools.js.map +1 -0
  108. package/dist/src/tools/navigation-tools.d.ts +36 -0
  109. package/dist/src/tools/navigation-tools.d.ts.map +1 -0
  110. package/dist/src/tools/navigation-tools.js +60 -0
  111. package/dist/src/tools/navigation-tools.js.map +1 -0
  112. package/dist/src/tools/screenshot-tools.d.ts +298 -0
  113. package/dist/src/tools/screenshot-tools.d.ts.map +1 -0
  114. package/dist/src/tools/screenshot-tools.js +565 -0
  115. package/dist/src/tools/screenshot-tools.js.map +1 -0
  116. package/dist/src/tools/snapshot-tools.d.ts +161 -0
  117. package/dist/src/tools/snapshot-tools.d.ts.map +1 -0
  118. package/dist/src/tools/snapshot-tools.js +479 -0
  119. package/dist/src/tools/snapshot-tools.js.map +1 -0
  120. package/dist/src/utils/image-preprocess.d.ts +49 -0
  121. package/dist/src/utils/image-preprocess.d.ts.map +1 -0
  122. package/dist/src/utils/image-preprocess.js +322 -0
  123. package/dist/src/utils/image-preprocess.js.map +1 -0
  124. package/dist/src/utils/retry.d.ts +21 -0
  125. package/dist/src/utils/retry.d.ts.map +1 -0
  126. package/dist/src/utils/retry.js +33 -0
  127. package/dist/src/utils/retry.js.map +1 -0
  128. package/dist/src/visual/comparator.d.ts +51 -0
  129. package/dist/src/visual/comparator.d.ts.map +1 -0
  130. package/dist/src/visual/comparator.js +119 -0
  131. package/dist/src/visual/comparator.js.map +1 -0
  132. package/dist/src/visual/layout-analyzer.d.ts +64 -0
  133. package/dist/src/visual/layout-analyzer.d.ts.map +1 -0
  134. package/dist/src/visual/layout-analyzer.js +198 -0
  135. package/dist/src/visual/layout-analyzer.js.map +1 -0
  136. package/dist/src/visual/screenshot.d.ts +17 -0
  137. package/dist/src/visual/screenshot.d.ts.map +1 -0
  138. package/dist/src/visual/screenshot.js +39 -0
  139. package/dist/src/visual/screenshot.js.map +1 -0
  140. package/docs/figma-workflow.md +289 -0
  141. package/docs/setup-guide.md +360 -0
  142. package/docs/tool-reference.md +622 -0
  143. package/package.json +57 -0
  144. package/src/config/devlens-config.ts +76 -0
  145. package/src/index.ts +5 -0
  146. package/src/metro/cdp-client.ts +160 -0
  147. package/src/metro/log-collector.ts +137 -0
  148. package/src/metro/metro-bridge.ts +307 -0
  149. package/src/metro/network-inspector.ts +134 -0
  150. package/src/platform/android/adb.ts +200 -0
  151. package/src/platform/android/android-device.ts +116 -0
  152. package/src/platform/android/ui-automator.ts +141 -0
  153. package/src/platform/device-manager.ts +229 -0
  154. package/src/platform/device.ts +110 -0
  155. package/src/platform/ios/accessibility.ts +189 -0
  156. package/src/platform/ios/ios-device.ts +116 -0
  157. package/src/platform/ios/simctl.ts +244 -0
  158. package/src/server.ts +228 -0
  159. package/src/snapshot/formatter.ts +102 -0
  160. package/src/snapshot/ref-registry.ts +230 -0
  161. package/src/snapshot/snapshot-differ.ts +220 -0
  162. package/src/tools/app-tools.ts +111 -0
  163. package/src/tools/device-tools.ts +96 -0
  164. package/src/tools/ds-tools.ts +395 -0
  165. package/src/tools/interaction-tools.ts +467 -0
  166. package/src/tools/metro-tools.ts +320 -0
  167. package/src/tools/navigation-tools.ts +71 -0
  168. package/src/tools/screenshot-tools.ts +698 -0
  169. package/src/tools/snapshot-tools.ts +585 -0
  170. package/src/utils/image-preprocess.ts +430 -0
  171. package/src/utils/retry.ts +51 -0
  172. package/src/visual/comparator.ts +191 -0
  173. package/src/visual/layout-analyzer.ts +283 -0
  174. package/src/visual/screenshot.ts +49 -0
  175. 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
+ }