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
|
-
|
|
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
|
/**
|
|
@@ -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
|
|
1739
|
-
//
|
|
1740
|
-
// This handles
|
|
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
|
-
//
|
|
1743
|
-
const
|
|
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 (
|
|
1746
|
-
debugLog("Medium/low confidence match -
|
|
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
|
-
|
|
1884
|
+
textCount: allTextContent.length,
|
|
1885
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
1750
1886
|
});
|
|
1751
1887
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1888
|
+
// Score all imported files
|
|
1889
|
+
const bestMatch = scoreFilesForTextContent(
|
|
1890
|
+
allTextContent,
|
|
1754
1891
|
pageContext.componentSources
|
|
1755
1892
|
);
|
|
1756
1893
|
|
|
1757
|
-
|
|
1758
|
-
|
|
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
|
-
|
|
1761
|
-
redirectTo:
|
|
1762
|
-
|
|
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
|
|
1916
|
+
// Switch target file to where most text content lives
|
|
1766
1917
|
actualTargetFile = {
|
|
1767
|
-
path:
|
|
1768
|
-
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:
|
|
1772
|
-
snippet:
|
|
1928
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
1929
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
1773
1930
|
confidence: 'high',
|
|
1774
|
-
matchedBy: `
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -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
|
|
1708
|
-
//
|
|
1709
|
-
// This handles
|
|
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
|
-
//
|
|
1712
|
-
const
|
|
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 (
|
|
1715
|
-
debugLog("Medium/low confidence match -
|
|
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
|
-
|
|
1853
|
+
textCount: allTextContent.length,
|
|
1854
|
+
texts: allTextContent.slice(0, 5).map(t => t.substring(0, 25))
|
|
1719
1855
|
});
|
|
1720
1856
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1857
|
+
// Score all imported files
|
|
1858
|
+
const bestMatch = scoreFilesForTextContent(
|
|
1859
|
+
allTextContent,
|
|
1723
1860
|
pageContext.componentSources
|
|
1724
1861
|
);
|
|
1725
1862
|
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
-
|
|
1730
|
-
redirectTo:
|
|
1731
|
-
|
|
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
|
|
1885
|
+
// Switch target file to where most text content lives
|
|
1735
1886
|
actualTargetFile = {
|
|
1736
|
-
path:
|
|
1737
|
-
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:
|
|
1741
|
-
snippet:
|
|
1897
|
+
lineNumber: bestMatch.firstMatchLine,
|
|
1898
|
+
snippet: lines.slice(snippetStart, snippetEnd).join('\n'),
|
|
1742
1899
|
confidence: 'high',
|
|
1743
|
-
matchedBy: `
|
|
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.
|
|
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",
|