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
- if (result && result.confidence !== 'low') {
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 : 10;
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
- const smartSearchTopPath = searchResults[0]?.path || null;
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
- let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
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
- if (actualTargetFile && focusedElements && focusedElements.length > 0) {
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
- if (result && result.confidence !== 'low') {
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 : 10;
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
- const smartSearchTopPath = searchResults[0]?.path || null;
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
- let actualTargetFile = recommendedFileContent || (pageContext.pageContent ? { path: pageContext.pageFile, content: pageContext.pageContent } : null);
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
- if (actualTargetFile && focusedElements && focusedElements.length > 0) {
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.106",
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",