sonance-brand-mcp 1.3.27 → 1.3.28

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.
@@ -58,6 +58,26 @@ interface BackupManifest {
58
58
  }
59
59
 
60
60
  const BACKUP_ROOT = ".sonance-backups";
61
+ const DEBUG_LOG_FILE = "sonance-debug.log";
62
+
63
+ /**
64
+ * Debug logging utility - writes to sonance-debug.log in project root
65
+ * This helps diagnose issues with file discovery, validation, and revert
66
+ */
67
+ function debugLog(message: string, data?: unknown) {
68
+ const timestamp = new Date().toISOString();
69
+ const dataStr = data !== undefined ? `\n${JSON.stringify(data, null, 2)}` : "";
70
+ const logEntry = `[${timestamp}] [apply] ${message}${dataStr}\n`;
71
+
72
+ try {
73
+ const logPath = path.join(process.cwd(), DEBUG_LOG_FILE);
74
+ fs.appendFileSync(logPath, logEntry);
75
+ // Also log to console for terminal visibility
76
+ console.log(`[Sonance] ${message}`, data !== undefined ? data : "");
77
+ } catch (e) {
78
+ console.error("[Sonance] Failed to write debug log:", e);
79
+ }
80
+ }
61
81
 
62
82
  const VISION_SYSTEM_PROMPT = `You are an expert React/TypeScript developer with vision capabilities. You can see screenshots and modify code.
63
83
 
@@ -377,6 +397,12 @@ CRITICAL: Your modified file should have approximately the same number of lines
377
397
  validFilePaths.add(comp.path);
378
398
  }
379
399
 
400
+ debugLog("VALIDATION: Valid file paths from page context", {
401
+ pageFile: pageContext.pageFile,
402
+ validFilePaths: Array.from(validFilePaths),
403
+ aiRequestedFiles: aiResponse.modifications.map(m => m.filePath)
404
+ });
405
+
380
406
  // Process modifications - apply patches to get modified content
381
407
  const modifications: VisionFileModification[] = [];
382
408
  const patchErrors: string[] = [];
@@ -384,7 +410,15 @@ CRITICAL: Your modified file should have approximately the same number of lines
384
410
  for (const mod of aiResponse.modifications) {
385
411
  // Validate that the file path is in the page context
386
412
  // This prevents the AI from creating new files
387
- if (!validFilePaths.has(mod.filePath)) {
413
+ const isValidPath = validFilePaths.has(mod.filePath);
414
+ debugLog(`VALIDATION CHECK: ${mod.filePath}`, {
415
+ isValidPath,
416
+ fileExists: fs.existsSync(path.join(projectRoot, mod.filePath)),
417
+ validPaths: Array.from(validFilePaths)
418
+ });
419
+
420
+ if (!isValidPath) {
421
+ debugLog(`REJECTED: File not in page context`, { filePath: mod.filePath });
388
422
  console.warn(`[Apply-First] Rejected modification to unknown file: ${mod.filePath}`);
389
423
  console.warn(`[Apply-First] Valid files are: ${Array.from(validFilePaths).join(", ")}`);
390
424
  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.`);
@@ -603,9 +637,11 @@ async function revertFromBackups(
603
637
  sessionId: string,
604
638
  projectRoot: string
605
639
  ): Promise<{ success: boolean; message: string; filesReverted: number }> {
640
+ debugLog("revertFromBackups called", { sessionId, projectRoot });
606
641
  const backupDir = path.join(projectRoot, BACKUP_ROOT, sessionId);
607
642
 
608
643
  if (!fs.existsSync(backupDir)) {
644
+ debugLog("REVERT ERROR: Backup directory not found", { backupDir });
609
645
  return {
610
646
  success: false,
611
647
  message: "Backup session not found",
@@ -617,7 +653,7 @@ async function revertFromBackups(
617
653
  const manifestPath = path.join(backupDir, "manifest.json");
618
654
 
619
655
  if (!fs.existsSync(manifestPath)) {
620
- // If no manifest, try to restore any backup files we find
656
+ debugLog("REVERT ERROR: Manifest not found", { manifestPath });
621
657
  return {
622
658
  success: false,
623
659
  message: "Backup manifest not found",
@@ -628,18 +664,31 @@ async function revertFromBackups(
628
664
  const manifest: BackupManifest = JSON.parse(
629
665
  fs.readFileSync(manifestPath, "utf-8")
630
666
  );
667
+
668
+ debugLog("REVERT: Loaded manifest", {
669
+ sessionId: manifest.sessionId,
670
+ fileCount: manifest.files.length,
671
+ files: manifest.files.map(f => ({ path: f.original, isNewFile: f.isNewFile }))
672
+ });
631
673
 
632
674
  let filesReverted = 0;
633
675
  let filesDeleted = 0;
634
676
 
635
677
  for (const file of manifest.files) {
636
678
  const originalPath = path.join(projectRoot, file.original);
679
+ debugLog(`REVERT: Processing file`, {
680
+ filePath: file.original,
681
+ isNewFile: file.isNewFile,
682
+ fileExists: fs.existsSync(originalPath)
683
+ });
637
684
 
638
685
  // Handle new files - delete them
639
686
  if (file.isNewFile) {
687
+ debugLog(`REVERT: Attempting to delete new file`, { originalPath });
640
688
  if (fs.existsSync(originalPath)) {
641
689
  fs.unlinkSync(originalPath);
642
690
  filesDeleted++;
691
+ debugLog(`REVERT: Successfully deleted new file`, { originalPath });
643
692
  console.log(`[Revert] Deleted new file: ${file.original}`);
644
693
 
645
694
  // Clean up empty parent directories
@@ -853,8 +902,11 @@ function gatherPageContext(
853
902
  }
854
903
 
855
904
  function discoverPageFile(route: string, projectRoot: string): string | null {
905
+ debugLog("discoverPageFile called", { route, projectRoot });
906
+
856
907
  // Handle root route
857
908
  if (route === "/" || route === "") {
909
+ debugLog("Handling root route, checking patterns...");
858
910
  const rootPatterns = [
859
911
  // App Router patterns
860
912
  "src/app/page.tsx",
@@ -901,18 +953,26 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
901
953
  ];
902
954
 
903
955
  for (const pattern of patterns) {
904
- if (fs.existsSync(path.join(projectRoot, pattern))) {
956
+ const fullPath = path.join(projectRoot, pattern);
957
+ const exists = fs.existsSync(fullPath);
958
+ debugLog(`Checking exact pattern: ${pattern}`, { exists });
959
+ if (exists) {
960
+ debugLog("Found exact match!", { pattern });
905
961
  return pattern;
906
962
  }
907
963
  }
908
964
 
965
+ debugLog("No exact match found, trying dynamic route matching...");
966
+
909
967
  // If exact match not found, try dynamic route matching
910
968
  // e.g., /processes/123 -> src/pages/processes/[id].tsx
911
969
  const dynamicResult = findDynamicRoute(cleanRoute, projectRoot);
912
970
  if (dynamicResult) {
971
+ debugLog("Found dynamic route match!", { dynamicResult });
913
972
  return dynamicResult;
914
973
  }
915
974
 
975
+ debugLog("discoverPageFile: NO FILE FOUND for route", { route });
916
976
  return null;
917
977
  }
918
978
 
@@ -922,6 +982,7 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
922
982
  */
923
983
  function findDynamicRoute(cleanRoute: string, projectRoot: string): string | null {
924
984
  const segments = cleanRoute.split("/");
985
+ debugLog("findDynamicRoute called", { cleanRoute, segments });
925
986
 
926
987
  // Try replacing the last segment with dynamic patterns
927
988
  const baseDirs = [
@@ -936,7 +997,10 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
936
997
 
937
998
  for (const baseDir of baseDirs) {
938
999
  const basePath = path.join(projectRoot, baseDir);
939
- if (!fs.existsSync(basePath)) continue;
1000
+ if (!fs.existsSync(basePath)) {
1001
+ debugLog(`Base dir does not exist: ${baseDir}`);
1002
+ continue;
1003
+ }
940
1004
 
941
1005
  // Build path with all segments except the last one
942
1006
  const parentSegments = segments.slice(0, -1);
@@ -944,6 +1008,8 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
944
1008
  ? path.join(basePath, ...parentSegments)
945
1009
  : basePath;
946
1010
 
1011
+ debugLog(`Checking parent path for dynamic routes`, { baseDir, parentPath, exists: fs.existsSync(parentPath) });
1012
+
947
1013
  if (!fs.existsSync(parentPath)) continue;
948
1014
 
949
1015
  // Check for dynamic route files
@@ -974,31 +1040,43 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
974
1040
  // Also scan directory for any dynamic segment pattern [...]
975
1041
  try {
976
1042
  const entries = fs.readdirSync(parentPath, { withFileTypes: true });
1043
+ const dynamicEntries = entries.filter(e => e.name.startsWith("[") && e.name.includes("]"));
1044
+ debugLog(`Scanning directory for dynamic segments`, { parentPath, dynamicEntries: dynamicEntries.map(e => e.name) });
1045
+
977
1046
  for (const entry of entries) {
978
1047
  if (entry.name.startsWith("[") && entry.name.includes("]")) {
1048
+ debugLog(`Found dynamic segment: ${entry.name}`, { isDirectory: entry.isDirectory() });
979
1049
  for (const ext of extensions) {
980
1050
  if (entry.isDirectory()) {
981
1051
  // App Router or Pages Router with folder
982
1052
  const pagePath = path.join(parentPath, entry.name, `page${ext}`);
983
1053
  const indexPath = path.join(parentPath, entry.name, `index${ext}`);
1054
+ debugLog(`Checking dynamic folder paths`, { pagePath, indexPath, pageExists: fs.existsSync(pagePath), indexExists: fs.existsSync(indexPath) });
984
1055
  if (fs.existsSync(pagePath)) {
985
- return path.relative(projectRoot, pagePath);
1056
+ const result = path.relative(projectRoot, pagePath);
1057
+ debugLog(`Found dynamic route (folder/page)!`, { result });
1058
+ return result;
986
1059
  }
987
1060
  if (fs.existsSync(indexPath)) {
988
- return path.relative(projectRoot, indexPath);
1061
+ const result = path.relative(projectRoot, indexPath);
1062
+ debugLog(`Found dynamic route (folder/index)!`, { result });
1063
+ return result;
989
1064
  }
990
1065
  } else if (entry.isFile() && entry.name.endsWith(ext)) {
991
1066
  // Pages Router file-based
992
- return path.relative(projectRoot, path.join(parentPath, entry.name));
1067
+ const result = path.relative(projectRoot, path.join(parentPath, entry.name));
1068
+ debugLog(`Found dynamic route (file)!`, { result });
1069
+ return result;
993
1070
  }
994
1071
  }
995
1072
  }
996
1073
  }
997
- } catch {
998
- // Skip if directory can't be read
1074
+ } catch (e) {
1075
+ debugLog(`Error scanning directory: ${parentPath}`, { error: String(e) });
999
1076
  }
1000
1077
  }
1001
1078
 
1079
+ debugLog("findDynamicRoute: NO DYNAMIC ROUTE FOUND");
1002
1080
  return null;
1003
1081
  }
1004
1082
 
@@ -56,6 +56,27 @@ interface VisionEditResponse {
56
56
  error?: string;
57
57
  }
58
58
 
59
+ const DEBUG_LOG_FILE = "sonance-debug.log";
60
+
61
+ /**
62
+ * Debug logging utility - writes to sonance-debug.log in project root
63
+ * This helps diagnose issues with file discovery, validation, and revert
64
+ */
65
+ function debugLog(message: string, data?: unknown) {
66
+ const timestamp = new Date().toISOString();
67
+ const dataStr = data !== undefined ? `\n${JSON.stringify(data, null, 2)}` : "";
68
+ const logEntry = `[${timestamp}] [edit] ${message}${dataStr}\n`;
69
+
70
+ try {
71
+ const logPath = path.join(process.cwd(), DEBUG_LOG_FILE);
72
+ fs.appendFileSync(logPath, logEntry);
73
+ // Also log to console for terminal visibility
74
+ console.log(`[Sonance] ${message}`, data !== undefined ? data : "");
75
+ } catch (e) {
76
+ console.error("[Sonance] Failed to write debug log:", e);
77
+ }
78
+ }
79
+
59
80
  const VISION_SYSTEM_PROMPT = `You are an expert React/TypeScript developer with vision capabilities. You can see screenshots and modify code.
60
81
 
61
82
  ═══════════════════════════════════════════════════════════════════════════════
@@ -381,12 +402,19 @@ CRITICAL: Only use file paths from the VALID FILES list above. Do NOT create new
381
402
  validPaths.add(comp.path);
382
403
  }
383
404
 
405
+ debugLog("VALIDATION: Valid file paths from page context", {
406
+ pageFile: pageContext.pageFile,
407
+ validPaths: Array.from(validPaths),
408
+ aiRequestedFiles: (aiResponse.modifications || []).map(m => m.filePath)
409
+ });
410
+
384
411
  // Validate AI response - reject any file paths not in our valid list
385
412
  const invalidMods = (aiResponse.modifications || []).filter(
386
413
  (mod) => !validPaths.has(mod.filePath)
387
414
  );
388
415
 
389
416
  if (invalidMods.length > 0) {
417
+ debugLog("REJECTED: AI attempted to create new files", { invalidMods: invalidMods.map(m => m.filePath) });
390
418
  console.error(
391
419
  "AI attempted to create new files:",
392
420
  invalidMods.map((m) => m.filePath)
@@ -761,8 +789,11 @@ function gatherPageContext(
761
789
  * Supports both App Router (src/app/) and Pages Router (src/pages/, pages/)
762
790
  */
763
791
  function discoverPageFile(route: string, projectRoot: string): string | null {
792
+ debugLog("discoverPageFile called", { route, projectRoot });
793
+
764
794
  // Handle root route
765
795
  if (route === "/" || route === "") {
796
+ debugLog("Handling root route, checking patterns...");
766
797
  const rootPatterns = [
767
798
  // App Router patterns
768
799
  "src/app/page.tsx",
@@ -810,18 +841,26 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
810
841
  ];
811
842
 
812
843
  for (const pattern of patterns) {
813
- if (fs.existsSync(path.join(projectRoot, pattern))) {
844
+ const fullPath = path.join(projectRoot, pattern);
845
+ const exists = fs.existsSync(fullPath);
846
+ debugLog(`Checking exact pattern: ${pattern}`, { exists });
847
+ if (exists) {
848
+ debugLog("Found exact match!", { pattern });
814
849
  return pattern;
815
850
  }
816
851
  }
817
852
 
853
+ debugLog("No exact match found, trying dynamic route matching...");
854
+
818
855
  // If exact match not found, try dynamic route matching
819
856
  // e.g., /processes/123 -> src/pages/processes/[id].tsx
820
857
  const dynamicResult = findDynamicRoute(cleanRoute, projectRoot);
821
858
  if (dynamicResult) {
859
+ debugLog("Found dynamic route match!", { dynamicResult });
822
860
  return dynamicResult;
823
861
  }
824
862
 
863
+ debugLog("discoverPageFile: NO FILE FOUND for route", { route });
825
864
  return null;
826
865
  }
827
866
 
@@ -831,6 +870,7 @@ function discoverPageFile(route: string, projectRoot: string): string | null {
831
870
  */
832
871
  function findDynamicRoute(cleanRoute: string, projectRoot: string): string | null {
833
872
  const segments = cleanRoute.split("/");
873
+ debugLog("findDynamicRoute called", { cleanRoute, segments });
834
874
 
835
875
  // Try replacing the last segment with dynamic patterns
836
876
  const baseDirs = [
@@ -845,7 +885,10 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
845
885
 
846
886
  for (const baseDir of baseDirs) {
847
887
  const basePath = path.join(projectRoot, baseDir);
848
- if (!fs.existsSync(basePath)) continue;
888
+ if (!fs.existsSync(basePath)) {
889
+ debugLog(`Base dir does not exist: ${baseDir}`);
890
+ continue;
891
+ }
849
892
 
850
893
  // Build path with all segments except the last one
851
894
  const parentSegments = segments.slice(0, -1);
@@ -853,6 +896,8 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
853
896
  ? path.join(basePath, ...parentSegments)
854
897
  : basePath;
855
898
 
899
+ debugLog(`Checking parent path for dynamic routes`, { baseDir, parentPath, exists: fs.existsSync(parentPath) });
900
+
856
901
  if (!fs.existsSync(parentPath)) continue;
857
902
 
858
903
  // Check for dynamic route files
@@ -883,31 +928,43 @@ function findDynamicRoute(cleanRoute: string, projectRoot: string): string | nul
883
928
  // Also scan directory for any dynamic segment pattern [...]
884
929
  try {
885
930
  const entries = fs.readdirSync(parentPath, { withFileTypes: true });
931
+ const dynamicEntries = entries.filter(e => e.name.startsWith("[") && e.name.includes("]"));
932
+ debugLog(`Scanning directory for dynamic segments`, { parentPath, dynamicEntries: dynamicEntries.map(e => e.name) });
933
+
886
934
  for (const entry of entries) {
887
935
  if (entry.name.startsWith("[") && entry.name.includes("]")) {
936
+ debugLog(`Found dynamic segment: ${entry.name}`, { isDirectory: entry.isDirectory() });
888
937
  for (const ext of extensions) {
889
938
  if (entry.isDirectory()) {
890
939
  // App Router or Pages Router with folder
891
940
  const pagePath = path.join(parentPath, entry.name, `page${ext}`);
892
941
  const indexPath = path.join(parentPath, entry.name, `index${ext}`);
942
+ debugLog(`Checking dynamic folder paths`, { pagePath, indexPath, pageExists: fs.existsSync(pagePath), indexExists: fs.existsSync(indexPath) });
893
943
  if (fs.existsSync(pagePath)) {
894
- return path.relative(projectRoot, pagePath);
944
+ const result = path.relative(projectRoot, pagePath);
945
+ debugLog(`Found dynamic route (folder/page)!`, { result });
946
+ return result;
895
947
  }
896
948
  if (fs.existsSync(indexPath)) {
897
- return path.relative(projectRoot, indexPath);
949
+ const result = path.relative(projectRoot, indexPath);
950
+ debugLog(`Found dynamic route (folder/index)!`, { result });
951
+ return result;
898
952
  }
899
953
  } else if (entry.isFile() && entry.name.endsWith(ext)) {
900
954
  // Pages Router file-based
901
- return path.relative(projectRoot, path.join(parentPath, entry.name));
955
+ const result = path.relative(projectRoot, path.join(parentPath, entry.name));
956
+ debugLog(`Found dynamic route (file)!`, { result });
957
+ return result;
902
958
  }
903
959
  }
904
960
  }
905
961
  }
906
- } catch {
907
- // Skip if directory can't be read
962
+ } catch (e) {
963
+ debugLog(`Error scanning directory: ${parentPath}`, { error: String(e) });
908
964
  }
909
965
  }
910
966
 
967
+ debugLog("findDynamicRoute: NO DYNAMIC ROUTE FOUND");
911
968
  return null;
912
969
  }
913
970
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.27",
3
+ "version": "1.3.28",
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",