claude-mpm 3.7.4__py3-none-any.whl → 3.8.1__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 -78
- claude_mpm/agents/MEMORY.md +88 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +26 -11
- claude_mpm/agents/templates/data_engineer.json +4 -7
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +2 -3
- claude_mpm/agents/templates/security.json +3 -6
- claude_mpm/agents/templates/ticketing.json +4 -9
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +4 -4
- claude_mpm/agents/templates/web_ui.json +4 -4
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +228 -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 +5 -1
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +461 -22
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +208 -93
- claude_mpm/core/interactive_session.py +432 -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/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- 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 +592 -269
- claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
- 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/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/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 +377 -51
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/utils/robust_installer.py +587 -0
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/cli/README.md +0 -108
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/config/async_logging_config.yaml +0 -145
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
- claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/README.md +0 -121
- claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- claude_mpm/hooks/README.md +0 -96
- claude_mpm/schemas/agent_schema.json +0 -435
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/version_control/VERSION +0 -1
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,213 @@ | |
| 1 | 
            +
            """File operation event handlers for Socket.IO.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module handles file-related events including reading file content
         | 
| 4 | 
            +
            safely with security checks. Separating file operations improves security
         | 
| 5 | 
            +
            auditing and makes it easier to add file-related features.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            import os
         | 
| 9 | 
            +
            from typing import Optional, Dict, Any
         | 
| 10 | 
            +
            from pathlib import Path
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            from .base import BaseEventHandler
         | 
| 13 | 
            +
            from ....deployment_paths import get_project_root
         | 
| 14 | 
            +
            from ....core.typing_utils import SocketId, EventData, PathLike
         | 
| 15 | 
            +
             | 
| 16 | 
            +
             | 
| 17 | 
            +
            class FileEventHandler(BaseEventHandler):
         | 
| 18 | 
            +
                """Handles file operation Socket.IO events.
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                WHY: File operations require careful security considerations and
         | 
| 21 | 
            +
                consistent error handling. Having a dedicated handler ensures
         | 
| 22 | 
            +
                all file operations follow the same security patterns.
         | 
| 23 | 
            +
                """
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                def register_events(self) -> None:
         | 
| 26 | 
            +
                    """Register file operation event handlers."""
         | 
| 27 | 
            +
                    
         | 
| 28 | 
            +
                    @self.sio.event
         | 
| 29 | 
            +
                    async def read_file(sid, data):
         | 
| 30 | 
            +
                        """Read file contents safely.
         | 
| 31 | 
            +
                        
         | 
| 32 | 
            +
                        WHY: The dashboard needs to display file contents when users
         | 
| 33 | 
            +
                        click on files, but we must ensure secure file access with
         | 
| 34 | 
            +
                        proper validation and size limits.
         | 
| 35 | 
            +
                        """
         | 
| 36 | 
            +
                        try:
         | 
| 37 | 
            +
                            file_path = data.get('file_path')
         | 
| 38 | 
            +
                            working_dir = data.get('working_dir', os.getcwd())
         | 
| 39 | 
            +
                            max_size = data.get('max_size', 1024 * 1024)  # 1MB default limit
         | 
| 40 | 
            +
                            
         | 
| 41 | 
            +
                            if not file_path:
         | 
| 42 | 
            +
                                await self.emit_to_client(sid, 'file_content_response', {
         | 
| 43 | 
            +
                                    'success': False,
         | 
| 44 | 
            +
                                    'error': 'file_path is required',
         | 
| 45 | 
            +
                                    'file_path': file_path
         | 
| 46 | 
            +
                                })
         | 
| 47 | 
            +
                                return
         | 
| 48 | 
            +
                            
         | 
| 49 | 
            +
                            # Use the shared file reading logic
         | 
| 50 | 
            +
                            result = await self._read_file_safely(file_path, working_dir, max_size)
         | 
| 51 | 
            +
                            
         | 
| 52 | 
            +
                            # Send the result back to the client
         | 
| 53 | 
            +
                            await self.emit_to_client(sid, 'file_content_response', result)
         | 
| 54 | 
            +
                                    
         | 
| 55 | 
            +
                        except Exception as e:
         | 
| 56 | 
            +
                            self.log_error("read_file", e, data)
         | 
| 57 | 
            +
                            await self.emit_to_client(sid, 'file_content_response', {
         | 
| 58 | 
            +
                                'success': False,
         | 
| 59 | 
            +
                                'error': str(e),
         | 
| 60 | 
            +
                                'file_path': data.get('file_path', 'unknown')
         | 
| 61 | 
            +
                            })
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                async def _read_file_safely(self, file_path: str, working_dir: Optional[str] = None, max_size: int = 1024 * 1024) -> EventData:
         | 
| 64 | 
            +
                    """Safely read file content with security checks.
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    WHY: File reading must be secure to prevent directory traversal attacks
         | 
| 67 | 
            +
                    and resource exhaustion. This method centralizes all security checks
         | 
| 68 | 
            +
                    and provides consistent error handling.
         | 
| 69 | 
            +
                    
         | 
| 70 | 
            +
                    Args:
         | 
| 71 | 
            +
                        file_path: Path to the file to read
         | 
| 72 | 
            +
                        working_dir: Working directory (defaults to current directory)
         | 
| 73 | 
            +
                        max_size: Maximum file size in bytes
         | 
| 74 | 
            +
                        
         | 
| 75 | 
            +
                    Returns:
         | 
| 76 | 
            +
                        dict: Response with success status, content, and metadata
         | 
| 77 | 
            +
                    """
         | 
| 78 | 
            +
                    try:
         | 
| 79 | 
            +
                        if working_dir is None:
         | 
| 80 | 
            +
                            working_dir = os.getcwd()
         | 
| 81 | 
            +
                            
         | 
| 82 | 
            +
                        # Resolve absolute path based on working directory
         | 
| 83 | 
            +
                        if not os.path.isabs(file_path):
         | 
| 84 | 
            +
                            full_path = os.path.join(working_dir, file_path)
         | 
| 85 | 
            +
                        else:
         | 
| 86 | 
            +
                            full_path = file_path
         | 
| 87 | 
            +
                        
         | 
| 88 | 
            +
                        # Security check: ensure file is within working directory or project
         | 
| 89 | 
            +
                        try:
         | 
| 90 | 
            +
                            real_path = os.path.realpath(full_path)
         | 
| 91 | 
            +
                            real_working_dir = os.path.realpath(working_dir)
         | 
| 92 | 
            +
                            
         | 
| 93 | 
            +
                            # Allow access to files within working directory or the project root
         | 
| 94 | 
            +
                            project_root = os.path.realpath(get_project_root())
         | 
| 95 | 
            +
                            allowed_paths = [real_working_dir, project_root]
         | 
| 96 | 
            +
                            
         | 
| 97 | 
            +
                            is_allowed = any(real_path.startswith(allowed_path) for allowed_path in allowed_paths)
         | 
| 98 | 
            +
                            
         | 
| 99 | 
            +
                            if not is_allowed:
         | 
| 100 | 
            +
                                return {
         | 
| 101 | 
            +
                                    'success': False,
         | 
| 102 | 
            +
                                    'error': 'Access denied: file is outside allowed directories',
         | 
| 103 | 
            +
                                    'file_path': file_path
         | 
| 104 | 
            +
                                }
         | 
| 105 | 
            +
                                
         | 
| 106 | 
            +
                        except Exception as path_error:
         | 
| 107 | 
            +
                            self.logger.error(f"Path validation error: {path_error}")
         | 
| 108 | 
            +
                            return {
         | 
| 109 | 
            +
                                'success': False,
         | 
| 110 | 
            +
                                'error': 'Invalid file path',
         | 
| 111 | 
            +
                                'file_path': file_path
         | 
| 112 | 
            +
                            }
         | 
| 113 | 
            +
                        
         | 
| 114 | 
            +
                        # Check if file exists
         | 
| 115 | 
            +
                        if not os.path.exists(real_path):
         | 
| 116 | 
            +
                            return {
         | 
| 117 | 
            +
                                'success': False,
         | 
| 118 | 
            +
                                'error': 'File does not exist',
         | 
| 119 | 
            +
                                'file_path': file_path
         | 
| 120 | 
            +
                            }
         | 
| 121 | 
            +
                        
         | 
| 122 | 
            +
                        # Check if it's a file (not directory)
         | 
| 123 | 
            +
                        if not os.path.isfile(real_path):
         | 
| 124 | 
            +
                            return {
         | 
| 125 | 
            +
                                'success': False,
         | 
| 126 | 
            +
                                'error': 'Path is not a file',
         | 
| 127 | 
            +
                                'file_path': file_path
         | 
| 128 | 
            +
                            }
         | 
| 129 | 
            +
                        
         | 
| 130 | 
            +
                        # Check file size
         | 
| 131 | 
            +
                        file_size = os.path.getsize(real_path)
         | 
| 132 | 
            +
                        if file_size > max_size:
         | 
| 133 | 
            +
                            return {
         | 
| 134 | 
            +
                                'success': False,
         | 
| 135 | 
            +
                                'error': f'File too large ({file_size} bytes). Maximum allowed: {max_size} bytes',
         | 
| 136 | 
            +
                                'file_path': file_path,
         | 
| 137 | 
            +
                                'file_size': file_size
         | 
| 138 | 
            +
                            }
         | 
| 139 | 
            +
                        
         | 
| 140 | 
            +
                        # Read file content
         | 
| 141 | 
            +
                        try:
         | 
| 142 | 
            +
                            with open(real_path, 'r', encoding='utf-8') as f:
         | 
| 143 | 
            +
                                content = f.read()
         | 
| 144 | 
            +
                            
         | 
| 145 | 
            +
                            # Get file extension for syntax highlighting hint
         | 
| 146 | 
            +
                            _, ext = os.path.splitext(real_path)
         | 
| 147 | 
            +
                            
         | 
| 148 | 
            +
                            return {
         | 
| 149 | 
            +
                                'success': True,
         | 
| 150 | 
            +
                                'file_path': file_path,
         | 
| 151 | 
            +
                                'content': content,
         | 
| 152 | 
            +
                                'file_size': file_size,
         | 
| 153 | 
            +
                                'extension': ext.lower(),
         | 
| 154 | 
            +
                                'encoding': 'utf-8'
         | 
| 155 | 
            +
                            }
         | 
| 156 | 
            +
                            
         | 
| 157 | 
            +
                        except UnicodeDecodeError:
         | 
| 158 | 
            +
                            # Try reading as binary if UTF-8 fails
         | 
| 159 | 
            +
                            return self._read_binary_file(real_path, file_path, file_size)
         | 
| 160 | 
            +
                                
         | 
| 161 | 
            +
                    except Exception as e:
         | 
| 162 | 
            +
                        self.logger.error(f"Error in _read_file_safely: {e}")
         | 
| 163 | 
            +
                        return {
         | 
| 164 | 
            +
                            'success': False,
         | 
| 165 | 
            +
                            'error': str(e),
         | 
| 166 | 
            +
                            'file_path': file_path
         | 
| 167 | 
            +
                        }
         | 
| 168 | 
            +
                
         | 
| 169 | 
            +
                def _read_binary_file(self, real_path: str, file_path: str, file_size: int) -> Dict[str, Any]:
         | 
| 170 | 
            +
                    """Handle binary or non-UTF8 files.
         | 
| 171 | 
            +
                    
         | 
| 172 | 
            +
                    WHY: Not all files are UTF-8 encoded. We need to handle other
         | 
| 173 | 
            +
                    encodings gracefully and detect binary files that shouldn't
         | 
| 174 | 
            +
                    be displayed as text.
         | 
| 175 | 
            +
                    """
         | 
| 176 | 
            +
                    try:
         | 
| 177 | 
            +
                        with open(real_path, 'rb') as f:
         | 
| 178 | 
            +
                            binary_content = f.read()
         | 
| 179 | 
            +
                        
         | 
| 180 | 
            +
                        # Check if it's a text file by looking for common text patterns
         | 
| 181 | 
            +
                        try:
         | 
| 182 | 
            +
                            text_content = binary_content.decode('latin-1')
         | 
| 183 | 
            +
                            if '\x00' in text_content:
         | 
| 184 | 
            +
                                # Binary file
         | 
| 185 | 
            +
                                return {
         | 
| 186 | 
            +
                                    'success': False,
         | 
| 187 | 
            +
                                    'error': 'File appears to be binary and cannot be displayed as text',
         | 
| 188 | 
            +
                                    'file_path': file_path,
         | 
| 189 | 
            +
                                    'file_size': file_size
         | 
| 190 | 
            +
                                }
         | 
| 191 | 
            +
                            else:
         | 
| 192 | 
            +
                                # Text file with different encoding
         | 
| 193 | 
            +
                                _, ext = os.path.splitext(real_path)
         | 
| 194 | 
            +
                                return {
         | 
| 195 | 
            +
                                    'success': True,
         | 
| 196 | 
            +
                                    'file_path': file_path,
         | 
| 197 | 
            +
                                    'content': text_content,
         | 
| 198 | 
            +
                                    'file_size': file_size,
         | 
| 199 | 
            +
                                    'extension': ext.lower(),
         | 
| 200 | 
            +
                                    'encoding': 'latin-1'
         | 
| 201 | 
            +
                                }
         | 
| 202 | 
            +
                        except Exception:
         | 
| 203 | 
            +
                            return {
         | 
| 204 | 
            +
                                'success': False,
         | 
| 205 | 
            +
                                'error': 'File encoding not supported',
         | 
| 206 | 
            +
                                'file_path': file_path
         | 
| 207 | 
            +
                            }
         | 
| 208 | 
            +
                    except Exception as read_error:
         | 
| 209 | 
            +
                        return {
         | 
| 210 | 
            +
                            'success': False,
         | 
| 211 | 
            +
                            'error': f'Failed to read file: {str(read_error)}',
         | 
| 212 | 
            +
                            'file_path': file_path
         | 
| 213 | 
            +
                        }
         |