sonance-brand-mcp 1.3.58 → 1.3.59

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.
@@ -171,6 +171,73 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
171
171
  }
172
172
  }
173
173
 
174
+ /**
175
+ * Search candidate files for JSX code matching the focused element
176
+ * This helps identify which file actually contains the element the user clicked on
177
+ */
178
+ function findFilesContainingElement(
179
+ focusedElements: VisionFocusedElement[] | undefined,
180
+ candidateFiles: { path: string; content: string }[]
181
+ ): { path: string; score: number; matches: string[] }[] {
182
+ if (!focusedElements || focusedElements.length === 0) {
183
+ return [];
184
+ }
185
+
186
+ const results: { path: string; score: number; matches: string[] }[] = [];
187
+
188
+ for (const file of candidateFiles) {
189
+ let score = 0;
190
+ const matches: string[] = [];
191
+ const content = file.content.toLowerCase();
192
+
193
+ for (const el of focusedElements) {
194
+ // Extract element type from the name (e.g., "button #123" -> "button")
195
+ const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
196
+ const componentType = el.type.toLowerCase();
197
+
198
+ // Search for JSX patterns
199
+ // Check for HTML tags like <button, <div, etc.
200
+ if (content.includes(`<${elementType}`)) {
201
+ score += 10;
202
+ matches.push(`<${elementType}>`);
203
+ }
204
+
205
+ // Check for React component like <Button, <Card, etc.
206
+ const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
207
+ if (file.content.includes(`<${capitalizedType}`)) {
208
+ score += 15;
209
+ matches.push(`<${capitalizedType}>`);
210
+ }
211
+
212
+ // Check for component imports
213
+ if (content.includes(`import`) && content.includes(componentType)) {
214
+ score += 5;
215
+ }
216
+
217
+ // Check for event handlers commonly associated with buttons
218
+ if (elementType === "button" || componentType === "button") {
219
+ if (content.includes("onclick") || content.includes("onpress")) {
220
+ score += 3;
221
+ }
222
+ }
223
+
224
+ // Check for the component being defined in this file
225
+ if (file.content.includes(`function ${capitalizedType}`) ||
226
+ file.content.includes(`const ${capitalizedType}`)) {
227
+ score += 20;
228
+ matches.push(`defines ${capitalizedType}`);
229
+ }
230
+ }
231
+
232
+ if (score > 0) {
233
+ results.push({ path: file.path, score, matches });
234
+ }
235
+ }
236
+
237
+ // Sort by score descending
238
+ return results.sort((a, b) => b.score - a.score);
239
+ }
240
+
174
241
  /**
175
242
  * Phase 3: Ask LLM to select the best file from actual file list
176
243
  * This replaces guessing - the LLM sees the real filenames and picks one
@@ -179,7 +246,8 @@ async function selectBestFileFromList(
179
246
  screenshot: string,
180
247
  userPrompt: string,
181
248
  candidateFiles: string[],
182
- apiKey: string
249
+ apiKey: string,
250
+ focusedElementHints?: { path: string; score: number; matches: string[] }[]
183
251
  ): Promise<string | null> {
184
252
  if (candidateFiles.length === 0) return null;
185
253
 
@@ -203,7 +271,12 @@ async function selectBestFileFromList(
203
271
 
204
272
  Here are the actual component files found in this codebase:
205
273
  ${candidateFiles.map((f, i) => `${i + 1}. ${f}`).join('\n')}
274
+ ${focusedElementHints && focusedElementHints.length > 0 ? `
275
+ FOCUSED ELEMENT ANALYSIS - Files that contain the element the user clicked on:
276
+ ${focusedElementHints.slice(0, 3).map(h => `- ${h.path} (confidence: ${h.score}, found: ${h.matches.join(", ")})`).join('\n')}
206
277
 
278
+ IMPORTANT: Prefer files from the FOCUSED ELEMENT ANALYSIS above, as they contain the actual element the user wants to modify.
279
+ ` : ''}
207
280
  Looking at the screenshot, which file MOST LIKELY contains the UI elements the user wants to modify?
208
281
 
209
282
  IMPORTANT: Return ONLY the exact file path from the list above (e.g., "components/ProcessCatalogue/ProcessDetailPanel.tsx").
@@ -451,6 +524,13 @@ RULES:
451
524
  8. NEVER change data mappings (icon names, keys, enum values) unless user explicitly provides the new values
452
525
  9. If you don't know what values exist in a database or data source, DO NOT guess - ask for clarification
453
526
 
527
+ CRITICAL - ELEMENT VERIFICATION:
528
+ - When a focused element is mentioned, SEARCH the provided file content for that EXACT element
529
+ - Look for the actual JSX/HTML code: <button, <Button, <div, className, onClick, etc.
530
+ - If you cannot find the element in the TARGET COMPONENT section, it may be in a child component
531
+ - NEVER guess what element code looks like - find it in the file or report "element not found"
532
+ - If the element is not in the provided file, return: {"modifications": [], "explanation": "The focused element appears to be in a child component, not in [filename]"}
533
+
454
534
  CRITICAL - DATA INTEGRITY:
455
535
  - If code references database values (like icon_name, type, status), DO NOT change the mapping keys
456
536
  - You cannot see database content - only change STRUCTURE, not DATA MAPPINGS
@@ -604,6 +684,22 @@ export async function POST(request: Request) {
604
684
  const searchResults = searchFilesSmart(analysis, projectRoot, 10);
605
685
  smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
606
686
 
687
+ // PHASE 2.5: Find which files contain the focused element
688
+ const focusedElementHints = findFilesContainingElement(
689
+ focusedElements,
690
+ searchResults.map(r => ({ path: r.path, content: r.content }))
691
+ );
692
+
693
+ if (focusedElementHints.length > 0) {
694
+ debugLog("Focused element search results", {
695
+ topMatches: focusedElementHints.slice(0, 3).map(h => ({
696
+ path: h.path,
697
+ score: h.score,
698
+ matches: h.matches
699
+ }))
700
+ });
701
+ }
702
+
607
703
  // PHASE 3: Ask LLM to pick the best file from actual file list
608
704
  if (searchResults.length > 0) {
609
705
  const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
@@ -611,7 +707,8 @@ export async function POST(request: Request) {
611
707
  screenshot,
612
708
  userPrompt,
613
709
  candidateFiles,
614
- apiKey
710
+ apiKey,
711
+ focusedElementHints
615
712
  );
616
713
 
617
714
  if (selectedFile) {
@@ -167,6 +167,73 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
167
167
  }
168
168
  }
169
169
 
170
+ /**
171
+ * Search candidate files for JSX code matching the focused element
172
+ * This helps identify which file actually contains the element the user clicked on
173
+ */
174
+ function findFilesContainingElement(
175
+ focusedElements: VisionFocusedElement[] | undefined,
176
+ candidateFiles: { path: string; content: string }[]
177
+ ): { path: string; score: number; matches: string[] }[] {
178
+ if (!focusedElements || focusedElements.length === 0) {
179
+ return [];
180
+ }
181
+
182
+ const results: { path: string; score: number; matches: string[] }[] = [];
183
+
184
+ for (const file of candidateFiles) {
185
+ let score = 0;
186
+ const matches: string[] = [];
187
+ const content = file.content.toLowerCase();
188
+
189
+ for (const el of focusedElements) {
190
+ // Extract element type from the name (e.g., "button #123" -> "button")
191
+ const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
192
+ const componentType = el.type.toLowerCase();
193
+
194
+ // Search for JSX patterns
195
+ // Check for HTML tags like <button, <div, etc.
196
+ if (content.includes(`<${elementType}`)) {
197
+ score += 10;
198
+ matches.push(`<${elementType}>`);
199
+ }
200
+
201
+ // Check for React component like <Button, <Card, etc.
202
+ const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
203
+ if (file.content.includes(`<${capitalizedType}`)) {
204
+ score += 15;
205
+ matches.push(`<${capitalizedType}>`);
206
+ }
207
+
208
+ // Check for component imports
209
+ if (content.includes(`import`) && content.includes(componentType)) {
210
+ score += 5;
211
+ }
212
+
213
+ // Check for event handlers commonly associated with buttons
214
+ if (elementType === "button" || componentType === "button") {
215
+ if (content.includes("onclick") || content.includes("onpress")) {
216
+ score += 3;
217
+ }
218
+ }
219
+
220
+ // Check for the component being defined in this file
221
+ if (file.content.includes(`function ${capitalizedType}`) ||
222
+ file.content.includes(`const ${capitalizedType}`)) {
223
+ score += 20;
224
+ matches.push(`defines ${capitalizedType}`);
225
+ }
226
+ }
227
+
228
+ if (score > 0) {
229
+ results.push({ path: file.path, score, matches });
230
+ }
231
+ }
232
+
233
+ // Sort by score descending
234
+ return results.sort((a, b) => b.score - a.score);
235
+ }
236
+
170
237
  /**
171
238
  * Phase 3: Ask LLM to select the best file from actual file list
172
239
  * This replaces guessing - the LLM sees the real filenames and picks one
@@ -175,7 +242,8 @@ async function selectBestFileFromList(
175
242
  screenshot: string,
176
243
  userPrompt: string,
177
244
  candidateFiles: string[],
178
- apiKey: string
245
+ apiKey: string,
246
+ focusedElementHints?: { path: string; score: number; matches: string[] }[]
179
247
  ): Promise<string | null> {
180
248
  if (candidateFiles.length === 0) return null;
181
249
 
@@ -199,7 +267,12 @@ async function selectBestFileFromList(
199
267
 
200
268
  Here are the actual component files found in this codebase:
201
269
  ${candidateFiles.map((f, i) => `${i + 1}. ${f}`).join('\n')}
270
+ ${focusedElementHints && focusedElementHints.length > 0 ? `
271
+ FOCUSED ELEMENT ANALYSIS - Files that contain the element the user clicked on:
272
+ ${focusedElementHints.slice(0, 3).map(h => `- ${h.path} (confidence: ${h.score}, found: ${h.matches.join(", ")})`).join('\n')}
202
273
 
274
+ IMPORTANT: Prefer files from the FOCUSED ELEMENT ANALYSIS above, as they contain the actual element the user wants to modify.
275
+ ` : ''}
203
276
  Looking at the screenshot, which file MOST LIKELY contains the UI elements the user wants to modify?
204
277
 
205
278
  IMPORTANT: Return ONLY the exact file path from the list above (e.g., "components/ProcessCatalogue/ProcessDetailPanel.tsx").
@@ -447,6 +520,13 @@ RULES:
447
520
  8. NEVER change data mappings (icon names, keys, enum values) unless user explicitly provides the new values
448
521
  9. If you don't know what values exist in a database or data source, DO NOT guess - ask for clarification
449
522
 
523
+ CRITICAL - ELEMENT VERIFICATION:
524
+ - When a focused element is mentioned, SEARCH the provided file content for that EXACT element
525
+ - Look for the actual JSX/HTML code: <button, <Button, <div, className, onClick, etc.
526
+ - If you cannot find the element in the TARGET COMPONENT section, it may be in a child component
527
+ - NEVER guess what element code looks like - find it in the file or report "element not found"
528
+ - If the element is not in the provided file, return: {"modifications": [], "explanation": "The focused element appears to be in a child component, not in [filename]"}
529
+
450
530
  CRITICAL - DATA INTEGRITY:
451
531
  - If code references database values (like icon_name, type, status), DO NOT change the mapping keys
452
532
  - You cannot see database content - only change STRUCTURE, not DATA MAPPINGS
@@ -575,6 +655,22 @@ export async function POST(request: Request) {
575
655
  const searchResults = searchFilesSmart(analysis, projectRoot, 10);
576
656
  smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
577
657
 
658
+ // PHASE 2.5: Find which files contain the focused element
659
+ const focusedElementHints = findFilesContainingElement(
660
+ focusedElements,
661
+ searchResults.map(r => ({ path: r.path, content: r.content }))
662
+ );
663
+
664
+ if (focusedElementHints.length > 0) {
665
+ debugLog("Focused element search results", {
666
+ topMatches: focusedElementHints.slice(0, 3).map(h => ({
667
+ path: h.path,
668
+ score: h.score,
669
+ matches: h.matches
670
+ }))
671
+ });
672
+ }
673
+
578
674
  // PHASE 3: Ask LLM to pick the best file from actual file list
579
675
  if (searchResults.length > 0) {
580
676
  const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
@@ -582,7 +678,8 @@ export async function POST(request: Request) {
582
678
  screenshot,
583
679
  userPrompt,
584
680
  candidateFiles,
585
- apiKey
681
+ apiKey,
682
+ focusedElementHints
586
683
  );
587
684
 
588
685
  if (selectedFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.58",
3
+ "version": "1.3.59",
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",