sonance-brand-mcp 1.3.100 → 1.3.102

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.
@@ -1519,53 +1519,37 @@ function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
1519
1519
  return ''; // No problems identified, skip design protocol
1520
1520
  }
1521
1521
 
1522
- const problemsJson = JSON.stringify(problems, null, 2);
1522
+ // Create a simple, readable list of problems (no JSON complexity)
1523
+ const problemsList = problems.map(p => `• ${p.description}`).join('\n');
1523
1524
 
1524
1525
  return `
1525
1526
  ═══════════════════════════════════════════════════════════════════════════════
1526
- PHASE 2: TARGETED PATCH GENERATION
1527
+ DESIGN ISSUES TO FIX
1527
1528
  ═══════════════════════════════════════════════════════════════════════════════
1528
1529
 
1529
- A visual analysis identified these specific problems in the UI:
1530
+ These visual issues were identified:
1531
+ ${problemsList}
1530
1532
 
1531
- ${problemsJson}
1533
+ Fix these issues by modifying the code below.
1532
1534
 
1533
- YOUR TASK: Generate patches that fix ONLY these specific problems.
1535
+ ⚠️ CRITICAL - EXACT CODE MATCHING:
1536
+ Your "search" string MUST be copied CHARACTER-FOR-CHARACTER from the file.
1537
+ Use the line numbers on the left to find the exact code.
1534
1538
 
1535
- STRICT RULES:
1536
- 1. Each patch MUST include a "problemId" field referencing one of the problems above
1537
- 2. Do NOT make changes unrelated to the identified problems
1538
- 3. Make the SMALLEST change that fixes each problem
1539
- 4. Do NOT change layout systems (flex→grid) unless absolutely necessary to fix a problem
1540
- 5. Preserve existing structure - adjust properties, don't restructure
1539
+ EXAMPLE:
1540
+ If line 42 shows: 42| <div className="mt-6 flex items-center">
1541
+ Your search must be: <div className="mt-6 flex items-center">
1541
1542
 
1542
- For each problem, find the exact code that causes it and create a targeted fix.
1543
+ DO NOT:
1544
+ - Combine multiple lines into one search string
1545
+ - Add or remove classes/attributes
1546
+ - Invent code that "should" exist
1547
+ - Guess at the structure
1543
1548
 
1544
- OUTPUT FORMAT (note the problemId and rationale fields):
1545
- {
1546
- "modifications": [{
1547
- "filePath": "components/Example.tsx",
1548
- "patches": [
1549
- {
1550
- "problemId": "grouping-1",
1551
- "search": "<Badge className=\"absolute right-4\">",
1552
- "replace": "<Badge className=\"ml-2\">",
1553
- "rationale": "Removes absolute positioning to group badge with label"
1554
- },
1555
- {
1556
- "problemId": "spacing-1",
1557
- "search": "className=\"mt-8\"",
1558
- "replace": "className=\"mt-4\"",
1559
- "rationale": "Reduces excessive vertical gap"
1560
- }
1561
- ]
1562
- }]
1563
- }
1564
-
1565
- VALIDATION:
1566
- - Patches without a valid problemId will be REJECTED
1567
- - Patches that don't trace to an identified problem will be REJECTED
1568
- - Each patch must have search, replace, problemId, and rationale fields
1549
+ DO:
1550
+ - Copy the exact text from the numbered lines
1551
+ - Make small, targeted changes in the "replace" field
1552
+ - Keep patches focused on one change each
1569
1553
  `;
1570
1554
  }
1571
1555
 
@@ -2087,6 +2071,124 @@ export async function POST(request: Request) {
2087
2071
  }
2088
2072
  }
2089
2073
 
2074
+ // ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
2075
+ // This determines the ACTUAL target file by checking for text matches in imported components
2076
+ // Must run first so that all subsequent code uses the correct file path
2077
+ let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
2078
+ let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
2079
+
2080
+ if (actualTargetFile && focusedElements && focusedElements.length > 0) {
2081
+ const content = actualTargetFile.content;
2082
+
2083
+ // Search for focused element in the file using multiple strategies
2084
+ // Priority: DOM id > textContent > className patterns
2085
+ for (const el of focusedElements) {
2086
+ elementLocation = findElementLineInFile(content, el);
2087
+ if (elementLocation) {
2088
+ debugLog("Found focused element in file", {
2089
+ matchedBy: elementLocation.matchedBy,
2090
+ lineNumber: elementLocation.lineNumber,
2091
+ confidence: elementLocation.confidence,
2092
+ file: actualTargetFile.path,
2093
+ });
2094
+ break;
2095
+ }
2096
+ }
2097
+
2098
+ // TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
2099
+ // text strings they contain. The file with the highest score is the TRUE target.
2100
+ if (elementLocation && elementLocation.confidence !== 'high') {
2101
+ const allTextContent = focusedElements
2102
+ .filter(e => e.textContent && e.textContent.length > 5)
2103
+ .map(e => e.textContent!);
2104
+
2105
+ if (allTextContent.length > 0) {
2106
+ debugLog("Medium/low confidence match - scoring imports for all text", {
2107
+ currentFile: actualTargetFile.path,
2108
+ currentConfidence: elementLocation.confidence,
2109
+ textCount: allTextContent.length,
2110
+ texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
2111
+ });
2112
+
2113
+ const bestMatch = scoreFilesForTextContent(
2114
+ allTextContent,
2115
+ pageContext.componentSources
2116
+ );
2117
+
2118
+ const currentFileScore = allTextContent.filter(text =>
2119
+ actualTargetFile!.content.includes(text.substring(0, 20))
2120
+ ).length;
2121
+
2122
+ debugLog("Text scoring comparison", {
2123
+ currentFile: actualTargetFile.path,
2124
+ currentScore: currentFileScore,
2125
+ bestImport: bestMatch?.path || 'none',
2126
+ bestImportScore: bestMatch?.score || 0
2127
+ });
2128
+
2129
+ if (bestMatch && bestMatch.score > currentFileScore) {
2130
+ debugLog("TEXT REDIRECT: Imported file has more text matches", {
2131
+ originalFile: actualTargetFile.path,
2132
+ originalScore: currentFileScore,
2133
+ redirectTo: bestMatch.path,
2134
+ redirectScore: bestMatch.score,
2135
+ matchedTexts: bestMatch.matchedTexts
2136
+ });
2137
+
2138
+ actualTargetFile = {
2139
+ path: bestMatch.path,
2140
+ content: bestMatch.content
2141
+ };
2142
+
2143
+ const lines = bestMatch.content.split('\n');
2144
+ const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
2145
+ const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
2146
+
2147
+ elementLocation = {
2148
+ lineNumber: bestMatch.firstMatchLine,
2149
+ snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
2150
+ confidence: 'high',
2151
+ matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
2152
+ };
2153
+ }
2154
+ }
2155
+ }
2156
+
2157
+ // DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
2158
+ if (!elementLocation) {
2159
+ debugLog("Element not in main file, searching imported components...", {
2160
+ mainFile: actualTargetFile.path,
2161
+ importedFilesCount: pageContext.componentSources.length
2162
+ });
2163
+
2164
+ const importedMatch = findElementInImportedFiles(
2165
+ focusedElements[0],
2166
+ pageContext.componentSources
2167
+ );
2168
+
2169
+ if (importedMatch) {
2170
+ debugLog("REDIRECT: Element found in imported component", {
2171
+ originalFile: actualTargetFile.path,
2172
+ redirectTo: importedMatch.path,
2173
+ matchedBy: importedMatch.matchedBy,
2174
+ lineNumber: importedMatch.lineNumber
2175
+ });
2176
+
2177
+ actualTargetFile = {
2178
+ path: importedMatch.path,
2179
+ content: importedMatch.content
2180
+ };
2181
+ elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
2182
+ }
2183
+ }
2184
+ }
2185
+
2186
+ debugLog("File redirect complete", {
2187
+ originalRecommended: recommendedFileContent?.path || 'none',
2188
+ actualTarget: actualTargetFile?.path || 'none',
2189
+ wasRedirected: actualTargetFile?.path !== recommendedFileContent?.path
2190
+ });
2191
+
2090
2192
  // Build text content
2091
2193
  let textContent = `VISION MODE EDIT REQUEST
2092
2194
 
@@ -2110,7 +2212,8 @@ User Request: "${userPrompt}"
2110
2212
 
2111
2213
  // Phase 1: Analyze the screenshot to identify specific problems
2112
2214
  // This is a SEPARATE LLM call that only identifies problems
2113
- const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
2215
+ // Use actualTargetFile since redirect has already happened
2216
+ const fileContentForAnalysis = actualTargetFile?.content || pageContext.pageContent || '';
2114
2217
  identifiedProblems = await analyzeDesignProblems(
2115
2218
  screenshot,
2116
2219
  fileContentForAnalysis,
@@ -2135,124 +2238,8 @@ User Request: "${userPrompt}"
2135
2238
 
2136
2239
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
2137
2240
  // CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
2138
- if (recommendedFileContent) {
2139
- const content = recommendedFileContent.content;
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
-
2241
+ // Note: Redirect logic already ran above, so actualTargetFile is the correct file
2242
+ if (actualTargetFile) {
2256
2243
  // Build focused elements section with precise targeting info
2257
2244
  if (focusedElements && focusedElements.length > 0) {
2258
2245
  textContent += `FOCUSED ELEMENTS (user clicked on these):\n`;
@@ -2316,7 +2303,7 @@ ${elementLocation.snippet}
2316
2303
  // Element NOT found in main file OR any imported components
2317
2304
  // BLOCK the LLM from guessing - require empty modifications
2318
2305
  debugLog("BLOCK: Could not locate focused element anywhere", {
2319
- mainFile: recommendedFileContent.path,
2306
+ mainFile: actualTargetFile.path,
2320
2307
  searchedImports: pageContext.componentSources.length,
2321
2308
  focusedElements: focusedElements.map(el => ({
2322
2309
  name: el.name,
@@ -2332,7 +2319,7 @@ ${elementLocation.snippet}
2332
2319
  ⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
2333
2320
 
2334
2321
  The user clicked on a specific element, but it could NOT be found in:
2335
- - ${recommendedFileContent.path} (main target file)
2322
+ - ${actualTargetFile.path} (main target file)
2336
2323
  - Any of the ${pageContext.componentSources.length} imported component files
2337
2324
 
2338
2325
  The element may be:
@@ -2447,7 +2434,8 @@ ${variantsMatch[0]}
2447
2434
  }
2448
2435
 
2449
2436
  // ========== SIMPLIFIED INSTRUCTIONS ==========
2450
- const targetPath = recommendedFileContent?.path || pageContext.pageFile || "unknown";
2437
+ // Use actualTargetFile which may have been redirected to an imported component
2438
+ const targetPath = actualTargetFile?.path || pageContext.pageFile || "unknown";
2451
2439
 
2452
2440
  textContent += `═══════════════════════════════════════════════════════════════════════════════
2453
2441
  HOW TO MAKE YOUR EDIT
@@ -2491,10 +2479,14 @@ CRITICAL: Your "search" string MUST exist in the file. If you can't find the exa
2491
2479
  validFilePaths.add(comp.path);
2492
2480
  }
2493
2481
 
2494
- // FIX: Add the recommended file to validFilePaths (it was spliced out for display purposes)
2482
+ // FIX: Add the recommended file and actual target file to validFilePaths
2483
+ // (recommended was spliced out for display, actual may be different due to redirect)
2495
2484
  if (recommendedFileContent) {
2496
2485
  validFilePaths.add(recommendedFileContent.path);
2497
2486
  }
2487
+ if (actualTargetFile && actualTargetFile.path !== recommendedFileContent?.path) {
2488
+ validFilePaths.add(actualTargetFile.path);
2489
+ }
2498
2490
 
2499
2491
  // Retry loop for handling patch failures
2500
2492
  const MAX_RETRIES = 1;
@@ -2630,7 +2622,8 @@ This is better than generating patches with made-up code.`,
2630
2622
 
2631
2623
  // CRITICAL: Warn if LLM is trying to modify a file OTHER than the TARGET COMPONENT
2632
2624
  // This usually means the LLM is trying to modify a file it doesn't have full visibility into
2633
- const targetComponentPath = recommendedFileContent?.path;
2625
+ // Use actualTargetFile since it may have been redirected from recommendedFileContent
2626
+ const targetComponentPath = actualTargetFile?.path;
2634
2627
  if (targetComponentPath && mod.filePath !== targetComponentPath) {
2635
2628
  debugLog("WARNING: LLM trying to modify non-target file", {
2636
2629
  targetComponent: targetComponentPath,
@@ -2655,36 +2648,17 @@ This is better than generating patches with made-up code.`,
2655
2648
  // New patch-based approach
2656
2649
  console.log(`[Apply-First] Applying ${mod.patches.length} patches to ${mod.filePath}`);
2657
2650
 
2658
- // DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
2659
- let patchesToApply = mod.patches;
2651
+ // Use patches directly - simplified approach focuses on exact code matching
2652
+ // rather than problemId validation (which was causing LLM to prioritize
2653
+ // problem-solving over exact code copying)
2654
+ const patchesToApply = mod.patches;
2655
+
2660
2656
  if (isDesignRequest && identifiedProblems.length > 0) {
2661
- const validationResult = validateDesignPatches(mod.patches, identifiedProblems);
2662
-
2663
- if (validationResult.rejected.length > 0) {
2664
- debugLog("Design patch validation rejected some patches", {
2665
- filePath: mod.filePath,
2666
- validCount: validationResult.valid.length,
2667
- rejectedCount: validationResult.rejected.length,
2668
- rejectedReasons: validationResult.rejected.map(r => r.reason)
2669
- });
2670
- console.warn(`[Apply-First] ${validationResult.rejected.length} patches rejected for missing/invalid problemId`);
2671
- }
2672
-
2673
- // Only use validated patches
2674
- if (validationResult.valid.length === 0) {
2675
- patchErrors.push(`${mod.filePath}: All patches were rejected - they don't reference identified problems`);
2676
- continue;
2677
- }
2678
-
2679
- // Log which problems are being fixed
2680
- const problemsBeingFixed = [...new Set(validationResult.valid.map(p => p.problemId))];
2681
- debugLog("Design patches validated", {
2657
+ debugLog("Design patches received", {
2682
2658
  filePath: mod.filePath,
2683
- problemsBeingFixed,
2684
- patchCount: validationResult.valid.length
2659
+ patchCount: patchesToApply.length,
2660
+ problemsIdentified: identifiedProblems.length
2685
2661
  });
2686
-
2687
- patchesToApply = validationResult.valid;
2688
2662
  }
2689
2663
 
2690
2664
  // PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
@@ -1515,53 +1515,37 @@ function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
1515
1515
  return ''; // No problems identified, skip design protocol
1516
1516
  }
1517
1517
 
1518
- const problemsJson = JSON.stringify(problems, null, 2);
1518
+ // Create a simple, readable list of problems (no JSON complexity)
1519
+ const problemsList = problems.map(p => `• ${p.description}`).join('\n');
1519
1520
 
1520
1521
  return `
1521
1522
  ═══════════════════════════════════════════════════════════════════════════════
1522
- PHASE 2: TARGETED PATCH GENERATION
1523
+ DESIGN ISSUES TO FIX
1523
1524
  ═══════════════════════════════════════════════════════════════════════════════
1524
1525
 
1525
- A visual analysis identified these specific problems in the UI:
1526
+ These visual issues were identified:
1527
+ ${problemsList}
1526
1528
 
1527
- ${problemsJson}
1529
+ Fix these issues by modifying the code below.
1528
1530
 
1529
- YOUR TASK: Generate patches that fix ONLY these specific problems.
1531
+ ⚠️ CRITICAL - EXACT CODE MATCHING:
1532
+ Your "search" string MUST be copied CHARACTER-FOR-CHARACTER from the file.
1533
+ Use the line numbers on the left to find the exact code.
1530
1534
 
1531
- STRICT RULES:
1532
- 1. Each patch MUST include a "problemId" field referencing one of the problems above
1533
- 2. Do NOT make changes unrelated to the identified problems
1534
- 3. Make the SMALLEST change that fixes each problem
1535
- 4. Do NOT change layout systems (flex→grid) unless absolutely necessary to fix a problem
1536
- 5. Preserve existing structure - adjust properties, don't restructure
1535
+ EXAMPLE:
1536
+ If line 42 shows: 42| <div className="mt-6 flex items-center">
1537
+ Your search must be: <div className="mt-6 flex items-center">
1537
1538
 
1538
- For each problem, find the exact code that causes it and create a targeted fix.
1539
+ DO NOT:
1540
+ - Combine multiple lines into one search string
1541
+ - Add or remove classes/attributes
1542
+ - Invent code that "should" exist
1543
+ - Guess at the structure
1539
1544
 
1540
- OUTPUT FORMAT (note the problemId and rationale fields):
1541
- {
1542
- "modifications": [{
1543
- "filePath": "components/Example.tsx",
1544
- "patches": [
1545
- {
1546
- "problemId": "grouping-1",
1547
- "search": "<Badge className=\"absolute right-4\">",
1548
- "replace": "<Badge className=\"ml-2\">",
1549
- "rationale": "Removes absolute positioning to group badge with label"
1550
- },
1551
- {
1552
- "problemId": "spacing-1",
1553
- "search": "className=\"mt-8\"",
1554
- "replace": "className=\"mt-4\"",
1555
- "rationale": "Reduces excessive vertical gap"
1556
- }
1557
- ]
1558
- }]
1559
- }
1560
-
1561
- VALIDATION:
1562
- - Patches without a valid problemId will be REJECTED
1563
- - Patches that don't trace to an identified problem will be REJECTED
1564
- - Each patch must have search, replace, problemId, and rationale fields
1545
+ DO:
1546
+ - Copy the exact text from the numbered lines
1547
+ - Make small, targeted changes in the "replace" field
1548
+ - Keep patches focused on one change each
1565
1549
  `;
1566
1550
  }
1567
1551
 
@@ -2056,6 +2040,124 @@ export async function POST(request: Request) {
2056
2040
  }
2057
2041
  }
2058
2042
 
2043
+ // ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
2044
+ // This determines the ACTUAL target file by checking for text matches in imported components
2045
+ // Must run first so that all subsequent code uses the correct file path
2046
+ let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
2047
+ let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
2048
+
2049
+ if (actualTargetFile && focusedElements && focusedElements.length > 0) {
2050
+ const content = actualTargetFile.content;
2051
+
2052
+ // Search for focused element in the file using multiple strategies
2053
+ // Priority: DOM id > textContent > className patterns
2054
+ for (const el of focusedElements) {
2055
+ elementLocation = findElementLineInFile(content, el);
2056
+ if (elementLocation) {
2057
+ debugLog("Found focused element in file", {
2058
+ matchedBy: elementLocation.matchedBy,
2059
+ lineNumber: elementLocation.lineNumber,
2060
+ confidence: elementLocation.confidence,
2061
+ file: actualTargetFile.path,
2062
+ });
2063
+ break;
2064
+ }
2065
+ }
2066
+
2067
+ // TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
2068
+ // text strings they contain. The file with the highest score is the TRUE target.
2069
+ if (elementLocation && elementLocation.confidence !== 'high') {
2070
+ const allTextContent = focusedElements
2071
+ .filter(e => e.textContent && e.textContent.length > 5)
2072
+ .map(e => e.textContent!);
2073
+
2074
+ if (allTextContent.length > 0) {
2075
+ debugLog("Medium/low confidence match - scoring imports for all text", {
2076
+ currentFile: actualTargetFile.path,
2077
+ currentConfidence: elementLocation.confidence,
2078
+ textCount: allTextContent.length,
2079
+ texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
2080
+ });
2081
+
2082
+ const bestMatch = scoreFilesForTextContent(
2083
+ allTextContent,
2084
+ pageContext.componentSources
2085
+ );
2086
+
2087
+ const currentFileScore = allTextContent.filter(text =>
2088
+ actualTargetFile!.content.includes(text.substring(0, 20))
2089
+ ).length;
2090
+
2091
+ debugLog("Text scoring comparison", {
2092
+ currentFile: actualTargetFile.path,
2093
+ currentScore: currentFileScore,
2094
+ bestImport: bestMatch?.path || 'none',
2095
+ bestImportScore: bestMatch?.score || 0
2096
+ });
2097
+
2098
+ if (bestMatch && bestMatch.score > currentFileScore) {
2099
+ debugLog("TEXT REDIRECT: Imported file has more text matches", {
2100
+ originalFile: actualTargetFile.path,
2101
+ originalScore: currentFileScore,
2102
+ redirectTo: bestMatch.path,
2103
+ redirectScore: bestMatch.score,
2104
+ matchedTexts: bestMatch.matchedTexts
2105
+ });
2106
+
2107
+ actualTargetFile = {
2108
+ path: bestMatch.path,
2109
+ content: bestMatch.content
2110
+ };
2111
+
2112
+ const lines = bestMatch.content.split('\n');
2113
+ const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
2114
+ const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
2115
+
2116
+ elementLocation = {
2117
+ lineNumber: bestMatch.firstMatchLine,
2118
+ snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
2119
+ confidence: 'high',
2120
+ matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
2121
+ };
2122
+ }
2123
+ }
2124
+ }
2125
+
2126
+ // DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
2127
+ if (!elementLocation) {
2128
+ debugLog("Element not in main file, searching imported components...", {
2129
+ mainFile: actualTargetFile.path,
2130
+ importedFilesCount: pageContext.componentSources.length
2131
+ });
2132
+
2133
+ const importedMatch = findElementInImportedFiles(
2134
+ focusedElements[0],
2135
+ pageContext.componentSources
2136
+ );
2137
+
2138
+ if (importedMatch) {
2139
+ debugLog("REDIRECT: Element found in imported component", {
2140
+ originalFile: actualTargetFile.path,
2141
+ redirectTo: importedMatch.path,
2142
+ matchedBy: importedMatch.matchedBy,
2143
+ lineNumber: importedMatch.lineNumber
2144
+ });
2145
+
2146
+ actualTargetFile = {
2147
+ path: importedMatch.path,
2148
+ content: importedMatch.content
2149
+ };
2150
+ elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
2151
+ }
2152
+ }
2153
+ }
2154
+
2155
+ debugLog("File redirect complete", {
2156
+ originalRecommended: recommendedFileContent?.path || 'none',
2157
+ actualTarget: actualTargetFile?.path || 'none',
2158
+ wasRedirected: actualTargetFile?.path !== recommendedFileContent?.path
2159
+ });
2160
+
2059
2161
  // Build text content
2060
2162
  let textContent = `VISION MODE EDIT REQUEST
2061
2163
 
@@ -2079,7 +2181,8 @@ User Request: "${userPrompt}"
2079
2181
 
2080
2182
  // Phase 1: Analyze the screenshot to identify specific problems
2081
2183
  // This is a SEPARATE LLM call that only identifies problems
2082
- const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
2184
+ // Use actualTargetFile since redirect has already happened
2185
+ const fileContentForAnalysis = actualTargetFile?.content || pageContext.pageContent || '';
2083
2186
  identifiedProblems = await analyzeDesignProblems(
2084
2187
  screenshot,
2085
2188
  fileContentForAnalysis,
@@ -2104,124 +2207,8 @@ User Request: "${userPrompt}"
2104
2207
 
2105
2208
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
2106
2209
  // CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
2107
- if (recommendedFileContent) {
2108
- const content = recommendedFileContent.content;
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
-
2210
+ // Note: Redirect logic already ran above, so actualTargetFile is the correct file
2211
+ if (actualTargetFile) {
2225
2212
  // Build focused elements section with precise targeting info
2226
2213
  if (focusedElements && focusedElements.length > 0) {
2227
2214
  textContent += `FOCUSED ELEMENTS (user clicked on these):\n`;
@@ -2285,7 +2272,7 @@ ${elementLocation.snippet}
2285
2272
  // Element NOT found in main file OR any imported components
2286
2273
  // BLOCK the LLM from guessing - require empty modifications
2287
2274
  debugLog("BLOCK: Could not locate focused element anywhere", {
2288
- mainFile: recommendedFileContent.path,
2275
+ mainFile: actualTargetFile.path,
2289
2276
  searchedImports: pageContext.componentSources.length,
2290
2277
  focusedElements: focusedElements.map(el => ({
2291
2278
  name: el.name,
@@ -2301,7 +2288,7 @@ ${elementLocation.snippet}
2301
2288
  ⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
2302
2289
 
2303
2290
  The user clicked on a specific element, but it could NOT be found in:
2304
- - ${recommendedFileContent.path} (main target file)
2291
+ - ${actualTargetFile.path} (main target file)
2305
2292
  - Any of the ${pageContext.componentSources.length} imported component files
2306
2293
 
2307
2294
  The element may be:
@@ -2343,7 +2330,7 @@ ${linesWithNumbers}
2343
2330
  path: actualTargetFile.path,
2344
2331
  lines: targetContent.split('\n').length,
2345
2332
  size: targetContent.length,
2346
- wasRedirected: actualTargetFile.path !== recommendedFileContent.path
2333
+ wasRedirected: actualTargetFile.path !== recommendedFileContent?.path
2347
2334
  });
2348
2335
  } else if (pageContext.pageContent) {
2349
2336
  // Fallback: use page file if no recommended file
@@ -2416,7 +2403,8 @@ ${variantsMatch[0]}
2416
2403
  }
2417
2404
 
2418
2405
  // ========== SIMPLIFIED INSTRUCTIONS ==========
2419
- const targetPath = recommendedFileContent?.path || pageContext.pageFile || "unknown";
2406
+ // Use actualTargetFile which may have been redirected to an imported component
2407
+ const targetPath = actualTargetFile?.path || pageContext.pageFile || "unknown";
2420
2408
 
2421
2409
  textContent += `═══════════════════════════════════════════════════════════════════════════════
2422
2410
  HOW TO MAKE YOUR EDIT
@@ -2628,41 +2616,17 @@ This is better than generating patches with made-up code.`,
2628
2616
  // New patch-based approach
2629
2617
  console.log(`[Vision Mode] Applying ${mod.patches.length} patches to ${mod.filePath}`);
2630
2618
 
2631
- // DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
2632
- let patchesToApply = mod.patches;
2619
+ // Use patches directly - simplified approach focuses on exact code matching
2620
+ // rather than problemId validation (which was causing LLM to prioritize
2621
+ // problem-solving over exact code copying)
2622
+ const patchesToApply = mod.patches;
2623
+
2633
2624
  if (isDesignRequest && identifiedProblems.length > 0) {
2634
- const validationResult = validateDesignPatches(mod.patches, identifiedProblems);
2635
-
2636
- if (validationResult.rejected.length > 0) {
2637
- debugLog("Design patch validation rejected some patches", {
2638
- filePath: mod.filePath,
2639
- validCount: validationResult.valid.length,
2640
- rejectedCount: validationResult.rejected.length,
2641
- rejectedReasons: validationResult.rejected.map(r => r.reason)
2642
- });
2643
- console.warn(`[Vision Mode] ${validationResult.rejected.length} patches rejected for missing/invalid problemId`);
2644
- }
2645
-
2646
- // Only use validated patches
2647
- if (validationResult.valid.length === 0) {
2648
- patchErrors.push(`${mod.filePath}: All patches were rejected - they don't reference identified problems`);
2649
- continue;
2650
- }
2651
-
2652
- // Log which problems are being fixed
2653
- const problemsBeingFixed = [...new Set(validationResult.valid.map(p => p.problemId))];
2654
- debugLog("Design patches validated", {
2625
+ debugLog("Design patches received", {
2655
2626
  filePath: mod.filePath,
2656
- problemsBeingFixed,
2657
- patchCount: validationResult.valid.length
2627
+ patchCount: patchesToApply.length,
2628
+ problemsIdentified: identifiedProblems.length
2658
2629
  });
2659
-
2660
- // Map validated patches to Patch interface (rationale -> explanation)
2661
- patchesToApply = validationResult.valid.map(p => ({
2662
- search: p.search,
2663
- replace: p.replace,
2664
- explanation: `[${p.problemId}] ${p.rationale}`
2665
- }));
2666
2630
  }
2667
2631
 
2668
2632
  // PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.100",
3
+ "version": "1.3.102",
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",