sonance-brand-mcp 1.3.63 → 1.3.65
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.
|
@@ -4,6 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import { discoverTheme } from "./theme-discovery";
|
|
7
|
+
import * as babelParser from "@babel/parser";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Sonance DevTools API - Apply-First Vision Mode
|
|
@@ -29,6 +30,10 @@ interface VisionFocusedElement {
|
|
|
29
30
|
height: number;
|
|
30
31
|
};
|
|
31
32
|
description?: string;
|
|
33
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
34
|
+
textContent?: string;
|
|
35
|
+
/** The className string of the element for pattern matching */
|
|
36
|
+
className?: string;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
interface VisionFileModification {
|
|
@@ -82,6 +87,39 @@ function debugLog(message: string, data?: unknown) {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
/**
|
|
91
|
+
* AST-based syntax validation using Babel parser
|
|
92
|
+
* This catches actual syntax errors, not just tag counting
|
|
93
|
+
*/
|
|
94
|
+
function validateSyntaxWithAST(content: string, filePath: string): { valid: boolean; error?: string } {
|
|
95
|
+
// Only validate JSX/TSX files
|
|
96
|
+
if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx') && !filePath.endsWith('.ts') && !filePath.endsWith('.js')) {
|
|
97
|
+
return { valid: true };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const isTypeScript = filePath.endsWith('.tsx') || filePath.endsWith('.ts');
|
|
102
|
+
const hasJsx = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
|
|
103
|
+
|
|
104
|
+
const plugins: babelParser.ParserPlugin[] = [];
|
|
105
|
+
if (isTypeScript) plugins.push('typescript');
|
|
106
|
+
if (hasJsx) plugins.push('jsx');
|
|
107
|
+
|
|
108
|
+
babelParser.parse(content, {
|
|
109
|
+
sourceType: 'module',
|
|
110
|
+
plugins,
|
|
111
|
+
});
|
|
112
|
+
return { valid: true };
|
|
113
|
+
} catch (e: unknown) {
|
|
114
|
+
const error = e as { message?: string; loc?: { line: number; column: number } };
|
|
115
|
+
const location = error.loc ? ` at line ${error.loc.line}, column ${error.loc.column}` : '';
|
|
116
|
+
return {
|
|
117
|
+
valid: false,
|
|
118
|
+
error: `Syntax error in ${filePath}${location}: ${error.message || 'Unknown parse error'}`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
85
123
|
/**
|
|
86
124
|
* Result of LLM screenshot analysis for smart file discovery
|
|
87
125
|
*/
|
|
@@ -176,10 +214,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
176
214
|
* This helps identify which file actually contains the element the user clicked on
|
|
177
215
|
*
|
|
178
216
|
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
217
|
+
*
|
|
218
|
+
* @param phase2aMatches - File paths that Phase 2a identified by component name matching visible text
|
|
219
|
+
* These get a MASSIVE bonus because they're contextually relevant
|
|
179
220
|
*/
|
|
180
221
|
function findFilesContainingElement(
|
|
181
222
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
182
|
-
candidateFiles: { path: string; content: string }[]
|
|
223
|
+
candidateFiles: { path: string; content: string }[],
|
|
224
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
183
225
|
): { path: string; score: number; matches: string[] }[] {
|
|
184
226
|
if (!focusedElements || focusedElements.length === 0) {
|
|
185
227
|
return [];
|
|
@@ -191,7 +233,13 @@ function findFilesContainingElement(
|
|
|
191
233
|
let score = 0;
|
|
192
234
|
const matches: string[] = [];
|
|
193
235
|
const content = file.content;
|
|
194
|
-
|
|
236
|
+
|
|
237
|
+
// HIGHEST VALUE: Phase 2a identified this file by matching component name to visible text
|
|
238
|
+
// This means the LLM saw "ProcessRow" in the screenshot and this file is ProcessRow.tsx
|
|
239
|
+
if (phase2aMatches.includes(file.path)) {
|
|
240
|
+
score += 200;
|
|
241
|
+
matches.push('Phase2a component match');
|
|
242
|
+
}
|
|
195
243
|
|
|
196
244
|
for (const el of focusedElements) {
|
|
197
245
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
@@ -216,28 +264,42 @@ function findFilesContainingElement(
|
|
|
216
264
|
matches.push(`defines ${capitalizedType}`);
|
|
217
265
|
}
|
|
218
266
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
267
|
+
// NOTE: We intentionally DO NOT count buttons, onClick handlers, or JSX tags
|
|
268
|
+
// because files with MORE of these are often "kitchen sink" components,
|
|
269
|
+
// not the focused component the user is looking at.
|
|
270
|
+
// Instead, we rely on Phase 2a matches and textContent matching.
|
|
271
|
+
|
|
272
|
+
// VERY HIGH VALUE: Exact text content match (e.g., button says "REFRESH")
|
|
273
|
+
// This is THE file if it contains the exact text the user clicked on
|
|
274
|
+
if (el.textContent && el.textContent.length >= 2) {
|
|
275
|
+
// Search for the text in JSX patterns like >REFRESH< or {`REFRESH`} or {"REFRESH"}
|
|
276
|
+
const textPatterns = [
|
|
277
|
+
`>${el.textContent}<`, // JSX text content
|
|
278
|
+
`"${el.textContent}"`, // String literal
|
|
279
|
+
`'${el.textContent}'`, // Single-quoted string
|
|
280
|
+
`\`${el.textContent}\``, // Template literal
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
for (const pattern of textPatterns) {
|
|
284
|
+
if (content.includes(pattern)) {
|
|
285
|
+
score += 150; // Very high - exact match!
|
|
286
|
+
matches.push(`contains "${el.textContent.substring(0, 20)}"`);
|
|
287
|
+
break; // Only add once per element
|
|
288
|
+
}
|
|
289
|
+
}
|
|
233
290
|
}
|
|
234
291
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
292
|
+
// MEDIUM VALUE: className pattern match
|
|
293
|
+
// If the file contains the same className string, it likely defines this element
|
|
294
|
+
if (el.className && el.className.length >= 10) {
|
|
295
|
+
// Extract the first few unique class names (not all to avoid noise)
|
|
296
|
+
const classNames = el.className.split(/\s+/).filter(c => c.length > 3).slice(0, 5);
|
|
297
|
+
for (const cls of classNames) {
|
|
298
|
+
if (content.includes(cls)) {
|
|
299
|
+
score += 25;
|
|
300
|
+
matches.push(`has class "${cls.substring(0, 20)}"`);
|
|
301
|
+
break; // Only add once per element
|
|
302
|
+
}
|
|
241
303
|
}
|
|
242
304
|
}
|
|
243
305
|
|
|
@@ -412,7 +474,7 @@ function searchFilesSmart(
|
|
|
412
474
|
analysis: ScreenshotAnalysis,
|
|
413
475
|
projectRoot: string,
|
|
414
476
|
maxFiles: number = 10
|
|
415
|
-
): { path: string; content: string; score: number }[] {
|
|
477
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
416
478
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
417
479
|
|
|
418
480
|
// Combine all search terms
|
|
@@ -534,7 +596,7 @@ function searchFilesSmart(
|
|
|
534
596
|
}))
|
|
535
597
|
});
|
|
536
598
|
|
|
537
|
-
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
599
|
+
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score, filenameMatch: r.filenameMatch }));
|
|
538
600
|
}
|
|
539
601
|
|
|
540
602
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -678,10 +740,19 @@ export async function POST(request: Request) {
|
|
|
678
740
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
679
741
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
680
742
|
|
|
743
|
+
// Extract Phase 2a matches - files identified by component name matching visible text
|
|
744
|
+
// These are contextually relevant (e.g., "ProcessRow" seen in screenshot -> ProcessRow.tsx)
|
|
745
|
+
const phase2aMatches = searchResults
|
|
746
|
+
.filter(r => r.filenameMatch)
|
|
747
|
+
.map(r => r.path);
|
|
748
|
+
|
|
749
|
+
debugLog("Phase 2a component-name matches", { phase2aMatches });
|
|
750
|
+
|
|
681
751
|
// PHASE 2.5: Find which files contain the focused element
|
|
682
752
|
const focusedElementHints = findFilesContainingElement(
|
|
683
753
|
focusedElements,
|
|
684
|
-
searchResults.map(r => ({ path: r.path, content: r.content }))
|
|
754
|
+
searchResults.map(r => ({ path: r.path, content: r.content })),
|
|
755
|
+
phase2aMatches // Pass Phase 2a matches for massive bonus
|
|
685
756
|
);
|
|
686
757
|
|
|
687
758
|
if (focusedElementHints.length > 0) {
|
|
@@ -1199,32 +1270,17 @@ This is better than generating patches with made-up code.`,
|
|
|
1199
1270
|
console.log(`[Apply-First] All ${mod.patches.length} patches applied successfully to ${mod.filePath}`);
|
|
1200
1271
|
}
|
|
1201
1272
|
|
|
1202
|
-
//
|
|
1203
|
-
// This
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
divs: { open: openDivs, close: closeDivs },
|
|
1214
|
-
spans: { open: openSpans, close: closeSpans },
|
|
1215
|
-
});
|
|
1216
|
-
console.warn(`[Apply-First] ⚠️ SYNTAX WARNING: Tag mismatch in ${mod.filePath}`);
|
|
1217
|
-
console.warn(`[Apply-First] divs: ${openDivs} open, ${closeDivs} close`);
|
|
1218
|
-
console.warn(`[Apply-First] spans: ${openSpans} open, ${closeSpans} close`);
|
|
1219
|
-
|
|
1220
|
-
// If there's a significant mismatch, reject the change
|
|
1221
|
-
const divDiff = Math.abs(openDivs - closeDivs);
|
|
1222
|
-
const spanDiff = Math.abs(openSpans - closeSpans);
|
|
1223
|
-
if (divDiff > 0 || spanDiff > 0) {
|
|
1224
|
-
patchErrors.push(`${mod.filePath}: LLM introduced syntax error - tag mismatch detected (${divDiff} div, ${spanDiff} span). Change rejected.`);
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1273
|
+
// AST VALIDATION: Use Babel parser to catch actual syntax errors
|
|
1274
|
+
// This is more accurate than tag counting and catches real issues
|
|
1275
|
+
const syntaxValidation = validateSyntaxWithAST(modifiedContent, mod.filePath);
|
|
1276
|
+
if (!syntaxValidation.valid) {
|
|
1277
|
+
debugLog("SYNTAX ERROR: AST validation failed", {
|
|
1278
|
+
filePath: mod.filePath,
|
|
1279
|
+
error: syntaxValidation.error,
|
|
1280
|
+
});
|
|
1281
|
+
console.warn(`[Apply-First] ⚠️ SYNTAX ERROR: ${syntaxValidation.error}`);
|
|
1282
|
+
patchErrors.push(`${mod.filePath}: ${syntaxValidation.error}`);
|
|
1283
|
+
continue; // Skip this file, trigger retry
|
|
1228
1284
|
}
|
|
1229
1285
|
} else {
|
|
1230
1286
|
// No patches - skip
|
|
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
5
5
|
import { discoverTheme } from "../sonance-vision-apply/theme-discovery";
|
|
6
|
+
import * as babelParser from "@babel/parser";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Sonance DevTools API - Vision Mode Editor
|
|
@@ -28,6 +29,10 @@ interface VisionFocusedElement {
|
|
|
28
29
|
height: number;
|
|
29
30
|
};
|
|
30
31
|
description?: string;
|
|
32
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
33
|
+
textContent?: string;
|
|
34
|
+
/** The className string of the element for pattern matching */
|
|
35
|
+
className?: string;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
interface VisionFileModification {
|
|
@@ -78,6 +83,39 @@ function debugLog(message: string, data?: unknown) {
|
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/**
|
|
87
|
+
* AST-based syntax validation using Babel parser
|
|
88
|
+
* This catches actual syntax errors, not just tag counting
|
|
89
|
+
*/
|
|
90
|
+
function validateSyntaxWithAST(content: string, filePath: string): { valid: boolean; error?: string } {
|
|
91
|
+
// Only validate JSX/TSX files
|
|
92
|
+
if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx') && !filePath.endsWith('.ts') && !filePath.endsWith('.js')) {
|
|
93
|
+
return { valid: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const isTypeScript = filePath.endsWith('.tsx') || filePath.endsWith('.ts');
|
|
98
|
+
const hasJsx = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
|
|
99
|
+
|
|
100
|
+
const plugins: babelParser.ParserPlugin[] = [];
|
|
101
|
+
if (isTypeScript) plugins.push('typescript');
|
|
102
|
+
if (hasJsx) plugins.push('jsx');
|
|
103
|
+
|
|
104
|
+
babelParser.parse(content, {
|
|
105
|
+
sourceType: 'module',
|
|
106
|
+
plugins,
|
|
107
|
+
});
|
|
108
|
+
return { valid: true };
|
|
109
|
+
} catch (e: unknown) {
|
|
110
|
+
const error = e as { message?: string; loc?: { line: number; column: number } };
|
|
111
|
+
const location = error.loc ? ` at line ${error.loc.line}, column ${error.loc.column}` : '';
|
|
112
|
+
return {
|
|
113
|
+
valid: false,
|
|
114
|
+
error: `Syntax error in ${filePath}${location}: ${error.message || 'Unknown parse error'}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
/**
|
|
82
120
|
* Result of LLM screenshot analysis for smart file discovery
|
|
83
121
|
*/
|
|
@@ -172,10 +210,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
172
210
|
* This helps identify which file actually contains the element the user clicked on
|
|
173
211
|
*
|
|
174
212
|
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
213
|
+
*
|
|
214
|
+
* @param phase2aMatches - File paths that Phase 2a identified by component name matching visible text
|
|
215
|
+
* These get a MASSIVE bonus because they're contextually relevant
|
|
175
216
|
*/
|
|
176
217
|
function findFilesContainingElement(
|
|
177
218
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
178
|
-
candidateFiles: { path: string; content: string }[]
|
|
219
|
+
candidateFiles: { path: string; content: string }[],
|
|
220
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
179
221
|
): { path: string; score: number; matches: string[] }[] {
|
|
180
222
|
if (!focusedElements || focusedElements.length === 0) {
|
|
181
223
|
return [];
|
|
@@ -187,7 +229,13 @@ function findFilesContainingElement(
|
|
|
187
229
|
let score = 0;
|
|
188
230
|
const matches: string[] = [];
|
|
189
231
|
const content = file.content;
|
|
190
|
-
|
|
232
|
+
|
|
233
|
+
// HIGHEST VALUE: Phase 2a identified this file by matching component name to visible text
|
|
234
|
+
// This means the LLM saw "ProcessRow" in the screenshot and this file is ProcessRow.tsx
|
|
235
|
+
if (phase2aMatches.includes(file.path)) {
|
|
236
|
+
score += 200;
|
|
237
|
+
matches.push('Phase2a component match');
|
|
238
|
+
}
|
|
191
239
|
|
|
192
240
|
for (const el of focusedElements) {
|
|
193
241
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
@@ -212,28 +260,42 @@ function findFilesContainingElement(
|
|
|
212
260
|
matches.push(`defines ${capitalizedType}`);
|
|
213
261
|
}
|
|
214
262
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
263
|
+
// NOTE: We intentionally DO NOT count buttons, onClick handlers, or JSX tags
|
|
264
|
+
// because files with MORE of these are often "kitchen sink" components,
|
|
265
|
+
// not the focused component the user is looking at.
|
|
266
|
+
// Instead, we rely on Phase 2a matches and textContent matching.
|
|
267
|
+
|
|
268
|
+
// VERY HIGH VALUE: Exact text content match (e.g., button says "REFRESH")
|
|
269
|
+
// This is THE file if it contains the exact text the user clicked on
|
|
270
|
+
if (el.textContent && el.textContent.length >= 2) {
|
|
271
|
+
// Search for the text in JSX patterns like >REFRESH< or {`REFRESH`} or {"REFRESH"}
|
|
272
|
+
const textPatterns = [
|
|
273
|
+
`>${el.textContent}<`, // JSX text content
|
|
274
|
+
`"${el.textContent}"`, // String literal
|
|
275
|
+
`'${el.textContent}'`, // Single-quoted string
|
|
276
|
+
`\`${el.textContent}\``, // Template literal
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
for (const pattern of textPatterns) {
|
|
280
|
+
if (content.includes(pattern)) {
|
|
281
|
+
score += 150; // Very high - exact match!
|
|
282
|
+
matches.push(`contains "${el.textContent.substring(0, 20)}"`);
|
|
283
|
+
break; // Only add once per element
|
|
284
|
+
}
|
|
285
|
+
}
|
|
229
286
|
}
|
|
230
287
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
288
|
+
// MEDIUM VALUE: className pattern match
|
|
289
|
+
// If the file contains the same className string, it likely defines this element
|
|
290
|
+
if (el.className && el.className.length >= 10) {
|
|
291
|
+
// Extract the first few unique class names (not all to avoid noise)
|
|
292
|
+
const classNames = el.className.split(/\s+/).filter(c => c.length > 3).slice(0, 5);
|
|
293
|
+
for (const cls of classNames) {
|
|
294
|
+
if (content.includes(cls)) {
|
|
295
|
+
score += 25;
|
|
296
|
+
matches.push(`has class "${cls.substring(0, 20)}"`);
|
|
297
|
+
break; // Only add once per element
|
|
298
|
+
}
|
|
237
299
|
}
|
|
238
300
|
}
|
|
239
301
|
|
|
@@ -408,7 +470,7 @@ function searchFilesSmart(
|
|
|
408
470
|
analysis: ScreenshotAnalysis,
|
|
409
471
|
projectRoot: string,
|
|
410
472
|
maxFiles: number = 10
|
|
411
|
-
): { path: string; content: string; score: number }[] {
|
|
473
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
412
474
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
413
475
|
|
|
414
476
|
// Combine all search terms
|
|
@@ -530,7 +592,7 @@ function searchFilesSmart(
|
|
|
530
592
|
}))
|
|
531
593
|
});
|
|
532
594
|
|
|
533
|
-
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
595
|
+
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score, filenameMatch: r.filenameMatch }));
|
|
534
596
|
}
|
|
535
597
|
|
|
536
598
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -647,10 +709,19 @@ export async function POST(request: Request) {
|
|
|
647
709
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
648
710
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
649
711
|
|
|
712
|
+
// Extract Phase 2a matches - files identified by component name matching visible text
|
|
713
|
+
// These are contextually relevant (e.g., "ProcessRow" seen in screenshot -> ProcessRow.tsx)
|
|
714
|
+
const phase2aMatches = searchResults
|
|
715
|
+
.filter(r => r.filenameMatch)
|
|
716
|
+
.map(r => r.path);
|
|
717
|
+
|
|
718
|
+
debugLog("Phase 2a component-name matches", { phase2aMatches });
|
|
719
|
+
|
|
650
720
|
// PHASE 2.5: Find which files contain the focused element
|
|
651
721
|
const focusedElementHints = findFilesContainingElement(
|
|
652
722
|
focusedElements,
|
|
653
|
-
searchResults.map(r => ({ path: r.path, content: r.content }))
|
|
723
|
+
searchResults.map(r => ({ path: r.path, content: r.content })),
|
|
724
|
+
phase2aMatches // Pass Phase 2a matches for massive bonus
|
|
654
725
|
);
|
|
655
726
|
|
|
656
727
|
if (focusedElementHints.length > 0) {
|
|
@@ -1172,6 +1243,18 @@ This is better than generating patches with made-up code.`,
|
|
|
1172
1243
|
modifiedContent = patchResult.modifiedContent;
|
|
1173
1244
|
console.log(`[Vision Mode] All ${mod.patches.length} patches applied successfully to ${mod.filePath}`);
|
|
1174
1245
|
}
|
|
1246
|
+
|
|
1247
|
+
// AST VALIDATION: Use Babel parser to catch actual syntax errors
|
|
1248
|
+
const syntaxValidation = validateSyntaxWithAST(modifiedContent, mod.filePath);
|
|
1249
|
+
if (!syntaxValidation.valid) {
|
|
1250
|
+
debugLog("SYNTAX ERROR: AST validation failed", {
|
|
1251
|
+
filePath: mod.filePath,
|
|
1252
|
+
error: syntaxValidation.error,
|
|
1253
|
+
});
|
|
1254
|
+
console.warn(`[Vision Mode] ⚠️ SYNTAX ERROR: ${syntaxValidation.error}`);
|
|
1255
|
+
patchErrors.push(`${mod.filePath}: ${syntaxValidation.error}`);
|
|
1256
|
+
continue; // Skip this file, trigger retry
|
|
1257
|
+
}
|
|
1175
1258
|
} else {
|
|
1176
1259
|
// No patches - skip
|
|
1177
1260
|
console.warn(`[Vision Mode] No patches for ${mod.filePath}`);
|
|
@@ -787,7 +787,11 @@ export function SonanceDevTools() {
|
|
|
787
787
|
el.setAttribute("data-sonance-variant", variantId);
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
-
|
|
790
|
+
// Capture textContent and className for dynamic element matching
|
|
791
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100); // Cap at 100 chars
|
|
792
|
+
const className = el.className?.toString() || "";
|
|
793
|
+
|
|
794
|
+
newTagged.push({ name, rect, type: "component", variantId, textContent, className });
|
|
791
795
|
}
|
|
792
796
|
}
|
|
793
797
|
});
|
|
@@ -826,7 +830,11 @@ export function SonanceDevTools() {
|
|
|
826
830
|
el.setAttribute("data-sonance-name", genericName);
|
|
827
831
|
}
|
|
828
832
|
|
|
829
|
-
|
|
833
|
+
// Capture textContent and className for dynamic element matching
|
|
834
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100); // Cap at 100 chars
|
|
835
|
+
const elClassName = el.className?.toString() || "";
|
|
836
|
+
|
|
837
|
+
newTagged.push({ name: genericName, rect, type: "component", variantId, textContent, className: elClassName });
|
|
830
838
|
}
|
|
831
839
|
});
|
|
832
840
|
});
|
|
@@ -1017,6 +1025,9 @@ export function SonanceDevTools() {
|
|
|
1017
1025
|
width: element.rect.width,
|
|
1018
1026
|
height: element.rect.height,
|
|
1019
1027
|
},
|
|
1028
|
+
// NEW: Capture text and className for dynamic file matching
|
|
1029
|
+
textContent: element.textContent,
|
|
1030
|
+
className: element.className,
|
|
1020
1031
|
};
|
|
1021
1032
|
|
|
1022
1033
|
setVisionFocusedElements((prev) => {
|
|
@@ -22,8 +22,10 @@ export interface DetectedElement {
|
|
|
22
22
|
logoId?: string;
|
|
23
23
|
/** Unique ID for text elements (for selection/editing) */
|
|
24
24
|
textId?: string;
|
|
25
|
-
/** The actual text content (for text
|
|
25
|
+
/** The actual text content (for all elements - button text, link text, etc.) */
|
|
26
26
|
textContent?: string;
|
|
27
|
+
/** The className string for pattern matching in code */
|
|
28
|
+
className?: string;
|
|
27
29
|
/** Component variant ID (hash of styles/classes) to distinguish visual styles */
|
|
28
30
|
variantId?: string;
|
|
29
31
|
}
|
|
@@ -220,6 +222,10 @@ export interface VisionFocusedElement {
|
|
|
220
222
|
height: number;
|
|
221
223
|
};
|
|
222
224
|
description?: string;
|
|
225
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
226
|
+
textContent?: string;
|
|
227
|
+
/** The className string of the element for pattern matching */
|
|
228
|
+
className?: string;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
export interface VisionEditRequest {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.65",
|
|
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",
|