""" Legacy Agent - Intelligent management of legacy interface lifecycle. The Legacy Agent provides automated management of legacy interfaces including lifecycle progression, cleanup scheduling, migration assistance, and proactive deprecation management. """ import json import logging from datetime import datetime, timedelta from pathlib import Path from typing import Dict, List, Optional, Any, Callable from dataclasses import dataclass, asdict from enum import Enum from .registry import LegacyRegistry, LegacyInterface, LegacyStatus from .deprecation import DeprecationManager, DeprecationLevel from .git_tracker import GitStateTracker from .exceptions import LegacyError, LegacyConfigurationError class AgentAction(Enum): """Types of actions the legacy agent can perform.""" PROGRESS_DEPRECATION = "progress_deprecation" SCHEDULE_REMOVAL = "schedule_removal" GENERATE_MIGRATION_GUIDE = "generate_migration_guide" CREATE_COMPATIBILITY_SHIM = "create_compatibility_shim" CLEANUP_UNUSED = "cleanup_unused" NOTIFY_USERS = "notify_users" @dataclass class AgentTask: """Represents a task for the legacy agent to execute.""" action: AgentAction command: str version: str scheduled_for: str priority: int = 5 # 1=highest, 10=lowest metadata: Dict[str, Any] = None completed: bool = False completed_at: Optional[str] = None def __post_init__(self): if self.metadata is None: self.metadata = {} @dataclass class AgentConfig: """Configuration for the legacy agent.""" auto_progression: bool = True cleanup_unused_days: int = 180 migration_guide_auto_generation: bool = True notification_threshold_days: int = 30 max_concurrent_migrations: int = 3 backup_before_cleanup: bool = True class LegacyAgent: """ Intelligent agent for managing legacy interface lifecycle. Responsibilities: - Automatically progress deprecation phases based on timelines - Schedule and execute cleanup of unused legacy code - Generate migration guides and compatibility reports - Notify users of pending deprecations - Coordinate migration activities - Maintain audit trail of all legacy operations """ def __init__( self, registry: Optional[LegacyRegistry] = None, config: Optional[AgentConfig] = None, data_dir: Optional[Path] = None ): """ Initialize the legacy agent. Args: registry: Legacy registry instance config: Agent configuration data_dir: Directory for agent data storage """ self.registry = registry or LegacyRegistry() self.config = config or AgentConfig() self.data_dir = data_dir or Path.home() / '.markitect' / 'legacy_agent' self.data_dir.mkdir(parents=True, exist_ok=True) self.deprecation_manager = DeprecationManager() self.git_tracker = GitStateTracker() self._tasks: List[AgentTask] = [] self._load_tasks() # Setup logging self._setup_logging() def _setup_logging(self): """Setup logging for agent operations.""" log_file = self.data_dir / 'agent.log' logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file), logging.StreamHandler() ] ) self.logger = logging.getLogger('LegacyAgent') def run_maintenance(self) -> Dict[str, Any]: """ Run scheduled maintenance tasks. Returns: Summary of maintenance activities performed """ self.logger.info("Starting legacy maintenance cycle") summary = { 'started_at': datetime.now().isoformat(), 'tasks_executed': 0, 'progressions': 0, 'cleanups': 0, 'notifications': 0, 'errors': [] } try: # Load current interfaces self._analyze_legacy_interfaces() # Execute scheduled tasks summary['tasks_executed'] = self._execute_scheduled_tasks() # Auto-progression if enabled if self.config.auto_progression: summary['progressions'] = self._auto_progress_deprecations() # Cleanup unused interfaces summary['cleanups'] = self._cleanup_unused_interfaces() # Generate notifications summary['notifications'] = self._generate_notifications() # Update task schedule self._schedule_future_tasks() except Exception as e: error_msg = f"Maintenance error: {e}" self.logger.error(error_msg) summary['errors'].append(error_msg) summary['completed_at'] = datetime.now().isoformat() self.logger.info(f"Maintenance cycle completed: {summary}") return summary def _analyze_legacy_interfaces(self): """Analyze all legacy interfaces for needed actions.""" self.logger.info("Analyzing legacy interfaces") all_interfaces = {} for command in self.registry._interfaces: all_interfaces.update({ f"{command}:{version}": interface for version, interface in self.registry._interfaces[command].items() }) for key, interface in all_interfaces.items(): # Check if deprecation should progress if interface.deprecated_date: next_status = self.deprecation_manager.should_progress_deprecation( interface.command, interface.version, interface.status.value, interface.deprecated_date ) if next_status and next_status != interface.status.value: self._schedule_task(AgentTask( action=AgentAction.PROGRESS_DEPRECATION, command=interface.command, version=interface.version, scheduled_for=datetime.now().isoformat(), priority=3, metadata={'new_status': next_status} )) # Check if migration guide is needed if (interface.status in [LegacyStatus.LEGACY, LegacyStatus.SUNSET] and not interface.migration_guide and self.config.migration_guide_auto_generation): self._schedule_task(AgentTask( action=AgentAction.GENERATE_MIGRATION_GUIDE, command=interface.command, version=interface.version, scheduled_for=datetime.now().isoformat(), priority=4 )) def _execute_scheduled_tasks(self) -> int: """Execute tasks that are due for execution.""" executed_count = 0 now = datetime.now() for task in self._tasks: if task.completed: continue try: scheduled_time = datetime.fromisoformat(task.scheduled_for) if scheduled_time <= now: self._execute_task(task) task.completed = True task.completed_at = now.isoformat() executed_count += 1 except Exception as e: self.logger.error(f"Task execution failed: {task.action.value} for {task.command}:{task.version} - {e}") # Save updated tasks self._save_tasks() return executed_count def _execute_task(self, task: AgentTask): """Execute a specific agent task.""" self.logger.info(f"Executing task: {task.action.value} for {task.command}:{task.version}") if task.action == AgentAction.PROGRESS_DEPRECATION: self._progress_deprecation_task(task) elif task.action == AgentAction.GENERATE_MIGRATION_GUIDE: self._generate_migration_guide_task(task) elif task.action == AgentAction.CLEANUP_UNUSED: self._cleanup_unused_task(task) elif task.action == AgentAction.NOTIFY_USERS: self._notify_users_task(task) else: self.logger.warning(f"Unknown task action: {task.action}") def _progress_deprecation_task(self, task: AgentTask): """Execute deprecation progression task.""" new_status = LegacyStatus(task.metadata['new_status']) self.registry.update_interface_status(task.command, task.version, new_status) self.logger.info(f"Progressed {task.command}:{task.version} to status: {new_status.value}") # Schedule removal if moving to sunset if new_status == LegacyStatus.SUNSET: removal_date = (datetime.now() + timedelta(days=30)).isoformat() self._schedule_task(AgentTask( action=AgentAction.SCHEDULE_REMOVAL, command=task.command, version=task.version, scheduled_for=removal_date, priority=1 )) def _generate_migration_guide_task(self, task: AgentTask): """Generate migration guide for a legacy interface.""" interface = self.registry.get_legacy_interface(task.command, task.version) if not interface: return # Generate basic migration guide guide = self._create_migration_guide(interface) # Update interface with migration guide interface.migration_guide = guide # Save to registry (this would need registry update method) self.logger.info(f"Generated migration guide for {task.command}:{task.version}") def _cleanup_unused_task(self, task: AgentTask): """Execute cleanup of unused legacy interface.""" # This would perform actual cleanup operations # For now, we'll mark as removed self.registry.update_interface_status( task.command, task.version, LegacyStatus.REMOVED ) # Create backup if configured if self.config.backup_before_cleanup: backup_dir = self.data_dir / 'backups' / f"{task.command}_{task.version}" self.git_tracker.create_version_snapshot( task.command, task.version, backup_dir ) self.logger.info(f"Cleaned up unused interface: {task.command}:{task.version}") def _notify_users_task(self, task: AgentTask): """Send notification about pending deprecation.""" # This could integrate with notification systems # For now, we'll log the notification interface = self.registry.get_legacy_interface(task.command, task.version) if interface: self.logger.warning( f"NOTIFICATION: {task.command}:{task.version} is approaching removal. " f"Removal date: {interface.removal_date}" ) def _auto_progress_deprecations(self) -> int: """Automatically progress deprecations based on timeline.""" progressions = 0 for command in self.registry._interfaces: for version, interface in self.registry._interfaces[command].items(): if not interface.deprecated_date: continue next_status = self.deprecation_manager.should_progress_deprecation( command, version, interface.status.value, interface.deprecated_date ) if next_status and next_status != interface.status.value: new_status = LegacyStatus(next_status) self.registry.update_interface_status(command, version, new_status) progressions += 1 self.logger.info(f"Auto-progressed {command}:{version} to {next_status}") return progressions def _cleanup_unused_interfaces(self) -> int: """Clean up interfaces that haven't been used in configured period.""" cleanups = 0 cutoff_date = datetime.now() - timedelta(days=self.config.cleanup_unused_days) # Get usage statistics stats = self.registry.get_usage_statistics(days=self.config.cleanup_unused_days) for command in self.registry._interfaces: for version, interface in self.registry._interfaces[command].items(): if interface.status != LegacyStatus.SUNSET: continue # Check if unused in the cleanup period version_key = f"{command}:{version}" if version_key not in stats['by_version']: # Schedule for cleanup self._schedule_task(AgentTask( action=AgentAction.CLEANUP_UNUSED, command=command, version=version, scheduled_for=datetime.now().isoformat(), priority=6 )) cleanups += 1 return cleanups def _generate_notifications(self) -> int: """Generate notifications for approaching deprecations.""" notifications = 0 threshold_date = (datetime.now() + timedelta(days=self.config.notification_threshold_days)).isoformat() for command in self.registry._interfaces: for version, interface in self.registry._interfaces[command].items(): if (interface.removal_date and interface.removal_date <= threshold_date and interface.status != LegacyStatus.REMOVED): # Schedule notification self._schedule_task(AgentTask( action=AgentAction.NOTIFY_USERS, command=command, version=version, scheduled_for=datetime.now().isoformat(), priority=2 )) notifications += 1 return notifications def _schedule_future_tasks(self): """Schedule future maintenance tasks.""" # Schedule next maintenance cycle next_maintenance = (datetime.now() + timedelta(days=1)).isoformat() # This would typically schedule the next run of the agent # Implementation depends on the scheduling system used def _create_migration_guide(self, interface: LegacyInterface) -> str: """Create a basic migration guide for an interface.""" guide_parts = [ f"Migration Guide for {interface.command} {interface.version}", "=" * 50, "", "OVERVIEW:", f"This legacy version ({interface.version}) of the '{interface.command}' command", "has been deprecated and will be removed in a future release.", "", "MIGRATION STEPS:", f"1. Remove the --legacy-{interface.version} flag from your commands", f"2. Test the current version of '{interface.command}' with your use cases", "3. Update any scripts or automation that use this command", "4. Review the breaking changes section below", "", "BREAKING CHANGES:" ] if interface.breaking_changes: for change in interface.breaking_changes: guide_parts.append(f"- {change}") else: guide_parts.append("- No specific breaking changes documented") guide_parts.extend([ "", "SUPPORT:", "If you encounter issues during migration, please:", f"- Run: markitect help {interface.command}", "- Check the documentation for the latest syntax", "- Open an issue if you find compatibility problems" ]) return "\n".join(guide_parts) def _schedule_task(self, task: AgentTask): """Schedule a task for future execution.""" # Check for duplicate tasks for existing_task in self._tasks: if (existing_task.action == task.action and existing_task.command == task.command and existing_task.version == task.version and not existing_task.completed): return # Task already scheduled self._tasks.append(task) self._save_tasks() def _load_tasks(self): """Load scheduled tasks from storage.""" tasks_file = self.data_dir / 'scheduled_tasks.json' if tasks_file.exists(): try: data = json.loads(tasks_file.read_text()) self._tasks = [ AgentTask( action=AgentAction(task_data['action']), command=task_data['command'], version=task_data['version'], scheduled_for=task_data['scheduled_for'], priority=task_data.get('priority', 5), metadata=task_data.get('metadata', {}), completed=task_data.get('completed', False), completed_at=task_data.get('completed_at') ) for task_data in data.get('tasks', []) ] except Exception as e: self.logger.error(f"Failed to load tasks: {e}") self._tasks = [] def _save_tasks(self): """Save scheduled tasks to storage.""" tasks_file = self.data_dir / 'scheduled_tasks.json' data = { 'version': '1.0', 'updated_at': datetime.now().isoformat(), 'tasks': [asdict(task) for task in self._tasks] } try: tasks_file.write_text(json.dumps(data, indent=2)) except Exception as e: self.logger.error(f"Failed to save tasks: {e}") def get_agent_status(self) -> Dict[str, Any]: """Get the current status of the legacy agent.""" pending_tasks = [task for task in self._tasks if not task.completed] completed_tasks = [task for task in self._tasks if task.completed] return { 'config': asdict(self.config), 'tasks': { 'total': len(self._tasks), 'pending': len(pending_tasks), 'completed': len(completed_tasks) }, 'next_maintenance': self._get_next_maintenance_time(), 'data_directory': str(self.data_dir), 'registry_stats': self._get_registry_stats() } def _get_next_maintenance_time(self) -> Optional[str]: """Get the next scheduled maintenance time.""" pending_tasks = [task for task in self._tasks if not task.completed] if not pending_tasks: return None next_task = min(pending_tasks, key=lambda t: t.scheduled_for) return next_task.scheduled_for def _get_registry_stats(self) -> Dict[str, Any]: """Get statistics about the legacy registry.""" stats = { 'total_interfaces': 0, 'by_status': {}, 'commands': set() } for command, versions in self.registry._interfaces.items(): stats['commands'].add(command) for version, interface in versions.items(): stats['total_interfaces'] += 1 status = interface.status.value stats['by_status'][status] = stats['by_status'].get(status, 0) + 1 stats['commands'] = list(stats['commands']) return stats def force_cleanup(self, command: str, version: str) -> bool: """ Force immediate cleanup of a legacy interface. Args: command: Command name version: Version identifier Returns: True if cleanup was successful """ try: interface = self.registry.get_legacy_interface(command, version) if not interface: return False # Create backup if configured if self.config.backup_before_cleanup: backup_dir = self.data_dir / 'backups' / f"{command}_{version}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" self.git_tracker.create_version_snapshot(command, version, backup_dir) # Mark as removed self.registry.update_interface_status(command, version, LegacyStatus.REMOVED) self.logger.info(f"Force cleanup completed for {command}:{version}") return True except Exception as e: self.logger.error(f"Force cleanup failed for {command}:{version}: {e}") return False def schedule_migration_assistance(self, command: str, from_version: str, target_date: str): """ Schedule migration assistance for a specific legacy version. Args: command: Command name from_version: Legacy version to migrate from target_date: Target date for migration completion """ # Schedule migration guide generation self._schedule_task(AgentTask( action=AgentAction.GENERATE_MIGRATION_GUIDE, command=command, version=from_version, scheduled_for=datetime.now().isoformat(), priority=3, metadata={'target_date': target_date} )) # Schedule compatibility analysis analysis_date = (datetime.now() + timedelta(days=7)).isoformat() self._schedule_task(AgentTask( action=AgentAction.CREATE_COMPATIBILITY_SHIM, command=command, version=from_version, scheduled_for=analysis_date, priority=4, metadata={'target_date': target_date} )) self.logger.info(f"Scheduled migration assistance for {command}:{from_version}") def export_agent_data(self) -> Dict[str, Any]: """Export all agent data for backup/analysis.""" return { 'version': '1.0', 'exported_at': datetime.now().isoformat(), 'config': asdict(self.config), 'tasks': [asdict(task) for task in self._tasks], 'registry_data': self.registry.export_configuration(), 'git_bindings': self.git_tracker.export_bindings() }