sonance-brand-mcp 1.3.74 → 1.3.75

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.
@@ -177,6 +177,48 @@ interface ScreenshotAnalysis {
177
177
  visibleText: string[];
178
178
  componentNames: string[];
179
179
  codePatterns: string[];
180
+ elementTypes: string[]; // Detected UI element types: table, button, form, card, list, modal, dropdown, input
181
+ }
182
+
183
+ /**
184
+ * Map element types to JSX/HTML patterns to search for in files
185
+ * This is the key to Cursor-style file discovery - search for WHAT we see, not guessed names
186
+ */
187
+ const ELEMENT_PATTERNS: Record<string, string[]> = {
188
+ table: ['<Table', 'TableRow', 'TableCell', 'TableHeader', 'TableBody', '<table', '<tr', '<td', '<th'],
189
+ button: ['<Button', '<button', 'onClick={', 'handleClick'],
190
+ form: ['<Form', '<form', 'onSubmit', '<Input', '<input', 'handleSubmit'],
191
+ card: ['<Card', 'CardContent', 'CardHeader', 'CardTitle', 'CardDescription'],
192
+ list: ['<List', '<ul', '<li', 'ListItem', '.map(('],
193
+ modal: ['<Modal', '<Dialog', 'DialogContent', 'isOpen', 'onClose', 'onOpenChange'],
194
+ dropdown: ['<Dropdown', '<Select', 'DropdownMenu', 'SelectContent', 'SelectTrigger'],
195
+ input: ['<Input', '<input', '<Textarea', '<textarea', 'onChange={'],
196
+ header: ['<Header', '<header', '<Navbar', '<Nav', 'navigation'],
197
+ sidebar: ['<Sidebar', '<aside', 'SidebarContent', 'NavigationMenu'],
198
+ };
199
+
200
+ /**
201
+ * Score a file based on whether it contains patterns for a specific element type
202
+ * Higher scores indicate the file is more likely to contain the target element
203
+ */
204
+ function scoreFileByElementType(content: string, elementType: string): number {
205
+ const patterns = ELEMENT_PATTERNS[elementType.toLowerCase()] || [];
206
+ let score = 0;
207
+ let matchCount = 0;
208
+
209
+ for (const pattern of patterns) {
210
+ if (content.includes(pattern)) {
211
+ score += 300; // High score per pattern match
212
+ matchCount++;
213
+ }
214
+ }
215
+
216
+ // Bonus for multiple pattern matches (indicates primary component for this element type)
217
+ if (matchCount >= 3) {
218
+ score += 500;
219
+ }
220
+
221
+ return score;
180
222
  }
181
223
 
182
224
  /**
@@ -216,15 +258,17 @@ Analyze this UI to help find the correct source file. Return:
216
258
  1. **visibleText**: Extract the EXACT text visible in UI elements (button labels, headings, tab names, etc.)
217
259
  2. **componentNames**: Deduce likely React component names based on what you see (e.g., "ProcessDetailPanel", "UserSettings", "DataTable"). Think about common React naming conventions.
218
260
  3. **codePatterns**: Suggest code identifiers that might exist (e.g., "handleEdit", "isLoading", "activeTab", "onDelete")
261
+ 4. **elementTypes**: What TYPE of UI element is the user's request about? Pick from: table, button, form, card, list, modal, dropdown, input, header, sidebar. List the most relevant first.
219
262
 
220
263
  Return ONLY valid JSON:
221
264
  {
222
265
  "visibleText": ["Assets", "Flowchart", "Edit", "Delete"],
223
266
  "componentNames": ["ProcessDetailPanel", "ProcessActions", "QuickActions"],
224
- "codePatterns": ["handleEdit", "handleDelete", "activeTab", "setActiveTab"]
267
+ "codePatterns": ["handleEdit", "handleDelete", "activeTab", "setActiveTab"],
268
+ "elementTypes": ["table", "button"]
225
269
  }
226
270
 
227
- Be smart - use your knowledge of React patterns to make educated guesses about component and variable names.`,
271
+ Be smart - use your knowledge of React patterns to identify what type of element the user wants to modify.`,
228
272
  },
229
273
  ],
230
274
  },
@@ -234,7 +278,7 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
234
278
  try {
235
279
  const textBlock = response.content.find((block) => block.type === "text");
236
280
  if (!textBlock || textBlock.type !== "text") {
237
- return { visibleText: [], componentNames: [], codePatterns: [] };
281
+ return { visibleText: [], componentNames: [], codePatterns: [], elementTypes: [] };
238
282
  }
239
283
 
240
284
  let jsonText = textBlock.text.trim();
@@ -249,13 +293,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
249
293
  visibleText: parsed.visibleText || parsed.textStrings || [],
250
294
  componentNames: parsed.componentNames || [],
251
295
  codePatterns: parsed.codePatterns || [],
296
+ elementTypes: parsed.elementTypes || [],
252
297
  };
253
298
 
254
299
  debugLog("Phase 1: Analyzed screenshot for search", result);
255
300
  return result;
256
301
  } catch (e) {
257
302
  debugLog("Phase 1: Failed to parse screenshot analysis response", { error: String(e) });
258
- return { visibleText: [], componentNames: [], codePatterns: [] };
303
+ return { visibleText: [], componentNames: [], codePatterns: [], elementTypes: [] };
259
304
  }
260
305
  }
261
306
 
@@ -547,12 +592,18 @@ function searchFilesSmart(
547
592
  projectRoot: string,
548
593
  maxFiles: number = 10
549
594
  ): { path: string; content: string; score: number; filenameMatch: boolean }[] {
550
- const { visibleText, componentNames, codePatterns } = analysis;
595
+ const { visibleText, componentNames, codePatterns, elementTypes } = analysis;
551
596
 
552
597
  // Combine all search terms
553
598
  const allSearchTerms = [...visibleText, ...codePatterns];
554
599
 
555
- if (allSearchTerms.length === 0 && componentNames.length === 0) return [];
600
+ // Get the primary element type for pattern-based scoring (Cursor-style)
601
+ const primaryElementType = elementTypes?.[0] || null;
602
+ if (primaryElementType) {
603
+ debugLog("Phase 2: Element type detected for pattern matching", { primaryElementType });
604
+ }
605
+
606
+ if (allSearchTerms.length === 0 && componentNames.length === 0 && !primaryElementType) return [];
556
607
 
557
608
  const results: Map<string, { path: string; content: string; score: number; filenameMatch: boolean }> = new Map();
558
609
  const searchDirs = ["components", "src/components", "app", "src/app", "pages", "src/pages"];
@@ -619,23 +670,47 @@ function searchFilesSmart(
619
670
  }
620
671
  }
621
672
 
673
+ // Calculate content score based on text matches
674
+ let contentScore = 0;
622
675
  if (uniqueMatches > 0) {
623
676
  // Score: unique matches * 10 + total matches
624
- const contentScore = (uniqueMatches * 10) + totalMatches;
625
-
677
+ contentScore = (uniqueMatches * 10) + totalMatches;
678
+ }
679
+
680
+ // CURSOR-STYLE: Add element type pattern scoring
681
+ // Files containing patterns for the detected element type get a big boost
682
+ let elementTypeScore = 0;
683
+ if (primaryElementType) {
684
+ elementTypeScore = scoreFileByElementType(content, primaryElementType);
685
+ }
686
+
687
+ const totalScore = contentScore + elementTypeScore;
688
+
689
+ if (totalScore > 0) {
626
690
  // Check if we already have this file (from component name search)
627
691
  const existing = results.get(entryPath);
628
692
  if (existing) {
629
- // Add content score to existing filename match score
630
- existing.score += contentScore;
693
+ // Add scores to existing entry
694
+ existing.score += totalScore;
631
695
  } else {
632
696
  results.set(entryPath, {
633
697
  path: entryPath,
634
698
  content,
635
- score: contentScore,
699
+ score: totalScore,
636
700
  filenameMatch: false
637
701
  });
638
702
  }
703
+
704
+ // Log high-confidence element type matches
705
+ if (elementTypeScore >= 800) {
706
+ debugLog("Phase 2: High-confidence element type match", {
707
+ file: entryPath,
708
+ elementType: primaryElementType,
709
+ elementScore: elementTypeScore,
710
+ contentScore,
711
+ totalScore
712
+ });
713
+ }
639
714
  }
640
715
  } catch {
641
716
  // Skip files that can't be read
@@ -958,7 +1033,7 @@ User Request: "${userPrompt}"
958
1033
 
959
1034
  // Search for element IDs in the file to enable precise targeting
960
1035
  let idMatch: { lineNumber: number; matchedId: string; snippet: string } | null = null;
961
- if (focusedElements && focusedElements.length > 0) {
1036
+ if (focusedElements && focusedElements.length > 0) {
962
1037
  for (const el of focusedElements) {
963
1038
  idMatch = findElementIdInFile(content, el.elementId, el.childIds);
964
1039
  if (idMatch) break;
@@ -1049,7 +1124,7 @@ ${linesWithNumbers}
1049
1124
  // Only include the variants/props section, not the whole file
1050
1125
  const variantsMatch = comp.content.match(/variants:\s*\{[\s\S]*?\n\s*\},\n\s*defaultVariants/);
1051
1126
  if (variantsMatch) {
1052
- textContent += `
1127
+ textContent += `
1053
1128
  --- UI Component: ${comp.path} (variants only) ---
1054
1129
  ${variantsMatch[0]}
1055
1130
  ---
@@ -1123,7 +1198,7 @@ CRITICAL: Your "search" string MUST exist in the file. If you can't find the exa
1123
1198
 
1124
1199
  // Call Claude Vision API with retry mechanism
1125
1200
  const anthropic = new Anthropic({ apiKey });
1126
-
1201
+
1127
1202
  // Build set of valid file paths from page context (needed for validation)
1128
1203
  const validFilePaths = new Set<string>();
1129
1204
  if (pageContext.pageFile) {
@@ -173,6 +173,48 @@ interface ScreenshotAnalysis {
173
173
  visibleText: string[];
174
174
  componentNames: string[];
175
175
  codePatterns: string[];
176
+ elementTypes: string[]; // Detected UI element types: table, button, form, card, list, modal, dropdown, input
177
+ }
178
+
179
+ /**
180
+ * Map element types to JSX/HTML patterns to search for in files
181
+ * This is the key to Cursor-style file discovery - search for WHAT we see, not guessed names
182
+ */
183
+ const ELEMENT_PATTERNS: Record<string, string[]> = {
184
+ table: ['<Table', 'TableRow', 'TableCell', 'TableHeader', 'TableBody', '<table', '<tr', '<td', '<th'],
185
+ button: ['<Button', '<button', 'onClick={', 'handleClick'],
186
+ form: ['<Form', '<form', 'onSubmit', '<Input', '<input', 'handleSubmit'],
187
+ card: ['<Card', 'CardContent', 'CardHeader', 'CardTitle', 'CardDescription'],
188
+ list: ['<List', '<ul', '<li', 'ListItem', '.map(('],
189
+ modal: ['<Modal', '<Dialog', 'DialogContent', 'isOpen', 'onClose', 'onOpenChange'],
190
+ dropdown: ['<Dropdown', '<Select', 'DropdownMenu', 'SelectContent', 'SelectTrigger'],
191
+ input: ['<Input', '<input', '<Textarea', '<textarea', 'onChange={'],
192
+ header: ['<Header', '<header', '<Navbar', '<Nav', 'navigation'],
193
+ sidebar: ['<Sidebar', '<aside', 'SidebarContent', 'NavigationMenu'],
194
+ };
195
+
196
+ /**
197
+ * Score a file based on whether it contains patterns for a specific element type
198
+ * Higher scores indicate the file is more likely to contain the target element
199
+ */
200
+ function scoreFileByElementType(content: string, elementType: string): number {
201
+ const patterns = ELEMENT_PATTERNS[elementType.toLowerCase()] || [];
202
+ let score = 0;
203
+ let matchCount = 0;
204
+
205
+ for (const pattern of patterns) {
206
+ if (content.includes(pattern)) {
207
+ score += 300; // High score per pattern match
208
+ matchCount++;
209
+ }
210
+ }
211
+
212
+ // Bonus for multiple pattern matches (indicates primary component for this element type)
213
+ if (matchCount >= 3) {
214
+ score += 500;
215
+ }
216
+
217
+ return score;
176
218
  }
177
219
 
178
220
  /**
@@ -212,15 +254,17 @@ Analyze this UI to help find the correct source file. Return:
212
254
  1. **visibleText**: Extract the EXACT text visible in UI elements (button labels, headings, tab names, etc.)
213
255
  2. **componentNames**: Deduce likely React component names based on what you see (e.g., "ProcessDetailPanel", "UserSettings", "DataTable"). Think about common React naming conventions.
214
256
  3. **codePatterns**: Suggest code identifiers that might exist (e.g., "handleEdit", "isLoading", "activeTab", "onDelete")
257
+ 4. **elementTypes**: What TYPE of UI element is the user's request about? Pick from: table, button, form, card, list, modal, dropdown, input, header, sidebar. List the most relevant first.
215
258
 
216
259
  Return ONLY valid JSON:
217
260
  {
218
261
  "visibleText": ["Assets", "Flowchart", "Edit", "Delete"],
219
262
  "componentNames": ["ProcessDetailPanel", "ProcessActions", "QuickActions"],
220
- "codePatterns": ["handleEdit", "handleDelete", "activeTab", "setActiveTab"]
263
+ "codePatterns": ["handleEdit", "handleDelete", "activeTab", "setActiveTab"],
264
+ "elementTypes": ["table", "button"]
221
265
  }
222
266
 
223
- Be smart - use your knowledge of React patterns to make educated guesses about component and variable names.`,
267
+ Be smart - use your knowledge of React patterns to identify what type of element the user wants to modify.`,
224
268
  },
225
269
  ],
226
270
  },
@@ -230,7 +274,7 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
230
274
  try {
231
275
  const textBlock = response.content.find((block) => block.type === "text");
232
276
  if (!textBlock || textBlock.type !== "text") {
233
- return { visibleText: [], componentNames: [], codePatterns: [] };
277
+ return { visibleText: [], componentNames: [], codePatterns: [], elementTypes: [] };
234
278
  }
235
279
 
236
280
  let jsonText = textBlock.text.trim();
@@ -245,13 +289,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
245
289
  visibleText: parsed.visibleText || parsed.textStrings || [],
246
290
  componentNames: parsed.componentNames || [],
247
291
  codePatterns: parsed.codePatterns || [],
292
+ elementTypes: parsed.elementTypes || [],
248
293
  };
249
294
 
250
295
  debugLog("Phase 1: Analyzed screenshot for search", result);
251
296
  return result;
252
297
  } catch (e) {
253
298
  debugLog("Phase 1: Failed to parse screenshot analysis response", { error: String(e) });
254
- return { visibleText: [], componentNames: [], codePatterns: [] };
299
+ return { visibleText: [], componentNames: [], codePatterns: [], elementTypes: [] };
255
300
  }
256
301
  }
257
302
 
@@ -543,12 +588,18 @@ function searchFilesSmart(
543
588
  projectRoot: string,
544
589
  maxFiles: number = 10
545
590
  ): { path: string; content: string; score: number; filenameMatch: boolean }[] {
546
- const { visibleText, componentNames, codePatterns } = analysis;
591
+ const { visibleText, componentNames, codePatterns, elementTypes } = analysis;
547
592
 
548
593
  // Combine all search terms
549
594
  const allSearchTerms = [...visibleText, ...codePatterns];
550
595
 
551
- if (allSearchTerms.length === 0 && componentNames.length === 0) return [];
596
+ // Get the primary element type for pattern-based scoring (Cursor-style)
597
+ const primaryElementType = elementTypes?.[0] || null;
598
+ if (primaryElementType) {
599
+ debugLog("Phase 2: Element type detected for pattern matching", { primaryElementType });
600
+ }
601
+
602
+ if (allSearchTerms.length === 0 && componentNames.length === 0 && !primaryElementType) return [];
552
603
 
553
604
  const results: Map<string, { path: string; content: string; score: number; filenameMatch: boolean }> = new Map();
554
605
  const searchDirs = ["components", "src/components", "app", "src/app", "pages", "src/pages"];
@@ -615,23 +666,47 @@ function searchFilesSmart(
615
666
  }
616
667
  }
617
668
 
669
+ // Calculate content score based on text matches
670
+ let contentScore = 0;
618
671
  if (uniqueMatches > 0) {
619
672
  // Score: unique matches * 10 + total matches
620
- const contentScore = (uniqueMatches * 10) + totalMatches;
621
-
673
+ contentScore = (uniqueMatches * 10) + totalMatches;
674
+ }
675
+
676
+ // CURSOR-STYLE: Add element type pattern scoring
677
+ // Files containing patterns for the detected element type get a big boost
678
+ let elementTypeScore = 0;
679
+ if (primaryElementType) {
680
+ elementTypeScore = scoreFileByElementType(content, primaryElementType);
681
+ }
682
+
683
+ const totalScore = contentScore + elementTypeScore;
684
+
685
+ if (totalScore > 0) {
622
686
  // Check if we already have this file (from component name search)
623
687
  const existing = results.get(entryPath);
624
688
  if (existing) {
625
- // Add content score to existing filename match score
626
- existing.score += contentScore;
689
+ // Add scores to existing entry
690
+ existing.score += totalScore;
627
691
  } else {
628
692
  results.set(entryPath, {
629
693
  path: entryPath,
630
694
  content,
631
- score: contentScore,
695
+ score: totalScore,
632
696
  filenameMatch: false
633
697
  });
634
698
  }
699
+
700
+ // Log high-confidence element type matches
701
+ if (elementTypeScore >= 800) {
702
+ debugLog("Phase 2: High-confidence element type match", {
703
+ file: entryPath,
704
+ elementType: primaryElementType,
705
+ elementScore: elementTypeScore,
706
+ contentScore,
707
+ totalScore
708
+ });
709
+ }
635
710
  }
636
711
  } catch {
637
712
  // Skip files that can't be read
@@ -927,7 +1002,7 @@ User Request: "${userPrompt}"
927
1002
 
928
1003
  // Search for element IDs in the file to enable precise targeting
929
1004
  let idMatch: { lineNumber: number; matchedId: string; snippet: string } | null = null;
930
- if (focusedElements && focusedElements.length > 0) {
1005
+ if (focusedElements && focusedElements.length > 0) {
931
1006
  for (const el of focusedElements) {
932
1007
  idMatch = findElementIdInFile(content, el.elementId, el.childIds);
933
1008
  if (idMatch) break;
@@ -1018,7 +1093,7 @@ ${linesWithNumbers}
1018
1093
  // Only include the variants/props section, not the whole file
1019
1094
  const variantsMatch = comp.content.match(/variants:\s*\{[\s\S]*?\n\s*\},\n\s*defaultVariants/);
1020
1095
  if (variantsMatch) {
1021
- textContent += `
1096
+ textContent += `
1022
1097
  --- UI Component: ${comp.path} (variants only) ---
1023
1098
  ${variantsMatch[0]}
1024
1099
  ---
@@ -1092,7 +1167,7 @@ CRITICAL: Your "search" string MUST exist in the file. If you can't find the exa
1092
1167
 
1093
1168
  // Call Claude Vision API with retry mechanism
1094
1169
  const anthropic = new Anthropic({ apiKey });
1095
-
1170
+
1096
1171
  // Build list of known file paths (for logging)
1097
1172
  const knownPaths = new Set<string>();
1098
1173
  if (pageContext.pageFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.74",
3
+ "version": "1.3.75",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",