claude-mpm 3.5.6__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +96 -23
- claude_mpm/agents/BASE_PM.md +273 -0
- claude_mpm/agents/INSTRUCTIONS.md +114 -103
- claude_mpm/agents/agent_loader.py +36 -1
- claude_mpm/agents/async_agent_loader.py +421 -0
- claude_mpm/agents/templates/code_analyzer.json +81 -0
- claude_mpm/agents/templates/data_engineer.json +18 -3
- claude_mpm/agents/templates/documentation.json +18 -3
- claude_mpm/agents/templates/engineer.json +19 -4
- claude_mpm/agents/templates/ops.json +18 -3
- claude_mpm/agents/templates/qa.json +20 -4
- claude_mpm/agents/templates/research.json +20 -4
- claude_mpm/agents/templates/security.json +18 -3
- claude_mpm/agents/templates/version_control.json +16 -3
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +212 -3
- claude_mpm/cli/commands/aggregate.py +462 -0
- claude_mpm/cli/commands/config.py +277 -0
- claude_mpm/cli/commands/run.py +224 -36
- claude_mpm/cli/parser.py +176 -1
- claude_mpm/constants.py +19 -0
- claude_mpm/core/claude_runner.py +320 -44
- claude_mpm/core/config.py +161 -4
- claude_mpm/core/framework_loader.py +81 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +391 -9
- claude_mpm/init.py +40 -5
- claude_mpm/models/agent_session.py +511 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/start_activity_logging.py +86 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +165 -19
- claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
- claude_mpm/services/event_aggregator.py +547 -0
- claude_mpm/utils/agent_dependency_loader.py +655 -0
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/dependency_cache.py +376 -0
- claude_mpm/utils/dependency_strategies.py +343 -0
- claude_mpm/utils/environment_context.py +310 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/METADATA +47 -3
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/RECORD +45 -31
- claude_mpm/agents/templates/pm.json +0 -122
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.6.0.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,277 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            Configuration management commands for claude-mpm CLI.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            WHY: Users need a simple way to validate and manage their configuration from
         | 
| 5 | 
            +
            the command line. This module provides commands for configuration validation,
         | 
| 6 | 
            +
            viewing, and troubleshooting.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            DESIGN DECISIONS:
         | 
| 9 | 
            +
            - Integrate with existing CLI structure
         | 
| 10 | 
            +
            - Provide clear, actionable output
         | 
| 11 | 
            +
            - Support both validation and viewing operations
         | 
| 12 | 
            +
            - Use consistent error codes for CI/CD integration
         | 
| 13 | 
            +
            """
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            import sys
         | 
| 16 | 
            +
            from pathlib import Path
         | 
| 17 | 
            +
            from typing import Optional
         | 
| 18 | 
            +
            import json
         | 
| 19 | 
            +
            import yaml
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            from ...core.config import Config
         | 
| 22 | 
            +
            from ...core.logger import get_logger
         | 
| 23 | 
            +
            from ...utils.console import console
         | 
| 24 | 
            +
            from rich.table import Table
         | 
| 25 | 
            +
            from rich.syntax import Syntax
         | 
| 26 | 
            +
            from rich.panel import Panel
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            logger = get_logger(__name__)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
             | 
| 31 | 
            +
            def manage_config(args) -> int:
         | 
| 32 | 
            +
                """Main entry point for configuration management commands.
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                WHY: This dispatcher handles different configuration subcommands,
         | 
| 35 | 
            +
                routing to the appropriate handler based on user input.
         | 
| 36 | 
            +
                
         | 
| 37 | 
            +
                Args:
         | 
| 38 | 
            +
                    args: Parsed command line arguments
         | 
| 39 | 
            +
                    
         | 
| 40 | 
            +
                Returns:
         | 
| 41 | 
            +
                    Exit code (0 for success, non-zero for failure)
         | 
| 42 | 
            +
                """
         | 
| 43 | 
            +
                if args.config_command == 'validate':
         | 
| 44 | 
            +
                    return validate_config(args)
         | 
| 45 | 
            +
                elif args.config_command == 'view':
         | 
| 46 | 
            +
                    return view_config(args)
         | 
| 47 | 
            +
                elif args.config_command == 'status':
         | 
| 48 | 
            +
                    return show_config_status(args)
         | 
| 49 | 
            +
                else:
         | 
| 50 | 
            +
                    console.print(f"[red]Unknown config command: {args.config_command}[/red]")
         | 
| 51 | 
            +
                    return 1
         | 
| 52 | 
            +
             | 
| 53 | 
            +
             | 
| 54 | 
            +
            def validate_config(args) -> int:
         | 
| 55 | 
            +
                """Validate configuration file.
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                WHY: Users need immediate feedback on configuration validity with
         | 
| 58 | 
            +
                clear guidance on how to fix any issues found.
         | 
| 59 | 
            +
                
         | 
| 60 | 
            +
                Args:
         | 
| 61 | 
            +
                    args: Command line arguments including config file path
         | 
| 62 | 
            +
                    
         | 
| 63 | 
            +
                Returns:
         | 
| 64 | 
            +
                    Exit code (0=valid, 1=errors, 2=warnings in strict mode)
         | 
| 65 | 
            +
                """
         | 
| 66 | 
            +
                config_file = args.config_file or Path(".claude-mpm/configuration.yaml")
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                console.print(f"\n[bold blue]Validating configuration: {config_file}[/bold blue]\n")
         | 
| 69 | 
            +
                
         | 
| 70 | 
            +
                # Check if file exists
         | 
| 71 | 
            +
                if not config_file.exists():
         | 
| 72 | 
            +
                    console.print(f"[red]✗ Configuration file not found: {config_file}[/red]")
         | 
| 73 | 
            +
                    console.print(f"[yellow]→ Create with: mkdir -p {config_file.parent} && touch {config_file}[/yellow]")
         | 
| 74 | 
            +
                    return 1
         | 
| 75 | 
            +
                    
         | 
| 76 | 
            +
                try:
         | 
| 77 | 
            +
                    # Try to load configuration
         | 
| 78 | 
            +
                    config = Config(config_file=config_file)
         | 
| 79 | 
            +
                    
         | 
| 80 | 
            +
                    # Validate configuration
         | 
| 81 | 
            +
                    is_valid, errors, warnings = config.validate_configuration()
         | 
| 82 | 
            +
                    
         | 
| 83 | 
            +
                    # Display results
         | 
| 84 | 
            +
                    if errors:
         | 
| 85 | 
            +
                        console.print("[bold red]ERRORS:[/bold red]")
         | 
| 86 | 
            +
                        for error in errors:
         | 
| 87 | 
            +
                            console.print(f"  [red]✗ {error}[/red]")
         | 
| 88 | 
            +
                            
         | 
| 89 | 
            +
                    if warnings:
         | 
| 90 | 
            +
                        console.print("\n[bold yellow]WARNINGS:[/bold yellow]")
         | 
| 91 | 
            +
                        for warning in warnings:
         | 
| 92 | 
            +
                            console.print(f"  [yellow]⚠ {warning}[/yellow]")
         | 
| 93 | 
            +
                            
         | 
| 94 | 
            +
                    # Show summary
         | 
| 95 | 
            +
                    if is_valid and not warnings:
         | 
| 96 | 
            +
                        console.print("\n[green]✓ Configuration is valid[/green]")
         | 
| 97 | 
            +
                        return 0
         | 
| 98 | 
            +
                    elif is_valid and warnings:
         | 
| 99 | 
            +
                        console.print(f"\n[green]✓ Configuration is valid with {len(warnings)} warning(s)[/green]")
         | 
| 100 | 
            +
                        return 2 if args.strict else 0
         | 
| 101 | 
            +
                    else:
         | 
| 102 | 
            +
                        console.print(f"\n[red]✗ Configuration validation failed with {len(errors)} error(s)[/red]")
         | 
| 103 | 
            +
                        console.print("\n[yellow]Run 'python scripts/validate_configuration.py' for detailed analysis[/yellow]")
         | 
| 104 | 
            +
                        return 1
         | 
| 105 | 
            +
                        
         | 
| 106 | 
            +
                except Exception as e:
         | 
| 107 | 
            +
                    console.print(f"[red]Failed to validate configuration: {e}[/red]")
         | 
| 108 | 
            +
                    logger.exception("Configuration validation error")
         | 
| 109 | 
            +
                    return 1
         | 
| 110 | 
            +
             | 
| 111 | 
            +
             | 
| 112 | 
            +
            def view_config(args) -> int:
         | 
| 113 | 
            +
                """View current configuration.
         | 
| 114 | 
            +
                
         | 
| 115 | 
            +
                WHY: Users need to see their effective configuration including
         | 
| 116 | 
            +
                defaults and environment variable overrides.
         | 
| 117 | 
            +
                
         | 
| 118 | 
            +
                Args:
         | 
| 119 | 
            +
                    args: Command line arguments
         | 
| 120 | 
            +
                    
         | 
| 121 | 
            +
                Returns:
         | 
| 122 | 
            +
                    Exit code (0 for success)
         | 
| 123 | 
            +
                """
         | 
| 124 | 
            +
                try:
         | 
| 125 | 
            +
                    # Load configuration
         | 
| 126 | 
            +
                    config_file = args.config_file
         | 
| 127 | 
            +
                    config = Config(config_file=config_file)
         | 
| 128 | 
            +
                    
         | 
| 129 | 
            +
                    # Get configuration as dictionary
         | 
| 130 | 
            +
                    config_dict = config.to_dict()
         | 
| 131 | 
            +
                    
         | 
| 132 | 
            +
                    # Filter by section if specified
         | 
| 133 | 
            +
                    if args.section:
         | 
| 134 | 
            +
                        if args.section in config_dict:
         | 
| 135 | 
            +
                            config_dict = {args.section: config_dict[args.section]}
         | 
| 136 | 
            +
                        else:
         | 
| 137 | 
            +
                            console.print(f"[red]Section '{args.section}' not found in configuration[/red]")
         | 
| 138 | 
            +
                            return 1
         | 
| 139 | 
            +
                            
         | 
| 140 | 
            +
                    # Format output
         | 
| 141 | 
            +
                    if args.format == 'json':
         | 
| 142 | 
            +
                        output = json.dumps(config_dict, indent=2)
         | 
| 143 | 
            +
                        syntax = Syntax(output, "json", theme="monokai", line_numbers=False)
         | 
| 144 | 
            +
                        console.print(syntax)
         | 
| 145 | 
            +
                    elif args.format == 'yaml':
         | 
| 146 | 
            +
                        output = yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
         | 
| 147 | 
            +
                        syntax = Syntax(output, "yaml", theme="monokai", line_numbers=False)
         | 
| 148 | 
            +
                        console.print(syntax)
         | 
| 149 | 
            +
                    else:  # table format
         | 
| 150 | 
            +
                        display_config_table(config_dict)
         | 
| 151 | 
            +
                        
         | 
| 152 | 
            +
                    return 0
         | 
| 153 | 
            +
                    
         | 
| 154 | 
            +
                except Exception as e:
         | 
| 155 | 
            +
                    console.print(f"[red]Failed to view configuration: {e}[/red]")
         | 
| 156 | 
            +
                    logger.exception("Configuration view error")
         | 
| 157 | 
            +
                    return 1
         | 
| 158 | 
            +
             | 
| 159 | 
            +
             | 
| 160 | 
            +
            def show_config_status(args) -> int:
         | 
| 161 | 
            +
                """Show configuration status and health.
         | 
| 162 | 
            +
                
         | 
| 163 | 
            +
                WHY: Users need a quick way to check if their configuration is
         | 
| 164 | 
            +
                working correctly, especially for response logging.
         | 
| 165 | 
            +
                
         | 
| 166 | 
            +
                Args:
         | 
| 167 | 
            +
                    args: Command line arguments
         | 
| 168 | 
            +
                    
         | 
| 169 | 
            +
                Returns:
         | 
| 170 | 
            +
                    Exit code (0 for success)
         | 
| 171 | 
            +
                """
         | 
| 172 | 
            +
                try:
         | 
| 173 | 
            +
                    # Load configuration
         | 
| 174 | 
            +
                    config = Config(config_file=args.config_file)
         | 
| 175 | 
            +
                    
         | 
| 176 | 
            +
                    # Get status
         | 
| 177 | 
            +
                    status = config.get_configuration_status()
         | 
| 178 | 
            +
                    
         | 
| 179 | 
            +
                    # Create status panel
         | 
| 180 | 
            +
                    panel_content = []
         | 
| 181 | 
            +
                    
         | 
| 182 | 
            +
                    # Basic info
         | 
| 183 | 
            +
                    panel_content.append(f"[bold]Configuration Status[/bold]")
         | 
| 184 | 
            +
                    panel_content.append(f"Valid: {'✓' if status['valid'] else '✗'}")
         | 
| 185 | 
            +
                    panel_content.append(f"Loaded from: {status.get('loaded_from', 'defaults')}")
         | 
| 186 | 
            +
                    panel_content.append(f"Total keys: {status['key_count']}")
         | 
| 187 | 
            +
                    
         | 
| 188 | 
            +
                    # Feature status
         | 
| 189 | 
            +
                    panel_content.append("\n[bold]Features:[/bold]")
         | 
| 190 | 
            +
                    panel_content.append(
         | 
| 191 | 
            +
                        f"Response Logging: {'✓ Enabled' if status['response_logging_enabled'] else '✗ Disabled'}"
         | 
| 192 | 
            +
                    )
         | 
| 193 | 
            +
                    panel_content.append(
         | 
| 194 | 
            +
                        f"Memory System: {'✓ Enabled' if status['memory_enabled'] else '✗ Disabled'}"
         | 
| 195 | 
            +
                    )
         | 
| 196 | 
            +
                    
         | 
| 197 | 
            +
                    # Errors and warnings
         | 
| 198 | 
            +
                    if status['errors']:
         | 
| 199 | 
            +
                        panel_content.append(f"\n[red]Errors: {len(status['errors'])}[/red]")
         | 
| 200 | 
            +
                    if status['warnings']:
         | 
| 201 | 
            +
                        panel_content.append(f"\n[yellow]Warnings: {len(status['warnings'])}[/yellow]")
         | 
| 202 | 
            +
                        
         | 
| 203 | 
            +
                    # Display panel
         | 
| 204 | 
            +
                    panel = Panel(
         | 
| 205 | 
            +
                        "\n".join(panel_content),
         | 
| 206 | 
            +
                        title="Configuration Status",
         | 
| 207 | 
            +
                        border_style="green" if status['valid'] else "red"
         | 
| 208 | 
            +
                    )
         | 
| 209 | 
            +
                    console.print(panel)
         | 
| 210 | 
            +
                    
         | 
| 211 | 
            +
                    # Show detailed errors/warnings if verbose
         | 
| 212 | 
            +
                    if args.verbose:
         | 
| 213 | 
            +
                        if status['errors']:
         | 
| 214 | 
            +
                            console.print("\n[bold red]Errors:[/bold red]")
         | 
| 215 | 
            +
                            for error in status['errors']:
         | 
| 216 | 
            +
                                console.print(f"  [red]• {error}[/red]")
         | 
| 217 | 
            +
                                
         | 
| 218 | 
            +
                        if status['warnings']:
         | 
| 219 | 
            +
                            console.print("\n[bold yellow]Warnings:[/bold yellow]")
         | 
| 220 | 
            +
                            for warning in status['warnings']:
         | 
| 221 | 
            +
                                console.print(f"  [yellow]• {warning}[/yellow]")
         | 
| 222 | 
            +
                                
         | 
| 223 | 
            +
                    # Check response logging specifically
         | 
| 224 | 
            +
                    if args.check_response_logging:
         | 
| 225 | 
            +
                        console.print("\n[bold]Response Logging Configuration:[/bold]")
         | 
| 226 | 
            +
                        rl_config = config.get('response_logging', {})
         | 
| 227 | 
            +
                        
         | 
| 228 | 
            +
                        table = Table(show_header=True)
         | 
| 229 | 
            +
                        table.add_column("Setting", style="cyan")
         | 
| 230 | 
            +
                        table.add_column("Value", style="white")
         | 
| 231 | 
            +
                        
         | 
| 232 | 
            +
                        table.add_row("Enabled", str(rl_config.get('enabled', False)))
         | 
| 233 | 
            +
                        table.add_row("Format", rl_config.get('format', 'json'))
         | 
| 234 | 
            +
                        table.add_row("Use Async", str(rl_config.get('use_async', True)))
         | 
| 235 | 
            +
                        table.add_row("Session Directory", rl_config.get('session_directory', '.claude-mpm/responses'))
         | 
| 236 | 
            +
                        table.add_row("Compression", str(rl_config.get('enable_compression', False)))
         | 
| 237 | 
            +
                        
         | 
| 238 | 
            +
                        console.print(table)
         | 
| 239 | 
            +
                        
         | 
| 240 | 
            +
                    return 0 if status['valid'] else 1
         | 
| 241 | 
            +
                    
         | 
| 242 | 
            +
                except Exception as e:
         | 
| 243 | 
            +
                    console.print(f"[red]Failed to get configuration status: {e}[/red]")
         | 
| 244 | 
            +
                    logger.exception("Configuration status error")
         | 
| 245 | 
            +
                    return 1
         | 
| 246 | 
            +
             | 
| 247 | 
            +
             | 
| 248 | 
            +
            def display_config_table(config_dict: dict, prefix: str = "") -> None:
         | 
| 249 | 
            +
                """Display configuration as a formatted table.
         | 
| 250 | 
            +
                
         | 
| 251 | 
            +
                Args:
         | 
| 252 | 
            +
                    config_dict: Configuration dictionary
         | 
| 253 | 
            +
                    prefix: Key prefix for nested values
         | 
| 254 | 
            +
                """
         | 
| 255 | 
            +
                table = Table(show_header=True, title="Configuration")
         | 
| 256 | 
            +
                table.add_column("Key", style="cyan", no_wrap=True)
         | 
| 257 | 
            +
                table.add_column("Value", style="white")
         | 
| 258 | 
            +
                table.add_column("Type", style="dim")
         | 
| 259 | 
            +
                
         | 
| 260 | 
            +
                def add_items(d: dict, prefix: str = ""):
         | 
| 261 | 
            +
                    for key, value in d.items():
         | 
| 262 | 
            +
                        full_key = f"{prefix}.{key}" if prefix else key
         | 
| 263 | 
            +
                        
         | 
| 264 | 
            +
                        if isinstance(value, dict) and value:
         | 
| 265 | 
            +
                            # Add nested items
         | 
| 266 | 
            +
                            add_items(value, full_key)
         | 
| 267 | 
            +
                        else:
         | 
| 268 | 
            +
                            # Add leaf value
         | 
| 269 | 
            +
                            value_str = str(value)
         | 
| 270 | 
            +
                            if len(value_str) > 50:
         | 
| 271 | 
            +
                                value_str = value_str[:47] + "..."
         | 
| 272 | 
            +
                                
         | 
| 273 | 
            +
                            type_str = type(value).__name__
         | 
| 274 | 
            +
                            table.add_row(full_key, value_str, type_str)
         | 
| 275 | 
            +
                            
         | 
| 276 | 
            +
                add_items(config_dict)
         | 
| 277 | 
            +
                console.print(table)
         | 
    
        claude_mpm/cli/commands/run.py
    CHANGED
    
    | @@ -10,10 +10,12 @@ import subprocess | |
| 10 10 | 
             
            import sys
         | 
| 11 11 | 
             
            import time
         | 
| 12 12 | 
             
            import webbrowser
         | 
| 13 | 
            +
            import logging
         | 
| 13 14 | 
             
            from pathlib import Path
         | 
| 14 15 | 
             
            from datetime import datetime
         | 
| 15 16 |  | 
| 16 17 | 
             
            from ...core.logger import get_logger
         | 
| 18 | 
            +
            from ...core.config import Config
         | 
| 17 19 | 
             
            from ...constants import LogLevel
         | 
| 18 20 | 
             
            from ..utils import get_user_input, list_agent_versions_at_startup
         | 
| 19 21 | 
             
            from ...utils.dependency_manager import ensure_socketio_dependencies
         | 
| @@ -52,6 +54,11 @@ def filter_claude_mpm_args(claude_args): | |
| 52 54 | 
             
                    '--no-native-agents',
         | 
| 53 55 | 
             
                    '--launch-method',
         | 
| 54 56 | 
             
                    '--resume',
         | 
| 57 | 
            +
                    # Dependency checking flags (MPM-specific)
         | 
| 58 | 
            +
                    '--no-check-dependencies',
         | 
| 59 | 
            +
                    '--force-check-dependencies',
         | 
| 60 | 
            +
                    '--no-prompt',
         | 
| 61 | 
            +
                    '--force-prompt',
         | 
| 55 62 | 
             
                    # Input/output flags (these are MPM-specific, not Claude CLI flags)
         | 
| 56 63 | 
             
                    '--input',
         | 
| 57 64 | 
             
                    '--non-interactive',
         | 
| @@ -172,6 +179,9 @@ def run_session(args): | |
| 172 179 | 
             
                if args.logging != LogLevel.OFF.value:
         | 
| 173 180 | 
             
                    logger.info("Starting Claude MPM session")
         | 
| 174 181 |  | 
| 182 | 
            +
                # Perform startup configuration check
         | 
| 183 | 
            +
                _check_configuration_health(logger)
         | 
| 184 | 
            +
                
         | 
| 175 185 | 
             
                try:
         | 
| 176 186 | 
             
                    from ...core.claude_runner import ClaudeRunner, create_simple_context
         | 
| 177 187 | 
             
                    from ...core.session_manager import SessionManager
         | 
| @@ -219,6 +229,98 @@ def run_session(args): | |
| 219 229 | 
             
                else:
         | 
| 220 230 | 
             
                    # List deployed agent versions at startup
         | 
| 221 231 | 
             
                    list_agent_versions_at_startup()
         | 
| 232 | 
            +
                    
         | 
| 233 | 
            +
                    # Smart dependency checking - only when needed
         | 
| 234 | 
            +
                    if getattr(args, 'check_dependencies', True):  # Default to checking
         | 
| 235 | 
            +
                        try:
         | 
| 236 | 
            +
                            from ...utils.agent_dependency_loader import AgentDependencyLoader
         | 
| 237 | 
            +
                            from ...utils.dependency_cache import SmartDependencyChecker
         | 
| 238 | 
            +
                            from ...utils.environment_context import should_prompt_for_dependencies
         | 
| 239 | 
            +
                            
         | 
| 240 | 
            +
                            # Initialize smart checker
         | 
| 241 | 
            +
                            smart_checker = SmartDependencyChecker()
         | 
| 242 | 
            +
                            loader = AgentDependencyLoader(auto_install=False)
         | 
| 243 | 
            +
                            
         | 
| 244 | 
            +
                            # Check if agents have changed
         | 
| 245 | 
            +
                            has_changed, deployment_hash = loader.has_agents_changed()
         | 
| 246 | 
            +
                            
         | 
| 247 | 
            +
                            # Determine if we should check dependencies
         | 
| 248 | 
            +
                            should_check, check_reason = smart_checker.should_check_dependencies(
         | 
| 249 | 
            +
                                force_check=getattr(args, 'force_check_dependencies', False),
         | 
| 250 | 
            +
                                deployment_hash=deployment_hash
         | 
| 251 | 
            +
                            )
         | 
| 252 | 
            +
                            
         | 
| 253 | 
            +
                            if should_check:
         | 
| 254 | 
            +
                                # Check if we're in an environment where prompting makes sense
         | 
| 255 | 
            +
                                can_prompt, prompt_reason = should_prompt_for_dependencies(
         | 
| 256 | 
            +
                                    force_prompt=getattr(args, 'force_prompt', False),
         | 
| 257 | 
            +
                                    force_skip=getattr(args, 'no_prompt', False)
         | 
| 258 | 
            +
                                )
         | 
| 259 | 
            +
                                
         | 
| 260 | 
            +
                                logger.debug(f"Dependency check needed: {check_reason}")
         | 
| 261 | 
            +
                                logger.debug(f"Interactive prompting: {can_prompt} ({prompt_reason})")
         | 
| 262 | 
            +
                                
         | 
| 263 | 
            +
                                # Get or check dependencies
         | 
| 264 | 
            +
                                results, was_cached = smart_checker.get_or_check_dependencies(
         | 
| 265 | 
            +
                                    loader=loader,
         | 
| 266 | 
            +
                                    force_check=getattr(args, 'force_check_dependencies', False)
         | 
| 267 | 
            +
                                )
         | 
| 268 | 
            +
                                
         | 
| 269 | 
            +
                                # Show summary if there are missing dependencies
         | 
| 270 | 
            +
                                if results['summary']['missing_python']:
         | 
| 271 | 
            +
                                    missing_count = len(results['summary']['missing_python'])
         | 
| 272 | 
            +
                                    print(f"⚠️  {missing_count} agent dependencies missing")
         | 
| 273 | 
            +
                                    
         | 
| 274 | 
            +
                                    if can_prompt and missing_count > 0:
         | 
| 275 | 
            +
                                        # Interactive prompt for installation
         | 
| 276 | 
            +
                                        print(f"\n📦 Missing dependencies detected:")
         | 
| 277 | 
            +
                                        for dep in results['summary']['missing_python'][:5]:
         | 
| 278 | 
            +
                                            print(f"   - {dep}")
         | 
| 279 | 
            +
                                        if missing_count > 5:
         | 
| 280 | 
            +
                                            print(f"   ... and {missing_count - 5} more")
         | 
| 281 | 
            +
                                        
         | 
| 282 | 
            +
                                        print("\nWould you like to install them now?")
         | 
| 283 | 
            +
                                        print("  [y] Yes, install missing dependencies")
         | 
| 284 | 
            +
                                        print("  [n] No, continue without installing")
         | 
| 285 | 
            +
                                        print("  [q] Quit")
         | 
| 286 | 
            +
                                        
         | 
| 287 | 
            +
                                        try:
         | 
| 288 | 
            +
                                            response = input("\nChoice [y/n/q]: ").strip().lower()
         | 
| 289 | 
            +
                                            if response == 'y':
         | 
| 290 | 
            +
                                                print("\n🔧 Installing missing dependencies...")
         | 
| 291 | 
            +
                                                loader.auto_install = True
         | 
| 292 | 
            +
                                                success, error = loader.install_missing_dependencies(
         | 
| 293 | 
            +
                                                    results['summary']['missing_python']
         | 
| 294 | 
            +
                                                )
         | 
| 295 | 
            +
                                                if success:
         | 
| 296 | 
            +
                                                    print("✅ Dependencies installed successfully")
         | 
| 297 | 
            +
                                                    # Invalidate cache after installation
         | 
| 298 | 
            +
                                                    smart_checker.cache.invalidate(deployment_hash)
         | 
| 299 | 
            +
                                                else:
         | 
| 300 | 
            +
                                                    print(f"❌ Installation failed: {error}")
         | 
| 301 | 
            +
                                            elif response == 'q':
         | 
| 302 | 
            +
                                                print("👋 Exiting...")
         | 
| 303 | 
            +
                                                return
         | 
| 304 | 
            +
                                            else:
         | 
| 305 | 
            +
                                                print("⏩ Continuing without installing dependencies")
         | 
| 306 | 
            +
                                        except (EOFError, KeyboardInterrupt):
         | 
| 307 | 
            +
                                            print("\n⏩ Continuing without installing dependencies")
         | 
| 308 | 
            +
                                    else:
         | 
| 309 | 
            +
                                        # Non-interactive environment or prompting disabled
         | 
| 310 | 
            +
                                        print("   Run 'pip install \"claude-mpm[agents]\"' to install all agent dependencies")
         | 
| 311 | 
            +
                                        if not can_prompt:
         | 
| 312 | 
            +
                                            logger.debug(f"Not prompting for installation: {prompt_reason}")
         | 
| 313 | 
            +
                                elif was_cached:
         | 
| 314 | 
            +
                                    logger.debug("Dependencies satisfied (cached result)")
         | 
| 315 | 
            +
                                else:
         | 
| 316 | 
            +
                                    logger.debug("All dependencies satisfied")
         | 
| 317 | 
            +
                            else:
         | 
| 318 | 
            +
                                logger.debug(f"Skipping dependency check: {check_reason}")
         | 
| 319 | 
            +
                                    
         | 
| 320 | 
            +
                        except Exception as e:
         | 
| 321 | 
            +
                            if args.logging != LogLevel.OFF.value:
         | 
| 322 | 
            +
                                logger.debug(f"Could not check agent dependencies: {e}")
         | 
| 323 | 
            +
                            # Continue anyway - don't block execution
         | 
| 222 324 |  | 
| 223 325 | 
             
                # Create simple runner
         | 
| 224 326 | 
             
                enable_tickets = not args.no_tickets
         | 
| @@ -421,13 +523,17 @@ def launch_socketio_monitor(port, logger): | |
| 421 523 | 
             
                            print(f"✅ Socket.IO server started successfully")
         | 
| 422 524 | 
             
                            print(f"📊 Dashboard: {dashboard_url}")
         | 
| 423 525 |  | 
| 424 | 
            -
                            # Final verification that server is responsive
         | 
| 526 | 
            +
                            # Final verification that server is responsive using event-based checking
         | 
| 425 527 | 
             
                            final_check_passed = False
         | 
| 426 | 
            -
                             | 
| 528 | 
            +
                            check_start = time.time()
         | 
| 529 | 
            +
                            max_wait = 3  # Maximum 3 seconds
         | 
| 530 | 
            +
                            
         | 
| 531 | 
            +
                            while time.time() - check_start < max_wait:
         | 
| 427 532 | 
             
                                if _check_socketio_server_running(socketio_port, logger):
         | 
| 428 533 | 
             
                                    final_check_passed = True
         | 
| 429 534 | 
             
                                    break
         | 
| 430 | 
            -
                                 | 
| 535 | 
            +
                                # Use a very short sleep just to yield CPU
         | 
| 536 | 
            +
                                time.sleep(0.1)  # 100ms polling interval
         | 
| 431 537 |  | 
| 432 538 | 
             
                            if not final_check_passed:
         | 
| 433 539 | 
             
                                logger.warning("Server started but final connectivity check failed")
         | 
| @@ -513,20 +619,26 @@ def _check_socketio_server_running(port, logger): | |
| 513 619 | 
             
                            else:
         | 
| 514 620 | 
             
                                logger.debug(f"⚠️ HTTP response code {response.getcode()} from port {port} (attempt {retry + 1})")
         | 
| 515 621 | 
             
                                if retry < max_retries - 1:
         | 
| 516 | 
            -
                                     | 
| 622 | 
            +
                                    # Use exponential backoff with shorter initial delay
         | 
| 623 | 
            +
                                    backoff = min(0.1 * (2 ** retry), 1.0)  # 0.1s, 0.2s, 0.4s...
         | 
| 624 | 
            +
                                    time.sleep(backoff)
         | 
| 517 625 |  | 
| 518 626 | 
             
                        except urllib.error.HTTPError as e:
         | 
| 519 627 | 
             
                            logger.debug(f"⚠️ HTTP error {e.code} from server on port {port} (attempt {retry + 1})")
         | 
| 520 628 | 
             
                            if retry < max_retries - 1 and e.code in [404, 503]:  # Server starting but not ready
         | 
| 521 629 | 
             
                                logger.debug("Server appears to be starting, retrying...")
         | 
| 522 | 
            -
                                 | 
| 630 | 
            +
                                # Use exponential backoff for retries
         | 
| 631 | 
            +
                                backoff = min(0.1 * (2 ** retry), 1.0)
         | 
| 632 | 
            +
                                time.sleep(backoff)
         | 
| 523 633 | 
             
                                continue
         | 
| 524 634 | 
             
                            return False
         | 
| 525 635 | 
             
                        except urllib.error.URLError as e:
         | 
| 526 636 | 
             
                            logger.debug(f"⚠️ URL error connecting to port {port} (attempt {retry + 1}): {e.reason}")
         | 
| 527 637 | 
             
                            if retry < max_retries - 1:
         | 
| 528 638 | 
             
                                logger.debug("Connection refused - server may still be initializing, retrying...")
         | 
| 529 | 
            -
                                 | 
| 639 | 
            +
                                # Use exponential backoff for retries
         | 
| 640 | 
            +
                                backoff = min(0.1 * (2 ** retry), 1.0)
         | 
| 641 | 
            +
                                time.sleep(backoff)
         | 
| 530 642 | 
             
                                continue
         | 
| 531 643 | 
             
                            return False
         | 
| 532 644 |  | 
| @@ -585,44 +697,46 @@ def _start_standalone_socketio_server(port, logger): | |
| 585 697 | 
             
                        logger.error(f"Failed to start Socket.IO daemon: {result.stderr}")
         | 
| 586 698 | 
             
                        return False
         | 
| 587 699 |  | 
| 588 | 
            -
                    # Wait for server  | 
| 589 | 
            -
                    # WHY:  | 
| 590 | 
            -
                    #  | 
| 591 | 
            -
                     | 
| 592 | 
            -
                     | 
| 593 | 
            -
                     | 
| 594 | 
            -
                     | 
| 595 | 
            -
                     | 
| 596 | 
            -
                     | 
| 597 | 
            -
                     | 
| 598 | 
            -
                    
         | 
| 599 | 
            -
                     | 
| 600 | 
            -
                    
         | 
| 601 | 
            -
                     | 
| 602 | 
            -
             | 
| 603 | 
            -
             | 
| 604 | 
            -
                    
         | 
| 605 | 
            -
                    for attempt in range(max_attempts):
         | 
| 606 | 
            -
                        # Progressive delay - start fast, then slow down for socket binding
         | 
| 607 | 
            -
                        if attempt < 5:
         | 
| 608 | 
            -
                            delay = initial_delay
         | 
| 609 | 
            -
                        else:
         | 
| 610 | 
            -
                            delay = min(max_delay, initial_delay + (attempt - 5) * 0.2)
         | 
| 700 | 
            +
                    # Wait for server using event-based polling instead of fixed delays
         | 
| 701 | 
            +
                    # WHY: Replace fixed sleep delays with active polling for faster startup detection
         | 
| 702 | 
            +
                    max_wait_time = 15  # Maximum 15 seconds
         | 
| 703 | 
            +
                    poll_interval = 0.1  # Start with 100ms polling
         | 
| 704 | 
            +
                    
         | 
| 705 | 
            +
                    logger.info(f"Waiting up to {max_wait_time} seconds for server to be ready...")
         | 
| 706 | 
            +
                    
         | 
| 707 | 
            +
                    # Give daemon minimal time to fork
         | 
| 708 | 
            +
                    time.sleep(0.2)  # Reduced from 0.5s
         | 
| 709 | 
            +
                    
         | 
| 710 | 
            +
                    start_time = time.time()
         | 
| 711 | 
            +
                    attempt = 0
         | 
| 712 | 
            +
                    
         | 
| 713 | 
            +
                    while time.time() - start_time < max_wait_time:
         | 
| 714 | 
            +
                        attempt += 1
         | 
| 715 | 
            +
                        elapsed = time.time() - start_time
         | 
| 611 716 |  | 
| 612 | 
            -
                        logger.debug(f"Checking server readiness (attempt {attempt | 
| 717 | 
            +
                        logger.debug(f"Checking server readiness (attempt {attempt}, elapsed {elapsed:.1f}s)")
         | 
| 718 | 
            +
                        
         | 
| 719 | 
            +
                        # Adaptive polling - start fast, slow down over time
         | 
| 720 | 
            +
                        if elapsed < 2:
         | 
| 721 | 
            +
                            poll_interval = 0.1  # 100ms for first 2 seconds
         | 
| 722 | 
            +
                        elif elapsed < 5:
         | 
| 723 | 
            +
                            poll_interval = 0.25  # 250ms for next 3 seconds
         | 
| 724 | 
            +
                        else:
         | 
| 725 | 
            +
                            poll_interval = 0.5  # 500ms after 5 seconds
         | 
| 613 726 |  | 
| 614 | 
            -
                         | 
| 615 | 
            -
                        time.sleep(delay)
         | 
| 727 | 
            +
                        time.sleep(poll_interval)
         | 
| 616 728 |  | 
| 617 729 | 
             
                        # Check if the daemon server is accepting connections
         | 
| 618 730 | 
             
                        if _check_socketio_server_running(port, logger):
         | 
| 619 731 | 
             
                            logger.info(f"✅ Standalone Socket.IO server started successfully on port {port}")
         | 
| 620 | 
            -
                            logger.info(f"🕐 Server ready after {attempt | 
| 732 | 
            +
                            logger.info(f"🕐 Server ready after {attempt} attempts ({elapsed:.1f}s)")
         | 
| 621 733 | 
             
                            return True
         | 
| 622 734 | 
             
                        else:
         | 
| 623 | 
            -
                            logger.debug(f"Server not yet accepting connections on attempt {attempt | 
| 735 | 
            +
                            logger.debug(f"Server not yet accepting connections on attempt {attempt}")
         | 
| 624 736 |  | 
| 625 | 
            -
                     | 
| 737 | 
            +
                    # Timeout reached
         | 
| 738 | 
            +
                    elapsed_total = time.time() - start_time
         | 
| 739 | 
            +
                    logger.error(f"❌ Socket.IO server health check failed after {max_wait_time}s timeout ({attempt} attempts)")
         | 
| 626 740 | 
             
                    logger.warning(f"⏱️  Server may still be starting - try waiting a few more seconds")
         | 
| 627 741 | 
             
                    logger.warning(f"💡 The daemon process might be running but not yet accepting HTTP connections")
         | 
| 628 742 | 
             
                    logger.error(f"🔧 Troubleshooting steps:")
         | 
| @@ -689,4 +803,78 @@ def open_in_browser_tab(url, logger): | |
| 689 803 | 
             
                except Exception as e:
         | 
| 690 804 | 
             
                    logger.warning(f"Browser opening failed: {e}")
         | 
| 691 805 | 
             
                    # Final fallback
         | 
| 692 | 
            -
                    webbrowser.open(url)
         | 
| 806 | 
            +
                    webbrowser.open(url)
         | 
| 807 | 
            +
             | 
| 808 | 
            +
             | 
| 809 | 
            +
            def _check_configuration_health(logger):
         | 
| 810 | 
            +
                """Check configuration health at startup and warn about issues.
         | 
| 811 | 
            +
                
         | 
| 812 | 
            +
                WHY: Configuration errors can cause silent failures, especially for response
         | 
| 813 | 
            +
                logging. This function proactively checks configuration at startup and warns
         | 
| 814 | 
            +
                users about any issues, providing actionable guidance.
         | 
| 815 | 
            +
                
         | 
| 816 | 
            +
                DESIGN DECISIONS:
         | 
| 817 | 
            +
                - Non-blocking: Issues are logged as warnings, not errors
         | 
| 818 | 
            +
                - Actionable: Provides specific commands to fix issues
         | 
| 819 | 
            +
                - Focused: Only checks critical configuration that affects runtime
         | 
| 820 | 
            +
                
         | 
| 821 | 
            +
                Args:
         | 
| 822 | 
            +
                    logger: Logger instance for output
         | 
| 823 | 
            +
                """
         | 
| 824 | 
            +
                try:
         | 
| 825 | 
            +
                    # Load configuration
         | 
| 826 | 
            +
                    config = Config()
         | 
| 827 | 
            +
                    
         | 
| 828 | 
            +
                    # Validate configuration
         | 
| 829 | 
            +
                    is_valid, errors, warnings = config.validate_configuration()
         | 
| 830 | 
            +
                    
         | 
| 831 | 
            +
                    # Get configuration status for additional context
         | 
| 832 | 
            +
                    status = config.get_configuration_status()
         | 
| 833 | 
            +
                    
         | 
| 834 | 
            +
                    # Report critical errors that will affect functionality
         | 
| 835 | 
            +
                    if errors:
         | 
| 836 | 
            +
                        logger.warning("⚠️  Configuration issues detected:")
         | 
| 837 | 
            +
                        for error in errors[:3]:  # Show first 3 errors
         | 
| 838 | 
            +
                            logger.warning(f"  • {error}")
         | 
| 839 | 
            +
                        if len(errors) > 3:
         | 
| 840 | 
            +
                            logger.warning(f"  • ... and {len(errors) - 3} more")
         | 
| 841 | 
            +
                        logger.info("💡 Run 'claude-mpm config validate' to see all issues and fixes")
         | 
| 842 | 
            +
                        
         | 
| 843 | 
            +
                    # Check response logging specifically since it's commonly misconfigured
         | 
| 844 | 
            +
                    response_logging_enabled = config.get('response_logging.enabled', False)
         | 
| 845 | 
            +
                    if not response_logging_enabled:
         | 
| 846 | 
            +
                        logger.debug("Response logging is disabled (response_logging.enabled=false)")
         | 
| 847 | 
            +
                    else:
         | 
| 848 | 
            +
                        # Check if session directory is writable
         | 
| 849 | 
            +
                        session_dir = Path(config.get('response_logging.session_directory', '.claude-mpm/responses'))
         | 
| 850 | 
            +
                        if not session_dir.is_absolute():
         | 
| 851 | 
            +
                            session_dir = Path.cwd() / session_dir
         | 
| 852 | 
            +
                            
         | 
| 853 | 
            +
                        if not session_dir.exists():
         | 
| 854 | 
            +
                            try:
         | 
| 855 | 
            +
                                session_dir.mkdir(parents=True, exist_ok=True)
         | 
| 856 | 
            +
                                logger.debug(f"Created response logging directory: {session_dir}")
         | 
| 857 | 
            +
                            except Exception as e:
         | 
| 858 | 
            +
                                logger.warning(f"Cannot create response logging directory {session_dir}: {e}")
         | 
| 859 | 
            +
                                logger.info("💡 Fix with: mkdir -p " + str(session_dir))
         | 
| 860 | 
            +
                        elif not os.access(session_dir, os.W_OK):
         | 
| 861 | 
            +
                            logger.warning(f"Response logging directory is not writable: {session_dir}")
         | 
| 862 | 
            +
                            logger.info("💡 Fix with: chmod 755 " + str(session_dir))
         | 
| 863 | 
            +
                            
         | 
| 864 | 
            +
                    # Report non-critical warnings (only in debug mode)
         | 
| 865 | 
            +
                    if warnings and logger.isEnabledFor(logging.DEBUG):
         | 
| 866 | 
            +
                        logger.debug("Configuration warnings:")
         | 
| 867 | 
            +
                        for warning in warnings:
         | 
| 868 | 
            +
                            logger.debug(f"  • {warning}")
         | 
| 869 | 
            +
                            
         | 
| 870 | 
            +
                    # Log loaded configuration source for debugging
         | 
| 871 | 
            +
                    if status.get('loaded_from') and status['loaded_from'] != 'defaults':
         | 
| 872 | 
            +
                        logger.debug(f"Configuration loaded from: {status['loaded_from']}")
         | 
| 873 | 
            +
                        
         | 
| 874 | 
            +
                except Exception as e:
         | 
| 875 | 
            +
                    # Don't let configuration check errors prevent startup
         | 
| 876 | 
            +
                    logger.debug(f"Configuration check failed (non-critical): {e}")
         | 
| 877 | 
            +
                    # Only show user-facing message if it's likely to affect them
         | 
| 878 | 
            +
                    if "yaml" in str(e).lower():
         | 
| 879 | 
            +
                        logger.warning("⚠️  Configuration file may have YAML syntax errors")
         | 
| 880 | 
            +
                        logger.info("💡 Validate with: claude-mpm config validate")
         |