claude-mpm 3.1.0__py3-none-any.whl → 3.1.2__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 +10 -3
 - claude_mpm/agents/templates/test-integration-agent.md +34 -0
 - claude_mpm/cli/README.md +109 -0
 - claude_mpm/cli/__init__.py +163 -0
 - claude_mpm/cli/commands/__init__.py +20 -0
 - claude_mpm/cli/commands/agents.py +191 -0
 - claude_mpm/cli/commands/info.py +89 -0
 - claude_mpm/cli/commands/run.py +80 -0
 - claude_mpm/cli/commands/tickets.py +63 -0
 - claude_mpm/cli/commands/ui.py +57 -0
 - claude_mpm/cli/parser.py +337 -0
 - claude_mpm/cli/utils.py +171 -0
 - claude_mpm/cli_enhancements.py +19 -0
 - claude_mpm/core/simple_runner.py +40 -2
 - claude_mpm/models/__init__.py +24 -0
 - claude_mpm/models/agent_definition.py +196 -0
 - claude_mpm/services/agent_lifecycle_manager.py +183 -20
 - claude_mpm/services/agent_modification_tracker.py +10 -2
 - claude_mpm/services/agent_persistence_service.py +94 -0
 - claude_mpm/services/agent_versioning.py +37 -0
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/METADATA +1 -1
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/RECORD +27 -13
 - claude_mpm/cli_main.py +0 -13
 - /claude_mpm/{cli.py → cli_old.py} +0 -0
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/entry_points.txt +0 -0
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.0.dist-info → claude_mpm-3.1.2.dist-info}/top_level.txt +0 -0
 
    
        claude_mpm/cli/utils.py
    ADDED
    
    | 
         @@ -0,0 +1,171 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """
         
     | 
| 
      
 2 
     | 
    
         
            +
            Utility functions for the CLI.
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            WHY: This module contains shared utility functions used across different CLI commands.
         
     | 
| 
      
 5 
     | 
    
         
            +
            Centralizing these functions reduces code duplication and provides a single place
         
     | 
| 
      
 6 
     | 
    
         
            +
            for common CLI operations.
         
     | 
| 
      
 7 
     | 
    
         
            +
            """
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            import sys
         
     | 
| 
      
 10 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 11 
     | 
    
         
            +
            from typing import Optional
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            from ..core.logger import get_logger
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            def get_user_input(input_arg: Optional[str], logger) -> str:
         
     | 
| 
      
 17 
     | 
    
         
            +
                """
         
     | 
| 
      
 18 
     | 
    
         
            +
                Get user input based on command line arguments.
         
     | 
| 
      
 19 
     | 
    
         
            +
                
         
     | 
| 
      
 20 
     | 
    
         
            +
                WHY: This function handles the three ways users can provide input:
         
     | 
| 
      
 21 
     | 
    
         
            +
                1. Direct text via -i/--input
         
     | 
| 
      
 22 
     | 
    
         
            +
                2. File path via -i/--input
         
     | 
| 
      
 23 
     | 
    
         
            +
                3. stdin (for piping)
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                DESIGN DECISION: We check if the input is a file path first, then fall back
         
     | 
| 
      
 26 
     | 
    
         
            +
                to treating it as direct text. This allows maximum flexibility.
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 29 
     | 
    
         
            +
                    input_arg: The value of the -i/--input argument
         
     | 
| 
      
 30 
     | 
    
         
            +
                    logger: Logger instance for output
         
     | 
| 
      
 31 
     | 
    
         
            +
                    
         
     | 
| 
      
 32 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 33 
     | 
    
         
            +
                    The user input as a string
         
     | 
| 
      
 34 
     | 
    
         
            +
                """
         
     | 
| 
      
 35 
     | 
    
         
            +
                if input_arg:
         
     | 
| 
      
 36 
     | 
    
         
            +
                    # Check if it's a file path
         
     | 
| 
      
 37 
     | 
    
         
            +
                    input_path = Path(input_arg)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if input_path.exists():
         
     | 
| 
      
 39 
     | 
    
         
            +
                        logger.info(f"Reading input from file: {input_path}")
         
     | 
| 
      
 40 
     | 
    
         
            +
                        return input_path.read_text()
         
     | 
| 
      
 41 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 42 
     | 
    
         
            +
                        logger.info("Using command line input")
         
     | 
| 
      
 43 
     | 
    
         
            +
                        return input_arg
         
     | 
| 
      
 44 
     | 
    
         
            +
                else:
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # Read from stdin
         
     | 
| 
      
 46 
     | 
    
         
            +
                    logger.info("Reading input from stdin")
         
     | 
| 
      
 47 
     | 
    
         
            +
                    return sys.stdin.read()
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            def get_agent_versions_display() -> Optional[str]:
         
     | 
| 
      
 51 
     | 
    
         
            +
                """
         
     | 
| 
      
 52 
     | 
    
         
            +
                Get formatted agent versions display as a string.
         
     | 
| 
      
 53 
     | 
    
         
            +
                
         
     | 
| 
      
 54 
     | 
    
         
            +
                WHY: This function provides a single source of truth for agent version
         
     | 
| 
      
 55 
     | 
    
         
            +
                information that can be displayed both at startup and on-demand via the
         
     | 
| 
      
 56 
     | 
    
         
            +
                /mpm agents command. This ensures consistency in how agent versions are
         
     | 
| 
      
 57 
     | 
    
         
            +
                presented to users.
         
     | 
| 
      
 58 
     | 
    
         
            +
                
         
     | 
| 
      
 59 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 60 
     | 
    
         
            +
                    Formatted string containing agent version information, or None if failed
         
     | 
| 
      
 61 
     | 
    
         
            +
                """
         
     | 
| 
      
 62 
     | 
    
         
            +
                try:
         
     | 
| 
      
 63 
     | 
    
         
            +
                    from ..services.agent_deployment import AgentDeploymentService
         
     | 
| 
      
 64 
     | 
    
         
            +
                    deployment_service = AgentDeploymentService()
         
     | 
| 
      
 65 
     | 
    
         
            +
                    
         
     | 
| 
      
 66 
     | 
    
         
            +
                    # Get deployed agents
         
     | 
| 
      
 67 
     | 
    
         
            +
                    verification = deployment_service.verify_deployment()
         
     | 
| 
      
 68 
     | 
    
         
            +
                    if not verification.get("agents_found"):
         
     | 
| 
      
 69 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 70 
     | 
    
         
            +
                        
         
     | 
| 
      
 71 
     | 
    
         
            +
                    output_lines = []
         
     | 
| 
      
 72 
     | 
    
         
            +
                    output_lines.append("\nDeployed Agent Versions:")
         
     | 
| 
      
 73 
     | 
    
         
            +
                    output_lines.append("-" * 40)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    
         
     | 
| 
      
 75 
     | 
    
         
            +
                    # Sort agents by name for consistent display
         
     | 
| 
      
 76 
     | 
    
         
            +
                    agents = sorted(verification["agents_found"], key=lambda x: x.get('name', x.get('file', '')))
         
     | 
| 
      
 77 
     | 
    
         
            +
                    
         
     | 
| 
      
 78 
     | 
    
         
            +
                    for agent in agents:
         
     | 
| 
      
 79 
     | 
    
         
            +
                        name = agent.get('name', 'unknown')
         
     | 
| 
      
 80 
     | 
    
         
            +
                        version = agent.get('version', 'unknown')
         
     | 
| 
      
 81 
     | 
    
         
            +
                        # Format: name (version)
         
     | 
| 
      
 82 
     | 
    
         
            +
                        output_lines.append(f"  {name:<20} {version}")
         
     | 
| 
      
 83 
     | 
    
         
            +
                    
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # Add base agent version info
         
     | 
| 
      
 85 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 86 
     | 
    
         
            +
                        import json
         
     | 
| 
      
 87 
     | 
    
         
            +
                        base_agent_path = deployment_service.base_agent_path
         
     | 
| 
      
 88 
     | 
    
         
            +
                        if base_agent_path.exists():
         
     | 
| 
      
 89 
     | 
    
         
            +
                            base_data = json.loads(base_agent_path.read_text())
         
     | 
| 
      
 90 
     | 
    
         
            +
                            # Parse version the same way as AgentDeploymentService
         
     | 
| 
      
 91 
     | 
    
         
            +
                            raw_version = base_data.get('base_version') or base_data.get('version', 0)
         
     | 
| 
      
 92 
     | 
    
         
            +
                            base_version_tuple = deployment_service._parse_version(raw_version)
         
     | 
| 
      
 93 
     | 
    
         
            +
                            base_version_str = deployment_service._format_version_display(base_version_tuple)
         
     | 
| 
      
 94 
     | 
    
         
            +
                            output_lines.append(f"\n  Base Agent Version:  {base_version_str}")
         
     | 
| 
      
 95 
     | 
    
         
            +
                    except:
         
     | 
| 
      
 96 
     | 
    
         
            +
                        pass
         
     | 
| 
      
 97 
     | 
    
         
            +
                    
         
     | 
| 
      
 98 
     | 
    
         
            +
                    # Check for agents needing migration
         
     | 
| 
      
 99 
     | 
    
         
            +
                    if verification.get("agents_needing_migration"):
         
     | 
| 
      
 100 
     | 
    
         
            +
                        output_lines.append(f"\n  ⚠️  {len(verification['agents_needing_migration'])} agent(s) need migration to semantic versioning")
         
     | 
| 
      
 101 
     | 
    
         
            +
                        output_lines.append(f"     Run 'claude-mpm agents deploy' to update")
         
     | 
| 
      
 102 
     | 
    
         
            +
                        
         
     | 
| 
      
 103 
     | 
    
         
            +
                    output_lines.append("-" * 40)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    return "\n".join(output_lines)
         
     | 
| 
      
 105 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 106 
     | 
    
         
            +
                    # Log error but don't fail
         
     | 
| 
      
 107 
     | 
    
         
            +
                    logger = get_logger("cli")
         
     | 
| 
      
 108 
     | 
    
         
            +
                    logger.debug(f"Failed to get agent versions: {e}")
         
     | 
| 
      
 109 
     | 
    
         
            +
                    return None
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            def list_agent_versions_at_startup() -> None:
         
     | 
| 
      
 113 
     | 
    
         
            +
                """
         
     | 
| 
      
 114 
     | 
    
         
            +
                List deployed agent versions at startup.
         
     | 
| 
      
 115 
     | 
    
         
            +
                
         
     | 
| 
      
 116 
     | 
    
         
            +
                WHY: Users want to see what agents are available when they start a session.
         
     | 
| 
      
 117 
     | 
    
         
            +
                This provides immediate feedback about the deployed agent environment.
         
     | 
| 
      
 118 
     | 
    
         
            +
                """
         
     | 
| 
      
 119 
     | 
    
         
            +
                agent_versions = get_agent_versions_display()
         
     | 
| 
      
 120 
     | 
    
         
            +
                if agent_versions:
         
     | 
| 
      
 121 
     | 
    
         
            +
                    print(agent_versions)
         
     | 
| 
      
 122 
     | 
    
         
            +
                    print()  # Extra newline after the display
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            def setup_logging(args) -> object:
         
     | 
| 
      
 126 
     | 
    
         
            +
                """
         
     | 
| 
      
 127 
     | 
    
         
            +
                Set up logging based on parsed arguments.
         
     | 
| 
      
 128 
     | 
    
         
            +
                
         
     | 
| 
      
 129 
     | 
    
         
            +
                WHY: This centralizes logging setup logic, handling the deprecated --debug flag
         
     | 
| 
      
 130 
     | 
    
         
            +
                and the new --logging argument consistently across all commands.
         
     | 
| 
      
 131 
     | 
    
         
            +
                
         
     | 
| 
      
 132 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 133 
     | 
    
         
            +
                    args: Parsed command line arguments
         
     | 
| 
      
 134 
     | 
    
         
            +
                    
         
     | 
| 
      
 135 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 136 
     | 
    
         
            +
                    Logger instance
         
     | 
| 
      
 137 
     | 
    
         
            +
                """
         
     | 
| 
      
 138 
     | 
    
         
            +
                from ..core.logger import setup_logging as core_setup_logging, get_logger
         
     | 
| 
      
 139 
     | 
    
         
            +
                from ..constants import LogLevel
         
     | 
| 
      
 140 
     | 
    
         
            +
                
         
     | 
| 
      
 141 
     | 
    
         
            +
                # Handle deprecated --debug flag
         
     | 
| 
      
 142 
     | 
    
         
            +
                if hasattr(args, 'debug') and args.debug and args.logging == LogLevel.INFO.value:
         
     | 
| 
      
 143 
     | 
    
         
            +
                    args.logging = LogLevel.DEBUG.value
         
     | 
| 
      
 144 
     | 
    
         
            +
                
         
     | 
| 
      
 145 
     | 
    
         
            +
                # Only setup logging if not OFF
         
     | 
| 
      
 146 
     | 
    
         
            +
                if args.logging != LogLevel.OFF.value:
         
     | 
| 
      
 147 
     | 
    
         
            +
                    logger = core_setup_logging(level=args.logging, log_dir=args.log_dir)
         
     | 
| 
      
 148 
     | 
    
         
            +
                else:
         
     | 
| 
      
 149 
     | 
    
         
            +
                    # Minimal logger for CLI feedback
         
     | 
| 
      
 150 
     | 
    
         
            +
                    import logging
         
     | 
| 
      
 151 
     | 
    
         
            +
                    logger = logging.getLogger("cli")
         
     | 
| 
      
 152 
     | 
    
         
            +
                    logger.setLevel(logging.WARNING)
         
     | 
| 
      
 153 
     | 
    
         
            +
                
         
     | 
| 
      
 154 
     | 
    
         
            +
                return logger
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
            def ensure_directories() -> None:
         
     | 
| 
      
 158 
     | 
    
         
            +
                """
         
     | 
| 
      
 159 
     | 
    
         
            +
                Ensure required directories exist on first run.
         
     | 
| 
      
 160 
     | 
    
         
            +
                
         
     | 
| 
      
 161 
     | 
    
         
            +
                WHY: Claude-mpm needs certain directories to function properly. Rather than
         
     | 
| 
      
 162 
     | 
    
         
            +
                failing when they don't exist, we create them automatically for a better
         
     | 
| 
      
 163 
     | 
    
         
            +
                user experience.
         
     | 
| 
      
 164 
     | 
    
         
            +
                """
         
     | 
| 
      
 165 
     | 
    
         
            +
                try:
         
     | 
| 
      
 166 
     | 
    
         
            +
                    from ..init import ensure_directories as init_ensure_directories
         
     | 
| 
      
 167 
     | 
    
         
            +
                    init_ensure_directories()
         
     | 
| 
      
 168 
     | 
    
         
            +
                except Exception:
         
     | 
| 
      
 169 
     | 
    
         
            +
                    # Continue even if initialization fails
         
     | 
| 
      
 170 
     | 
    
         
            +
                    # The individual commands will handle missing directories as needed
         
     | 
| 
      
 171 
     | 
    
         
            +
                    pass
         
     | 
    
        claude_mpm/cli_enhancements.py
    CHANGED
    
    | 
         @@ -1,6 +1,25 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            """
         
     | 
| 
       2 
2 
     | 
    
         
             
            Enhanced CLI operations for claude-mpm.
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            WHY THIS FILE EXISTS:
         
     | 
| 
      
 5 
     | 
    
         
            +
            This module provides an alternative CLI implementation with enhanced error handling
         
     | 
| 
      
 6 
     | 
    
         
            +
            and validation features. It was created to explore advanced CLI patterns including:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Comprehensive prerequisite validation
         
     | 
| 
      
 8 
     | 
    
         
            +
            - User-friendly error messages with suggestions
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Dry-run mode for testing
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Profile validation and generation
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Rich terminal output with status indicators
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            CURRENT STATUS: This is an experimental/alternative CLI implementation that uses
         
     | 
| 
      
 14 
     | 
    
         
            +
            Click instead of argparse. It's kept separate from the main CLI to:
         
     | 
| 
      
 15 
     | 
    
         
            +
            1. Preserve the existing CLI behavior
         
     | 
| 
      
 16 
     | 
    
         
            +
            2. Allow testing of new features without breaking the main interface
         
     | 
| 
      
 17 
     | 
    
         
            +
            3. Provide a reference implementation for future CLI enhancements
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            NOTE: This CLI is not currently used in production. The main CLI is in cli/__init__.py.
         
     | 
| 
      
 20 
     | 
    
         
            +
            To use this enhanced CLI, you would need to create a separate entry point or
         
     | 
| 
      
 21 
     | 
    
         
            +
            integrate selected features into the main CLI.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
       4 
23 
     | 
    
         
             
            Implements error handling and user guidance patterns from awesome-claude-code.
         
     | 
| 
       5 
24 
     | 
    
         
             
            """
         
     | 
| 
       6 
25 
     | 
    
         | 
    
        claude_mpm/core/simple_runner.py
    CHANGED
    
    | 
         @@ -194,6 +194,18 @@ class SimpleClaudeRunner: 
     | 
|
| 
       194 
194 
     | 
    
         
             
                        for var in claude_vars_to_remove:
         
     | 
| 
       195 
195 
     | 
    
         
             
                            clean_env.pop(var, None)
         
     | 
| 
       196 
196 
     | 
    
         | 
| 
      
 197 
     | 
    
         
            +
                        # Set the correct working directory for Claude Code
         
     | 
| 
      
 198 
     | 
    
         
            +
                        # If CLAUDE_MPM_USER_PWD is set, use that as the working directory
         
     | 
| 
      
 199 
     | 
    
         
            +
                        if 'CLAUDE_MPM_USER_PWD' in clean_env:
         
     | 
| 
      
 200 
     | 
    
         
            +
                            user_pwd = clean_env['CLAUDE_MPM_USER_PWD']
         
     | 
| 
      
 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}")
         
     | 
| 
      
 208 
     | 
    
         
            +
                        
         
     | 
| 
       197 
209 
     | 
    
         
             
                        print("Launching Claude...")
         
     | 
| 
       198 
210 
     | 
    
         | 
| 
       199 
211 
     | 
    
         
             
                        if self.project_logger:
         
     | 
| 
         @@ -225,7 +237,8 @@ class SimpleClaudeRunner: 
     | 
|
| 
       225 
237 
     | 
    
         
             
                            })
         
     | 
| 
       226 
238 
     | 
    
         
             
                        # Fallback to subprocess
         
     | 
| 
       227 
239 
     | 
    
         
             
                        try:
         
     | 
| 
       228 
     | 
    
         
            -
                             
     | 
| 
      
 240 
     | 
    
         
            +
                            # Use the same clean_env we prepared earlier
         
     | 
| 
      
 241 
     | 
    
         
            +
                            subprocess.run(cmd, stdin=None, stdout=None, stderr=None, env=clean_env)
         
     | 
| 
       229 
242 
     | 
    
         
             
                            if self.project_logger:
         
     | 
| 
       230 
243 
     | 
    
         
             
                                self.project_logger.log_system(
         
     | 
| 
       231 
244 
     | 
    
         
             
                                    "Interactive session completed (subprocess fallback)",
         
     | 
| 
         @@ -296,6 +309,24 @@ class SimpleClaudeRunner: 
     | 
|
| 
       296 
309 
     | 
    
         
             
                        cmd.insert(-2, system_prompt)
         
     | 
| 
       297 
310 
     | 
    
         | 
| 
       298 
311 
     | 
    
         
             
                    try:
         
     | 
| 
      
 312 
     | 
    
         
            +
                        # Set up environment with correct working directory
         
     | 
| 
      
 313 
     | 
    
         
            +
                        env = os.environ.copy()
         
     | 
| 
      
 314 
     | 
    
         
            +
                        
         
     | 
| 
      
 315 
     | 
    
         
            +
                        # Set the correct working directory for Claude Code
         
     | 
| 
      
 316 
     | 
    
         
            +
                        if 'CLAUDE_MPM_USER_PWD' in env:
         
     | 
| 
      
 317 
     | 
    
         
            +
                            user_pwd = env['CLAUDE_MPM_USER_PWD']
         
     | 
| 
      
 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
         
     | 
| 
      
 329 
     | 
    
         
            +
                        
         
     | 
| 
       299 
330 
     | 
    
         
             
                        # Run Claude
         
     | 
| 
       300 
331 
     | 
    
         
             
                        if self.project_logger:
         
     | 
| 
       301 
332 
     | 
    
         
             
                            self.project_logger.log_system(
         
     | 
| 
         @@ -304,7 +335,14 @@ class SimpleClaudeRunner: 
     | 
|
| 
       304 
335 
     | 
    
         
             
                                component="session"
         
     | 
| 
       305 
336 
     | 
    
         
             
                            )
         
     | 
| 
       306 
337 
     | 
    
         | 
| 
       307 
     | 
    
         
            -
                        result = subprocess.run(cmd, capture_output=True, text=True)
         
     | 
| 
      
 338 
     | 
    
         
            +
                        result = subprocess.run(cmd, capture_output=True, text=True, env=env)
         
     | 
| 
      
 339 
     | 
    
         
            +
                        
         
     | 
| 
      
 340 
     | 
    
         
            +
                        # Restore original directory if we changed it
         
     | 
| 
      
 341 
     | 
    
         
            +
                        if original_cwd:
         
     | 
| 
      
 342 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 343 
     | 
    
         
            +
                                os.chdir(original_cwd)
         
     | 
| 
      
 344 
     | 
    
         
            +
                            except Exception:
         
     | 
| 
      
 345 
     | 
    
         
            +
                                pass
         
     | 
| 
       308 
346 
     | 
    
         
             
                        execution_time = time.time() - start_time
         
     | 
| 
       309 
347 
     | 
    
         | 
| 
       310 
348 
     | 
    
         
             
                        if result.returncode == 0:
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """
         
     | 
| 
      
 2 
     | 
    
         
            +
            Agent models package.
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            WHY: This package centralizes all data models used for agent management,
         
     | 
| 
      
 5 
     | 
    
         
            +
            providing a single source of truth for data structures across the system.
         
     | 
| 
      
 6 
     | 
    
         
            +
            """
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            from .agent_definition import (
         
     | 
| 
      
 9 
     | 
    
         
            +
                AgentDefinition,
         
     | 
| 
      
 10 
     | 
    
         
            +
                AgentMetadata,
         
     | 
| 
      
 11 
     | 
    
         
            +
                AgentType,
         
     | 
| 
      
 12 
     | 
    
         
            +
                AgentSection,
         
     | 
| 
      
 13 
     | 
    
         
            +
                AgentWorkflow,
         
     | 
| 
      
 14 
     | 
    
         
            +
                AgentPermissions
         
     | 
| 
      
 15 
     | 
    
         
            +
            )
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            __all__ = [
         
     | 
| 
      
 18 
     | 
    
         
            +
                'AgentDefinition',
         
     | 
| 
      
 19 
     | 
    
         
            +
                'AgentMetadata',
         
     | 
| 
      
 20 
     | 
    
         
            +
                'AgentType',
         
     | 
| 
      
 21 
     | 
    
         
            +
                'AgentSection',
         
     | 
| 
      
 22 
     | 
    
         
            +
                'AgentWorkflow',
         
     | 
| 
      
 23 
     | 
    
         
            +
                'AgentPermissions'
         
     | 
| 
      
 24 
     | 
    
         
            +
            ]
         
     | 
| 
         @@ -0,0 +1,196 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env python3
         
     | 
| 
      
 2 
     | 
    
         
            +
            """
         
     | 
| 
      
 3 
     | 
    
         
            +
            Agent Definition Models
         
     | 
| 
      
 4 
     | 
    
         
            +
            =======================
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Data models for agent definitions used by AgentManager.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            WHY: These models provide a structured representation of agent data to ensure
         
     | 
| 
      
 9 
     | 
    
         
            +
            consistency across the system. They separate the data structure from the
         
     | 
| 
      
 10 
     | 
    
         
            +
            business logic, following the separation of concerns principle.
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            DESIGN DECISION: Using dataclasses for models because:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - They provide automatic __init__, __repr__, and other methods
         
     | 
| 
      
 14 
     | 
    
         
            +
            - Type hints ensure better IDE support and runtime validation
         
     | 
| 
      
 15 
     | 
    
         
            +
            - Easy to serialize/deserialize for persistence
         
     | 
| 
      
 16 
     | 
    
         
            +
            - Less boilerplate than traditional classes
         
     | 
| 
      
 17 
     | 
    
         
            +
            """
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            from dataclasses import dataclass, field
         
     | 
| 
      
 20 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 21 
     | 
    
         
            +
            from enum import Enum
         
     | 
| 
      
 22 
     | 
    
         
            +
            from typing import Dict, List, Optional, Any
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            class AgentType(str, Enum):
         
     | 
| 
      
 26 
     | 
    
         
            +
                """Agent type classification.
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                WHY: Enum ensures only valid agent types are used throughout the system,
         
     | 
| 
      
 29 
     | 
    
         
            +
                preventing typos and making the code more maintainable.
         
     | 
| 
      
 30 
     | 
    
         
            +
                """
         
     | 
| 
      
 31 
     | 
    
         
            +
                CORE = "core"
         
     | 
| 
      
 32 
     | 
    
         
            +
                PROJECT = "project"
         
     | 
| 
      
 33 
     | 
    
         
            +
                CUSTOM = "custom"
         
     | 
| 
      
 34 
     | 
    
         
            +
                SYSTEM = "system"
         
     | 
| 
      
 35 
     | 
    
         
            +
                SPECIALIZED = "specialized"
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            class AgentSection(str, Enum):
         
     | 
| 
      
 39 
     | 
    
         
            +
                """Agent markdown section identifiers.
         
     | 
| 
      
 40 
     | 
    
         
            +
                
         
     | 
| 
      
 41 
     | 
    
         
            +
                WHY: Standardizes section names across the codebase, making it easier
         
     | 
| 
      
 42 
     | 
    
         
            +
                to parse and update specific sections programmatically.
         
     | 
| 
      
 43 
     | 
    
         
            +
                """
         
     | 
| 
      
 44 
     | 
    
         
            +
                PRIMARY_ROLE = "Primary Role"
         
     | 
| 
      
 45 
     | 
    
         
            +
                WHEN_TO_USE = "When to Use This Agent"
         
     | 
| 
      
 46 
     | 
    
         
            +
                CAPABILITIES = "Core Capabilities"
         
     | 
| 
      
 47 
     | 
    
         
            +
                AUTHORITY = "Authority & Permissions"
         
     | 
| 
      
 48 
     | 
    
         
            +
                WORKFLOWS = "Agent-Specific Workflows"
         
     | 
| 
      
 49 
     | 
    
         
            +
                ESCALATION = "Unique Escalation Triggers"
         
     | 
| 
      
 50 
     | 
    
         
            +
                KPI = "Key Performance Indicators"
         
     | 
| 
      
 51 
     | 
    
         
            +
                DEPENDENCIES = "Critical Dependencies"
         
     | 
| 
      
 52 
     | 
    
         
            +
                TOOLS = "Specialized Tools/Commands"
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            @dataclass
         
     | 
| 
      
 56 
     | 
    
         
            +
            class AgentPermissions:
         
     | 
| 
      
 57 
     | 
    
         
            +
                """Agent authority and permissions.
         
     | 
| 
      
 58 
     | 
    
         
            +
                
         
     | 
| 
      
 59 
     | 
    
         
            +
                WHY: Separating permissions into a dedicated class allows for:
         
     | 
| 
      
 60 
     | 
    
         
            +
                - Clear permission boundaries
         
     | 
| 
      
 61 
     | 
    
         
            +
                - Easy permission checking and validation
         
     | 
| 
      
 62 
     | 
    
         
            +
                - Future extension without modifying the main agent definition
         
     | 
| 
      
 63 
     | 
    
         
            +
                """
         
     | 
| 
      
 64 
     | 
    
         
            +
                exclusive_write_access: List[str] = field(default_factory=list)
         
     | 
| 
      
 65 
     | 
    
         
            +
                forbidden_operations: List[str] = field(default_factory=list)
         
     | 
| 
      
 66 
     | 
    
         
            +
                read_access: List[str] = field(default_factory=list)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
            @dataclass
         
     | 
| 
      
 70 
     | 
    
         
            +
            class AgentWorkflow:
         
     | 
| 
      
 71 
     | 
    
         
            +
                """Agent workflow definition.
         
     | 
| 
      
 72 
     | 
    
         
            +
                
         
     | 
| 
      
 73 
     | 
    
         
            +
                WHY: Workflows are complex structures that benefit from their own model:
         
     | 
| 
      
 74 
     | 
    
         
            +
                - Ensures consistent workflow structure
         
     | 
| 
      
 75 
     | 
    
         
            +
                - Makes workflow validation easier
         
     | 
| 
      
 76 
     | 
    
         
            +
                - Allows workflow-specific operations
         
     | 
| 
      
 77 
     | 
    
         
            +
                """
         
     | 
| 
      
 78 
     | 
    
         
            +
                name: str
         
     | 
| 
      
 79 
     | 
    
         
            +
                trigger: str
         
     | 
| 
      
 80 
     | 
    
         
            +
                process: List[str]
         
     | 
| 
      
 81 
     | 
    
         
            +
                output: str
         
     | 
| 
      
 82 
     | 
    
         
            +
                raw_yaml: Optional[str] = None
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            @dataclass
         
     | 
| 
      
 86 
     | 
    
         
            +
            class AgentMetadata:
         
     | 
| 
      
 87 
     | 
    
         
            +
                """Agent metadata information.
         
     | 
| 
      
 88 
     | 
    
         
            +
                
         
     | 
| 
      
 89 
     | 
    
         
            +
                WHY: Metadata is separated from the main definition because:
         
     | 
| 
      
 90 
     | 
    
         
            +
                - It changes independently of agent behavior
         
     | 
| 
      
 91 
     | 
    
         
            +
                - It's used for discovery and management, not execution
         
     | 
| 
      
 92 
     | 
    
         
            +
                - Different services may need different metadata views
         
     | 
| 
      
 93 
     | 
    
         
            +
                """
         
     | 
| 
      
 94 
     | 
    
         
            +
                type: AgentType
         
     | 
| 
      
 95 
     | 
    
         
            +
                model_preference: str = "claude-3-sonnet"
         
     | 
| 
      
 96 
     | 
    
         
            +
                version: str = "1.0.0"
         
     | 
| 
      
 97 
     | 
    
         
            +
                last_updated: Optional[datetime] = None
         
     | 
| 
      
 98 
     | 
    
         
            +
                author: Optional[str] = None
         
     | 
| 
      
 99 
     | 
    
         
            +
                tags: List[str] = field(default_factory=list)
         
     | 
| 
      
 100 
     | 
    
         
            +
                specializations: List[str] = field(default_factory=list)
         
     | 
| 
      
 101 
     | 
    
         
            +
                
         
     | 
| 
      
 102 
     | 
    
         
            +
                def increment_serial_version(self) -> None:
         
     | 
| 
      
 103 
     | 
    
         
            +
                    """Increment the patch version number.
         
     | 
| 
      
 104 
     | 
    
         
            +
                    
         
     | 
| 
      
 105 
     | 
    
         
            +
                    WHY: Automatic version incrementing ensures every change is tracked
         
     | 
| 
      
 106 
     | 
    
         
            +
                    and follows semantic versioning principles.
         
     | 
| 
      
 107 
     | 
    
         
            +
                    """
         
     | 
| 
      
 108 
     | 
    
         
            +
                    parts = self.version.split('.')
         
     | 
| 
      
 109 
     | 
    
         
            +
                    if len(parts) == 3:
         
     | 
| 
      
 110 
     | 
    
         
            +
                        parts[2] = str(int(parts[2]) + 1)
         
     | 
| 
      
 111 
     | 
    
         
            +
                        self.version = '.'.join(parts)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 113 
     | 
    
         
            +
                        # If version format is unexpected, append .1
         
     | 
| 
      
 114 
     | 
    
         
            +
                        self.version = f"{self.version}.1"
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
            @dataclass
         
     | 
| 
      
 118 
     | 
    
         
            +
            class AgentDefinition:
         
     | 
| 
      
 119 
     | 
    
         
            +
                """Complete agent definition.
         
     | 
| 
      
 120 
     | 
    
         
            +
                
         
     | 
| 
      
 121 
     | 
    
         
            +
                WHY: This is the main model that represents an agent's complete configuration:
         
     | 
| 
      
 122 
     | 
    
         
            +
                - Combines all aspects of an agent in one place
         
     | 
| 
      
 123 
     | 
    
         
            +
                - Provides a clear contract for what constitutes an agent
         
     | 
| 
      
 124 
     | 
    
         
            +
                - Makes serialization/deserialization straightforward
         
     | 
| 
      
 125 
     | 
    
         
            +
                
         
     | 
| 
      
 126 
     | 
    
         
            +
                DESIGN DECISION: Using composition over inheritance:
         
     | 
| 
      
 127 
     | 
    
         
            +
                - AgentMetadata, AgentPermissions, and AgentWorkflow are separate classes
         
     | 
| 
      
 128 
     | 
    
         
            +
                - This allows each component to evolve independently
         
     | 
| 
      
 129 
     | 
    
         
            +
                - Services can work with just the parts they need
         
     | 
| 
      
 130 
     | 
    
         
            +
                """
         
     | 
| 
      
 131 
     | 
    
         
            +
                # Core identifiers
         
     | 
| 
      
 132 
     | 
    
         
            +
                name: str
         
     | 
| 
      
 133 
     | 
    
         
            +
                title: str
         
     | 
| 
      
 134 
     | 
    
         
            +
                file_path: str
         
     | 
| 
      
 135 
     | 
    
         
            +
                
         
     | 
| 
      
 136 
     | 
    
         
            +
                # Metadata
         
     | 
| 
      
 137 
     | 
    
         
            +
                metadata: AgentMetadata
         
     | 
| 
      
 138 
     | 
    
         
            +
                
         
     | 
| 
      
 139 
     | 
    
         
            +
                # Agent behavior definition
         
     | 
| 
      
 140 
     | 
    
         
            +
                primary_role: str
         
     | 
| 
      
 141 
     | 
    
         
            +
                when_to_use: Dict[str, List[str]]  # {"select": [...], "do_not_select": [...]}
         
     | 
| 
      
 142 
     | 
    
         
            +
                capabilities: List[str]
         
     | 
| 
      
 143 
     | 
    
         
            +
                authority: AgentPermissions
         
     | 
| 
      
 144 
     | 
    
         
            +
                workflows: List[AgentWorkflow]
         
     | 
| 
      
 145 
     | 
    
         
            +
                escalation_triggers: List[str]
         
     | 
| 
      
 146 
     | 
    
         
            +
                kpis: List[str]
         
     | 
| 
      
 147 
     | 
    
         
            +
                dependencies: List[str]
         
     | 
| 
      
 148 
     | 
    
         
            +
                tools_commands: str
         
     | 
| 
      
 149 
     | 
    
         
            +
                
         
     | 
| 
      
 150 
     | 
    
         
            +
                # Raw content for preservation
         
     | 
| 
      
 151 
     | 
    
         
            +
                raw_content: str = ""
         
     | 
| 
      
 152 
     | 
    
         
            +
                raw_sections: Dict[str, str] = field(default_factory=dict)
         
     | 
| 
      
 153 
     | 
    
         
            +
                
         
     | 
| 
      
 154 
     | 
    
         
            +
                def to_dict(self) -> Dict[str, Any]:
         
     | 
| 
      
 155 
     | 
    
         
            +
                    """Convert to dictionary for API responses.
         
     | 
| 
      
 156 
     | 
    
         
            +
                    
         
     | 
| 
      
 157 
     | 
    
         
            +
                    WHY: Many parts of the system need agent data as dictionaries:
         
     | 
| 
      
 158 
     | 
    
         
            +
                    - JSON serialization for APIs
         
     | 
| 
      
 159 
     | 
    
         
            +
                    - Configuration storage
         
     | 
| 
      
 160 
     | 
    
         
            +
                    - Integration with other services
         
     | 
| 
      
 161 
     | 
    
         
            +
                    """
         
     | 
| 
      
 162 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 163 
     | 
    
         
            +
                        "name": self.name,
         
     | 
| 
      
 164 
     | 
    
         
            +
                        "title": self.title,
         
     | 
| 
      
 165 
     | 
    
         
            +
                        "file_path": self.file_path,
         
     | 
| 
      
 166 
     | 
    
         
            +
                        "metadata": {
         
     | 
| 
      
 167 
     | 
    
         
            +
                            "type": self.metadata.type.value,
         
     | 
| 
      
 168 
     | 
    
         
            +
                            "model_preference": self.metadata.model_preference,
         
     | 
| 
      
 169 
     | 
    
         
            +
                            "version": self.metadata.version,
         
     | 
| 
      
 170 
     | 
    
         
            +
                            "last_updated": self.metadata.last_updated.isoformat() if self.metadata.last_updated else None,
         
     | 
| 
      
 171 
     | 
    
         
            +
                            "author": self.metadata.author,
         
     | 
| 
      
 172 
     | 
    
         
            +
                            "tags": self.metadata.tags,
         
     | 
| 
      
 173 
     | 
    
         
            +
                            "specializations": self.metadata.specializations
         
     | 
| 
      
 174 
     | 
    
         
            +
                        },
         
     | 
| 
      
 175 
     | 
    
         
            +
                        "primary_role": self.primary_role,
         
     | 
| 
      
 176 
     | 
    
         
            +
                        "when_to_use": self.when_to_use,
         
     | 
| 
      
 177 
     | 
    
         
            +
                        "capabilities": self.capabilities,
         
     | 
| 
      
 178 
     | 
    
         
            +
                        "authority": {
         
     | 
| 
      
 179 
     | 
    
         
            +
                            "exclusive_write_access": self.authority.exclusive_write_access,
         
     | 
| 
      
 180 
     | 
    
         
            +
                            "forbidden_operations": self.authority.forbidden_operations,
         
     | 
| 
      
 181 
     | 
    
         
            +
                            "read_access": self.authority.read_access
         
     | 
| 
      
 182 
     | 
    
         
            +
                        },
         
     | 
| 
      
 183 
     | 
    
         
            +
                        "workflows": [
         
     | 
| 
      
 184 
     | 
    
         
            +
                            {
         
     | 
| 
      
 185 
     | 
    
         
            +
                                "name": w.name,
         
     | 
| 
      
 186 
     | 
    
         
            +
                                "trigger": w.trigger,
         
     | 
| 
      
 187 
     | 
    
         
            +
                                "process": w.process,
         
     | 
| 
      
 188 
     | 
    
         
            +
                                "output": w.output
         
     | 
| 
      
 189 
     | 
    
         
            +
                            }
         
     | 
| 
      
 190 
     | 
    
         
            +
                            for w in self.workflows
         
     | 
| 
      
 191 
     | 
    
         
            +
                        ],
         
     | 
| 
      
 192 
     | 
    
         
            +
                        "escalation_triggers": self.escalation_triggers,
         
     | 
| 
      
 193 
     | 
    
         
            +
                        "kpis": self.kpis,
         
     | 
| 
      
 194 
     | 
    
         
            +
                        "dependencies": self.dependencies,
         
     | 
| 
      
 195 
     | 
    
         
            +
                        "tools_commands": self.tools_commands
         
     | 
| 
      
 196 
     | 
    
         
            +
                    }
         
     |