sonance-brand-mcp 1.3.88 → 1.3.89
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.
|
@@ -229,6 +229,109 @@ function findElementIdInFile(
|
|
|
229
229
|
return null;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* PHASE 0: Deterministic Element ID Search (Cursor-style)
|
|
234
|
+
* Grep entire codebase for the element ID. If found in multiple files,
|
|
235
|
+
* use the current route to disambiguate (prioritize page file for route).
|
|
236
|
+
*
|
|
237
|
+
* This is the most reliable signal - like Cursor knowing which file you're in.
|
|
238
|
+
* Element IDs should be unique, so if we find one, that's THE file.
|
|
239
|
+
*/
|
|
240
|
+
function findFilesByElementId(
|
|
241
|
+
projectRoot: string,
|
|
242
|
+
elementId: string,
|
|
243
|
+
currentRoute: string,
|
|
244
|
+
discoverPageFileFn: (route: string, projectRoot: string) => string | null
|
|
245
|
+
): { path: string; lineNumber: number; isRouteMatch: boolean }[] {
|
|
246
|
+
const pattern = new RegExp(`id=["'\`]${elementId}["'\`]`);
|
|
247
|
+
const matches: { path: string; lineNumber: number; isRouteMatch: boolean }[] = [];
|
|
248
|
+
|
|
249
|
+
// Determine expected page file from route
|
|
250
|
+
const routePageFile = discoverPageFileFn(currentRoute, projectRoot);
|
|
251
|
+
|
|
252
|
+
// Search all common project directories that exist
|
|
253
|
+
// This supports: src/app, app/, pages/, components/, lib/ structures
|
|
254
|
+
const commonDirs = ['src', 'app', 'pages', 'components', 'lib'];
|
|
255
|
+
const searchDirs = commonDirs
|
|
256
|
+
.map(dir => path.join(projectRoot, dir))
|
|
257
|
+
.filter(dir => {
|
|
258
|
+
try {
|
|
259
|
+
return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// If no standard dirs found, search project root (excluding node_modules)
|
|
266
|
+
if (searchDirs.length === 0) {
|
|
267
|
+
searchDirs.push(projectRoot);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
debugLog("PHASE 0: Searching for element ID", {
|
|
271
|
+
elementId,
|
|
272
|
+
currentRoute,
|
|
273
|
+
routePageFile,
|
|
274
|
+
searchDirs: searchDirs.map(d => d.replace(projectRoot + '/', ''))
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
function searchDirRecursive(dir: string): void {
|
|
278
|
+
try {
|
|
279
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const fullPath = path.join(dir, entry.name);
|
|
282
|
+
|
|
283
|
+
// Skip node_modules, hidden directories, and build outputs
|
|
284
|
+
if (entry.isDirectory()) {
|
|
285
|
+
if (
|
|
286
|
+
entry.name.includes('node_modules') ||
|
|
287
|
+
entry.name.startsWith('.') ||
|
|
288
|
+
entry.name === 'dist' ||
|
|
289
|
+
entry.name === 'build' ||
|
|
290
|
+
entry.name === '.next'
|
|
291
|
+
) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
searchDirRecursive(fullPath);
|
|
295
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
296
|
+
try {
|
|
297
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
298
|
+
const lines = content.split('\n');
|
|
299
|
+
for (let i = 0; i < lines.length; i++) {
|
|
300
|
+
if (pattern.test(lines[i])) {
|
|
301
|
+
const relativePath = fullPath.replace(projectRoot + '/', '');
|
|
302
|
+
const isRouteMatch = relativePath === routePageFile;
|
|
303
|
+
matches.push({
|
|
304
|
+
path: relativePath,
|
|
305
|
+
lineNumber: i + 1,
|
|
306
|
+
isRouteMatch
|
|
307
|
+
});
|
|
308
|
+
debugLog("PHASE 0: Found ID match", {
|
|
309
|
+
file: relativePath,
|
|
310
|
+
line: i + 1,
|
|
311
|
+
isRouteMatch
|
|
312
|
+
});
|
|
313
|
+
break; // One match per file is enough
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
// Skip files that can't be read
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
// Skip directories that can't be read
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Search all directories
|
|
327
|
+
for (const searchDir of searchDirs) {
|
|
328
|
+
searchDirRecursive(searchDir);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Sort: route matches first
|
|
332
|
+
return matches.sort((a, b) => (b.isRouteMatch ? 1 : 0) - (a.isRouteMatch ? 1 : 0));
|
|
333
|
+
}
|
|
334
|
+
|
|
232
335
|
/**
|
|
233
336
|
* Result of LLM screenshot analysis for smart file discovery
|
|
234
337
|
*/
|
|
@@ -927,12 +1030,73 @@ export async function POST(request: Request) {
|
|
|
927
1030
|
// Generate a unique session ID
|
|
928
1031
|
const newSessionId = randomUUID().slice(0, 8);
|
|
929
1032
|
|
|
930
|
-
//
|
|
931
|
-
// The LLM analyzes the screenshot and deduces component names, code patterns, and visible text
|
|
1033
|
+
// Initialize file discovery variables
|
|
932
1034
|
let smartSearchFiles: { path: string; content: string }[] = [];
|
|
933
1035
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
934
|
-
|
|
935
|
-
|
|
1036
|
+
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
1037
|
+
|
|
1038
|
+
// ========================================================================
|
|
1039
|
+
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
1040
|
+
// If we have an element ID, find it directly - no scoring, no heuristics
|
|
1041
|
+
// This is the most reliable signal, like Cursor knowing which file you're in
|
|
1042
|
+
// ========================================================================
|
|
1043
|
+
if (focusedElements?.some(el => el.elementId)) {
|
|
1044
|
+
const elementWithId = focusedElements.find(el => el.elementId)!;
|
|
1045
|
+
debugLog("PHASE 0: Element has ID, starting deterministic search", {
|
|
1046
|
+
elementId: elementWithId.elementId,
|
|
1047
|
+
route: pageRoute
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const matches = findFilesByElementId(
|
|
1051
|
+
projectRoot,
|
|
1052
|
+
elementWithId.elementId!,
|
|
1053
|
+
pageRoute || "/",
|
|
1054
|
+
discoverPageFile
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
if (matches.length === 1) {
|
|
1058
|
+
// Single match - 100% confidence
|
|
1059
|
+
deterministicMatch = matches[0];
|
|
1060
|
+
recommendedFile = {
|
|
1061
|
+
path: matches[0].path,
|
|
1062
|
+
reason: `Deterministic ID match: id="${elementWithId.elementId}" (unique in codebase)`
|
|
1063
|
+
};
|
|
1064
|
+
debugLog("PHASE 0 SUCCESS: Single ID match - skipping smart search", deterministicMatch);
|
|
1065
|
+
} else if (matches.length > 1) {
|
|
1066
|
+
// Multiple matches - use route to disambiguate
|
|
1067
|
+
const routeMatch = matches.find(m => m.isRouteMatch);
|
|
1068
|
+
if (routeMatch) {
|
|
1069
|
+
deterministicMatch = routeMatch;
|
|
1070
|
+
recommendedFile = {
|
|
1071
|
+
path: routeMatch.path,
|
|
1072
|
+
reason: `Deterministic ID match: id="${elementWithId.elementId}" (route "${pageRoute}" disambiguated from ${matches.length} files)`
|
|
1073
|
+
};
|
|
1074
|
+
debugLog("PHASE 0 SUCCESS: ID found in multiple files, using route match", {
|
|
1075
|
+
match: deterministicMatch,
|
|
1076
|
+
otherFiles: matches.filter(m => !m.isRouteMatch).map(m => m.path)
|
|
1077
|
+
});
|
|
1078
|
+
} else {
|
|
1079
|
+
debugLog("PHASE 0 FALLBACK: ID found in multiple files, no route match", {
|
|
1080
|
+
elementId: elementWithId.elementId,
|
|
1081
|
+
matchCount: matches.length,
|
|
1082
|
+
files: matches.map(m => m.path),
|
|
1083
|
+
route: pageRoute
|
|
1084
|
+
});
|
|
1085
|
+
// Fall through to smart search
|
|
1086
|
+
}
|
|
1087
|
+
} else {
|
|
1088
|
+
debugLog("PHASE 0 FALLBACK: Element ID not found in codebase", {
|
|
1089
|
+
elementId: elementWithId.elementId
|
|
1090
|
+
});
|
|
1091
|
+
// Fall through to smart search
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// ========================================================================
|
|
1096
|
+
// PHASE 1+2: LLM-driven smart file discovery (only if Phase 0 didn't match)
|
|
1097
|
+
// The LLM analyzes the screenshot and deduces component names, code patterns, and visible text
|
|
1098
|
+
// ========================================================================
|
|
1099
|
+
if (!deterministicMatch && screenshot) {
|
|
936
1100
|
debugLog("Starting Phase 1: LLM screenshot analysis");
|
|
937
1101
|
const analysis = await analyzeScreenshotForSearch(screenshot, userPrompt, apiKey);
|
|
938
1102
|
|
|
@@ -1024,8 +1188,12 @@ export async function POST(request: Request) {
|
|
|
1024
1188
|
return bestMatch;
|
|
1025
1189
|
};
|
|
1026
1190
|
|
|
1191
|
+
// ====================================================================
|
|
1192
|
+
// FALLBACK PRIORITY LOGIC (only reached when Phase 0 didn't find a match)
|
|
1193
|
+
// This handles cases: no element ID, dynamic IDs, or ambiguous ID matches
|
|
1194
|
+
// ====================================================================
|
|
1027
1195
|
if (phase2aConfirmed) {
|
|
1028
|
-
// PRIORITY 1: Phase 2a match confirmed by element search
|
|
1196
|
+
// FALLBACK PRIORITY 1: Phase 2a match confirmed by element search
|
|
1029
1197
|
const confirmedPath = phase2aMatches.find(p =>
|
|
1030
1198
|
focusedElementHints.some(h => h.path === p && h.score > 0)
|
|
1031
1199
|
);
|
|
@@ -1033,33 +1201,47 @@ export async function POST(request: Request) {
|
|
|
1033
1201
|
path: confirmedPath!,
|
|
1034
1202
|
reason: `Phase 2a component-name match confirmed by element search`
|
|
1035
1203
|
};
|
|
1036
|
-
debugLog("PRIORITY 1: Phase 2a confirmed by element search", recommendedFile);
|
|
1204
|
+
debugLog("FALLBACK PRIORITY 1: Phase 2a confirmed by element search", recommendedFile);
|
|
1037
1205
|
} else if (focusedTopScore > smartSearchTopScore * 0.5 && focusedTopScore >= 400) {
|
|
1038
|
-
// PRIORITY 2: Focused element has strong
|
|
1206
|
+
// FALLBACK PRIORITY 2: Focused element has strong score
|
|
1039
1207
|
recommendedFile = {
|
|
1040
1208
|
path: focusedTopPath!,
|
|
1041
1209
|
reason: `Focused element match (score: ${focusedTopScore}, exceeds threshold)`
|
|
1042
1210
|
};
|
|
1043
|
-
debugLog("PRIORITY 2: Strong focused element match", recommendedFile);
|
|
1211
|
+
debugLog("FALLBACK PRIORITY 2: Strong focused element match", recommendedFile);
|
|
1044
1212
|
} else if (phase2aMatches.length > 0) {
|
|
1045
|
-
// PRIORITY
|
|
1213
|
+
// FALLBACK PRIORITY 3: Phase 2a match WITHOUT focused element
|
|
1046
1214
|
// Use prompt-aware selection to pick the best match
|
|
1047
1215
|
const bestMatch = findBestPhase2aMatch()!;
|
|
1048
1216
|
recommendedFile = {
|
|
1049
1217
|
path: bestMatch,
|
|
1050
1218
|
reason: `Phase 2a component-name match (prompt-aware selection from ${phase2aMatches.length} candidates)`
|
|
1051
1219
|
};
|
|
1052
|
-
debugLog("PRIORITY
|
|
1220
|
+
debugLog("FALLBACK PRIORITY 3: Phase 2a match (no focused element)", {
|
|
1053
1221
|
selectedPath: bestMatch,
|
|
1054
1222
|
allCandidates: phase2aMatches
|
|
1055
1223
|
});
|
|
1056
|
-
} else
|
|
1057
|
-
// PRIORITY
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1224
|
+
} else {
|
|
1225
|
+
// FALLBACK PRIORITY 4: Use the page file from the current route
|
|
1226
|
+
const routePageFile = discoverPageFile(pageRoute || "/", projectRoot);
|
|
1227
|
+
|
|
1228
|
+
if (routePageFile && fs.existsSync(path.join(projectRoot, routePageFile))) {
|
|
1229
|
+
recommendedFile = {
|
|
1230
|
+
path: routePageFile,
|
|
1231
|
+
reason: `Page file from route "${pageRoute || "/"}"`
|
|
1232
|
+
};
|
|
1233
|
+
debugLog("FALLBACK PRIORITY 4: Using page file from route", {
|
|
1234
|
+
route: pageRoute || "/",
|
|
1235
|
+
pageFile: routePageFile
|
|
1236
|
+
});
|
|
1237
|
+
} else if (smartSearchTopPath) {
|
|
1238
|
+
// FALLBACK PRIORITY 5: Smart search top result - last resort
|
|
1239
|
+
recommendedFile = {
|
|
1240
|
+
path: smartSearchTopPath,
|
|
1241
|
+
reason: `Smart search top result (score: ${smartSearchTopScore})`
|
|
1242
|
+
};
|
|
1243
|
+
debugLog("FALLBACK PRIORITY 5: Using smart search top result", recommendedFile);
|
|
1244
|
+
}
|
|
1063
1245
|
}
|
|
1064
1246
|
|
|
1065
1247
|
debugLog("Phase 1+2 complete", {
|
|
@@ -225,6 +225,109 @@ function findElementIdInFile(
|
|
|
225
225
|
return null;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* PHASE 0: Deterministic Element ID Search (Cursor-style)
|
|
230
|
+
* Grep entire codebase for the element ID. If found in multiple files,
|
|
231
|
+
* use the current route to disambiguate (prioritize page file for route).
|
|
232
|
+
*
|
|
233
|
+
* This is the most reliable signal - like Cursor knowing which file you're in.
|
|
234
|
+
* Element IDs should be unique, so if we find one, that's THE file.
|
|
235
|
+
*/
|
|
236
|
+
function findFilesByElementId(
|
|
237
|
+
projectRoot: string,
|
|
238
|
+
elementId: string,
|
|
239
|
+
currentRoute: string,
|
|
240
|
+
discoverPageFileFn: (route: string, projectRoot: string) => string | null
|
|
241
|
+
): { path: string; lineNumber: number; isRouteMatch: boolean }[] {
|
|
242
|
+
const pattern = new RegExp(`id=["'\`]${elementId}["'\`]`);
|
|
243
|
+
const matches: { path: string; lineNumber: number; isRouteMatch: boolean }[] = [];
|
|
244
|
+
|
|
245
|
+
// Determine expected page file from route
|
|
246
|
+
const routePageFile = discoverPageFileFn(currentRoute, projectRoot);
|
|
247
|
+
|
|
248
|
+
// Search all common project directories that exist
|
|
249
|
+
// This supports: src/app, app/, pages/, components/, lib/ structures
|
|
250
|
+
const commonDirs = ['src', 'app', 'pages', 'components', 'lib'];
|
|
251
|
+
const searchDirs = commonDirs
|
|
252
|
+
.map(dir => path.join(projectRoot, dir))
|
|
253
|
+
.filter(dir => {
|
|
254
|
+
try {
|
|
255
|
+
return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// If no standard dirs found, search project root (excluding node_modules)
|
|
262
|
+
if (searchDirs.length === 0) {
|
|
263
|
+
searchDirs.push(projectRoot);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
debugLog("PHASE 0: Searching for element ID", {
|
|
267
|
+
elementId,
|
|
268
|
+
currentRoute,
|
|
269
|
+
routePageFile,
|
|
270
|
+
searchDirs: searchDirs.map(d => d.replace(projectRoot + '/', ''))
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
function searchDirRecursive(dir: string): void {
|
|
274
|
+
try {
|
|
275
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
const fullPath = path.join(dir, entry.name);
|
|
278
|
+
|
|
279
|
+
// Skip node_modules, hidden directories, and build outputs
|
|
280
|
+
if (entry.isDirectory()) {
|
|
281
|
+
if (
|
|
282
|
+
entry.name.includes('node_modules') ||
|
|
283
|
+
entry.name.startsWith('.') ||
|
|
284
|
+
entry.name === 'dist' ||
|
|
285
|
+
entry.name === 'build' ||
|
|
286
|
+
entry.name === '.next'
|
|
287
|
+
) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
searchDirRecursive(fullPath);
|
|
291
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
292
|
+
try {
|
|
293
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
294
|
+
const lines = content.split('\n');
|
|
295
|
+
for (let i = 0; i < lines.length; i++) {
|
|
296
|
+
if (pattern.test(lines[i])) {
|
|
297
|
+
const relativePath = fullPath.replace(projectRoot + '/', '');
|
|
298
|
+
const isRouteMatch = relativePath === routePageFile;
|
|
299
|
+
matches.push({
|
|
300
|
+
path: relativePath,
|
|
301
|
+
lineNumber: i + 1,
|
|
302
|
+
isRouteMatch
|
|
303
|
+
});
|
|
304
|
+
debugLog("PHASE 0: Found ID match", {
|
|
305
|
+
file: relativePath,
|
|
306
|
+
line: i + 1,
|
|
307
|
+
isRouteMatch
|
|
308
|
+
});
|
|
309
|
+
break; // One match per file is enough
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// Skip files that can't be read
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
// Skip directories that can't be read
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Search all directories
|
|
323
|
+
for (const searchDir of searchDirs) {
|
|
324
|
+
searchDirRecursive(searchDir);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Sort: route matches first
|
|
328
|
+
return matches.sort((a, b) => (b.isRouteMatch ? 1 : 0) - (a.isRouteMatch ? 1 : 0));
|
|
329
|
+
}
|
|
330
|
+
|
|
228
331
|
/**
|
|
229
332
|
* Result of LLM screenshot analysis for smart file discovery
|
|
230
333
|
*/
|
|
@@ -896,12 +999,73 @@ export async function POST(request: Request) {
|
|
|
896
999
|
);
|
|
897
1000
|
}
|
|
898
1001
|
|
|
899
|
-
//
|
|
900
|
-
// The LLM analyzes the screenshot and deduces component names, code patterns, and visible text
|
|
1002
|
+
// Initialize file discovery variables
|
|
901
1003
|
let smartSearchFiles: { path: string; content: string }[] = [];
|
|
902
1004
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
903
|
-
|
|
904
|
-
|
|
1005
|
+
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
1006
|
+
|
|
1007
|
+
// ========================================================================
|
|
1008
|
+
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
1009
|
+
// If we have an element ID, find it directly - no scoring, no heuristics
|
|
1010
|
+
// This is the most reliable signal, like Cursor knowing which file you're in
|
|
1011
|
+
// ========================================================================
|
|
1012
|
+
if (focusedElements?.some(el => el.elementId)) {
|
|
1013
|
+
const elementWithId = focusedElements.find(el => el.elementId)!;
|
|
1014
|
+
debugLog("PHASE 0: Element has ID, starting deterministic search", {
|
|
1015
|
+
elementId: elementWithId.elementId,
|
|
1016
|
+
route: pageRoute
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
const matches = findFilesByElementId(
|
|
1020
|
+
projectRoot,
|
|
1021
|
+
elementWithId.elementId!,
|
|
1022
|
+
pageRoute || "/",
|
|
1023
|
+
discoverPageFile
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
if (matches.length === 1) {
|
|
1027
|
+
// Single match - 100% confidence
|
|
1028
|
+
deterministicMatch = matches[0];
|
|
1029
|
+
recommendedFile = {
|
|
1030
|
+
path: matches[0].path,
|
|
1031
|
+
reason: `Deterministic ID match: id="${elementWithId.elementId}" (unique in codebase)`
|
|
1032
|
+
};
|
|
1033
|
+
debugLog("PHASE 0 SUCCESS: Single ID match - skipping smart search", deterministicMatch);
|
|
1034
|
+
} else if (matches.length > 1) {
|
|
1035
|
+
// Multiple matches - use route to disambiguate
|
|
1036
|
+
const routeMatch = matches.find(m => m.isRouteMatch);
|
|
1037
|
+
if (routeMatch) {
|
|
1038
|
+
deterministicMatch = routeMatch;
|
|
1039
|
+
recommendedFile = {
|
|
1040
|
+
path: routeMatch.path,
|
|
1041
|
+
reason: `Deterministic ID match: id="${elementWithId.elementId}" (route "${pageRoute}" disambiguated from ${matches.length} files)`
|
|
1042
|
+
};
|
|
1043
|
+
debugLog("PHASE 0 SUCCESS: ID found in multiple files, using route match", {
|
|
1044
|
+
match: deterministicMatch,
|
|
1045
|
+
otherFiles: matches.filter(m => !m.isRouteMatch).map(m => m.path)
|
|
1046
|
+
});
|
|
1047
|
+
} else {
|
|
1048
|
+
debugLog("PHASE 0 FALLBACK: ID found in multiple files, no route match", {
|
|
1049
|
+
elementId: elementWithId.elementId,
|
|
1050
|
+
matchCount: matches.length,
|
|
1051
|
+
files: matches.map(m => m.path),
|
|
1052
|
+
route: pageRoute
|
|
1053
|
+
});
|
|
1054
|
+
// Fall through to smart search
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
debugLog("PHASE 0 FALLBACK: Element ID not found in codebase", {
|
|
1058
|
+
elementId: elementWithId.elementId
|
|
1059
|
+
});
|
|
1060
|
+
// Fall through to smart search
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// ========================================================================
|
|
1065
|
+
// PHASE 1+2: LLM-driven smart file discovery (only if Phase 0 didn't match)
|
|
1066
|
+
// The LLM analyzes the screenshot and deduces component names, code patterns, and visible text
|
|
1067
|
+
// ========================================================================
|
|
1068
|
+
if (!deterministicMatch && screenshot) {
|
|
905
1069
|
debugLog("Starting Phase 1: LLM screenshot analysis");
|
|
906
1070
|
const analysis = await analyzeScreenshotForSearch(screenshot, userPrompt, apiKey);
|
|
907
1071
|
|
|
@@ -993,8 +1157,12 @@ export async function POST(request: Request) {
|
|
|
993
1157
|
return bestMatch;
|
|
994
1158
|
};
|
|
995
1159
|
|
|
1160
|
+
// ====================================================================
|
|
1161
|
+
// FALLBACK PRIORITY LOGIC (only reached when Phase 0 didn't find a match)
|
|
1162
|
+
// This handles cases: no element ID, dynamic IDs, or ambiguous ID matches
|
|
1163
|
+
// ====================================================================
|
|
996
1164
|
if (phase2aConfirmed) {
|
|
997
|
-
// PRIORITY 1: Phase 2a match confirmed by element search
|
|
1165
|
+
// FALLBACK PRIORITY 1: Phase 2a match confirmed by element search
|
|
998
1166
|
const confirmedPath = phase2aMatches.find(p =>
|
|
999
1167
|
focusedElementHints.some(h => h.path === p && h.score > 0)
|
|
1000
1168
|
);
|
|
@@ -1002,33 +1170,47 @@ export async function POST(request: Request) {
|
|
|
1002
1170
|
path: confirmedPath!,
|
|
1003
1171
|
reason: `Phase 2a component-name match confirmed by element search`
|
|
1004
1172
|
};
|
|
1005
|
-
debugLog("PRIORITY 1: Phase 2a confirmed by element search", recommendedFile);
|
|
1173
|
+
debugLog("FALLBACK PRIORITY 1: Phase 2a confirmed by element search", recommendedFile);
|
|
1006
1174
|
} else if (focusedTopScore > smartSearchTopScore * 0.5 && focusedTopScore >= 400) {
|
|
1007
|
-
// PRIORITY 2: Focused element has strong
|
|
1175
|
+
// FALLBACK PRIORITY 2: Focused element has strong score
|
|
1008
1176
|
recommendedFile = {
|
|
1009
1177
|
path: focusedTopPath!,
|
|
1010
1178
|
reason: `Focused element match (score: ${focusedTopScore}, exceeds threshold)`
|
|
1011
1179
|
};
|
|
1012
|
-
debugLog("PRIORITY 2: Strong focused element match", recommendedFile);
|
|
1180
|
+
debugLog("FALLBACK PRIORITY 2: Strong focused element match", recommendedFile);
|
|
1013
1181
|
} else if (phase2aMatches.length > 0) {
|
|
1014
|
-
// PRIORITY
|
|
1182
|
+
// FALLBACK PRIORITY 3: Phase 2a match WITHOUT focused element
|
|
1015
1183
|
// Use prompt-aware selection to pick the best match
|
|
1016
1184
|
const bestMatch = findBestPhase2aMatch()!;
|
|
1017
1185
|
recommendedFile = {
|
|
1018
1186
|
path: bestMatch,
|
|
1019
1187
|
reason: `Phase 2a component-name match (prompt-aware selection from ${phase2aMatches.length} candidates)`
|
|
1020
1188
|
};
|
|
1021
|
-
debugLog("PRIORITY
|
|
1189
|
+
debugLog("FALLBACK PRIORITY 3: Phase 2a match (no focused element)", {
|
|
1022
1190
|
selectedPath: bestMatch,
|
|
1023
1191
|
allCandidates: phase2aMatches
|
|
1024
1192
|
});
|
|
1025
|
-
} else
|
|
1026
|
-
// PRIORITY
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1193
|
+
} else {
|
|
1194
|
+
// FALLBACK PRIORITY 4: Use the page file from the current route
|
|
1195
|
+
const routePageFile = discoverPageFile(pageRoute || "/", projectRoot);
|
|
1196
|
+
|
|
1197
|
+
if (routePageFile && fs.existsSync(path.join(projectRoot, routePageFile))) {
|
|
1198
|
+
recommendedFile = {
|
|
1199
|
+
path: routePageFile,
|
|
1200
|
+
reason: `Page file from route "${pageRoute || "/"}"`
|
|
1201
|
+
};
|
|
1202
|
+
debugLog("FALLBACK PRIORITY 4: Using page file from route", {
|
|
1203
|
+
route: pageRoute || "/",
|
|
1204
|
+
pageFile: routePageFile
|
|
1205
|
+
});
|
|
1206
|
+
} else if (smartSearchTopPath) {
|
|
1207
|
+
// FALLBACK PRIORITY 5: Smart search top result - last resort
|
|
1208
|
+
recommendedFile = {
|
|
1209
|
+
path: smartSearchTopPath,
|
|
1210
|
+
reason: `Smart search top result (score: ${smartSearchTopScore})`
|
|
1211
|
+
};
|
|
1212
|
+
debugLog("FALLBACK PRIORITY 5: Using smart search top result", recommendedFile);
|
|
1213
|
+
}
|
|
1032
1214
|
}
|
|
1033
1215
|
|
|
1034
1216
|
debugLog("Phase 1+2 complete", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.89",
|
|
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",
|