sonance-brand-mcp 1.3.98 → 1.3.100
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.
|
@@ -88,6 +88,40 @@ interface BackupManifest {
|
|
|
88
88
|
const BACKUP_ROOT = ".sonance-backups";
|
|
89
89
|
const DEBUG_LOG_FILE = "sonance-debug.log";
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* TWO-PHASE DESIGN ANALYSIS TYPES
|
|
93
|
+
* Phase 1 identifies problems, Phase 2 generates patches that reference those problems
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/** Categories of visual/UX problems that can be identified */
|
|
97
|
+
type DesignProblemCategory = 'hierarchy' | 'spacing' | 'alignment' | 'feedback' | 'grouping';
|
|
98
|
+
|
|
99
|
+
/** A specific visual problem identified in Phase 1 analysis */
|
|
100
|
+
interface DesignProblem {
|
|
101
|
+
/** Unique identifier (e.g., "hierarchy-1", "spacing-2") */
|
|
102
|
+
id: string;
|
|
103
|
+
/** Problem category */
|
|
104
|
+
category: DesignProblemCategory;
|
|
105
|
+
/** Specific description of the issue */
|
|
106
|
+
description: string;
|
|
107
|
+
/** Visual impact severity */
|
|
108
|
+
severity: 'high' | 'medium' | 'low';
|
|
109
|
+
/** Hint for what to look for in the code */
|
|
110
|
+
codeHint?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Extended patch that references a specific problem from Phase 1 */
|
|
114
|
+
interface DesignPatch {
|
|
115
|
+
/** Reference to the problem this patch fixes */
|
|
116
|
+
problemId: string;
|
|
117
|
+
/** Exact code to find */
|
|
118
|
+
search: string;
|
|
119
|
+
/** Replacement code */
|
|
120
|
+
replace: string;
|
|
121
|
+
/** Explanation of how this fixes the problem */
|
|
122
|
+
rationale: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
91
125
|
/**
|
|
92
126
|
* Debug logging utility - writes to sonance-debug.log in project root
|
|
93
127
|
* This helps diagnose issues with file discovery, validation, and revert
|
|
@@ -889,6 +923,139 @@ Be smart - use your knowledge of React patterns to identify what type of element
|
|
|
889
923
|
}
|
|
890
924
|
}
|
|
891
925
|
|
|
926
|
+
/**
|
|
927
|
+
* PHASE 1: Design Problem Analysis
|
|
928
|
+
*
|
|
929
|
+
* Analyzes the screenshot to identify specific visual/UX problems.
|
|
930
|
+
* This is a SEPARATE LLM call that ONLY identifies problems - no code changes.
|
|
931
|
+
* The problems are then passed to Phase 2 to generate targeted patches.
|
|
932
|
+
*/
|
|
933
|
+
async function analyzeDesignProblems(
|
|
934
|
+
screenshot: string,
|
|
935
|
+
fileContent: string,
|
|
936
|
+
userPrompt: string,
|
|
937
|
+
apiKey: string
|
|
938
|
+
): Promise<DesignProblem[]> {
|
|
939
|
+
const anthropic = new Anthropic({ apiKey });
|
|
940
|
+
const base64Data = screenshot.split(",")[1] || screenshot;
|
|
941
|
+
|
|
942
|
+
debugLog("Phase 1 Design Analysis: Starting visual problem identification", {
|
|
943
|
+
promptPreview: userPrompt?.substring(0, 50),
|
|
944
|
+
fileContentLength: fileContent.length
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
const response = await anthropic.messages.create({
|
|
949
|
+
model: "claude-sonnet-4-20250514",
|
|
950
|
+
max_tokens: 2048,
|
|
951
|
+
messages: [
|
|
952
|
+
{
|
|
953
|
+
role: "user",
|
|
954
|
+
content: [
|
|
955
|
+
{
|
|
956
|
+
type: "image",
|
|
957
|
+
source: {
|
|
958
|
+
type: "base64",
|
|
959
|
+
media_type: "image/png",
|
|
960
|
+
data: base64Data,
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
type: "text",
|
|
965
|
+
text: `You are a senior UI/UX designer. Analyze this screenshot and identify SPECIFIC visual problems.
|
|
966
|
+
|
|
967
|
+
User's request: "${userPrompt}"
|
|
968
|
+
|
|
969
|
+
Examine the UI for issues in these categories:
|
|
970
|
+
|
|
971
|
+
1. HIERARCHY: Is there a clear visual focus? Are elements competing for attention?
|
|
972
|
+
- Look for: competing headings, unclear primary actions, visual clutter
|
|
973
|
+
|
|
974
|
+
2. SPACING: Are related elements grouped? Is there awkward empty space?
|
|
975
|
+
- Look for: disconnected labels/values, uneven gaps, cramped or overly spread content
|
|
976
|
+
|
|
977
|
+
3. ALIGNMENT: Do elements align naturally? Are there visual disconnects?
|
|
978
|
+
- Look for: misaligned text, floating elements, broken visual flow
|
|
979
|
+
|
|
980
|
+
4. FEEDBACK: Are progress indicators, states, and actions clear?
|
|
981
|
+
- Look for: weak progress visualization, unclear states, orphaned status indicators
|
|
982
|
+
|
|
983
|
+
5. GROUPING: Are related items visually connected? Do containers make sense?
|
|
984
|
+
- Look for: related content that appears separate, missing visual boundaries
|
|
985
|
+
|
|
986
|
+
For each problem found, provide:
|
|
987
|
+
- id: unique identifier (e.g., "hierarchy-1", "spacing-2")
|
|
988
|
+
- category: one of [hierarchy, spacing, alignment, feedback, grouping]
|
|
989
|
+
- description: specific description of what's wrong
|
|
990
|
+
- severity: high/medium/low based on visual impact
|
|
991
|
+
- codeHint: what to look for in the code to fix it
|
|
992
|
+
|
|
993
|
+
Return ONLY a JSON array of problems. Example:
|
|
994
|
+
[
|
|
995
|
+
{
|
|
996
|
+
"id": "grouping-1",
|
|
997
|
+
"category": "grouping",
|
|
998
|
+
"description": "The 'Essence Quality' label and '0% COMPLETE' badge appear visually disconnected - they should be grouped together",
|
|
999
|
+
"severity": "high",
|
|
1000
|
+
"codeHint": "Check if label and badge are in separate containers or using absolute positioning"
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
"id": "spacing-1",
|
|
1004
|
+
"category": "spacing",
|
|
1005
|
+
"description": "Too much vertical gap between the header section and the quality indicator",
|
|
1006
|
+
"severity": "medium",
|
|
1007
|
+
"codeHint": "Look for large margin or padding values like mt-6, mb-8, py-6"
|
|
1008
|
+
}
|
|
1009
|
+
]
|
|
1010
|
+
|
|
1011
|
+
IMPORTANT:
|
|
1012
|
+
- Only identify problems that are actually visible in the screenshot
|
|
1013
|
+
- Be specific - vague problems lead to vague fixes
|
|
1014
|
+
- Maximum 5 problems - focus on the most impactful ones
|
|
1015
|
+
- If no significant problems are found, return an empty array []
|
|
1016
|
+
- Do NOT suggest code changes - only identify problems
|
|
1017
|
+
|
|
1018
|
+
Return ONLY the JSON array, no other text.`,
|
|
1019
|
+
},
|
|
1020
|
+
],
|
|
1021
|
+
},
|
|
1022
|
+
],
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
1026
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
1027
|
+
debugLog("Phase 1 Design Analysis: No text response from LLM");
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
let jsonText = textBlock.text.trim();
|
|
1032
|
+
|
|
1033
|
+
// Extract JSON from code fences if present
|
|
1034
|
+
const jsonMatch = jsonText.match(/```(?:json)?\n?([\s\S]*?)\n?```/) ||
|
|
1035
|
+
jsonText.match(/\[[\s\S]*\]/);
|
|
1036
|
+
if (jsonMatch) {
|
|
1037
|
+
jsonText = jsonMatch[1] || jsonMatch[0];
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const problems: DesignProblem[] = JSON.parse(jsonText);
|
|
1041
|
+
|
|
1042
|
+
// Validate the structure
|
|
1043
|
+
const validatedProblems = problems.filter(p =>
|
|
1044
|
+
p.id && p.category && p.description && p.severity
|
|
1045
|
+
).slice(0, 5); // Maximum 5 problems
|
|
1046
|
+
|
|
1047
|
+
debugLog("Phase 1 Design Analysis: Identified problems", {
|
|
1048
|
+
count: validatedProblems.length,
|
|
1049
|
+
problems: validatedProblems.map(p => ({ id: p.id, category: p.category, severity: p.severity }))
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
return validatedProblems;
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
debugLog("Phase 1 Design Analysis: Failed to analyze", { error: String(e) });
|
|
1055
|
+
return [];
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
892
1059
|
/**
|
|
893
1060
|
* Search candidate files for JSX code matching the focused element
|
|
894
1061
|
* This helps identify which file actually contains the element the user clicked on
|
|
@@ -1342,65 +1509,166 @@ Output format:
|
|
|
1342
1509
|
The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
|
|
1343
1510
|
|
|
1344
1511
|
/**
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
* This
|
|
1512
|
+
* PHASE 2: Targeted Patch Generation Prompt
|
|
1513
|
+
*
|
|
1514
|
+
* This prompt is used AFTER Phase 1 has identified specific problems.
|
|
1515
|
+
* It requires each patch to reference a problem ID from Phase 1.
|
|
1348
1516
|
*/
|
|
1349
|
-
|
|
1517
|
+
function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
|
|
1518
|
+
if (problems.length === 0) {
|
|
1519
|
+
return ''; // No problems identified, skip design protocol
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const problemsJson = JSON.stringify(problems, null, 2);
|
|
1523
|
+
|
|
1524
|
+
return `
|
|
1350
1525
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1351
|
-
|
|
1526
|
+
PHASE 2: TARGETED PATCH GENERATION
|
|
1352
1527
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1353
1528
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1529
|
+
A visual analysis identified these specific problems in the UI:
|
|
1530
|
+
|
|
1531
|
+
${problemsJson}
|
|
1532
|
+
|
|
1533
|
+
YOUR TASK: Generate patches that fix ONLY these specific problems.
|
|
1534
|
+
|
|
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
|
|
1541
|
+
|
|
1542
|
+
For each problem, find the exact code that causes it and create a targeted fix.
|
|
1543
|
+
|
|
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
|
|
1376
1569
|
`;
|
|
1570
|
+
}
|
|
1377
1571
|
|
|
1378
1572
|
/**
|
|
1379
|
-
* DESIGN
|
|
1380
|
-
* Explicit constraints to prevent the LLM from over-engineering design changes.
|
|
1573
|
+
* DESIGN DECISION RULES for Phase 2
|
|
1381
1574
|
*/
|
|
1382
1575
|
const DESIGN_GUARDRAILS = `
|
|
1383
1576
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1384
|
-
DESIGN
|
|
1577
|
+
DESIGN CONSTRAINTS
|
|
1385
1578
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1386
1579
|
|
|
1387
|
-
|
|
1388
|
-
•
|
|
1389
|
-
•
|
|
1390
|
-
•
|
|
1391
|
-
•
|
|
1392
|
-
•
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
•
|
|
1397
|
-
•
|
|
1398
|
-
•
|
|
1399
|
-
•
|
|
1400
|
-
|
|
1401
|
-
|
|
1580
|
+
✓ ALLOWED when fixing identified problems:
|
|
1581
|
+
• Adjust spacing (margin, padding) - e.g., mt-4 → mt-2
|
|
1582
|
+
• Adjust alignment within containers - e.g., items-start → items-center
|
|
1583
|
+
• Move elements within the same container to improve grouping
|
|
1584
|
+
• Adjust typography - font size, weight, line-height
|
|
1585
|
+
• Add/remove whitespace classes
|
|
1586
|
+
|
|
1587
|
+
✗ FORBIDDEN (will cause rejection):
|
|
1588
|
+
• Changing layout system (flex → grid, grid → flex)
|
|
1589
|
+
• Restructuring component hierarchy
|
|
1590
|
+
• Wrapping elements in new containers (unless specifically needed for a grouping problem)
|
|
1591
|
+
• Removing or changing functionality
|
|
1592
|
+
• Changes without a problemId reference
|
|
1593
|
+
|
|
1594
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
1402
1595
|
`;
|
|
1403
1596
|
|
|
1597
|
+
/**
|
|
1598
|
+
* Validate patches against identified problems
|
|
1599
|
+
* Rejects patches that don't reference a valid problem ID
|
|
1600
|
+
*/
|
|
1601
|
+
function validateDesignPatches(
|
|
1602
|
+
patches: Array<{ search: string; replace: string; problemId?: string; rationale?: string }>,
|
|
1603
|
+
problems: DesignProblem[]
|
|
1604
|
+
): { valid: Array<{ search: string; replace: string; problemId: string; rationale: string }>; rejected: Array<{ patch: unknown; reason: string }> } {
|
|
1605
|
+
const validProblemIds = new Set(problems.map(p => p.id));
|
|
1606
|
+
const valid: Array<{ search: string; replace: string; problemId: string; rationale: string }> = [];
|
|
1607
|
+
const rejected: Array<{ patch: unknown; reason: string }> = [];
|
|
1608
|
+
|
|
1609
|
+
for (const patch of patches) {
|
|
1610
|
+
// Check for required fields
|
|
1611
|
+
if (!patch.search || !patch.replace) {
|
|
1612
|
+
rejected.push({ patch, reason: "Missing search or replace field" });
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// For design-heavy requests, require problemId
|
|
1617
|
+
if (!patch.problemId) {
|
|
1618
|
+
rejected.push({ patch, reason: "Missing problemId - patch doesn't reference an identified problem" });
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// Validate problemId exists in the problems list
|
|
1623
|
+
if (!validProblemIds.has(patch.problemId)) {
|
|
1624
|
+
rejected.push({ patch, reason: `Invalid problemId "${patch.problemId}" - not in identified problems list` });
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Check for forbidden structural changes
|
|
1629
|
+
const searchLower = patch.search.toLowerCase();
|
|
1630
|
+
const replaceLower = patch.replace.toLowerCase();
|
|
1631
|
+
|
|
1632
|
+
// Detect flex → grid or grid → flex conversions
|
|
1633
|
+
const hadFlex = searchLower.includes('flex') || searchLower.includes('items-') || searchLower.includes('justify-');
|
|
1634
|
+
const hasGrid = replaceLower.includes('grid') && !searchLower.includes('grid');
|
|
1635
|
+
const hadGrid = searchLower.includes('grid');
|
|
1636
|
+
const hasFlex = (replaceLower.includes('flex') || replaceLower.includes('items-') || replaceLower.includes('justify-')) && !hadFlex;
|
|
1637
|
+
|
|
1638
|
+
if ((hadFlex && hasGrid) || (hadGrid && hasFlex)) {
|
|
1639
|
+
rejected.push({ patch, reason: "Forbidden: Changing layout system (flex ↔ grid)" });
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// Detect space-y/space-x additions that fundamentally change layout
|
|
1644
|
+
const addedSpaceY = replaceLower.includes('space-y') && !searchLower.includes('space-y');
|
|
1645
|
+
const addedSpaceX = replaceLower.includes('space-x') && !searchLower.includes('space-x');
|
|
1646
|
+
const hadJustifyBetween = searchLower.includes('justify-between');
|
|
1647
|
+
|
|
1648
|
+
if ((addedSpaceY || addedSpaceX) && hadJustifyBetween) {
|
|
1649
|
+
rejected.push({ patch, reason: "Forbidden: Converting flex layout to stack layout" });
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
valid.push({
|
|
1654
|
+
search: patch.search,
|
|
1655
|
+
replace: patch.replace,
|
|
1656
|
+
problemId: patch.problemId,
|
|
1657
|
+
rationale: patch.rationale || 'No rationale provided'
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (rejected.length > 0) {
|
|
1662
|
+
debugLog("Design patch validation: Some patches rejected", {
|
|
1663
|
+
validCount: valid.length,
|
|
1664
|
+
rejectedCount: rejected.length,
|
|
1665
|
+
rejected: rejected.map(r => ({ reason: r.reason, searchPreview: String(r.patch).substring(0, 50) }))
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
return { valid, rejected };
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1404
1672
|
/**
|
|
1405
1673
|
* Detect if a user request is design-heavy and needs the reasoning protocol.
|
|
1406
1674
|
* Simple requests like "change button color to blue" don't need it.
|
|
@@ -1827,21 +2095,42 @@ User Request: "${userPrompt}"
|
|
|
1827
2095
|
|
|
1828
2096
|
`;
|
|
1829
2097
|
|
|
1830
|
-
// ========== DESIGN
|
|
1831
|
-
//
|
|
1832
|
-
//
|
|
2098
|
+
// ========== TWO-PHASE DESIGN ANALYSIS ==========
|
|
2099
|
+
// Phase 1: Identify specific visual problems (separate LLM call)
|
|
2100
|
+
// Phase 2: Generate patches that reference those problems
|
|
1833
2101
|
const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
|
|
2102
|
+
let identifiedProblems: DesignProblem[] = [];
|
|
1834
2103
|
|
|
1835
|
-
if (isDesignRequest) {
|
|
1836
|
-
debugLog("Design-heavy request detected,
|
|
2104
|
+
if (isDesignRequest && screenshot) {
|
|
2105
|
+
debugLog("Design-heavy request detected, running Phase 1 analysis", {
|
|
1837
2106
|
prompt: userPrompt?.substring(0, 50),
|
|
1838
2107
|
triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
|
|
1839
2108
|
.filter(k => userPrompt?.toLowerCase().includes(k))
|
|
1840
2109
|
});
|
|
1841
2110
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2111
|
+
// Phase 1: Analyze the screenshot to identify specific problems
|
|
2112
|
+
// This is a SEPARATE LLM call that only identifies problems
|
|
2113
|
+
const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
|
|
2114
|
+
identifiedProblems = await analyzeDesignProblems(
|
|
2115
|
+
screenshot,
|
|
2116
|
+
fileContentForAnalysis,
|
|
2117
|
+
userPrompt || '',
|
|
2118
|
+
apiKey
|
|
2119
|
+
);
|
|
2120
|
+
|
|
2121
|
+
debugLog("Phase 1 complete: Problems identified", {
|
|
2122
|
+
problemCount: identifiedProblems.length,
|
|
2123
|
+
problems: identifiedProblems.map(p => `${p.id}: ${p.description.substring(0, 50)}`)
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
// Add Phase 2 prompt with the identified problems
|
|
2127
|
+
if (identifiedProblems.length > 0) {
|
|
2128
|
+
textContent += buildPhase2DesignPrompt(identifiedProblems);
|
|
2129
|
+
textContent += DESIGN_GUARDRAILS;
|
|
2130
|
+
textContent += '\n';
|
|
2131
|
+
} else {
|
|
2132
|
+
debugLog("No problems identified - proceeding with standard edit");
|
|
2133
|
+
}
|
|
1845
2134
|
}
|
|
1846
2135
|
|
|
1847
2136
|
// ========== TARGET COMPONENT ONLY (with line numbers) ==========
|
|
@@ -2366,9 +2655,41 @@ This is better than generating patches with made-up code.`,
|
|
|
2366
2655
|
// New patch-based approach
|
|
2367
2656
|
console.log(`[Apply-First] Applying ${mod.patches.length} patches to ${mod.filePath}`);
|
|
2368
2657
|
|
|
2658
|
+
// DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
|
|
2659
|
+
let patchesToApply = mod.patches;
|
|
2660
|
+
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", {
|
|
2682
|
+
filePath: mod.filePath,
|
|
2683
|
+
problemsBeingFixed,
|
|
2684
|
+
patchCount: validationResult.valid.length
|
|
2685
|
+
});
|
|
2686
|
+
|
|
2687
|
+
patchesToApply = validationResult.valid;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2369
2690
|
// PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
|
|
2370
2691
|
const preValidationErrors: string[] = [];
|
|
2371
|
-
for (const patch of
|
|
2692
|
+
for (const patch of patchesToApply) {
|
|
2372
2693
|
const normalizedSearch = patch.search.replace(/\\n/g, "\n");
|
|
2373
2694
|
if (!originalContent.includes(normalizedSearch)) {
|
|
2374
2695
|
// Try fuzzy match as fallback
|
|
@@ -2407,7 +2728,7 @@ This is better than generating patches with made-up code.`,
|
|
|
2407
2728
|
continue;
|
|
2408
2729
|
}
|
|
2409
2730
|
|
|
2410
|
-
const patchResult = applyPatches(originalContent,
|
|
2731
|
+
const patchResult = applyPatches(originalContent, patchesToApply);
|
|
2411
2732
|
|
|
2412
2733
|
if (!patchResult.success) {
|
|
2413
2734
|
const failedMessages = patchResult.failedPatches.map(
|
|
@@ -2417,15 +2738,15 @@ This is better than generating patches with made-up code.`,
|
|
|
2417
2738
|
|
|
2418
2739
|
// If some patches succeeded, use partial result
|
|
2419
2740
|
if (patchResult.appliedPatches > 0) {
|
|
2420
|
-
console.warn(`[Apply-First] ${patchResult.appliedPatches}/${
|
|
2741
|
+
console.warn(`[Apply-First] ${patchResult.appliedPatches}/${patchesToApply.length} patches applied to ${mod.filePath}`);
|
|
2421
2742
|
modifiedContent = patchResult.modifiedContent;
|
|
2422
|
-
explanation += ` (${patchResult.appliedPatches}/${
|
|
2743
|
+
explanation += ` (${patchResult.appliedPatches}/${patchesToApply.length} patches applied)`;
|
|
2423
2744
|
} else {
|
|
2424
2745
|
continue; // Skip this file entirely if no patches worked
|
|
2425
2746
|
}
|
|
2426
2747
|
} else {
|
|
2427
2748
|
modifiedContent = patchResult.modifiedContent;
|
|
2428
|
-
console.log(`[Apply-First] All ${
|
|
2749
|
+
console.log(`[Apply-First] All ${patchesToApply.length} patches applied successfully to ${mod.filePath}`);
|
|
2429
2750
|
}
|
|
2430
2751
|
|
|
2431
2752
|
// AST VALIDATION: Use Babel parser to catch actual syntax errors
|
|
@@ -84,6 +84,40 @@ interface VisionEditResponse {
|
|
|
84
84
|
|
|
85
85
|
const DEBUG_LOG_FILE = "sonance-debug.log";
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* TWO-PHASE DESIGN ANALYSIS TYPES
|
|
89
|
+
* Phase 1 identifies problems, Phase 2 generates patches that reference those problems
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/** Categories of visual/UX problems that can be identified */
|
|
93
|
+
type DesignProblemCategory = 'hierarchy' | 'spacing' | 'alignment' | 'feedback' | 'grouping';
|
|
94
|
+
|
|
95
|
+
/** A specific visual problem identified in Phase 1 analysis */
|
|
96
|
+
interface DesignProblem {
|
|
97
|
+
/** Unique identifier (e.g., "hierarchy-1", "spacing-2") */
|
|
98
|
+
id: string;
|
|
99
|
+
/** Problem category */
|
|
100
|
+
category: DesignProblemCategory;
|
|
101
|
+
/** Specific description of the issue */
|
|
102
|
+
description: string;
|
|
103
|
+
/** Visual impact severity */
|
|
104
|
+
severity: 'high' | 'medium' | 'low';
|
|
105
|
+
/** Hint for what to look for in the code */
|
|
106
|
+
codeHint?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Extended patch that references a specific problem from Phase 1 */
|
|
110
|
+
interface DesignPatch {
|
|
111
|
+
/** Reference to the problem this patch fixes */
|
|
112
|
+
problemId: string;
|
|
113
|
+
/** Exact code to find */
|
|
114
|
+
search: string;
|
|
115
|
+
/** Replacement code */
|
|
116
|
+
replace: string;
|
|
117
|
+
/** Explanation of how this fixes the problem */
|
|
118
|
+
rationale: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
/**
|
|
88
122
|
* Debug logging utility - writes to sonance-debug.log in project root
|
|
89
123
|
* This helps diagnose issues with file discovery, validation, and revert
|
|
@@ -885,6 +919,139 @@ Be smart - use your knowledge of React patterns to identify what type of element
|
|
|
885
919
|
}
|
|
886
920
|
}
|
|
887
921
|
|
|
922
|
+
/**
|
|
923
|
+
* PHASE 1: Design Problem Analysis
|
|
924
|
+
*
|
|
925
|
+
* Analyzes the screenshot to identify specific visual/UX problems.
|
|
926
|
+
* This is a SEPARATE LLM call that ONLY identifies problems - no code changes.
|
|
927
|
+
* The problems are then passed to Phase 2 to generate targeted patches.
|
|
928
|
+
*/
|
|
929
|
+
async function analyzeDesignProblems(
|
|
930
|
+
screenshot: string,
|
|
931
|
+
fileContent: string,
|
|
932
|
+
userPrompt: string,
|
|
933
|
+
apiKey: string
|
|
934
|
+
): Promise<DesignProblem[]> {
|
|
935
|
+
const anthropic = new Anthropic({ apiKey });
|
|
936
|
+
const base64Data = screenshot.split(",")[1] || screenshot;
|
|
937
|
+
|
|
938
|
+
debugLog("Phase 1 Design Analysis: Starting visual problem identification", {
|
|
939
|
+
promptPreview: userPrompt?.substring(0, 50),
|
|
940
|
+
fileContentLength: fileContent.length
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
try {
|
|
944
|
+
const response = await anthropic.messages.create({
|
|
945
|
+
model: "claude-sonnet-4-20250514",
|
|
946
|
+
max_tokens: 2048,
|
|
947
|
+
messages: [
|
|
948
|
+
{
|
|
949
|
+
role: "user",
|
|
950
|
+
content: [
|
|
951
|
+
{
|
|
952
|
+
type: "image",
|
|
953
|
+
source: {
|
|
954
|
+
type: "base64",
|
|
955
|
+
media_type: "image/png",
|
|
956
|
+
data: base64Data,
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
type: "text",
|
|
961
|
+
text: `You are a senior UI/UX designer. Analyze this screenshot and identify SPECIFIC visual problems.
|
|
962
|
+
|
|
963
|
+
User's request: "${userPrompt}"
|
|
964
|
+
|
|
965
|
+
Examine the UI for issues in these categories:
|
|
966
|
+
|
|
967
|
+
1. HIERARCHY: Is there a clear visual focus? Are elements competing for attention?
|
|
968
|
+
- Look for: competing headings, unclear primary actions, visual clutter
|
|
969
|
+
|
|
970
|
+
2. SPACING: Are related elements grouped? Is there awkward empty space?
|
|
971
|
+
- Look for: disconnected labels/values, uneven gaps, cramped or overly spread content
|
|
972
|
+
|
|
973
|
+
3. ALIGNMENT: Do elements align naturally? Are there visual disconnects?
|
|
974
|
+
- Look for: misaligned text, floating elements, broken visual flow
|
|
975
|
+
|
|
976
|
+
4. FEEDBACK: Are progress indicators, states, and actions clear?
|
|
977
|
+
- Look for: weak progress visualization, unclear states, orphaned status indicators
|
|
978
|
+
|
|
979
|
+
5. GROUPING: Are related items visually connected? Do containers make sense?
|
|
980
|
+
- Look for: related content that appears separate, missing visual boundaries
|
|
981
|
+
|
|
982
|
+
For each problem found, provide:
|
|
983
|
+
- id: unique identifier (e.g., "hierarchy-1", "spacing-2")
|
|
984
|
+
- category: one of [hierarchy, spacing, alignment, feedback, grouping]
|
|
985
|
+
- description: specific description of what's wrong
|
|
986
|
+
- severity: high/medium/low based on visual impact
|
|
987
|
+
- codeHint: what to look for in the code to fix it
|
|
988
|
+
|
|
989
|
+
Return ONLY a JSON array of problems. Example:
|
|
990
|
+
[
|
|
991
|
+
{
|
|
992
|
+
"id": "grouping-1",
|
|
993
|
+
"category": "grouping",
|
|
994
|
+
"description": "The 'Essence Quality' label and '0% COMPLETE' badge appear visually disconnected - they should be grouped together",
|
|
995
|
+
"severity": "high",
|
|
996
|
+
"codeHint": "Check if label and badge are in separate containers or using absolute positioning"
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"id": "spacing-1",
|
|
1000
|
+
"category": "spacing",
|
|
1001
|
+
"description": "Too much vertical gap between the header section and the quality indicator",
|
|
1002
|
+
"severity": "medium",
|
|
1003
|
+
"codeHint": "Look for large margin or padding values like mt-6, mb-8, py-6"
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
|
|
1007
|
+
IMPORTANT:
|
|
1008
|
+
- Only identify problems that are actually visible in the screenshot
|
|
1009
|
+
- Be specific - vague problems lead to vague fixes
|
|
1010
|
+
- Maximum 5 problems - focus on the most impactful ones
|
|
1011
|
+
- If no significant problems are found, return an empty array []
|
|
1012
|
+
- Do NOT suggest code changes - only identify problems
|
|
1013
|
+
|
|
1014
|
+
Return ONLY the JSON array, no other text.`,
|
|
1015
|
+
},
|
|
1016
|
+
],
|
|
1017
|
+
},
|
|
1018
|
+
],
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
1022
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
1023
|
+
debugLog("Phase 1 Design Analysis: No text response from LLM");
|
|
1024
|
+
return [];
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
let jsonText = textBlock.text.trim();
|
|
1028
|
+
|
|
1029
|
+
// Extract JSON from code fences if present
|
|
1030
|
+
const jsonMatch = jsonText.match(/```(?:json)?\n?([\s\S]*?)\n?```/) ||
|
|
1031
|
+
jsonText.match(/\[[\s\S]*\]/);
|
|
1032
|
+
if (jsonMatch) {
|
|
1033
|
+
jsonText = jsonMatch[1] || jsonMatch[0];
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const problems: DesignProblem[] = JSON.parse(jsonText);
|
|
1037
|
+
|
|
1038
|
+
// Validate the structure
|
|
1039
|
+
const validatedProblems = problems.filter(p =>
|
|
1040
|
+
p.id && p.category && p.description && p.severity
|
|
1041
|
+
).slice(0, 5); // Maximum 5 problems
|
|
1042
|
+
|
|
1043
|
+
debugLog("Phase 1 Design Analysis: Identified problems", {
|
|
1044
|
+
count: validatedProblems.length,
|
|
1045
|
+
problems: validatedProblems.map(p => ({ id: p.id, category: p.category, severity: p.severity }))
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
return validatedProblems;
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
debugLog("Phase 1 Design Analysis: Failed to analyze", { error: String(e) });
|
|
1051
|
+
return [];
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
888
1055
|
/**
|
|
889
1056
|
* Search candidate files for JSX code matching the focused element
|
|
890
1057
|
* This helps identify which file actually contains the element the user clicked on
|
|
@@ -1338,65 +1505,166 @@ Output format:
|
|
|
1338
1505
|
The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
|
|
1339
1506
|
|
|
1340
1507
|
/**
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
* This
|
|
1508
|
+
* PHASE 2: Targeted Patch Generation Prompt
|
|
1509
|
+
*
|
|
1510
|
+
* This prompt is used AFTER Phase 1 has identified specific problems.
|
|
1511
|
+
* It requires each patch to reference a problem ID from Phase 1.
|
|
1344
1512
|
*/
|
|
1345
|
-
|
|
1513
|
+
function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
|
|
1514
|
+
if (problems.length === 0) {
|
|
1515
|
+
return ''; // No problems identified, skip design protocol
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const problemsJson = JSON.stringify(problems, null, 2);
|
|
1519
|
+
|
|
1520
|
+
return `
|
|
1346
1521
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1347
|
-
|
|
1522
|
+
PHASE 2: TARGETED PATCH GENERATION
|
|
1348
1523
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1349
1524
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1525
|
+
A visual analysis identified these specific problems in the UI:
|
|
1526
|
+
|
|
1527
|
+
${problemsJson}
|
|
1528
|
+
|
|
1529
|
+
YOUR TASK: Generate patches that fix ONLY these specific problems.
|
|
1530
|
+
|
|
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
|
|
1537
|
+
|
|
1538
|
+
For each problem, find the exact code that causes it and create a targeted fix.
|
|
1539
|
+
|
|
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
|
|
1372
1565
|
`;
|
|
1566
|
+
}
|
|
1373
1567
|
|
|
1374
1568
|
/**
|
|
1375
|
-
* DESIGN
|
|
1376
|
-
* Explicit constraints to prevent the LLM from over-engineering design changes.
|
|
1569
|
+
* DESIGN DECISION RULES for Phase 2
|
|
1377
1570
|
*/
|
|
1378
1571
|
const DESIGN_GUARDRAILS = `
|
|
1379
1572
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1380
|
-
DESIGN
|
|
1573
|
+
DESIGN CONSTRAINTS
|
|
1381
1574
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1382
1575
|
|
|
1383
|
-
|
|
1384
|
-
•
|
|
1385
|
-
•
|
|
1386
|
-
•
|
|
1387
|
-
•
|
|
1388
|
-
•
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
•
|
|
1393
|
-
•
|
|
1394
|
-
•
|
|
1395
|
-
•
|
|
1396
|
-
|
|
1397
|
-
|
|
1576
|
+
✓ ALLOWED when fixing identified problems:
|
|
1577
|
+
• Adjust spacing (margin, padding) - e.g., mt-4 → mt-2
|
|
1578
|
+
• Adjust alignment within containers - e.g., items-start → items-center
|
|
1579
|
+
• Move elements within the same container to improve grouping
|
|
1580
|
+
• Adjust typography - font size, weight, line-height
|
|
1581
|
+
• Add/remove whitespace classes
|
|
1582
|
+
|
|
1583
|
+
✗ FORBIDDEN (will cause rejection):
|
|
1584
|
+
• Changing layout system (flex → grid, grid → flex)
|
|
1585
|
+
• Restructuring component hierarchy
|
|
1586
|
+
• Wrapping elements in new containers (unless specifically needed for a grouping problem)
|
|
1587
|
+
• Removing or changing functionality
|
|
1588
|
+
• Changes without a problemId reference
|
|
1589
|
+
|
|
1590
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
1398
1591
|
`;
|
|
1399
1592
|
|
|
1593
|
+
/**
|
|
1594
|
+
* Validate patches against identified problems
|
|
1595
|
+
* Rejects patches that don't reference a valid problem ID
|
|
1596
|
+
*/
|
|
1597
|
+
function validateDesignPatches(
|
|
1598
|
+
patches: Array<{ search: string; replace: string; problemId?: string; rationale?: string }>,
|
|
1599
|
+
problems: DesignProblem[]
|
|
1600
|
+
): { valid: Array<{ search: string; replace: string; problemId: string; rationale: string }>; rejected: Array<{ patch: unknown; reason: string }> } {
|
|
1601
|
+
const validProblemIds = new Set(problems.map(p => p.id));
|
|
1602
|
+
const valid: Array<{ search: string; replace: string; problemId: string; rationale: string }> = [];
|
|
1603
|
+
const rejected: Array<{ patch: unknown; reason: string }> = [];
|
|
1604
|
+
|
|
1605
|
+
for (const patch of patches) {
|
|
1606
|
+
// Check for required fields
|
|
1607
|
+
if (!patch.search || !patch.replace) {
|
|
1608
|
+
rejected.push({ patch, reason: "Missing search or replace field" });
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// For design-heavy requests, require problemId
|
|
1613
|
+
if (!patch.problemId) {
|
|
1614
|
+
rejected.push({ patch, reason: "Missing problemId - patch doesn't reference an identified problem" });
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// Validate problemId exists in the problems list
|
|
1619
|
+
if (!validProblemIds.has(patch.problemId)) {
|
|
1620
|
+
rejected.push({ patch, reason: `Invalid problemId "${patch.problemId}" - not in identified problems list` });
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Check for forbidden structural changes
|
|
1625
|
+
const searchLower = patch.search.toLowerCase();
|
|
1626
|
+
const replaceLower = patch.replace.toLowerCase();
|
|
1627
|
+
|
|
1628
|
+
// Detect flex → grid or grid → flex conversions
|
|
1629
|
+
const hadFlex = searchLower.includes('flex') || searchLower.includes('items-') || searchLower.includes('justify-');
|
|
1630
|
+
const hasGrid = replaceLower.includes('grid') && !searchLower.includes('grid');
|
|
1631
|
+
const hadGrid = searchLower.includes('grid');
|
|
1632
|
+
const hasFlex = (replaceLower.includes('flex') || replaceLower.includes('items-') || replaceLower.includes('justify-')) && !hadFlex;
|
|
1633
|
+
|
|
1634
|
+
if ((hadFlex && hasGrid) || (hadGrid && hasFlex)) {
|
|
1635
|
+
rejected.push({ patch, reason: "Forbidden: Changing layout system (flex ↔ grid)" });
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Detect space-y/space-x additions that fundamentally change layout
|
|
1640
|
+
const addedSpaceY = replaceLower.includes('space-y') && !searchLower.includes('space-y');
|
|
1641
|
+
const addedSpaceX = replaceLower.includes('space-x') && !searchLower.includes('space-x');
|
|
1642
|
+
const hadJustifyBetween = searchLower.includes('justify-between');
|
|
1643
|
+
|
|
1644
|
+
if ((addedSpaceY || addedSpaceX) && hadJustifyBetween) {
|
|
1645
|
+
rejected.push({ patch, reason: "Forbidden: Converting flex layout to stack layout" });
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
valid.push({
|
|
1650
|
+
search: patch.search,
|
|
1651
|
+
replace: patch.replace,
|
|
1652
|
+
problemId: patch.problemId,
|
|
1653
|
+
rationale: patch.rationale || 'No rationale provided'
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if (rejected.length > 0) {
|
|
1658
|
+
debugLog("Design patch validation: Some patches rejected", {
|
|
1659
|
+
validCount: valid.length,
|
|
1660
|
+
rejectedCount: rejected.length,
|
|
1661
|
+
rejected: rejected.map(r => ({ reason: r.reason, searchPreview: String(r.patch).substring(0, 50) }))
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
return { valid, rejected };
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1400
1668
|
/**
|
|
1401
1669
|
* Detect if a user request is design-heavy and needs the reasoning protocol.
|
|
1402
1670
|
* Simple requests like "change button color to blue" don't need it.
|
|
@@ -1796,21 +2064,42 @@ User Request: "${userPrompt}"
|
|
|
1796
2064
|
|
|
1797
2065
|
`;
|
|
1798
2066
|
|
|
1799
|
-
// ========== DESIGN
|
|
1800
|
-
//
|
|
1801
|
-
//
|
|
2067
|
+
// ========== TWO-PHASE DESIGN ANALYSIS ==========
|
|
2068
|
+
// Phase 1: Identify specific visual problems (separate LLM call)
|
|
2069
|
+
// Phase 2: Generate patches that reference those problems
|
|
1802
2070
|
const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
|
|
2071
|
+
let identifiedProblems: DesignProblem[] = [];
|
|
1803
2072
|
|
|
1804
|
-
if (isDesignRequest) {
|
|
1805
|
-
debugLog("Design-heavy request detected,
|
|
2073
|
+
if (isDesignRequest && screenshot) {
|
|
2074
|
+
debugLog("Design-heavy request detected, running Phase 1 analysis", {
|
|
1806
2075
|
prompt: userPrompt?.substring(0, 50),
|
|
1807
2076
|
triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
|
|
1808
2077
|
.filter(k => userPrompt?.toLowerCase().includes(k))
|
|
1809
2078
|
});
|
|
1810
2079
|
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2080
|
+
// Phase 1: Analyze the screenshot to identify specific problems
|
|
2081
|
+
// This is a SEPARATE LLM call that only identifies problems
|
|
2082
|
+
const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
|
|
2083
|
+
identifiedProblems = await analyzeDesignProblems(
|
|
2084
|
+
screenshot,
|
|
2085
|
+
fileContentForAnalysis,
|
|
2086
|
+
userPrompt || '',
|
|
2087
|
+
apiKey
|
|
2088
|
+
);
|
|
2089
|
+
|
|
2090
|
+
debugLog("Phase 1 complete: Problems identified", {
|
|
2091
|
+
problemCount: identifiedProblems.length,
|
|
2092
|
+
problems: identifiedProblems.map(p => `${p.id}: ${p.description.substring(0, 50)}`)
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
// Add Phase 2 prompt with the identified problems
|
|
2096
|
+
if (identifiedProblems.length > 0) {
|
|
2097
|
+
textContent += buildPhase2DesignPrompt(identifiedProblems);
|
|
2098
|
+
textContent += DESIGN_GUARDRAILS;
|
|
2099
|
+
textContent += '\n';
|
|
2100
|
+
} else {
|
|
2101
|
+
debugLog("No problems identified - proceeding with standard edit");
|
|
2102
|
+
}
|
|
1814
2103
|
}
|
|
1815
2104
|
|
|
1816
2105
|
// ========== TARGET COMPONENT ONLY (with line numbers) ==========
|
|
@@ -2339,9 +2628,46 @@ This is better than generating patches with made-up code.`,
|
|
|
2339
2628
|
// New patch-based approach
|
|
2340
2629
|
console.log(`[Vision Mode] Applying ${mod.patches.length} patches to ${mod.filePath}`);
|
|
2341
2630
|
|
|
2631
|
+
// DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
|
|
2632
|
+
let patchesToApply = mod.patches;
|
|
2633
|
+
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", {
|
|
2655
|
+
filePath: mod.filePath,
|
|
2656
|
+
problemsBeingFixed,
|
|
2657
|
+
patchCount: validationResult.valid.length
|
|
2658
|
+
});
|
|
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
|
+
}
|
|
2667
|
+
|
|
2342
2668
|
// PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
|
|
2343
2669
|
const preValidationErrors: string[] = [];
|
|
2344
|
-
for (const patch of
|
|
2670
|
+
for (const patch of patchesToApply) {
|
|
2345
2671
|
const normalizedSearch = patch.search.replace(/\\n/g, "\n");
|
|
2346
2672
|
if (!originalContent.includes(normalizedSearch)) {
|
|
2347
2673
|
// Try fuzzy match as fallback
|
|
@@ -2380,7 +2706,7 @@ This is better than generating patches with made-up code.`,
|
|
|
2380
2706
|
continue;
|
|
2381
2707
|
}
|
|
2382
2708
|
|
|
2383
|
-
const patchResult = applyPatches(originalContent,
|
|
2709
|
+
const patchResult = applyPatches(originalContent, patchesToApply);
|
|
2384
2710
|
|
|
2385
2711
|
if (!patchResult.success) {
|
|
2386
2712
|
const failedMessages = patchResult.failedPatches.map(
|
|
@@ -2390,15 +2716,15 @@ This is better than generating patches with made-up code.`,
|
|
|
2390
2716
|
|
|
2391
2717
|
// If some patches succeeded, use partial result
|
|
2392
2718
|
if (patchResult.appliedPatches > 0) {
|
|
2393
|
-
console.warn(`[Vision Mode] ${patchResult.appliedPatches}/${
|
|
2719
|
+
console.warn(`[Vision Mode] ${patchResult.appliedPatches}/${patchesToApply.length} patches applied to ${mod.filePath}`);
|
|
2394
2720
|
modifiedContent = patchResult.modifiedContent;
|
|
2395
|
-
explanation += ` (${patchResult.appliedPatches}/${
|
|
2721
|
+
explanation += ` (${patchResult.appliedPatches}/${patchesToApply.length} patches applied)`;
|
|
2396
2722
|
} else {
|
|
2397
2723
|
continue; // Skip this file entirely if no patches worked
|
|
2398
2724
|
}
|
|
2399
2725
|
} else {
|
|
2400
2726
|
modifiedContent = patchResult.modifiedContent;
|
|
2401
|
-
console.log(`[Vision Mode] All ${
|
|
2727
|
+
console.log(`[Vision Mode] All ${patchesToApply.length} patches applied successfully to ${mod.filePath}`);
|
|
2402
2728
|
}
|
|
2403
2729
|
|
|
2404
2730
|
// AST VALIDATION: Use Babel parser to catch actual syntax errors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.100",
|
|
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",
|