sonance-brand-mcp 1.3.96 → 1.3.97
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
|
-
|
|
528
|
-
|
|
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;
|
|
531
|
-
if (!
|
|
538
|
+
): { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number } | null {
|
|
539
|
+
if (!textContents || textContents.length === 0) return null;
|
|
532
540
|
|
|
533
|
-
debugLog("
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
571
|
-
|
|
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
|
/**
|
|
@@ -1735,43 +1772,67 @@ User Request: "${userPrompt}"
|
|
|
1735
1772
|
}
|
|
1736
1773
|
}
|
|
1737
1774
|
|
|
1738
|
-
// TEXT
|
|
1739
|
-
//
|
|
1740
|
-
// This handles
|
|
1775
|
+
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
1776
|
+
// text strings they contain. The file with the highest score is the TRUE target.
|
|
1777
|
+
// This handles parent/child component cases accurately.
|
|
1741
1778
|
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
1742
|
-
//
|
|
1743
|
-
const
|
|
1779
|
+
// Collect ALL text content from focused elements
|
|
1780
|
+
const allTextContent = focusedElements
|
|
1781
|
+
.filter(e => e.textContent && e.textContent.length > 5)
|
|
1782
|
+
.map(e => e.textContent!);
|
|
1744
1783
|
|
|
1745
|
-
if (
|
|
1746
|
-
debugLog("Medium/low confidence match -
|
|
1784
|
+
if (allTextContent.length > 0) {
|
|
1785
|
+
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
1747
1786
|
currentFile: recommendedFileContent.path,
|
|
1748
1787
|
currentConfidence: elementLocation.confidence,
|
|
1749
|
-
|
|
1788
|
+
textCount: allTextContent.length,
|
|
1789
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
1750
1790
|
});
|
|
1751
1791
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1792
|
+
// Score all imported files
|
|
1793
|
+
const bestMatch = scoreFilesForTextContent(
|
|
1794
|
+
allTextContent,
|
|
1754
1795
|
pageContext.componentSources
|
|
1755
1796
|
);
|
|
1756
1797
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1798
|
+
// Also score the current file for comparison
|
|
1799
|
+
const currentFileScore = allTextContent.filter(text =>
|
|
1800
|
+
recommendedFileContent.content.includes(text.substring(0, 20))
|
|
1801
|
+
).length;
|
|
1802
|
+
|
|
1803
|
+
debugLog("Text scoring comparison", {
|
|
1804
|
+
currentFile: recommendedFileContent.path,
|
|
1805
|
+
currentScore: currentFileScore,
|
|
1806
|
+
bestImport: bestMatch?.path || 'none',
|
|
1807
|
+
bestImportScore: bestMatch?.score || 0
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
// Redirect only if imported file has MORE matches than current file
|
|
1811
|
+
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
1812
|
+
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
1759
1813
|
originalFile: recommendedFileContent.path,
|
|
1760
|
-
|
|
1761
|
-
redirectTo:
|
|
1762
|
-
|
|
1814
|
+
originalScore: currentFileScore,
|
|
1815
|
+
redirectTo: bestMatch.path,
|
|
1816
|
+
redirectScore: bestMatch.score,
|
|
1817
|
+
matchedTexts: bestMatch.matchedTexts
|
|
1763
1818
|
});
|
|
1764
1819
|
|
|
1765
|
-
// Switch target file to where
|
|
1820
|
+
// Switch target file to where most text content lives
|
|
1766
1821
|
actualTargetFile = {
|
|
1767
|
-
path:
|
|
1768
|
-
content:
|
|
1822
|
+
path: bestMatch.path,
|
|
1823
|
+
content: bestMatch.content
|
|
1769
1824
|
};
|
|
1825
|
+
|
|
1826
|
+
// Find a line with matched text for element location
|
|
1827
|
+
const lines = bestMatch.content.split('\n');
|
|
1828
|
+
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
1829
|
+
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
1830
|
+
|
|
1770
1831
|
elementLocation = {
|
|
1771
|
-
lineNumber:
|
|
1772
|
-
snippet:
|
|
1832
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
1833
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
1773
1834
|
confidence: 'high',
|
|
1774
|
-
matchedBy: `
|
|
1835
|
+
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
1775
1836
|
};
|
|
1776
1837
|
}
|
|
1777
1838
|
}
|
|
@@ -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
|
-
|
|
524
|
-
|
|
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;
|
|
527
|
-
if (!
|
|
534
|
+
): { path: string; content: string; score: number; matchedTexts: string[]; firstMatchLine: number } | null {
|
|
535
|
+
if (!textContents || textContents.length === 0) return null;
|
|
528
536
|
|
|
529
|
-
debugLog("
|
|
530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
567
|
-
|
|
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
|
/**
|
|
@@ -1704,43 +1741,67 @@ User Request: "${userPrompt}"
|
|
|
1704
1741
|
}
|
|
1705
1742
|
}
|
|
1706
1743
|
|
|
1707
|
-
// TEXT
|
|
1708
|
-
//
|
|
1709
|
-
// This handles
|
|
1744
|
+
// TEXT SCORING REDIRECT: Score ALL imported files by how many discovered
|
|
1745
|
+
// text strings they contain. The file with the highest score is the TRUE target.
|
|
1746
|
+
// This handles parent/child component cases accurately.
|
|
1710
1747
|
if (elementLocation && elementLocation.confidence !== 'high') {
|
|
1711
|
-
//
|
|
1712
|
-
const
|
|
1748
|
+
// Collect ALL text content from focused elements
|
|
1749
|
+
const allTextContent = focusedElements
|
|
1750
|
+
.filter(e => e.textContent && e.textContent.length > 5)
|
|
1751
|
+
.map(e => e.textContent!);
|
|
1713
1752
|
|
|
1714
|
-
if (
|
|
1715
|
-
debugLog("Medium/low confidence match -
|
|
1753
|
+
if (allTextContent.length > 0) {
|
|
1754
|
+
debugLog("Medium/low confidence match - scoring imports for all text", {
|
|
1716
1755
|
currentFile: recommendedFileContent.path,
|
|
1717
1756
|
currentConfidence: elementLocation.confidence,
|
|
1718
|
-
|
|
1757
|
+
textCount: allTextContent.length,
|
|
1758
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
1719
1759
|
});
|
|
1720
1760
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1761
|
+
// Score all imported files
|
|
1762
|
+
const bestMatch = scoreFilesForTextContent(
|
|
1763
|
+
allTextContent,
|
|
1723
1764
|
pageContext.componentSources
|
|
1724
1765
|
);
|
|
1725
1766
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1767
|
+
// Also score the current file for comparison
|
|
1768
|
+
const currentFileScore = allTextContent.filter(text =>
|
|
1769
|
+
recommendedFileContent.content.includes(text.substring(0, 20))
|
|
1770
|
+
).length;
|
|
1771
|
+
|
|
1772
|
+
debugLog("Text scoring comparison", {
|
|
1773
|
+
currentFile: recommendedFileContent.path,
|
|
1774
|
+
currentScore: currentFileScore,
|
|
1775
|
+
bestImport: bestMatch?.path || 'none',
|
|
1776
|
+
bestImportScore: bestMatch?.score || 0
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
// Redirect only if imported file has MORE matches than current file
|
|
1780
|
+
if (bestMatch && bestMatch.score > currentFileScore) {
|
|
1781
|
+
debugLog("TEXT REDIRECT: Imported file has more text matches", {
|
|
1728
1782
|
originalFile: recommendedFileContent.path,
|
|
1729
|
-
|
|
1730
|
-
redirectTo:
|
|
1731
|
-
|
|
1783
|
+
originalScore: currentFileScore,
|
|
1784
|
+
redirectTo: bestMatch.path,
|
|
1785
|
+
redirectScore: bestMatch.score,
|
|
1786
|
+
matchedTexts: bestMatch.matchedTexts
|
|
1732
1787
|
});
|
|
1733
1788
|
|
|
1734
|
-
// Switch target file to where
|
|
1789
|
+
// Switch target file to where most text content lives
|
|
1735
1790
|
actualTargetFile = {
|
|
1736
|
-
path:
|
|
1737
|
-
content:
|
|
1791
|
+
path: bestMatch.path,
|
|
1792
|
+
content: bestMatch.content
|
|
1738
1793
|
};
|
|
1794
|
+
|
|
1795
|
+
// Find a line with matched text for element location
|
|
1796
|
+
const lines = bestMatch.content.split('\n');
|
|
1797
|
+
const snippetStart = Math.max(0, bestMatch.firstMatchLine - 4);
|
|
1798
|
+
const snippetEnd = Math.min(lines.length, bestMatch.firstMatchLine + 5);
|
|
1799
|
+
|
|
1739
1800
|
elementLocation = {
|
|
1740
|
-
lineNumber:
|
|
1741
|
-
snippet:
|
|
1801
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
1802
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
1742
1803
|
confidence: 'high',
|
|
1743
|
-
matchedBy: `
|
|
1804
|
+
matchedBy: `text scoring: ${bestMatch.score} matches in imported file`
|
|
1744
1805
|
};
|
|
1745
1806
|
}
|
|
1746
1807
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.97",
|
|
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",
|