sonance-brand-mcp 1.3.47 → 1.3.49
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.
|
@@ -468,6 +468,8 @@ Return search/replace patches (NOT full files). The system applies your patches
|
|
|
468
468
|
- "replace" contains your modified version
|
|
469
469
|
- Include 2-4 lines of context in "search" to make it unique
|
|
470
470
|
- You may ONLY edit files provided in the PAGE CONTEXT section
|
|
471
|
+
- CRITICAL: NEVER invent or guess code. Your "search" string MUST be copied EXACTLY from the provided file content. If you cannot find the exact code to modify, return an empty modifications array.
|
|
472
|
+
- If the file content appears truncated, only modify code that is visible in the provided content.
|
|
471
473
|
|
|
472
474
|
**SONANCE BRAND COLORS:**
|
|
473
475
|
- Charcoal: #333F48 (primary text)
|
|
@@ -543,6 +545,7 @@ export async function POST(request: Request) {
|
|
|
543
545
|
return NextResponse.json({
|
|
544
546
|
success: result.success,
|
|
545
547
|
message: result.message,
|
|
548
|
+
error: result.success ? undefined : result.message, // Include error for client compatibility
|
|
546
549
|
filesReverted: result.filesReverted,
|
|
547
550
|
});
|
|
548
551
|
}
|
|
@@ -658,10 +661,10 @@ export async function POST(request: Request) {
|
|
|
658
661
|
}
|
|
659
662
|
|
|
660
663
|
// ========== SMART CONTEXT BUDGETING ==========
|
|
661
|
-
//
|
|
662
|
-
// Priority: Recommended file (
|
|
663
|
-
const TOTAL_CONTEXT_BUDGET =
|
|
664
|
-
const MAX_RECOMMENDED_FILE =
|
|
664
|
+
// Claude can handle 200k tokens (~800k chars), so we can safely include large files
|
|
665
|
+
// Priority: Recommended file (NEVER truncate) > Page file (limited) > Other components (dynamic)
|
|
666
|
+
const TOTAL_CONTEXT_BUDGET = 500000; // 500k chars total budget
|
|
667
|
+
const MAX_RECOMMENDED_FILE = Infinity; // NEVER truncate the target file - AI needs full context
|
|
665
668
|
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
666
669
|
const MAX_GLOBALS_CSS = 1500;
|
|
667
670
|
const MAX_FILES = 25;
|
|
@@ -695,9 +698,8 @@ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}
|
|
|
695
698
|
|
|
696
699
|
// ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
|
|
697
700
|
if (recommendedFileContent) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
: recommendedFileContent.content;
|
|
701
|
+
// Never truncate the recommended file - AI needs full context to avoid hallucination
|
|
702
|
+
const content = recommendedFileContent.content;
|
|
701
703
|
|
|
702
704
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
703
705
|
⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
|
|
@@ -808,112 +810,166 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
808
810
|
text: textContent,
|
|
809
811
|
});
|
|
810
812
|
|
|
811
|
-
// Call Claude Vision API
|
|
813
|
+
// Call Claude Vision API with retry mechanism
|
|
812
814
|
const anthropic = new Anthropic({ apiKey });
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
815
|
+
|
|
816
|
+
// Build set of valid file paths from page context (needed for validation)
|
|
817
|
+
const validFilePaths = new Set<string>();
|
|
818
|
+
if (pageContext.pageFile) {
|
|
819
|
+
validFilePaths.add(pageContext.pageFile);
|
|
820
|
+
}
|
|
821
|
+
for (const comp of pageContext.componentSources) {
|
|
822
|
+
validFilePaths.add(comp.path);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// FIX: Add the recommended file to validFilePaths (it was spliced out for display purposes)
|
|
826
|
+
if (recommendedFileContent) {
|
|
827
|
+
validFilePaths.add(recommendedFileContent.path);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Retry loop for handling patch failures
|
|
831
|
+
const MAX_RETRIES = 1;
|
|
832
|
+
let retryCount = 0;
|
|
833
|
+
let lastPatchErrors: string[] = [];
|
|
834
|
+
let modifications: VisionFileModification[] = [];
|
|
835
|
+
let finalExplanation: string | undefined;
|
|
836
|
+
let finalReasoning: string | undefined;
|
|
837
|
+
|
|
838
|
+
while (retryCount <= MAX_RETRIES) {
|
|
839
|
+
// Build messages for this attempt
|
|
840
|
+
const currentMessages: Anthropic.MessageCreateParams["messages"] = [
|
|
818
841
|
{
|
|
819
842
|
role: "user",
|
|
820
843
|
content: messageContent,
|
|
821
844
|
},
|
|
822
|
-
]
|
|
823
|
-
|
|
824
|
-
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
// If this is a retry, add feedback about what went wrong
|
|
848
|
+
if (retryCount > 0 && lastPatchErrors.length > 0) {
|
|
849
|
+
debugLog("Retry attempt with feedback", { retryCount, errorCount: lastPatchErrors.length });
|
|
850
|
+
|
|
851
|
+
// Get a snippet of the actual file content to help AI find correct code
|
|
852
|
+
let fileSnippet = "";
|
|
853
|
+
if (recommendedFileContent) {
|
|
854
|
+
// Show first 800 chars to give AI context about what code actually exists
|
|
855
|
+
fileSnippet = recommendedFileContent.content.substring(0, 800).replace(/\n/g, "\n");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
currentMessages.push({
|
|
859
|
+
role: "assistant",
|
|
860
|
+
content: "I'll analyze the screenshot and generate the patches now.",
|
|
861
|
+
});
|
|
862
|
+
currentMessages.push({
|
|
863
|
+
role: "user",
|
|
864
|
+
content: `PATCH APPLICATION FAILED. Your previous patches referenced code that does not exist in the file (hallucination detected).
|
|
825
865
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (!textResponse || textResponse.type !== "text") {
|
|
829
|
-
return NextResponse.json(
|
|
830
|
-
{ error: "No text response from AI" },
|
|
831
|
-
{ status: 500 }
|
|
832
|
-
);
|
|
833
|
-
}
|
|
866
|
+
Failed patches:
|
|
867
|
+
${lastPatchErrors.join("\n\n")}
|
|
834
868
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
869
|
+
${fileSnippet ? `Here is the ACTUAL beginning of the file you're trying to edit:
|
|
870
|
+
\`\`\`tsx
|
|
871
|
+
${fileSnippet}...
|
|
872
|
+
\`\`\`
|
|
873
|
+
` : ""}
|
|
874
|
+
CRITICAL INSTRUCTIONS:
|
|
875
|
+
1. Your "search" string MUST be copied EXACTLY from the file content I provided above
|
|
876
|
+
2. Do NOT invent, guess, or imagine code that might exist
|
|
877
|
+
3. Look for the ACTUAL code in the TARGET COMPONENT section and copy it exactly
|
|
878
|
+
|
|
879
|
+
If you still cannot find the exact code to modify after reviewing the file:
|
|
880
|
+
- Return {"modifications": [], "explanation": "Could not locate the exact code. Please specify which element you want to change more precisely."}
|
|
881
|
+
|
|
882
|
+
This is better than generating patches with made-up code.`,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const response = await anthropic.messages.create({
|
|
887
|
+
model: "claude-sonnet-4-20250514",
|
|
888
|
+
max_tokens: 16384,
|
|
889
|
+
messages: currentMessages,
|
|
890
|
+
system: VISION_SYSTEM_PROMPT,
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
// Extract text content from response
|
|
894
|
+
const textResponse = response.content.find((block) => block.type === "text");
|
|
895
|
+
if (!textResponse || textResponse.type !== "text") {
|
|
896
|
+
return NextResponse.json(
|
|
897
|
+
{ error: "No text response from AI" },
|
|
898
|
+
{ status: 500 }
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Parse AI response - now expecting patches instead of full file content
|
|
903
|
+
let aiResponse: {
|
|
904
|
+
reasoning?: string;
|
|
905
|
+
modifications: Array<{
|
|
906
|
+
filePath: string;
|
|
907
|
+
patches?: Patch[];
|
|
908
|
+
// Legacy support for modifiedContent (will be deprecated)
|
|
909
|
+
modifiedContent?: string;
|
|
910
|
+
explanation?: string;
|
|
911
|
+
}>;
|
|
843
912
|
explanation?: string;
|
|
844
|
-
}
|
|
845
|
-
explanation?: string;
|
|
846
|
-
};
|
|
913
|
+
};
|
|
847
914
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
915
|
+
try {
|
|
916
|
+
let jsonText = textResponse.text.trim();
|
|
917
|
+
|
|
918
|
+
// Try to extract JSON from markdown code blocks
|
|
919
|
+
const jsonMatch = jsonText.match(/```json\n([\s\S]*?)\n```/) ||
|
|
920
|
+
jsonText.match(/```\n([\s\S]*?)\n```/);
|
|
921
|
+
|
|
922
|
+
if (jsonMatch) {
|
|
923
|
+
jsonText = jsonMatch[1];
|
|
924
|
+
} else if (jsonText.includes("```json")) {
|
|
925
|
+
const start = jsonText.indexOf("```json") + 7;
|
|
926
|
+
const end = jsonText.lastIndexOf("```");
|
|
927
|
+
if (end > start) {
|
|
928
|
+
jsonText = jsonText.substring(start, end);
|
|
929
|
+
}
|
|
862
930
|
}
|
|
863
|
-
}
|
|
864
931
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
932
|
+
jsonText = jsonText.trim();
|
|
933
|
+
|
|
934
|
+
// Robust JSON extraction: find the first { and last } to extract JSON object
|
|
935
|
+
// This handles cases where the LLM includes preamble text before the JSON
|
|
936
|
+
const firstBrace = jsonText.indexOf('{');
|
|
937
|
+
const lastBrace = jsonText.lastIndexOf('}');
|
|
938
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
939
|
+
jsonText = jsonText.substring(firstBrace, lastBrace + 1);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
aiResponse = JSON.parse(jsonText);
|
|
943
|
+
} catch {
|
|
944
|
+
console.error("Failed to parse AI response:", textResponse.text);
|
|
945
|
+
return NextResponse.json(
|
|
946
|
+
{ error: "Failed to parse AI response. Please try again." },
|
|
947
|
+
{ status: 500 }
|
|
948
|
+
);
|
|
873
949
|
}
|
|
874
|
-
|
|
875
|
-
aiResponse = JSON.parse(jsonText);
|
|
876
|
-
} catch {
|
|
877
|
-
console.error("Failed to parse AI response:", textResponse.text);
|
|
878
|
-
return NextResponse.json(
|
|
879
|
-
{ error: "Failed to parse AI response. Please try again." },
|
|
880
|
-
{ status: 500 }
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
950
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
success: true,
|
|
887
|
-
sessionId: newSessionId,
|
|
888
|
-
modifications: [],
|
|
889
|
-
explanation: aiResponse.explanation || "No changes needed.",
|
|
890
|
-
reasoning: aiResponse.reasoning,
|
|
891
|
-
});
|
|
892
|
-
}
|
|
951
|
+
finalExplanation = aiResponse.explanation;
|
|
952
|
+
finalReasoning = aiResponse.reasoning;
|
|
893
953
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
// FIX: Add the recommended file to validFilePaths (it was spliced out for display purposes)
|
|
904
|
-
if (recommendedFileContent) {
|
|
905
|
-
validFilePaths.add(recommendedFileContent.path);
|
|
906
|
-
}
|
|
954
|
+
if (!aiResponse.modifications || aiResponse.modifications.length === 0) {
|
|
955
|
+
return NextResponse.json({
|
|
956
|
+
success: true,
|
|
957
|
+
sessionId: newSessionId,
|
|
958
|
+
modifications: [],
|
|
959
|
+
explanation: aiResponse.explanation || "No changes needed.",
|
|
960
|
+
reasoning: aiResponse.reasoning,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
907
963
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
964
|
+
debugLog("VALIDATION: Valid file paths from page context", {
|
|
965
|
+
pageFile: pageContext.pageFile,
|
|
966
|
+
validFilePaths: Array.from(validFilePaths),
|
|
967
|
+
aiRequestedFiles: aiResponse.modifications.map(m => m.filePath)
|
|
968
|
+
});
|
|
913
969
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
970
|
+
// Process modifications - apply patches to get modified content
|
|
971
|
+
modifications = [];
|
|
972
|
+
const patchErrors: string[] = [];
|
|
917
973
|
|
|
918
974
|
for (const mod of aiResponse.modifications) {
|
|
919
975
|
// Validate that the file path is in the page context
|
|
@@ -947,6 +1003,47 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
947
1003
|
// New patch-based approach
|
|
948
1004
|
console.log(`[Apply-First] Applying ${mod.patches.length} patches to ${mod.filePath}`);
|
|
949
1005
|
|
|
1006
|
+
// PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
|
|
1007
|
+
const preValidationErrors: string[] = [];
|
|
1008
|
+
for (const patch of mod.patches) {
|
|
1009
|
+
const normalizedSearch = patch.search.replace(/\\n/g, "\n");
|
|
1010
|
+
if (!originalContent.includes(normalizedSearch)) {
|
|
1011
|
+
// Try fuzzy match as fallback
|
|
1012
|
+
const fuzzyMatch = findFuzzyMatch(normalizedSearch, originalContent);
|
|
1013
|
+
if (!fuzzyMatch) {
|
|
1014
|
+
// Find the closest matching snippet to help with debugging
|
|
1015
|
+
const searchPreview = normalizedSearch.substring(0, 80).replace(/\n/g, "\\n");
|
|
1016
|
+
|
|
1017
|
+
// Look for partial matches to give helpful feedback
|
|
1018
|
+
const searchLines = normalizedSearch.split("\n").filter(l => l.trim().length > 10);
|
|
1019
|
+
const partialMatches: string[] = [];
|
|
1020
|
+
for (const line of searchLines.slice(0, 3)) {
|
|
1021
|
+
const trimmedLine = line.trim();
|
|
1022
|
+
if (trimmedLine.length > 10 && originalContent.includes(trimmedLine)) {
|
|
1023
|
+
partialMatches.push(trimmedLine.substring(0, 50));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
let errorMsg = `Patch search string not found: "${searchPreview}..."`;
|
|
1028
|
+
if (partialMatches.length > 0) {
|
|
1029
|
+
errorMsg += ` (partial matches found: ${partialMatches.join(", ")})`;
|
|
1030
|
+
}
|
|
1031
|
+
preValidationErrors.push(errorMsg);
|
|
1032
|
+
debugLog("Pre-validation failed: search string not found", {
|
|
1033
|
+
filePath: mod.filePath,
|
|
1034
|
+
searchPreview,
|
|
1035
|
+
partialMatches
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// If pre-validation failed, add to errors and skip this file
|
|
1042
|
+
if (preValidationErrors.length > 0) {
|
|
1043
|
+
patchErrors.push(`${mod.filePath}: AI generated patches with non-existent code (hallucination detected):\n${preValidationErrors.join("\n")}`);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
950
1047
|
const patchResult = applyPatches(originalContent, mod.patches);
|
|
951
1048
|
|
|
952
1049
|
if (!patchResult.success) {
|
|
@@ -993,22 +1090,39 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
993
1090
|
});
|
|
994
1091
|
}
|
|
995
1092
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1093
|
+
// If all modifications failed, check if we should retry
|
|
1094
|
+
if (patchErrors.length > 0 && modifications.length === 0) {
|
|
1095
|
+
if (retryCount < MAX_RETRIES) {
|
|
1096
|
+
console.warn(`[Apply-First] All patches failed, retrying (attempt ${retryCount + 1}/${MAX_RETRIES + 1})...`);
|
|
1097
|
+
debugLog("Retry triggered due to patch failures", {
|
|
1098
|
+
retryCount,
|
|
1099
|
+
errorCount: patchErrors.length,
|
|
1100
|
+
errors: patchErrors.slice(0, 3) // Log first 3 errors
|
|
1101
|
+
});
|
|
1102
|
+
lastPatchErrors = patchErrors;
|
|
1103
|
+
retryCount++;
|
|
1104
|
+
continue; // Retry the LLM call
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Exhausted retries, return error
|
|
1108
|
+
console.error("All AI patches failed after retries:", patchErrors);
|
|
1109
|
+
return NextResponse.json(
|
|
1110
|
+
{
|
|
1111
|
+
success: false,
|
|
1112
|
+
error: `Patch application failed (after ${retryCount} retry attempts):\n\n${patchErrors.join("\n\n")}`,
|
|
1113
|
+
},
|
|
1114
|
+
{ status: 400 }
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1007
1117
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1118
|
+
// Log patch errors as warnings if some modifications succeeded
|
|
1119
|
+
if (patchErrors.length > 0) {
|
|
1120
|
+
console.warn("Some patches failed:", patchErrors);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Successfully processed at least some modifications - break out of retry loop
|
|
1124
|
+
break;
|
|
1125
|
+
} // End of retry loop
|
|
1012
1126
|
|
|
1013
1127
|
// Create backups and apply changes atomically
|
|
1014
1128
|
const applyResult = await applyChangesWithBackup(
|
|
@@ -1029,8 +1143,8 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
1029
1143
|
sessionId: newSessionId,
|
|
1030
1144
|
modifications,
|
|
1031
1145
|
backupPaths: applyResult.backupPaths,
|
|
1032
|
-
explanation:
|
|
1033
|
-
reasoning:
|
|
1146
|
+
explanation: finalExplanation,
|
|
1147
|
+
reasoning: finalReasoning,
|
|
1034
1148
|
});
|
|
1035
1149
|
}
|
|
1036
1150
|
|
|
@@ -1754,6 +1868,7 @@ function searchFilesForKeywords(
|
|
|
1754
1868
|
let cachedPathAliases: Map<string, string> | null = null;
|
|
1755
1869
|
let cachedProjectRoot: string | null = null;
|
|
1756
1870
|
let cachedTsconfigMtime: number | null = null;
|
|
1871
|
+
let cachedParseError: string | null = null; // Cache parse errors to avoid log spam
|
|
1757
1872
|
|
|
1758
1873
|
/**
|
|
1759
1874
|
* Clean tsconfig.json content to make it valid JSON
|
|
@@ -1826,21 +1941,23 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1826
1941
|
cachedTsconfigMtime = null;
|
|
1827
1942
|
}
|
|
1828
1943
|
} catch (e) {
|
|
1829
|
-
// Log the error with more context for debugging
|
|
1830
1944
|
const errorStr = String(e);
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
if (
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1945
|
+
|
|
1946
|
+
// Only log parse error once to avoid log spam
|
|
1947
|
+
if (!cachedParseError) {
|
|
1948
|
+
const posMatch = errorStr.match(/position (\d+)/);
|
|
1949
|
+
let context = "";
|
|
1950
|
+
if (posMatch) {
|
|
1951
|
+
const pos = parseInt(posMatch[1], 10);
|
|
1952
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1953
|
+
context = `Near: "${content.substring(Math.max(0, pos - 20), pos + 20)}"`;
|
|
1954
|
+
}
|
|
1955
|
+
debugLog("[apply] Failed to parse tsconfig.json (will use defaults, logging once)", { error: errorStr, context });
|
|
1956
|
+
cachedParseError = errorStr;
|
|
1837
1957
|
}
|
|
1838
|
-
debugLog("[apply] Failed to parse tsconfig.json", { error: errorStr, context });
|
|
1839
1958
|
|
|
1840
|
-
//
|
|
1841
|
-
cachedPathAliases
|
|
1842
|
-
cachedProjectRoot = null;
|
|
1843
|
-
cachedTsconfigMtime = null;
|
|
1959
|
+
// Don't clear cache - we'll use the fallback aliases
|
|
1960
|
+
// cachedPathAliases will remain null, triggering fallback below
|
|
1844
1961
|
}
|
|
1845
1962
|
}
|
|
1846
1963
|
|
|
@@ -1855,13 +1972,16 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1855
1972
|
} else {
|
|
1856
1973
|
aliases.set("@/", "");
|
|
1857
1974
|
}
|
|
1858
|
-
|
|
1975
|
+
// Only log default alias once (not when using cached error fallback)
|
|
1976
|
+
if (!cachedParseError) {
|
|
1977
|
+
debugLog("[apply] Using default @/ alias", { alias: aliases.get("@/") });
|
|
1978
|
+
}
|
|
1859
1979
|
}
|
|
1860
1980
|
|
|
1861
|
-
//
|
|
1862
|
-
if (parsedSuccessfully || !fs.existsSync(tsconfigPath)) {
|
|
1863
|
-
|
|
1864
|
-
|
|
1981
|
+
// Cache aliases if parsed successfully, no tsconfig exists, OR we have a parse error (cache fallback)
|
|
1982
|
+
if (parsedSuccessfully || !fs.existsSync(tsconfigPath) || cachedParseError) {
|
|
1983
|
+
cachedPathAliases = aliases;
|
|
1984
|
+
cachedProjectRoot = projectRoot;
|
|
1865
1985
|
}
|
|
1866
1986
|
|
|
1867
1987
|
return aliases;
|
|
@@ -466,6 +466,8 @@ Return search/replace patches (NOT full files). The system applies your patches
|
|
|
466
466
|
- "replace" contains your modified version
|
|
467
467
|
- Include 2-4 lines of context in "search" to make it unique
|
|
468
468
|
- You may ONLY edit files provided in the PAGE CONTEXT section
|
|
469
|
+
- CRITICAL: NEVER invent or guess code. Your "search" string MUST be copied EXACTLY from the provided file content. If you cannot find the exact code to modify, return an empty modifications array.
|
|
470
|
+
- If the file content appears truncated, only modify code that is visible in the provided content.
|
|
469
471
|
|
|
470
472
|
**SONANCE BRAND COLORS:**
|
|
471
473
|
- Charcoal: #333F48 (primary text)
|
|
@@ -668,10 +670,10 @@ export async function POST(request: Request) {
|
|
|
668
670
|
}
|
|
669
671
|
|
|
670
672
|
// ========== SMART CONTEXT BUDGETING ==========
|
|
671
|
-
//
|
|
672
|
-
// Priority: Recommended file (
|
|
673
|
-
const TOTAL_CONTEXT_BUDGET =
|
|
674
|
-
const MAX_RECOMMENDED_FILE =
|
|
673
|
+
// Claude can handle 200k tokens (~800k chars), so we can safely include large files
|
|
674
|
+
// Priority: Recommended file (NEVER truncate) > Page file (limited) > Other components (dynamic)
|
|
675
|
+
const TOTAL_CONTEXT_BUDGET = 500000; // 500k chars total budget
|
|
676
|
+
const MAX_RECOMMENDED_FILE = Infinity; // NEVER truncate the target file - AI needs full context
|
|
675
677
|
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
676
678
|
const MAX_GLOBALS_CSS = 1500;
|
|
677
679
|
const MAX_FILES = 25;
|
|
@@ -705,9 +707,8 @@ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}
|
|
|
705
707
|
|
|
706
708
|
// ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
|
|
707
709
|
if (recommendedFileContent) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
: recommendedFileContent.content;
|
|
710
|
+
// Never truncate the recommended file - AI needs full context to avoid hallucination
|
|
711
|
+
const content = recommendedFileContent.content;
|
|
711
712
|
|
|
712
713
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
713
714
|
⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
|
|
@@ -818,97 +819,153 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
818
819
|
text: textContent,
|
|
819
820
|
});
|
|
820
821
|
|
|
821
|
-
// Call Claude Vision API
|
|
822
|
+
// Call Claude Vision API with retry mechanism
|
|
822
823
|
const anthropic = new Anthropic({ apiKey });
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
824
|
+
|
|
825
|
+
// Build list of known file paths (for logging)
|
|
826
|
+
const knownPaths = new Set<string>();
|
|
827
|
+
if (pageContext.pageFile) {
|
|
828
|
+
knownPaths.add(pageContext.pageFile);
|
|
829
|
+
}
|
|
830
|
+
for (const comp of pageContext.componentSources) {
|
|
831
|
+
knownPaths.add(comp.path);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Retry loop for handling patch failures
|
|
835
|
+
const MAX_RETRIES = 1;
|
|
836
|
+
let retryCount = 0;
|
|
837
|
+
let lastPatchErrors: string[] = [];
|
|
838
|
+
let modificationsWithOriginals: VisionFileModification[] = [];
|
|
839
|
+
let finalExplanation: string | undefined;
|
|
840
|
+
let finalReasoning: string | undefined;
|
|
841
|
+
let finalAggregatedCSS: string | undefined;
|
|
842
|
+
|
|
843
|
+
while (retryCount <= MAX_RETRIES) {
|
|
844
|
+
// Build messages for this attempt
|
|
845
|
+
const currentMessages: Anthropic.MessageCreateParams["messages"] = [
|
|
828
846
|
{
|
|
829
847
|
role: "user",
|
|
830
848
|
content: messageContent,
|
|
831
849
|
},
|
|
832
|
-
]
|
|
833
|
-
|
|
834
|
-
|
|
850
|
+
];
|
|
851
|
+
|
|
852
|
+
// If this is a retry, add feedback about what went wrong
|
|
853
|
+
if (retryCount > 0 && lastPatchErrors.length > 0) {
|
|
854
|
+
debugLog("Retry attempt with feedback", { retryCount, errorCount: lastPatchErrors.length });
|
|
855
|
+
|
|
856
|
+
// Get a snippet of the actual file content to help AI find correct code
|
|
857
|
+
let fileSnippet = "";
|
|
858
|
+
if (recommendedFileContent) {
|
|
859
|
+
// Show first 800 chars to give AI context about what code actually exists
|
|
860
|
+
fileSnippet = recommendedFileContent.content.substring(0, 800).replace(/\n/g, "\n");
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
currentMessages.push({
|
|
864
|
+
role: "assistant",
|
|
865
|
+
content: "I'll analyze the screenshot and generate the patches now.",
|
|
866
|
+
});
|
|
867
|
+
currentMessages.push({
|
|
868
|
+
role: "user",
|
|
869
|
+
content: `PATCH APPLICATION FAILED. Your previous patches referenced code that does not exist in the file (hallucination detected).
|
|
835
870
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (!textResponse || textResponse.type !== "text") {
|
|
839
|
-
return NextResponse.json(
|
|
840
|
-
{ error: "No text response from AI" },
|
|
841
|
-
{ status: 500 }
|
|
842
|
-
);
|
|
843
|
-
}
|
|
871
|
+
Failed patches:
|
|
872
|
+
${lastPatchErrors.join("\n\n")}
|
|
844
873
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
previewCSS?: string;
|
|
855
|
-
}>;
|
|
856
|
-
aggregatedPreviewCSS?: string;
|
|
857
|
-
explanation?: string;
|
|
858
|
-
};
|
|
874
|
+
${fileSnippet ? `Here is the ACTUAL beginning of the file you're trying to edit:
|
|
875
|
+
\`\`\`tsx
|
|
876
|
+
${fileSnippet}...
|
|
877
|
+
\`\`\`
|
|
878
|
+
` : ""}
|
|
879
|
+
CRITICAL INSTRUCTIONS:
|
|
880
|
+
1. Your "search" string MUST be copied EXACTLY from the file content I provided above
|
|
881
|
+
2. Do NOT invent, guess, or imagine code that might exist
|
|
882
|
+
3. Look for the ACTUAL code in the TARGET COMPONENT section and copy it exactly
|
|
859
883
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
jsonText.match(/```\n([\s\S]*?)\n```/);
|
|
866
|
-
|
|
867
|
-
if (jsonMatch) {
|
|
868
|
-
jsonText = jsonMatch[1];
|
|
869
|
-
} else if (jsonText.includes("```json")) {
|
|
870
|
-
// Fallback for cases where regex might miss due to newlines
|
|
871
|
-
const start = jsonText.indexOf("```json") + 7;
|
|
872
|
-
const end = jsonText.lastIndexOf("```");
|
|
873
|
-
if (end > start) {
|
|
874
|
-
jsonText = jsonText.substring(start, end);
|
|
875
|
-
}
|
|
884
|
+
If you still cannot find the exact code to modify after reviewing the file:
|
|
885
|
+
- Return {"modifications": [], "explanation": "Could not locate the exact code. Please specify which element you want to change more precisely."}
|
|
886
|
+
|
|
887
|
+
This is better than generating patches with made-up code.`,
|
|
888
|
+
});
|
|
876
889
|
}
|
|
877
890
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
891
|
+
const response = await anthropic.messages.create({
|
|
892
|
+
model: "claude-sonnet-4-20250514",
|
|
893
|
+
max_tokens: 16384,
|
|
894
|
+
messages: currentMessages,
|
|
895
|
+
system: VISION_SYSTEM_PROMPT,
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// Extract text content from response
|
|
899
|
+
const textResponse = response.content.find((block) => block.type === "text");
|
|
900
|
+
if (!textResponse || textResponse.type !== "text") {
|
|
901
|
+
return NextResponse.json(
|
|
902
|
+
{ error: "No text response from AI" },
|
|
903
|
+
{ status: 500 }
|
|
904
|
+
);
|
|
887
905
|
}
|
|
888
|
-
|
|
889
|
-
aiResponse = JSON.parse(jsonText);
|
|
890
|
-
} catch {
|
|
891
|
-
console.error("Failed to parse AI response:", textResponse.text);
|
|
892
|
-
return NextResponse.json(
|
|
893
|
-
{ error: "Failed to parse AI response. Please try again." },
|
|
894
|
-
{ status: 500 }
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
906
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
907
|
+
// Parse AI response - now expecting patches instead of full file content
|
|
908
|
+
let aiResponse: {
|
|
909
|
+
reasoning?: string;
|
|
910
|
+
modifications: Array<{
|
|
911
|
+
filePath: string;
|
|
912
|
+
patches?: Patch[];
|
|
913
|
+
// Legacy support for modifiedContent (will be deprecated)
|
|
914
|
+
modifiedContent?: string;
|
|
915
|
+
explanation?: string;
|
|
916
|
+
previewCSS?: string;
|
|
917
|
+
}>;
|
|
918
|
+
aggregatedPreviewCSS?: string;
|
|
919
|
+
explanation?: string;
|
|
920
|
+
};
|
|
906
921
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
922
|
+
try {
|
|
923
|
+
let jsonText = textResponse.text.trim();
|
|
924
|
+
|
|
925
|
+
// Try to extract JSON from markdown code blocks
|
|
926
|
+
const jsonMatch = jsonText.match(/```json\n([\s\S]*?)\n```/) ||
|
|
927
|
+
jsonText.match(/```\n([\s\S]*?)\n```/);
|
|
928
|
+
|
|
929
|
+
if (jsonMatch) {
|
|
930
|
+
jsonText = jsonMatch[1];
|
|
931
|
+
} else if (jsonText.includes("```json")) {
|
|
932
|
+
// Fallback for cases where regex might miss due to newlines
|
|
933
|
+
const start = jsonText.indexOf("```json") + 7;
|
|
934
|
+
const end = jsonText.lastIndexOf("```");
|
|
935
|
+
if (end > start) {
|
|
936
|
+
jsonText = jsonText.substring(start, end);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Clean up any remaining whitespace
|
|
941
|
+
jsonText = jsonText.trim();
|
|
942
|
+
|
|
943
|
+
// Robust JSON extraction: find the first { and last } to extract JSON object
|
|
944
|
+
// This handles cases where the LLM includes preamble text before the JSON
|
|
945
|
+
const firstBrace = jsonText.indexOf('{');
|
|
946
|
+
const lastBrace = jsonText.lastIndexOf('}');
|
|
947
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
948
|
+
jsonText = jsonText.substring(firstBrace, lastBrace + 1);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
aiResponse = JSON.parse(jsonText);
|
|
952
|
+
} catch {
|
|
953
|
+
console.error("Failed to parse AI response:", textResponse.text);
|
|
954
|
+
return NextResponse.json(
|
|
955
|
+
{ error: "Failed to parse AI response. Please try again." },
|
|
956
|
+
{ status: 500 }
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
finalExplanation = aiResponse.explanation;
|
|
961
|
+
finalReasoning = aiResponse.reasoning;
|
|
962
|
+
finalAggregatedCSS = aiResponse.aggregatedPreviewCSS;
|
|
963
|
+
|
|
964
|
+
debugLog("VALIDATION: Known file paths from page context", {
|
|
965
|
+
pageFile: pageContext.pageFile,
|
|
966
|
+
knownPaths: Array.from(knownPaths),
|
|
967
|
+
aiRequestedFiles: (aiResponse.modifications || []).map(m => m.filePath)
|
|
968
|
+
});
|
|
912
969
|
|
|
913
970
|
// Validate AI response - trust the LLM to identify the correct file
|
|
914
971
|
// Only reject paths that are outside the project or don't exist
|
|
@@ -949,9 +1006,9 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
949
1006
|
}
|
|
950
1007
|
}
|
|
951
1008
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1009
|
+
// Process modifications - apply patches to get modified content
|
|
1010
|
+
modificationsWithOriginals = [];
|
|
1011
|
+
const patchErrors: string[] = [];
|
|
955
1012
|
|
|
956
1013
|
for (const mod of aiResponse.modifications || []) {
|
|
957
1014
|
const fullPath = path.join(projectRoot, mod.filePath);
|
|
@@ -968,6 +1025,47 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
968
1025
|
// New patch-based approach
|
|
969
1026
|
console.log(`[Vision Mode] Applying ${mod.patches.length} patches to ${mod.filePath}`);
|
|
970
1027
|
|
|
1028
|
+
// PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
|
|
1029
|
+
const preValidationErrors: string[] = [];
|
|
1030
|
+
for (const patch of mod.patches) {
|
|
1031
|
+
const normalizedSearch = patch.search.replace(/\\n/g, "\n");
|
|
1032
|
+
if (!originalContent.includes(normalizedSearch)) {
|
|
1033
|
+
// Try fuzzy match as fallback
|
|
1034
|
+
const fuzzyMatch = findFuzzyMatch(normalizedSearch, originalContent);
|
|
1035
|
+
if (!fuzzyMatch) {
|
|
1036
|
+
// Find the closest matching snippet to help with debugging
|
|
1037
|
+
const searchPreview = normalizedSearch.substring(0, 80).replace(/\n/g, "\\n");
|
|
1038
|
+
|
|
1039
|
+
// Look for partial matches to give helpful feedback
|
|
1040
|
+
const searchLines = normalizedSearch.split("\n").filter(l => l.trim().length > 10);
|
|
1041
|
+
const partialMatches: string[] = [];
|
|
1042
|
+
for (const line of searchLines.slice(0, 3)) {
|
|
1043
|
+
const trimmedLine = line.trim();
|
|
1044
|
+
if (trimmedLine.length > 10 && originalContent.includes(trimmedLine)) {
|
|
1045
|
+
partialMatches.push(trimmedLine.substring(0, 50));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
let errorMsg = `Patch search string not found: "${searchPreview}..."`;
|
|
1050
|
+
if (partialMatches.length > 0) {
|
|
1051
|
+
errorMsg += ` (partial matches found: ${partialMatches.join(", ")})`;
|
|
1052
|
+
}
|
|
1053
|
+
preValidationErrors.push(errorMsg);
|
|
1054
|
+
debugLog("Pre-validation failed: search string not found", {
|
|
1055
|
+
filePath: mod.filePath,
|
|
1056
|
+
searchPreview,
|
|
1057
|
+
partialMatches
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// If pre-validation failed, add to errors and skip this file
|
|
1064
|
+
if (preValidationErrors.length > 0) {
|
|
1065
|
+
patchErrors.push(`${mod.filePath}: AI generated patches with non-existent code (hallucination detected):\n${preValidationErrors.join("\n")}`);
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
971
1069
|
const patchResult = applyPatches(originalContent, mod.patches);
|
|
972
1070
|
|
|
973
1071
|
if (!patchResult.success) {
|
|
@@ -1015,22 +1113,39 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
1015
1113
|
});
|
|
1016
1114
|
}
|
|
1017
1115
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1116
|
+
// If all modifications failed, check if we should retry
|
|
1117
|
+
if (patchErrors.length > 0 && modificationsWithOriginals.length === 0) {
|
|
1118
|
+
if (retryCount < MAX_RETRIES) {
|
|
1119
|
+
console.warn(`[Vision Mode] All patches failed, retrying (attempt ${retryCount + 1}/${MAX_RETRIES + 1})...`);
|
|
1120
|
+
debugLog("Retry triggered due to patch failures", {
|
|
1121
|
+
retryCount,
|
|
1122
|
+
errorCount: patchErrors.length,
|
|
1123
|
+
errors: patchErrors.slice(0, 3) // Log first 3 errors
|
|
1124
|
+
});
|
|
1125
|
+
lastPatchErrors = patchErrors;
|
|
1126
|
+
retryCount++;
|
|
1127
|
+
continue; // Retry the LLM call
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Exhausted retries, return error
|
|
1131
|
+
console.error("All AI patches failed after retries:", patchErrors);
|
|
1132
|
+
return NextResponse.json(
|
|
1133
|
+
{
|
|
1134
|
+
success: false,
|
|
1135
|
+
error: `Patch application failed (after ${retryCount} retry attempts):\n\n${patchErrors.join("\n\n")}`,
|
|
1136
|
+
} as VisionEditResponse,
|
|
1137
|
+
{ status: 400 }
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1029
1140
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1141
|
+
// Log patch errors as warnings if some modifications succeeded
|
|
1142
|
+
if (patchErrors.length > 0) {
|
|
1143
|
+
console.warn("Some patches failed:", patchErrors);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Successfully processed at least some modifications - break out of retry loop
|
|
1147
|
+
break;
|
|
1148
|
+
} // End of retry loop
|
|
1034
1149
|
|
|
1035
1150
|
// Aggregate preview CSS
|
|
1036
1151
|
const aggregatedCSS = modificationsWithOriginals
|
|
@@ -1041,9 +1156,9 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
1041
1156
|
return NextResponse.json({
|
|
1042
1157
|
success: true,
|
|
1043
1158
|
modifications: modificationsWithOriginals,
|
|
1044
|
-
aggregatedPreviewCSS:
|
|
1045
|
-
explanation:
|
|
1046
|
-
reasoning:
|
|
1159
|
+
aggregatedPreviewCSS: finalAggregatedCSS || aggregatedCSS,
|
|
1160
|
+
explanation: finalExplanation,
|
|
1161
|
+
reasoning: finalReasoning,
|
|
1047
1162
|
} as VisionEditResponse);
|
|
1048
1163
|
}
|
|
1049
1164
|
|
|
@@ -1658,6 +1773,7 @@ function searchFilesForKeywords(
|
|
|
1658
1773
|
let cachedPathAliases: Map<string, string> | null = null;
|
|
1659
1774
|
let cachedProjectRoot: string | null = null;
|
|
1660
1775
|
let cachedTsconfigMtime: number | null = null;
|
|
1776
|
+
let cachedParseError: string | null = null; // Cache parse errors to avoid log spam
|
|
1661
1777
|
|
|
1662
1778
|
/**
|
|
1663
1779
|
* Clean tsconfig.json content to make it valid JSON
|
|
@@ -1730,21 +1846,23 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1730
1846
|
cachedTsconfigMtime = null;
|
|
1731
1847
|
}
|
|
1732
1848
|
} catch (e) {
|
|
1733
|
-
// Log the error with more context for debugging
|
|
1734
1849
|
const errorStr = String(e);
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
if (
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
|
|
1850
|
+
|
|
1851
|
+
// Only log parse error once to avoid log spam
|
|
1852
|
+
if (!cachedParseError) {
|
|
1853
|
+
const posMatch = errorStr.match(/position (\d+)/);
|
|
1854
|
+
let context = "";
|
|
1855
|
+
if (posMatch) {
|
|
1856
|
+
const pos = parseInt(posMatch[1], 10);
|
|
1857
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1858
|
+
context = `Near: "${content.substring(Math.max(0, pos - 20), pos + 20)}"`;
|
|
1859
|
+
}
|
|
1860
|
+
debugLog("[edit] Failed to parse tsconfig.json (will use defaults, logging once)", { error: errorStr, context });
|
|
1861
|
+
cachedParseError = errorStr;
|
|
1741
1862
|
}
|
|
1742
|
-
debugLog("[edit] Failed to parse tsconfig.json", { error: errorStr, context });
|
|
1743
1863
|
|
|
1744
|
-
//
|
|
1745
|
-
cachedPathAliases
|
|
1746
|
-
cachedProjectRoot = null;
|
|
1747
|
-
cachedTsconfigMtime = null;
|
|
1864
|
+
// Don't clear cache - we'll use the fallback aliases
|
|
1865
|
+
// cachedPathAliases will remain null, triggering fallback below
|
|
1748
1866
|
}
|
|
1749
1867
|
}
|
|
1750
1868
|
|
|
@@ -1759,13 +1877,16 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1759
1877
|
} else {
|
|
1760
1878
|
aliases.set("@/", "");
|
|
1761
1879
|
}
|
|
1762
|
-
|
|
1880
|
+
// Only log default alias once (not when using cached error fallback)
|
|
1881
|
+
if (!cachedParseError) {
|
|
1882
|
+
debugLog("[edit] Using default @/ alias", { alias: aliases.get("@/") });
|
|
1883
|
+
}
|
|
1763
1884
|
}
|
|
1764
1885
|
|
|
1765
|
-
//
|
|
1766
|
-
if (parsedSuccessfully || !fs.existsSync(tsconfigPath)) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1886
|
+
// Cache aliases if parsed successfully, no tsconfig exists, OR we have a parse error (cache fallback)
|
|
1887
|
+
if (parsedSuccessfully || !fs.existsSync(tsconfigPath) || cachedParseError) {
|
|
1888
|
+
cachedPathAliases = aliases;
|
|
1889
|
+
cachedProjectRoot = projectRoot;
|
|
1769
1890
|
}
|
|
1770
1891
|
|
|
1771
1892
|
return aliases;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.49",
|
|
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",
|