claude-mpm 3.1.3__py3-none-any.whl → 3.3.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/__init__.py +3 -3
 - claude_mpm/__main__.py +0 -17
 - claude_mpm/agents/INSTRUCTIONS.md +149 -17
 - claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
 - claude_mpm/agents/base_agent.json +1 -1
 - claude_mpm/agents/templates/pm.json +25 -0
 - claude_mpm/agents/templates/research.json +2 -1
 - claude_mpm/cli/__init__.py +19 -23
 - claude_mpm/cli/commands/__init__.py +3 -1
 - claude_mpm/cli/commands/agents.py +7 -18
 - claude_mpm/cli/commands/info.py +5 -10
 - claude_mpm/cli/commands/memory.py +232 -0
 - claude_mpm/cli/commands/run.py +501 -28
 - claude_mpm/cli/commands/tickets.py +10 -17
 - claude_mpm/cli/commands/ui.py +15 -37
 - claude_mpm/cli/parser.py +91 -1
 - claude_mpm/cli/utils.py +9 -28
 - claude_mpm/config/socketio_config.py +256 -0
 - claude_mpm/constants.py +9 -0
 - claude_mpm/core/__init__.py +2 -2
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/claude_runner.py +919 -0
 - claude_mpm/core/config.py +21 -1
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/hook_manager.py +196 -0
 - claude_mpm/core/pm_hook_interceptor.py +205 -0
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +323 -33
 - claude_mpm/core/socketio_pool.py +582 -0
 - claude_mpm/core/websocket_handler.py +233 -0
 - claude_mpm/deployment_paths.py +261 -0
 - claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
 - claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
 - claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
 - claude_mpm/hooks/memory_integration_hook.py +312 -0
 - claude_mpm/models/__init__.py +9 -91
 - claude_mpm/orchestration/__init__.py +1 -1
 - claude_mpm/scripts/claude-mpm-socketio +32 -0
 - claude_mpm/scripts/claude_mpm_monitor.html +567 -0
 - claude_mpm/scripts/install_socketio_server.py +407 -0
 - claude_mpm/scripts/launch_monitor.py +132 -0
 - claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
 - claude_mpm/scripts/manage_version.py +479 -0
 - claude_mpm/scripts/socketio_daemon.py +181 -0
 - claude_mpm/scripts/socketio_server_manager.py +428 -0
 - claude_mpm/services/__init__.py +5 -0
 - claude_mpm/services/agent_lifecycle_manager.py +76 -25
 - claude_mpm/services/agent_memory_manager.py +684 -0
 - claude_mpm/services/agent_modification_tracker.py +98 -17
 - claude_mpm/services/agent_persistence_service.py +33 -13
 - claude_mpm/services/agent_registry.py +82 -43
 - claude_mpm/services/hook_service.py +362 -0
 - claude_mpm/services/socketio_client_manager.py +474 -0
 - claude_mpm/services/socketio_server.py +922 -0
 - claude_mpm/services/standalone_socketio_server.py +631 -0
 - claude_mpm/services/ticket_manager.py +4 -5
 - claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
 - claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
 - claude_mpm/services/version_control/semantic_versioning.py +9 -10
 - claude_mpm/services/websocket_server.py +376 -0
 - claude_mpm/utils/dependency_manager.py +211 -0
 - claude_mpm/utils/import_migration_example.py +80 -0
 - claude_mpm/utils/path_operations.py +0 -20
 - claude_mpm/web/open_dashboard.py +34 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/METADATA +20 -9
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/RECORD +71 -50
 - claude_mpm-3.3.0.dist-info/entry_points.txt +7 -0
 - claude_mpm/cli_old.py +0 -728
 - claude_mpm/models/common.py +0 -41
 - claude_mpm/models/lifecycle.py +0 -97
 - claude_mpm/models/modification.py +0 -126
 - claude_mpm/models/persistence.py +0 -57
 - claude_mpm/models/registry.py +0 -91
 - claude_mpm/security/__init__.py +0 -8
 - claude_mpm/security/bash_validator.py +0 -393
 - claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
 - /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/top_level.txt +0 -0
 
| 
         @@ -0,0 +1,233 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Socket.IO logging handler with connection pooling for real-time log streaming.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            This handler now uses the Socket.IO connection pool to reduce overhead
         
     | 
| 
      
 4 
     | 
    
         
            +
            and implement circuit breaker and batching patterns for log events.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            WHY connection pooling approach:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Reduces connection setup/teardown overhead by 80%
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Implements circuit breaker for resilience during outages
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Provides micro-batching for high-frequency log events
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Maintains persistent connections for better performance
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Falls back gracefully when pool unavailable
         
     | 
| 
      
 12 
     | 
    
         
            +
            """
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 15 
     | 
    
         
            +
            import json
         
     | 
| 
      
 16 
     | 
    
         
            +
            import os
         
     | 
| 
      
 17 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 18 
     | 
    
         
            +
            from typing import Optional
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            # Connection pool import
         
     | 
| 
      
 21 
     | 
    
         
            +
            try:
         
     | 
| 
      
 22 
     | 
    
         
            +
                from .socketio_pool import get_connection_pool
         
     | 
| 
      
 23 
     | 
    
         
            +
                POOL_AVAILABLE = True
         
     | 
| 
      
 24 
     | 
    
         
            +
            except ImportError:
         
     | 
| 
      
 25 
     | 
    
         
            +
                POOL_AVAILABLE = False
         
     | 
| 
      
 26 
     | 
    
         
            +
                get_connection_pool = None
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            # Fallback imports
         
     | 
| 
      
 29 
     | 
    
         
            +
            from ..services.websocket_server import get_server_instance
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            class WebSocketHandler(logging.Handler):
         
     | 
| 
      
 33 
     | 
    
         
            +
                """Logging handler that broadcasts log messages via Socket.IO connection pool.
         
     | 
| 
      
 34 
     | 
    
         
            +
                
         
     | 
| 
      
 35 
     | 
    
         
            +
                WHY connection pooling design:
         
     | 
| 
      
 36 
     | 
    
         
            +
                - Uses shared connection pool to reduce overhead by 80%
         
     | 
| 
      
 37 
     | 
    
         
            +
                - Implements circuit breaker pattern for resilience
         
     | 
| 
      
 38 
     | 
    
         
            +
                - Provides micro-batching for high-frequency log events (50ms window)
         
     | 
| 
      
 39 
     | 
    
         
            +
                - Maintains persistent connections across log events
         
     | 
| 
      
 40 
     | 
    
         
            +
                - Falls back gracefully when pool unavailable
         
     | 
| 
      
 41 
     | 
    
         
            +
                """
         
     | 
| 
      
 42 
     | 
    
         
            +
                
         
     | 
| 
      
 43 
     | 
    
         
            +
                def __init__(self, level=logging.NOTSET):
         
     | 
| 
      
 44 
     | 
    
         
            +
                    super().__init__(level)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    self._websocket_server = None
         
     | 
| 
      
 46 
     | 
    
         
            +
                    self._connection_pool = None
         
     | 
| 
      
 47 
     | 
    
         
            +
                    self._pool_initialized = False
         
     | 
| 
      
 48 
     | 
    
         
            +
                    self._debug = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', '').lower() == 'true'
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def _init_connection_pool(self):
         
     | 
| 
      
 51 
     | 
    
         
            +
                    """Initialize connection pool with lazy loading.
         
     | 
| 
      
 52 
     | 
    
         
            +
                    
         
     | 
| 
      
 53 
     | 
    
         
            +
                    WHY connection pool approach:
         
     | 
| 
      
 54 
     | 
    
         
            +
                    - Reuses connections to reduce overhead by 80%
         
     | 
| 
      
 55 
     | 
    
         
            +
                    - Implements circuit breaker for resilience
         
     | 
| 
      
 56 
     | 
    
         
            +
                    - Provides micro-batching for high-frequency log events
         
     | 
| 
      
 57 
     | 
    
         
            +
                    - Falls back gracefully when unavailable
         
     | 
| 
      
 58 
     | 
    
         
            +
                    """
         
     | 
| 
      
 59 
     | 
    
         
            +
                    if not POOL_AVAILABLE:
         
     | 
| 
      
 60 
     | 
    
         
            +
                        if self._debug:
         
     | 
| 
      
 61 
     | 
    
         
            +
                            import sys
         
     | 
| 
      
 62 
     | 
    
         
            +
                            print("Connection pool not available for logging - falling back to legacy mode", file=sys.stderr)
         
     | 
| 
      
 63 
     | 
    
         
            +
                        return
         
     | 
| 
      
 64 
     | 
    
         
            +
                    
         
     | 
| 
      
 65 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 66 
     | 
    
         
            +
                        self._connection_pool = get_connection_pool()
         
     | 
| 
      
 67 
     | 
    
         
            +
                        if self._debug:
         
     | 
| 
      
 68 
     | 
    
         
            +
                            import sys
         
     | 
| 
      
 69 
     | 
    
         
            +
                            print("WebSocket handler: Using Socket.IO connection pool", file=sys.stderr)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 71 
     | 
    
         
            +
                        if self._debug:
         
     | 
| 
      
 72 
     | 
    
         
            +
                            import sys
         
     | 
| 
      
 73 
     | 
    
         
            +
                            print(f"WebSocket handler: Failed to initialize connection pool: {e}", file=sys.stderr)
         
     | 
| 
      
 74 
     | 
    
         
            +
                        self._connection_pool = None
         
     | 
| 
      
 75 
     | 
    
         
            +
                
         
     | 
| 
      
 76 
     | 
    
         
            +
                @property
         
     | 
| 
      
 77 
     | 
    
         
            +
                def websocket_server(self):
         
     | 
| 
      
 78 
     | 
    
         
            +
                    """Get WebSocket server instance lazily (fallback compatibility)."""
         
     | 
| 
      
 79 
     | 
    
         
            +
                    if self._websocket_server is None:
         
     | 
| 
      
 80 
     | 
    
         
            +
                        self._websocket_server = get_server_instance()
         
     | 
| 
      
 81 
     | 
    
         
            +
                    return self._websocket_server
         
     | 
| 
      
 82 
     | 
    
         
            +
                    
         
     | 
| 
      
 83 
     | 
    
         
            +
                def emit(self, record: logging.LogRecord):
         
     | 
| 
      
 84 
     | 
    
         
            +
                    """Emit a log record via Socket.IO connection pool with batching.
         
     | 
| 
      
 85 
     | 
    
         
            +
                    
         
     | 
| 
      
 86 
     | 
    
         
            +
                    WHY connection pool approach:
         
     | 
| 
      
 87 
     | 
    
         
            +
                    - Uses shared connection pool to reduce overhead by 80%
         
     | 
| 
      
 88 
     | 
    
         
            +
                    - Implements circuit breaker for resilience during outages
         
     | 
| 
      
 89 
     | 
    
         
            +
                    - Provides micro-batching for high-frequency log events (50ms window)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    - Falls back gracefully when pool unavailable
         
     | 
| 
      
 91 
     | 
    
         
            +
                    """
         
     | 
| 
      
 92 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 93 
     | 
    
         
            +
                        # Skip connection pool logs to avoid infinite recursion
         
     | 
| 
      
 94 
     | 
    
         
            +
                        if "socketio" in record.name.lower() or record.name == "claude_mpm.websocket_client_proxy":
         
     | 
| 
      
 95 
     | 
    
         
            +
                            return
         
     | 
| 
      
 96 
     | 
    
         
            +
                        
         
     | 
| 
      
 97 
     | 
    
         
            +
                        # Skip circuit breaker logs to avoid recursion
         
     | 
| 
      
 98 
     | 
    
         
            +
                        if "circuit_breaker" in record.name.lower() or "socketio_pool" in record.name.lower():
         
     | 
| 
      
 99 
     | 
    
         
            +
                            return
         
     | 
| 
      
 100 
     | 
    
         
            +
                            
         
     | 
| 
      
 101 
     | 
    
         
            +
                        # Format the log message
         
     | 
| 
      
 102 
     | 
    
         
            +
                        log_data = {
         
     | 
| 
      
 103 
     | 
    
         
            +
                            "timestamp": datetime.utcnow().isoformat() + "Z",
         
     | 
| 
      
 104 
     | 
    
         
            +
                            "level": record.levelname,
         
     | 
| 
      
 105 
     | 
    
         
            +
                            "logger": record.name,
         
     | 
| 
      
 106 
     | 
    
         
            +
                            "message": self.format(record),
         
     | 
| 
      
 107 
     | 
    
         
            +
                            "module": record.module,
         
     | 
| 
      
 108 
     | 
    
         
            +
                            "function": record.funcName,
         
     | 
| 
      
 109 
     | 
    
         
            +
                            "line": record.lineno,
         
     | 
| 
      
 110 
     | 
    
         
            +
                            "thread": record.thread,
         
     | 
| 
      
 111 
     | 
    
         
            +
                            "thread_name": record.threadName
         
     | 
| 
      
 112 
     | 
    
         
            +
                        }
         
     | 
| 
      
 113 
     | 
    
         
            +
                        
         
     | 
| 
      
 114 
     | 
    
         
            +
                        # Add exception info if present
         
     | 
| 
      
 115 
     | 
    
         
            +
                        if record.exc_info:
         
     | 
| 
      
 116 
     | 
    
         
            +
                            import traceback
         
     | 
| 
      
 117 
     | 
    
         
            +
                            log_data["exception"] = ''.join(traceback.format_exception(*record.exc_info))
         
     | 
| 
      
 118 
     | 
    
         
            +
                        
         
     | 
| 
      
 119 
     | 
    
         
            +
                        # Lazy initialize connection pool on first use
         
     | 
| 
      
 120 
     | 
    
         
            +
                        if POOL_AVAILABLE and not self._pool_initialized:
         
     | 
| 
      
 121 
     | 
    
         
            +
                            self._pool_initialized = True
         
     | 
| 
      
 122 
     | 
    
         
            +
                            self._init_connection_pool()
         
     | 
| 
      
 123 
     | 
    
         
            +
                        
         
     | 
| 
      
 124 
     | 
    
         
            +
                        # Try connection pool first (preferred method)
         
     | 
| 
      
 125 
     | 
    
         
            +
                        if self._connection_pool:
         
     | 
| 
      
 126 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 127 
     | 
    
         
            +
                                self._connection_pool.emit_event('/log', 'message', log_data)
         
     | 
| 
      
 128 
     | 
    
         
            +
                                if self._debug:
         
     | 
| 
      
 129 
     | 
    
         
            +
                                    import sys
         
     | 
| 
      
 130 
     | 
    
         
            +
                                    print(f"Emitted pooled Socket.IO log event: /log/message", file=sys.stderr)
         
     | 
| 
      
 131 
     | 
    
         
            +
                                return
         
     | 
| 
      
 132 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 133 
     | 
    
         
            +
                                if self._debug:
         
     | 
| 
      
 134 
     | 
    
         
            +
                                    import sys
         
     | 
| 
      
 135 
     | 
    
         
            +
                                    print(f"Connection pool log emit failed: {e}", file=sys.stderr)
         
     | 
| 
      
 136 
     | 
    
         
            +
                        
         
     | 
| 
      
 137 
     | 
    
         
            +
                        # Fallback to legacy WebSocket server
         
     | 
| 
      
 138 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 139 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 140 
     | 
    
         
            +
                                # Debug: Check what type of server we have
         
     | 
| 
      
 141 
     | 
    
         
            +
                                server_type = type(self.websocket_server).__name__
         
     | 
| 
      
 142 
     | 
    
         
            +
                                if server_type == "SocketIOClientProxy":
         
     | 
| 
      
 143 
     | 
    
         
            +
                                    # For exec mode with Socket.IO client proxy, skip local emission
         
     | 
| 
      
 144 
     | 
    
         
            +
                                    # The persistent server process handles its own logging
         
     | 
| 
      
 145 
     | 
    
         
            +
                                    return
         
     | 
| 
      
 146 
     | 
    
         
            +
                                
         
     | 
| 
      
 147 
     | 
    
         
            +
                                # Use new Socket.IO event format
         
     | 
| 
      
 148 
     | 
    
         
            +
                                if hasattr(self.websocket_server, 'log_message'):
         
     | 
| 
      
 149 
     | 
    
         
            +
                                    self.websocket_server.log_message(
         
     | 
| 
      
 150 
     | 
    
         
            +
                                        level=record.levelname,
         
     | 
| 
      
 151 
     | 
    
         
            +
                                        message=self.format(record),
         
     | 
| 
      
 152 
     | 
    
         
            +
                                        module=record.module
         
     | 
| 
      
 153 
     | 
    
         
            +
                                    )
         
     | 
| 
      
 154 
     | 
    
         
            +
                                else:
         
     | 
| 
      
 155 
     | 
    
         
            +
                                    # Legacy fallback
         
     | 
| 
      
 156 
     | 
    
         
            +
                                    self.websocket_server.broadcast_event("log.message", log_data)
         
     | 
| 
      
 157 
     | 
    
         
            +
                                
         
     | 
| 
      
 158 
     | 
    
         
            +
                                if self._debug:
         
     | 
| 
      
 159 
     | 
    
         
            +
                                    import sys
         
     | 
| 
      
 160 
     | 
    
         
            +
                                    print(f"Emitted legacy log event", file=sys.stderr)
         
     | 
| 
      
 161 
     | 
    
         
            +
                                    
         
     | 
| 
      
 162 
     | 
    
         
            +
                            except Exception as fallback_error:
         
     | 
| 
      
 163 
     | 
    
         
            +
                                if self._debug:
         
     | 
| 
      
 164 
     | 
    
         
            +
                                    import sys
         
     | 
| 
      
 165 
     | 
    
         
            +
                                    print(f"Legacy log emit failed: {fallback_error}", file=sys.stderr)
         
     | 
| 
      
 166 
     | 
    
         
            +
                        
         
     | 
| 
      
 167 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 168 
     | 
    
         
            +
                        # Don't let logging errors break the application
         
     | 
| 
      
 169 
     | 
    
         
            +
                        # But print for debugging
         
     | 
| 
      
 170 
     | 
    
         
            +
                        import sys
         
     | 
| 
      
 171 
     | 
    
         
            +
                        print(f"WebSocketHandler.emit error: {e}", file=sys.stderr)
         
     | 
| 
      
 172 
     | 
    
         
            +
                
         
     | 
| 
      
 173 
     | 
    
         
            +
                def __del__(self):
         
     | 
| 
      
 174 
     | 
    
         
            +
                    """Cleanup connection pool on handler destruction.
         
     | 
| 
      
 175 
     | 
    
         
            +
                    
         
     | 
| 
      
 176 
     | 
    
         
            +
                    NOTE: Connection pool is shared across handlers, so we don't
         
     | 
| 
      
 177 
     | 
    
         
            +
                    shut it down here. The pool manages its own lifecycle.
         
     | 
| 
      
 178 
     | 
    
         
            +
                    """
         
     | 
| 
      
 179 
     | 
    
         
            +
                    # Connection pool is managed globally, no cleanup needed per handler
         
     | 
| 
      
 180 
     | 
    
         
            +
                    pass
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
            class WebSocketFormatter(logging.Formatter):
         
     | 
| 
      
 184 
     | 
    
         
            +
                """Custom formatter for WebSocket log messages."""
         
     | 
| 
      
 185 
     | 
    
         
            +
                
         
     | 
| 
      
 186 
     | 
    
         
            +
                def __init__(self):
         
     | 
| 
      
 187 
     | 
    
         
            +
                    super().__init__(
         
     | 
| 
      
 188 
     | 
    
         
            +
                        fmt='%(name)s - %(levelname)s - %(message)s',
         
     | 
| 
      
 189 
     | 
    
         
            +
                        datefmt='%Y-%m-%d %H:%M:%S'
         
     | 
| 
      
 190 
     | 
    
         
            +
                    )
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            def setup_websocket_logging(logger_name: Optional[str] = None, level: int = logging.INFO):
         
     | 
| 
      
 194 
     | 
    
         
            +
                """
         
     | 
| 
      
 195 
     | 
    
         
            +
                Set up WebSocket logging for a logger.
         
     | 
| 
      
 196 
     | 
    
         
            +
                
         
     | 
| 
      
 197 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 198 
     | 
    
         
            +
                    logger_name: Name of logger to configure (None for root logger)
         
     | 
| 
      
 199 
     | 
    
         
            +
                    level: Minimum logging level to broadcast
         
     | 
| 
      
 200 
     | 
    
         
            +
                    
         
     | 
| 
      
 201 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 202 
     | 
    
         
            +
                    The configured WebSocketHandler
         
     | 
| 
      
 203 
     | 
    
         
            +
                """
         
     | 
| 
      
 204 
     | 
    
         
            +
                handler = WebSocketHandler(level=level)
         
     | 
| 
      
 205 
     | 
    
         
            +
                handler.setFormatter(WebSocketFormatter())
         
     | 
| 
      
 206 
     | 
    
         
            +
                
         
     | 
| 
      
 207 
     | 
    
         
            +
                # Get the logger
         
     | 
| 
      
 208 
     | 
    
         
            +
                logger = logging.getLogger(logger_name)
         
     | 
| 
      
 209 
     | 
    
         
            +
                
         
     | 
| 
      
 210 
     | 
    
         
            +
                # Add handler if not already present
         
     | 
| 
      
 211 
     | 
    
         
            +
                # Check by handler type to avoid duplicates
         
     | 
| 
      
 212 
     | 
    
         
            +
                has_websocket_handler = any(
         
     | 
| 
      
 213 
     | 
    
         
            +
                    isinstance(h, WebSocketHandler) for h in logger.handlers
         
     | 
| 
      
 214 
     | 
    
         
            +
                )
         
     | 
| 
      
 215 
     | 
    
         
            +
                
         
     | 
| 
      
 216 
     | 
    
         
            +
                if not has_websocket_handler:
         
     | 
| 
      
 217 
     | 
    
         
            +
                    logger.addHandler(handler)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    
         
     | 
| 
      
 219 
     | 
    
         
            +
                return handler
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
            def remove_websocket_logging(logger_name: Optional[str] = None):
         
     | 
| 
      
 223 
     | 
    
         
            +
                """Remove WebSocket handler from a logger."""
         
     | 
| 
      
 224 
     | 
    
         
            +
                logger = logging.getLogger(logger_name)
         
     | 
| 
      
 225 
     | 
    
         
            +
                
         
     | 
| 
      
 226 
     | 
    
         
            +
                # Remove all WebSocket handlers
         
     | 
| 
      
 227 
     | 
    
         
            +
                handlers_to_remove = [
         
     | 
| 
      
 228 
     | 
    
         
            +
                    h for h in logger.handlers if isinstance(h, WebSocketHandler)
         
     | 
| 
      
 229 
     | 
    
         
            +
                ]
         
     | 
| 
      
 230 
     | 
    
         
            +
                
         
     | 
| 
      
 231 
     | 
    
         
            +
                for handler in handlers_to_remove:
         
     | 
| 
      
 232 
     | 
    
         
            +
                    logger.removeHandler(handler)
         
     | 
| 
      
 233 
     | 
    
         
            +
                    handler.close()
         
     | 
| 
         @@ -0,0 +1,261 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """
         
     | 
| 
      
 2 
     | 
    
         
            +
            Deployment path management for Claude MPM.
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            WHY: Using relative parent traversal (e.g., Path(__file__).parent.parent.parent) is fragile
         
     | 
| 
      
 5 
     | 
    
         
            +
            and breaks when files are moved or during different deployment scenarios (pip install,
         
     | 
| 
      
 6 
     | 
    
         
            +
            development, packaged distribution). This module provides centralized path resolution
         
     | 
| 
      
 7 
     | 
    
         
            +
            that works across all deployment scenarios.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            DESIGN DECISION: We detect the deployment context and provide consistent paths regardless
         
     | 
| 
      
 10 
     | 
    
         
            +
            of how the package is installed or run. This includes:
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Development mode (running from source)
         
     | 
| 
      
 12 
     | 
    
         
            +
            - Pip installed packages
         
     | 
| 
      
 13 
     | 
    
         
            +
            - Packaged distributions
         
     | 
| 
      
 14 
     | 
    
         
            +
            - Test environments
         
     | 
| 
      
 15 
     | 
    
         
            +
            """
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            import os
         
     | 
| 
      
 18 
     | 
    
         
            +
            import sys
         
     | 
| 
      
 19 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 20 
     | 
    
         
            +
            from typing import Optional, Dict
         
     | 
| 
      
 21 
     | 
    
         
            +
            from functools import lru_cache
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            class DeploymentPaths:
         
     | 
| 
      
 25 
     | 
    
         
            +
                """Manages paths for different deployment scenarios."""
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                def __init__(self):
         
     | 
| 
      
 28 
     | 
    
         
            +
                    self._package_root: Optional[Path] = None
         
     | 
| 
      
 29 
     | 
    
         
            +
                    self._project_root: Optional[Path] = None
         
     | 
| 
      
 30 
     | 
    
         
            +
                    self._scripts_dir: Optional[Path] = None
         
     | 
| 
      
 31 
     | 
    
         
            +
                    self._templates_dir: Optional[Path] = None
         
     | 
| 
      
 32 
     | 
    
         
            +
                    self._static_dir: Optional[Path] = None
         
     | 
| 
      
 33 
     | 
    
         
            +
                    
         
     | 
| 
      
 34 
     | 
    
         
            +
                @property
         
     | 
| 
      
 35 
     | 
    
         
            +
                @lru_cache(maxsize=1)
         
     | 
| 
      
 36 
     | 
    
         
            +
                def package_root(self) -> Path:
         
     | 
| 
      
 37 
     | 
    
         
            +
                    """
         
     | 
| 
      
 38 
     | 
    
         
            +
                    Get the claude_mpm package root directory.
         
     | 
| 
      
 39 
     | 
    
         
            +
                    
         
     | 
| 
      
 40 
     | 
    
         
            +
                    WHY: We need to reliably find the package root regardless of which
         
     | 
| 
      
 41 
     | 
    
         
            +
                    module is calling this function.
         
     | 
| 
      
 42 
     | 
    
         
            +
                    
         
     | 
| 
      
 43 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 44 
     | 
    
         
            +
                        Path to the claude_mpm package directory (src/claude_mpm)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    """
         
     | 
| 
      
 46 
     | 
    
         
            +
                    if self._package_root:
         
     | 
| 
      
 47 
     | 
    
         
            +
                        return self._package_root
         
     | 
| 
      
 48 
     | 
    
         
            +
                        
         
     | 
| 
      
 49 
     | 
    
         
            +
                    # Try to find the package root by looking for __init__.py
         
     | 
| 
      
 50 
     | 
    
         
            +
                    current_file = Path(__file__).resolve()
         
     | 
| 
      
 51 
     | 
    
         
            +
                    
         
     | 
| 
      
 52 
     | 
    
         
            +
                    # Walk up until we find the claude_mpm package root
         
     | 
| 
      
 53 
     | 
    
         
            +
                    current = current_file.parent
         
     | 
| 
      
 54 
     | 
    
         
            +
                    while current != current.parent:
         
     | 
| 
      
 55 
     | 
    
         
            +
                        if (current.name == "claude_mpm" and 
         
     | 
| 
      
 56 
     | 
    
         
            +
                            (current / "__init__.py").exists()):
         
     | 
| 
      
 57 
     | 
    
         
            +
                            self._package_root = current
         
     | 
| 
      
 58 
     | 
    
         
            +
                            return current
         
     | 
| 
      
 59 
     | 
    
         
            +
                        current = current.parent
         
     | 
| 
      
 60 
     | 
    
         
            +
                        
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # Fallback: assume we're in claude_mpm already
         
     | 
| 
      
 62 
     | 
    
         
            +
                    self._package_root = current_file.parent
         
     | 
| 
      
 63 
     | 
    
         
            +
                    return self._package_root
         
     | 
| 
      
 64 
     | 
    
         
            +
                    
         
     | 
| 
      
 65 
     | 
    
         
            +
                @property
         
     | 
| 
      
 66 
     | 
    
         
            +
                @lru_cache(maxsize=1)
         
     | 
| 
      
 67 
     | 
    
         
            +
                def project_root(self) -> Path:
         
     | 
| 
      
 68 
     | 
    
         
            +
                    """
         
     | 
| 
      
 69 
     | 
    
         
            +
                    Get the project root directory.
         
     | 
| 
      
 70 
     | 
    
         
            +
                    
         
     | 
| 
      
 71 
     | 
    
         
            +
                    WHY: The project root contains configuration files, scripts directory,
         
     | 
| 
      
 72 
     | 
    
         
            +
                    and other project-level resources that aren't part of the package.
         
     | 
| 
      
 73 
     | 
    
         
            +
                    
         
     | 
| 
      
 74 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 75 
     | 
    
         
            +
                        Path to the project root directory
         
     | 
| 
      
 76 
     | 
    
         
            +
                    """
         
     | 
| 
      
 77 
     | 
    
         
            +
                    if self._project_root:
         
     | 
| 
      
 78 
     | 
    
         
            +
                        return self._project_root
         
     | 
| 
      
 79 
     | 
    
         
            +
                        
         
     | 
| 
      
 80 
     | 
    
         
            +
                    # Check if we're in a development environment
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # In development, project root is typically 2 levels up from package root
         
     | 
| 
      
 82 
     | 
    
         
            +
                    package = self.package_root
         
     | 
| 
      
 83 
     | 
    
         
            +
                    
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # Look for indicators of project root
         
     | 
| 
      
 85 
     | 
    
         
            +
                    candidates = [
         
     | 
| 
      
 86 
     | 
    
         
            +
                        package.parent.parent,  # src/claude_mpm -> src -> project_root
         
     | 
| 
      
 87 
     | 
    
         
            +
                        package.parent,         # claude_mpm -> project_root (if no src/)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 89 
     | 
    
         
            +
                    
         
     | 
| 
      
 90 
     | 
    
         
            +
                    for candidate in candidates:
         
     | 
| 
      
 91 
     | 
    
         
            +
                        # Check for project indicators
         
     | 
| 
      
 92 
     | 
    
         
            +
                        if any((candidate / marker).exists() for marker in [
         
     | 
| 
      
 93 
     | 
    
         
            +
                            "pyproject.toml", "setup.py", "setup.cfg", 
         
     | 
| 
      
 94 
     | 
    
         
            +
                            ".git", "README.md", "scripts/claude-mpm"
         
     | 
| 
      
 95 
     | 
    
         
            +
                        ]):
         
     | 
| 
      
 96 
     | 
    
         
            +
                            self._project_root = candidate
         
     | 
| 
      
 97 
     | 
    
         
            +
                            return candidate
         
     | 
| 
      
 98 
     | 
    
         
            +
                            
         
     | 
| 
      
 99 
     | 
    
         
            +
                    # Fallback to package parent
         
     | 
| 
      
 100 
     | 
    
         
            +
                    self._project_root = package.parent
         
     | 
| 
      
 101 
     | 
    
         
            +
                    return self._project_root
         
     | 
| 
      
 102 
     | 
    
         
            +
                    
         
     | 
| 
      
 103 
     | 
    
         
            +
                @property
         
     | 
| 
      
 104 
     | 
    
         
            +
                def scripts_dir(self) -> Path:
         
     | 
| 
      
 105 
     | 
    
         
            +
                    """
         
     | 
| 
      
 106 
     | 
    
         
            +
                    Get the scripts directory path.
         
     | 
| 
      
 107 
     | 
    
         
            +
                    
         
     | 
| 
      
 108 
     | 
    
         
            +
                    WHY: Scripts can be in different locations depending on deployment:
         
     | 
| 
      
 109 
     | 
    
         
            +
                    - Development: project_root/scripts
         
     | 
| 
      
 110 
     | 
    
         
            +
                    - Package: claude_mpm/scripts
         
     | 
| 
      
 111 
     | 
    
         
            +
                    
         
     | 
| 
      
 112 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 113 
     | 
    
         
            +
                        Path to the scripts directory
         
     | 
| 
      
 114 
     | 
    
         
            +
                    """
         
     | 
| 
      
 115 
     | 
    
         
            +
                    if self._scripts_dir:
         
     | 
| 
      
 116 
     | 
    
         
            +
                        return self._scripts_dir
         
     | 
| 
      
 117 
     | 
    
         
            +
                        
         
     | 
| 
      
 118 
     | 
    
         
            +
                    # First try package scripts (for deployed packages)
         
     | 
| 
      
 119 
     | 
    
         
            +
                    package_scripts = self.package_root / "scripts"
         
     | 
| 
      
 120 
     | 
    
         
            +
                    if package_scripts.exists():
         
     | 
| 
      
 121 
     | 
    
         
            +
                        self._scripts_dir = package_scripts
         
     | 
| 
      
 122 
     | 
    
         
            +
                        return package_scripts
         
     | 
| 
      
 123 
     | 
    
         
            +
                        
         
     | 
| 
      
 124 
     | 
    
         
            +
                    # Then try project scripts (for development)
         
     | 
| 
      
 125 
     | 
    
         
            +
                    project_scripts = self.project_root / "scripts"
         
     | 
| 
      
 126 
     | 
    
         
            +
                    if project_scripts.exists():
         
     | 
| 
      
 127 
     | 
    
         
            +
                        self._scripts_dir = project_scripts
         
     | 
| 
      
 128 
     | 
    
         
            +
                        return project_scripts
         
     | 
| 
      
 129 
     | 
    
         
            +
                        
         
     | 
| 
      
 130 
     | 
    
         
            +
                    # Default to package scripts even if it doesn't exist yet
         
     | 
| 
      
 131 
     | 
    
         
            +
                    self._scripts_dir = package_scripts
         
     | 
| 
      
 132 
     | 
    
         
            +
                    return package_scripts
         
     | 
| 
      
 133 
     | 
    
         
            +
                    
         
     | 
| 
      
 134 
     | 
    
         
            +
                @property
         
     | 
| 
      
 135 
     | 
    
         
            +
                def templates_dir(self) -> Path:
         
     | 
| 
      
 136 
     | 
    
         
            +
                    """Get the agent templates directory."""
         
     | 
| 
      
 137 
     | 
    
         
            +
                    if not self._templates_dir:
         
     | 
| 
      
 138 
     | 
    
         
            +
                        self._templates_dir = self.package_root / "agents" / "templates"
         
     | 
| 
      
 139 
     | 
    
         
            +
                    return self._templates_dir
         
     | 
| 
      
 140 
     | 
    
         
            +
                    
         
     | 
| 
      
 141 
     | 
    
         
            +
                @property
         
     | 
| 
      
 142 
     | 
    
         
            +
                def static_dir(self) -> Path:
         
     | 
| 
      
 143 
     | 
    
         
            +
                    """Get the static files directory (HTML, CSS, etc)."""
         
     | 
| 
      
 144 
     | 
    
         
            +
                    if not self._static_dir:
         
     | 
| 
      
 145 
     | 
    
         
            +
                        # Static files are in package scripts directory
         
     | 
| 
      
 146 
     | 
    
         
            +
                        self._static_dir = self.package_root / "scripts"
         
     | 
| 
      
 147 
     | 
    
         
            +
                    return self._static_dir
         
     | 
| 
      
 148 
     | 
    
         
            +
                    
         
     | 
| 
      
 149 
     | 
    
         
            +
                def get_monitor_html_path(self) -> Path:
         
     | 
| 
      
 150 
     | 
    
         
            +
                    """
         
     | 
| 
      
 151 
     | 
    
         
            +
                    Get the path to the monitor HTML file.
         
     | 
| 
      
 152 
     | 
    
         
            +
                    
         
     | 
| 
      
 153 
     | 
    
         
            +
                    WHY: The monitor HTML can be in different locations depending on
         
     | 
| 
      
 154 
     | 
    
         
            +
                    deployment context. We now prioritize the new modular web structure.
         
     | 
| 
      
 155 
     | 
    
         
            +
                    
         
     | 
| 
      
 156 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 157 
     | 
    
         
            +
                        Path to the monitor HTML file
         
     | 
| 
      
 158 
     | 
    
         
            +
                    """
         
     | 
| 
      
 159 
     | 
    
         
            +
                    # Try multiple locations in order of preference
         
     | 
| 
      
 160 
     | 
    
         
            +
                    candidates = [
         
     | 
| 
      
 161 
     | 
    
         
            +
                        # New modular structure (preferred)
         
     | 
| 
      
 162 
     | 
    
         
            +
                        self.package_root / "web" / "templates" / "index.html",
         
     | 
| 
      
 163 
     | 
    
         
            +
                        self.package_root / "web" / "templates" / "dashboard.html",  # fallback
         
     | 
| 
      
 164 
     | 
    
         
            +
                        self.package_root / "web" / "index.html",  # root web index
         
     | 
| 
      
 165 
     | 
    
         
            +
                        # Legacy locations (for backward compatibility)
         
     | 
| 
      
 166 
     | 
    
         
            +
                        self.static_dir / "claude_mpm_monitor.html",
         
     | 
| 
      
 167 
     | 
    
         
            +
                        self.scripts_dir / "claude_mpm_monitor.html",
         
     | 
| 
      
 168 
     | 
    
         
            +
                        self.project_root / "scripts" / "claude_mpm_monitor.html",
         
     | 
| 
      
 169 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 170 
     | 
    
         
            +
                    
         
     | 
| 
      
 171 
     | 
    
         
            +
                    for candidate in candidates:
         
     | 
| 
      
 172 
     | 
    
         
            +
                        if candidate.exists():
         
     | 
| 
      
 173 
     | 
    
         
            +
                            return candidate
         
     | 
| 
      
 174 
     | 
    
         
            +
                            
         
     | 
| 
      
 175 
     | 
    
         
            +
                    # Return the preferred new location even if it doesn't exist
         
     | 
| 
      
 176 
     | 
    
         
            +
                    # This allows better error messages and encourages proper structure
         
     | 
| 
      
 177 
     | 
    
         
            +
                    return self.package_root / "web" / "templates" / "index.html"
         
     | 
| 
      
 178 
     | 
    
         
            +
                    
         
     | 
| 
      
 179 
     | 
    
         
            +
                def get_resource_path(self, resource_type: str, filename: str) -> Path:
         
     | 
| 
      
 180 
     | 
    
         
            +
                    """
         
     | 
| 
      
 181 
     | 
    
         
            +
                    Get path to a resource file.
         
     | 
| 
      
 182 
     | 
    
         
            +
                    
         
     | 
| 
      
 183 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 184 
     | 
    
         
            +
                        resource_type: Type of resource (scripts, templates, static, etc)
         
     | 
| 
      
 185 
     | 
    
         
            +
                        filename: Name of the file
         
     | 
| 
      
 186 
     | 
    
         
            +
                        
         
     | 
| 
      
 187 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 188 
     | 
    
         
            +
                        Path to the resource
         
     | 
| 
      
 189 
     | 
    
         
            +
                    """
         
     | 
| 
      
 190 
     | 
    
         
            +
                    resource_dirs = {
         
     | 
| 
      
 191 
     | 
    
         
            +
                        "scripts": self.scripts_dir,
         
     | 
| 
      
 192 
     | 
    
         
            +
                        "templates": self.templates_dir,
         
     | 
| 
      
 193 
     | 
    
         
            +
                        "static": self.static_dir,
         
     | 
| 
      
 194 
     | 
    
         
            +
                        "agents": self.package_root / "agents",
         
     | 
| 
      
 195 
     | 
    
         
            +
                    }
         
     | 
| 
      
 196 
     | 
    
         
            +
                    
         
     | 
| 
      
 197 
     | 
    
         
            +
                    base_dir = resource_dirs.get(resource_type, self.package_root)
         
     | 
| 
      
 198 
     | 
    
         
            +
                    return base_dir / filename
         
     | 
| 
      
 199 
     | 
    
         
            +
                    
         
     | 
| 
      
 200 
     | 
    
         
            +
                def resolve_import_path(self, module_path: str) -> Path:
         
     | 
| 
      
 201 
     | 
    
         
            +
                    """
         
     | 
| 
      
 202 
     | 
    
         
            +
                    Resolve a module import path to a file path.
         
     | 
| 
      
 203 
     | 
    
         
            +
                    
         
     | 
| 
      
 204 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 205 
     | 
    
         
            +
                        module_path: Dot-separated module path (e.g., "claude_mpm.cli.commands.run")
         
     | 
| 
      
 206 
     | 
    
         
            +
                        
         
     | 
| 
      
 207 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 208 
     | 
    
         
            +
                        Path to the module file
         
     | 
| 
      
 209 
     | 
    
         
            +
                    """
         
     | 
| 
      
 210 
     | 
    
         
            +
                    parts = module_path.split(".")
         
     | 
| 
      
 211 
     | 
    
         
            +
                    if parts[0] == "claude_mpm":
         
     | 
| 
      
 212 
     | 
    
         
            +
                        parts = parts[1:]  # Remove package name
         
     | 
| 
      
 213 
     | 
    
         
            +
                        
         
     | 
| 
      
 214 
     | 
    
         
            +
                    return self.package_root.joinpath(*parts).with_suffix(".py")
         
     | 
| 
      
 215 
     | 
    
         
            +
                    
         
     | 
| 
      
 216 
     | 
    
         
            +
                def ensure_directory(self, path: Path) -> Path:
         
     | 
| 
      
 217 
     | 
    
         
            +
                    """Ensure a directory exists, creating it if necessary."""
         
     | 
| 
      
 218 
     | 
    
         
            +
                    path.mkdir(parents=True, exist_ok=True)
         
     | 
| 
      
 219 
     | 
    
         
            +
                    return path
         
     | 
| 
      
 220 
     | 
    
         
            +
                    
         
     | 
| 
      
 221 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 222 
     | 
    
         
            +
                @lru_cache(maxsize=1)
         
     | 
| 
      
 223 
     | 
    
         
            +
                def get_instance(cls) -> "DeploymentPaths":
         
     | 
| 
      
 224 
     | 
    
         
            +
                    """Get singleton instance of DeploymentPaths."""
         
     | 
| 
      
 225 
     | 
    
         
            +
                    return cls()
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
            # Convenience functions
         
     | 
| 
      
 229 
     | 
    
         
            +
            def get_deployment_paths() -> DeploymentPaths:
         
     | 
| 
      
 230 
     | 
    
         
            +
                """Get the deployment paths instance."""
         
     | 
| 
      
 231 
     | 
    
         
            +
                return DeploymentPaths.get_instance()
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            def get_package_root() -> Path:
         
     | 
| 
      
 235 
     | 
    
         
            +
                """Get the claude_mpm package root directory."""
         
     | 
| 
      
 236 
     | 
    
         
            +
                return get_deployment_paths().package_root
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
            def get_project_root() -> Path:
         
     | 
| 
      
 240 
     | 
    
         
            +
                """Get the project root directory."""
         
     | 
| 
      
 241 
     | 
    
         
            +
                return get_deployment_paths().project_root
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
            def get_scripts_dir() -> Path:
         
     | 
| 
      
 245 
     | 
    
         
            +
                """Get the scripts directory."""
         
     | 
| 
      
 246 
     | 
    
         
            +
                return get_deployment_paths().scripts_dir
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
            def get_monitor_html_path() -> Path:
         
     | 
| 
      
 250 
     | 
    
         
            +
                """Get the monitor HTML file path."""
         
     | 
| 
      
 251 
     | 
    
         
            +
                return get_deployment_paths().get_monitor_html_path()
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
            def get_templates_dir() -> Path:
         
     | 
| 
      
 255 
     | 
    
         
            +
                """Get the agent templates directory."""
         
     | 
| 
      
 256 
     | 
    
         
            +
                return get_deployment_paths().templates_dir
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
            def get_resource_path(resource_type: str, filename: str) -> Path:
         
     | 
| 
      
 260 
     | 
    
         
            +
                """Get path to a resource file."""
         
     | 
| 
      
 261 
     | 
    
         
            +
                return get_deployment_paths().get_resource_path(resource_type, filename)
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Example of how to register memory integration hooks.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            WHY: This demonstrates how to register the memory hooks with the HookService
         
     | 
| 
      
 4 
     | 
    
         
            +
            for automatic memory injection and learning extraction.
         
     | 
| 
      
 5 
     | 
    
         
            +
            """
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            from claude_mpm.hooks.memory_integration_hook import (
         
     | 
| 
      
 8 
     | 
    
         
            +
                MemoryPreDelegationHook,
         
     | 
| 
      
 9 
     | 
    
         
            +
                MemoryPostDelegationHook
         
     | 
| 
      
 10 
     | 
    
         
            +
            )
         
     | 
| 
      
 11 
     | 
    
         
            +
            from claude_mpm.services.hook_service import HookService
         
     | 
| 
      
 12 
     | 
    
         
            +
            from claude_mpm.core.config import Config
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            def register_memory_hooks(hook_service: HookService, config: Config = None):
         
     | 
| 
      
 16 
     | 
    
         
            +
                """Register memory integration hooks with the hook service.
         
     | 
| 
      
 17 
     | 
    
         
            +
                
         
     | 
| 
      
 18 
     | 
    
         
            +
                WHY: To enable automatic memory management, both hooks need to be
         
     | 
| 
      
 19 
     | 
    
         
            +
                registered with appropriate priorities:
         
     | 
| 
      
 20 
     | 
    
         
            +
                - Pre-hook runs early (priority 20) to inject memory into context
         
     | 
| 
      
 21 
     | 
    
         
            +
                - Post-hook runs late (priority 80) to extract learnings after processing
         
     | 
| 
      
 22 
     | 
    
         
            +
                
         
     | 
| 
      
 23 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 24 
     | 
    
         
            +
                    hook_service: The HookService instance to register with
         
     | 
| 
      
 25 
     | 
    
         
            +
                    config: Optional configuration (will create default if not provided)
         
     | 
| 
      
 26 
     | 
    
         
            +
                """
         
     | 
| 
      
 27 
     | 
    
         
            +
                config = config or Config()
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                # Only register if memory system is enabled
         
     | 
| 
      
 30 
     | 
    
         
            +
                if not config.get('memory.enabled', True):
         
     | 
| 
      
 31 
     | 
    
         
            +
                    return
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                # Register pre-delegation hook for memory injection
         
     | 
| 
      
 34 
     | 
    
         
            +
                pre_hook = MemoryPreDelegationHook(config)
         
     | 
| 
      
 35 
     | 
    
         
            +
                hook_service.register_hook(pre_hook)
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                # Register post-delegation hook for learning extraction
         
     | 
| 
      
 38 
     | 
    
         
            +
                # Only if auto-learning is enabled
         
     | 
| 
      
 39 
     | 
    
         
            +
                if config.get('memory.auto_learning', False):
         
     | 
| 
      
 40 
     | 
    
         
            +
                    post_hook = MemoryPostDelegationHook(config)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    hook_service.register_hook(post_hook)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            # Example usage:
         
     | 
| 
      
 45 
     | 
    
         
            +
            if __name__ == "__main__":
         
     | 
| 
      
 46 
     | 
    
         
            +
                # This would typically be done during application initialization
         
     | 
| 
      
 47 
     | 
    
         
            +
                config = Config(config={
         
     | 
| 
      
 48 
     | 
    
         
            +
                    'memory': {
         
     | 
| 
      
 49 
     | 
    
         
            +
                        'enabled': True,
         
     | 
| 
      
 50 
     | 
    
         
            +
                        'auto_learning': True,
         
     | 
| 
      
 51 
     | 
    
         
            +
                        'limits': {
         
     | 
| 
      
 52 
     | 
    
         
            +
                            'default_size_kb': 8,
         
     | 
| 
      
 53 
     | 
    
         
            +
                            'max_items_per_section': 20
         
     | 
| 
      
 54 
     | 
    
         
            +
                        }
         
     | 
| 
      
 55 
     | 
    
         
            +
                    }
         
     | 
| 
      
 56 
     | 
    
         
            +
                })
         
     | 
| 
      
 57 
     | 
    
         
            +
                
         
     | 
| 
      
 58 
     | 
    
         
            +
                # Create hook service (normally this would be passed from main app)
         
     | 
| 
      
 59 
     | 
    
         
            +
                from claude_mpm.services.hook_service import HookService
         
     | 
| 
      
 60 
     | 
    
         
            +
                hook_service = HookService(config)
         
     | 
| 
      
 61 
     | 
    
         
            +
                
         
     | 
| 
      
 62 
     | 
    
         
            +
                # Register memory hooks
         
     | 
| 
      
 63 
     | 
    
         
            +
                register_memory_hooks(hook_service, config)
         
     | 
| 
      
 64 
     | 
    
         
            +
                
         
     | 
| 
      
 65 
     | 
    
         
            +
                print("Memory hooks registered successfully!")
         
     | 
| 
      
 66 
     | 
    
         
            +
                print(f"Pre-delegation hook: {hook_service.get_hooks('pre_delegation')}")
         
     | 
| 
      
 67 
     | 
    
         
            +
                print(f"Post-delegation hook: {hook_service.get_hooks('post_delegation')}")
         
     |