@@ -166,7 +166,7 @@ def _build_generation_prompt(self, finding: Finding, context: RepoContext) -> st
166166 if current_content :
167167 prompt += (
168168 f"\n ## Current File Content ({ finding .file_path } )\n "
169- f"```\n { current_content [:4000 ]} \n ```\n "
169+ f"```\n { current_content [:6000 ]} \n ```\n "
170170 )
171171
172172 prompt += "\n ## Output Format\n Return your changes as a JSON object.\n \n "
@@ -256,35 +256,84 @@ def _parse_changes(self, response: str, context: RepoContext) -> list[FileChange
256256 # Search/replace mode — apply edits to original content
257257 original = context .relevant_files .get (path , "" )
258258 if not original :
259- logger .warning ("No original content for %s, skipping edits" , path )
259+ logger .warning (
260+ "No original content for %s (finding file not fetched), skipping edits" ,
261+ path ,
262+ )
260263 continue
261264
262265 new_content = original
263266 edits_applied = 0
267+ edits_total = len (item ["edits" ])
264268 for edit in item ["edits" ]:
265269 search = edit .get ("search" , "" )
266270 replace = edit .get ("replace" , "" )
267271 if not search :
268272 continue
273+
274+ matched = False
275+
276+ # Try 1: Exact match
269277 if search in new_content :
270278 new_content = new_content .replace (search , replace , 1 )
279+ matched = True
280+
281+ # Try 2: Normalize trailing whitespace per line
282+ if not matched :
283+ norm_search = "\n " .join (line .rstrip () for line in search .split ("\n " ))
284+ norm_content = "\n " .join (
285+ line .rstrip () for line in new_content .split ("\n " )
286+ )
287+ if norm_search in norm_content :
288+ # Find position in normalized, apply to original
289+ idx = norm_content .index (norm_search )
290+ # Map back: count newlines to find line range
291+ start_line = norm_content [:idx ].count ("\n " )
292+ end_line = start_line + norm_search .count ("\n " )
293+ lines = new_content .split ("\n " )
294+ lines [start_line : end_line + 1 ] = replace .split ("\n " )
295+ new_content = "\n " .join (lines )
296+ matched = True
297+ logger .debug (
298+ "Fuzzy match (whitespace normalized) for %s" ,
299+ path ,
300+ )
301+
302+ # Try 3: Strip all leading/trailing whitespace
303+ if not matched :
304+ stripped_search = search .strip ()
305+ if len (stripped_search ) > 20 and stripped_search in new_content :
306+ new_content = new_content .replace (
307+ stripped_search , replace .strip (), 1
308+ )
309+ matched = True
310+ logger .debug (
311+ "Fuzzy match (stripped) for %s" ,
312+ path ,
313+ )
314+
315+ if matched :
271316 edits_applied += 1
272317 else :
273318 logger .warning (
274- "Search text not found in %s: %s..." ,
319+ "Search text not found in %s (tried exact + fuzzy). "
320+ "Search[:%d]: %.80s..." ,
275321 path ,
276- search [:60 ],
322+ len (search ),
323+ search .replace ("\n " , "\\ n" ),
277324 )
278325
279- if edits_applied == 0 :
280- logger .warning ("No edits applied to %s" , path )
281- continue
282-
283326 logger .info (
284- "Applied %d search/replace edits to %s" ,
285- edits_applied ,
327+ "Edits for %s: %d/%d applied" ,
286328 path ,
329+ edits_applied ,
330+ edits_total ,
287331 )
332+
333+ if edits_applied == 0 :
334+ logger .warning ("No edits applied to %s, skipping file" , path )
335+ continue
336+
288337 changes .append (
289338 FileChange (
290339 path = path ,
0 commit comments