claude-mpm 3.7.1__py3-none-any.whl → 3.7.8__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/INSTRUCTIONS.md +18 -0
- claude_mpm/agents/frontmatter_validator.py +116 -17
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/{dashboard → agents}/templates/.claude-mpm/memories/engineer_agent.md +1 -1
- claude_mpm/{dashboard/templates/.claude-mpm/memories/version_control_agent.md → agents/templates/.claude-mpm/memories/qa_agent.md} +2 -2
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +39 -0
- claude_mpm/agents/templates/code_analyzer.json +34 -12
- claude_mpm/agents/templates/data_engineer.json +5 -8
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +6 -6
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +12 -9
- claude_mpm/agents/templates/security.json +4 -7
- claude_mpm/agents/templates/ticketing.json +161 -0
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +214 -0
- claude_mpm/agents/templates/web_ui.json +176 -0
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/parser.py +11 -0
- claude_mpm/cli/ticket_cli.py +31 -0
- claude_mpm/core/framework_loader.py +102 -49
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +9 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +174 -13
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/ticket_manager.py +207 -44
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/robust_installer.py +587 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +37 -46
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/agents/templates/test_integration.json +0 -113
- 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/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.1.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,587 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            Robust dependency installer with retry logic and fallback strategies.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            WHY: Network issues and temporary unavailability can cause dependency installation
         | 
| 5 | 
            +
            to fail. This module provides resilient installation with automatic retries,
         | 
| 6 | 
            +
            fallback strategies, and clear error reporting.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            DESIGN DECISION: We implement exponential backoff for retries and provide
         | 
| 9 | 
            +
            multiple installation strategies (pip, conda, source) to maximize success rate.
         | 
| 10 | 
            +
            """
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            import subprocess
         | 
| 13 | 
            +
            import sys
         | 
| 14 | 
            +
            import time
         | 
| 15 | 
            +
            import json
         | 
| 16 | 
            +
            import re
         | 
| 17 | 
            +
            from pathlib import Path
         | 
| 18 | 
            +
            from typing import List, Tuple, Optional, Dict, Any
         | 
| 19 | 
            +
            from enum import Enum
         | 
| 20 | 
            +
            from dataclasses import dataclass
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            from ..core.logger import get_logger
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            logger = get_logger(__name__)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
             | 
| 27 | 
            +
            class InstallStrategy(Enum):
         | 
| 28 | 
            +
                """Available installation strategies."""
         | 
| 29 | 
            +
                PIP = "pip"
         | 
| 30 | 
            +
                PIP_NO_DEPS = "pip_no_deps"
         | 
| 31 | 
            +
                PIP_UPGRADE = "pip_upgrade"
         | 
| 32 | 
            +
                PIP_INDEX_URL = "pip_index_url"
         | 
| 33 | 
            +
                SOURCE = "source"
         | 
| 34 | 
            +
             | 
| 35 | 
            +
             | 
| 36 | 
            +
            @dataclass
         | 
| 37 | 
            +
            class InstallAttempt:
         | 
| 38 | 
            +
                """Record of an installation attempt."""
         | 
| 39 | 
            +
                strategy: InstallStrategy
         | 
| 40 | 
            +
                package: str
         | 
| 41 | 
            +
                success: bool
         | 
| 42 | 
            +
                error: Optional[str]
         | 
| 43 | 
            +
                duration: float
         | 
| 44 | 
            +
                retry_count: int
         | 
| 45 | 
            +
             | 
| 46 | 
            +
             | 
| 47 | 
            +
            class RobustPackageInstaller:
         | 
| 48 | 
            +
                """
         | 
| 49 | 
            +
                Robust package installer with retry logic and multiple strategies.
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                WHY: This class handles the complexity of package installation in various
         | 
| 52 | 
            +
                environments, network conditions, and Python versions. It ensures maximum
         | 
| 53 | 
            +
                success rate while providing clear feedback on failures.
         | 
| 54 | 
            +
                """
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def __init__(
         | 
| 57 | 
            +
                    self,
         | 
| 58 | 
            +
                    max_retries: int = 3,
         | 
| 59 | 
            +
                    retry_delay: float = 2.0,
         | 
| 60 | 
            +
                    timeout: int = 300,
         | 
| 61 | 
            +
                    use_cache: bool = True
         | 
| 62 | 
            +
                ):
         | 
| 63 | 
            +
                    """
         | 
| 64 | 
            +
                    Initialize robust installer.
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    Args:
         | 
| 67 | 
            +
                        max_retries: Maximum number of retry attempts per package
         | 
| 68 | 
            +
                        retry_delay: Initial delay between retries (uses exponential backoff)
         | 
| 69 | 
            +
                        timeout: Maximum time for each installation attempt in seconds
         | 
| 70 | 
            +
                        use_cache: Whether to use pip cache
         | 
| 71 | 
            +
                    """
         | 
| 72 | 
            +
                    self.max_retries = max_retries
         | 
| 73 | 
            +
                    self.retry_delay = retry_delay
         | 
| 74 | 
            +
                    self.timeout = timeout
         | 
| 75 | 
            +
                    self.use_cache = use_cache
         | 
| 76 | 
            +
                    self.attempts: List[InstallAttempt] = []
         | 
| 77 | 
            +
                    self.success_cache: Dict[str, bool] = {}
         | 
| 78 | 
            +
                    
         | 
| 79 | 
            +
                def install_package(
         | 
| 80 | 
            +
                    self,
         | 
| 81 | 
            +
                    package_spec: str,
         | 
| 82 | 
            +
                    strategies: Optional[List[InstallStrategy]] = None
         | 
| 83 | 
            +
                ) -> Tuple[bool, Optional[str]]:
         | 
| 84 | 
            +
                    """
         | 
| 85 | 
            +
                    Install a package using robust retry logic and multiple strategies.
         | 
| 86 | 
            +
                    
         | 
| 87 | 
            +
                    WHY: Single installation attempts often fail due to transient issues.
         | 
| 88 | 
            +
                    This method tries multiple strategies with retries to maximize success.
         | 
| 89 | 
            +
                    
         | 
| 90 | 
            +
                    Args:
         | 
| 91 | 
            +
                        package_spec: Package specification (e.g., "pandas>=2.0.0")
         | 
| 92 | 
            +
                        strategies: List of strategies to try (defaults to sensible order)
         | 
| 93 | 
            +
                        
         | 
| 94 | 
            +
                    Returns:
         | 
| 95 | 
            +
                        Tuple of (success, error_message)
         | 
| 96 | 
            +
                    """
         | 
| 97 | 
            +
                    # Check success cache first
         | 
| 98 | 
            +
                    if package_spec in self.success_cache:
         | 
| 99 | 
            +
                        if self.success_cache[package_spec]:
         | 
| 100 | 
            +
                            logger.debug(f"Package {package_spec} already successfully installed")
         | 
| 101 | 
            +
                            return True, None
         | 
| 102 | 
            +
                    
         | 
| 103 | 
            +
                    # Default strategy order
         | 
| 104 | 
            +
                    if strategies is None:
         | 
| 105 | 
            +
                        strategies = [
         | 
| 106 | 
            +
                            InstallStrategy.PIP,
         | 
| 107 | 
            +
                            InstallStrategy.PIP_UPGRADE,
         | 
| 108 | 
            +
                            InstallStrategy.PIP_NO_DEPS,
         | 
| 109 | 
            +
                            InstallStrategy.PIP_INDEX_URL,
         | 
| 110 | 
            +
                        ]
         | 
| 111 | 
            +
                    
         | 
| 112 | 
            +
                    # Extract package name for special handling
         | 
| 113 | 
            +
                    package_name = self._extract_package_name(package_spec)
         | 
| 114 | 
            +
                    
         | 
| 115 | 
            +
                    # Special handling for known problematic packages
         | 
| 116 | 
            +
                    if self._needs_special_handling(package_name):
         | 
| 117 | 
            +
                        strategies = self._get_special_strategies(package_name)
         | 
| 118 | 
            +
                    
         | 
| 119 | 
            +
                    # Try each strategy with retries
         | 
| 120 | 
            +
                    for strategy in strategies:
         | 
| 121 | 
            +
                        for retry in range(self.max_retries):
         | 
| 122 | 
            +
                            start_time = time.time()
         | 
| 123 | 
            +
                            
         | 
| 124 | 
            +
                            # Calculate delay with exponential backoff
         | 
| 125 | 
            +
                            if retry > 0:
         | 
| 126 | 
            +
                                delay = self.retry_delay * (2 ** (retry - 1))
         | 
| 127 | 
            +
                                logger.info(f"Retry {retry}/{self.max_retries} after {delay:.1f}s delay...")
         | 
| 128 | 
            +
                                time.sleep(delay)
         | 
| 129 | 
            +
                            
         | 
| 130 | 
            +
                            # Attempt installation
         | 
| 131 | 
            +
                            success, error = self._attempt_install(package_spec, strategy)
         | 
| 132 | 
            +
                            duration = time.time() - start_time
         | 
| 133 | 
            +
                            
         | 
| 134 | 
            +
                            # Record attempt
         | 
| 135 | 
            +
                            self.attempts.append(InstallAttempt(
         | 
| 136 | 
            +
                                strategy=strategy,
         | 
| 137 | 
            +
                                package=package_spec,
         | 
| 138 | 
            +
                                success=success,
         | 
| 139 | 
            +
                                error=error,
         | 
| 140 | 
            +
                                duration=duration,
         | 
| 141 | 
            +
                                retry_count=retry
         | 
| 142 | 
            +
                            ))
         | 
| 143 | 
            +
                            
         | 
| 144 | 
            +
                            if success:
         | 
| 145 | 
            +
                                logger.info(f"Successfully installed {package_spec} using {strategy.value}")
         | 
| 146 | 
            +
                                self.success_cache[package_spec] = True
         | 
| 147 | 
            +
                                return True, None
         | 
| 148 | 
            +
                            
         | 
| 149 | 
            +
                            # Check if error is retryable
         | 
| 150 | 
            +
                            if not self._is_retryable_error(error):
         | 
| 151 | 
            +
                                logger.warning(f"Non-retryable error for {package_spec}: {error}")
         | 
| 152 | 
            +
                                break
         | 
| 153 | 
            +
                    
         | 
| 154 | 
            +
                    # All attempts failed
         | 
| 155 | 
            +
                    self.success_cache[package_spec] = False
         | 
| 156 | 
            +
                    final_error = self._get_consolidated_error(package_spec)
         | 
| 157 | 
            +
                    return False, final_error
         | 
| 158 | 
            +
                
         | 
| 159 | 
            +
                def _attempt_install(
         | 
| 160 | 
            +
                    self,
         | 
| 161 | 
            +
                    package_spec: str,
         | 
| 162 | 
            +
                    strategy: InstallStrategy
         | 
| 163 | 
            +
                ) -> Tuple[bool, Optional[str]]:
         | 
| 164 | 
            +
                    """
         | 
| 165 | 
            +
                    Attempt to install a package using a specific strategy.
         | 
| 166 | 
            +
                    
         | 
| 167 | 
            +
                    Args:
         | 
| 168 | 
            +
                        package_spec: Package specification
         | 
| 169 | 
            +
                        strategy: Installation strategy to use
         | 
| 170 | 
            +
                        
         | 
| 171 | 
            +
                    Returns:
         | 
| 172 | 
            +
                        Tuple of (success, error_message)
         | 
| 173 | 
            +
                    """
         | 
| 174 | 
            +
                    try:
         | 
| 175 | 
            +
                        cmd = self._build_install_command(package_spec, strategy)
         | 
| 176 | 
            +
                        logger.debug(f"Running: {' '.join(cmd)}")
         | 
| 177 | 
            +
                        
         | 
| 178 | 
            +
                        result = subprocess.run(
         | 
| 179 | 
            +
                            cmd,
         | 
| 180 | 
            +
                            capture_output=True,
         | 
| 181 | 
            +
                            text=True,
         | 
| 182 | 
            +
                            timeout=self.timeout
         | 
| 183 | 
            +
                        )
         | 
| 184 | 
            +
                        
         | 
| 185 | 
            +
                        if result.returncode == 0:
         | 
| 186 | 
            +
                            # Verify installation
         | 
| 187 | 
            +
                            if self._verify_installation(package_spec):
         | 
| 188 | 
            +
                                return True, None
         | 
| 189 | 
            +
                            else:
         | 
| 190 | 
            +
                                return False, "Package installed but verification failed"
         | 
| 191 | 
            +
                        else:
         | 
| 192 | 
            +
                            error_msg = self._extract_error_message(result.stderr)
         | 
| 193 | 
            +
                            logger.debug(f"Installation failed: {error_msg}")
         | 
| 194 | 
            +
                            return False, error_msg
         | 
| 195 | 
            +
                            
         | 
| 196 | 
            +
                    except subprocess.TimeoutExpired:
         | 
| 197 | 
            +
                        return False, f"Installation timed out after {self.timeout}s"
         | 
| 198 | 
            +
                    except Exception as e:
         | 
| 199 | 
            +
                        return False, f"Unexpected error: {str(e)}"
         | 
| 200 | 
            +
                
         | 
| 201 | 
            +
                def _build_install_command(
         | 
| 202 | 
            +
                    self,
         | 
| 203 | 
            +
                    package_spec: str,
         | 
| 204 | 
            +
                    strategy: InstallStrategy
         | 
| 205 | 
            +
                ) -> List[str]:
         | 
| 206 | 
            +
                    """
         | 
| 207 | 
            +
                    Build the installation command for a given strategy.
         | 
| 208 | 
            +
                    
         | 
| 209 | 
            +
                    Args:
         | 
| 210 | 
            +
                        package_spec: Package specification
         | 
| 211 | 
            +
                        strategy: Installation strategy
         | 
| 212 | 
            +
                        
         | 
| 213 | 
            +
                    Returns:
         | 
| 214 | 
            +
                        Command as list of arguments
         | 
| 215 | 
            +
                    """
         | 
| 216 | 
            +
                    base_cmd = [sys.executable, "-m", "pip", "install"]
         | 
| 217 | 
            +
                    
         | 
| 218 | 
            +
                    # Add cache control
         | 
| 219 | 
            +
                    if not self.use_cache:
         | 
| 220 | 
            +
                        base_cmd.append("--no-cache-dir")
         | 
| 221 | 
            +
                    
         | 
| 222 | 
            +
                    if strategy == InstallStrategy.PIP:
         | 
| 223 | 
            +
                        return base_cmd + [package_spec]
         | 
| 224 | 
            +
                        
         | 
| 225 | 
            +
                    elif strategy == InstallStrategy.PIP_NO_DEPS:
         | 
| 226 | 
            +
                        return base_cmd + ["--no-deps", package_spec]
         | 
| 227 | 
            +
                        
         | 
| 228 | 
            +
                    elif strategy == InstallStrategy.PIP_UPGRADE:
         | 
| 229 | 
            +
                        return base_cmd + ["--upgrade", package_spec]
         | 
| 230 | 
            +
                        
         | 
| 231 | 
            +
                    elif strategy == InstallStrategy.PIP_INDEX_URL:
         | 
| 232 | 
            +
                        # Try alternative index (PyPI mirror)
         | 
| 233 | 
            +
                        return base_cmd + [
         | 
| 234 | 
            +
                            "--index-url", "https://pypi.org/simple",
         | 
| 235 | 
            +
                            "--extra-index-url", "https://pypi.python.org/simple",
         | 
| 236 | 
            +
                            package_spec
         | 
| 237 | 
            +
                        ]
         | 
| 238 | 
            +
                        
         | 
| 239 | 
            +
                    else:
         | 
| 240 | 
            +
                        return base_cmd + [package_spec]
         | 
| 241 | 
            +
                
         | 
| 242 | 
            +
                def _extract_package_name(self, package_spec: str) -> str:
         | 
| 243 | 
            +
                    """
         | 
| 244 | 
            +
                    Extract package name from specification.
         | 
| 245 | 
            +
                    
         | 
| 246 | 
            +
                    Args:
         | 
| 247 | 
            +
                        package_spec: Package specification (e.g., "pandas>=2.0.0")
         | 
| 248 | 
            +
                        
         | 
| 249 | 
            +
                    Returns:
         | 
| 250 | 
            +
                        Package name (e.g., "pandas")
         | 
| 251 | 
            +
                    """
         | 
| 252 | 
            +
                    # Remove version specifiers
         | 
| 253 | 
            +
                    match = re.match(r'^([a-zA-Z0-9_-]+)', package_spec)
         | 
| 254 | 
            +
                    if match:
         | 
| 255 | 
            +
                        return match.group(1)
         | 
| 256 | 
            +
                    return package_spec
         | 
| 257 | 
            +
                
         | 
| 258 | 
            +
                def _needs_special_handling(self, package_name: str) -> bool:
         | 
| 259 | 
            +
                    """
         | 
| 260 | 
            +
                    Check if package needs special installation handling.
         | 
| 261 | 
            +
                    
         | 
| 262 | 
            +
                    Args:
         | 
| 263 | 
            +
                        package_name: Name of the package
         | 
| 264 | 
            +
                        
         | 
| 265 | 
            +
                    Returns:
         | 
| 266 | 
            +
                        True if package needs special handling
         | 
| 267 | 
            +
                    """
         | 
| 268 | 
            +
                    # Known problematic packages
         | 
| 269 | 
            +
                    special_packages = {
         | 
| 270 | 
            +
                        'tree-sitter-ruby',
         | 
| 271 | 
            +
                        'tree-sitter-php',
         | 
| 272 | 
            +
                        'tree-sitter-javascript',
         | 
| 273 | 
            +
                        'tree-sitter-typescript',
         | 
| 274 | 
            +
                        'tree-sitter-go',
         | 
| 275 | 
            +
                        'tree-sitter-rust',
         | 
| 276 | 
            +
                        'tree-sitter-java',
         | 
| 277 | 
            +
                        'tree-sitter-cpp',
         | 
| 278 | 
            +
                        'tree-sitter-c',
         | 
| 279 | 
            +
                    }
         | 
| 280 | 
            +
                    
         | 
| 281 | 
            +
                    return package_name.lower() in special_packages
         | 
| 282 | 
            +
                
         | 
| 283 | 
            +
                def _get_special_strategies(self, package_name: str) -> List[InstallStrategy]:
         | 
| 284 | 
            +
                    """
         | 
| 285 | 
            +
                    Get special installation strategies for problematic packages.
         | 
| 286 | 
            +
                    
         | 
| 287 | 
            +
                    Args:
         | 
| 288 | 
            +
                        package_name: Name of the package
         | 
| 289 | 
            +
                        
         | 
| 290 | 
            +
                    Returns:
         | 
| 291 | 
            +
                        List of strategies to try
         | 
| 292 | 
            +
                    """
         | 
| 293 | 
            +
                    # For tree-sitter packages, try upgrade first (often fixes version conflicts)
         | 
| 294 | 
            +
                    if package_name.startswith('tree-sitter-'):
         | 
| 295 | 
            +
                        return [
         | 
| 296 | 
            +
                            InstallStrategy.PIP_UPGRADE,
         | 
| 297 | 
            +
                            InstallStrategy.PIP,
         | 
| 298 | 
            +
                            InstallStrategy.PIP_INDEX_URL,
         | 
| 299 | 
            +
                            InstallStrategy.PIP_NO_DEPS,
         | 
| 300 | 
            +
                        ]
         | 
| 301 | 
            +
                    
         | 
| 302 | 
            +
                    return [InstallStrategy.PIP, InstallStrategy.PIP_UPGRADE]
         | 
| 303 | 
            +
                
         | 
| 304 | 
            +
                def _verify_installation(self, package_spec: str) -> bool:
         | 
| 305 | 
            +
                    """
         | 
| 306 | 
            +
                    Verify that a package was successfully installed.
         | 
| 307 | 
            +
                    
         | 
| 308 | 
            +
                    Args:
         | 
| 309 | 
            +
                        package_spec: Package specification
         | 
| 310 | 
            +
                        
         | 
| 311 | 
            +
                    Returns:
         | 
| 312 | 
            +
                        True if package is installed and importable
         | 
| 313 | 
            +
                    """
         | 
| 314 | 
            +
                    package_name = self._extract_package_name(package_spec)
         | 
| 315 | 
            +
                    
         | 
| 316 | 
            +
                    # Convert package name to import name (e.g., tree-sitter-ruby -> tree_sitter_ruby)
         | 
| 317 | 
            +
                    import_name = package_name.replace('-', '_')
         | 
| 318 | 
            +
                    
         | 
| 319 | 
            +
                    try:
         | 
| 320 | 
            +
                        # Check if package is installed
         | 
| 321 | 
            +
                        import importlib.metadata
         | 
| 322 | 
            +
                        try:
         | 
| 323 | 
            +
                            version = importlib.metadata.version(package_name)
         | 
| 324 | 
            +
                            logger.debug(f"Package {package_name} version {version} is installed")
         | 
| 325 | 
            +
                            
         | 
| 326 | 
            +
                            # For tree-sitter packages, don't try to import (they have C extensions)
         | 
| 327 | 
            +
                            if package_name.startswith('tree-sitter-'):
         | 
| 328 | 
            +
                                return True
         | 
| 329 | 
            +
                            
         | 
| 330 | 
            +
                            # Try to import the package
         | 
| 331 | 
            +
                            try:
         | 
| 332 | 
            +
                                __import__(import_name)
         | 
| 333 | 
            +
                                return True
         | 
| 334 | 
            +
                            except ImportError:
         | 
| 335 | 
            +
                                # Some packages have different import names, that's OK
         | 
| 336 | 
            +
                                return True
         | 
| 337 | 
            +
                                
         | 
| 338 | 
            +
                        except importlib.metadata.PackageNotFoundError:
         | 
| 339 | 
            +
                            return False
         | 
| 340 | 
            +
                            
         | 
| 341 | 
            +
                    except ImportError:
         | 
| 342 | 
            +
                        # Fallback for older Python versions
         | 
| 343 | 
            +
                        try:
         | 
| 344 | 
            +
                            import pkg_resources
         | 
| 345 | 
            +
                            pkg_resources.get_distribution(package_name)
         | 
| 346 | 
            +
                            return True
         | 
| 347 | 
            +
                        except pkg_resources.DistributionNotFound:
         | 
| 348 | 
            +
                            return False
         | 
| 349 | 
            +
                
         | 
| 350 | 
            +
                def _is_retryable_error(self, error: Optional[str]) -> bool:
         | 
| 351 | 
            +
                    """
         | 
| 352 | 
            +
                    Determine if an error is worth retrying.
         | 
| 353 | 
            +
                    
         | 
| 354 | 
            +
                    Args:
         | 
| 355 | 
            +
                        error: Error message
         | 
| 356 | 
            +
                        
         | 
| 357 | 
            +
                    Returns:
         | 
| 358 | 
            +
                        True if error is retryable
         | 
| 359 | 
            +
                    """
         | 
| 360 | 
            +
                    if not error:
         | 
| 361 | 
            +
                        return False
         | 
| 362 | 
            +
                    
         | 
| 363 | 
            +
                    # Retryable error patterns
         | 
| 364 | 
            +
                    retryable_patterns = [
         | 
| 365 | 
            +
                        'connection', 'timeout', 'temporary failure',
         | 
| 366 | 
            +
                        'network', 'unreachable', 'could not find',
         | 
| 367 | 
            +
                        'no matching distribution', 'httperror',
         | 
| 368 | 
            +
                        'http error', 'ssl', 'certificate',
         | 
| 369 | 
            +
                        'readtimeout', 'connectionerror'
         | 
| 370 | 
            +
                    ]
         | 
| 371 | 
            +
                    
         | 
| 372 | 
            +
                    error_lower = error.lower()
         | 
| 373 | 
            +
                    return any(pattern in error_lower for pattern in retryable_patterns)
         | 
| 374 | 
            +
                
         | 
| 375 | 
            +
                def _extract_error_message(self, stderr: str) -> str:
         | 
| 376 | 
            +
                    """
         | 
| 377 | 
            +
                    Extract meaningful error message from pip stderr.
         | 
| 378 | 
            +
                    
         | 
| 379 | 
            +
                    Args:
         | 
| 380 | 
            +
                        stderr: Standard error output from pip
         | 
| 381 | 
            +
                        
         | 
| 382 | 
            +
                    Returns:
         | 
| 383 | 
            +
                        Extracted error message
         | 
| 384 | 
            +
                    """
         | 
| 385 | 
            +
                    if not stderr:
         | 
| 386 | 
            +
                        return "Unknown error"
         | 
| 387 | 
            +
                    
         | 
| 388 | 
            +
                    # Look for ERROR: lines
         | 
| 389 | 
            +
                    error_lines = []
         | 
| 390 | 
            +
                    for line in stderr.splitlines():
         | 
| 391 | 
            +
                        if 'ERROR:' in line:
         | 
| 392 | 
            +
                            error_lines.append(line.split('ERROR:', 1)[1].strip())
         | 
| 393 | 
            +
                    
         | 
| 394 | 
            +
                    if error_lines:
         | 
| 395 | 
            +
                        return ' | '.join(error_lines)
         | 
| 396 | 
            +
                    
         | 
| 397 | 
            +
                    # Fall back to last non-empty line
         | 
| 398 | 
            +
                    lines = [l.strip() for l in stderr.splitlines() if l.strip()]
         | 
| 399 | 
            +
                    if lines:
         | 
| 400 | 
            +
                        return lines[-1]
         | 
| 401 | 
            +
                    
         | 
| 402 | 
            +
                    return "Installation failed"
         | 
| 403 | 
            +
                
         | 
| 404 | 
            +
                def _get_consolidated_error(self, package_spec: str) -> str:
         | 
| 405 | 
            +
                    """
         | 
| 406 | 
            +
                    Get a consolidated error message from all attempts.
         | 
| 407 | 
            +
                    
         | 
| 408 | 
            +
                    Args:
         | 
| 409 | 
            +
                        package_spec: Package specification that failed
         | 
| 410 | 
            +
                        
         | 
| 411 | 
            +
                    Returns:
         | 
| 412 | 
            +
                        Consolidated error message
         | 
| 413 | 
            +
                    """
         | 
| 414 | 
            +
                    # Get unique error messages from attempts
         | 
| 415 | 
            +
                    errors = set()
         | 
| 416 | 
            +
                    for attempt in self.attempts:
         | 
| 417 | 
            +
                        if attempt.package == package_spec and attempt.error:
         | 
| 418 | 
            +
                            errors.add(attempt.error)
         | 
| 419 | 
            +
                    
         | 
| 420 | 
            +
                    if not errors:
         | 
| 421 | 
            +
                        return f"Failed to install {package_spec} after {len(self.attempts)} attempts"
         | 
| 422 | 
            +
                    
         | 
| 423 | 
            +
                    # Format error message
         | 
| 424 | 
            +
                    if len(errors) == 1:
         | 
| 425 | 
            +
                        return list(errors)[0]
         | 
| 426 | 
            +
                    else:
         | 
| 427 | 
            +
                        return f"Multiple errors: {' | '.join(errors)}"
         | 
| 428 | 
            +
                
         | 
| 429 | 
            +
                def install_packages(
         | 
| 430 | 
            +
                    self,
         | 
| 431 | 
            +
                    packages: List[str],
         | 
| 432 | 
            +
                    parallel: bool = False
         | 
| 433 | 
            +
                ) -> Tuple[List[str], List[str], Dict[str, str]]:
         | 
| 434 | 
            +
                    """
         | 
| 435 | 
            +
                    Install multiple packages with robust error handling.
         | 
| 436 | 
            +
                    
         | 
| 437 | 
            +
                    Args:
         | 
| 438 | 
            +
                        packages: List of package specifications
         | 
| 439 | 
            +
                        parallel: Whether to attempt parallel installation
         | 
| 440 | 
            +
                        
         | 
| 441 | 
            +
                    Returns:
         | 
| 442 | 
            +
                        Tuple of (successful_packages, failed_packages, error_map)
         | 
| 443 | 
            +
                    """
         | 
| 444 | 
            +
                    successful = []
         | 
| 445 | 
            +
                    failed = []
         | 
| 446 | 
            +
                    errors = {}
         | 
| 447 | 
            +
                    
         | 
| 448 | 
            +
                    # Group packages that can be installed together
         | 
| 449 | 
            +
                    if parallel and len(packages) > 1:
         | 
| 450 | 
            +
                        # Try to install all at once first
         | 
| 451 | 
            +
                        logger.info(f"Attempting batch installation of {len(packages)} packages...")
         | 
| 452 | 
            +
                        success, error = self._attempt_batch_install(packages)
         | 
| 453 | 
            +
                        
         | 
| 454 | 
            +
                        if success:
         | 
| 455 | 
            +
                            logger.info("Batch installation successful")
         | 
| 456 | 
            +
                            return packages, [], {}
         | 
| 457 | 
            +
                        else:
         | 
| 458 | 
            +
                            logger.warning(f"Batch installation failed: {error}")
         | 
| 459 | 
            +
                            logger.info("Falling back to individual installation...")
         | 
| 460 | 
            +
                    
         | 
| 461 | 
            +
                    # Install packages individually
         | 
| 462 | 
            +
                    for i, package in enumerate(packages, 1):
         | 
| 463 | 
            +
                        logger.info(f"Installing package {i}/{len(packages)}: {package}")
         | 
| 464 | 
            +
                        
         | 
| 465 | 
            +
                        success, error = self.install_package(package)
         | 
| 466 | 
            +
                        
         | 
| 467 | 
            +
                        if success:
         | 
| 468 | 
            +
                            successful.append(package)
         | 
| 469 | 
            +
                        else:
         | 
| 470 | 
            +
                            failed.append(package)
         | 
| 471 | 
            +
                            errors[package] = error or "Unknown error"
         | 
| 472 | 
            +
                    
         | 
| 473 | 
            +
                    return successful, failed, errors
         | 
| 474 | 
            +
                
         | 
| 475 | 
            +
                def _attempt_batch_install(self, packages: List[str]) -> Tuple[bool, Optional[str]]:
         | 
| 476 | 
            +
                    """
         | 
| 477 | 
            +
                    Attempt to install multiple packages in a single pip command.
         | 
| 478 | 
            +
                    
         | 
| 479 | 
            +
                    Args:
         | 
| 480 | 
            +
                        packages: List of package specifications
         | 
| 481 | 
            +
                        
         | 
| 482 | 
            +
                    Returns:
         | 
| 483 | 
            +
                        Tuple of (success, error_message)
         | 
| 484 | 
            +
                    """
         | 
| 485 | 
            +
                    try:
         | 
| 486 | 
            +
                        cmd = [sys.executable, "-m", "pip", "install"] + packages
         | 
| 487 | 
            +
                        
         | 
| 488 | 
            +
                        result = subprocess.run(
         | 
| 489 | 
            +
                            cmd,
         | 
| 490 | 
            +
                            capture_output=True,
         | 
| 491 | 
            +
                            text=True,
         | 
| 492 | 
            +
                            timeout=self.timeout * 2  # Longer timeout for batch
         | 
| 493 | 
            +
                        )
         | 
| 494 | 
            +
                        
         | 
| 495 | 
            +
                        if result.returncode == 0:
         | 
| 496 | 
            +
                            # Verify all packages
         | 
| 497 | 
            +
                            all_verified = all(
         | 
| 498 | 
            +
                                self._verify_installation(pkg) for pkg in packages
         | 
| 499 | 
            +
                            )
         | 
| 500 | 
            +
                            if all_verified:
         | 
| 501 | 
            +
                                return True, None
         | 
| 502 | 
            +
                            else:
         | 
| 503 | 
            +
                                return False, "Some packages failed verification"
         | 
| 504 | 
            +
                        else:
         | 
| 505 | 
            +
                            error_msg = self._extract_error_message(result.stderr)
         | 
| 506 | 
            +
                            return False, error_msg
         | 
| 507 | 
            +
                            
         | 
| 508 | 
            +
                    except subprocess.TimeoutExpired:
         | 
| 509 | 
            +
                        return False, f"Batch installation timed out"
         | 
| 510 | 
            +
                    except Exception as e:
         | 
| 511 | 
            +
                        return False, f"Batch installation error: {str(e)}"
         | 
| 512 | 
            +
                
         | 
| 513 | 
            +
                def get_report(self) -> str:
         | 
| 514 | 
            +
                    """
         | 
| 515 | 
            +
                    Generate a report of installation attempts.
         | 
| 516 | 
            +
                    
         | 
| 517 | 
            +
                    Returns:
         | 
| 518 | 
            +
                        Formatted report string
         | 
| 519 | 
            +
                    """
         | 
| 520 | 
            +
                    lines = []
         | 
| 521 | 
            +
                    lines.append("=" * 60)
         | 
| 522 | 
            +
                    lines.append("INSTALLATION REPORT")
         | 
| 523 | 
            +
                    lines.append("=" * 60)
         | 
| 524 | 
            +
                    
         | 
| 525 | 
            +
                    # Summary
         | 
| 526 | 
            +
                    total_attempts = len(self.attempts)
         | 
| 527 | 
            +
                    successful = sum(1 for a in self.attempts if a.success)
         | 
| 528 | 
            +
                    failed = total_attempts - successful
         | 
| 529 | 
            +
                    
         | 
| 530 | 
            +
                    lines.append(f"Total attempts: {total_attempts}")
         | 
| 531 | 
            +
                    lines.append(f"Successful: {successful}")
         | 
| 532 | 
            +
                    lines.append(f"Failed: {failed}")
         | 
| 533 | 
            +
                    lines.append("")
         | 
| 534 | 
            +
                    
         | 
| 535 | 
            +
                    # Details by package
         | 
| 536 | 
            +
                    packages = {}
         | 
| 537 | 
            +
                    for attempt in self.attempts:
         | 
| 538 | 
            +
                        if attempt.package not in packages:
         | 
| 539 | 
            +
                            packages[attempt.package] = []
         | 
| 540 | 
            +
                        packages[attempt.package].append(attempt)
         | 
| 541 | 
            +
                    
         | 
| 542 | 
            +
                    for package, attempts in packages.items():
         | 
| 543 | 
            +
                        success = any(a.success for a in attempts)
         | 
| 544 | 
            +
                        status = "✓" if success else "✗"
         | 
| 545 | 
            +
                        lines.append(f"{status} {package}:")
         | 
| 546 | 
            +
                        
         | 
| 547 | 
            +
                        for attempt in attempts:
         | 
| 548 | 
            +
                            retry_str = f" (retry {attempt.retry_count})" if attempt.retry_count > 0 else ""
         | 
| 549 | 
            +
                            result = "success" if attempt.success else f"failed: {attempt.error}"
         | 
| 550 | 
            +
                            lines.append(f"  - {attempt.strategy.value}{retry_str}: {result}")
         | 
| 551 | 
            +
                    
         | 
| 552 | 
            +
                    lines.append("=" * 60)
         | 
| 553 | 
            +
                    return "\n".join(lines)
         | 
| 554 | 
            +
             | 
| 555 | 
            +
             | 
| 556 | 
            +
            def install_with_retry(
         | 
| 557 | 
            +
                packages: List[str],
         | 
| 558 | 
            +
                max_retries: int = 3,
         | 
| 559 | 
            +
                verbose: bool = False
         | 
| 560 | 
            +
            ) -> Tuple[bool, str]:
         | 
| 561 | 
            +
                """
         | 
| 562 | 
            +
                Convenience function to install packages with retry logic.
         | 
| 563 | 
            +
                
         | 
| 564 | 
            +
                Args:
         | 
| 565 | 
            +
                    packages: List of package specifications
         | 
| 566 | 
            +
                    max_retries: Maximum retry attempts
         | 
| 567 | 
            +
                    verbose: Whether to print verbose output
         | 
| 568 | 
            +
                    
         | 
| 569 | 
            +
                Returns:
         | 
| 570 | 
            +
                    Tuple of (all_success, error_message)
         | 
| 571 | 
            +
                """
         | 
| 572 | 
            +
                if verbose:
         | 
| 573 | 
            +
                    import logging
         | 
| 574 | 
            +
                    logging.getLogger().setLevel(logging.DEBUG)
         | 
| 575 | 
            +
                
         | 
| 576 | 
            +
                installer = RobustPackageInstaller(max_retries=max_retries)
         | 
| 577 | 
            +
                successful, failed, errors = installer.install_packages(packages)
         | 
| 578 | 
            +
                
         | 
| 579 | 
            +
                if verbose:
         | 
| 580 | 
            +
                    print(installer.get_report())
         | 
| 581 | 
            +
                
         | 
| 582 | 
            +
                if failed:
         | 
| 583 | 
            +
                    error_msg = f"Failed to install {len(failed)} packages: "
         | 
| 584 | 
            +
                    error_msg += ", ".join(f"{pkg} ({errors[pkg]})" for pkg in failed)
         | 
| 585 | 
            +
                    return False, error_msg
         | 
| 586 | 
            +
                
         | 
| 587 | 
            +
                return True, ""
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.4
         | 
| 2 2 | 
             
            Name: claude-mpm
         | 
| 3 | 
            -
            Version: 3.7. | 
| 3 | 
            +
            Version: 3.7.8
         | 
| 4 4 | 
             
            Summary: Claude Multi-agent Project Manager - Clean orchestration with ticket management
         | 
| 5 5 | 
             
            Home-page: https://github.com/bobmatnyc/claude-mpm
         | 
| 6 6 | 
             
            Author: Claude MPM Team
         | 
| @@ -30,7 +30,6 @@ Requires-Dist: flask>=3.0.0 | |
| 30 30 | 
             
            Requires-Dist: flask-cors>=4.0.0
         | 
| 31 31 | 
             
            Requires-Dist: watchdog>=3.0.0
         | 
| 32 32 | 
             
            Requires-Dist: tree-sitter>=0.21.0
         | 
| 33 | 
            -
            Requires-Dist: tree-sitter-language-pack>=0.8.0
         | 
| 34 33 | 
             
            Requires-Dist: python-socketio>=5.11.0
         | 
| 35 34 | 
             
            Requires-Dist: aiohttp>=3.9.0
         | 
| 36 35 | 
             
            Requires-Dist: aiohttp-cors>=0.8.0
         | 
| @@ -47,53 +46,48 @@ Requires-Dist: flake8; extra == "dev" | |
| 47 46 | 
             
            Requires-Dist: mypy; extra == "dev"
         | 
| 48 47 | 
             
            Provides-Extra: monitor
         | 
| 49 48 | 
             
            Provides-Extra: agents
         | 
| 50 | 
            -
            Requires-Dist: allure-pytest>=2.13.0; extra == "agents"
         | 
| 51 | 
            -
            Requires-Dist: ansible>=9.0.0; extra == "agents"
         | 
| 52 | 
            -
            Requires-Dist: apache-airflow>=2.8.0; extra == "agents"
         | 
| 53 49 | 
             
            Requires-Dist: bandit>=1.7.5; extra == "agents"
         | 
| 54 50 | 
             
            Requires-Dist: black>=23.0.0; extra == "agents"
         | 
| 55 | 
            -
            Requires-Dist: checkov>=3.1.0; extra == "agents"
         | 
| 56 51 | 
             
            Requires-Dist: commitizen>=3.13.0; extra == "agents"
         | 
| 57 | 
            -
            Requires-Dist: cryptography>=41.0.0; extra == "agents"
         | 
| 58 52 | 
             
            Requires-Dist: dask>=2023.12.0; extra == "agents"
         | 
| 59 53 | 
             
            Requires-Dist: detect-secrets>=1.4.0; extra == "agents"
         | 
| 60 54 | 
             
            Requires-Dist: diagrams>=0.23.0; extra == "agents"
         | 
| 61 | 
            -
            Requires-Dist: docker>=7.0.0; extra == "agents"
         | 
| 62 55 | 
             
            Requires-Dist: docstring-parser>=0.15.0; extra == "agents"
         | 
| 63 56 | 
             
            Requires-Dist: faker>=20.0.0; extra == "agents"
         | 
| 64 57 | 
             
            Requires-Dist: gitlint>=0.19.0; extra == "agents"
         | 
| 65 58 | 
             
            Requires-Dist: gitpython>=3.1.40; extra == "agents"
         | 
| 66 | 
            -
            Requires-Dist: great-expectations>=0.18.0; extra == "agents"
         | 
| 67 59 | 
             
            Requires-Dist: hypothesis>=6.92.0; extra == "agents"
         | 
| 68 60 | 
             
            Requires-Dist: isort>=5.12.0; extra == "agents"
         | 
| 69 | 
            -
            Requires-Dist: kubernetes>=28.0.0; extra == "agents"
         | 
| 70 61 | 
             
            Requires-Dist: lizard>=1.17.0; extra == "agents"
         | 
| 71 62 | 
             
            Requires-Dist: mermaid-py>=0.2.0; extra == "agents"
         | 
| 72 63 | 
             
            Requires-Dist: mkdocs>=1.5.0; extra == "agents"
         | 
| 73 64 | 
             
            Requires-Dist: mutmut>=2.4.0; extra == "agents"
         | 
| 74 65 | 
             
            Requires-Dist: mypy>=1.8.0; extra == "agents"
         | 
| 75 66 | 
             
            Requires-Dist: pandas>=2.1.0; extra == "agents"
         | 
| 76 | 
            -
            Requires-Dist: pip-audit>=2.6.0; extra == "agents"
         | 
| 77 67 | 
             
            Requires-Dist: pre-commit>=3.5.0; extra == "agents"
         | 
| 78 68 | 
             
            Requires-Dist: prometheus-client>=0.19.0; extra == "agents"
         | 
| 79 69 | 
             
            Requires-Dist: pydoc-markdown>=4.8.0; extra == "agents"
         | 
| 80 70 | 
             
            Requires-Dist: pydriller>=2.5.0; extra == "agents"
         | 
| 81 71 | 
             
            Requires-Dist: pygments>=2.17.0; extra == "agents"
         | 
| 82 | 
            -
            Requires-Dist: pyjwt>=2.8.0; extra == "agents"
         | 
| 83 72 | 
             
            Requires-Dist: pytest>=7.4.0; extra == "agents"
         | 
| 84 73 | 
             
            Requires-Dist: pytest-benchmark>=4.0.0; extra == "agents"
         | 
| 85 74 | 
             
            Requires-Dist: pytest-cov>=4.1.0; extra == "agents"
         | 
| 86 75 | 
             
            Requires-Dist: radon>=6.0.0; extra == "agents"
         | 
| 87 76 | 
             
            Requires-Dist: rope>=1.11.0; extra == "agents"
         | 
| 88 | 
            -
            Requires-Dist: safety>=3.0.0; extra == "agents"
         | 
| 89 | 
            -
            Requires-Dist: semgrep>=1.45.0; extra == "agents"
         | 
| 90 77 | 
             
            Requires-Dist: sphinx>=7.2.0; extra == "agents"
         | 
| 91 78 | 
             
            Requires-Dist: sqlalchemy>=2.0.0; extra == "agents"
         | 
| 92 79 | 
             
            Requires-Dist: sqlparse>=0.4.4; extra == "agents"
         | 
| 93 | 
            -
            Requires-Dist: terraform-compliance>=1.3.0; extra == "agents"
         | 
| 94 80 | 
             
            Requires-Dist: tree-sitter>=0.21.0; extra == "agents"
         | 
| 95 | 
            -
            Requires-Dist: tree-sitter- | 
| 96 | 
            -
            Requires-Dist:  | 
| 81 | 
            +
            Requires-Dist: tree-sitter-python>=0.21.0; extra == "agents"
         | 
| 82 | 
            +
            Requires-Dist: tree-sitter-javascript>=0.21.0; extra == "agents"
         | 
| 83 | 
            +
            Requires-Dist: tree-sitter-typescript>=0.21.0; extra == "agents"
         | 
| 84 | 
            +
            Requires-Dist: tree-sitter-go>=0.21.0; extra == "agents"
         | 
| 85 | 
            +
            Requires-Dist: tree-sitter-rust>=0.21.0; extra == "agents"
         | 
| 86 | 
            +
            Requires-Dist: tree-sitter-java>=0.21.0; extra == "agents"
         | 
| 87 | 
            +
            Requires-Dist: tree-sitter-cpp>=0.21.0; extra == "agents"
         | 
| 88 | 
            +
            Requires-Dist: tree-sitter-c>=0.21.0; extra == "agents"
         | 
| 89 | 
            +
            Requires-Dist: tree-sitter-ruby>=0.21.0; extra == "agents"
         | 
| 90 | 
            +
            Requires-Dist: tree-sitter-php>=0.21.0; extra == "agents"
         | 
| 97 91 | 
             
            Dynamic: author-email
         | 
| 98 92 | 
             
            Dynamic: home-page
         | 
| 99 93 | 
             
            Dynamic: license-file
         | 
| @@ -118,19 +112,21 @@ A powerful orchestration framework for Claude Code that enables multi-agent work | |
| 118 112 | 
             
            ## Installation
         | 
| 119 113 |  | 
| 120 114 | 
             
            ```bash
         | 
| 121 | 
            -
            #  | 
| 115 | 
            +
            # Basic installation - pure Python, no compilation required
         | 
| 122 116 | 
             
            pip install claude-mpm
         | 
| 123 117 |  | 
| 124 118 | 
             
            # Install with development dependencies
         | 
| 125 119 | 
             
            pip install "claude-mpm[dev]"
         | 
| 126 120 |  | 
| 127 | 
            -
            # Install with agent dependencies  | 
| 121 | 
            +
            # Install with agent dependencies - pure Python tools
         | 
| 128 122 | 
             
            pip install "claude-mpm[agents]"
         | 
| 129 123 |  | 
| 130 124 | 
             
            # Install with all optional dependencies
         | 
| 131 125 | 
             
            pip install "claude-mpm[agents,dev]"
         | 
| 132 126 | 
             
            ```
         | 
| 133 127 |  | 
| 128 | 
            +
            All dependencies are pure Python - no Rust or compilation required. The `agents` dependency group includes tools for testing, code analysis, documentation, and development workflows.
         | 
| 129 | 
            +
             | 
| 134 130 | 
             
            ## Basic Usage
         | 
| 135 131 |  | 
| 136 132 | 
             
            ```bash
         | 
| @@ -164,10 +160,10 @@ For detailed usage, see [QUICKSTART.md](QUICKSTART.md) | |
| 164 160 |  | 
| 165 161 | 
             
            ### Agent Dependencies
         | 
| 166 162 |  | 
| 167 | 
            -
            Claude MPM automatically manages Python dependencies required by agents. Agents can declare their dependencies in their configuration files, and the system aggregates them for easy installation.
         | 
| 163 | 
            +
            Claude MPM automatically manages Python dependencies required by agents. All dependencies are pure Python packages that don't require compilation. Agents can declare their dependencies in their configuration files, and the system aggregates them for easy installation.
         | 
| 168 164 |  | 
| 169 165 | 
             
            ```bash
         | 
| 170 | 
            -
            # Install all agent dependencies
         | 
| 166 | 
            +
            # Install all agent dependencies (pure Python)
         | 
| 171 167 | 
             
            pip install "claude-mpm[agents]"
         | 
| 172 168 |  | 
| 173 169 | 
             
            # View current agent dependencies
         |