sonance-brand-mcp 1.3.44 → 1.3.45
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.
|
@@ -168,6 +168,73 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Phase 3: Ask LLM to select the best file from actual file list
|
|
173
|
+
* This replaces guessing - the LLM sees the real filenames and picks one
|
|
174
|
+
*/
|
|
175
|
+
async function selectBestFileFromList(
|
|
176
|
+
screenshot: string,
|
|
177
|
+
userPrompt: string,
|
|
178
|
+
candidateFiles: string[],
|
|
179
|
+
apiKey: string
|
|
180
|
+
): Promise<string | null> {
|
|
181
|
+
if (candidateFiles.length === 0) return null;
|
|
182
|
+
|
|
183
|
+
const anthropic = new Anthropic({ apiKey });
|
|
184
|
+
const base64Data = screenshot.split(",")[1] || screenshot;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const response = await anthropic.messages.create({
|
|
188
|
+
model: "claude-sonnet-4-20250514",
|
|
189
|
+
max_tokens: 256,
|
|
190
|
+
messages: [{
|
|
191
|
+
role: "user",
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
type: "image",
|
|
195
|
+
source: { type: "base64", media_type: "image/png", data: base64Data }
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: `The user wants to: "${userPrompt}"
|
|
200
|
+
|
|
201
|
+
Here are the actual component files found in this codebase:
|
|
202
|
+
${candidateFiles.map((f, i) => `${i + 1}. ${f}`).join('\n')}
|
|
203
|
+
|
|
204
|
+
Looking at the screenshot, which file MOST LIKELY contains the UI elements the user wants to modify?
|
|
205
|
+
|
|
206
|
+
IMPORTANT: Return ONLY the exact file path from the list above (e.g., "components/ProcessCatalogue/ProcessDetailPanel.tsx").
|
|
207
|
+
Do not add any explanation or other text.`
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}]
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const textBlock = response.content.find(b => b.type === "text");
|
|
214
|
+
if (!textBlock || textBlock.type !== "text") return null;
|
|
215
|
+
|
|
216
|
+
const selectedPath = textBlock.text.trim().replace(/^["']|["']$/g, ''); // Remove quotes if present
|
|
217
|
+
|
|
218
|
+
// Validate it's in our list (exact match or ends with the path)
|
|
219
|
+
const matchedFile = candidateFiles.find(f =>
|
|
220
|
+
f === selectedPath ||
|
|
221
|
+
f.endsWith(selectedPath) ||
|
|
222
|
+
selectedPath.endsWith(f)
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
debugLog("Phase 3: LLM selected file from list", {
|
|
226
|
+
selectedPath,
|
|
227
|
+
matchedFile,
|
|
228
|
+
candidateCount: candidateFiles.length
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return matchedFile || null;
|
|
232
|
+
} catch (e) {
|
|
233
|
+
debugLog("Phase 3: Failed to select file from list", { error: String(e) });
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
171
238
|
/**
|
|
172
239
|
* Search for component files by name across the project
|
|
173
240
|
* Returns the file path if found, null otherwise
|
|
@@ -519,37 +586,23 @@ export async function POST(request: Request) {
|
|
|
519
586
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
520
587
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
521
588
|
|
|
522
|
-
//
|
|
589
|
+
// PHASE 3: Ask LLM to pick the best file from actual file list
|
|
523
590
|
if (searchResults.length > 0) {
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const filenameMatch = searchResults.find(r => {
|
|
532
|
-
const filename = getFilenameWithoutExt(r.path);
|
|
533
|
-
return analysis.componentNames.some(name => {
|
|
534
|
-
const nameLower = name.toLowerCase();
|
|
535
|
-
// Exact match: ProcessDetailPanel === processdetailpanel
|
|
536
|
-
// Or filename contains the component name
|
|
537
|
-
return filename === nameLower || filename.includes(nameLower);
|
|
538
|
-
});
|
|
539
|
-
});
|
|
591
|
+
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
592
|
+
const selectedFile = await selectBestFileFromList(
|
|
593
|
+
screenshot,
|
|
594
|
+
userPrompt,
|
|
595
|
+
candidateFiles,
|
|
596
|
+
apiKey
|
|
597
|
+
);
|
|
540
598
|
|
|
541
|
-
if (
|
|
542
|
-
const matchedName = analysis.componentNames.find(name => {
|
|
543
|
-
const filename = getFilenameWithoutExt(filenameMatch.path);
|
|
544
|
-
const nameLower = name.toLowerCase();
|
|
545
|
-
return filename === nameLower || filename.includes(nameLower);
|
|
546
|
-
});
|
|
599
|
+
if (selectedFile) {
|
|
547
600
|
recommendedFile = {
|
|
548
|
-
path:
|
|
549
|
-
reason: `
|
|
601
|
+
path: selectedFile,
|
|
602
|
+
reason: `LLM selected from ${candidateFiles.length} candidate files`
|
|
550
603
|
};
|
|
551
604
|
} else {
|
|
552
|
-
//
|
|
605
|
+
// Fallback to highest score if LLM selection fails
|
|
553
606
|
recommendedFile = {
|
|
554
607
|
path: searchResults[0].path,
|
|
555
608
|
reason: `Highest content match score (${searchResults[0].score} points)`
|
|
@@ -166,6 +166,73 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Phase 3: Ask LLM to select the best file from actual file list
|
|
171
|
+
* This replaces guessing - the LLM sees the real filenames and picks one
|
|
172
|
+
*/
|
|
173
|
+
async function selectBestFileFromList(
|
|
174
|
+
screenshot: string,
|
|
175
|
+
userPrompt: string,
|
|
176
|
+
candidateFiles: string[],
|
|
177
|
+
apiKey: string
|
|
178
|
+
): Promise<string | null> {
|
|
179
|
+
if (candidateFiles.length === 0) return null;
|
|
180
|
+
|
|
181
|
+
const anthropic = new Anthropic({ apiKey });
|
|
182
|
+
const base64Data = screenshot.split(",")[1] || screenshot;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const response = await anthropic.messages.create({
|
|
186
|
+
model: "claude-sonnet-4-20250514",
|
|
187
|
+
max_tokens: 256,
|
|
188
|
+
messages: [{
|
|
189
|
+
role: "user",
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "image",
|
|
193
|
+
source: { type: "base64", media_type: "image/png", data: base64Data }
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "text",
|
|
197
|
+
text: `The user wants to: "${userPrompt}"
|
|
198
|
+
|
|
199
|
+
Here are the actual component files found in this codebase:
|
|
200
|
+
${candidateFiles.map((f, i) => `${i + 1}. ${f}`).join('\n')}
|
|
201
|
+
|
|
202
|
+
Looking at the screenshot, which file MOST LIKELY contains the UI elements the user wants to modify?
|
|
203
|
+
|
|
204
|
+
IMPORTANT: Return ONLY the exact file path from the list above (e.g., "components/ProcessCatalogue/ProcessDetailPanel.tsx").
|
|
205
|
+
Do not add any explanation or other text.`
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}]
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const textBlock = response.content.find(b => b.type === "text");
|
|
212
|
+
if (!textBlock || textBlock.type !== "text") return null;
|
|
213
|
+
|
|
214
|
+
const selectedPath = textBlock.text.trim().replace(/^["']|["']$/g, ''); // Remove quotes if present
|
|
215
|
+
|
|
216
|
+
// Validate it's in our list (exact match or ends with the path)
|
|
217
|
+
const matchedFile = candidateFiles.find(f =>
|
|
218
|
+
f === selectedPath ||
|
|
219
|
+
f.endsWith(selectedPath) ||
|
|
220
|
+
selectedPath.endsWith(f)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
debugLog("Phase 3: LLM selected file from list", {
|
|
224
|
+
selectedPath,
|
|
225
|
+
matchedFile,
|
|
226
|
+
candidateCount: candidateFiles.length
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return matchedFile || null;
|
|
230
|
+
} catch (e) {
|
|
231
|
+
debugLog("Phase 3: Failed to select file from list", { error: String(e) });
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
169
236
|
/**
|
|
170
237
|
* Search for component files by name across the project (for smart search)
|
|
171
238
|
* Returns the file path if found, null otherwise
|
|
@@ -529,37 +596,23 @@ export async function POST(request: Request) {
|
|
|
529
596
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
530
597
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
531
598
|
|
|
532
|
-
//
|
|
599
|
+
// PHASE 3: Ask LLM to pick the best file from actual file list
|
|
533
600
|
if (searchResults.length > 0) {
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const filenameMatch = searchResults.find(r => {
|
|
542
|
-
const filename = getFilenameWithoutExt(r.path);
|
|
543
|
-
return analysis.componentNames.some(name => {
|
|
544
|
-
const nameLower = name.toLowerCase();
|
|
545
|
-
// Exact match: ProcessDetailPanel === processdetailpanel
|
|
546
|
-
// Or filename contains the component name
|
|
547
|
-
return filename === nameLower || filename.includes(nameLower);
|
|
548
|
-
});
|
|
549
|
-
});
|
|
601
|
+
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
602
|
+
const selectedFile = await selectBestFileFromList(
|
|
603
|
+
screenshot,
|
|
604
|
+
userPrompt,
|
|
605
|
+
candidateFiles,
|
|
606
|
+
apiKey
|
|
607
|
+
);
|
|
550
608
|
|
|
551
|
-
if (
|
|
552
|
-
const matchedName = analysis.componentNames.find(name => {
|
|
553
|
-
const filename = getFilenameWithoutExt(filenameMatch.path);
|
|
554
|
-
const nameLower = name.toLowerCase();
|
|
555
|
-
return filename === nameLower || filename.includes(nameLower);
|
|
556
|
-
});
|
|
609
|
+
if (selectedFile) {
|
|
557
610
|
recommendedFile = {
|
|
558
|
-
path:
|
|
559
|
-
reason: `
|
|
611
|
+
path: selectedFile,
|
|
612
|
+
reason: `LLM selected from ${candidateFiles.length} candidate files`
|
|
560
613
|
};
|
|
561
614
|
} else {
|
|
562
|
-
//
|
|
615
|
+
// Fallback to highest score if LLM selection fails
|
|
563
616
|
recommendedFile = {
|
|
564
617
|
path: searchResults[0].path,
|
|
565
618
|
reason: `Highest content match score (${searchResults[0].score} points)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.45",
|
|
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",
|