sonance-brand-mcp 1.3.100 → 1.3.101
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.
|
@@ -2087,6 +2087,124 @@ export async function POST(request: Request) {
|
|
|
2087
2087
|
}
|
|
2088
2088
|
}
|
|
2089
2089
|
|
|
2090
|
+
// ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
|
|
2091
|
+
// This determines the ACTUAL target file by checking for text matches in imported components
|
|
2092
|
+
// Must run first so that all subsequent code uses the correct file path
|
|
2093
|
+
let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
|
|
2094
|
+
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2095
|
+
|
|
2096
|
+
if (actualTargetFile && focusedElements && focusedElements.length > 0) {
|
|
2097
|
+
const content = actualTargetFile.content;
|
|
2098
|
+
|
|
2099
|
+
// Search for focused element in the file using multiple strategies
|
|
2100
|
+
// Priority: DOM id > textContent > className patterns
|
|
2101
|
+
for (const el of focusedElements) {
|
|
2102
|
+
elementLocation = findElementLineInFile(content, el);
|
|
2103
|
+
if (elementLocation) {
|
|
2104
|
+
debugLog("Found focused element in file", {
|
|
2105
|
+
matchedBy: elementLocation.matchedBy,
|
|
2106
|
+
lineNumber: elementLocation.lineNumber,
|
|
2107
|
+
confidence: elementLocation.confidence,
|
|
2108
|
+
file: actualTargetFile.path,
|
|
2109
|
+
});
|
|
2110
|
+
break;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
2115
|
+
// text strings they contain. The file with the highest score is the TRUE target.
|
|
2116
|
+
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
2117
|
+
const allTextContent = focusedElements
|
|
2118
|
+
.filter(e => e.textContent && e.textContent.length > 5)
|
|
2119
|
+
.map(e => e.textContent!);
|
|
2120
|
+
|
|
2121
|
+
if (allTextContent.length > 0) {
|
|
2122
|
+
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
2123
|
+
currentFile: actualTargetFile.path,
|
|
2124
|
+
currentConfidence: elementLocation.confidence,
|
|
2125
|
+
textCount: allTextContent.length,
|
|
2126
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
const bestMatch = scoreFilesForTextContent(
|
|
2130
|
+
allTextContent,
|
|
2131
|
+
pageContext.componentSources
|
|
2132
|
+
);
|
|
2133
|
+
|
|
2134
|
+
const currentFileScore = allTextContent.filter(text =>
|
|
2135
|
+
actualTargetFile!.content.includes(text.substring(0, 20))
|
|
2136
|
+
).length;
|
|
2137
|
+
|
|
2138
|
+
debugLog("Text scoring comparison", {
|
|
2139
|
+
currentFile: actualTargetFile.path,
|
|
2140
|
+
currentScore: currentFileScore,
|
|
2141
|
+
bestImport: bestMatch?.path || 'none',
|
|
2142
|
+
bestImportScore: bestMatch?.score || 0
|
|
2143
|
+
});
|
|
2144
|
+
|
|
2145
|
+
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
2146
|
+
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
2147
|
+
originalFile: actualTargetFile.path,
|
|
2148
|
+
originalScore: currentFileScore,
|
|
2149
|
+
redirectTo: bestMatch.path,
|
|
2150
|
+
redirectScore: bestMatch.score,
|
|
2151
|
+
matchedTexts: bestMatch.matchedTexts
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
actualTargetFile = {
|
|
2155
|
+
path: bestMatch.path,
|
|
2156
|
+
content: bestMatch.content
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
const lines = bestMatch.content.split('\n');
|
|
2160
|
+
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
2161
|
+
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
2162
|
+
|
|
2163
|
+
elementLocation = {
|
|
2164
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
2165
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
2166
|
+
confidence: 'high',
|
|
2167
|
+
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
2174
|
+
if (!elementLocation) {
|
|
2175
|
+
debugLog("Element not in main file, searching imported components...", {
|
|
2176
|
+
mainFile: actualTargetFile.path,
|
|
2177
|
+
importedFilesCount: pageContext.componentSources.length
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
const importedMatch = findElementInImportedFiles(
|
|
2181
|
+
focusedElements[0],
|
|
2182
|
+
pageContext.componentSources
|
|
2183
|
+
);
|
|
2184
|
+
|
|
2185
|
+
if (importedMatch) {
|
|
2186
|
+
debugLog("REDIRECT: Element found in imported component", {
|
|
2187
|
+
originalFile: actualTargetFile.path,
|
|
2188
|
+
redirectTo: importedMatch.path,
|
|
2189
|
+
matchedBy: importedMatch.matchedBy,
|
|
2190
|
+
lineNumber: importedMatch.lineNumber
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
actualTargetFile = {
|
|
2194
|
+
path: importedMatch.path,
|
|
2195
|
+
content: importedMatch.content
|
|
2196
|
+
};
|
|
2197
|
+
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
debugLog("File redirect complete", {
|
|
2203
|
+
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2204
|
+
actualTarget: actualTargetFile?.path || 'none',
|
|
2205
|
+
wasRedirected: actualTargetFile?.path !== recommendedFileContent?.path
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2090
2208
|
// Build text content
|
|
2091
2209
|
let textContent = `VISION MODE EDIT REQUEST
|
|
2092
2210
|
|
|
@@ -2110,7 +2228,8 @@ User Request: "${userPrompt}"
|
|
|
2110
2228
|
|
|
2111
2229
|
// Phase 1: Analyze the screenshot to identify specific problems
|
|
2112
2230
|
// This is a SEPARATE LLM call that only identifies problems
|
|
2113
|
-
|
|
2231
|
+
// Use actualTargetFile since redirect has already happened
|
|
2232
|
+
const fileContentForAnalysis = actualTargetFile?.content || pageContext.pageContent || '';
|
|
2114
2233
|
identifiedProblems = await analyzeDesignProblems(
|
|
2115
2234
|
screenshot,
|
|
2116
2235
|
fileContentForAnalysis,
|
|
@@ -2135,124 +2254,8 @@ User Request: "${userPrompt}"
|
|
|
2135
2254
|
|
|
2136
2255
|
// ========== TARGET COMPONENT ONLY (with line numbers) ==========
|
|
2137
2256
|
// CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
// Search for focused element in the file using multiple strategies
|
|
2142
|
-
// Priority: DOM id > textContent > className patterns
|
|
2143
|
-
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2144
|
-
let actualTargetFile = recommendedFileContent; // May change if we redirect
|
|
2145
|
-
|
|
2146
|
-
if (focusedElements && focusedElements.length > 0) {
|
|
2147
|
-
for (const el of focusedElements) {
|
|
2148
|
-
elementLocation = findElementLineInFile(content, el);
|
|
2149
|
-
if (elementLocation) {
|
|
2150
|
-
debugLog("Found focused element in file", {
|
|
2151
|
-
matchedBy: elementLocation.matchedBy,
|
|
2152
|
-
lineNumber: elementLocation.lineNumber,
|
|
2153
|
-
confidence: elementLocation.confidence,
|
|
2154
|
-
file: recommendedFileContent.path,
|
|
2155
|
-
});
|
|
2156
|
-
break;
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
2161
|
-
// text strings they contain. The file with the highest score is the TRUE target.
|
|
2162
|
-
// This handles parent/child component cases accurately.
|
|
2163
|
-
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
2164
|
-
// Collect ALL text content from focused elements
|
|
2165
|
-
const allTextContent = focusedElements
|
|
2166
|
-
.filter(e => e.textContent && e.textContent.length > 5)
|
|
2167
|
-
.map(e => e.textContent!);
|
|
2168
|
-
|
|
2169
|
-
if (allTextContent.length > 0) {
|
|
2170
|
-
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
2171
|
-
currentFile: recommendedFileContent.path,
|
|
2172
|
-
currentConfidence: elementLocation.confidence,
|
|
2173
|
-
textCount: allTextContent.length,
|
|
2174
|
-
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
2175
|
-
});
|
|
2176
|
-
|
|
2177
|
-
// Score all imported files
|
|
2178
|
-
const bestMatch = scoreFilesForTextContent(
|
|
2179
|
-
allTextContent,
|
|
2180
|
-
pageContext.componentSources
|
|
2181
|
-
);
|
|
2182
|
-
|
|
2183
|
-
// Also score the current file for comparison
|
|
2184
|
-
const currentFileScore = allTextContent.filter(text =>
|
|
2185
|
-
recommendedFileContent.content.includes(text.substring(0, 20))
|
|
2186
|
-
).length;
|
|
2187
|
-
|
|
2188
|
-
debugLog("Text scoring comparison", {
|
|
2189
|
-
currentFile: recommendedFileContent.path,
|
|
2190
|
-
currentScore: currentFileScore,
|
|
2191
|
-
bestImport: bestMatch?.path || 'none',
|
|
2192
|
-
bestImportScore: bestMatch?.score || 0
|
|
2193
|
-
});
|
|
2194
|
-
|
|
2195
|
-
// Redirect only if imported file has MORE matches than current file
|
|
2196
|
-
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
2197
|
-
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
2198
|
-
originalFile: recommendedFileContent.path,
|
|
2199
|
-
originalScore: currentFileScore,
|
|
2200
|
-
redirectTo: bestMatch.path,
|
|
2201
|
-
redirectScore: bestMatch.score,
|
|
2202
|
-
matchedTexts: bestMatch.matchedTexts
|
|
2203
|
-
});
|
|
2204
|
-
|
|
2205
|
-
// Switch target file to where most text content lives
|
|
2206
|
-
actualTargetFile = {
|
|
2207
|
-
path: bestMatch.path,
|
|
2208
|
-
content: bestMatch.content
|
|
2209
|
-
};
|
|
2210
|
-
|
|
2211
|
-
// Find a line with matched text for element location
|
|
2212
|
-
const lines = bestMatch.content.split('\n');
|
|
2213
|
-
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
2214
|
-
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
2215
|
-
|
|
2216
|
-
elementLocation = {
|
|
2217
|
-
lineNumber: bestMatch.firstMatchLine,
|
|
2218
|
-
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
2219
|
-
confidence: 'high',
|
|
2220
|
-
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
2221
|
-
};
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
|
|
2226
|
-
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
2227
|
-
if (!elementLocation) {
|
|
2228
|
-
debugLog("Element not in main file, searching imported components...", {
|
|
2229
|
-
mainFile: recommendedFileContent.path,
|
|
2230
|
-
importedFilesCount: pageContext.componentSources.length
|
|
2231
|
-
});
|
|
2232
|
-
|
|
2233
|
-
const importedMatch = findElementInImportedFiles(
|
|
2234
|
-
focusedElements[0],
|
|
2235
|
-
pageContext.componentSources
|
|
2236
|
-
);
|
|
2237
|
-
|
|
2238
|
-
if (importedMatch) {
|
|
2239
|
-
debugLog("REDIRECT: Element found in imported component", {
|
|
2240
|
-
originalFile: recommendedFileContent.path,
|
|
2241
|
-
redirectTo: importedMatch.path,
|
|
2242
|
-
matchedBy: importedMatch.matchedBy,
|
|
2243
|
-
lineNumber: importedMatch.lineNumber
|
|
2244
|
-
});
|
|
2245
|
-
|
|
2246
|
-
// Switch target file to where element actually is
|
|
2247
|
-
actualTargetFile = {
|
|
2248
|
-
path: importedMatch.path,
|
|
2249
|
-
content: importedMatch.content
|
|
2250
|
-
};
|
|
2251
|
-
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2257
|
+
// Note: Redirect logic already ran above, so actualTargetFile is the correct file
|
|
2258
|
+
if (actualTargetFile) {
|
|
2256
2259
|
// Build focused elements section with precise targeting info
|
|
2257
2260
|
if (focusedElements && focusedElements.length > 0) {
|
|
2258
2261
|
textContent += `FOCUSED ELEMENTS (user clicked on these):\n`;
|
|
@@ -2316,7 +2319,7 @@ ${elementLocation.snippet}
|
|
|
2316
2319
|
// Element NOT found in main file OR any imported components
|
|
2317
2320
|
// BLOCK the LLM from guessing - require empty modifications
|
|
2318
2321
|
debugLog("BLOCK: Could not locate focused element anywhere", {
|
|
2319
|
-
mainFile:
|
|
2322
|
+
mainFile: actualTargetFile.path,
|
|
2320
2323
|
searchedImports: pageContext.componentSources.length,
|
|
2321
2324
|
focusedElements: focusedElements.map(el => ({
|
|
2322
2325
|
name: el.name,
|
|
@@ -2332,7 +2335,7 @@ ${elementLocation.snippet}
|
|
|
2332
2335
|
⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
|
|
2333
2336
|
|
|
2334
2337
|
The user clicked on a specific element, but it could NOT be found in:
|
|
2335
|
-
- ${
|
|
2338
|
+
- ${actualTargetFile.path} (main target file)
|
|
2336
2339
|
- Any of the ${pageContext.componentSources.length} imported component files
|
|
2337
2340
|
|
|
2338
2341
|
The element may be:
|
|
@@ -2447,7 +2450,8 @@ ${variantsMatch[0]}
|
|
|
2447
2450
|
}
|
|
2448
2451
|
|
|
2449
2452
|
// ========== SIMPLIFIED INSTRUCTIONS ==========
|
|
2450
|
-
|
|
2453
|
+
// Use actualTargetFile which may have been redirected to an imported component
|
|
2454
|
+
const targetPath = actualTargetFile?.path || pageContext.pageFile || "unknown";
|
|
2451
2455
|
|
|
2452
2456
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
2453
2457
|
HOW TO MAKE YOUR EDIT
|
|
@@ -2491,10 +2495,14 @@ CRITICAL: Your "search" string MUST exist in the file. If you can't find the exa
|
|
|
2491
2495
|
validFilePaths.add(comp.path);
|
|
2492
2496
|
}
|
|
2493
2497
|
|
|
2494
|
-
// FIX: Add the recommended file
|
|
2498
|
+
// FIX: Add the recommended file and actual target file to validFilePaths
|
|
2499
|
+
// (recommended was spliced out for display, actual may be different due to redirect)
|
|
2495
2500
|
if (recommendedFileContent) {
|
|
2496
2501
|
validFilePaths.add(recommendedFileContent.path);
|
|
2497
2502
|
}
|
|
2503
|
+
if (actualTargetFile && actualTargetFile.path !== recommendedFileContent?.path) {
|
|
2504
|
+
validFilePaths.add(actualTargetFile.path);
|
|
2505
|
+
}
|
|
2498
2506
|
|
|
2499
2507
|
// Retry loop for handling patch failures
|
|
2500
2508
|
const MAX_RETRIES = 1;
|
|
@@ -2630,7 +2638,8 @@ This is better than generating patches with made-up code.`,
|
|
|
2630
2638
|
|
|
2631
2639
|
// CRITICAL: Warn if LLM is trying to modify a file OTHER than the TARGET COMPONENT
|
|
2632
2640
|
// This usually means the LLM is trying to modify a file it doesn't have full visibility into
|
|
2633
|
-
|
|
2641
|
+
// Use actualTargetFile since it may have been redirected from recommendedFileContent
|
|
2642
|
+
const targetComponentPath = actualTargetFile?.path;
|
|
2634
2643
|
if (targetComponentPath && mod.filePath !== targetComponentPath) {
|
|
2635
2644
|
debugLog("WARNING: LLM trying to modify non-target file", {
|
|
2636
2645
|
targetComponent: targetComponentPath,
|
|
@@ -2056,6 +2056,124 @@ export async function POST(request: Request) {
|
|
|
2056
2056
|
}
|
|
2057
2057
|
}
|
|
2058
2058
|
|
|
2059
|
+
// ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
|
|
2060
|
+
// This determines the ACTUAL target file by checking for text matches in imported components
|
|
2061
|
+
// Must run first so that all subsequent code uses the correct file path
|
|
2062
|
+
let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
|
|
2063
|
+
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2064
|
+
|
|
2065
|
+
if (actualTargetFile && focusedElements && focusedElements.length > 0) {
|
|
2066
|
+
const content = actualTargetFile.content;
|
|
2067
|
+
|
|
2068
|
+
// Search for focused element in the file using multiple strategies
|
|
2069
|
+
// Priority: DOM id > textContent > className patterns
|
|
2070
|
+
for (const el of focusedElements) {
|
|
2071
|
+
elementLocation = findElementLineInFile(content, el);
|
|
2072
|
+
if (elementLocation) {
|
|
2073
|
+
debugLog("Found focused element in file", {
|
|
2074
|
+
matchedBy: elementLocation.matchedBy,
|
|
2075
|
+
lineNumber: elementLocation.lineNumber,
|
|
2076
|
+
confidence: elementLocation.confidence,
|
|
2077
|
+
file: actualTargetFile.path,
|
|
2078
|
+
});
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
2084
|
+
// text strings they contain. The file with the highest score is the TRUE target.
|
|
2085
|
+
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
2086
|
+
const allTextContent = focusedElements
|
|
2087
|
+
.filter(e => e.textContent && e.textContent.length > 5)
|
|
2088
|
+
.map(e => e.textContent!);
|
|
2089
|
+
|
|
2090
|
+
if (allTextContent.length > 0) {
|
|
2091
|
+
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
2092
|
+
currentFile: actualTargetFile.path,
|
|
2093
|
+
currentConfidence: elementLocation.confidence,
|
|
2094
|
+
textCount: allTextContent.length,
|
|
2095
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
2096
|
+
});
|
|
2097
|
+
|
|
2098
|
+
const bestMatch = scoreFilesForTextContent(
|
|
2099
|
+
allTextContent,
|
|
2100
|
+
pageContext.componentSources
|
|
2101
|
+
);
|
|
2102
|
+
|
|
2103
|
+
const currentFileScore = allTextContent.filter(text =>
|
|
2104
|
+
actualTargetFile!.content.includes(text.substring(0, 20))
|
|
2105
|
+
).length;
|
|
2106
|
+
|
|
2107
|
+
debugLog("Text scoring comparison", {
|
|
2108
|
+
currentFile: actualTargetFile.path,
|
|
2109
|
+
currentScore: currentFileScore,
|
|
2110
|
+
bestImport: bestMatch?.path || 'none',
|
|
2111
|
+
bestImportScore: bestMatch?.score || 0
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
2115
|
+
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
2116
|
+
originalFile: actualTargetFile.path,
|
|
2117
|
+
originalScore: currentFileScore,
|
|
2118
|
+
redirectTo: bestMatch.path,
|
|
2119
|
+
redirectScore: bestMatch.score,
|
|
2120
|
+
matchedTexts: bestMatch.matchedTexts
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
actualTargetFile = {
|
|
2124
|
+
path: bestMatch.path,
|
|
2125
|
+
content: bestMatch.content
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
const lines = bestMatch.content.split('\n');
|
|
2129
|
+
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
2130
|
+
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
2131
|
+
|
|
2132
|
+
elementLocation = {
|
|
2133
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
2134
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
2135
|
+
confidence: 'high',
|
|
2136
|
+
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
2143
|
+
if (!elementLocation) {
|
|
2144
|
+
debugLog("Element not in main file, searching imported components...", {
|
|
2145
|
+
mainFile: actualTargetFile.path,
|
|
2146
|
+
importedFilesCount: pageContext.componentSources.length
|
|
2147
|
+
});
|
|
2148
|
+
|
|
2149
|
+
const importedMatch = findElementInImportedFiles(
|
|
2150
|
+
focusedElements[0],
|
|
2151
|
+
pageContext.componentSources
|
|
2152
|
+
);
|
|
2153
|
+
|
|
2154
|
+
if (importedMatch) {
|
|
2155
|
+
debugLog("REDIRECT: Element found in imported component", {
|
|
2156
|
+
originalFile: actualTargetFile.path,
|
|
2157
|
+
redirectTo: importedMatch.path,
|
|
2158
|
+
matchedBy: importedMatch.matchedBy,
|
|
2159
|
+
lineNumber: importedMatch.lineNumber
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
actualTargetFile = {
|
|
2163
|
+
path: importedMatch.path,
|
|
2164
|
+
content: importedMatch.content
|
|
2165
|
+
};
|
|
2166
|
+
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
debugLog("File redirect complete", {
|
|
2172
|
+
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2173
|
+
actualTarget: actualTargetFile?.path || 'none',
|
|
2174
|
+
wasRedirected: actualTargetFile?.path !== recommendedFileContent?.path
|
|
2175
|
+
});
|
|
2176
|
+
|
|
2059
2177
|
// Build text content
|
|
2060
2178
|
let textContent = `VISION MODE EDIT REQUEST
|
|
2061
2179
|
|
|
@@ -2079,7 +2197,8 @@ User Request: "${userPrompt}"
|
|
|
2079
2197
|
|
|
2080
2198
|
// Phase 1: Analyze the screenshot to identify specific problems
|
|
2081
2199
|
// This is a SEPARATE LLM call that only identifies problems
|
|
2082
|
-
|
|
2200
|
+
// Use actualTargetFile since redirect has already happened
|
|
2201
|
+
const fileContentForAnalysis = actualTargetFile?.content || pageContext.pageContent || '';
|
|
2083
2202
|
identifiedProblems = await analyzeDesignProblems(
|
|
2084
2203
|
screenshot,
|
|
2085
2204
|
fileContentForAnalysis,
|
|
@@ -2104,124 +2223,8 @@ User Request: "${userPrompt}"
|
|
|
2104
2223
|
|
|
2105
2224
|
// ========== TARGET COMPONENT ONLY (with line numbers) ==========
|
|
2106
2225
|
// CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
// Search for focused element in the file using multiple strategies
|
|
2111
|
-
// Priority: DOM id > textContent > className patterns
|
|
2112
|
-
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2113
|
-
let actualTargetFile = recommendedFileContent; // May change if we redirect
|
|
2114
|
-
|
|
2115
|
-
if (focusedElements && focusedElements.length > 0) {
|
|
2116
|
-
for (const el of focusedElements) {
|
|
2117
|
-
elementLocation = findElementLineInFile(content, el);
|
|
2118
|
-
if (elementLocation) {
|
|
2119
|
-
debugLog("Found focused element in file", {
|
|
2120
|
-
matchedBy: elementLocation.matchedBy,
|
|
2121
|
-
lineNumber: elementLocation.lineNumber,
|
|
2122
|
-
confidence: elementLocation.confidence,
|
|
2123
|
-
file: recommendedFileContent.path,
|
|
2124
|
-
});
|
|
2125
|
-
break;
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
2130
|
-
// text strings they contain. The file with the highest score is the TRUE target.
|
|
2131
|
-
// This handles parent/child component cases accurately.
|
|
2132
|
-
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
2133
|
-
// Collect ALL text content from focused elements
|
|
2134
|
-
const allTextContent = focusedElements
|
|
2135
|
-
.filter(e => e.textContent && e.textContent.length > 5)
|
|
2136
|
-
.map(e => e.textContent!);
|
|
2137
|
-
|
|
2138
|
-
if (allTextContent.length > 0) {
|
|
2139
|
-
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
2140
|
-
currentFile: recommendedFileContent.path,
|
|
2141
|
-
currentConfidence: elementLocation.confidence,
|
|
2142
|
-
textCount: allTextContent.length,
|
|
2143
|
-
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
2144
|
-
});
|
|
2145
|
-
|
|
2146
|
-
// Score all imported files
|
|
2147
|
-
const bestMatch = scoreFilesForTextContent(
|
|
2148
|
-
allTextContent,
|
|
2149
|
-
pageContext.componentSources
|
|
2150
|
-
);
|
|
2151
|
-
|
|
2152
|
-
// Also score the current file for comparison
|
|
2153
|
-
const currentFileScore = allTextContent.filter(text =>
|
|
2154
|
-
recommendedFileContent.content.includes(text.substring(0, 20))
|
|
2155
|
-
).length;
|
|
2156
|
-
|
|
2157
|
-
debugLog("Text scoring comparison", {
|
|
2158
|
-
currentFile: recommendedFileContent.path,
|
|
2159
|
-
currentScore: currentFileScore,
|
|
2160
|
-
bestImport: bestMatch?.path || 'none',
|
|
2161
|
-
bestImportScore: bestMatch?.score || 0
|
|
2162
|
-
});
|
|
2163
|
-
|
|
2164
|
-
// Redirect only if imported file has MORE matches than current file
|
|
2165
|
-
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
2166
|
-
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
2167
|
-
originalFile: recommendedFileContent.path,
|
|
2168
|
-
originalScore: currentFileScore,
|
|
2169
|
-
redirectTo: bestMatch.path,
|
|
2170
|
-
redirectScore: bestMatch.score,
|
|
2171
|
-
matchedTexts: bestMatch.matchedTexts
|
|
2172
|
-
});
|
|
2173
|
-
|
|
2174
|
-
// Switch target file to where most text content lives
|
|
2175
|
-
actualTargetFile = {
|
|
2176
|
-
path: bestMatch.path,
|
|
2177
|
-
content: bestMatch.content
|
|
2178
|
-
};
|
|
2179
|
-
|
|
2180
|
-
// Find a line with matched text for element location
|
|
2181
|
-
const lines = bestMatch.content.split('\n');
|
|
2182
|
-
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
2183
|
-
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
2184
|
-
|
|
2185
|
-
elementLocation = {
|
|
2186
|
-
lineNumber: bestMatch.firstMatchLine,
|
|
2187
|
-
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
2188
|
-
confidence: 'high',
|
|
2189
|
-
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
2196
|
-
if (!elementLocation) {
|
|
2197
|
-
debugLog("Element not in main file, searching imported components...", {
|
|
2198
|
-
mainFile: recommendedFileContent.path,
|
|
2199
|
-
importedFilesCount: pageContext.componentSources.length
|
|
2200
|
-
});
|
|
2201
|
-
|
|
2202
|
-
const importedMatch = findElementInImportedFiles(
|
|
2203
|
-
focusedElements[0],
|
|
2204
|
-
pageContext.componentSources
|
|
2205
|
-
);
|
|
2206
|
-
|
|
2207
|
-
if (importedMatch) {
|
|
2208
|
-
debugLog("REDIRECT: Element found in imported component", {
|
|
2209
|
-
originalFile: recommendedFileContent.path,
|
|
2210
|
-
redirectTo: importedMatch.path,
|
|
2211
|
-
matchedBy: importedMatch.matchedBy,
|
|
2212
|
-
lineNumber: importedMatch.lineNumber
|
|
2213
|
-
});
|
|
2214
|
-
|
|
2215
|
-
// Switch target file to where element actually is
|
|
2216
|
-
actualTargetFile = {
|
|
2217
|
-
path: importedMatch.path,
|
|
2218
|
-
content: importedMatch.content
|
|
2219
|
-
};
|
|
2220
|
-
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2226
|
+
// Note: Redirect logic already ran above, so actualTargetFile is the correct file
|
|
2227
|
+
if (actualTargetFile) {
|
|
2225
2228
|
// Build focused elements section with precise targeting info
|
|
2226
2229
|
if (focusedElements && focusedElements.length > 0) {
|
|
2227
2230
|
textContent += `FOCUSED ELEMENTS (user clicked on these):\n`;
|
|
@@ -2285,7 +2288,7 @@ ${elementLocation.snippet}
|
|
|
2285
2288
|
// Element NOT found in main file OR any imported components
|
|
2286
2289
|
// BLOCK the LLM from guessing - require empty modifications
|
|
2287
2290
|
debugLog("BLOCK: Could not locate focused element anywhere", {
|
|
2288
|
-
mainFile:
|
|
2291
|
+
mainFile: actualTargetFile.path,
|
|
2289
2292
|
searchedImports: pageContext.componentSources.length,
|
|
2290
2293
|
focusedElements: focusedElements.map(el => ({
|
|
2291
2294
|
name: el.name,
|
|
@@ -2301,7 +2304,7 @@ ${elementLocation.snippet}
|
|
|
2301
2304
|
⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
|
|
2302
2305
|
|
|
2303
2306
|
The user clicked on a specific element, but it could NOT be found in:
|
|
2304
|
-
- ${
|
|
2307
|
+
- ${actualTargetFile.path} (main target file)
|
|
2305
2308
|
- Any of the ${pageContext.componentSources.length} imported component files
|
|
2306
2309
|
|
|
2307
2310
|
The element may be:
|
|
@@ -2343,7 +2346,7 @@ ${linesWithNumbers}
|
|
|
2343
2346
|
path: actualTargetFile.path,
|
|
2344
2347
|
lines: targetContent.split('\n').length,
|
|
2345
2348
|
size: targetContent.length,
|
|
2346
|
-
wasRedirected: actualTargetFile.path !== recommendedFileContent
|
|
2349
|
+
wasRedirected: actualTargetFile.path !== recommendedFileContent?.path
|
|
2347
2350
|
});
|
|
2348
2351
|
} else if (pageContext.pageContent) {
|
|
2349
2352
|
// Fallback: use page file if no recommended file
|
|
@@ -2416,7 +2419,8 @@ ${variantsMatch[0]}
|
|
|
2416
2419
|
}
|
|
2417
2420
|
|
|
2418
2421
|
// ========== SIMPLIFIED INSTRUCTIONS ==========
|
|
2419
|
-
|
|
2422
|
+
// Use actualTargetFile which may have been redirected to an imported component
|
|
2423
|
+
const targetPath = actualTargetFile?.path || pageContext.pageFile || "unknown";
|
|
2420
2424
|
|
|
2421
2425
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
2422
2426
|
HOW TO MAKE YOUR EDIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.101",
|
|
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",
|