claude-mpm 3.1.2__py3-none-any.whl → 3.1.3__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/__main__.py +17 -0
 - claude_mpm/agents/INSTRUCTIONS.md +17 -2
 - claude_mpm/cli/__init__.py +23 -14
 - claude_mpm/cli/commands/agents.py +18 -7
 - claude_mpm/cli/commands/info.py +10 -5
 - claude_mpm/cli/commands/run.py +22 -7
 - claude_mpm/cli/commands/tickets.py +17 -10
 - claude_mpm/cli/commands/ui.py +37 -15
 - claude_mpm/cli/utils.py +28 -9
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +17 -27
 - claude_mpm/hooks/claude_hooks/hook_handler.py +53 -4
 - claude_mpm/models/__init__.py +91 -9
 - claude_mpm/models/common.py +41 -0
 - claude_mpm/models/lifecycle.py +97 -0
 - claude_mpm/models/modification.py +126 -0
 - claude_mpm/models/persistence.py +57 -0
 - claude_mpm/models/registry.py +91 -0
 - claude_mpm/security/__init__.py +8 -0
 - claude_mpm/security/bash_validator.py +393 -0
 - claude_mpm/services/agent_lifecycle_manager.py +25 -76
 - claude_mpm/services/agent_modification_tracker.py +17 -98
 - claude_mpm/services/agent_persistence_service.py +13 -33
 - claude_mpm/services/agent_registry.py +43 -82
 - claude_mpm/services/{ticketing_service_original.py → legacy_ticketing_service.py} +16 -9
 - claude_mpm/services/ticket_manager.py +5 -4
 - claude_mpm/services/{ticket_manager_di.py → ticket_manager_dependency_injection.py} +39 -12
 - claude_mpm/services/version_control/semantic_versioning.py +10 -9
 - claude_mpm/utils/path_operations.py +20 -0
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/METADATA +9 -1
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/RECORD +37 -31
 - claude_mpm/utils/import_migration_example.py +0 -80
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/entry_points.txt +0 -0
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.2.dist-info → claude_mpm-3.1.3.dist-info}/top_level.txt +0 -0
 
    
        claude_mpm/__main__.py
    CHANGED
    
    | 
         @@ -8,6 +8,7 @@ DESIGN DECISION: We only import and call the main function from the CLI module, 
     | 
|
| 
       8 
8 
     | 
    
         
             
            keeping this file minimal and focused on its single responsibility.
         
     | 
| 
       9 
9 
     | 
    
         
             
            """
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
      
 11 
     | 
    
         
            +
            import os
         
     | 
| 
       11 
12 
     | 
    
         
             
            import sys
         
     | 
| 
       12 
13 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
         @@ -17,5 +18,21 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) 
     | 
|
| 
       17 
18 
     | 
    
         
             
            # Import main function from the new CLI module structure
         
     | 
| 
       18 
19 
     | 
    
         
             
            from claude_mpm.cli import main
         
     | 
| 
       19 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
            # Restore user's working directory if preserved by bash wrapper
         
     | 
| 
      
 22 
     | 
    
         
            +
            # WHY: The bash wrapper preserves the user's launch directory in CLAUDE_MPM_USER_PWD
         
     | 
| 
      
 23 
     | 
    
         
            +
            # to maintain proper file access permissions and security boundaries.
         
     | 
| 
      
 24 
     | 
    
         
            +
            # Python imports work via PYTHONPATH, so we can safely restore the original directory.
         
     | 
| 
       20 
25 
     | 
    
         
             
            if __name__ == "__main__":
         
     | 
| 
      
 26 
     | 
    
         
            +
                user_pwd = os.environ.get('CLAUDE_MPM_USER_PWD')
         
     | 
| 
      
 27 
     | 
    
         
            +
                if user_pwd and os.path.exists(user_pwd):
         
     | 
| 
      
 28 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 29 
     | 
    
         
            +
                        os.chdir(user_pwd)
         
     | 
| 
      
 30 
     | 
    
         
            +
                        # Only log if debug is enabled
         
     | 
| 
      
 31 
     | 
    
         
            +
                        if os.environ.get('CLAUDE_MPM_DEBUG') == '1':
         
     | 
| 
      
 32 
     | 
    
         
            +
                            print(f"[INFO] Restored working directory to: {user_pwd}")
         
     | 
| 
      
 33 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 34 
     | 
    
         
            +
                        # If we can't change to user directory, continue but log warning
         
     | 
| 
      
 35 
     | 
    
         
            +
                        if os.environ.get('CLAUDE_MPM_DEBUG') == '1':
         
     | 
| 
      
 36 
     | 
    
         
            +
                            print(f"[WARNING] Could not restore working directory to {user_pwd}: {e}")
         
     | 
| 
      
 37 
     | 
    
         
            +
                
         
     | 
| 
       21 
38 
     | 
    
         
             
                sys.exit(main())
         
     | 
| 
         @@ -4,15 +4,30 @@ 
     | 
|
| 
       4 
4 
     | 
    
         
             
            # Claude Multi-Agent Project Manager Instructions
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            ## Core Identity & Authority
         
     | 
| 
       7 
     | 
    
         
            -
            You are **Claude Multi-Agent Project Manager (claude-mpm)** - your **SOLE function** is **orchestration and delegation**.  
     | 
| 
      
 7 
     | 
    
         
            +
            You are **Claude Multi-Agent Project Manager (claude-mpm)** - your **SOLE function** is **orchestration and delegation**. 
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ### Permitted Actions (Exhaustive List)
         
     | 
| 
       8 
10 
     | 
    
         
             
            - **Task Tool** for delegation (primary function)
         
     | 
| 
       9 
11 
     | 
    
         
             
            - **TodoWrite** for tracking (with [Agent] prefixes)
         
     | 
| 
       10 
12 
     | 
    
         
             
            - **WebSearch/WebFetch** only for delegation requirements
         
     | 
| 
       11 
13 
     | 
    
         
             
            - **Direct answers** for PM role/capability questions only
         
     | 
| 
       12 
     | 
    
         
            -
            - **Direct work** only when explicitly authorized: "do this yourself", "don't delegate", "implement directly"
         
     | 
| 
      
 14 
     | 
    
         
            +
            - **Direct work** only when explicitly authorized with phrases like: "do this yourself", "don't delegate", "implement directly"
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
            **ABSOLUTE RULE**: ALL other work must be delegated to specialized agents via Task Tool.
         
     | 
| 
       15 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
            ### Professional Standards
         
     | 
| 
      
 19 
     | 
    
         
            +
            - **Clarity Threshold**: Achieve 90% certainty of success before delegating. Ask clarifying questions when needed.
         
     | 
| 
      
 20 
     | 
    
         
            +
            - **Agent Standards**: Require the same 90% clarity threshold from your agents before they proceed.
         
     | 
| 
      
 21 
     | 
    
         
            +
            - **Project Validation**: Verify that requested tasks align with project context and question any incongruities.
         
     | 
| 
      
 22 
     | 
    
         
            +
            - **Communication Style**: 
         
     | 
| 
      
 23 
     | 
    
         
            +
              - Avoid unnecessary affirmation or praise
         
     | 
| 
      
 24 
     | 
    
         
            +
              - Never say "You're right!" (or similar) unless correctness was in question and validated by agent authority
         
     | 
| 
      
 25 
     | 
    
         
            +
              - Focus on substance over pleasantries
         
     | 
| 
      
 26 
     | 
    
         
            +
              - Ask direct questions when clarity is needed
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ### Your Responsibility
         
     | 
| 
      
 29 
     | 
    
         
            +
            It is your **JOB** to ensure sufficient clarity before delegation. Set high standards for both yourself and your agents to ensure successful outcomes.
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
       16 
31 
     | 
    
         
             
            ## Context-Aware Agent Selection
         
     | 
| 
       17 
32 
     | 
    
         
             
            - **PM role/capabilities questions**: Answer directly (only exception)
         
     | 
| 
       18 
33 
     | 
    
         
             
            - **Explanations/How-to questions**: Delegate to Documentation Agent
         
     | 
    
        claude_mpm/cli/__init__.py
    CHANGED
    
    | 
         @@ -10,30 +10,35 @@ interface while organizing code into logical modules. The main() function 
     | 
|
| 
       10 
10 
     | 
    
         
             
            remains the primary entry point for both direct execution and package imports.
         
     | 
| 
       11 
11 
     | 
    
         
             
            """
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
      
 13 
     | 
    
         
            +
            import os
         
     | 
| 
       13 
14 
     | 
    
         
             
            import sys
         
     | 
| 
       14 
15 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       15 
16 
     | 
    
         
             
            from typing import Optional
         
     | 
| 
       16 
17 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
            from  
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
            )
         
     | 
| 
      
 18 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            # Import constants and utilities using safe_import pattern
         
     | 
| 
      
 21 
     | 
    
         
            +
            CLICommands, LogLevel = safe_import('claude_mpm.constants', None, ['CLICommands', 'LogLevel'])
         
     | 
| 
      
 22 
     | 
    
         
            +
            create_parser, preprocess_args = safe_import('claude_mpm.cli.parser', None, ['create_parser', 'preprocess_args'])
         
     | 
| 
      
 23 
     | 
    
         
            +
            ensure_directories, setup_logging = safe_import('claude_mpm.cli.utils', None, ['ensure_directories', 'setup_logging'])
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            # Import commands
         
     | 
| 
      
 26 
     | 
    
         
            +
            run_session = safe_import('claude_mpm.cli.commands', None, ['run_session'])
         
     | 
| 
      
 27 
     | 
    
         
            +
            list_tickets = safe_import('claude_mpm.cli.commands', None, ['list_tickets'])
         
     | 
| 
      
 28 
     | 
    
         
            +
            show_info = safe_import('claude_mpm.cli.commands', None, ['show_info'])
         
     | 
| 
      
 29 
     | 
    
         
            +
            manage_agents = safe_import('claude_mpm.cli.commands', None, ['manage_agents'])
         
     | 
| 
      
 30 
     | 
    
         
            +
            run_terminal_ui = safe_import('claude_mpm.cli.commands', None, ['run_terminal_ui'])
         
     | 
| 
       27 
31 
     | 
    
         | 
| 
       28 
32 
     | 
    
         
             
            # Get version from VERSION file - single source of truth
         
     | 
| 
       29 
33 
     | 
    
         
             
            version_file = Path(__file__).parent.parent.parent / "VERSION"
         
     | 
| 
       30 
34 
     | 
    
         
             
            if version_file.exists():
         
     | 
| 
       31 
35 
     | 
    
         
             
                __version__ = version_file.read_text().strip()
         
     | 
| 
       32 
36 
     | 
    
         
             
            else:
         
     | 
| 
       33 
     | 
    
         
            -
                # Try to import from package as fallback
         
     | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                # Try to import from package as fallback using safe_import
         
     | 
| 
      
 38 
     | 
    
         
            +
                version_module = safe_import('claude_mpm', None)
         
     | 
| 
      
 39 
     | 
    
         
            +
                if version_module and hasattr(version_module, '__version__'):
         
     | 
| 
      
 40 
     | 
    
         
            +
                    __version__ = version_module.__version__
         
     | 
| 
      
 41 
     | 
    
         
            +
                else:
         
     | 
| 
       37 
42 
     | 
    
         
             
                    # Default version if all else fails
         
     | 
| 
       38 
43 
     | 
    
         
             
                    __version__ = "0.0.0"
         
     | 
| 
       39 
44 
     | 
    
         | 
| 
         @@ -75,6 +80,10 @@ def main(argv: Optional[list] = None): 
     | 
|
| 
       75 
80 
     | 
    
         
             
                if hasattr(args, 'debug') and args.debug:
         
     | 
| 
       76 
81 
     | 
    
         
             
                    logger.debug(f"Command: {args.command}")
         
     | 
| 
       77 
82 
     | 
    
         
             
                    logger.debug(f"Arguments: {args}")
         
     | 
| 
      
 83 
     | 
    
         
            +
                    # Log working directory context
         
     | 
| 
      
 84 
     | 
    
         
            +
                    logger.debug(f"Working directory: {os.getcwd()}")
         
     | 
| 
      
 85 
     | 
    
         
            +
                    logger.debug(f"User PWD (from env): {os.environ.get('CLAUDE_MPM_USER_PWD', 'Not set')}")
         
     | 
| 
      
 86 
     | 
    
         
            +
                    logger.debug(f"Framework path: {os.environ.get('CLAUDE_MPM_FRAMEWORK_PATH', 'Not set')}")
         
     | 
| 
       78 
87 
     | 
    
         | 
| 
       79 
88 
     | 
    
         
             
                # Hook system note: Claude Code hooks are handled externally via the
         
     | 
| 
       80 
89 
     | 
    
         
             
                # hook_handler.py script installed in ~/.claude/settings.json
         
     | 
| 
         @@ -7,9 +7,12 @@ and cleaning agent deployments. 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
            from  
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 10 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            # Import dependencies using safe_import pattern
         
     | 
| 
      
 13 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
      
 14 
     | 
    
         
            +
            AgentCommands = safe_import('claude_mpm.constants', None, ['AgentCommands'])
         
     | 
| 
      
 15 
     | 
    
         
            +
            get_agent_versions_display = safe_import('claude_mpm.cli.utils', None, ['get_agent_versions_display'])
         
     | 
| 
       13 
16 
     | 
    
         | 
| 
       14 
17 
     | 
    
         | 
| 
       15 
18 
     | 
    
         
             
            def manage_agents(args):
         
     | 
| 
         @@ -27,8 +30,19 @@ def manage_agents(args): 
     | 
|
| 
       27 
30 
     | 
    
         
             
                """
         
     | 
| 
       28 
31 
     | 
    
         
             
                logger = get_logger("cli")
         
     | 
| 
       29 
32 
     | 
    
         | 
| 
      
 33 
     | 
    
         
            +
                # Import AgentDeploymentService using safe_import pattern
         
     | 
| 
      
 34 
     | 
    
         
            +
                AgentDeploymentService = safe_import(
         
     | 
| 
      
 35 
     | 
    
         
            +
                    'claude_mpm.services.agent_deployment',
         
     | 
| 
      
 36 
     | 
    
         
            +
                    None,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    ['AgentDeploymentService']
         
     | 
| 
      
 38 
     | 
    
         
            +
                )
         
     | 
| 
      
 39 
     | 
    
         
            +
                
         
     | 
| 
      
 40 
     | 
    
         
            +
                if not AgentDeploymentService:
         
     | 
| 
      
 41 
     | 
    
         
            +
                    logger.error("Agent deployment service not available")
         
     | 
| 
      
 42 
     | 
    
         
            +
                    print("Error: Agent deployment service not available")
         
     | 
| 
      
 43 
     | 
    
         
            +
                    return
         
     | 
| 
      
 44 
     | 
    
         
            +
                
         
     | 
| 
       30 
45 
     | 
    
         
             
                try:
         
     | 
| 
       31 
     | 
    
         
            -
                    from ...services.agent_deployment import AgentDeploymentService
         
     | 
| 
       32 
46 
     | 
    
         
             
                    deployment_service = AgentDeploymentService()
         
     | 
| 
       33 
47 
     | 
    
         | 
| 
       34 
48 
     | 
    
         
             
                    if not args.agents_command:
         
     | 
| 
         @@ -55,9 +69,6 @@ def manage_agents(args): 
     | 
|
| 
       55 
69 
     | 
    
         
             
                    elif args.agents_command == AgentCommands.CLEAN.value:
         
     | 
| 
       56 
70 
     | 
    
         
             
                        _clean_agents(args, deployment_service)
         
     | 
| 
       57 
71 
     | 
    
         | 
| 
       58 
     | 
    
         
            -
                except ImportError:
         
     | 
| 
       59 
     | 
    
         
            -
                    logger.error("Agent deployment service not available")
         
     | 
| 
       60 
     | 
    
         
            -
                    print("Error: Agent deployment service not available")
         
     | 
| 
       61 
72 
     | 
    
         
             
                except Exception as e:
         
     | 
| 
       62 
73 
     | 
    
         
             
                    logger.error(f"Error managing agents: {e}")
         
     | 
| 
       63 
74 
     | 
    
         
             
                    print(f"Error: {e}")
         
     | 
    
        claude_mpm/cli/commands/info.py
    CHANGED
    
    | 
         @@ -8,7 +8,10 @@ users understand their claude-mpm setup and troubleshoot issues. 
     | 
|
| 
       8 
8 
     | 
    
         
             
            import shutil
         
     | 
| 
       9 
9 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
            from  
     | 
| 
      
 11 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            # Import logger using safe_import pattern
         
     | 
| 
      
 14 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
       12 
15 
     | 
    
         | 
| 
       13 
16 
     | 
    
         | 
| 
       14 
17 
     | 
    
         
             
            def show_info(args):
         
     | 
| 
         @@ -25,10 +28,12 @@ def show_info(args): 
     | 
|
| 
       25 
28 
     | 
    
         
             
                Args:
         
     | 
| 
       26 
29 
     | 
    
         
             
                    args: Parsed command line arguments
         
     | 
| 
       27 
30 
     | 
    
         
             
                """
         
     | 
| 
       28 
     | 
    
         
            -
                 
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                     
     | 
| 
      
 31 
     | 
    
         
            +
                # Import FrameworkLoader using safe_import pattern
         
     | 
| 
      
 32 
     | 
    
         
            +
                FrameworkLoader = safe_import(
         
     | 
| 
      
 33 
     | 
    
         
            +
                    'claude_mpm.core.framework_loader',
         
     | 
| 
      
 34 
     | 
    
         
            +
                    None,
         
     | 
| 
      
 35 
     | 
    
         
            +
                    ['FrameworkLoader']
         
     | 
| 
      
 36 
     | 
    
         
            +
                )
         
     | 
| 
       32 
37 
     | 
    
         | 
| 
       33 
38 
     | 
    
         
             
                print("Claude MPM - Multi-Agent Project Manager")
         
     | 
| 
       34 
39 
     | 
    
         
             
                print("=" * 50)
         
     | 
    
        claude_mpm/cli/commands/run.py
    CHANGED
    
    | 
         @@ -9,9 +9,16 @@ import subprocess 
     | 
|
| 
       9 
9 
     | 
    
         
             
            import sys
         
     | 
| 
       10 
10 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            from  
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            # Import with safe_import pattern for better error handling
         
     | 
| 
      
 15 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
      
 16 
     | 
    
         
            +
            LogLevel = safe_import('claude_mpm.constants', None, ['LogLevel'])
         
     | 
| 
      
 17 
     | 
    
         
            +
            get_user_input, list_agent_versions_at_startup = safe_import(
         
     | 
| 
      
 18 
     | 
    
         
            +
                'claude_mpm.cli.utils', 
         
     | 
| 
      
 19 
     | 
    
         
            +
                None, 
         
     | 
| 
      
 20 
     | 
    
         
            +
                ['get_user_input', 'list_agent_versions_at_startup']
         
     | 
| 
      
 21 
     | 
    
         
            +
            )
         
     | 
| 
       15 
22 
     | 
    
         | 
| 
       16 
23 
     | 
    
         | 
| 
       17 
24 
     | 
    
         
             
            def run_session(args):
         
     | 
| 
         @@ -28,14 +35,22 @@ def run_session(args): 
     | 
|
| 
       28 
35 
     | 
    
         
             
                Args:
         
     | 
| 
       29 
36 
     | 
    
         
             
                    args: Parsed command line arguments
         
     | 
| 
       30 
37 
     | 
    
         
             
                """
         
     | 
| 
      
 38 
     | 
    
         
            +
                import os
         
     | 
| 
       31 
39 
     | 
    
         
             
                logger = get_logger("cli")
         
     | 
| 
       32 
40 
     | 
    
         
             
                if args.logging != LogLevel.OFF.value:
         
     | 
| 
       33 
41 
     | 
    
         
             
                    logger.info("Starting Claude MPM session")
         
     | 
| 
      
 42 
     | 
    
         
            +
                    # Log working directory context
         
     | 
| 
      
 43 
     | 
    
         
            +
                    logger.info(f"Working directory: {os.getcwd()}")
         
     | 
| 
      
 44 
     | 
    
         
            +
                    if args.debug:
         
     | 
| 
      
 45 
     | 
    
         
            +
                        logger.debug(f"User PWD (from env): {os.environ.get('CLAUDE_MPM_USER_PWD', 'Not set')}")
         
     | 
| 
      
 46 
     | 
    
         
            +
                        logger.debug(f"Framework path: {os.environ.get('CLAUDE_MPM_FRAMEWORK_PATH', 'Not set')}")
         
     | 
| 
       34 
47 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                 
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                     
     | 
| 
      
 48 
     | 
    
         
            +
                # Import SimpleClaudeRunner using safe_import pattern
         
     | 
| 
      
 49 
     | 
    
         
            +
                SimpleClaudeRunner, create_simple_context = safe_import(
         
     | 
| 
      
 50 
     | 
    
         
            +
                    'claude_mpm.core.simple_runner',
         
     | 
| 
      
 51 
     | 
    
         
            +
                    None,
         
     | 
| 
      
 52 
     | 
    
         
            +
                    ['SimpleClaudeRunner', 'create_simple_context']
         
     | 
| 
      
 53 
     | 
    
         
            +
                )
         
     | 
| 
       39 
54 
     | 
    
         | 
| 
       40 
55 
     | 
    
         
             
                # Skip native agents if disabled
         
     | 
| 
       41 
56 
     | 
    
         
             
                if getattr(args, 'no_native_agents', False):
         
     | 
| 
         @@ -5,7 +5,10 @@ WHY: This module handles ticket listing functionality, allowing users to view 
     | 
|
| 
       5 
5 
     | 
    
         
             
            recent tickets created during Claude sessions.
         
     | 
| 
       6 
6 
     | 
    
         
             
            """
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            from  
     | 
| 
      
 8 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            # Import logger using safe_import pattern
         
     | 
| 
      
 11 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
       9 
12 
     | 
    
         | 
| 
       10 
13 
     | 
    
         | 
| 
       11 
14 
     | 
    
         
             
            def list_tickets(args):
         
     | 
| 
         @@ -24,12 +27,20 @@ def list_tickets(args): 
     | 
|
| 
       24 
27 
     | 
    
         
             
                """
         
     | 
| 
       25 
28 
     | 
    
         
             
                logger = get_logger("cli")
         
     | 
| 
       26 
29 
     | 
    
         | 
| 
      
 30 
     | 
    
         
            +
                # Import TicketManager using safe_import pattern
         
     | 
| 
      
 31 
     | 
    
         
            +
                TicketManager = safe_import(
         
     | 
| 
      
 32 
     | 
    
         
            +
                    '...services.ticket_manager',
         
     | 
| 
      
 33 
     | 
    
         
            +
                    'claude_mpm.services.ticket_manager',
         
     | 
| 
      
 34 
     | 
    
         
            +
                    ['TicketManager']
         
     | 
| 
      
 35 
     | 
    
         
            +
                )
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                if not TicketManager:
         
     | 
| 
      
 38 
     | 
    
         
            +
                    logger.error("ai-trackdown-pytools not installed")
         
     | 
| 
      
 39 
     | 
    
         
            +
                    print("Error: ai-trackdown-pytools not installed")
         
     | 
| 
      
 40 
     | 
    
         
            +
                    print("Install with: pip install ai-trackdown-pytools")
         
     | 
| 
      
 41 
     | 
    
         
            +
                    return
         
     | 
| 
      
 42 
     | 
    
         
            +
                
         
     | 
| 
       27 
43 
     | 
    
         
             
                try:
         
     | 
| 
       28 
     | 
    
         
            -
                    try:
         
     | 
| 
       29 
     | 
    
         
            -
                        from ...services.ticket_manager import TicketManager
         
     | 
| 
       30 
     | 
    
         
            -
                    except ImportError:
         
     | 
| 
       31 
     | 
    
         
            -
                        from claude_mpm.services.ticket_manager import TicketManager
         
     | 
| 
       32 
     | 
    
         
            -
                    
         
     | 
| 
       33 
44 
     | 
    
         
             
                    ticket_manager = TicketManager()
         
     | 
| 
       34 
45 
     | 
    
         
             
                    tickets = ticket_manager.list_recent_tickets(limit=args.limit)
         
     | 
| 
       35 
46 
     | 
    
         | 
| 
         @@ -54,10 +65,6 @@ def list_tickets(args): 
     | 
|
| 
       54 
65 
     | 
    
         
             
                        print(f"   Created: {ticket['created_at']}")
         
     | 
| 
       55 
66 
     | 
    
         
             
                        print()
         
     | 
| 
       56 
67 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                except ImportError:
         
     | 
| 
       58 
     | 
    
         
            -
                    logger.error("ai-trackdown-pytools not installed")
         
     | 
| 
       59 
     | 
    
         
            -
                    print("Error: ai-trackdown-pytools not installed")
         
     | 
| 
       60 
     | 
    
         
            -
                    print("Install with: pip install ai-trackdown-pytools")
         
     | 
| 
       61 
68 
     | 
    
         
             
                except Exception as e:
         
     | 
| 
       62 
69 
     | 
    
         
             
                    logger.error(f"Error listing tickets: {e}")
         
     | 
| 
       63 
70 
     | 
    
         
             
                    print(f"Error: {e}")
         
     | 
    
        claude_mpm/cli/commands/ui.py
    CHANGED
    
    | 
         @@ -5,7 +5,10 @@ WHY: This module provides terminal UI functionality for users who prefer a 
     | 
|
| 
       5 
5 
     | 
    
         
             
            visual interface over command-line interaction.
         
     | 
| 
       6 
6 
     | 
    
         
             
            """
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            from  
     | 
| 
      
 8 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            # Import logger using safe_import pattern
         
     | 
| 
      
 11 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
       9 
12 
     | 
    
         | 
| 
       10 
13 
     | 
    
         | 
| 
       11 
14 
     | 
    
         
             
            def run_terminal_ui(args):
         
     | 
| 
         @@ -29,26 +32,45 @@ def run_terminal_ui(args): 
     | 
|
| 
       29 
32 
     | 
    
         | 
| 
       30 
33 
     | 
    
         
             
                try:
         
     | 
| 
       31 
34 
     | 
    
         
             
                    if ui_mode == 'terminal':
         
     | 
| 
       32 
     | 
    
         
            -
                        # Try rich UI first
         
     | 
| 
       33 
     | 
    
         
            -
                         
     | 
| 
       34 
     | 
    
         
            -
                             
     | 
| 
      
 35 
     | 
    
         
            +
                        # Try rich UI first using safe_import
         
     | 
| 
      
 36 
     | 
    
         
            +
                        run_rich_ui = safe_import(
         
     | 
| 
      
 37 
     | 
    
         
            +
                            '...ui.rich_terminal_ui',
         
     | 
| 
      
 38 
     | 
    
         
            +
                            'claude_mpm.ui.rich_terminal_ui',
         
     | 
| 
      
 39 
     | 
    
         
            +
                            ['main']
         
     | 
| 
      
 40 
     | 
    
         
            +
                        )
         
     | 
| 
      
 41 
     | 
    
         
            +
                        
         
     | 
| 
      
 42 
     | 
    
         
            +
                        if run_rich_ui:
         
     | 
| 
       35 
43 
     | 
    
         
             
                            logger.info("Starting rich terminal UI...")
         
     | 
| 
       36 
44 
     | 
    
         
             
                            run_rich_ui()
         
     | 
| 
       37 
     | 
    
         
            -
                         
     | 
| 
      
 45 
     | 
    
         
            +
                        else:
         
     | 
| 
       38 
46 
     | 
    
         
             
                            # Fallback to curses UI
         
     | 
| 
       39 
47 
     | 
    
         
             
                            logger.info("Rich not available, falling back to curses UI...")
         
     | 
| 
       40 
     | 
    
         
            -
                             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 48 
     | 
    
         
            +
                            TerminalUI = safe_import(
         
     | 
| 
      
 49 
     | 
    
         
            +
                                '...ui.terminal_ui',
         
     | 
| 
      
 50 
     | 
    
         
            +
                                'claude_mpm.ui.terminal_ui',
         
     | 
| 
      
 51 
     | 
    
         
            +
                                ['TerminalUI']
         
     | 
| 
      
 52 
     | 
    
         
            +
                            )
         
     | 
| 
      
 53 
     | 
    
         
            +
                            if TerminalUI:
         
     | 
| 
      
 54 
     | 
    
         
            +
                                ui = TerminalUI()
         
     | 
| 
      
 55 
     | 
    
         
            +
                                ui.run()
         
     | 
| 
      
 56 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 57 
     | 
    
         
            +
                                logger.error("UI module not found")
         
     | 
| 
      
 58 
     | 
    
         
            +
                                print("Error: Terminal UI requires 'curses' (built-in) or 'rich' (pip install rich)")
         
     | 
| 
      
 59 
     | 
    
         
            +
                                return 1
         
     | 
| 
       43 
60 
     | 
    
         
             
                    else:
         
     | 
| 
       44 
61 
     | 
    
         
             
                        # Use curses UI explicitly
         
     | 
| 
       45 
     | 
    
         
            -
                         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
      
 62 
     | 
    
         
            +
                        TerminalUI = safe_import(
         
     | 
| 
      
 63 
     | 
    
         
            +
                            '...ui.terminal_ui',
         
     | 
| 
      
 64 
     | 
    
         
            +
                            'claude_mpm.ui.terminal_ui',
         
     | 
| 
      
 65 
     | 
    
         
            +
                            ['TerminalUI']
         
     | 
| 
      
 66 
     | 
    
         
            +
                        )
         
     | 
| 
      
 67 
     | 
    
         
            +
                        if TerminalUI:
         
     | 
| 
      
 68 
     | 
    
         
            +
                            ui = TerminalUI()
         
     | 
| 
      
 69 
     | 
    
         
            +
                            ui.run()
         
     | 
| 
      
 70 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 71 
     | 
    
         
            +
                            logger.error("UI module not found")
         
     | 
| 
      
 72 
     | 
    
         
            +
                            print("Error: Terminal UI not available")
         
     | 
| 
      
 73 
     | 
    
         
            +
                            return 1
         
     | 
| 
       52 
74 
     | 
    
         
             
                except Exception as e:
         
     | 
| 
       53 
75 
     | 
    
         
             
                    logger.error(f"Error running terminal UI: {e}")
         
     | 
| 
       54 
76 
     | 
    
         
             
                    print(f"Error: {e}")
         
     | 
    
        claude_mpm/cli/utils.py
    CHANGED
    
    | 
         @@ -10,7 +10,10 @@ import sys 
     | 
|
| 
       10 
10 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       11 
11 
     | 
    
         
             
            from typing import Optional
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
            from  
     | 
| 
      
 13 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            # Import logger using safe_import pattern
         
     | 
| 
      
 16 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
       14 
17 
     | 
    
         | 
| 
       15 
18 
     | 
    
         | 
| 
       16 
19 
     | 
    
         
             
            def get_user_input(input_arg: Optional[str], logger) -> str:
         
     | 
| 
         @@ -59,8 +62,17 @@ def get_agent_versions_display() -> Optional[str]: 
     | 
|
| 
       59 
62 
     | 
    
         
             
                Returns:
         
     | 
| 
       60 
63 
     | 
    
         
             
                    Formatted string containing agent version information, or None if failed
         
     | 
| 
       61 
64 
     | 
    
         
             
                """
         
     | 
| 
      
 65 
     | 
    
         
            +
                # Import AgentDeploymentService using safe_import pattern
         
     | 
| 
      
 66 
     | 
    
         
            +
                AgentDeploymentService = safe_import(
         
     | 
| 
      
 67 
     | 
    
         
            +
                    'claude_mpm.services.agent_deployment',
         
     | 
| 
      
 68 
     | 
    
         
            +
                    None,
         
     | 
| 
      
 69 
     | 
    
         
            +
                    ['AgentDeploymentService']
         
     | 
| 
      
 70 
     | 
    
         
            +
                )
         
     | 
| 
      
 71 
     | 
    
         
            +
                
         
     | 
| 
      
 72 
     | 
    
         
            +
                if not AgentDeploymentService:
         
     | 
| 
      
 73 
     | 
    
         
            +
                    return None
         
     | 
| 
      
 74 
     | 
    
         
            +
                    
         
     | 
| 
       62 
75 
     | 
    
         
             
                try:
         
     | 
| 
       63 
     | 
    
         
            -
                    from ..services.agent_deployment import AgentDeploymentService
         
     | 
| 
       64 
76 
     | 
    
         
             
                    deployment_service = AgentDeploymentService()
         
     | 
| 
       65 
77 
     | 
    
         | 
| 
       66 
78 
     | 
    
         
             
                    # Get deployed agents
         
     | 
| 
         @@ -162,10 +174,17 @@ def ensure_directories() -> None: 
     | 
|
| 
       162 
174 
     | 
    
         
             
                failing when they don't exist, we create them automatically for a better
         
     | 
| 
       163 
175 
     | 
    
         
             
                user experience.
         
     | 
| 
       164 
176 
     | 
    
         
             
                """
         
     | 
| 
       165 
     | 
    
         
            -
                 
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
                     
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
                     
     | 
| 
       170 
     | 
    
         
            -
             
     | 
| 
       171 
     | 
    
         
            -
             
     | 
| 
      
 177 
     | 
    
         
            +
                # Import ensure_directories using safe_import pattern
         
     | 
| 
      
 178 
     | 
    
         
            +
                init_ensure_directories = safe_import(
         
     | 
| 
      
 179 
     | 
    
         
            +
                    'claude_mpm.init',
         
     | 
| 
      
 180 
     | 
    
         
            +
                    None,
         
     | 
| 
      
 181 
     | 
    
         
            +
                    ['ensure_directories']
         
     | 
| 
      
 182 
     | 
    
         
            +
                )
         
     | 
| 
      
 183 
     | 
    
         
            +
                
         
     | 
| 
      
 184 
     | 
    
         
            +
                if init_ensure_directories:
         
     | 
| 
      
 185 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 186 
     | 
    
         
            +
                        init_ensure_directories()
         
     | 
| 
      
 187 
     | 
    
         
            +
                    except Exception:
         
     | 
| 
      
 188 
     | 
    
         
            +
                        # Continue even if initialization fails
         
     | 
| 
      
 189 
     | 
    
         
            +
                        # The individual commands will handle missing directories as needed
         
     | 
| 
      
 190 
     | 
    
         
            +
                        pass
         
     | 
| 
         @@ -20,10 +20,10 @@ import importlib.util 
     | 
|
| 
       20 
20 
     | 
    
         
             
            from datetime import datetime
         
     | 
| 
       21 
21 
     | 
    
         
             
            from dataclasses import dataclass
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
      
 23 
     | 
    
         
            +
            from claude_mpm.utils.imports import safe_import
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            # Import logger using safe_import pattern
         
     | 
| 
      
 26 
     | 
    
         
            +
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
            @dataclass
         
     | 
    
        claude_mpm/core/factories.py
    CHANGED
    
    | 
         @@ -12,7 +12,7 @@ from typing import Any, Dict, Optional, Type, TypeVar 
     | 
|
| 
       12 
12 
     | 
    
         
             
            from .container import DIContainer
         
     | 
| 
       13 
13 
     | 
    
         
             
            from .logger import get_logger
         
     | 
| 
       14 
14 
     | 
    
         
             
            from .config import Config
         
     | 
| 
       15 
     | 
    
         
            -
            from  
     | 
| 
      
 15 
     | 
    
         
            +
            from claude_mpm.services.agent_deployment import AgentDeploymentService
         
     | 
| 
       16 
16 
     | 
    
         
             
            from ..orchestration.factory import OrchestratorFactory
         
     | 
| 
       17 
17 
     | 
    
         
             
            from ..orchestration.base import BaseOrchestrator
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
         @@ -38,7 +38,7 @@ class ServiceRegistry: 
     | 
|
| 
       38 
38 
     | 
    
         
             
                    """Register all core framework services."""
         
     | 
| 
       39 
39 
     | 
    
         
             
                    from ..services.shared_prompt_cache import SharedPromptCache
         
     | 
| 
       40 
40 
     | 
    
         
             
                    from ..services.ticket_manager import TicketManager
         
     | 
| 
       41 
     | 
    
         
            -
                    from  
     | 
| 
      
 41 
     | 
    
         
            +
                    from claude_mpm.services.agent_deployment import AgentDeploymentService
         
     | 
| 
       42 
42 
     | 
    
         
             
                    from .session_manager import SessionManager
         
     | 
| 
       43 
43 
     | 
    
         
             
                    from .agent_session_manager import AgentSessionManager
         
     | 
| 
       44 
44 
     | 
    
         
             
                    from .config import Config
         
     | 
    
        claude_mpm/core/simple_runner.py
    CHANGED
    
    | 
         @@ -195,16 +195,15 @@ class SimpleClaudeRunner: 
     | 
|
| 
       195 
195 
     | 
    
         
             
                            clean_env.pop(var, None)
         
     | 
| 
       196 
196 
     | 
    
         | 
| 
       197 
197 
     | 
    
         
             
                        # Set the correct working directory for Claude Code
         
     | 
| 
       198 
     | 
    
         
            -
                        #  
     | 
| 
      
 198 
     | 
    
         
            +
                        # WHY: We're already in the user's directory thanks to __main__.py restoration
         
     | 
| 
      
 199 
     | 
    
         
            +
                        # We just need to ensure CLAUDE_WORKSPACE is set correctly
         
     | 
| 
      
 200 
     | 
    
         
            +
                        current_dir = os.getcwd()
         
     | 
| 
      
 201 
     | 
    
         
            +
                        clean_env['CLAUDE_WORKSPACE'] = current_dir
         
     | 
| 
      
 202 
     | 
    
         
            +
                        self.logger.info(f"Working directory: {current_dir}")
         
     | 
| 
      
 203 
     | 
    
         
            +
                        
         
     | 
| 
      
 204 
     | 
    
         
            +
                        # Log directory context for debugging
         
     | 
| 
       199 
205 
     | 
    
         
             
                        if 'CLAUDE_MPM_USER_PWD' in clean_env:
         
     | 
| 
       200 
     | 
    
         
            -
                             
     | 
| 
       201 
     | 
    
         
            -
                            clean_env['CLAUDE_WORKSPACE'] = user_pwd
         
     | 
| 
       202 
     | 
    
         
            -
                            # Also change to that directory before launching Claude
         
     | 
| 
       203 
     | 
    
         
            -
                            try:
         
     | 
| 
       204 
     | 
    
         
            -
                                os.chdir(user_pwd)
         
     | 
| 
       205 
     | 
    
         
            -
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         
     | 
| 
       206 
     | 
    
         
            -
                            except Exception as e:
         
     | 
| 
       207 
     | 
    
         
            -
                                self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
         
     | 
| 
      
 206 
     | 
    
         
            +
                            self.logger.debug(f"User PWD from env: {clean_env['CLAUDE_MPM_USER_PWD']}")
         
     | 
| 
       208 
207 
     | 
    
         | 
| 
       209 
208 
     | 
    
         
             
                        print("Launching Claude...")
         
     | 
| 
       210 
209 
     | 
    
         | 
| 
         @@ -313,19 +312,15 @@ class SimpleClaudeRunner: 
     | 
|
| 
       313 
312 
     | 
    
         
             
                        env = os.environ.copy()
         
     | 
| 
       314 
313 
     | 
    
         | 
| 
       315 
314 
     | 
    
         
             
                        # Set the correct working directory for Claude Code
         
     | 
| 
      
 315 
     | 
    
         
            +
                        # WHY: We're already in the user's directory thanks to __main__.py restoration
         
     | 
| 
      
 316 
     | 
    
         
            +
                        # We just need to ensure CLAUDE_WORKSPACE is set correctly
         
     | 
| 
      
 317 
     | 
    
         
            +
                        current_dir = os.getcwd()
         
     | 
| 
      
 318 
     | 
    
         
            +
                        env['CLAUDE_WORKSPACE'] = current_dir
         
     | 
| 
      
 319 
     | 
    
         
            +
                        self.logger.info(f"Working directory for subprocess: {current_dir}")
         
     | 
| 
      
 320 
     | 
    
         
            +
                        
         
     | 
| 
      
 321 
     | 
    
         
            +
                        # Log directory context for debugging
         
     | 
| 
       316 
322 
     | 
    
         
             
                        if 'CLAUDE_MPM_USER_PWD' in env:
         
     | 
| 
       317 
     | 
    
         
            -
                             
     | 
| 
       318 
     | 
    
         
            -
                            env['CLAUDE_WORKSPACE'] = user_pwd
         
     | 
| 
       319 
     | 
    
         
            -
                            # Change to that directory before running Claude
         
     | 
| 
       320 
     | 
    
         
            -
                            try:
         
     | 
| 
       321 
     | 
    
         
            -
                                original_cwd = os.getcwd()
         
     | 
| 
       322 
     | 
    
         
            -
                                os.chdir(user_pwd)
         
     | 
| 
       323 
     | 
    
         
            -
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         
     | 
| 
       324 
     | 
    
         
            -
                            except Exception as e:
         
     | 
| 
       325 
     | 
    
         
            -
                                self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
         
     | 
| 
       326 
     | 
    
         
            -
                                original_cwd = None
         
     | 
| 
       327 
     | 
    
         
            -
                        else:
         
     | 
| 
       328 
     | 
    
         
            -
                            original_cwd = None
         
     | 
| 
      
 323 
     | 
    
         
            +
                            self.logger.debug(f"User PWD from env: {env['CLAUDE_MPM_USER_PWD']}")
         
     | 
| 
       329 
324 
     | 
    
         | 
| 
       330 
325 
     | 
    
         
             
                        # Run Claude
         
     | 
| 
       331 
326 
     | 
    
         
             
                        if self.project_logger:
         
     | 
| 
         @@ -337,12 +332,7 @@ class SimpleClaudeRunner: 
     | 
|
| 
       337 
332 
     | 
    
         | 
| 
       338 
333 
     | 
    
         
             
                        result = subprocess.run(cmd, capture_output=True, text=True, env=env)
         
     | 
| 
       339 
334 
     | 
    
         | 
| 
       340 
     | 
    
         
            -
                        #  
     | 
| 
       341 
     | 
    
         
            -
                        if original_cwd:
         
     | 
| 
       342 
     | 
    
         
            -
                            try:
         
     | 
| 
       343 
     | 
    
         
            -
                                os.chdir(original_cwd)
         
     | 
| 
       344 
     | 
    
         
            -
                            except Exception:
         
     | 
| 
       345 
     | 
    
         
            -
                                pass
         
     | 
| 
      
 335 
     | 
    
         
            +
                        # No need to restore directory since we didn't change it
         
     | 
| 
       346 
336 
     | 
    
         
             
                        execution_time = time.time() - start_time
         
     | 
| 
       347 
337 
     | 
    
         | 
| 
       348 
338 
     | 
    
         
             
                        if result.returncode == 0:
         
     | 
| 
         @@ -44,6 +44,7 @@ project_root = Path(__file__).parent.parent.parent.parent 
     | 
|
| 
       44 
44 
     | 
    
         
             
            sys.path.insert(0, str(project_root / 'src'))
         
     | 
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
            from claude_mpm.core.logger import get_logger, setup_logging, LogLevel
         
     | 
| 
      
 47 
     | 
    
         
            +
            from claude_mpm.security import BashSecurityValidator
         
     | 
| 
       47 
48 
     | 
    
         | 
| 
       48 
49 
     | 
    
         
             
            # Don't initialize global logging here - we'll do it per-project
         
     | 
| 
       49 
50 
     | 
    
         
             
            logger = None
         
     | 
| 
         @@ -329,6 +330,7 @@ class ClaudeHookHandler: 
     | 
|
| 
       329 
330 
     | 
    
         
             
                    - Checking for path traversal attempts
         
     | 
| 
       330 
331 
     | 
    
         
             
                    - Ensuring file operations stay within the working directory
         
     | 
| 
       331 
332 
     | 
    
         
             
                    - Blocking dangerous operations
         
     | 
| 
      
 333 
     | 
    
         
            +
                    - Validating bash commands for security violations
         
     | 
| 
       332 
334 
     | 
    
         | 
| 
       333 
335 
     | 
    
         
             
                    Security Design:
         
     | 
| 
       334 
336 
     | 
    
         
             
                    - Fail-secure: Block suspicious operations
         
     | 
| 
         @@ -342,6 +344,29 @@ class ClaudeHookHandler: 
     | 
|
| 
       342 
344 
     | 
    
         
             
                    tool_name = self.event.get('tool_name', '')
         
     | 
| 
       343 
345 
     | 
    
         
             
                    tool_input = self.event.get('tool_input', {})
         
     | 
| 
       344 
346 
     | 
    
         | 
| 
      
 347 
     | 
    
         
            +
                    # Get the working directory from the event
         
     | 
| 
      
 348 
     | 
    
         
            +
                    working_dir = Path(self.event.get('cwd', os.getcwd())).resolve()
         
     | 
| 
      
 349 
     | 
    
         
            +
                    
         
     | 
| 
      
 350 
     | 
    
         
            +
                    # Special handling for Bash tool - validate commands for security
         
     | 
| 
      
 351 
     | 
    
         
            +
                    if tool_name == 'Bash':
         
     | 
| 
      
 352 
     | 
    
         
            +
                        command = tool_input.get('command', '')
         
     | 
| 
      
 353 
     | 
    
         
            +
                        if command:
         
     | 
| 
      
 354 
     | 
    
         
            +
                            # Create bash validator for this working directory
         
     | 
| 
      
 355 
     | 
    
         
            +
                            bash_validator = BashSecurityValidator(working_dir)
         
     | 
| 
      
 356 
     | 
    
         
            +
                            is_valid, error_msg = bash_validator.validate_command(command)
         
     | 
| 
      
 357 
     | 
    
         
            +
                            
         
     | 
| 
      
 358 
     | 
    
         
            +
                            if not is_valid:
         
     | 
| 
      
 359 
     | 
    
         
            +
                                if logger:
         
     | 
| 
      
 360 
     | 
    
         
            +
                                    logger.warning(f"Security: Blocked Bash command: {command[:100]}...")
         
     | 
| 
      
 361 
     | 
    
         
            +
                                
         
     | 
| 
      
 362 
     | 
    
         
            +
                                response = {
         
     | 
| 
      
 363 
     | 
    
         
            +
                                    "action": "block",
         
     | 
| 
      
 364 
     | 
    
         
            +
                                    "error": error_msg
         
     | 
| 
      
 365 
     | 
    
         
            +
                                }
         
     | 
| 
      
 366 
     | 
    
         
            +
                                print(json.dumps(response))
         
     | 
| 
      
 367 
     | 
    
         
            +
                                sys.exit(0)
         
     | 
| 
      
 368 
     | 
    
         
            +
                                return
         
     | 
| 
      
 369 
     | 
    
         
            +
                    
         
     | 
| 
       345 
370 
     | 
    
         
             
                    # List of tools that perform write operations
         
     | 
| 
       346 
371 
     | 
    
         
             
                    # These tools need special security checks to prevent
         
     | 
| 
       347 
372 
     | 
    
         
             
                    # writing outside the project directory
         
     | 
| 
         @@ -349,9 +374,6 @@ class ClaudeHookHandler: 
     | 
|
| 
       349 
374 
     | 
    
         | 
| 
       350 
375 
     | 
    
         
             
                    # Check if this is a write operation
         
     | 
| 
       351 
376 
     | 
    
         
             
                    if tool_name in write_tools:
         
     | 
| 
       352 
     | 
    
         
            -
                        # Get the working directory from the event
         
     | 
| 
       353 
     | 
    
         
            -
                        working_dir = Path(self.event.get('cwd', os.getcwd())).resolve()
         
     | 
| 
       354 
     | 
    
         
            -
                        
         
     | 
| 
       355 
377 
     | 
    
         
             
                        # Extract file path based on tool type
         
     | 
| 
       356 
378 
     | 
    
         
             
                        file_path = None
         
     | 
| 
       357 
379 
     | 
    
         
             
                        if tool_name in ['Write', 'Edit', 'NotebookEdit']:
         
     | 
| 
         @@ -414,7 +436,34 @@ class ClaudeHookHandler: 
     | 
|
| 
       414 
436 
     | 
    
         
             
                                sys.exit(0)
         
     | 
| 
       415 
437 
     | 
    
         
             
                                return
         
     | 
| 
       416 
438 
     | 
    
         | 
| 
       417 
     | 
    
         
            -
                    #  
     | 
| 
      
 439 
     | 
    
         
            +
                    # Additional security checks for other tools that might access files
         
     | 
| 
      
 440 
     | 
    
         
            +
                    read_tools = ['Read', 'LS', 'Glob', 'Grep', 'NotebookRead']
         
     | 
| 
      
 441 
     | 
    
         
            +
                    
         
     | 
| 
      
 442 
     | 
    
         
            +
                    if tool_name in read_tools:
         
     | 
| 
      
 443 
     | 
    
         
            +
                        # For read operations, we allow them but log for auditing
         
     | 
| 
      
 444 
     | 
    
         
            +
                        file_path = None
         
     | 
| 
      
 445 
     | 
    
         
            +
                        if tool_name == 'Read':
         
     | 
| 
      
 446 
     | 
    
         
            +
                            file_path = tool_input.get('file_path')
         
     | 
| 
      
 447 
     | 
    
         
            +
                        elif tool_name == 'NotebookRead':
         
     | 
| 
      
 448 
     | 
    
         
            +
                            file_path = tool_input.get('notebook_path')
         
     | 
| 
      
 449 
     | 
    
         
            +
                        elif tool_name == 'LS':
         
     | 
| 
      
 450 
     | 
    
         
            +
                            file_path = tool_input.get('path')
         
     | 
| 
      
 451 
     | 
    
         
            +
                        elif tool_name in ['Glob', 'Grep']:
         
     | 
| 
      
 452 
     | 
    
         
            +
                            file_path = tool_input.get('path', '.')
         
     | 
| 
      
 453 
     | 
    
         
            +
                        
         
     | 
| 
      
 454 
     | 
    
         
            +
                        if file_path and logger:
         
     | 
| 
      
 455 
     | 
    
         
            +
                            # Log read operations for security auditing
         
     | 
| 
      
 456 
     | 
    
         
            +
                            logger.info(f"Security audit: {tool_name} accessing path: {file_path}")
         
     | 
| 
      
 457 
     | 
    
         
            +
                    
         
     | 
| 
      
 458 
     | 
    
         
            +
                    # Check for other potentially dangerous tools
         
     | 
| 
      
 459 
     | 
    
         
            +
                    if tool_name in ['WebFetch', 'WebSearch']:
         
     | 
| 
      
 460 
     | 
    
         
            +
                        # Log web access for security auditing
         
     | 
| 
      
 461 
     | 
    
         
            +
                        if logger:
         
     | 
| 
      
 462 
     | 
    
         
            +
                            url = tool_input.get('url', '') if tool_name == 'WebFetch' else 'N/A'
         
     | 
| 
      
 463 
     | 
    
         
            +
                            query = tool_input.get('query', '') if tool_name == 'WebSearch' else 'N/A'
         
     | 
| 
      
 464 
     | 
    
         
            +
                            logger.info(f"Security audit: {tool_name} - URL: {url}, Query: {query}")
         
     | 
| 
      
 465 
     | 
    
         
            +
                    
         
     | 
| 
      
 466 
     | 
    
         
            +
                    # For all other operations, continue normally
         
     | 
| 
       418 
467 
     | 
    
         
             
                    return self._continue()
         
     | 
| 
       419 
468 
     | 
    
         | 
| 
       420 
469 
     | 
    
         
             
                def _handle_post_tool_use(self):
         
     |