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.
|
|
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",
|