sonance-brand-mcp 1.3.96 → 1.3.98

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.
@@ -524,14 +524,23 @@ function findElementLineInFile(
524
524
  * Example: "What's the essence of this process?" might be in EssenceTab.tsx
525
525
  * but the parent QuickAddProcessModal.tsx was selected by Phase 2a.
526
526
  */
527
- function findTextInImportedFiles(
528
- textContent: string,
527
+ /**
528
+ * Score imported files by how many discovered text strings they contain.
529
+ * This finds the TRUE file by matching ALL visible text, not just one substring.
530
+ *
531
+ * Example: If annotation discovers ["What's the essence?", "Essence Quality", "Show Example"]
532
+ * and EssenceTab.tsx contains 2 of them while QuickAddProcessModal.tsx contains 1,
533
+ * EssenceTab.tsx wins with the higher score.
534
+ */
535
+ function scoreFilesForTextContent(
536
+ textContents: string[],
529
537
  importedFiles: { path: string; content: string }[]
530
- ): { path: string; lineNumber: number; snippet: string; content: string } | null {
531
- if (!textContent || textContent.length < 5) return null;
538
+ ): { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number } | null {
539
+ if (!textContents || textContents.length === 0) return null;
532
540
 
533
- debugLog("Searching imported files for text content", {
534
- textContent: textContent.substring(0, 40),
541
+ debugLog("Scoring imported files for text content", {
542
+ textCount: textContents.length,
543
+ texts: textContents.slice(0, 5).map(t => t.substring(0, 30)),
535
544
  filesCount: importedFiles.length
536
545
  });
537
546
 
@@ -540,35 +549,63 @@ function findTextInImportedFiles(
540
549
  f.path.includes('components/') || f.path.includes('/ui/')
541
550
  );
542
551
 
543
- // Use first 20 chars for matching (handles dynamic suffixes)
544
- const searchText = textContent.substring(0, 20);
552
+ const results: { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number }[] = [];
545
553
 
546
554
  for (const file of componentFiles) {
547
- const lines = file.content.split('\n');
548
- for (let i = 0; i < lines.length; i++) {
549
- const line = lines[i];
550
- // Look for the text in JSX patterns: >Text<, "Text", 'Text', `Text`
551
- if (line.includes(`>${searchText}`) ||
552
- line.includes(`"${searchText}`) ||
553
- line.includes(`'${searchText}`) ||
554
- line.includes(`\`${searchText}`)) {
555
- debugLog("Found text in imported file", {
556
- path: file.path,
557
- lineNumber: i + 1,
558
- matchedText: searchText
559
- });
560
- return {
561
- path: file.path,
562
- lineNumber: i + 1,
563
- snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
564
- content: file.content
565
- };
555
+ let score = 0;
556
+ const matchedTexts: string[] = [];
557
+ let firstMatchLine = 0;
558
+
559
+ for (const text of textContents) {
560
+ if (!text || text.length < 5) continue;
561
+
562
+ // Use first 20 chars for matching (handles dynamic suffixes)
563
+ const searchText = text.substring(0, 20);
564
+
565
+ // Check if this text exists in the file
566
+ if (file.content.includes(searchText)) {
567
+ score++;
568
+ matchedTexts.push(text.substring(0, 30) + (text.length > 30 ? '...' : ''));
569
+
570
+ // Find line number for first match (for element location)
571
+ if (firstMatchLine === 0) {
572
+ const lines = file.content.split('\n');
573
+ for (let i = 0; i < lines.length; i++) {
574
+ if (lines[i].includes(searchText)) {
575
+ firstMatchLine = i + 1;
576
+ break;
577
+ }
578
+ }
579
+ }
566
580
  }
567
581
  }
582
+
583
+ if (score > 0) {
584
+ results.push({
585
+ path: file.path,
586
+ content: file.content,
587
+ score,
588
+ matchedTexts,
589
+ firstMatchLine
590
+ });
591
+ }
568
592
  }
569
593
 
570
- debugLog("Text not found in any imported component files");
571
- return null;
594
+ // Sort by score descending (highest match count wins)
595
+ results.sort((a, b) => b.score - a.score);
596
+
597
+ if (results.length > 0) {
598
+ debugLog("Text scoring results", {
599
+ topFile: results[0].path,
600
+ topScore: results[0].score,
601
+ topMatches: results[0].matchedTexts,
602
+ allResults: results.slice(0, 3).map(r => ({ path: r.path, score: r.score }))
603
+ });
604
+ } else {
605
+ debugLog("No text matches found in any imported component files");
606
+ }
607
+
608
+ return results.length > 0 ? results[0] : null;
572
609
  }
573
610
 
574
611
  /**
@@ -1304,6 +1341,85 @@ Output format:
1304
1341
 
1305
1342
  The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
1306
1343
 
1344
+ /**
1345
+ * DESIGN REASONING PROMPT
1346
+ * Forces the LLM through a structured analysis before making design changes.
1347
+ * This prevents over-engineering and ensures thoughtful, targeted modifications.
1348
+ */
1349
+ const DESIGN_REASONING_PROMPT = `
1350
+ ═══════════════════════════════════════════════════════════════════════════════
1351
+ DESIGN REASONING PROTOCOL (Follow BEFORE generating patches)
1352
+ ═══════════════════════════════════════════════════════════════════════════════
1353
+
1354
+ STEP 1 - VISUAL ANALYSIS:
1355
+ Before making any changes, analyze the current UI in the screenshot:
1356
+ • What type of component is this? (form wizard, modal, card, data table, sidebar, etc.)
1357
+ • What is currently working well that should be PRESERVED?
1358
+ • What specific visual issues exist? (inconsistent spacing, unclear hierarchy, alignment problems, clutter)
1359
+
1360
+ STEP 2 - MINIMAL CHANGE PLAN:
1361
+ Identify the MINIMUM changes needed to address the user's request:
1362
+ • What 1-3 targeted fixes would solve the issue?
1363
+ • What should you explicitly NOT change?
1364
+ • Will these changes break existing functionality or visual balance?
1365
+
1366
+ STEP 3 - GUARDRAILS CHECK:
1367
+ Before implementing, verify you are following these rules:
1368
+ • Am I preserving the existing layout structure? (flex stays flex, grid stays grid)
1369
+ • Am I keeping the existing color palette unless specifically asked to change it?
1370
+ • Am I making incremental improvements, NOT a complete restructure?
1371
+ • Am I keeping the same visual hierarchy and element positions?
1372
+
1373
+ STEP 4 - IMPLEMENT:
1374
+ Only now generate patches for the targeted changes from Step 2.
1375
+ Each patch should be small and focused - prefer multiple small patches over one large rewrite.
1376
+ `;
1377
+
1378
+ /**
1379
+ * DESIGN GUARDRAILS
1380
+ * Explicit constraints to prevent the LLM from over-engineering design changes.
1381
+ */
1382
+ const DESIGN_GUARDRAILS = `
1383
+ ═══════════════════════════════════════════════════════════════════════════════
1384
+ DESIGN GUARDRAILS (MUST FOLLOW)
1385
+ ═══════════════════════════════════════════════════════════════════════════════
1386
+
1387
+ ❌ DO NOT:
1388
+ • Convert layout systems (do NOT change flex to grid or vice versa)
1389
+ • Restructure component hierarchy unless explicitly asked
1390
+ • Change color schemes unless specifically requested
1391
+ • Move elements to different positions unless asked
1392
+ • Rewrite entire sections - make targeted changes only
1393
+ • Add new wrapper elements or change semantic structure
1394
+
1395
+ ✓ DO:
1396
+ • Focus on spacing, padding, and margins for "cleaner" requests
1397
+ • Improve typography (font size, weight, line-height) for readability
1398
+ • Fix alignment issues within the existing structure
1399
+ • Adjust visual hierarchy through subtle styling, not restructuring
1400
+ • Make the smallest change that addresses the user's request
1401
+ • Preserve what is already working well
1402
+ `;
1403
+
1404
+ /**
1405
+ * Detect if a user request is design-heavy and needs the reasoning protocol.
1406
+ * Simple requests like "change button color to blue" don't need it.
1407
+ */
1408
+ function isDesignHeavyRequest(userPrompt: string): boolean {
1409
+ const designKeywords = [
1410
+ 'redesign', 'design', 'layout', 'improve', 'better', 'modernize', 'modern',
1411
+ 'cleaner', 'clean', 'messy', 'cluttered', 'update look', 'looks bad',
1412
+ 'ugly', 'prettier', 'beautiful', 'professional', 'polish', 'refine',
1413
+ 'restructure', 'reorganize', 'rearrange', 'simplify', 'streamline',
1414
+ 'compact', 'spacious', 'breathing room', 'visual', 'aesthetic',
1415
+ 'ux', 'user experience', 'ui', 'user interface', 'make it look',
1416
+ 'looks weird', 'not right', 'fix the look', 'appearance'
1417
+ ];
1418
+
1419
+ const lowerPrompt = userPrompt.toLowerCase();
1420
+ return designKeywords.some(keyword => lowerPrompt.includes(keyword));
1421
+ }
1422
+
1307
1423
  export async function POST(request: Request) {
1308
1424
  // Only allow in development
1309
1425
  if (process.env.NODE_ENV !== "development") {
@@ -1711,6 +1827,23 @@ User Request: "${userPrompt}"
1711
1827
 
1712
1828
  `;
1713
1829
 
1830
+ // ========== DESIGN REASONING PROTOCOL ==========
1831
+ // For design-heavy requests, add structured reasoning and guardrails
1832
+ // This prevents over-engineering and ensures thoughtful changes
1833
+ const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
1834
+
1835
+ if (isDesignRequest) {
1836
+ debugLog("Design-heavy request detected, adding reasoning protocol", {
1837
+ prompt: userPrompt?.substring(0, 50),
1838
+ triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
1839
+ .filter(k => userPrompt?.toLowerCase().includes(k))
1840
+ });
1841
+
1842
+ textContent += DESIGN_REASONING_PROMPT;
1843
+ textContent += DESIGN_GUARDRAILS;
1844
+ textContent += '\n';
1845
+ }
1846
+
1714
1847
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
1715
1848
  // CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
1716
1849
  if (recommendedFileContent) {
@@ -1735,43 +1868,67 @@ User Request: "${userPrompt}"
1735
1868
  }
1736
1869
  }
1737
1870
 
1738
- // TEXT-FIRST REDIRECT: Before accepting medium/low confidence match,
1739
- // search imported files for the actual TEXT CONTENT.
1740
- // This handles cases where parent component is selected but text lives in child.
1871
+ // TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
1872
+ // text strings they contain. The file with the highest score is the TRUE target.
1873
+ // This handles parent/child component cases accurately.
1741
1874
  if (elementLocation && elementLocation.confidence !== 'high') {
1742
- // Find the first focused element with meaningful text content
1743
- const textToFind = focusedElements.find(e => e.textContent && e.textContent.length > 5)?.textContent;
1875
+ // Collect ALL text content from focused elements
1876
+ const allTextContent = focusedElements
1877
+ .filter(e => e.textContent && e.textContent.length > 5)
1878
+ .map(e => e.textContent!);
1744
1879
 
1745
- if (textToFind) {
1746
- debugLog("Medium/low confidence match - checking imports for text", {
1880
+ if (allTextContent.length > 0) {
1881
+ debugLog("Medium/low confidence match - scoring imports for all text", {
1747
1882
  currentFile: recommendedFileContent.path,
1748
1883
  currentConfidence: elementLocation.confidence,
1749
- searchingFor: textToFind.substring(0, 40)
1884
+ textCount: allTextContent.length,
1885
+ texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
1750
1886
  });
1751
1887
 
1752
- const textMatch = findTextInImportedFiles(
1753
- textToFind,
1888
+ // Score all imported files
1889
+ const bestMatch = scoreFilesForTextContent(
1890
+ allTextContent,
1754
1891
  pageContext.componentSources
1755
1892
  );
1756
1893
 
1757
- if (textMatch) {
1758
- debugLog("TEXT REDIRECT: Found text in imported component", {
1894
+ // Also score the current file for comparison
1895
+ const currentFileScore = allTextContent.filter(text =>
1896
+ recommendedFileContent.content.includes(text.substring(0, 20))
1897
+ ).length;
1898
+
1899
+ debugLog("Text scoring comparison", {
1900
+ currentFile: recommendedFileContent.path,
1901
+ currentScore: currentFileScore,
1902
+ bestImport: bestMatch?.path || 'none',
1903
+ bestImportScore: bestMatch?.score || 0
1904
+ });
1905
+
1906
+ // Redirect only if imported file has MORE matches than current file
1907
+ if (bestMatch && bestMatch.score > currentFileScore) {
1908
+ debugLog("TEXT REDIRECT: Imported file has more text matches", {
1759
1909
  originalFile: recommendedFileContent.path,
1760
- originalConfidence: elementLocation.confidence,
1761
- redirectTo: textMatch.path,
1762
- lineNumber: textMatch.lineNumber
1910
+ originalScore: currentFileScore,
1911
+ redirectTo: bestMatch.path,
1912
+ redirectScore: bestMatch.score,
1913
+ matchedTexts: bestMatch.matchedTexts
1763
1914
  });
1764
1915
 
1765
- // Switch target file to where the text actually is
1916
+ // Switch target file to where most text content lives
1766
1917
  actualTargetFile = {
1767
- path: textMatch.path,
1768
- content: textMatch.content
1918
+ path: bestMatch.path,
1919
+ content: bestMatch.content
1769
1920
  };
1921
+
1922
+ // Find a line with matched text for element location
1923
+ const lines = bestMatch.content.split('\n');
1924
+ const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
1925
+ const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
1926
+
1770
1927
  elementLocation = {
1771
- lineNumber: textMatch.lineNumber,
1772
- snippet: textMatch.snippet,
1928
+ lineNumber: bestMatch.firstMatchLine,
1929
+ snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
1773
1930
  confidence: 'high',
1774
- matchedBy: `textContent in imported file "${textToFind.substring(0, 30)}..."`
1931
+ matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
1775
1932
  };
1776
1933
  }
1777
1934
  }
@@ -520,14 +520,23 @@ function findElementLineInFile(
520
520
  * Example: "What's the essence of this process?" might be in EssenceTab.tsx
521
521
  * but the parent QuickAddProcessModal.tsx was selected by Phase 2a.
522
522
  */
523
- function findTextInImportedFiles(
524
- textContent: string,
523
+ /**
524
+ * Score imported files by how many discovered text strings they contain.
525
+ * This finds the TRUE file by matching ALL visible text, not just one substring.
526
+ *
527
+ * Example: If annotation discovers ["What's the essence?", "Essence Quality", "Show Example"]
528
+ * and EssenceTab.tsx contains 2 of them while QuickAddProcessModal.tsx contains 1,
529
+ * EssenceTab.tsx wins with the higher score.
530
+ */
531
+ function scoreFilesForTextContent(
532
+ textContents: string[],
525
533
  importedFiles: { path: string; content: string }[]
526
- ): { path: string; lineNumber: number; snippet: string; content: string } | null {
527
- if (!textContent || textContent.length < 5) return null;
534
+ ): { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number } | null {
535
+ if (!textContents || textContents.length === 0) return null;
528
536
 
529
- debugLog("Searching imported files for text content", {
530
- textContent: textContent.substring(0, 40),
537
+ debugLog("Scoring imported files for text content", {
538
+ textCount: textContents.length,
539
+ texts: textContents.slice(0, 5).map(t => t.substring(0, 30)),
531
540
  filesCount: importedFiles.length
532
541
  });
533
542
 
@@ -536,35 +545,63 @@ function findTextInImportedFiles(
536
545
  f.path.includes('components/') || f.path.includes('/ui/')
537
546
  );
538
547
 
539
- // Use first 20 chars for matching (handles dynamic suffixes)
540
- const searchText = textContent.substring(0, 20);
548
+ const results: { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number }[] = [];
541
549
 
542
550
  for (const file of componentFiles) {
543
- const lines = file.content.split('\n');
544
- for (let i = 0; i < lines.length; i++) {
545
- const line = lines[i];
546
- // Look for the text in JSX patterns: >Text<, "Text", 'Text', `Text`
547
- if (line.includes(`>${searchText}`) ||
548
- line.includes(`"${searchText}`) ||
549
- line.includes(`'${searchText}`) ||
550
- line.includes(`\`${searchText}`)) {
551
- debugLog("Found text in imported file", {
552
- path: file.path,
553
- lineNumber: i + 1,
554
- matchedText: searchText
555
- });
556
- return {
557
- path: file.path,
558
- lineNumber: i + 1,
559
- snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
560
- content: file.content
561
- };
551
+ let score = 0;
552
+ const matchedTexts: string[] = [];
553
+ let firstMatchLine = 0;
554
+
555
+ for (const text of textContents) {
556
+ if (!text || text.length < 5) continue;
557
+
558
+ // Use first 20 chars for matching (handles dynamic suffixes)
559
+ const searchText = text.substring(0, 20);
560
+
561
+ // Check if this text exists in the file
562
+ if (file.content.includes(searchText)) {
563
+ score++;
564
+ matchedTexts.push(text.substring(0, 30) + (text.length > 30 ? '...' : ''));
565
+
566
+ // Find line number for first match (for element location)
567
+ if (firstMatchLine === 0) {
568
+ const lines = file.content.split('\n');
569
+ for (let i = 0; i < lines.length; i++) {
570
+ if (lines[i].includes(searchText)) {
571
+ firstMatchLine = i + 1;
572
+ break;
573
+ }
574
+ }
575
+ }
562
576
  }
563
577
  }
578
+
579
+ if (score > 0) {
580
+ results.push({
581
+ path: file.path,
582
+ content: file.content,
583
+ score,
584
+ matchedTexts,
585
+ firstMatchLine
586
+ });
587
+ }
564
588
  }
565
589
 
566
- debugLog("Text not found in any imported component files");
567
- return null;
590
+ // Sort by score descending (highest match count wins)
591
+ results.sort((a, b) => b.score - a.score);
592
+
593
+ if (results.length > 0) {
594
+ debugLog("Text scoring results", {
595
+ topFile: results[0].path,
596
+ topScore: results[0].score,
597
+ topMatches: results[0].matchedTexts,
598
+ allResults: results.slice(0, 3).map(r => ({ path: r.path, score: r.score }))
599
+ });
600
+ } else {
601
+ debugLog("No text matches found in any imported component files");
602
+ }
603
+
604
+ return results.length > 0 ? results[0] : null;
568
605
  }
569
606
 
570
607
  /**
@@ -1300,6 +1337,85 @@ Output format:
1300
1337
 
1301
1338
  The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
1302
1339
 
1340
+ /**
1341
+ * DESIGN REASONING PROMPT
1342
+ * Forces the LLM through a structured analysis before making design changes.
1343
+ * This prevents over-engineering and ensures thoughtful, targeted modifications.
1344
+ */
1345
+ const DESIGN_REASONING_PROMPT = `
1346
+ ═══════════════════════════════════════════════════════════════════════════════
1347
+ DESIGN REASONING PROTOCOL (Follow BEFORE generating patches)
1348
+ ═══════════════════════════════════════════════════════════════════════════════
1349
+
1350
+ STEP 1 - VISUAL ANALYSIS:
1351
+ Before making any changes, analyze the current UI in the screenshot:
1352
+ • What type of component is this? (form wizard, modal, card, data table, sidebar, etc.)
1353
+ • What is currently working well that should be PRESERVED?
1354
+ • What specific visual issues exist? (inconsistent spacing, unclear hierarchy, alignment problems, clutter)
1355
+
1356
+ STEP 2 - MINIMAL CHANGE PLAN:
1357
+ Identify the MINIMUM changes needed to address the user's request:
1358
+ • What 1-3 targeted fixes would solve the issue?
1359
+ • What should you explicitly NOT change?
1360
+ • Will these changes break existing functionality or visual balance?
1361
+
1362
+ STEP 3 - GUARDRAILS CHECK:
1363
+ Before implementing, verify you are following these rules:
1364
+ • Am I preserving the existing layout structure? (flex stays flex, grid stays grid)
1365
+ • Am I keeping the existing color palette unless specifically asked to change it?
1366
+ • Am I making incremental improvements, NOT a complete restructure?
1367
+ • Am I keeping the same visual hierarchy and element positions?
1368
+
1369
+ STEP 4 - IMPLEMENT:
1370
+ Only now generate patches for the targeted changes from Step 2.
1371
+ Each patch should be small and focused - prefer multiple small patches over one large rewrite.
1372
+ `;
1373
+
1374
+ /**
1375
+ * DESIGN GUARDRAILS
1376
+ * Explicit constraints to prevent the LLM from over-engineering design changes.
1377
+ */
1378
+ const DESIGN_GUARDRAILS = `
1379
+ ═══════════════════════════════════════════════════════════════════════════════
1380
+ DESIGN GUARDRAILS (MUST FOLLOW)
1381
+ ═══════════════════════════════════════════════════════════════════════════════
1382
+
1383
+ ❌ DO NOT:
1384
+ • Convert layout systems (do NOT change flex to grid or vice versa)
1385
+ • Restructure component hierarchy unless explicitly asked
1386
+ • Change color schemes unless specifically requested
1387
+ • Move elements to different positions unless asked
1388
+ • Rewrite entire sections - make targeted changes only
1389
+ • Add new wrapper elements or change semantic structure
1390
+
1391
+ ✓ DO:
1392
+ • Focus on spacing, padding, and margins for "cleaner" requests
1393
+ • Improve typography (font size, weight, line-height) for readability
1394
+ • Fix alignment issues within the existing structure
1395
+ • Adjust visual hierarchy through subtle styling, not restructuring
1396
+ • Make the smallest change that addresses the user's request
1397
+ • Preserve what is already working well
1398
+ `;
1399
+
1400
+ /**
1401
+ * Detect if a user request is design-heavy and needs the reasoning protocol.
1402
+ * Simple requests like "change button color to blue" don't need it.
1403
+ */
1404
+ function isDesignHeavyRequest(userPrompt: string): boolean {
1405
+ const designKeywords = [
1406
+ 'redesign', 'design', 'layout', 'improve', 'better', 'modernize', 'modern',
1407
+ 'cleaner', 'clean', 'messy', 'cluttered', 'update look', 'looks bad',
1408
+ 'ugly', 'prettier', 'beautiful', 'professional', 'polish', 'refine',
1409
+ 'restructure', 'reorganize', 'rearrange', 'simplify', 'streamline',
1410
+ 'compact', 'spacious', 'breathing room', 'visual', 'aesthetic',
1411
+ 'ux', 'user experience', 'ui', 'user interface', 'make it look',
1412
+ 'looks weird', 'not right', 'fix the look', 'appearance'
1413
+ ];
1414
+
1415
+ const lowerPrompt = userPrompt.toLowerCase();
1416
+ return designKeywords.some(keyword => lowerPrompt.includes(keyword));
1417
+ }
1418
+
1303
1419
  export async function POST(request: Request) {
1304
1420
  // Only allow in development
1305
1421
  if (process.env.NODE_ENV !== "development") {
@@ -1680,6 +1796,23 @@ User Request: "${userPrompt}"
1680
1796
 
1681
1797
  `;
1682
1798
 
1799
+ // ========== DESIGN REASONING PROTOCOL ==========
1800
+ // For design-heavy requests, add structured reasoning and guardrails
1801
+ // This prevents over-engineering and ensures thoughtful changes
1802
+ const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
1803
+
1804
+ if (isDesignRequest) {
1805
+ debugLog("Design-heavy request detected, adding reasoning protocol", {
1806
+ prompt: userPrompt?.substring(0, 50),
1807
+ triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
1808
+ .filter(k => userPrompt?.toLowerCase().includes(k))
1809
+ });
1810
+
1811
+ textContent += DESIGN_REASONING_PROMPT;
1812
+ textContent += DESIGN_GUARDRAILS;
1813
+ textContent += '\n';
1814
+ }
1815
+
1683
1816
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
1684
1817
  // CRITICAL: Only include the TARGET file to avoid overwhelming the LLM with noise
1685
1818
  if (recommendedFileContent) {
@@ -1704,43 +1837,67 @@ User Request: "${userPrompt}"
1704
1837
  }
1705
1838
  }
1706
1839
 
1707
- // TEXT-FIRST REDIRECT: Before accepting medium/low confidence match,
1708
- // search imported files for the actual TEXT CONTENT.
1709
- // This handles cases where parent component is selected but text lives in child.
1840
+ // TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
1841
+ // text strings they contain. The file with the highest score is the TRUE target.
1842
+ // This handles parent/child component cases accurately.
1710
1843
  if (elementLocation && elementLocation.confidence !== 'high') {
1711
- // Find the first focused element with meaningful text content
1712
- const textToFind = focusedElements.find(e => e.textContent && e.textContent.length > 5)?.textContent;
1844
+ // Collect ALL text content from focused elements
1845
+ const allTextContent = focusedElements
1846
+ .filter(e => e.textContent && e.textContent.length > 5)
1847
+ .map(e => e.textContent!);
1713
1848
 
1714
- if (textToFind) {
1715
- debugLog("Medium/low confidence match - checking imports for text", {
1849
+ if (allTextContent.length > 0) {
1850
+ debugLog("Medium/low confidence match - scoring imports for all text", {
1716
1851
  currentFile: recommendedFileContent.path,
1717
1852
  currentConfidence: elementLocation.confidence,
1718
- searchingFor: textToFind.substring(0, 40)
1853
+ textCount: allTextContent.length,
1854
+ texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
1719
1855
  });
1720
1856
 
1721
- const textMatch = findTextInImportedFiles(
1722
- textToFind,
1857
+ // Score all imported files
1858
+ const bestMatch = scoreFilesForTextContent(
1859
+ allTextContent,
1723
1860
  pageContext.componentSources
1724
1861
  );
1725
1862
 
1726
- if (textMatch) {
1727
- debugLog("TEXT REDIRECT: Found text in imported component", {
1863
+ // Also score the current file for comparison
1864
+ const currentFileScore = allTextContent.filter(text =>
1865
+ recommendedFileContent.content.includes(text.substring(0, 20))
1866
+ ).length;
1867
+
1868
+ debugLog("Text scoring comparison", {
1869
+ currentFile: recommendedFileContent.path,
1870
+ currentScore: currentFileScore,
1871
+ bestImport: bestMatch?.path || 'none',
1872
+ bestImportScore: bestMatch?.score || 0
1873
+ });
1874
+
1875
+ // Redirect only if imported file has MORE matches than current file
1876
+ if (bestMatch && bestMatch.score > currentFileScore) {
1877
+ debugLog("TEXT REDIRECT: Imported file has more text matches", {
1728
1878
  originalFile: recommendedFileContent.path,
1729
- originalConfidence: elementLocation.confidence,
1730
- redirectTo: textMatch.path,
1731
- lineNumber: textMatch.lineNumber
1879
+ originalScore: currentFileScore,
1880
+ redirectTo: bestMatch.path,
1881
+ redirectScore: bestMatch.score,
1882
+ matchedTexts: bestMatch.matchedTexts
1732
1883
  });
1733
1884
 
1734
- // Switch target file to where the text actually is
1885
+ // Switch target file to where most text content lives
1735
1886
  actualTargetFile = {
1736
- path: textMatch.path,
1737
- content: textMatch.content
1887
+ path: bestMatch.path,
1888
+ content: bestMatch.content
1738
1889
  };
1890
+
1891
+ // Find a line with matched text for element location
1892
+ const lines = bestMatch.content.split('\n');
1893
+ const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
1894
+ const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
1895
+
1739
1896
  elementLocation = {
1740
- lineNumber: textMatch.lineNumber,
1741
- snippet: textMatch.snippet,
1897
+ lineNumber: bestMatch.firstMatchLine,
1898
+ snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
1742
1899
  confidence: 'high',
1743
- matchedBy: `textContent in imported file "${textToFind.substring(0, 30)}..."`
1900
+ matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
1744
1901
  };
1745
1902
  }
1746
1903
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.96",
3
+ "version": "1.3.98",
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",