sonance-brand-mcp 1.3.40 → 1.3.42

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.
@@ -589,6 +589,27 @@ export async function POST(request: Request) {
589
589
  });
590
590
  }
591
591
 
592
+ // ========== SMART CONTEXT BUDGETING ==========
593
+ // Total budget: 100k chars (~25k tokens, safe for Claude)
594
+ // Priority: Recommended file (full) > Page file (limited) > Other components (dynamic)
595
+ const TOTAL_CONTEXT_BUDGET = 100000;
596
+ const MAX_RECOMMENDED_FILE = 50000; // 50k chars max for recommended file
597
+ const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
598
+ const MAX_GLOBALS_CSS = 1500;
599
+ const MAX_FILES = 25;
600
+
601
+ let usedContext = 0;
602
+
603
+ // Extract recommended file from component sources (to show first, avoid duplication)
604
+ let recommendedFileContent: { path: string; content: string } | null = null;
605
+ if (recommendedFile) {
606
+ const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
607
+ if (idx !== -1) {
608
+ recommendedFileContent = pageContext.componentSources[idx];
609
+ pageContext.componentSources.splice(idx, 1); // Remove to avoid duplication
610
+ }
611
+ }
612
+
592
613
  // Build text content
593
614
  let textContent = `VISION MODE EDIT REQUEST
594
615
 
@@ -597,64 +618,70 @@ User Request: "${userPrompt}"
597
618
 
598
619
  `;
599
620
 
600
- // Add recommendation if smart search identified a best match
601
- if (recommendedFile) {
602
- textContent += `═══════════════════════════════════════════════════════════════════════════════
603
- ⚡ RECOMMENDED FILE TO EDIT
604
- ═══════════════════════════════════════════════════════════════════════════════
605
-
606
- Based on the screenshot analysis, the component you should edit is:
607
- **${recommendedFile.path}**
608
- Reason: ${recommendedFile.reason}
609
-
610
- The page file (${pageContext.pageFile}) is just a wrapper - the actual UI elements are in the component above.
611
- STRONGLY PREFER editing the recommended file unless you have a specific reason not to.
621
+ if (focusedElements && focusedElements.length > 0) {
622
+ textContent += `FOCUSED ELEMENTS (user clicked on these):
623
+ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
612
624
 
613
625
  `;
614
626
  }
615
627
 
616
- if (focusedElements && focusedElements.length > 0) {
617
- textContent += `FOCUSED ELEMENTS (user clicked on these):
618
- ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
628
+ // ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
629
+ if (recommendedFileContent) {
630
+ const content = recommendedFileContent.content.length > MAX_RECOMMENDED_FILE
631
+ ? recommendedFileContent.content.substring(0, MAX_RECOMMENDED_FILE) + "\n// ... (file truncated at 50k chars, showing component definition and main logic)"
632
+ : recommendedFileContent.content;
633
+
634
+ textContent += `═══════════════════════════════════════════════════════════════════════════════
635
+ ⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
636
+ ═══════════════════════════════════════════════════════════════════════════════
637
+
638
+ This is the component that renders the UI you see in the screenshot.
639
+ File: ${recommendedFileContent.path}
640
+
641
+ \`\`\`tsx
642
+ ${content}
643
+ \`\`\`
619
644
 
620
645
  `;
646
+ usedContext += content.length;
647
+ debugLog("Added TARGET COMPONENT to context", {
648
+ path: recommendedFileContent.path,
649
+ originalSize: recommendedFileContent.content.length,
650
+ includedSize: content.length
651
+ });
621
652
  }
622
653
 
623
- textContent += `PAGE CONTEXT:
654
+ // ========== PAGE CONTEXT (wrapper - de-emphasized) ==========
655
+ const pageContentTruncated = pageContext.pageContent.substring(0, MAX_PAGE_FILE);
656
+ const pageWasTruncated = pageContext.pageContent.length > MAX_PAGE_FILE;
657
+
658
+ textContent += `PAGE CONTEXT (wrapper only${recommendedFileContent ? " - DO NOT edit this, edit the TARGET COMPONENT above" : ""}):
624
659
 
625
660
  Page File: ${pageContext.pageFile || "Not found"}
626
- ${pageContext.pageContent ? `\`\`\`tsx\n${pageContext.pageContent}\n\`\`\`` : ""}
661
+ ${pageContext.pageContent ? `\`\`\`tsx\n${pageContentTruncated}${pageWasTruncated ? "\n// ... (wrapper truncated)" : ""}\n\`\`\`` : ""}
627
662
 
628
663
  `;
664
+ usedContext += pageContentTruncated.length;
629
665
 
666
+ // ========== SUPPORTING COMPONENTS (dynamic budget) ==========
630
667
  if (pageContext.componentSources.length > 0) {
631
- // Smart truncation: prioritize first components (direct imports) and limit total context
632
- const MAX_TOTAL_CONTEXT = 80000; // ~80k chars to stay well under Claude's limit
633
- const MAX_PER_FILE_PRIORITY = 4000; // First 10 files get more space
634
- const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
635
- const MAX_FILES = 30; // Limit total number of files
636
-
637
- let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
638
- const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
668
+ const remainingBudget = TOTAL_CONTEXT_BUDGET - usedContext - MAX_GLOBALS_CSS - 5000; // Reserve 5k for instructions
669
+ const filesToInclude = pageContext.componentSources.slice(0, MAX_FILES);
670
+ const perFileLimit = Math.max(1000, Math.floor(remainingBudget / Math.max(filesToInclude.length, 1)));
639
671
 
640
- textContent += `IMPORTED COMPONENTS (${truncatedComponents.length} files, ${pageContext.componentSources.length > MAX_FILES ? `${pageContext.componentSources.length - MAX_FILES} omitted` : 'complete'}):\n`;
672
+ textContent += `SUPPORTING COMPONENTS (${filesToInclude.length} files, ~${Math.round(perFileLimit/1000)}k chars each):\n`;
641
673
 
642
- for (let i = 0; i < truncatedComponents.length; i++) {
643
- const comp = truncatedComponents[i];
644
- const isPriority = i < 10; // First 10 files are priority (direct imports)
645
- const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
646
-
647
- // Stop if we've used too much context
648
- if (usedContext > MAX_TOTAL_CONTEXT) {
649
- textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
674
+ for (const comp of filesToInclude) {
675
+ if (usedContext > TOTAL_CONTEXT_BUDGET - 10000) {
676
+ textContent += `\n// ... (remaining files omitted to stay within context limits)\n`;
650
677
  break;
651
678
  }
652
679
 
653
- const truncatedContent = comp.content.substring(0, maxSize);
654
- const wasTruncated = comp.content.length > maxSize;
680
+ const truncatedContent = comp.content.substring(0, perFileLimit);
681
+ const wasTruncated = comp.content.length > perFileLimit;
655
682
 
656
683
  textContent += `
657
- File: ${comp.path}${isPriority ? '' : ' (nested)'}
684
+ File: ${comp.path}
658
685
  \`\`\`tsx
659
686
  ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
660
687
  \`\`\`
@@ -663,25 +690,50 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
663
690
  }
664
691
  }
665
692
 
693
+ // ========== GLOBALS CSS ==========
694
+ const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
666
695
  textContent += `
667
- GLOBALS.CSS (relevant theme variables):
696
+ GLOBALS.CSS (theme variables):
668
697
  \`\`\`css
669
- ${pageContext.globalsCSS.substring(0, 2000)}${pageContext.globalsCSS.length > 2000 ? "\n/* ... (truncated) */" : ""}
698
+ ${globalsTruncated}${pageContext.globalsCSS.length > MAX_GLOBALS_CSS ? "\n/* ... (truncated) */" : ""}
670
699
  \`\`\`
671
700
 
672
- VALID FILES YOU MAY EDIT:
673
- ${pageContext.pageFile ? `- ${pageContext.pageFile}` : ""}
674
- ${pageContext.componentSources.map((c) => `- ${c.path}`).join("\n")}
701
+ `;
702
+
703
+ // ========== VALID FILES LIST ==========
704
+ const validFilesList: string[] = [];
705
+ const recommendedPath = recommendedFile?.path;
706
+
707
+ // Add recommended file first with marker
708
+ if (recommendedPath) {
709
+ validFilesList.push(`- ${recommendedPath} (*** TARGET - EDIT THIS FILE ***)`);
710
+ }
711
+
712
+ // Add page file (if not the recommended file)
713
+ if (pageContext.pageFile && pageContext.pageFile !== recommendedPath) {
714
+ validFilesList.push(`- ${pageContext.pageFile} (wrapper only)`);
715
+ }
716
+
717
+ // Add other component sources (excluding recommended file)
718
+ for (const comp of pageContext.componentSources) {
719
+ if (comp.path !== recommendedPath) {
720
+ validFilesList.push(`- ${comp.path}`);
721
+ }
722
+ }
723
+
724
+ textContent += `VALID FILES YOU MAY EDIT:
725
+ ${validFilesList.join("\n")}
675
726
 
676
727
  INSTRUCTIONS:
677
728
  1. Look at the screenshot and identify elements mentioned in the user's request
678
- 2. Review the code to understand current implementation
729
+ 2. The TARGET COMPONENT shown first contains the UI - EDIT THAT FILE
679
730
  3. Make SURGICAL EDITS - change only the specific lines needed
680
731
  4. PRESERVE all existing logic, hooks, API calls, and error handling
681
- 5. Return the FULL file content (no truncation, no "// ... existing ..." comments)
732
+ 5. Return patches in the specified format (search/replace)
682
733
  6. Only use file paths from the VALID FILES list above
734
+ 7. DO NOT edit the page wrapper unless the TARGET COMPONENT is unavailable
683
735
 
684
- CRITICAL: Your modified file should have approximately the same number of lines as the original. If the original has 200 lines and your output has 50 lines, you have made a mistake.`;
736
+ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
685
737
 
686
738
  messageContent.push({
687
739
  type: "text",
@@ -599,6 +599,27 @@ export async function POST(request: Request) {
599
599
  });
600
600
  }
601
601
 
602
+ // ========== SMART CONTEXT BUDGETING ==========
603
+ // Total budget: 100k chars (~25k tokens, safe for Claude)
604
+ // Priority: Recommended file (full) > Page file (limited) > Other components (dynamic)
605
+ const TOTAL_CONTEXT_BUDGET = 100000;
606
+ const MAX_RECOMMENDED_FILE = 50000; // 50k chars max for recommended file
607
+ const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
608
+ const MAX_GLOBALS_CSS = 1500;
609
+ const MAX_FILES = 25;
610
+
611
+ let usedContext = 0;
612
+
613
+ // Extract recommended file from component sources (to show first, avoid duplication)
614
+ let recommendedFileContent: { path: string; content: string } | null = null;
615
+ if (recommendedFile) {
616
+ const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
617
+ if (idx !== -1) {
618
+ recommendedFileContent = pageContext.componentSources[idx];
619
+ pageContext.componentSources.splice(idx, 1); // Remove to avoid duplication
620
+ }
621
+ }
622
+
602
623
  // Build text content
603
624
  let textContent = `VISION MODE EDIT REQUEST
604
625
 
@@ -607,64 +628,70 @@ User Request: "${userPrompt}"
607
628
 
608
629
  `;
609
630
 
610
- // Add recommendation if smart search identified a best match
611
- if (recommendedFile) {
612
- textContent += `═══════════════════════════════════════════════════════════════════════════════
613
- ⚡ RECOMMENDED FILE TO EDIT
614
- ═══════════════════════════════════════════════════════════════════════════════
615
-
616
- Based on the screenshot analysis, the component you should edit is:
617
- **${recommendedFile.path}**
618
- Reason: ${recommendedFile.reason}
619
-
620
- The page file (${pageContext.pageFile}) is just a wrapper - the actual UI elements are in the component above.
621
- STRONGLY PREFER editing the recommended file unless you have a specific reason not to.
631
+ if (focusedElements && focusedElements.length > 0) {
632
+ textContent += `FOCUSED ELEMENTS (user clicked on these):
633
+ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
622
634
 
623
635
  `;
624
636
  }
625
637
 
626
- if (focusedElements && focusedElements.length > 0) {
627
- textContent += `FOCUSED ELEMENTS (user clicked on these):
628
- ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
638
+ // ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
639
+ if (recommendedFileContent) {
640
+ const content = recommendedFileContent.content.length > MAX_RECOMMENDED_FILE
641
+ ? recommendedFileContent.content.substring(0, MAX_RECOMMENDED_FILE) + "\n// ... (file truncated at 50k chars, showing component definition and main logic)"
642
+ : recommendedFileContent.content;
643
+
644
+ textContent += `═══════════════════════════════════════════════════════════════════════════════
645
+ ⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
646
+ ═══════════════════════════════════════════════════════════════════════════════
647
+
648
+ This is the component that renders the UI you see in the screenshot.
649
+ File: ${recommendedFileContent.path}
650
+
651
+ \`\`\`tsx
652
+ ${content}
653
+ \`\`\`
629
654
 
630
655
  `;
656
+ usedContext += content.length;
657
+ debugLog("Added TARGET COMPONENT to context", {
658
+ path: recommendedFileContent.path,
659
+ originalSize: recommendedFileContent.content.length,
660
+ includedSize: content.length
661
+ });
631
662
  }
632
663
 
633
- textContent += `PAGE CONTEXT:
664
+ // ========== PAGE CONTEXT (wrapper - de-emphasized) ==========
665
+ const pageContentTruncated = pageContext.pageContent.substring(0, MAX_PAGE_FILE);
666
+ const pageWasTruncated = pageContext.pageContent.length > MAX_PAGE_FILE;
667
+
668
+ textContent += `PAGE CONTEXT (wrapper only${recommendedFileContent ? " - DO NOT edit this, edit the TARGET COMPONENT above" : ""}):
634
669
 
635
670
  Page File: ${pageContext.pageFile || "Not found"}
636
- ${pageContext.pageContent ? `\`\`\`tsx\n${pageContext.pageContent}\n\`\`\`` : ""}
671
+ ${pageContext.pageContent ? `\`\`\`tsx\n${pageContentTruncated}${pageWasTruncated ? "\n// ... (wrapper truncated)" : ""}\n\`\`\`` : ""}
637
672
 
638
673
  `;
674
+ usedContext += pageContentTruncated.length;
639
675
 
676
+ // ========== SUPPORTING COMPONENTS (dynamic budget) ==========
640
677
  if (pageContext.componentSources.length > 0) {
641
- // Smart truncation: prioritize first components (direct imports) and limit total context
642
- const MAX_TOTAL_CONTEXT = 80000; // ~80k chars to stay well under Claude's limit
643
- const MAX_PER_FILE_PRIORITY = 4000; // First 10 files get more space
644
- const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
645
- const MAX_FILES = 30; // Limit total number of files
646
-
647
- let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
648
- const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
678
+ const remainingBudget = TOTAL_CONTEXT_BUDGET - usedContext - MAX_GLOBALS_CSS - 5000; // Reserve 5k for instructions
679
+ const filesToInclude = pageContext.componentSources.slice(0, MAX_FILES);
680
+ const perFileLimit = Math.max(1000, Math.floor(remainingBudget / Math.max(filesToInclude.length, 1)));
649
681
 
650
- textContent += `IMPORTED COMPONENTS (${truncatedComponents.length} files, ${pageContext.componentSources.length > MAX_FILES ? `${pageContext.componentSources.length - MAX_FILES} omitted` : 'complete'}):\n`;
682
+ textContent += `SUPPORTING COMPONENTS (${filesToInclude.length} files, ~${Math.round(perFileLimit/1000)}k chars each):\n`;
651
683
 
652
- for (let i = 0; i < truncatedComponents.length; i++) {
653
- const comp = truncatedComponents[i];
654
- const isPriority = i < 10; // First 10 files are priority (direct imports)
655
- const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
656
-
657
- // Stop if we've used too much context
658
- if (usedContext > MAX_TOTAL_CONTEXT) {
659
- textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
684
+ for (const comp of filesToInclude) {
685
+ if (usedContext > TOTAL_CONTEXT_BUDGET - 10000) {
686
+ textContent += `\n// ... (remaining files omitted to stay within context limits)\n`;
660
687
  break;
661
688
  }
662
689
 
663
- const truncatedContent = comp.content.substring(0, maxSize);
664
- const wasTruncated = comp.content.length > maxSize;
690
+ const truncatedContent = comp.content.substring(0, perFileLimit);
691
+ const wasTruncated = comp.content.length > perFileLimit;
665
692
 
666
693
  textContent += `
667
- File: ${comp.path}${isPriority ? '' : ' (nested)'}
694
+ File: ${comp.path}
668
695
  \`\`\`tsx
669
696
  ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
670
697
  \`\`\`
@@ -673,25 +700,50 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
673
700
  }
674
701
  }
675
702
 
703
+ // ========== GLOBALS CSS ==========
704
+ const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
676
705
  textContent += `
677
- GLOBALS.CSS (relevant theme variables):
706
+ GLOBALS.CSS (theme variables):
678
707
  \`\`\`css
679
- ${pageContext.globalsCSS.substring(0, 2000)}${pageContext.globalsCSS.length > 2000 ? "\n/* ... (truncated) */" : ""}
708
+ ${globalsTruncated}${pageContext.globalsCSS.length > MAX_GLOBALS_CSS ? "\n/* ... (truncated) */" : ""}
680
709
  \`\`\`
681
710
 
682
- VALID FILES YOU MAY EDIT:
683
- ${pageContext.pageFile ? `- ${pageContext.pageFile}` : ""}
684
- ${pageContext.componentSources.map((c) => `- ${c.path}`).join("\n")}
711
+ `;
712
+
713
+ // ========== VALID FILES LIST ==========
714
+ const validFilesList: string[] = [];
715
+ const recommendedPath = recommendedFile?.path;
716
+
717
+ // Add recommended file first with marker
718
+ if (recommendedPath) {
719
+ validFilesList.push(`- ${recommendedPath} (*** TARGET - EDIT THIS FILE ***)`);
720
+ }
721
+
722
+ // Add page file (if not the recommended file)
723
+ if (pageContext.pageFile && pageContext.pageFile !== recommendedPath) {
724
+ validFilesList.push(`- ${pageContext.pageFile} (wrapper only)`);
725
+ }
726
+
727
+ // Add other component sources (excluding recommended file)
728
+ for (const comp of pageContext.componentSources) {
729
+ if (comp.path !== recommendedPath) {
730
+ validFilesList.push(`- ${comp.path}`);
731
+ }
732
+ }
733
+
734
+ textContent += `VALID FILES YOU MAY EDIT:
735
+ ${validFilesList.join("\n")}
685
736
 
686
737
  INSTRUCTIONS:
687
738
  1. Look at the screenshot and identify elements mentioned in the user's request
688
- 2. Review the provided code to understand current implementation
739
+ 2. The TARGET COMPONENT shown first contains the UI - EDIT THAT FILE
689
740
  3. Choose which of the VALID FILES above need modifications
690
741
  4. Generate complete modified code for each file
691
742
  5. Provide previewCSS for immediate visual feedback
692
743
  6. Return as JSON in the specified format
744
+ 7. DO NOT edit the page wrapper unless the TARGET COMPONENT is unavailable
693
745
 
694
- CRITICAL: Only use file paths from the VALID FILES list above. Do NOT create new files.`;
746
+ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
695
747
 
696
748
  messageContent.push({
697
749
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.40",
3
+ "version": "1.3.42",
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",