claude-mpm 3.7.8__py3-none-any.whl → 3.9.0__py3-none-any.whl
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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -96
- claude_mpm/agents/MEMORY.md +94 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +3 -8
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +2 -2
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +8 -3
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +217 -5
- claude_mpm/config/__init__.py +30 -39
- claude_mpm/config/socketio_config.py +8 -5
- claude_mpm/constants.py +13 -0
- claude_mpm/core/__init__.py +8 -18
- claude_mpm/core/cache.py +596 -0
- claude_mpm/core/claude_runner.py +166 -622
- claude_mpm/core/config.py +7 -3
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +548 -38
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +249 -93
- claude_mpm/core/interactive_session.py +479 -0
- claude_mpm/core/interfaces.py +424 -0
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/logging_config.py +444 -0
- claude_mpm/core/oneshot_session.py +465 -0
- claude_mpm/core/optimized_agent_loader.py +485 -0
- claude_mpm/core/optimized_startup.py +490 -0
- claude_mpm/core/service_registry.py +52 -26
- claude_mpm/core/socketio_pool.py +162 -5
- claude_mpm/core/types.py +292 -0
- claude_mpm/core/typing_utils.py +477 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
- claude_mpm/init.py +2 -1
- claude_mpm/services/__init__.py +78 -14
- claude_mpm/services/agent/__init__.py +24 -0
- claude_mpm/services/agent/deployment.py +2548 -0
- claude_mpm/services/agent/management.py +598 -0
- claude_mpm/services/agent/registry.py +813 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
- claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
- claude_mpm/services/async_session_logger.py +8 -3
- claude_mpm/services/communication/__init__.py +21 -0
- claude_mpm/services/communication/socketio.py +1933 -0
- claude_mpm/services/communication/websocket.py +479 -0
- claude_mpm/services/core/__init__.py +123 -0
- claude_mpm/services/core/base.py +247 -0
- claude_mpm/services/core/interfaces.py +951 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
- claude_mpm/services/framework_claude_md_generator.py +3 -2
- claude_mpm/services/health_monitor.py +4 -3
- claude_mpm/services/hook_service.py +64 -4
- claude_mpm/services/infrastructure/__init__.py +21 -0
- claude_mpm/services/infrastructure/logging.py +202 -0
- claude_mpm/services/infrastructure/monitoring.py +893 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/project/__init__.py +21 -0
- claude_mpm/services/project/analyzer.py +864 -0
- claude_mpm/services/project/registry.py +608 -0
- claude_mpm/services/project_analyzer.py +95 -2
- claude_mpm/services/recovery_manager.py +15 -9
- claude_mpm/services/response_tracker.py +3 -5
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/base.py +121 -0
- claude_mpm/services/socketio/handlers/connection.py +198 -0
- claude_mpm/services/socketio/handlers/file.py +213 -0
- claude_mpm/services/socketio/handlers/git.py +723 -0
- claude_mpm/services/socketio/handlers/memory.py +27 -0
- claude_mpm/services/socketio/handlers/project.py +25 -0
- claude_mpm/services/socketio/handlers/registry.py +145 -0
- claude_mpm/services/socketio_client_manager.py +12 -7
- claude_mpm/services/socketio_server.py +156 -30
- claude_mpm/services/ticket_manager.py +172 -9
- claude_mpm/services/ticket_manager_di.py +1 -1
- claude_mpm/services/version_control/semantic_versioning.py +80 -7
- claude_mpm/services/version_control/version_parser.py +528 -0
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            """Memory-related event handlers for Socket.IO.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module handles agent memory events. Currently the memory
         | 
| 4 | 
            +
            events are broadcast methods rather than Socket.IO events, but this
         | 
| 5 | 
            +
            provides a place for future memory management features.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from .base import BaseEventHandler
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            class MemoryEventHandler(BaseEventHandler):
         | 
| 12 | 
            +
                """Handles memory-related Socket.IO events.
         | 
| 13 | 
            +
                
         | 
| 14 | 
            +
                WHY: Agent memory management events will be handled here as the system
         | 
| 15 | 
            +
                grows. This provides a clean separation for memory-specific functionality.
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                
         | 
| 18 | 
            +
                def register_events(self):
         | 
| 19 | 
            +
                    """Register memory-related event handlers.
         | 
| 20 | 
            +
                    
         | 
| 21 | 
            +
                    Currently memory events are handled through broadcast methods
         | 
| 22 | 
            +
                    rather than Socket.IO event handlers, but this provides a place
         | 
| 23 | 
            +
                    for future interactive memory management features.
         | 
| 24 | 
            +
                    """
         | 
| 25 | 
            +
                    # Future memory management events will be registered here
         | 
| 26 | 
            +
                    # For example: query_memory, clear_memory, export_memory, etc.
         | 
| 27 | 
            +
                    pass
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            """Project-related event handlers for Socket.IO.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module handles project-specific events that don't fit into
         | 
| 4 | 
            +
            other categories. Currently empty but provides a place for future
         | 
| 5 | 
            +
            project management features.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from .base import BaseEventHandler
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            class ProjectEventHandler(BaseEventHandler):
         | 
| 12 | 
            +
                """Handles project-related Socket.IO events.
         | 
| 13 | 
            +
                
         | 
| 14 | 
            +
                WHY: Project management events will be handled here as the system
         | 
| 15 | 
            +
                grows. This provides a clean separation for project-specific functionality.
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                
         | 
| 18 | 
            +
                def register_events(self):
         | 
| 19 | 
            +
                    """Register project-related event handlers.
         | 
| 20 | 
            +
                    
         | 
| 21 | 
            +
                    Currently no project-specific events are defined, but this
         | 
| 22 | 
            +
                    handler is ready for future expansion.
         | 
| 23 | 
            +
                    """
         | 
| 24 | 
            +
                    # Future project events will be registered here
         | 
| 25 | 
            +
                    pass
         | 
| @@ -0,0 +1,145 @@ | |
| 1 | 
            +
            """Event handler registry for Socket.IO server.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This registry manages the registration of all event handlers,
         | 
| 4 | 
            +
            providing a clean interface for the SocketIOServer to register all
         | 
| 5 | 
            +
            events without knowing the details of each handler.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from typing import List, Type, Optional, TYPE_CHECKING
         | 
| 9 | 
            +
            from logging import Logger
         | 
| 10 | 
            +
            from ....core.logger import get_logger
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            from .base import BaseEventHandler
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            if TYPE_CHECKING:
         | 
| 15 | 
            +
                from ..server import SocketIOServer
         | 
| 16 | 
            +
            from .connection import ConnectionEventHandler
         | 
| 17 | 
            +
            from .project import ProjectEventHandler
         | 
| 18 | 
            +
            from .memory import MemoryEventHandler
         | 
| 19 | 
            +
            from .file import FileEventHandler
         | 
| 20 | 
            +
            from .git import GitEventHandler
         | 
| 21 | 
            +
             | 
| 22 | 
            +
             | 
| 23 | 
            +
            class EventHandlerRegistry:
         | 
| 24 | 
            +
                """Manages registration of Socket.IO event handlers.
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                WHY: The registry pattern allows us to easily add, remove, or modify
         | 
| 27 | 
            +
                event handlers without changing the SocketIOServer implementation.
         | 
| 28 | 
            +
                It provides a single point of configuration for all event handlers.
         | 
| 29 | 
            +
                """
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                # Default handler classes in registration order
         | 
| 32 | 
            +
                DEFAULT_HANDLERS: List[Type[BaseEventHandler]] = [
         | 
| 33 | 
            +
                    ConnectionEventHandler,  # Connection management first
         | 
| 34 | 
            +
                    GitEventHandler,         # Git operations
         | 
| 35 | 
            +
                    FileEventHandler,        # File operations
         | 
| 36 | 
            +
                    ProjectEventHandler,     # Project management (future)
         | 
| 37 | 
            +
                    MemoryEventHandler,      # Memory management (future)
         | 
| 38 | 
            +
                ]
         | 
| 39 | 
            +
                
         | 
| 40 | 
            +
                def __init__(self, server: 'SocketIOServer') -> None:
         | 
| 41 | 
            +
                    """Initialize the registry.
         | 
| 42 | 
            +
                    
         | 
| 43 | 
            +
                    Args:
         | 
| 44 | 
            +
                        server: The SocketIOServer instance
         | 
| 45 | 
            +
                    """
         | 
| 46 | 
            +
                    self.server: 'SocketIOServer' = server
         | 
| 47 | 
            +
                    self.logger: Logger = get_logger("EventHandlerRegistry")
         | 
| 48 | 
            +
                    self.handlers: List[BaseEventHandler] = []
         | 
| 49 | 
            +
                    self._initialized: bool = False
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                def initialize(self, handler_classes: Optional[List[Type[BaseEventHandler]]] = None) -> None:
         | 
| 52 | 
            +
                    """Initialize all event handlers.
         | 
| 53 | 
            +
                    
         | 
| 54 | 
            +
                    WHY: This creates instances of all handler classes and prepares
         | 
| 55 | 
            +
                    them for event registration. Using a list of classes allows
         | 
| 56 | 
            +
                    customization of which handlers to use.
         | 
| 57 | 
            +
                    
         | 
| 58 | 
            +
                    Args:
         | 
| 59 | 
            +
                        handler_classes: Optional list of handler classes to use.
         | 
| 60 | 
            +
                                       Defaults to DEFAULT_HANDLERS if not provided.
         | 
| 61 | 
            +
                    """
         | 
| 62 | 
            +
                    if self._initialized:
         | 
| 63 | 
            +
                        self.logger.warning("Registry already initialized, skipping re-initialization")
         | 
| 64 | 
            +
                        return
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    handler_classes = handler_classes or self.DEFAULT_HANDLERS
         | 
| 67 | 
            +
                    
         | 
| 68 | 
            +
                    for handler_class in handler_classes:
         | 
| 69 | 
            +
                        try:
         | 
| 70 | 
            +
                            handler = handler_class(self.server)
         | 
| 71 | 
            +
                            self.handlers.append(handler)
         | 
| 72 | 
            +
                            self.logger.info(f"Initialized handler: {handler_class.__name__}")
         | 
| 73 | 
            +
                        except Exception as e:
         | 
| 74 | 
            +
                            self.logger.error(f"Failed to initialize {handler_class.__name__}: {e}")
         | 
| 75 | 
            +
                            import traceback
         | 
| 76 | 
            +
                            self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| 77 | 
            +
                    
         | 
| 78 | 
            +
                    self._initialized = True
         | 
| 79 | 
            +
                    self.logger.info(f"Registry initialized with {len(self.handlers)} handlers")
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                def register_all_events(self) -> None:
         | 
| 82 | 
            +
                    """Register all events from all handlers.
         | 
| 83 | 
            +
                    
         | 
| 84 | 
            +
                    WHY: This is the main method called by SocketIOServer to register
         | 
| 85 | 
            +
                    all events. It delegates to each handler's register_events method,
         | 
| 86 | 
            +
                    keeping the server code clean and simple.
         | 
| 87 | 
            +
                    """
         | 
| 88 | 
            +
                    if not self._initialized:
         | 
| 89 | 
            +
                        self.logger.error("Registry not initialized. Call initialize() first.")
         | 
| 90 | 
            +
                        raise RuntimeError("EventHandlerRegistry not initialized")
         | 
| 91 | 
            +
                    
         | 
| 92 | 
            +
                    registered_count = 0
         | 
| 93 | 
            +
                    for handler in self.handlers:
         | 
| 94 | 
            +
                        try:
         | 
| 95 | 
            +
                            handler.register_events()
         | 
| 96 | 
            +
                            registered_count += 1
         | 
| 97 | 
            +
                            self.logger.info(f"Registered events for {handler.__class__.__name__}")
         | 
| 98 | 
            +
                        except NotImplementedError:
         | 
| 99 | 
            +
                            # Handler has no events to register (like ProjectEventHandler)
         | 
| 100 | 
            +
                            self.logger.debug(f"No events to register for {handler.__class__.__name__}")
         | 
| 101 | 
            +
                        except Exception as e:
         | 
| 102 | 
            +
                            self.logger.error(f"Failed to register events for {handler.__class__.__name__}: {e}")
         | 
| 103 | 
            +
                            import traceback
         | 
| 104 | 
            +
                            self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| 105 | 
            +
                    
         | 
| 106 | 
            +
                    self.logger.info(f"Successfully registered events from {registered_count} handlers")
         | 
| 107 | 
            +
                
         | 
| 108 | 
            +
                def add_handler(self, handler_class: Type[BaseEventHandler]):
         | 
| 109 | 
            +
                    """Add a custom handler to the registry.
         | 
| 110 | 
            +
                    
         | 
| 111 | 
            +
                    WHY: Allows dynamic addition of custom handlers for specific
         | 
| 112 | 
            +
                    deployments or testing without modifying the default handlers.
         | 
| 113 | 
            +
                    
         | 
| 114 | 
            +
                    Args:
         | 
| 115 | 
            +
                        handler_class: The handler class to add
         | 
| 116 | 
            +
                    """
         | 
| 117 | 
            +
                    if not self._initialized:
         | 
| 118 | 
            +
                        self.logger.error("Registry not initialized. Call initialize() first.")
         | 
| 119 | 
            +
                        raise RuntimeError("EventHandlerRegistry not initialized")
         | 
| 120 | 
            +
                    
         | 
| 121 | 
            +
                    try:
         | 
| 122 | 
            +
                        handler = handler_class(self.server)
         | 
| 123 | 
            +
                        self.handlers.append(handler)
         | 
| 124 | 
            +
                        handler.register_events()
         | 
| 125 | 
            +
                        self.logger.info(f"Added and registered handler: {handler_class.__name__}")
         | 
| 126 | 
            +
                    except Exception as e:
         | 
| 127 | 
            +
                        self.logger.error(f"Failed to add handler {handler_class.__name__}: {e}")
         | 
| 128 | 
            +
                        raise
         | 
| 129 | 
            +
                
         | 
| 130 | 
            +
                def get_handler(self, handler_class: Type[BaseEventHandler]) -> BaseEventHandler:
         | 
| 131 | 
            +
                    """Get a specific handler instance by class.
         | 
| 132 | 
            +
                    
         | 
| 133 | 
            +
                    WHY: Useful for testing or when specific handler functionality
         | 
| 134 | 
            +
                    needs to be accessed directly.
         | 
| 135 | 
            +
                    
         | 
| 136 | 
            +
                    Args:
         | 
| 137 | 
            +
                        handler_class: The handler class to find
         | 
| 138 | 
            +
                        
         | 
| 139 | 
            +
                    Returns:
         | 
| 140 | 
            +
                        The handler instance or None if not found
         | 
| 141 | 
            +
                    """
         | 
| 142 | 
            +
                    for handler in self.handlers:
         | 
| 143 | 
            +
                        if isinstance(handler, handler_class):
         | 
| 144 | 
            +
                            return handler
         | 
| 145 | 
            +
                    return None
         | 
| @@ -23,6 +23,11 @@ import time | |
| 23 23 | 
             
            from datetime import datetime
         | 
| 24 24 | 
             
            from typing import Dict, Any, Optional, List, Tuple
         | 
| 25 25 | 
             
            import importlib.metadata
         | 
| 26 | 
            +
            from claude_mpm.core.constants import (
         | 
| 27 | 
            +
                NetworkConfig,
         | 
| 28 | 
            +
                TimeoutConfig,
         | 
| 29 | 
            +
                PerformanceConfig
         | 
| 30 | 
            +
            )
         | 
| 26 31 |  | 
| 27 32 | 
             
            try:
         | 
| 28 33 | 
             
                import requests
         | 
| @@ -129,7 +134,7 @@ class SocketIOClientManager: | |
| 129 134 | 
             
                                try:
         | 
| 130 135 | 
             
                                    response = requests.get(
         | 
| 131 136 | 
             
                                        f"http://{host}:{port}/version",
         | 
| 132 | 
            -
                                        timeout= | 
| 137 | 
            +
                                        timeout=TimeoutConfig.QUICK_TIMEOUT
         | 
| 133 138 | 
             
                                    )
         | 
| 134 139 | 
             
                                    if response.status_code == 200:
         | 
| 135 140 | 
             
                                        data = response.json()
         | 
| @@ -177,9 +182,9 @@ class SocketIOClientManager: | |
| 177 182 | 
             
                        # Version scoring (basic semantic versioning)
         | 
| 178 183 | 
             
                        try:
         | 
| 179 184 | 
             
                            version_parts = server.server_version.split('.')
         | 
| 180 | 
            -
                            score += int(version_parts[0]) *  | 
| 181 | 
            -
                            score += int(version_parts[1]) *  | 
| 182 | 
            -
                            score += int(version_parts[2]) *  | 
| 185 | 
            +
                            score += int(version_parts[0]) * PerformanceConfig.VERSION_SCORE_MAJOR
         | 
| 186 | 
            +
                            score += int(version_parts[1]) * PerformanceConfig.VERSION_SCORE_MINOR
         | 
| 187 | 
            +
                            score += int(version_parts[2]) * PerformanceConfig.VERSION_SCORE_PATCH
         | 
| 183 188 | 
             
                        except (ValueError, IndexError):
         | 
| 184 189 | 
             
                            pass
         | 
| 185 190 |  | 
| @@ -211,7 +216,7 @@ class SocketIOClientManager: | |
| 211 216 | 
             
                        compat_response = requests.post(
         | 
| 212 217 | 
             
                            f"{server_info.url}/compatibility",
         | 
| 213 218 | 
             
                            json={"client_version": self.client_version},
         | 
| 214 | 
            -
                            timeout= | 
| 219 | 
            +
                            timeout=TimeoutConfig.FILE_OPERATION_TIMEOUT
         | 
| 215 220 | 
             
                        )
         | 
| 216 221 |  | 
| 217 222 | 
             
                        if compat_response.status_code == 200:
         | 
| @@ -224,7 +229,7 @@ class SocketIOClientManager: | |
| 224 229 | 
             
                        self.client = socketio.AsyncClient(
         | 
| 225 230 | 
             
                            reconnection=True,
         | 
| 226 231 | 
             
                            reconnection_attempts=0,  # Infinite
         | 
| 227 | 
            -
                            reconnection_delay= | 
| 232 | 
            +
                            reconnection_delay=NetworkConfig.RECONNECTION_DELAY,
         | 
| 228 233 | 
             
                            reconnection_delay_max=5,
         | 
| 229 234 | 
             
                            randomization_factor=0.5,
         | 
| 230 235 | 
             
                            logger=False,
         | 
| @@ -328,7 +333,7 @@ class SocketIOClientManager: | |
| 328 333 | 
             
                    self.running = False
         | 
| 329 334 |  | 
| 330 335 | 
             
                    if self.connection_thread:
         | 
| 331 | 
            -
                        self.connection_thread.join(timeout= | 
| 336 | 
            +
                        self.connection_thread.join(timeout=TimeoutConfig.THREAD_JOIN_TIMEOUT)
         | 
| 332 337 |  | 
| 333 338 | 
             
                    if self.client and self.connected:
         | 
| 334 339 | 
             
                        try:
         | 
| @@ -31,8 +31,17 @@ except ImportError: | |
| 31 31 | 
             
                web = None
         | 
| 32 32 | 
             
                # Don't print warnings at module level
         | 
| 33 33 |  | 
| 34 | 
            -
            from ..core. | 
| 34 | 
            +
            from ..core.logging_config import get_logger, log_operation, log_performance_context
         | 
| 35 35 | 
             
            from ..deployment_paths import get_project_root, get_scripts_dir
         | 
| 36 | 
            +
            from .socketio.handlers import EventHandlerRegistry, FileEventHandler, GitEventHandler
         | 
| 37 | 
            +
            from ..core.constants import (
         | 
| 38 | 
            +
                SystemLimits,
         | 
| 39 | 
            +
                NetworkConfig,
         | 
| 40 | 
            +
                TimeoutConfig,
         | 
| 41 | 
            +
                PerformanceConfig
         | 
| 42 | 
            +
            )
         | 
| 43 | 
            +
            from ..core.interfaces import SocketIOServiceInterface
         | 
| 44 | 
            +
            from ..exceptions import MPMConnectionError
         | 
| 36 45 |  | 
| 37 46 |  | 
| 38 47 | 
             
            class SocketIOClientProxy:
         | 
| @@ -47,19 +56,19 @@ class SocketIOClientProxy: | |
| 47 56 | 
             
                def __init__(self, host: str = "localhost", port: int = 8765):
         | 
| 48 57 | 
             
                    self.host = host
         | 
| 49 58 | 
             
                    self.port = port
         | 
| 50 | 
            -
                    self.logger = get_logger(" | 
| 59 | 
            +
                    self.logger = get_logger(__name__ + ".SocketIOClientProxy")
         | 
| 51 60 | 
             
                    self.running = True  # Always "running" for compatibility
         | 
| 52 61 | 
             
                    self._sio_client = None
         | 
| 53 62 | 
             
                    self._client_thread = None
         | 
| 54 63 | 
             
                    self._client_loop = None
         | 
| 55 64 |  | 
| 56 | 
            -
                def  | 
| 65 | 
            +
                def start_sync(self):
         | 
| 57 66 | 
             
                    """Start the Socket.IO client connection to the persistent server."""
         | 
| 58 67 | 
             
                    self.logger.debug(f"SocketIOClientProxy: Connecting to server on {self.host}:{self.port}")
         | 
| 59 68 | 
             
                    if SOCKETIO_AVAILABLE:
         | 
| 60 69 | 
             
                        self._start_client()
         | 
| 61 70 |  | 
| 62 | 
            -
                def  | 
| 71 | 
            +
                def stop_sync(self):
         | 
| 63 72 | 
             
                    """Stop the Socket.IO client connection."""
         | 
| 64 73 | 
             
                    self.logger.debug(f"SocketIOClientProxy: Disconnecting from server")
         | 
| 65 74 | 
             
                    if self._sio_client:
         | 
| @@ -161,7 +170,7 @@ class SocketIOClientProxy: | |
| 161 170 | 
             
                    self.logger.debug(f"SocketIOClientProxy: Todo updated ({len(todos)} todos)")
         | 
| 162 171 |  | 
| 163 172 |  | 
| 164 | 
            -
            class SocketIOServer:
         | 
| 173 | 
            +
            class SocketIOServer(SocketIOServiceInterface):
         | 
| 165 174 | 
             
                """Socket.IO server for broadcasting Claude MPM events.
         | 
| 166 175 |  | 
| 167 176 | 
             
                WHY: Socket.IO provides better connection reliability than raw WebSockets,
         | 
| @@ -172,9 +181,9 @@ class SocketIOServer: | |
| 172 181 | 
             
                def __init__(self, host: str = "localhost", port: int = 8765):
         | 
| 173 182 | 
             
                    self.host = host
         | 
| 174 183 | 
             
                    self.port = port
         | 
| 175 | 
            -
                    self.logger = get_logger( | 
| 184 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 176 185 | 
             
                    self.clients: Set[str] = set()  # Store session IDs instead of connection objects
         | 
| 177 | 
            -
                    self.event_history: deque = deque(maxlen= | 
| 186 | 
            +
                    self.event_history: deque = deque(maxlen=SystemLimits.MAX_EVENT_HISTORY)
         | 
| 178 187 | 
             
                    self.sio = None
         | 
| 179 188 | 
             
                    self.app = None
         | 
| 180 189 | 
             
                    self.runner = None
         | 
| @@ -199,8 +208,8 @@ class SocketIOServer: | |
| 199 208 | 
             
                        except:
         | 
| 200 209 | 
             
                            self.logger.info("Socket.IO server using python-socketio (version unavailable)")
         | 
| 201 210 |  | 
| 202 | 
            -
                def  | 
| 203 | 
            -
                    """Start the Socket.IO server in a background thread."""
         | 
| 211 | 
            +
                def start_sync(self):
         | 
| 212 | 
            +
                    """Start the Socket.IO server in a background thread (synchronous version)."""
         | 
| 204 213 | 
             
                    if not SOCKETIO_AVAILABLE:
         | 
| 205 214 | 
             
                        self.logger.debug("Socket.IO server skipped - required packages not installed")
         | 
| 206 215 | 
             
                        return
         | 
| @@ -223,13 +232,13 @@ class SocketIOServer: | |
| 223 232 | 
             
                    else:
         | 
| 224 233 | 
             
                        self.logger.error(f"❌ Socket.IO server thread failed to start!")
         | 
| 225 234 |  | 
| 226 | 
            -
                def  | 
| 227 | 
            -
                    """Stop the Socket.IO server."""
         | 
| 235 | 
            +
                def stop_sync(self):
         | 
| 236 | 
            +
                    """Stop the Socket.IO server (synchronous version)."""
         | 
| 228 237 | 
             
                    self.running = False
         | 
| 229 238 | 
             
                    if self.loop:
         | 
| 230 239 | 
             
                        asyncio.run_coroutine_threadsafe(self._shutdown(), self.loop)
         | 
| 231 240 | 
             
                    if self.thread:
         | 
| 232 | 
            -
                        self.thread.join(timeout= | 
| 241 | 
            +
                        self.thread.join(timeout=TimeoutConfig.THREAD_JOIN_TIMEOUT)
         | 
| 233 242 | 
             
                    self.logger.info("Socket.IO server stopped")
         | 
| 234 243 |  | 
| 235 244 | 
             
                def _run_server(self):
         | 
| @@ -258,8 +267,8 @@ class SocketIOServer: | |
| 258 267 | 
             
                        # Create Socket.IO server with improved configuration
         | 
| 259 268 | 
             
                        self.sio = socketio.AsyncServer(
         | 
| 260 269 | 
             
                            cors_allowed_origins="*",
         | 
| 261 | 
            -
                            ping_timeout= | 
| 262 | 
            -
                            ping_interval= | 
| 270 | 
            +
                            ping_timeout=NetworkConfig.PING_TIMEOUT,
         | 
| 271 | 
            +
                            ping_interval=NetworkConfig.PING_INTERVAL,
         | 
| 263 272 | 
             
                            max_http_buffer_size=1000000,
         | 
| 264 273 | 
             
                            allow_upgrades=True,
         | 
| 265 274 | 
             
                            transports=['websocket', 'polling'],
         | 
| @@ -310,7 +319,16 @@ class SocketIOServer: | |
| 310 319 | 
             
                        await self.runner.setup()
         | 
| 311 320 |  | 
| 312 321 | 
             
                        self.site = web.TCPSite(self.runner, self.host, self.port)
         | 
| 313 | 
            -
                         | 
| 322 | 
            +
                        try:
         | 
| 323 | 
            +
                            await self.site.start()
         | 
| 324 | 
            +
                        except OSError as e:
         | 
| 325 | 
            +
                            if "Address already in use" in str(e) or "address already in use" in str(e).lower():
         | 
| 326 | 
            +
                                raise MPMConnectionError(
         | 
| 327 | 
            +
                                    f"Port {self.port} is already in use",
         | 
| 328 | 
            +
                                    context={"host": self.host, "port": self.port, "error": str(e)}
         | 
| 329 | 
            +
                                ) from e
         | 
| 330 | 
            +
                            else:
         | 
| 331 | 
            +
                                raise
         | 
| 314 332 |  | 
| 315 333 | 
             
                        self.logger.info(f"🎉 Socket.IO server SUCCESSFULLY listening on http://{self.host}:{self.port}")
         | 
| 316 334 |  | 
| @@ -319,7 +337,7 @@ class SocketIOServer: | |
| 319 337 | 
             
                        while self.running:
         | 
| 320 338 | 
             
                            await asyncio.sleep(0.1)
         | 
| 321 339 | 
             
                            loop_count += 1
         | 
| 322 | 
            -
                            if loop_count %  | 
| 340 | 
            +
                            if loop_count % PerformanceConfig.LOG_EVERY_N_ITERATIONS == 0:
         | 
| 323 341 | 
             
                                self.logger.debug(f"🔄 Socket.IO server heartbeat - {len(self.clients)} clients connected")
         | 
| 324 342 |  | 
| 325 343 | 
             
                    except Exception as e:
         | 
| @@ -463,7 +481,7 @@ class SocketIOServer: | |
| 463 481 | 
             
                async def _handle_cors_preflight(self, request):
         | 
| 464 482 | 
             
                    """Handle CORS preflight requests."""
         | 
| 465 483 | 
             
                    return web.Response(
         | 
| 466 | 
            -
                        status= | 
| 484 | 
            +
                        status=NetworkConfig.HTTP_OK,
         | 
| 467 485 | 
             
                        headers={
         | 
| 468 486 | 
             
                            'Access-Control-Allow-Origin': '*',
         | 
| 469 487 | 
             
                            'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
         | 
| @@ -502,8 +520,12 @@ class SocketIOServer: | |
| 502 520 |  | 
| 503 521 | 
             
                        self.logger.debug(f"Git diff requested for file: {file_path}, timestamp: {timestamp}")
         | 
| 504 522 |  | 
| 505 | 
            -
                        # Generate git diff using the  | 
| 506 | 
            -
                         | 
| 523 | 
            +
                        # Generate git diff using the git handler's method
         | 
| 524 | 
            +
                        if hasattr(self, 'git_handler') and self.git_handler:
         | 
| 525 | 
            +
                            diff_result = await self.git_handler.generate_git_diff(file_path, timestamp, working_dir)
         | 
| 526 | 
            +
                        else:
         | 
| 527 | 
            +
                            # Fallback to old method if handler not available
         | 
| 528 | 
            +
                            diff_result = await self._generate_git_diff(file_path, timestamp, working_dir)
         | 
| 507 529 |  | 
| 508 530 | 
             
                        self.logger.info(f"Git diff result: success={diff_result.get('success', False)}, method={diff_result.get('method', 'unknown')}")
         | 
| 509 531 |  | 
| @@ -520,7 +542,7 @@ class SocketIOServer: | |
| 520 542 | 
             
                        return web.json_response({
         | 
| 521 543 | 
             
                            "success": False,
         | 
| 522 544 | 
             
                            "error": f"Failed to generate git diff: {str(e)}"
         | 
| 523 | 
            -
                        }, status= | 
| 545 | 
            +
                        }, status=NetworkConfig.HTTP_INTERNAL_ERROR, headers={
         | 
| 524 546 | 
             
                            'Access-Control-Allow-Origin': '*',
         | 
| 525 547 | 
             
                            'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
         | 
| 526 548 | 
             
                            'Access-Control-Allow-Headers': 'Content-Type, Accept'
         | 
| @@ -538,7 +560,7 @@ class SocketIOServer: | |
| 538 560 | 
             
                        # Extract query parameters
         | 
| 539 561 | 
             
                        file_path = request.query.get('file_path')
         | 
| 540 562 | 
             
                        working_dir = request.query.get('working_dir', os.getcwd())
         | 
| 541 | 
            -
                        max_size = int(request.query.get('max_size',  | 
| 563 | 
            +
                        max_size = int(request.query.get('max_size', SystemLimits.MAX_FILE_SIZE))
         | 
| 542 564 |  | 
| 543 565 | 
             
                        self.logger.info(f"File content API request: file_path={file_path}, working_dir={working_dir}")
         | 
| 544 566 |  | 
| @@ -553,8 +575,12 @@ class SocketIOServer: | |
| 553 575 | 
             
                                'Access-Control-Allow-Headers': 'Content-Type, Accept'
         | 
| 554 576 | 
             
                            })
         | 
| 555 577 |  | 
| 556 | 
            -
                        # Use the  | 
| 557 | 
            -
                         | 
| 578 | 
            +
                        # Use the file handler's safe reading logic
         | 
| 579 | 
            +
                        if hasattr(self, 'file_handler') and self.file_handler:
         | 
| 580 | 
            +
                            result = await self.file_handler._read_file_safely(file_path, working_dir, max_size)
         | 
| 581 | 
            +
                        else:
         | 
| 582 | 
            +
                            # Fallback to old method if handler not available
         | 
| 583 | 
            +
                            result = await self._read_file_safely(file_path, working_dir, max_size)
         | 
| 558 584 |  | 
| 559 585 | 
             
                        status_code = 200 if result.get('success') else 400
         | 
| 560 586 | 
             
                        return web.json_response(result, status=status_code, headers={
         | 
| @@ -570,13 +596,13 @@ class SocketIOServer: | |
| 570 596 | 
             
                        return web.json_response({
         | 
| 571 597 | 
             
                            "success": False,
         | 
| 572 598 | 
             
                            "error": f"Failed to read file: {str(e)}"
         | 
| 573 | 
            -
                        }, status= | 
| 599 | 
            +
                        }, status=NetworkConfig.HTTP_INTERNAL_ERROR, headers={
         | 
| 574 600 | 
             
                            'Access-Control-Allow-Origin': '*',
         | 
| 575 601 | 
             
                            'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
         | 
| 576 602 | 
             
                            'Access-Control-Allow-Headers': 'Content-Type, Accept'
         | 
| 577 603 | 
             
                        })
         | 
| 578 604 |  | 
| 579 | 
            -
                async def _read_file_safely(self, file_path: str, working_dir: str = None, max_size: int =  | 
| 605 | 
            +
                async def _read_file_safely(self, file_path: str, working_dir: str = None, max_size: int = SystemLimits.MAX_FILE_SIZE):
         | 
| 580 606 | 
             
                    """Safely read file content with security checks.
         | 
| 581 607 |  | 
| 582 608 | 
             
                    This method contains the core file reading logic that can be used by both
         | 
| @@ -998,7 +1024,30 @@ class SocketIOServer: | |
| 998 1024 |  | 
| 999 1025 |  | 
| 1000 1026 | 
             
                def _register_events(self):
         | 
| 1001 | 
            -
                    """Register Socket.IO event handlers. | 
| 1027 | 
            +
                    """Register Socket.IO event handlers.
         | 
| 1028 | 
            +
                    
         | 
| 1029 | 
            +
                    WHY: This method now uses the EventHandlerRegistry to manage all event
         | 
| 1030 | 
            +
                    handlers in a modular way. Each handler focuses on a specific domain,
         | 
| 1031 | 
            +
                    reducing complexity and improving maintainability.
         | 
| 1032 | 
            +
                    """
         | 
| 1033 | 
            +
                    # Initialize the event handler registry
         | 
| 1034 | 
            +
                    self.event_registry = EventHandlerRegistry(self)
         | 
| 1035 | 
            +
                    self.event_registry.initialize()
         | 
| 1036 | 
            +
                    
         | 
| 1037 | 
            +
                    # Register all events from all handlers
         | 
| 1038 | 
            +
                    self.event_registry.register_all_events()
         | 
| 1039 | 
            +
                    
         | 
| 1040 | 
            +
                    # Keep handler instances for HTTP endpoint compatibility
         | 
| 1041 | 
            +
                    self.file_handler = self.event_registry.get_handler(FileEventHandler)
         | 
| 1042 | 
            +
                    self.git_handler = self.event_registry.get_handler(GitEventHandler)
         | 
| 1043 | 
            +
                    
         | 
| 1044 | 
            +
                    self.logger.info("All Socket.IO events registered via handler system")
         | 
| 1045 | 
            +
                    
         | 
| 1046 | 
            +
                    # Note: The actual event registration is now handled by individual
         | 
| 1047 | 
            +
                    # handler classes in socketio/handlers/. This dramatically reduces
         | 
| 1048 | 
            +
                    # the complexity of this method from 514 lines to under 20 lines.
         | 
| 1049 | 
            +
                    
         | 
| 1050 | 
            +
                    return  # Early return to skip old implementation
         | 
| 1002 1051 |  | 
| 1003 1052 | 
             
                    @self.sio.event
         | 
| 1004 1053 | 
             
                    async def connect(sid, environ, *args):
         | 
| @@ -1240,7 +1289,7 @@ class SocketIOServer: | |
| 1240 1289 | 
             
                        try:
         | 
| 1241 1290 | 
             
                            file_path = data.get('file_path')
         | 
| 1242 1291 | 
             
                            working_dir = data.get('working_dir', os.getcwd())
         | 
| 1243 | 
            -
                            max_size = data.get('max_size',  | 
| 1292 | 
            +
                            max_size = data.get('max_size', SystemLimits.MAX_FILE_SIZE)
         | 
| 1244 1293 |  | 
| 1245 1294 | 
             
                            if not file_path:
         | 
| 1246 1295 | 
             
                                await self.sio.emit('file_content_response', {
         | 
| @@ -1626,7 +1675,7 @@ class SocketIOServer: | |
| 1626 1675 | 
             
                            )
         | 
| 1627 1676 | 
             
                            # Wait for completion with timeout to detect issues
         | 
| 1628 1677 | 
             
                            try:
         | 
| 1629 | 
            -
                                future.result(timeout= | 
| 1678 | 
            +
                                future.result(timeout=TimeoutConfig.QUICK_TIMEOUT)
         | 
| 1630 1679 | 
             
                                self.logger.debug(f"📨 Successfully broadcasted {event_type} to {len(self.clients)} clients")
         | 
| 1631 1680 | 
             
                            except asyncio.TimeoutError:
         | 
| 1632 1681 | 
             
                                self.logger.warning(f"⏰ Broadcast timeout for event {event_type} - continuing anyway")
         | 
| @@ -1758,6 +1807,83 @@ class SocketIOServer: | |
| 1758 1807 | 
             
                        "context_size": context_size,
         | 
| 1759 1808 | 
             
                        "timestamp": datetime.utcnow().isoformat() + "Z"
         | 
| 1760 1809 | 
             
                    })
         | 
| 1810 | 
            +
                
         | 
| 1811 | 
            +
                # ================================================================================
         | 
| 1812 | 
            +
                # Interface Adapter Methods
         | 
| 1813 | 
            +
                # ================================================================================
         | 
| 1814 | 
            +
                # These methods adapt the existing implementation to comply with SocketIOServiceInterface
         | 
| 1815 | 
            +
                
         | 
| 1816 | 
            +
                async def start(self, host: str = "localhost", port: int = 8765) -> None:
         | 
| 1817 | 
            +
                    """Start the WebSocket server (async adapter).
         | 
| 1818 | 
            +
                    
         | 
| 1819 | 
            +
                    WHY: The interface expects async methods, but our implementation uses
         | 
| 1820 | 
            +
                    synchronous start with background threads. This adapter provides compatibility.
         | 
| 1821 | 
            +
                    
         | 
| 1822 | 
            +
                    Args:
         | 
| 1823 | 
            +
                        host: Host to bind to
         | 
| 1824 | 
            +
                        port: Port to listen on
         | 
| 1825 | 
            +
                    """
         | 
| 1826 | 
            +
                    self.host = host
         | 
| 1827 | 
            +
                    self.port = port
         | 
| 1828 | 
            +
                    # Call the synchronous start method
         | 
| 1829 | 
            +
                    self.start_sync()
         | 
| 1830 | 
            +
                
         | 
| 1831 | 
            +
                async def stop(self) -> None:
         | 
| 1832 | 
            +
                    """Stop the WebSocket server (async adapter).
         | 
| 1833 | 
            +
                    
         | 
| 1834 | 
            +
                    WHY: The interface expects async methods. This adapter wraps the
         | 
| 1835 | 
            +
                    synchronous stop method for interface compliance.
         | 
| 1836 | 
            +
                    """
         | 
| 1837 | 
            +
                    # Call the synchronous stop method
         | 
| 1838 | 
            +
                    self.stop_sync()
         | 
| 1839 | 
            +
                
         | 
| 1840 | 
            +
                async def emit(self, event: str, data: Any, room: Optional[str] = None) -> None:
         | 
| 1841 | 
            +
                    """Emit an event to connected clients.
         | 
| 1842 | 
            +
                    
         | 
| 1843 | 
            +
                    WHY: Provides interface compliance by wrapping broadcast_event with
         | 
| 1844 | 
            +
                    async signature and room support.
         | 
| 1845 | 
            +
                    
         | 
| 1846 | 
            +
                    Args:
         | 
| 1847 | 
            +
                        event: Event name
         | 
| 1848 | 
            +
                        data: Event data
         | 
| 1849 | 
            +
                        room: Optional room to target (not supported in current implementation)
         | 
| 1850 | 
            +
                    """
         | 
| 1851 | 
            +
                    if room:
         | 
| 1852 | 
            +
                        self.logger.warning(f"Room-based emit not supported, broadcasting to all: {event}")
         | 
| 1853 | 
            +
                    
         | 
| 1854 | 
            +
                    # Use existing broadcast_event method
         | 
| 1855 | 
            +
                    self.broadcast_event(event, data)
         | 
| 1856 | 
            +
                
         | 
| 1857 | 
            +
                async def broadcast(self, event: str, data: Any) -> None:
         | 
| 1858 | 
            +
                    """Broadcast event to all connected clients.
         | 
| 1859 | 
            +
                    
         | 
| 1860 | 
            +
                    WHY: Provides interface compliance with async signature.
         | 
| 1861 | 
            +
                    
         | 
| 1862 | 
            +
                    Args:
         | 
| 1863 | 
            +
                        event: Event name
         | 
| 1864 | 
            +
                        data: Event data
         | 
| 1865 | 
            +
                    """
         | 
| 1866 | 
            +
                    self.broadcast_event(event, data)
         | 
| 1867 | 
            +
                
         | 
| 1868 | 
            +
                def get_connection_count(self) -> int:
         | 
| 1869 | 
            +
                    """Get number of connected clients.
         | 
| 1870 | 
            +
                    
         | 
| 1871 | 
            +
                    WHY: Provides interface compliance for monitoring connections.
         | 
| 1872 | 
            +
                    
         | 
| 1873 | 
            +
                    Returns:
         | 
| 1874 | 
            +
                        Number of active connections
         | 
| 1875 | 
            +
                    """
         | 
| 1876 | 
            +
                    return len(self.clients)
         | 
| 1877 | 
            +
                
         | 
| 1878 | 
            +
                def is_running(self) -> bool:
         | 
| 1879 | 
            +
                    """Check if server is running.
         | 
| 1880 | 
            +
                    
         | 
| 1881 | 
            +
                    WHY: Provides interface compliance for status checking.
         | 
| 1882 | 
            +
                    
         | 
| 1883 | 
            +
                    Returns:
         | 
| 1884 | 
            +
                        True if server is active
         | 
| 1885 | 
            +
                    """
         | 
| 1886 | 
            +
                    return self.running
         | 
| 1761 1887 |  | 
| 1762 1888 |  | 
| 1763 1889 | 
             
            # Global instance for easy access
         | 
| @@ -1795,7 +1921,7 @@ def get_socketio_server() -> SocketIOServer: | |
| 1795 1921 | 
             
            def start_socketio_server():
         | 
| 1796 1922 | 
             
                """Start the global Socket.IO server."""
         | 
| 1797 1923 | 
             
                server = get_socketio_server()
         | 
| 1798 | 
            -
                server. | 
| 1924 | 
            +
                server.start_sync()
         | 
| 1799 1925 | 
             
                return server
         | 
| 1800 1926 |  | 
| 1801 1927 |  | 
| @@ -1803,5 +1929,5 @@ def stop_socketio_server(): | |
| 1803 1929 | 
             
                """Stop the global Socket.IO server."""
         | 
| 1804 1930 | 
             
                global _socketio_server
         | 
| 1805 1931 | 
             
                if _socketio_server:
         | 
| 1806 | 
            -
                    _socketio_server. | 
| 1932 | 
            +
                    _socketio_server.stop_sync()
         | 
| 1807 1933 | 
             
                    _socketio_server = None
         |