sonance-brand-mcp 1.3.95 → 1.3.96

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.
@@ -516,6 +516,61 @@ function findElementLineInFile(
516
516
  return null;
517
517
  }
518
518
 
519
+ /**
520
+ * Search imported component files for specific TEXT CONTENT
521
+ * This is used to redirect from parent components to child components
522
+ * when the actual visible text lives in an imported file.
523
+ *
524
+ * Example: "What's the essence of this process?" might be in EssenceTab.tsx
525
+ * but the parent QuickAddProcessModal.tsx was selected by Phase 2a.
526
+ */
527
+ function findTextInImportedFiles(
528
+ textContent: string,
529
+ importedFiles: { path: string; content: string }[]
530
+ ): { path: string; lineNumber: number; snippet: string; content: string } | null {
531
+ if (!textContent || textContent.length < 5) return null;
532
+
533
+ debugLog("Searching imported files for text content", {
534
+ textContent: textContent.substring(0, 40),
535
+ filesCount: importedFiles.length
536
+ });
537
+
538
+ // Only search component files (where JSX text lives)
539
+ const componentFiles = importedFiles.filter(f =>
540
+ f.path.includes('components/') || f.path.includes('/ui/')
541
+ );
542
+
543
+ // Use first 20 chars for matching (handles dynamic suffixes)
544
+ const searchText = textContent.substring(0, 20);
545
+
546
+ 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
+ };
566
+ }
567
+ }
568
+ }
569
+
570
+ debugLog("Text not found in any imported component files");
571
+ return null;
572
+ }
573
+
519
574
  /**
520
575
  * Search imported component files for the focused element
521
576
  * Reuses findElementLineInFile() for consistent detection
@@ -1528,7 +1583,7 @@ export async function POST(request: Request) {
1528
1583
  const confirmedPath = phase2aMatches.find(p =>
1529
1584
  focusedElementHints.some(h => h.path === p && h.score > 0)
1530
1585
  );
1531
- recommendedFile = {
1586
+ recommendedFile = {
1532
1587
  path: confirmedPath!,
1533
1588
  reason: `Phase 2a component-name match confirmed by element search`
1534
1589
  };
@@ -1552,7 +1607,7 @@ export async function POST(request: Request) {
1552
1607
  selectedPath: bestMatch,
1553
1608
  allCandidates: phase2aMatches
1554
1609
  });
1555
- } else {
1610
+ } else {
1556
1611
  // FALLBACK PRIORITY 4: Use the page file from the current route
1557
1612
  const routePageFile = discoverPageFile(pageRoute || "/", projectRoot);
1558
1613
 
@@ -1666,7 +1721,7 @@ User Request: "${userPrompt}"
1666
1721
  let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
1667
1722
  let actualTargetFile = recommendedFileContent; // May change if we redirect
1668
1723
 
1669
- if (focusedElements && focusedElements.length > 0) {
1724
+ if (focusedElements && focusedElements.length > 0) {
1670
1725
  for (const el of focusedElements) {
1671
1726
  elementLocation = findElementLineInFile(content, el);
1672
1727
  if (elementLocation) {
@@ -1680,6 +1735,48 @@ User Request: "${userPrompt}"
1680
1735
  }
1681
1736
  }
1682
1737
 
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.
1741
+ 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;
1744
+
1745
+ if (textToFind) {
1746
+ debugLog("Medium/low confidence match - checking imports for text", {
1747
+ currentFile: recommendedFileContent.path,
1748
+ currentConfidence: elementLocation.confidence,
1749
+ searchingFor: textToFind.substring(0, 40)
1750
+ });
1751
+
1752
+ const textMatch = findTextInImportedFiles(
1753
+ textToFind,
1754
+ pageContext.componentSources
1755
+ );
1756
+
1757
+ if (textMatch) {
1758
+ debugLog("TEXT REDIRECT: Found text in imported component", {
1759
+ originalFile: recommendedFileContent.path,
1760
+ originalConfidence: elementLocation.confidence,
1761
+ redirectTo: textMatch.path,
1762
+ lineNumber: textMatch.lineNumber
1763
+ });
1764
+
1765
+ // Switch target file to where the text actually is
1766
+ actualTargetFile = {
1767
+ path: textMatch.path,
1768
+ content: textMatch.content
1769
+ };
1770
+ elementLocation = {
1771
+ lineNumber: textMatch.lineNumber,
1772
+ snippet: textMatch.snippet,
1773
+ confidence: 'high',
1774
+ matchedBy: `textContent in imported file "${textToFind.substring(0, 30)}..."`
1775
+ };
1776
+ }
1777
+ }
1778
+ }
1779
+
1683
1780
  // DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
1684
1781
  if (!elementLocation) {
1685
1782
  debugLog("Element not in main file, searching imported components...", {
@@ -512,6 +512,61 @@ function findElementLineInFile(
512
512
  return null;
513
513
  }
514
514
 
515
+ /**
516
+ * Search imported component files for specific TEXT CONTENT
517
+ * This is used to redirect from parent components to child components
518
+ * when the actual visible text lives in an imported file.
519
+ *
520
+ * Example: "What's the essence of this process?" might be in EssenceTab.tsx
521
+ * but the parent QuickAddProcessModal.tsx was selected by Phase 2a.
522
+ */
523
+ function findTextInImportedFiles(
524
+ textContent: string,
525
+ importedFiles: { path: string; content: string }[]
526
+ ): { path: string; lineNumber: number; snippet: string; content: string } | null {
527
+ if (!textContent || textContent.length < 5) return null;
528
+
529
+ debugLog("Searching imported files for text content", {
530
+ textContent: textContent.substring(0, 40),
531
+ filesCount: importedFiles.length
532
+ });
533
+
534
+ // Only search component files (where JSX text lives)
535
+ const componentFiles = importedFiles.filter(f =>
536
+ f.path.includes('components/') || f.path.includes('/ui/')
537
+ );
538
+
539
+ // Use first 20 chars for matching (handles dynamic suffixes)
540
+ const searchText = textContent.substring(0, 20);
541
+
542
+ 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
+ };
562
+ }
563
+ }
564
+ }
565
+
566
+ debugLog("Text not found in any imported component files");
567
+ return null;
568
+ }
569
+
515
570
  /**
516
571
  * Search imported component files for the focused element
517
572
  * Reuses findElementLineInFile() for consistent detection
@@ -1497,7 +1552,7 @@ export async function POST(request: Request) {
1497
1552
  const confirmedPath = phase2aMatches.find(p =>
1498
1553
  focusedElementHints.some(h => h.path === p && h.score > 0)
1499
1554
  );
1500
- recommendedFile = {
1555
+ recommendedFile = {
1501
1556
  path: confirmedPath!,
1502
1557
  reason: `Phase 2a component-name match confirmed by element search`
1503
1558
  };
@@ -1521,7 +1576,7 @@ export async function POST(request: Request) {
1521
1576
  selectedPath: bestMatch,
1522
1577
  allCandidates: phase2aMatches
1523
1578
  });
1524
- } else {
1579
+ } else {
1525
1580
  // FALLBACK PRIORITY 4: Use the page file from the current route
1526
1581
  const routePageFile = discoverPageFile(pageRoute || "/", projectRoot);
1527
1582
 
@@ -1635,7 +1690,7 @@ User Request: "${userPrompt}"
1635
1690
  let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
1636
1691
  let actualTargetFile = recommendedFileContent; // May change if we redirect
1637
1692
 
1638
- if (focusedElements && focusedElements.length > 0) {
1693
+ if (focusedElements && focusedElements.length > 0) {
1639
1694
  for (const el of focusedElements) {
1640
1695
  elementLocation = findElementLineInFile(content, el);
1641
1696
  if (elementLocation) {
@@ -1649,6 +1704,48 @@ User Request: "${userPrompt}"
1649
1704
  }
1650
1705
  }
1651
1706
 
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.
1710
+ 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;
1713
+
1714
+ if (textToFind) {
1715
+ debugLog("Medium/low confidence match - checking imports for text", {
1716
+ currentFile: recommendedFileContent.path,
1717
+ currentConfidence: elementLocation.confidence,
1718
+ searchingFor: textToFind.substring(0, 40)
1719
+ });
1720
+
1721
+ const textMatch = findTextInImportedFiles(
1722
+ textToFind,
1723
+ pageContext.componentSources
1724
+ );
1725
+
1726
+ if (textMatch) {
1727
+ debugLog("TEXT REDIRECT: Found text in imported component", {
1728
+ originalFile: recommendedFileContent.path,
1729
+ originalConfidence: elementLocation.confidence,
1730
+ redirectTo: textMatch.path,
1731
+ lineNumber: textMatch.lineNumber
1732
+ });
1733
+
1734
+ // Switch target file to where the text actually is
1735
+ actualTargetFile = {
1736
+ path: textMatch.path,
1737
+ content: textMatch.content
1738
+ };
1739
+ elementLocation = {
1740
+ lineNumber: textMatch.lineNumber,
1741
+ snippet: textMatch.snippet,
1742
+ confidence: 'high',
1743
+ matchedBy: `textContent in imported file "${textToFind.substring(0, 30)}..."`
1744
+ };
1745
+ }
1746
+ }
1747
+ }
1748
+
1652
1749
  // DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
1653
1750
  if (!elementLocation) {
1654
1751
  debugLog("Element not in main file, searching imported components...", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.95",
3
+ "version": "1.3.96",
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",