@@ -176,6 +176,52 @@ function validateDiff(diff) {
176176 }
177177 }
178178
179+ // Check for incomplete file sections (critical validation)
180+ const fileSections = diff . split ( / ^ - - - a \/ / gm) ;
181+ for ( let i = 1 ; i < fileSections . length ; i ++ ) {
182+ const section = fileSections [ i ] ;
183+ const lines = section . split ( '\n' ) ;
184+
185+ // First line should be the file path, second should be +++ b/path
186+ if ( lines . length < 2 ) {
187+ errors . push ( `Incomplete file section starting at line ${ diff . substring ( 0 , diff . indexOf ( section ) ) . split ( '\n' ) . length + 1 } : missing +++ b/ line` ) ;
188+ continue ;
189+ }
190+
191+ const oldPath = lines [ 0 ] . trim ( ) ;
192+ const newPathLine = lines [ 1 ] ;
193+
194+ if ( ! newPathLine . startsWith ( '+++ b/' ) ) {
195+ errors . push ( `Incomplete file section for ${ oldPath } : missing or malformed +++ b/ line` ) ;
196+ continue ;
197+ }
198+
199+ const newPath = newPathLine . substring ( 6 ) . trim ( ) ;
200+
201+ // Check if the section has at least one hunk
202+ const hasHunk = section . includes ( '@@' ) ;
203+ if ( ! hasHunk && ! section . includes ( 'new file' ) && ! section . includes ( 'deleted file' ) ) {
204+ errors . push ( `File section for ${ oldPath } has no hunks (might be incomplete)` ) ;
205+ }
206+
207+ // Check if section ends properly (not mid-line)
208+ const lastLine = lines [ lines . length - 1 ] ;
209+ if ( lastLine && ( lastLine . startsWith ( '+' ) || lastLine . startsWith ( '-' ) || lastLine . startsWith ( '\\' ) ) ) {
210+ // Section might be cut off
211+ const nextSectionStart = diff . indexOf ( '--- a/' , diff . indexOf ( section ) + section . length ) ;
212+ if ( nextSectionStart === - 1 && ! lastLine . match ( / ^ [ \s + -\\ ] / ) ) {
213+ // Last line doesn't look like a proper diff line ending
214+ errors . push ( `File section for ${ oldPath } appears to be cut off (incomplete diff)` ) ;
215+ }
216+ }
217+ }
218+
219+ // Check for incomplete +++ b/ lines (common issue)
220+ const incompletePlusPlus = diff . match ( / \+ \+ \+ b \/ [ ^ \n ] * $ / m) ;
221+ if ( incompletePlusPlus ) {
222+ errors . push ( `Incomplete +++ b/ line detected at end of diff (file name missing)` ) ;
223+ }
224+
179225 return {
180226 isValid : errors . length === 0 ,
181227 errors,
@@ -903,22 +949,25 @@ CRITICAL: Generate ONLY the file changes in this EXACT format (no "diff --git" h
903949 existing line
904950
905951Rules for the diff format:
906- 1. Start each file with "--- a/filepath" and "+++ b/filepath"
952+ 1. Start each file with "--- a/filepath" and "+++ b/filepath" (BOTH lines must be complete with full file path)
9079532. NO "diff --git" line, NO "index" line with hashes
9089543. Include enough context lines (unchanged lines) around changes
9099554. Use @@ -startLine,numLines +startLine,numLines @@ for hunks
9109565. Prefix added lines with "+", removed lines with "-", context lines with " " (space)
9119576. Include at least 3 lines of context before and after changes
9129587. Ensure line numbers match the actual file contents exactly
959+ 8. CRITICAL: The diff MUST be complete - every file section must have BOTH "--- a/path" AND "+++ b/path" lines with complete file paths
960+ 9. CRITICAL: Do NOT truncate the diff - if you reach token limits, prioritize completing fewer files rather than truncating
961+ 10. Each file section must end properly - do not cut off mid-line or mid-hunk
913962
914- Output ONLY the diff content, no explanations, no markdown code blocks.` ;
963+ Output ONLY the complete diff content, no explanations, no markdown code blocks. Ensure every file section is fully complete .` ;
915964
916965// Call AI provider with unified interface
917966async function callAI ( promptText ) {
918967 if ( provider === "claude" ) {
919968 const response = await aiClient . messages . create ( {
920969 model : aiModel ,
921- max_tokens : 8192 ,
970+ max_tokens : 16384 , // Increased for larger diffs
922971 temperature : 0.2 ,
923972 messages : [ { role : "user" , content : promptText } ] ,
924973 } ) ;
@@ -957,20 +1006,57 @@ try {
9571006// Extract diff from potential markdown code blocks
9581007let diff = output . trim ( ) ;
9591008if ( output . includes ( "```" ) ) {
960- // Extract content between code fences
961- const match = output . match ( / ` ` ` (?: d i f f ) ? \n ( [ \s \S ] * ?) ` ` ` / ) ;
962- if ( match ) {
963- diff = match [ 1 ] . trim ( ) ;
1009+ // Try to extract content between code fences
1010+ // Handle both single and multiple code blocks
1011+ const matches = output . matchAll ( / ` ` ` (?: d i f f ) ? \n ( [ \s \S ] * ?) ` ` ` / g) ;
1012+ const extractedDiffs = [ ] ;
1013+ for ( const match of matches ) {
1014+ extractedDiffs . push ( match [ 1 ] . trim ( ) ) ;
1015+ }
1016+ // Use the longest extracted diff (likely the actual diff)
1017+ if ( extractedDiffs . length > 0 ) {
1018+ diff = extractedDiffs . reduce ( ( a , b ) => a . length > b . length ? a : b ) ;
1019+ }
1020+
1021+ // If no code blocks found but output contains diff markers, use the whole output
1022+ if ( ! diff . includes ( "--- a/" ) && output . includes ( "--- a/" ) ) {
1023+ // Extract everything after the first "--- a/" line
1024+ const diffStart = output . indexOf ( "--- a/" ) ;
1025+ diff = output . substring ( diffStart ) . trim ( ) ;
1026+ // Remove any trailing markdown or explanations
1027+ const diffEnd = diff . indexOf ( "\n\n```" ) !== - 1 ? diff . indexOf ( "\n\n```" ) :
1028+ diff . indexOf ( "\n\n##" ) !== - 1 ? diff . indexOf ( "\n\n##" ) :
1029+ diff . indexOf ( "\n\n**" ) !== - 1 ? diff . indexOf ( "\n\n**" ) :
1030+ diff . length ;
1031+ diff = diff . substring ( 0 , diffEnd ) . trim ( ) ;
9641032 }
9651033}
9661034
9671035// Enhanced diff validation
9681036console . log ( "[AGENT] Validating diff format..." ) ;
9691037const validationResult = validateDiff ( diff ) ;
1038+
1039+ // Check if diff appears to be truncated
1040+ const diffLength = diff . length ;
1041+ const lastLines = diff . split ( '\n' ) . slice ( - 5 ) . join ( '\n' ) ;
1042+ if ( validationResult . errors . length > 0 || diff . match ( / \+ \+ \+ b \/ [ ^ \n ] * $ / m) ) {
1043+ console . warn ( `[AGENT] WARNING: Diff validation found issues or appears incomplete` ) ;
1044+ console . warn ( `[AGENT] Diff length: ${ diffLength } characters` ) ;
1045+ console . warn ( `[AGENT] Last 5 lines:\n${ lastLines } ` ) ;
1046+
1047+ // Try to detect if this is a token limit issue
1048+ if ( diffLength > 7000 && diff . match ( / \+ \+ \+ b \/ [ ^ \n ] * $ / m) ) {
1049+ const errorMsg = `Diff appears to be truncated (likely hit token limit). The AI model may have generated an incomplete diff.\n\nErrors:\n${ validationResult . errors . join ( '\n' ) } \n\nDiff preview (last 500 chars):\n\`\`\`\n${ diff . substring ( Math . max ( 0 , diffLength - 500 ) ) } \n\`\`\`\n\nSuggestion: Try breaking the task into smaller parts or increase max_tokens.` ;
1050+ const errorDetails = formatErrorDetails ( new Error ( errorMsg ) , { diff : diff . substring ( Math . max ( 0 , diffLength - 1000 ) ) , files : validationResult . stats . filesChanged } ) ;
1051+ await handleError ( new Error ( errorMsg ) , "Incomplete Diff (Token Limit?)" , { diff : diff . substring ( Math . max ( 0 , diffLength - 1000 ) ) , errorDetails } ) ;
1052+ // handleError calls process.exit(1), so we never reach here
1053+ }
1054+ }
1055+
9701056if ( ! validationResult . isValid ) {
971- const errorMsg = `Invalid diff format:\n${ validationResult . errors . join ( '\n' ) } \n\nDiff preview:\n\`\`\`\n${ diff . substring ( 0 , 500 ) } \n\`\`\`` ;
972- const errorDetails = formatErrorDetails ( new Error ( errorMsg ) , { diff, files : validationResult . stats . filesChanged } ) ;
973- await handleError ( new Error ( errorMsg ) , "Validation Error" , { diff, errorDetails } ) ;
1057+ const errorMsg = `Invalid diff format:\n${ validationResult . errors . join ( '\n' ) } \n\nDiff preview (first 1000 chars) :\n\`\`\`\n${ diff . substring ( 0 , 1000 ) } \n\`\`\`\n\nDiff preview (last 500 chars):\n\`\`\`\n ${ diff . substring ( Math . max ( 0 , diffLength - 500 ) ) } \n\`\`\`` ;
1058+ const errorDetails = formatErrorDetails ( new Error ( errorMsg ) , { diff : diff . substring ( 0 , 1000 ) , files : validationResult . stats . filesChanged } ) ;
1059+ await handleError ( new Error ( errorMsg ) , "Validation Error" , { diff : diff . substring ( 0 , 1000 ) , errorDetails } ) ;
9741060 // handleError calls process.exit(1), so we never reach here
9751061}
9761062console . log ( `[AGENT] Diff format valid: ${ validationResult . stats . filesChanged } files, ${ validationResult . stats . hunks } hunks` ) ;
0 commit comments