sonance-brand-mcp 1.3.61 → 1.3.63

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.
@@ -106,7 +106,7 @@ async function analyzeScreenshotForSearch(
106
106
 
107
107
  const response = await anthropic.messages.create({
108
108
  model: "claude-sonnet-4-20250514",
109
- max_tokens: 1024,
109
+ max_tokens: 2048, // Increased for better analysis
110
110
  messages: [
111
111
  {
112
112
  role: "user",
@@ -174,6 +174,8 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
174
174
  /**
175
175
  * Search candidate files for JSX code matching the focused element
176
176
  * This helps identify which file actually contains the element the user clicked on
177
+ *
178
+ * CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
177
179
  */
178
180
  function findFilesContainingElement(
179
181
  focusedElements: VisionFocusedElement[] | undefined,
@@ -188,44 +190,68 @@ function findFilesContainingElement(
188
190
  for (const file of candidateFiles) {
189
191
  let score = 0;
190
192
  const matches: string[] = [];
191
- const content = file.content.toLowerCase();
193
+ const content = file.content;
194
+ const contentLower = content.toLowerCase();
192
195
 
193
196
  for (const el of focusedElements) {
194
197
  // Extract element type from the name (e.g., "button #123" -> "button")
195
198
  const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
196
199
  const componentType = el.type.toLowerCase();
200
+ const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
197
201
 
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}>`);
202
+ // HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
203
+ if (content.includes(`export function ${capitalizedType}`) ||
204
+ content.includes(`export const ${capitalizedType}`) ||
205
+ content.includes(`export default function ${capitalizedType}`)) {
206
+ score += 100;
207
+ matches.push(`EXPORTS ${capitalizedType}`);
203
208
  }
204
209
 
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
+ // HIGH VALUE: Component is DEFINED in this file
211
+ if (content.includes(`function ${capitalizedType}(`) ||
212
+ content.includes(`const ${capitalizedType} =`) ||
213
+ content.includes(`const ${capitalizedType}:`) ||
214
+ content.includes(`function ${capitalizedType} (`)) {
215
+ score += 50;
216
+ matches.push(`defines ${capitalizedType}`);
210
217
  }
211
218
 
212
- // Check for component imports
213
- if (content.includes(`import`) && content.includes(componentType)) {
214
- score += 5;
219
+ // MEDIUM VALUE: JSX usage of this element type (rendered in this file)
220
+ const jsxTagPattern = new RegExp(`<${capitalizedType}[\\s/>]`, 'g');
221
+ const jsxMatches = content.match(jsxTagPattern);
222
+ if (jsxMatches) {
223
+ score += 20 * jsxMatches.length;
224
+ matches.push(`<${capitalizedType}> x${jsxMatches.length}`);
215
225
  }
216
226
 
217
- // Check for event handlers commonly associated with buttons
227
+ // MEDIUM VALUE: HTML tag usage
228
+ const htmlTagPattern = new RegExp(`<${elementType}[\\s>]`, 'gi');
229
+ const htmlMatches = content.match(htmlTagPattern);
230
+ if (htmlMatches) {
231
+ score += 10 * htmlMatches.length;
232
+ matches.push(`<${elementType}> x${htmlMatches.length}`);
233
+ }
234
+
235
+ // LOW VALUE: Event handlers (onClick, onPress, etc.)
218
236
  if (elementType === "button" || componentType === "button") {
219
- if (content.includes("onclick") || content.includes("onpress")) {
220
- score += 3;
237
+ const clickHandlers = content.match(/onClick\s*=/gi);
238
+ if (clickHandlers) {
239
+ score += 5 * clickHandlers.length;
240
+ matches.push(`onClick x${clickHandlers.length}`);
221
241
  }
222
242
  }
223
243
 
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}`);
244
+ // BONUS: File name contains the element type
245
+ const fileName = file.path.toLowerCase();
246
+ if (fileName.includes(elementType) || fileName.includes(componentType)) {
247
+ score += 30;
248
+ matches.push(`filename contains ${elementType}`);
249
+ }
250
+
251
+ // BONUS: File is a Row/Cell component (likely contains action buttons)
252
+ if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
253
+ score += 15;
254
+ matches.push('row/cell component');
229
255
  }
230
256
  }
231
257
 
@@ -511,7 +537,7 @@ function searchFilesSmart(
511
537
  return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
512
538
  }
513
539
 
514
- const VISION_SYSTEM_PROMPT = `You edit code. Make ONLY the change requested.
540
+ const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
515
541
 
516
542
  RULES:
517
543
  1. Copy code EXACTLY from the file - character for character
@@ -519,13 +545,8 @@ RULES:
519
545
  3. For color changes, just change the className
520
546
  4. Do not restructure or reorganize code
521
547
 
522
- Return JSON:
523
- {
524
- "modifications": [{
525
- "filePath": "path",
526
- "patches": [{ "search": "exact code from file", "replace": "changed code" }]
527
- }]
528
- }`;
548
+ RESPOND WITH ONLY THIS JSON FORMAT (nothing else):
549
+ {"modifications":[{"filePath":"path","patches":[{"search":"exact code","replace":"changed code"}]}]}`;
529
550
 
530
551
  export async function POST(request: Request) {
531
552
  // Only allow in development
@@ -673,8 +694,19 @@ export async function POST(request: Request) {
673
694
  });
674
695
  }
675
696
 
676
- // PHASE 3: Ask LLM to pick the best file from actual file list
677
- if (searchResults.length > 0) {
697
+ // CURSOR-STYLE: If focused element search has high confidence, use it directly
698
+ // Skip the LLM guessing phase - we found the file that contains the code
699
+ const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
700
+
701
+ if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
702
+ // HIGH CONFIDENCE: Use focused element match directly
703
+ recommendedFile = {
704
+ path: focusedElementHints[0].path,
705
+ reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
706
+ };
707
+ debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
708
+ } else if (searchResults.length > 0) {
709
+ // LOW CONFIDENCE: Fall back to LLM selection
678
710
  const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
679
711
  const selectedFile = await selectBestFileFromList(
680
712
  screenshot,
@@ -992,12 +1024,42 @@ This is better than generating patches with made-up code.`,
992
1024
 
993
1025
  jsonText = jsonText.trim();
994
1026
 
995
- // Robust JSON extraction: find the first { and last } to extract JSON object
996
- // This handles cases where the LLM includes preamble text before the JSON
1027
+ // Robust JSON extraction: look for {"modifications" pattern specifically
1028
+ // This handles cases where the LLM includes preamble text with code blocks
1029
+ const jsonStartPatterns = ['{"modifications"', '{ "modifications"', '{\n "modifications"'];
1030
+ let jsonStart = -1;
1031
+
1032
+ for (const pattern of jsonStartPatterns) {
1033
+ const idx = jsonText.indexOf(pattern);
1034
+ if (idx !== -1 && (jsonStart === -1 || idx < jsonStart)) {
1035
+ jsonStart = idx;
1036
+ }
1037
+ }
1038
+
1039
+ if (jsonStart !== -1) {
1040
+ // Find the matching closing brace by counting braces
1041
+ let braceCount = 0;
1042
+ let jsonEnd = -1;
1043
+ for (let i = jsonStart; i < jsonText.length; i++) {
1044
+ if (jsonText[i] === '{') braceCount++;
1045
+ if (jsonText[i] === '}') {
1046
+ braceCount--;
1047
+ if (braceCount === 0) {
1048
+ jsonEnd = i;
1049
+ break;
1050
+ }
1051
+ }
1052
+ }
1053
+ if (jsonEnd !== -1) {
1054
+ jsonText = jsonText.substring(jsonStart, jsonEnd + 1);
1055
+ }
1056
+ } else {
1057
+ // Fallback: try first { to last }
997
1058
  const firstBrace = jsonText.indexOf('{');
998
1059
  const lastBrace = jsonText.lastIndexOf('}');
999
1060
  if (firstBrace !== -1 && lastBrace > firstBrace) {
1000
1061
  jsonText = jsonText.substring(firstBrace, lastBrace + 1);
1062
+ }
1001
1063
  }
1002
1064
 
1003
1065
  aiResponse = JSON.parse(jsonText);
@@ -2397,3 +2459,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
2397
2459
  failedPatches,
2398
2460
  };
2399
2461
  }
2462
+
@@ -102,7 +102,7 @@ async function analyzeScreenshotForSearch(
102
102
 
103
103
  const response = await anthropic.messages.create({
104
104
  model: "claude-sonnet-4-20250514",
105
- max_tokens: 1024,
105
+ max_tokens: 2048, // Increased for better analysis
106
106
  messages: [
107
107
  {
108
108
  role: "user",
@@ -170,6 +170,8 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
170
170
  /**
171
171
  * Search candidate files for JSX code matching the focused element
172
172
  * This helps identify which file actually contains the element the user clicked on
173
+ *
174
+ * CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
173
175
  */
174
176
  function findFilesContainingElement(
175
177
  focusedElements: VisionFocusedElement[] | undefined,
@@ -184,44 +186,68 @@ function findFilesContainingElement(
184
186
  for (const file of candidateFiles) {
185
187
  let score = 0;
186
188
  const matches: string[] = [];
187
- const content = file.content.toLowerCase();
189
+ const content = file.content;
190
+ const contentLower = content.toLowerCase();
188
191
 
189
192
  for (const el of focusedElements) {
190
193
  // Extract element type from the name (e.g., "button #123" -> "button")
191
194
  const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
192
195
  const componentType = el.type.toLowerCase();
196
+ const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
193
197
 
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}>`);
198
+ // HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
199
+ if (content.includes(`export function ${capitalizedType}`) ||
200
+ content.includes(`export const ${capitalizedType}`) ||
201
+ content.includes(`export default function ${capitalizedType}`)) {
202
+ score += 100;
203
+ matches.push(`EXPORTS ${capitalizedType}`);
199
204
  }
200
205
 
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
+ // HIGH VALUE: Component is DEFINED in this file
207
+ if (content.includes(`function ${capitalizedType}(`) ||
208
+ content.includes(`const ${capitalizedType} =`) ||
209
+ content.includes(`const ${capitalizedType}:`) ||
210
+ content.includes(`function ${capitalizedType} (`)) {
211
+ score += 50;
212
+ matches.push(`defines ${capitalizedType}`);
206
213
  }
207
214
 
208
- // Check for component imports
209
- if (content.includes(`import`) && content.includes(componentType)) {
210
- score += 5;
215
+ // MEDIUM VALUE: JSX usage of this element type (rendered in this file)
216
+ const jsxTagPattern = new RegExp(`<${capitalizedType}[\\s/>]`, 'g');
217
+ const jsxMatches = content.match(jsxTagPattern);
218
+ if (jsxMatches) {
219
+ score += 20 * jsxMatches.length;
220
+ matches.push(`<${capitalizedType}> x${jsxMatches.length}`);
211
221
  }
212
222
 
213
- // Check for event handlers commonly associated with buttons
223
+ // MEDIUM VALUE: HTML tag usage
224
+ const htmlTagPattern = new RegExp(`<${elementType}[\\s>]`, 'gi');
225
+ const htmlMatches = content.match(htmlTagPattern);
226
+ if (htmlMatches) {
227
+ score += 10 * htmlMatches.length;
228
+ matches.push(`<${elementType}> x${htmlMatches.length}`);
229
+ }
230
+
231
+ // LOW VALUE: Event handlers (onClick, onPress, etc.)
214
232
  if (elementType === "button" || componentType === "button") {
215
- if (content.includes("onclick") || content.includes("onpress")) {
216
- score += 3;
233
+ const clickHandlers = content.match(/onClick\s*=/gi);
234
+ if (clickHandlers) {
235
+ score += 5 * clickHandlers.length;
236
+ matches.push(`onClick x${clickHandlers.length}`);
217
237
  }
218
238
  }
219
239
 
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}`);
240
+ // BONUS: File name contains the element type
241
+ const fileName = file.path.toLowerCase();
242
+ if (fileName.includes(elementType) || fileName.includes(componentType)) {
243
+ score += 30;
244
+ matches.push(`filename contains ${elementType}`);
245
+ }
246
+
247
+ // BONUS: File is a Row/Cell component (likely contains action buttons)
248
+ if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
249
+ score += 15;
250
+ matches.push('row/cell component');
225
251
  }
226
252
  }
227
253
 
@@ -507,7 +533,7 @@ function searchFilesSmart(
507
533
  return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
508
534
  }
509
535
 
510
- const VISION_SYSTEM_PROMPT = `You edit code. Make ONLY the change requested.
536
+ const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
511
537
 
512
538
  RULES:
513
539
  1. Copy code EXACTLY from the file - character for character
@@ -515,13 +541,8 @@ RULES:
515
541
  3. For color changes, just change the className
516
542
  4. Do not restructure or reorganize code
517
543
 
518
- Return JSON:
519
- {
520
- "modifications": [{
521
- "filePath": "path",
522
- "patches": [{ "search": "exact code from file", "replace": "changed code" }]
523
- }]
524
- }`;
544
+ RESPOND WITH ONLY THIS JSON FORMAT (nothing else):
545
+ {"modifications":[{"filePath":"path","patches":[{"search":"exact code","replace":"changed code"}]}]}`;
525
546
 
526
547
  export async function POST(request: Request) {
527
548
  // Only allow in development
@@ -642,8 +663,19 @@ export async function POST(request: Request) {
642
663
  });
643
664
  }
644
665
 
645
- // PHASE 3: Ask LLM to pick the best file from actual file list
646
- if (searchResults.length > 0) {
666
+ // CURSOR-STYLE: If focused element search has high confidence, use it directly
667
+ // Skip the LLM guessing phase - we found the file that contains the code
668
+ const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
669
+
670
+ if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
671
+ // HIGH CONFIDENCE: Use focused element match directly
672
+ recommendedFile = {
673
+ path: focusedElementHints[0].path,
674
+ reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
675
+ };
676
+ debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
677
+ } else if (searchResults.length > 0) {
678
+ // LOW CONFIDENCE: Fall back to LLM selection
647
679
  const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
648
680
  const selectedFile = await selectBestFileFromList(
649
681
  screenshot,
@@ -958,12 +990,42 @@ This is better than generating patches with made-up code.`,
958
990
  // Clean up any remaining whitespace
959
991
  jsonText = jsonText.trim();
960
992
 
961
- // Robust JSON extraction: find the first { and last } to extract JSON object
962
- // This handles cases where the LLM includes preamble text before the JSON
993
+ // Robust JSON extraction: look for {"modifications" pattern specifically
994
+ // This handles cases where the LLM includes preamble text with code blocks
995
+ const jsonStartPatterns = ['{"modifications"', '{ "modifications"', '{\n "modifications"'];
996
+ let jsonStart = -1;
997
+
998
+ for (const pattern of jsonStartPatterns) {
999
+ const idx = jsonText.indexOf(pattern);
1000
+ if (idx !== -1 && (jsonStart === -1 || idx < jsonStart)) {
1001
+ jsonStart = idx;
1002
+ }
1003
+ }
1004
+
1005
+ if (jsonStart !== -1) {
1006
+ // Find the matching closing brace by counting braces
1007
+ let braceCount = 0;
1008
+ let jsonEnd = -1;
1009
+ for (let i = jsonStart; i < jsonText.length; i++) {
1010
+ if (jsonText[i] === '{') braceCount++;
1011
+ if (jsonText[i] === '}') {
1012
+ braceCount--;
1013
+ if (braceCount === 0) {
1014
+ jsonEnd = i;
1015
+ break;
1016
+ }
1017
+ }
1018
+ }
1019
+ if (jsonEnd !== -1) {
1020
+ jsonText = jsonText.substring(jsonStart, jsonEnd + 1);
1021
+ }
1022
+ } else {
1023
+ // Fallback: try first { to last }
963
1024
  const firstBrace = jsonText.indexOf('{');
964
1025
  const lastBrace = jsonText.lastIndexOf('}');
965
1026
  if (firstBrace !== -1 && lastBrace > firstBrace) {
966
1027
  jsonText = jsonText.substring(firstBrace, lastBrace + 1);
1028
+ }
967
1029
  }
968
1030
 
969
1031
  aiResponse = JSON.parse(jsonText);
@@ -2206,3 +2268,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
2206
2268
  failedPatches,
2207
2269
  };
2208
2270
  }
2271
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.61",
3
+ "version": "1.3.63",
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",