sonance-brand-mcp 1.3.25 → 1.3.27
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.
|
@@ -50,7 +50,11 @@ interface ApplyFirstRequest {
|
|
|
50
50
|
interface BackupManifest {
|
|
51
51
|
sessionId: string;
|
|
52
52
|
timestamp: number;
|
|
53
|
-
files: {
|
|
53
|
+
files: {
|
|
54
|
+
original: string;
|
|
55
|
+
backup: string;
|
|
56
|
+
isNewFile: boolean; // Track if file was newly created (needs deletion on revert)
|
|
57
|
+
}[];
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
const BACKUP_ROOT = ".sonance-backups";
|
|
@@ -230,14 +234,38 @@ ${pageContext.pageContent ? `\`\`\`tsx\n${pageContext.pageContent}\n\`\`\`` : ""
|
|
|
230
234
|
`;
|
|
231
235
|
|
|
232
236
|
if (pageContext.componentSources.length > 0) {
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
// Smart truncation: prioritize first components (direct imports) and limit total context
|
|
238
|
+
const MAX_TOTAL_CONTEXT = 80000; // ~80k chars to stay well under Claude's limit
|
|
239
|
+
const MAX_PER_FILE_PRIORITY = 4000; // First 10 files get more space
|
|
240
|
+
const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
|
|
241
|
+
const MAX_FILES = 30; // Limit total number of files
|
|
242
|
+
|
|
243
|
+
let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
|
|
244
|
+
const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
|
|
245
|
+
|
|
246
|
+
textContent += `IMPORTED COMPONENTS (${truncatedComponents.length} files, ${pageContext.componentSources.length > MAX_FILES ? `${pageContext.componentSources.length - MAX_FILES} omitted` : 'complete'}):\n`;
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < truncatedComponents.length; i++) {
|
|
249
|
+
const comp = truncatedComponents[i];
|
|
250
|
+
const isPriority = i < 10; // First 10 files are priority (direct imports)
|
|
251
|
+
const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
|
|
252
|
+
|
|
253
|
+
// Stop if we've used too much context
|
|
254
|
+
if (usedContext > MAX_TOTAL_CONTEXT) {
|
|
255
|
+
textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const truncatedContent = comp.content.substring(0, maxSize);
|
|
260
|
+
const wasTruncated = comp.content.length > maxSize;
|
|
261
|
+
|
|
235
262
|
textContent += `
|
|
236
|
-
File: ${comp.path}
|
|
263
|
+
File: ${comp.path}${isPriority ? '' : ' (nested)'}
|
|
237
264
|
\`\`\`tsx
|
|
238
|
-
${
|
|
265
|
+
${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
239
266
|
\`\`\`
|
|
240
267
|
`;
|
|
268
|
+
usedContext += truncatedContent.length;
|
|
241
269
|
}
|
|
242
270
|
}
|
|
243
271
|
|
|
@@ -340,11 +368,29 @@ CRITICAL: Your modified file should have approximately the same number of lines
|
|
|
340
368
|
});
|
|
341
369
|
}
|
|
342
370
|
|
|
371
|
+
// Build set of valid file paths from page context
|
|
372
|
+
const validFilePaths = new Set<string>();
|
|
373
|
+
if (pageContext.pageFile) {
|
|
374
|
+
validFilePaths.add(pageContext.pageFile);
|
|
375
|
+
}
|
|
376
|
+
for (const comp of pageContext.componentSources) {
|
|
377
|
+
validFilePaths.add(comp.path);
|
|
378
|
+
}
|
|
379
|
+
|
|
343
380
|
// Process modifications - apply patches to get modified content
|
|
344
381
|
const modifications: VisionFileModification[] = [];
|
|
345
382
|
const patchErrors: string[] = [];
|
|
346
383
|
|
|
347
384
|
for (const mod of aiResponse.modifications) {
|
|
385
|
+
// Validate that the file path is in the page context
|
|
386
|
+
// This prevents the AI from creating new files
|
|
387
|
+
if (!validFilePaths.has(mod.filePath)) {
|
|
388
|
+
console.warn(`[Apply-First] Rejected modification to unknown file: ${mod.filePath}`);
|
|
389
|
+
console.warn(`[Apply-First] Valid files are: ${Array.from(validFilePaths).join(", ")}`);
|
|
390
|
+
patchErrors.push(`${mod.filePath}: This file was not found in the page context. The AI can only modify existing files that are part of the current page.`);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
348
394
|
const fullPath = path.join(projectRoot, mod.filePath);
|
|
349
395
|
let originalContent = "";
|
|
350
396
|
if (fs.existsSync(fullPath)) {
|
|
@@ -470,6 +516,15 @@ async function applyChangesWithBackup(
|
|
|
470
516
|
const backupDir = path.join(projectRoot, BACKUP_ROOT, sessionId);
|
|
471
517
|
const backupPaths: string[] = [];
|
|
472
518
|
|
|
519
|
+
// Track which files exist before we modify them
|
|
520
|
+
const existingFiles = new Set<string>();
|
|
521
|
+
for (const mod of modifications) {
|
|
522
|
+
const fullPath = path.join(projectRoot, mod.filePath);
|
|
523
|
+
if (fs.existsSync(fullPath)) {
|
|
524
|
+
existingFiles.add(mod.filePath);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
473
528
|
try {
|
|
474
529
|
// Step 1: Create backup directory
|
|
475
530
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
@@ -478,7 +533,7 @@ async function applyChangesWithBackup(
|
|
|
478
533
|
for (const mod of modifications) {
|
|
479
534
|
const fullPath = path.join(projectRoot, mod.filePath);
|
|
480
535
|
|
|
481
|
-
if (
|
|
536
|
+
if (existingFiles.has(mod.filePath)) {
|
|
482
537
|
const backupPath = path.join(backupDir, mod.filePath);
|
|
483
538
|
const backupDirForFile = path.dirname(backupPath);
|
|
484
539
|
|
|
@@ -488,13 +543,14 @@ async function applyChangesWithBackup(
|
|
|
488
543
|
}
|
|
489
544
|
}
|
|
490
545
|
|
|
491
|
-
// Step 3: Write manifest
|
|
546
|
+
// Step 3: Write manifest (track which files are new)
|
|
492
547
|
const manifest: BackupManifest = {
|
|
493
548
|
sessionId,
|
|
494
549
|
timestamp: Date.now(),
|
|
495
550
|
files: modifications.map(m => ({
|
|
496
551
|
original: m.filePath,
|
|
497
552
|
backup: path.join(backupDir, m.filePath),
|
|
553
|
+
isNewFile: !existingFiles.has(m.filePath), // Track if file was newly created
|
|
498
554
|
})),
|
|
499
555
|
};
|
|
500
556
|
|
|
@@ -574,24 +630,51 @@ async function revertFromBackups(
|
|
|
574
630
|
);
|
|
575
631
|
|
|
576
632
|
let filesReverted = 0;
|
|
633
|
+
let filesDeleted = 0;
|
|
577
634
|
|
|
578
635
|
for (const file of manifest.files) {
|
|
579
|
-
const backupPath = path.join(backupDir, file.original);
|
|
580
636
|
const originalPath = path.join(projectRoot, file.original);
|
|
581
637
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
638
|
+
// Handle new files - delete them
|
|
639
|
+
if (file.isNewFile) {
|
|
640
|
+
if (fs.existsSync(originalPath)) {
|
|
641
|
+
fs.unlinkSync(originalPath);
|
|
642
|
+
filesDeleted++;
|
|
643
|
+
console.log(`[Revert] Deleted new file: ${file.original}`);
|
|
644
|
+
|
|
645
|
+
// Clean up empty parent directories
|
|
646
|
+
try {
|
|
647
|
+
const parentDir = path.dirname(originalPath);
|
|
648
|
+
const entries = fs.readdirSync(parentDir);
|
|
649
|
+
if (entries.length === 0) {
|
|
650
|
+
fs.rmdirSync(parentDir);
|
|
651
|
+
console.log(`[Revert] Removed empty directory: ${path.dirname(file.original)}`);
|
|
652
|
+
}
|
|
653
|
+
} catch {
|
|
654
|
+
// Ignore errors when cleaning up directories
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
// Handle existing files - restore from backup
|
|
659
|
+
const backupPath = path.join(backupDir, file.original);
|
|
660
|
+
if (fs.existsSync(backupPath)) {
|
|
661
|
+
fs.copyFileSync(backupPath, originalPath);
|
|
662
|
+
filesReverted++;
|
|
663
|
+
}
|
|
585
664
|
}
|
|
586
665
|
}
|
|
587
666
|
|
|
588
667
|
// Delete backup directory after successful revert
|
|
589
668
|
fs.rmSync(backupDir, { recursive: true });
|
|
590
669
|
|
|
670
|
+
const message = filesDeleted > 0
|
|
671
|
+
? `Reverted ${filesReverted} file(s), deleted ${filesDeleted} new file(s)`
|
|
672
|
+
: `Reverted ${filesReverted} file(s)`;
|
|
673
|
+
|
|
591
674
|
return {
|
|
592
675
|
success: true,
|
|
593
|
-
message
|
|
594
|
-
filesReverted,
|
|
676
|
+
message,
|
|
677
|
+
filesReverted: filesReverted + filesDeleted,
|
|
595
678
|
};
|
|
596
679
|
} catch (error) {
|
|
597
680
|
console.error("Error reverting from backups:", error);
|
|
@@ -603,8 +686,109 @@ async function revertFromBackups(
|
|
|
603
686
|
}
|
|
604
687
|
}
|
|
605
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Recursively gather all imports from a file up to a max depth
|
|
691
|
+
* This builds a complete component graph for the AI to understand
|
|
692
|
+
*/
|
|
693
|
+
function gatherAllImports(
|
|
694
|
+
filePath: string,
|
|
695
|
+
projectRoot: string,
|
|
696
|
+
visited: Set<string> = new Set(),
|
|
697
|
+
maxDepth: number = 4
|
|
698
|
+
): { path: string; content: string }[] {
|
|
699
|
+
// Prevent infinite loops and limit total files
|
|
700
|
+
if (visited.has(filePath) || visited.size > 50) return [];
|
|
701
|
+
visited.add(filePath);
|
|
702
|
+
|
|
703
|
+
const results: { path: string; content: string }[] = [];
|
|
704
|
+
const fullPath = path.join(projectRoot, filePath);
|
|
705
|
+
|
|
706
|
+
if (!fs.existsSync(fullPath)) return results;
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
710
|
+
results.push({ path: filePath, content });
|
|
711
|
+
|
|
712
|
+
// Continue recursing if we haven't hit max depth
|
|
713
|
+
if (maxDepth > 0) {
|
|
714
|
+
const imports = extractImports(content);
|
|
715
|
+
for (const imp of imports) {
|
|
716
|
+
const resolved = resolveImportPath(imp, filePath, projectRoot);
|
|
717
|
+
if (resolved && !visited.has(resolved)) {
|
|
718
|
+
const nestedImports = gatherAllImports(resolved, projectRoot, visited, maxDepth - 1);
|
|
719
|
+
results.push(...nestedImports);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
// Skip files that can't be read
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return results;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Discover layout files that wrap the page
|
|
732
|
+
* App Router: layout.tsx in same and parent directories
|
|
733
|
+
* Pages Router: _app.tsx and _document.tsx
|
|
734
|
+
*/
|
|
735
|
+
function discoverLayoutFiles(pageFile: string | null, projectRoot: string): string[] {
|
|
736
|
+
const layoutFiles: string[] = [];
|
|
737
|
+
|
|
738
|
+
if (!pageFile) return layoutFiles;
|
|
739
|
+
|
|
740
|
+
// Determine if App Router or Pages Router
|
|
741
|
+
const isAppRouter = pageFile.includes("/app/") || pageFile.startsWith("app/");
|
|
742
|
+
const isPagesRouter = pageFile.includes("/pages/") || pageFile.startsWith("pages/");
|
|
743
|
+
|
|
744
|
+
if (isAppRouter) {
|
|
745
|
+
// App Router: Check for layout.tsx in same directory and parent directories
|
|
746
|
+
let currentDir = path.dirname(pageFile);
|
|
747
|
+
const appRoot = pageFile.includes("src/app") ? "src/app" : "app";
|
|
748
|
+
|
|
749
|
+
while (currentDir.includes(appRoot)) {
|
|
750
|
+
const layoutPatterns = [
|
|
751
|
+
path.join(currentDir, "layout.tsx"),
|
|
752
|
+
path.join(currentDir, "layout.jsx"),
|
|
753
|
+
];
|
|
754
|
+
|
|
755
|
+
for (const layoutPath of layoutPatterns) {
|
|
756
|
+
if (fs.existsSync(path.join(projectRoot, layoutPath))) {
|
|
757
|
+
layoutFiles.push(layoutPath);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Move to parent directory
|
|
762
|
+
const parentDir = path.dirname(currentDir);
|
|
763
|
+
if (parentDir === currentDir) break;
|
|
764
|
+
currentDir = parentDir;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (isPagesRouter) {
|
|
769
|
+
// Pages Router: Check for _app.tsx and _document.tsx
|
|
770
|
+
const pagesRoot = pageFile.includes("src/pages") ? "src/pages" : "pages";
|
|
771
|
+
|
|
772
|
+
const pagesRouterLayouts = [
|
|
773
|
+
`${pagesRoot}/_app.tsx`,
|
|
774
|
+
`${pagesRoot}/_app.jsx`,
|
|
775
|
+
`${pagesRoot}/_document.tsx`,
|
|
776
|
+
`${pagesRoot}/_document.jsx`,
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
for (const layoutPath of pagesRouterLayouts) {
|
|
780
|
+
if (fs.existsSync(path.join(projectRoot, layoutPath))) {
|
|
781
|
+
layoutFiles.push(layoutPath);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return layoutFiles;
|
|
787
|
+
}
|
|
788
|
+
|
|
606
789
|
/**
|
|
607
790
|
* Gather context about the current page for AI analysis
|
|
791
|
+
* Uses recursive import resolution to build complete component graph
|
|
608
792
|
*/
|
|
609
793
|
function gatherPageContext(
|
|
610
794
|
pageRoute: string,
|
|
@@ -618,50 +802,102 @@ function gatherPageContext(
|
|
|
618
802
|
const pageFile = discoverPageFile(pageRoute, projectRoot);
|
|
619
803
|
let pageContent = "";
|
|
620
804
|
const componentSources: { path: string; content: string }[] = [];
|
|
805
|
+
const visited = new Set<string>();
|
|
621
806
|
|
|
622
807
|
if (pageFile) {
|
|
623
808
|
const fullPath = path.join(projectRoot, pageFile);
|
|
624
809
|
if (fs.existsSync(fullPath)) {
|
|
625
810
|
pageContent = fs.readFileSync(fullPath, "utf-8");
|
|
811
|
+
visited.add(pageFile);
|
|
626
812
|
|
|
813
|
+
// Recursively gather all imported components (up to 4 levels deep)
|
|
627
814
|
const imports = extractImports(pageContent);
|
|
628
815
|
for (const importPath of imports) {
|
|
629
816
|
const resolvedPath = resolveImportPath(importPath, pageFile, projectRoot);
|
|
630
|
-
if (resolvedPath &&
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
817
|
+
if (resolvedPath && !visited.has(resolvedPath)) {
|
|
818
|
+
const nestedComponents = gatherAllImports(resolvedPath, projectRoot, visited, 3);
|
|
819
|
+
componentSources.push(...nestedComponents);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Also include layout files
|
|
824
|
+
const layoutFiles = discoverLayoutFiles(pageFile, projectRoot);
|
|
825
|
+
for (const layoutFile of layoutFiles) {
|
|
826
|
+
if (!visited.has(layoutFile)) {
|
|
827
|
+
const layoutComponents = gatherAllImports(layoutFile, projectRoot, visited, 2);
|
|
828
|
+
componentSources.push(...layoutComponents);
|
|
637
829
|
}
|
|
638
830
|
}
|
|
639
831
|
}
|
|
640
832
|
}
|
|
641
833
|
|
|
642
834
|
let globalsCSS = "";
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
835
|
+
const globalsCSSPatterns = [
|
|
836
|
+
"src/app/globals.css",
|
|
837
|
+
"app/globals.css",
|
|
838
|
+
"src/styles/globals.css",
|
|
839
|
+
"styles/globals.css",
|
|
840
|
+
"src/styles/global.css",
|
|
841
|
+
"styles/global.css",
|
|
842
|
+
];
|
|
843
|
+
|
|
844
|
+
for (const cssPattern of globalsCSSPatterns) {
|
|
845
|
+
const globalsPath = path.join(projectRoot, cssPattern);
|
|
846
|
+
if (fs.existsSync(globalsPath)) {
|
|
847
|
+
globalsCSS = fs.readFileSync(globalsPath, "utf-8");
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
646
850
|
}
|
|
647
851
|
|
|
648
852
|
return { pageFile, pageContent, componentSources, globalsCSS };
|
|
649
853
|
}
|
|
650
854
|
|
|
651
855
|
function discoverPageFile(route: string, projectRoot: string): string | null {
|
|
856
|
+
// Handle root route
|
|
652
857
|
if (route === "/" || route === "") {
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
858
|
+
const rootPatterns = [
|
|
859
|
+
// App Router patterns
|
|
860
|
+
"src/app/page.tsx",
|
|
861
|
+
"src/app/page.jsx",
|
|
862
|
+
"app/page.tsx",
|
|
863
|
+
"app/page.jsx",
|
|
864
|
+
// Pages Router patterns
|
|
865
|
+
"src/pages/index.tsx",
|
|
866
|
+
"src/pages/index.jsx",
|
|
867
|
+
"pages/index.tsx",
|
|
868
|
+
"pages/index.jsx",
|
|
869
|
+
];
|
|
870
|
+
|
|
871
|
+
for (const pattern of rootPatterns) {
|
|
872
|
+
if (fs.existsSync(path.join(projectRoot, pattern))) {
|
|
873
|
+
return pattern;
|
|
874
|
+
}
|
|
656
875
|
}
|
|
657
876
|
return null;
|
|
658
877
|
}
|
|
659
878
|
|
|
660
879
|
const cleanRoute = route.replace(/^\//, "");
|
|
880
|
+
|
|
881
|
+
// First, try exact match patterns
|
|
661
882
|
const patterns = [
|
|
883
|
+
// App Router patterns (with src)
|
|
662
884
|
`src/app/${cleanRoute}/page.tsx`,
|
|
663
885
|
`src/app/${cleanRoute}/page.jsx`,
|
|
664
|
-
|
|
886
|
+
// App Router patterns (without src)
|
|
887
|
+
`app/${cleanRoute}/page.tsx`,
|
|
888
|
+
`app/${cleanRoute}/page.jsx`,
|
|
889
|
+
// Pages Router patterns (with src) - file-based
|
|
890
|
+
`src/pages/${cleanRoute}.tsx`,
|
|
891
|
+
`src/pages/${cleanRoute}.jsx`,
|
|
892
|
+
// Pages Router patterns (with src) - folder-based
|
|
893
|
+
`src/pages/${cleanRoute}/index.tsx`,
|
|
894
|
+
`src/pages/${cleanRoute}/index.jsx`,
|
|
895
|
+
// Pages Router patterns (without src) - file-based
|
|
896
|
+
`pages/${cleanRoute}.tsx`,
|
|
897
|
+
`pages/${cleanRoute}.jsx`,
|
|
898
|
+
// Pages Router patterns (without src) - folder-based
|
|
899
|
+
`pages/${cleanRoute}/index.tsx`,
|
|
900
|
+
`pages/${cleanRoute}/index.jsx`,
|
|
665
901
|
];
|
|
666
902
|
|
|
667
903
|
for (const pattern of patterns) {
|
|
@@ -670,6 +906,99 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
|
|
|
670
906
|
}
|
|
671
907
|
}
|
|
672
908
|
|
|
909
|
+
// If exact match not found, try dynamic route matching
|
|
910
|
+
// e.g., /processes/123 -> src/pages/processes/[id].tsx
|
|
911
|
+
const dynamicResult = findDynamicRoute(cleanRoute, projectRoot);
|
|
912
|
+
if (dynamicResult) {
|
|
913
|
+
return dynamicResult;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Find dynamic route files when exact match fails
|
|
921
|
+
* Maps runtime routes like "/processes/123" to file paths like "src/pages/processes/[id].tsx"
|
|
922
|
+
*/
|
|
923
|
+
function findDynamicRoute(cleanRoute: string, projectRoot: string): string | null {
|
|
924
|
+
const segments = cleanRoute.split("/");
|
|
925
|
+
|
|
926
|
+
// Try replacing the last segment with dynamic patterns
|
|
927
|
+
const baseDirs = [
|
|
928
|
+
"src/app",
|
|
929
|
+
"app",
|
|
930
|
+
"src/pages",
|
|
931
|
+
"pages",
|
|
932
|
+
];
|
|
933
|
+
|
|
934
|
+
const dynamicPatterns = ["[id]", "[slug]", "[...slug]", "[[...slug]]"];
|
|
935
|
+
const extensions = [".tsx", ".jsx"];
|
|
936
|
+
|
|
937
|
+
for (const baseDir of baseDirs) {
|
|
938
|
+
const basePath = path.join(projectRoot, baseDir);
|
|
939
|
+
if (!fs.existsSync(basePath)) continue;
|
|
940
|
+
|
|
941
|
+
// Build path with all segments except the last one
|
|
942
|
+
const parentSegments = segments.slice(0, -1);
|
|
943
|
+
const parentPath = parentSegments.length > 0
|
|
944
|
+
? path.join(basePath, ...parentSegments)
|
|
945
|
+
: basePath;
|
|
946
|
+
|
|
947
|
+
if (!fs.existsSync(parentPath)) continue;
|
|
948
|
+
|
|
949
|
+
// Check for dynamic route files
|
|
950
|
+
for (const dynPattern of dynamicPatterns) {
|
|
951
|
+
for (const ext of extensions) {
|
|
952
|
+
// App Router: parent/[id]/page.tsx
|
|
953
|
+
if (baseDir.includes("app")) {
|
|
954
|
+
const appRouterPath = path.join(parentPath, dynPattern, `page${ext}`);
|
|
955
|
+
if (fs.existsSync(appRouterPath)) {
|
|
956
|
+
return path.relative(projectRoot, appRouterPath);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Pages Router: parent/[id].tsx
|
|
961
|
+
const pagesRouterFile = path.join(parentPath, `${dynPattern}${ext}`);
|
|
962
|
+
if (fs.existsSync(pagesRouterFile)) {
|
|
963
|
+
return path.relative(projectRoot, pagesRouterFile);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Pages Router: parent/[id]/index.tsx
|
|
967
|
+
const pagesRouterDir = path.join(parentPath, dynPattern, `index${ext}`);
|
|
968
|
+
if (fs.existsSync(pagesRouterDir)) {
|
|
969
|
+
return path.relative(projectRoot, pagesRouterDir);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Also scan directory for any dynamic segment pattern [...]
|
|
975
|
+
try {
|
|
976
|
+
const entries = fs.readdirSync(parentPath, { withFileTypes: true });
|
|
977
|
+
for (const entry of entries) {
|
|
978
|
+
if (entry.name.startsWith("[") && entry.name.includes("]")) {
|
|
979
|
+
for (const ext of extensions) {
|
|
980
|
+
if (entry.isDirectory()) {
|
|
981
|
+
// App Router or Pages Router with folder
|
|
982
|
+
const pagePath = path.join(parentPath, entry.name, `page${ext}`);
|
|
983
|
+
const indexPath = path.join(parentPath, entry.name, `index${ext}`);
|
|
984
|
+
if (fs.existsSync(pagePath)) {
|
|
985
|
+
return path.relative(projectRoot, pagePath);
|
|
986
|
+
}
|
|
987
|
+
if (fs.existsSync(indexPath)) {
|
|
988
|
+
return path.relative(projectRoot, indexPath);
|
|
989
|
+
}
|
|
990
|
+
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
991
|
+
// Pages Router file-based
|
|
992
|
+
return path.relative(projectRoot, path.join(parentPath, entry.name));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
} catch {
|
|
998
|
+
// Skip if directory can't be read
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
673
1002
|
return null;
|
|
674
1003
|
}
|
|
675
1004
|
|
|
@@ -243,14 +243,38 @@ ${pageContext.pageContent ? `\`\`\`tsx\n${pageContext.pageContent}\n\`\`\`` : ""
|
|
|
243
243
|
`;
|
|
244
244
|
|
|
245
245
|
if (pageContext.componentSources.length > 0) {
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
// Smart truncation: prioritize first components (direct imports) and limit total context
|
|
247
|
+
const MAX_TOTAL_CONTEXT = 80000; // ~80k chars to stay well under Claude's limit
|
|
248
|
+
const MAX_PER_FILE_PRIORITY = 4000; // First 10 files get more space
|
|
249
|
+
const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
|
|
250
|
+
const MAX_FILES = 30; // Limit total number of files
|
|
251
|
+
|
|
252
|
+
let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
|
|
253
|
+
const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
|
|
254
|
+
|
|
255
|
+
textContent += `IMPORTED COMPONENTS (${truncatedComponents.length} files, ${pageContext.componentSources.length > MAX_FILES ? `${pageContext.componentSources.length - MAX_FILES} omitted` : 'complete'}):\n`;
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < truncatedComponents.length; i++) {
|
|
258
|
+
const comp = truncatedComponents[i];
|
|
259
|
+
const isPriority = i < 10; // First 10 files are priority (direct imports)
|
|
260
|
+
const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
|
|
261
|
+
|
|
262
|
+
// Stop if we've used too much context
|
|
263
|
+
if (usedContext > MAX_TOTAL_CONTEXT) {
|
|
264
|
+
textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const truncatedContent = comp.content.substring(0, maxSize);
|
|
269
|
+
const wasTruncated = comp.content.length > maxSize;
|
|
270
|
+
|
|
248
271
|
textContent += `
|
|
249
|
-
File: ${comp.path}
|
|
272
|
+
File: ${comp.path}${isPriority ? '' : ' (nested)'}
|
|
250
273
|
\`\`\`tsx
|
|
251
|
-
${
|
|
274
|
+
${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
252
275
|
\`\`\`
|
|
253
276
|
`;
|
|
277
|
+
usedContext += truncatedContent.length;
|
|
254
278
|
}
|
|
255
279
|
}
|
|
256
280
|
|
|
@@ -551,8 +575,109 @@ function findComponentFileByName(
|
|
|
551
575
|
return null;
|
|
552
576
|
}
|
|
553
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Recursively gather all imports from a file up to a max depth
|
|
580
|
+
* This builds a complete component graph for the AI to understand
|
|
581
|
+
*/
|
|
582
|
+
function gatherAllImports(
|
|
583
|
+
filePath: string,
|
|
584
|
+
projectRoot: string,
|
|
585
|
+
visited: Set<string> = new Set(),
|
|
586
|
+
maxDepth: number = 4
|
|
587
|
+
): { path: string; content: string }[] {
|
|
588
|
+
// Prevent infinite loops and limit total files
|
|
589
|
+
if (visited.has(filePath) || visited.size > 50) return [];
|
|
590
|
+
visited.add(filePath);
|
|
591
|
+
|
|
592
|
+
const results: { path: string; content: string }[] = [];
|
|
593
|
+
const fullPath = path.join(projectRoot, filePath);
|
|
594
|
+
|
|
595
|
+
if (!fs.existsSync(fullPath)) return results;
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
599
|
+
results.push({ path: filePath, content });
|
|
600
|
+
|
|
601
|
+
// Continue recursing if we haven't hit max depth
|
|
602
|
+
if (maxDepth > 0) {
|
|
603
|
+
const imports = extractImports(content);
|
|
604
|
+
for (const imp of imports) {
|
|
605
|
+
const resolved = resolveImportPath(imp, filePath, projectRoot);
|
|
606
|
+
if (resolved && !visited.has(resolved)) {
|
|
607
|
+
const nestedImports = gatherAllImports(resolved, projectRoot, visited, maxDepth - 1);
|
|
608
|
+
results.push(...nestedImports);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
613
|
+
// Skip files that can't be read
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return results;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Discover layout files that wrap the page
|
|
621
|
+
* App Router: layout.tsx in same and parent directories
|
|
622
|
+
* Pages Router: _app.tsx and _document.tsx
|
|
623
|
+
*/
|
|
624
|
+
function discoverLayoutFiles(pageFile: string | null, projectRoot: string): string[] {
|
|
625
|
+
const layoutFiles: string[] = [];
|
|
626
|
+
|
|
627
|
+
if (!pageFile) return layoutFiles;
|
|
628
|
+
|
|
629
|
+
// Determine if App Router or Pages Router
|
|
630
|
+
const isAppRouter = pageFile.includes("/app/") || pageFile.startsWith("app/");
|
|
631
|
+
const isPagesRouter = pageFile.includes("/pages/") || pageFile.startsWith("pages/");
|
|
632
|
+
|
|
633
|
+
if (isAppRouter) {
|
|
634
|
+
// App Router: Check for layout.tsx in same directory and parent directories
|
|
635
|
+
let currentDir = path.dirname(pageFile);
|
|
636
|
+
const appRoot = pageFile.includes("src/app") ? "src/app" : "app";
|
|
637
|
+
|
|
638
|
+
while (currentDir.includes(appRoot)) {
|
|
639
|
+
const layoutPatterns = [
|
|
640
|
+
path.join(currentDir, "layout.tsx"),
|
|
641
|
+
path.join(currentDir, "layout.jsx"),
|
|
642
|
+
];
|
|
643
|
+
|
|
644
|
+
for (const layoutPath of layoutPatterns) {
|
|
645
|
+
if (fs.existsSync(path.join(projectRoot, layoutPath))) {
|
|
646
|
+
layoutFiles.push(layoutPath);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Move to parent directory
|
|
651
|
+
const parentDir = path.dirname(currentDir);
|
|
652
|
+
if (parentDir === currentDir) break;
|
|
653
|
+
currentDir = parentDir;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (isPagesRouter) {
|
|
658
|
+
// Pages Router: Check for _app.tsx and _document.tsx
|
|
659
|
+
const pagesRoot = pageFile.includes("src/pages") ? "src/pages" : "pages";
|
|
660
|
+
|
|
661
|
+
const pagesRouterLayouts = [
|
|
662
|
+
`${pagesRoot}/_app.tsx`,
|
|
663
|
+
`${pagesRoot}/_app.jsx`,
|
|
664
|
+
`${pagesRoot}/_document.tsx`,
|
|
665
|
+
`${pagesRoot}/_document.jsx`,
|
|
666
|
+
];
|
|
667
|
+
|
|
668
|
+
for (const layoutPath of pagesRouterLayouts) {
|
|
669
|
+
if (fs.existsSync(path.join(projectRoot, layoutPath))) {
|
|
670
|
+
layoutFiles.push(layoutPath);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return layoutFiles;
|
|
676
|
+
}
|
|
677
|
+
|
|
554
678
|
/**
|
|
555
679
|
* Gather context about the current page for AI analysis
|
|
680
|
+
* Uses recursive import resolution to build complete component graph
|
|
556
681
|
*/
|
|
557
682
|
function gatherPageContext(
|
|
558
683
|
pageRoute: string,
|
|
@@ -568,23 +693,30 @@ function gatherPageContext(
|
|
|
568
693
|
const pageFile = discoverPageFile(pageRoute, projectRoot);
|
|
569
694
|
let pageContent = "";
|
|
570
695
|
const componentSources: { path: string; content: string }[] = [];
|
|
696
|
+
const visited = new Set<string>();
|
|
571
697
|
|
|
572
698
|
if (pageFile) {
|
|
573
699
|
const fullPath = path.join(projectRoot, pageFile);
|
|
574
700
|
if (fs.existsSync(fullPath)) {
|
|
575
701
|
pageContent = fs.readFileSync(fullPath, "utf-8");
|
|
702
|
+
visited.add(pageFile);
|
|
576
703
|
|
|
577
|
-
//
|
|
704
|
+
// Recursively gather all imported components (up to 4 levels deep)
|
|
578
705
|
const imports = extractImports(pageContent);
|
|
579
706
|
for (const importPath of imports) {
|
|
580
707
|
const resolvedPath = resolveImportPath(importPath, pageFile, projectRoot);
|
|
581
|
-
if (resolvedPath &&
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
708
|
+
if (resolvedPath && !visited.has(resolvedPath)) {
|
|
709
|
+
const nestedComponents = gatherAllImports(resolvedPath, projectRoot, visited, 3);
|
|
710
|
+
componentSources.push(...nestedComponents);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Also include layout files
|
|
715
|
+
const layoutFiles = discoverLayoutFiles(pageFile, projectRoot);
|
|
716
|
+
for (const layoutFile of layoutFiles) {
|
|
717
|
+
if (!visited.has(layoutFile)) {
|
|
718
|
+
const layoutComponents = gatherAllImports(layoutFile, projectRoot, visited, 2);
|
|
719
|
+
componentSources.push(...layoutComponents);
|
|
588
720
|
}
|
|
589
721
|
}
|
|
590
722
|
}
|
|
@@ -595,25 +727,30 @@ function gatherPageContext(
|
|
|
595
727
|
for (const el of focusedElements) {
|
|
596
728
|
// Try to find component file by name
|
|
597
729
|
const foundPath = findComponentFileByName(el.name, projectRoot);
|
|
598
|
-
if (foundPath && !componentSources.some((c) => c.path === foundPath)) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
path.join(projectRoot, foundPath),
|
|
602
|
-
"utf-8"
|
|
603
|
-
);
|
|
604
|
-
componentSources.push({ path: foundPath, content });
|
|
605
|
-
} catch {
|
|
606
|
-
/* skip if unreadable */
|
|
607
|
-
}
|
|
730
|
+
if (foundPath && !visited.has(foundPath) && !componentSources.some((c) => c.path === foundPath)) {
|
|
731
|
+
const focusedComponents = gatherAllImports(foundPath, projectRoot, visited, 2);
|
|
732
|
+
componentSources.push(...focusedComponents);
|
|
608
733
|
}
|
|
609
734
|
}
|
|
610
735
|
}
|
|
611
736
|
|
|
612
|
-
// Read globals.css
|
|
737
|
+
// Read globals.css - check multiple possible locations
|
|
613
738
|
let globalsCSS = "";
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
739
|
+
const globalsCSSPatterns = [
|
|
740
|
+
"src/app/globals.css",
|
|
741
|
+
"app/globals.css",
|
|
742
|
+
"src/styles/globals.css",
|
|
743
|
+
"styles/globals.css",
|
|
744
|
+
"src/styles/global.css",
|
|
745
|
+
"styles/global.css",
|
|
746
|
+
];
|
|
747
|
+
|
|
748
|
+
for (const cssPattern of globalsCSSPatterns) {
|
|
749
|
+
const globalsPath = path.join(projectRoot, cssPattern);
|
|
750
|
+
if (fs.existsSync(globalsPath)) {
|
|
751
|
+
globalsCSS = fs.readFileSync(globalsPath, "utf-8");
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
617
754
|
}
|
|
618
755
|
|
|
619
756
|
return { pageFile, pageContent, componentSources, globalsCSS };
|
|
@@ -621,13 +758,28 @@ function gatherPageContext(
|
|
|
621
758
|
|
|
622
759
|
/**
|
|
623
760
|
* Discover the page file for a given route
|
|
761
|
+
* Supports both App Router (src/app/) and Pages Router (src/pages/, pages/)
|
|
624
762
|
*/
|
|
625
763
|
function discoverPageFile(route: string, projectRoot: string): string | null {
|
|
626
764
|
// Handle root route
|
|
627
765
|
if (route === "/" || route === "") {
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
766
|
+
const rootPatterns = [
|
|
767
|
+
// App Router patterns
|
|
768
|
+
"src/app/page.tsx",
|
|
769
|
+
"src/app/page.jsx",
|
|
770
|
+
"app/page.tsx",
|
|
771
|
+
"app/page.jsx",
|
|
772
|
+
// Pages Router patterns
|
|
773
|
+
"src/pages/index.tsx",
|
|
774
|
+
"src/pages/index.jsx",
|
|
775
|
+
"pages/index.tsx",
|
|
776
|
+
"pages/index.jsx",
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
for (const pattern of rootPatterns) {
|
|
780
|
+
if (fs.existsSync(path.join(projectRoot, pattern))) {
|
|
781
|
+
return pattern;
|
|
782
|
+
}
|
|
631
783
|
}
|
|
632
784
|
return null;
|
|
633
785
|
}
|
|
@@ -635,11 +787,26 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
|
|
|
635
787
|
// Remove leading slash
|
|
636
788
|
const cleanRoute = route.replace(/^\//, "");
|
|
637
789
|
|
|
638
|
-
//
|
|
790
|
+
// First, try exact match patterns
|
|
639
791
|
const patterns = [
|
|
792
|
+
// App Router patterns (with src)
|
|
640
793
|
`src/app/${cleanRoute}/page.tsx`,
|
|
641
794
|
`src/app/${cleanRoute}/page.jsx`,
|
|
642
|
-
|
|
795
|
+
// App Router patterns (without src)
|
|
796
|
+
`app/${cleanRoute}/page.tsx`,
|
|
797
|
+
`app/${cleanRoute}/page.jsx`,
|
|
798
|
+
// Pages Router patterns (with src) - file-based
|
|
799
|
+
`src/pages/${cleanRoute}.tsx`,
|
|
800
|
+
`src/pages/${cleanRoute}.jsx`,
|
|
801
|
+
// Pages Router patterns (with src) - folder-based
|
|
802
|
+
`src/pages/${cleanRoute}/index.tsx`,
|
|
803
|
+
`src/pages/${cleanRoute}/index.jsx`,
|
|
804
|
+
// Pages Router patterns (without src) - file-based
|
|
805
|
+
`pages/${cleanRoute}.tsx`,
|
|
806
|
+
`pages/${cleanRoute}.jsx`,
|
|
807
|
+
// Pages Router patterns (without src) - folder-based
|
|
808
|
+
`pages/${cleanRoute}/index.tsx`,
|
|
809
|
+
`pages/${cleanRoute}/index.jsx`,
|
|
643
810
|
];
|
|
644
811
|
|
|
645
812
|
for (const pattern of patterns) {
|
|
@@ -648,6 +815,99 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
|
|
|
648
815
|
}
|
|
649
816
|
}
|
|
650
817
|
|
|
818
|
+
// If exact match not found, try dynamic route matching
|
|
819
|
+
// e.g., /processes/123 -> src/pages/processes/[id].tsx
|
|
820
|
+
const dynamicResult = findDynamicRoute(cleanRoute, projectRoot);
|
|
821
|
+
if (dynamicResult) {
|
|
822
|
+
return dynamicResult;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Find dynamic route files when exact match fails
|
|
830
|
+
* Maps runtime routes like "/processes/123" to file paths like "src/pages/processes/[id].tsx"
|
|
831
|
+
*/
|
|
832
|
+
function findDynamicRoute(cleanRoute: string, projectRoot: string): string | null {
|
|
833
|
+
const segments = cleanRoute.split("/");
|
|
834
|
+
|
|
835
|
+
// Try replacing the last segment with dynamic patterns
|
|
836
|
+
const baseDirs = [
|
|
837
|
+
"src/app",
|
|
838
|
+
"app",
|
|
839
|
+
"src/pages",
|
|
840
|
+
"pages",
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
const dynamicPatterns = ["[id]", "[slug]", "[...slug]", "[[...slug]]"];
|
|
844
|
+
const extensions = [".tsx", ".jsx"];
|
|
845
|
+
|
|
846
|
+
for (const baseDir of baseDirs) {
|
|
847
|
+
const basePath = path.join(projectRoot, baseDir);
|
|
848
|
+
if (!fs.existsSync(basePath)) continue;
|
|
849
|
+
|
|
850
|
+
// Build path with all segments except the last one
|
|
851
|
+
const parentSegments = segments.slice(0, -1);
|
|
852
|
+
const parentPath = parentSegments.length > 0
|
|
853
|
+
? path.join(basePath, ...parentSegments)
|
|
854
|
+
: basePath;
|
|
855
|
+
|
|
856
|
+
if (!fs.existsSync(parentPath)) continue;
|
|
857
|
+
|
|
858
|
+
// Check for dynamic route files
|
|
859
|
+
for (const dynPattern of dynamicPatterns) {
|
|
860
|
+
for (const ext of extensions) {
|
|
861
|
+
// App Router: parent/[id]/page.tsx
|
|
862
|
+
if (baseDir.includes("app")) {
|
|
863
|
+
const appRouterPath = path.join(parentPath, dynPattern, `page${ext}`);
|
|
864
|
+
if (fs.existsSync(appRouterPath)) {
|
|
865
|
+
return path.relative(projectRoot, appRouterPath);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Pages Router: parent/[id].tsx
|
|
870
|
+
const pagesRouterFile = path.join(parentPath, `${dynPattern}${ext}`);
|
|
871
|
+
if (fs.existsSync(pagesRouterFile)) {
|
|
872
|
+
return path.relative(projectRoot, pagesRouterFile);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Pages Router: parent/[id]/index.tsx
|
|
876
|
+
const pagesRouterDir = path.join(parentPath, dynPattern, `index${ext}`);
|
|
877
|
+
if (fs.existsSync(pagesRouterDir)) {
|
|
878
|
+
return path.relative(projectRoot, pagesRouterDir);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Also scan directory for any dynamic segment pattern [...]
|
|
884
|
+
try {
|
|
885
|
+
const entries = fs.readdirSync(parentPath, { withFileTypes: true });
|
|
886
|
+
for (const entry of entries) {
|
|
887
|
+
if (entry.name.startsWith("[") && entry.name.includes("]")) {
|
|
888
|
+
for (const ext of extensions) {
|
|
889
|
+
if (entry.isDirectory()) {
|
|
890
|
+
// App Router or Pages Router with folder
|
|
891
|
+
const pagePath = path.join(parentPath, entry.name, `page${ext}`);
|
|
892
|
+
const indexPath = path.join(parentPath, entry.name, `index${ext}`);
|
|
893
|
+
if (fs.existsSync(pagePath)) {
|
|
894
|
+
return path.relative(projectRoot, pagePath);
|
|
895
|
+
}
|
|
896
|
+
if (fs.existsSync(indexPath)) {
|
|
897
|
+
return path.relative(projectRoot, indexPath);
|
|
898
|
+
}
|
|
899
|
+
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
900
|
+
// Pages Router file-based
|
|
901
|
+
return path.relative(projectRoot, path.join(parentPath, entry.name));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
} catch {
|
|
907
|
+
// Skip if directory can't be read
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
651
911
|
return null;
|
|
652
912
|
}
|
|
653
913
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.27",
|
|
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",
|