sonance-brand-mcp 1.3.106 → 1.3.108
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.
|
@@ -825,14 +825,21 @@ function findElementInImportedFiles(
|
|
|
825
825
|
file.path.includes('.d.ts')) continue;
|
|
826
826
|
|
|
827
827
|
const result = findElementLineInFile(file.content, focusedElement);
|
|
828
|
-
|
|
828
|
+
// Accept ALL matches including low confidence - let the scoring decide
|
|
829
|
+
// Low confidence matches from fallback logic (text/class fingerprint) are still useful
|
|
830
|
+
if (result) {
|
|
829
831
|
let score = 0;
|
|
830
832
|
|
|
831
833
|
// Text content match is highest priority (+100)
|
|
832
|
-
if (result.matchedBy.includes('textContent') || result.matchedBy.includes('word match')) {
|
|
834
|
+
if (result.matchedBy.includes('textContent') || result.matchedBy.includes('word match') || result.matchedBy.includes('flexible text')) {
|
|
833
835
|
score += 100;
|
|
834
836
|
}
|
|
835
837
|
|
|
838
|
+
// Class fingerprint match gets medium score (+60)
|
|
839
|
+
if (result.matchedBy.includes('class fingerprint')) {
|
|
840
|
+
score += 60;
|
|
841
|
+
}
|
|
842
|
+
|
|
836
843
|
// Directory proximity - same directory tree as page file (+50)
|
|
837
844
|
// e.g., for page src/app/typography/page.tsx, prefer src/app/typography/_components/
|
|
838
845
|
if (pageDir && file.path.includes(pageDir)) {
|
|
@@ -845,8 +852,8 @@ function findElementInImportedFiles(
|
|
|
845
852
|
score += 30;
|
|
846
853
|
}
|
|
847
854
|
|
|
848
|
-
// Confidence bonus
|
|
849
|
-
score += result.confidence === 'high' ? 20 :
|
|
855
|
+
// Confidence bonus - low confidence still gets some points
|
|
856
|
+
score += result.confidence === 'high' ? 30 : result.confidence === 'medium' ? 20 : 5;
|
|
850
857
|
|
|
851
858
|
matches.push({
|
|
852
859
|
path: file.path,
|
|
@@ -2028,6 +2035,7 @@ export async function POST(request: Request) {
|
|
|
2028
2035
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
2029
2036
|
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
2030
2037
|
let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
|
|
2038
|
+
let smartSearchTopPath: string | null = null; // Top smart search result for fallback
|
|
2031
2039
|
|
|
2032
2040
|
// ========================================================================
|
|
2033
2041
|
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
@@ -2134,7 +2142,7 @@ export async function POST(request: Request) {
|
|
|
2134
2142
|
// IMPROVED: Relative scoring - compare focused element vs smart search
|
|
2135
2143
|
// This prevents false positives from generic element IDs in wrong files
|
|
2136
2144
|
const smartSearchTopScore = searchResults[0]?.score || 0;
|
|
2137
|
-
|
|
2145
|
+
smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
|
|
2138
2146
|
const focusedTopScore = focusedElementHints[0]?.score || 0;
|
|
2139
2147
|
const focusedTopPath = focusedElementHints[0]?.path || null;
|
|
2140
2148
|
|
|
@@ -2299,9 +2307,25 @@ export async function POST(request: Request) {
|
|
|
2299
2307
|
|
|
2300
2308
|
let usedContext = 0;
|
|
2301
2309
|
|
|
2310
|
+
// ========== PHASE 0 PRIORITY: If Phase 0 found a file, use it directly ==========
|
|
2311
|
+
// Phase 0 is deterministic (ID match) so it should ALWAYS take priority
|
|
2312
|
+
let phase0FileContent: { path: string; content: string } | null = null;
|
|
2313
|
+
if (deterministicMatch) {
|
|
2314
|
+
const fullPath = path.join(projectRoot, deterministicMatch.path);
|
|
2315
|
+
if (fs.existsSync(fullPath)) {
|
|
2316
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
2317
|
+
phase0FileContent = { path: deterministicMatch.path, content };
|
|
2318
|
+
debugLog("PHASE 0 PRIORITY: Using deterministic match as target file", {
|
|
2319
|
+
path: deterministicMatch.path,
|
|
2320
|
+
lineNumber: deterministicMatch.lineNumber,
|
|
2321
|
+
contentLength: content.length
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2302
2326
|
// Extract recommended file from component sources (to show first, avoid duplication)
|
|
2303
2327
|
let recommendedFileContent: { path: string; content: string } | null = null;
|
|
2304
|
-
if (recommendedFile) {
|
|
2328
|
+
if (recommendedFile && !phase0FileContent) {
|
|
2305
2329
|
// Check componentSources first
|
|
2306
2330
|
const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
|
|
2307
2331
|
if (idx !== -1) {
|
|
@@ -2317,10 +2341,28 @@ export async function POST(request: Request) {
|
|
|
2317
2341
|
// ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
|
|
2318
2342
|
// This determines the ACTUAL target file by checking for text matches in imported components
|
|
2319
2343
|
// Must run first so that all subsequent code uses the correct file path
|
|
2320
|
-
|
|
2344
|
+
// PHASE 0 FILES SKIP REDIRECT - they're already definitively correct
|
|
2345
|
+
let actualTargetFile = phase0FileContent || recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
|
|
2321
2346
|
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2322
2347
|
|
|
2323
|
-
|
|
2348
|
+
// If Phase 0 succeeded, we already know the exact line - use it directly
|
|
2349
|
+
if (phase0FileContent && deterministicMatch) {
|
|
2350
|
+
const lines = phase0FileContent.content.split('\n');
|
|
2351
|
+
const lineNum = deterministicMatch.lineNumber;
|
|
2352
|
+
elementLocation = {
|
|
2353
|
+
lineNumber: lineNum,
|
|
2354
|
+
snippet: lines.slice(Math.max(0, lineNum - 4), lineNum + 5).join('\n'),
|
|
2355
|
+
confidence: 'high',
|
|
2356
|
+
matchedBy: 'Phase 0 deterministic ID match'
|
|
2357
|
+
};
|
|
2358
|
+
debugLog("PHASE 0: Set element location from deterministic match", {
|
|
2359
|
+
lineNumber: lineNum,
|
|
2360
|
+
confidence: 'high'
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// SKIP REDIRECT LOGIC if Phase 0 already determined the file (it's already correct)
|
|
2365
|
+
if (!phase0FileContent && actualTargetFile && focusedElements && focusedElements.length > 0) {
|
|
2324
2366
|
const content = actualTargetFile.content;
|
|
2325
2367
|
|
|
2326
2368
|
// Search for focused element in the file using multiple strategies
|
|
@@ -2469,6 +2511,22 @@ export async function POST(request: Request) {
|
|
|
2469
2511
|
}
|
|
2470
2512
|
}
|
|
2471
2513
|
|
|
2514
|
+
// ========== SMART SEARCH FALLBACK ==========
|
|
2515
|
+
// If we still don't have a good target file (just the page wrapper),
|
|
2516
|
+
// use the smart search top result - it already found relevant files!
|
|
2517
|
+
if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
|
|
2518
|
+
const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
|
|
2519
|
+
if (fs.existsSync(smartSearchFullPath)) {
|
|
2520
|
+
const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
|
|
2521
|
+
actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
|
|
2522
|
+
debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
|
|
2523
|
+
originalFile: pageContext.pageFile,
|
|
2524
|
+
redirectTo: smartSearchTopPath,
|
|
2525
|
+
contentLength: smartSearchContent.length
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2472
2530
|
debugLog("File redirect complete", {
|
|
2473
2531
|
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2474
2532
|
actualTarget: actualTargetFile?.path || 'none',
|
|
@@ -821,14 +821,21 @@ function findElementInImportedFiles(
|
|
|
821
821
|
file.path.includes('.d.ts')) continue;
|
|
822
822
|
|
|
823
823
|
const result = findElementLineInFile(file.content, focusedElement);
|
|
824
|
-
|
|
824
|
+
// Accept ALL matches including low confidence - let the scoring decide
|
|
825
|
+
// Low confidence matches from fallback logic (text/class fingerprint) are still useful
|
|
826
|
+
if (result) {
|
|
825
827
|
let score = 0;
|
|
826
828
|
|
|
827
829
|
// Text content match is highest priority (+100)
|
|
828
|
-
if (result.matchedBy.includes('textContent') || result.matchedBy.includes('word match')) {
|
|
830
|
+
if (result.matchedBy.includes('textContent') || result.matchedBy.includes('word match') || result.matchedBy.includes('flexible text')) {
|
|
829
831
|
score += 100;
|
|
830
832
|
}
|
|
831
833
|
|
|
834
|
+
// Class fingerprint match gets medium score (+60)
|
|
835
|
+
if (result.matchedBy.includes('class fingerprint')) {
|
|
836
|
+
score += 60;
|
|
837
|
+
}
|
|
838
|
+
|
|
832
839
|
// Directory proximity - same directory tree as page file (+50)
|
|
833
840
|
// e.g., for page src/app/typography/page.tsx, prefer src/app/typography/_components/
|
|
834
841
|
if (pageDir && file.path.includes(pageDir)) {
|
|
@@ -841,8 +848,8 @@ function findElementInImportedFiles(
|
|
|
841
848
|
score += 30;
|
|
842
849
|
}
|
|
843
850
|
|
|
844
|
-
// Confidence bonus
|
|
845
|
-
score += result.confidence === 'high' ? 20 :
|
|
851
|
+
// Confidence bonus - low confidence still gets some points
|
|
852
|
+
score += result.confidence === 'high' ? 30 : result.confidence === 'medium' ? 20 : 5;
|
|
846
853
|
|
|
847
854
|
matches.push({
|
|
848
855
|
path: file.path,
|
|
@@ -1997,6 +2004,7 @@ export async function POST(request: Request) {
|
|
|
1997
2004
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
1998
2005
|
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
1999
2006
|
let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
|
|
2007
|
+
let smartSearchTopPath: string | null = null; // Top smart search result for fallback
|
|
2000
2008
|
|
|
2001
2009
|
// ========================================================================
|
|
2002
2010
|
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
@@ -2103,7 +2111,7 @@ export async function POST(request: Request) {
|
|
|
2103
2111
|
// IMPROVED: Relative scoring - compare focused element vs smart search
|
|
2104
2112
|
// This prevents false positives from generic element IDs in wrong files
|
|
2105
2113
|
const smartSearchTopScore = searchResults[0]?.score || 0;
|
|
2106
|
-
|
|
2114
|
+
smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
|
|
2107
2115
|
const focusedTopScore = focusedElementHints[0]?.score || 0;
|
|
2108
2116
|
const focusedTopPath = focusedElementHints[0]?.path || null;
|
|
2109
2117
|
|
|
@@ -2268,9 +2276,25 @@ export async function POST(request: Request) {
|
|
|
2268
2276
|
|
|
2269
2277
|
let usedContext = 0;
|
|
2270
2278
|
|
|
2279
|
+
// ========== PHASE 0 PRIORITY: If Phase 0 found a file, use it directly ==========
|
|
2280
|
+
// Phase 0 is deterministic (ID match) so it should ALWAYS take priority
|
|
2281
|
+
let phase0FileContent: { path: string; content: string } | null = null;
|
|
2282
|
+
if (deterministicMatch) {
|
|
2283
|
+
const fullPath = path.join(projectRoot, deterministicMatch.path);
|
|
2284
|
+
if (fs.existsSync(fullPath)) {
|
|
2285
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
2286
|
+
phase0FileContent = { path: deterministicMatch.path, content };
|
|
2287
|
+
debugLog("PHASE 0 PRIORITY: Using deterministic match as target file", {
|
|
2288
|
+
path: deterministicMatch.path,
|
|
2289
|
+
lineNumber: deterministicMatch.lineNumber,
|
|
2290
|
+
contentLength: content.length
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2271
2295
|
// Extract recommended file from component sources (to show first, avoid duplication)
|
|
2272
2296
|
let recommendedFileContent: { path: string; content: string } | null = null;
|
|
2273
|
-
if (recommendedFile) {
|
|
2297
|
+
if (recommendedFile && !phase0FileContent) {
|
|
2274
2298
|
// Check componentSources first
|
|
2275
2299
|
const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
|
|
2276
2300
|
if (idx !== -1) {
|
|
@@ -2286,10 +2310,28 @@ export async function POST(request: Request) {
|
|
|
2286
2310
|
// ========== FILE REDIRECT LOGIC (runs BEFORE building instructions) ==========
|
|
2287
2311
|
// This determines the ACTUAL target file by checking for text matches in imported components
|
|
2288
2312
|
// Must run first so that all subsequent code uses the correct file path
|
|
2289
|
-
|
|
2313
|
+
// PHASE 0 FILES SKIP REDIRECT - they're already definitively correct
|
|
2314
|
+
let actualTargetFile = phase0FileContent || recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
|
|
2290
2315
|
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
2291
2316
|
|
|
2292
|
-
|
|
2317
|
+
// If Phase 0 succeeded, we already know the exact line - use it directly
|
|
2318
|
+
if (phase0FileContent && deterministicMatch) {
|
|
2319
|
+
const lines = phase0FileContent.content.split('\n');
|
|
2320
|
+
const lineNum = deterministicMatch.lineNumber;
|
|
2321
|
+
elementLocation = {
|
|
2322
|
+
lineNumber: lineNum,
|
|
2323
|
+
snippet: lines.slice(Math.max(0, lineNum - 4), lineNum + 5).join('\n'),
|
|
2324
|
+
confidence: 'high',
|
|
2325
|
+
matchedBy: 'Phase 0 deterministic ID match'
|
|
2326
|
+
};
|
|
2327
|
+
debugLog("PHASE 0: Set element location from deterministic match", {
|
|
2328
|
+
lineNumber: lineNum,
|
|
2329
|
+
confidence: 'high'
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// SKIP REDIRECT LOGIC if Phase 0 already determined the file (it's already correct)
|
|
2334
|
+
if (!phase0FileContent && actualTargetFile && focusedElements && focusedElements.length > 0) {
|
|
2293
2335
|
const content = actualTargetFile.content;
|
|
2294
2336
|
|
|
2295
2337
|
// Search for focused element in the file using multiple strategies
|
|
@@ -2438,6 +2480,22 @@ export async function POST(request: Request) {
|
|
|
2438
2480
|
}
|
|
2439
2481
|
}
|
|
2440
2482
|
|
|
2483
|
+
// ========== SMART SEARCH FALLBACK ==========
|
|
2484
|
+
// If we still don't have a good target file (just the page wrapper),
|
|
2485
|
+
// use the smart search top result - it already found relevant files!
|
|
2486
|
+
if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
|
|
2487
|
+
const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
|
|
2488
|
+
if (fs.existsSync(smartSearchFullPath)) {
|
|
2489
|
+
const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
|
|
2490
|
+
actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
|
|
2491
|
+
debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
|
|
2492
|
+
originalFile: pageContext.pageFile,
|
|
2493
|
+
redirectTo: smartSearchTopPath,
|
|
2494
|
+
contentLength: smartSearchContent.length
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2441
2499
|
debugLog("File redirect complete", {
|
|
2442
2500
|
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2443
2501
|
actualTarget: actualTargetFile?.path || 'none',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.108",
|
|
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",
|