#!/usr/bin/env python3 """ AST-based auto-fix script for indentation errors in Atom backend. This script uses the tokenize module to precisely fix indentation issues where `with get_db_session() as db:` is followed by an incorrectly indented `try:` block. """ import io import os from pathlib import Path import sys import tokenize from typing import List, Tuple # Backend directory BACKEND_DIR = Path("/Users/rushiparikh/projects/atom/backend") def fix_file_tokens(filepath: Path) -> Tuple[bool, str]: """ Fix indentation issues using token-level processing. Returns (success, message) """ try: with open(filepath, 'rb') as f: tokens = list(tokenize.tokenize(f.readline)) # Convert tokens back to source with fixes result_tokens = [] i = 0 while i < len(tokens): token = tokens[i] # Look for pattern: WITH_KEYWORD ('with') followed by NAME ('get_db_session') # then incorrectly indented try block if token.type == tokenize.NAME and token.string == 'with': # Check if this is our pattern j = i found_get_db_session = False found_try_at_wrong_indent = False # Look ahead for get_db_session while j < len(tokens) and tokens[j].type != tokenize.NEWLINE: if tokens[j].type == tokenize.NAME and tokens[j].string == 'get_db_session': found_get_db_session = True j += 1 if found_get_db_session: # Now look for the try statement # Skip to next line while j < len(tokens) and tokens[j].type in (tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT): j += 1 # Check if next line is 'try' with insufficient indentation if j < len(tokens) and tokens[j].type == tokenize.NAME and tokens[j].string == 'try': # Check indentation - should be more than 'with' statement with_indent = tokens[i].start[1] try_indent = tokens[j].start[1] if try_indent <= with_indent + 1: # Found the issue! We need to increase try indentation # Create a new token with proper indentation new_start = (tokens[j].start[0], with_indent + 4) if tokens[j].end[1] == tokens[j].start[1]: # Single token new_end = (tokens[j].end[0], with_indent + 4 + len(tokens[j].string)) else: new_end = tokens[j].end new_end = (new_end[0], new_end[1] + (with_indent + 4 - try_indent)) fixed_token = tokenize.TokenInfo( type=tokens[j].type, string=tokens[j].string, start=new_start, end=new_end, line=tokens[j].line ) result_tokens.append(fixed_token) i = j + 1 continue result_tokens.append(token) i += 1 # Reconstruct source from tokens source_lines = [] current_line = 1 current_col = 0 for token in result_tokens: if token.type == tokenize.ENCODING: continue # Handle line breaks while current_line < token.start[0]: source_lines.append('\n') current_line += 1 current_col = 0 # Handle column spacing while current_col < token.start[1]: source_lines.append(' ') current_col += 1 # Add the token string if token.type != tokenize.NEWLINE and token.type != tokenize.NL: source_lines.append(token.string) current_col += len(token.string) else: source_lines.append('\n') current_line += 1 current_col = 0 # Join and write back fixed_content = ''.join(source_lines) with open(filepath, 'w') as f: f.write(fixed_content) return True, "Fixed indentation" except Exception as e: return False, f"Error: {str(e)}" def fix_simple_pattern(filepath: Path) -> bool: """ Simple line-based fix for the specific pattern. """ try: with open(filepath, 'r') as f: lines = f.readlines() new_lines = [] i = 0 fixes = 0 while i < len(lines): line = lines[i] # Check for pattern: "with get_db_session() as db:" if 'with get_db_session() as db:' in line: # Get indentation indent = len(line) - len(line.lstrip()) new_lines.append(line) i += 1 # Check next line for "try:" at wrong indentation if i < len(lines): next_line = lines[i] next_indent = len(next_line) - len(next_line.lstrip()) # If "try:" is at same or less indentation than "with" if 'try:' in next_line and next_indent <= indent + 1: # Fix indentation fixed_try = ' ' * (indent + 4) + 'try:' + '\n' new_lines.append(fixed_try) fixes += 1 i += 1 # Continue with remaining lines while i < len(lines): new_lines.append(lines[i]) i += 1 break new_lines.append(line) i += 1 if fixes > 0: with open(filepath, 'w') as f: f.writelines(new_lines) return True return False except Exception as e: print(f" ✗ Error: {e}") return False def scan_and_fix(): """Scan all Python files and fix indentation errors.""" print("=" * 70) print("Auto-fixing indentation errors in Atom backend") print("=" * 70) print() # List of known problematic files problematic_files = [ "core/business_agents.py", "core/chat_session_manager.py", "core/change_order_agent.py", "core/communication_service.py", "core/workflow_engine.py", "core/resource_manager.py", "core/atom_meta_agent.py", "core/lifecycle_comm_generator.py", "core/background_agent_runner.py", "core/admin_bootstrap.py", "core/formula_memory.py", "core/uptime_tracker.py", "core/scheduler.py", "core/llm/byok_handler.py", "core/archive/database_v1.py", "integrations/chat_orchestrator.py", "integrations/universal_webhook_bridge.py", "integrations/zoho_workdrive_service.py", ] fixed_count = 0 for rel_path in problematic_files: filepath = BACKEND_DIR / rel_path if not filepath.exists(): continue print(f"🔧 {rel_path}") if fix_simple_pattern(filepath): print(f" ✓ Fixed") fixed_count += 1 else: print(f" (no fix needed or failed)") print() print("=" * 70) print(f"Fixed {fixed_count} files") print("=" * 70) print() # Verify import ast print("Verifying fixes...") remaining = 0 for rel_path in problematic_files: filepath = BACKEND_DIR / rel_path if not filepath.exists(): continue try: with open(filepath, 'r') as f: content = f.read() ast.parse(content) except (SyntaxError, IndentationError) as e: print(f" ✗ {rel_path}:{e.lineno} - {e.msg}") remaining += 1 if remaining == 0: print("✅ All files fixed successfully!") return 0 else: print(f"⚠️ {remaining} files still have errors") return 1 if __name__ == '__main__': sys.exit(scan_and_fix())