claude-mpm 3.5.2__py3-none-any.whl → 3.5.6__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/.claude-mpm/logs/hooks_20250728.log +10 -0
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +14 -13
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +109 -15
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +448 -0
- claude_mpm/agents/templates/data_engineer.json +4 -3
- claude_mpm/agents/templates/documentation.json +4 -3
- claude_mpm/agents/templates/engineer.json +4 -3
- claude_mpm/agents/templates/ops.json +4 -3
- claude_mpm/agents/templates/pm.json +5 -4
- claude_mpm/agents/templates/qa.json +4 -3
- claude_mpm/agents/templates/research.json +8 -7
- claude_mpm/agents/templates/security.json +4 -3
- claude_mpm/agents/templates/test_integration.json +4 -3
- claude_mpm/agents/templates/version_control.json +4 -3
- claude_mpm/cli/README.md +108 -0
- claude_mpm/cli/commands/agents.py +373 -7
- claude_mpm/cli/commands/run.py +4 -11
- claude_mpm/cli/parser.py +36 -0
- claude_mpm/cli/utils.py +9 -1
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/config/async_logging_config.yaml +145 -0
- claude_mpm/constants.py +2 -0
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
- claude_mpm/core/agent_registry.py +4 -1
- claude_mpm/core/claude_runner.py +297 -20
- claude_mpm/core/config_paths.py +0 -1
- claude_mpm/core/factories.py +9 -3
- claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/README.md +121 -0
- claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
- claude_mpm/hooks/README.md +96 -0
- claude_mpm/init.py +83 -13
- claude_mpm/schemas/agent_schema.json +435 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +204 -18
- claude_mpm/services/agents/management/agent_management_service.py +2 -1
- claude_mpm/services/agents/registry/agent_registry.py +22 -1
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/validation/agent_validator.py +56 -1
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/METADATA +60 -3
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/RECORD +53 -36
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.2.dist-info → claude_mpm-3.5.6.dist-info}/top_level.txt +0 -0
    
        claude_mpm/cli/README.md
    ADDED
    
    | @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            # Claude MPM CLI Architecture
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This document describes the refactored CLI architecture for claude-mpm.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Overview
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            The CLI has been refactored from a single monolithic `cli.py` file into a modular structure that improves maintainability and code organization.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## Directory Structure
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ```
         | 
| 12 | 
            +
            cli/
         | 
| 13 | 
            +
            ├── __init__.py       # Main entry point - orchestrates the CLI flow
         | 
| 14 | 
            +
            ├── parser.py         # Argument parsing logic - single source of truth for CLI arguments
         | 
| 15 | 
            +
            ├── utils.py          # Shared utility functions
         | 
| 16 | 
            +
            ├── commands/         # Individual command implementations
         | 
| 17 | 
            +
            │   ├── __init__.py
         | 
| 18 | 
            +
            │   ├── run.py        # Default command - runs Claude sessions
         | 
| 19 | 
            +
            │   ├── tickets.py    # Lists tickets
         | 
| 20 | 
            +
            │   ├── info.py       # Shows system information
         | 
| 21 | 
            +
            │   └── agents.py     # Manages agent deployments
         | 
| 22 | 
            +
            └── README.md         # This file
         | 
| 23 | 
            +
            ```
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ## Key Design Decisions
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### 1. Modular Command Structure
         | 
| 28 | 
            +
            Each command is implemented in its own module under `commands/`. This makes it easy to:
         | 
| 29 | 
            +
            - Add new commands without touching existing code
         | 
| 30 | 
            +
            - Test commands in isolation
         | 
| 31 | 
            +
            - Understand what each command does
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ### 2. Centralized Argument Parsing
         | 
| 34 | 
            +
            All argument definitions are in `parser.py`. This provides:
         | 
| 35 | 
            +
            - Single source of truth for CLI arguments
         | 
| 36 | 
            +
            - Reusable argument groups (common arguments, run arguments)
         | 
| 37 | 
            +
            - Clear separation of parsing from execution
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ### 3. Shared Utilities
         | 
| 40 | 
            +
            Common functions are in `utils.py`:
         | 
| 41 | 
            +
            - `get_user_input()` - Handles input from files, stdin, or command line
         | 
| 42 | 
            +
            - `get_agent_versions_display()` - Formats agent version information
         | 
| 43 | 
            +
            - `setup_logging()` - Configures logging based on arguments
         | 
| 44 | 
            +
            - `ensure_directories()` - Creates required directories on first run
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ### 4. Backward Compatibility
         | 
| 47 | 
            +
            The refactoring maintains full backward compatibility:
         | 
| 48 | 
            +
            - `__main__.py` still imports from `claude_mpm.cli`
         | 
| 49 | 
            +
            - The main `cli/__init__.py` exports the same `main()` function
         | 
| 50 | 
            +
            - All existing commands and arguments work exactly as before
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ## Entry Points
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            1. **Package execution**: `python -m claude_mpm`
         | 
| 55 | 
            +
               - Uses `__main__.py` which imports from `cli/__init__.py`
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            2. **Direct import**: `from claude_mpm.cli import main`
         | 
| 58 | 
            +
               - Imports the main function from `cli/__init__.py`
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            3. **Shell script**: `claude-mpm` command
         | 
| 61 | 
            +
               - Calls `python -m claude_mpm` with proper environment setup
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ## Adding New Commands
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            To add a new command:
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            1. Create a new module in `commands/`:
         | 
| 68 | 
            +
            ```python
         | 
| 69 | 
            +
            # commands/mycommand.py
         | 
| 70 | 
            +
            def my_command(args):
         | 
| 71 | 
            +
                """Execute my command."""
         | 
| 72 | 
            +
                # Implementation here
         | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            2. Add the command to `commands/__init__.py`:
         | 
| 76 | 
            +
            ```python
         | 
| 77 | 
            +
            from .mycommand import my_command
         | 
| 78 | 
            +
            ```
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            3. Add parser configuration in `parser.py`:
         | 
| 81 | 
            +
            ```python
         | 
| 82 | 
            +
            # In create_parser()
         | 
| 83 | 
            +
            mycommand_parser = subparsers.add_parser(
         | 
| 84 | 
            +
                "mycommand",
         | 
| 85 | 
            +
                help="Description of my command"
         | 
| 86 | 
            +
            )
         | 
| 87 | 
            +
            # Add command-specific arguments
         | 
| 88 | 
            +
            ```
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            4. Add the command mapping in `cli/__init__.py`:
         | 
| 91 | 
            +
            ```python
         | 
| 92 | 
            +
            # In _execute_command()
         | 
| 93 | 
            +
            command_map = {
         | 
| 94 | 
            +
                # ... existing commands ...
         | 
| 95 | 
            +
                "mycommand": my_command,
         | 
| 96 | 
            +
            }
         | 
| 97 | 
            +
            ```
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            ## Removed Files
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            - `cli_main.py` - Redundant entry point, functionality moved to `__main__.py`
         | 
| 102 | 
            +
            - Original `cli.py` - Split into the modular structure described above
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ## Preserved Files
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            - `cli_enhancements.py` - Experimental Click-based CLI with enhanced features
         | 
| 107 | 
            +
              - Kept for reference and future enhancement ideas
         | 
| 108 | 
            +
              - Not currently used in production
         | 
| @@ -6,10 +6,15 @@ and cleaning agent deployments. | |
| 6 6 | 
             
            """
         | 
| 7 7 |  | 
| 8 8 | 
             
            from pathlib import Path
         | 
| 9 | 
            +
            import json
         | 
| 10 | 
            +
            import yaml
         | 
| 11 | 
            +
            from typing import Dict, Any, Optional
         | 
| 9 12 |  | 
| 10 13 | 
             
            from ...core.logger import get_logger
         | 
| 11 14 | 
             
            from ...constants import AgentCommands
         | 
| 12 15 | 
             
            from ..utils import get_agent_versions_display
         | 
| 16 | 
            +
            from ...core.agent_registry import AgentRegistryAdapter
         | 
| 17 | 
            +
            from ...agents.frontmatter_validator import FrontmatterValidator
         | 
| 13 18 |  | 
| 14 19 |  | 
| 15 20 | 
             
            def manage_agents(args):
         | 
| @@ -29,7 +34,16 @@ def manage_agents(args): | |
| 29 34 |  | 
| 30 35 | 
             
                try:
         | 
| 31 36 | 
             
                    from ...services import AgentDeploymentService
         | 
| 32 | 
            -
                     | 
| 37 | 
            +
                    import os
         | 
| 38 | 
            +
                    from pathlib import Path
         | 
| 39 | 
            +
                    
         | 
| 40 | 
            +
                    # Determine the user's working directory from environment
         | 
| 41 | 
            +
                    # This ensures agents are deployed to the correct directory
         | 
| 42 | 
            +
                    user_working_dir = None
         | 
| 43 | 
            +
                    if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 44 | 
            +
                        user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 45 | 
            +
                    
         | 
| 46 | 
            +
                    deployment_service = AgentDeploymentService(working_directory=user_working_dir)
         | 
| 33 47 |  | 
| 34 48 | 
             
                    if not args.agents_command:
         | 
| 35 49 | 
             
                        # No subcommand - show agent versions
         | 
| @@ -55,6 +69,12 @@ def manage_agents(args): | |
| 55 69 | 
             
                    elif args.agents_command == AgentCommands.CLEAN.value:
         | 
| 56 70 | 
             
                        _clean_agents(args, deployment_service)
         | 
| 57 71 |  | 
| 72 | 
            +
                    elif args.agents_command == AgentCommands.VIEW.value:
         | 
| 73 | 
            +
                        _view_agent(args)
         | 
| 74 | 
            +
                    
         | 
| 75 | 
            +
                    elif args.agents_command == AgentCommands.FIX.value:
         | 
| 76 | 
            +
                        _fix_agents(args)
         | 
| 77 | 
            +
                    
         | 
| 58 78 | 
             
                except ImportError:
         | 
| 59 79 | 
             
                    logger.error("Agent deployment service not available")
         | 
| 60 80 | 
             
                    print("Error: Agent deployment service not available")
         | 
| @@ -71,10 +91,13 @@ def _list_agents(args, deployment_service): | |
| 71 91 | 
             
                currently deployed. This helps them understand the agent ecosystem.
         | 
| 72 92 |  | 
| 73 93 | 
             
                Args:
         | 
| 74 | 
            -
                    args: Command arguments with 'system' and ' | 
| 94 | 
            +
                    args: Command arguments with 'system', 'deployed', and 'by_tier' flags
         | 
| 75 95 | 
             
                    deployment_service: Agent deployment service instance
         | 
| 76 96 | 
             
                """
         | 
| 77 | 
            -
                if args. | 
| 97 | 
            +
                if hasattr(args, 'by_tier') and args.by_tier:
         | 
| 98 | 
            +
                    # List agents grouped by tier
         | 
| 99 | 
            +
                    _list_agents_by_tier()
         | 
| 100 | 
            +
                elif args.system:
         | 
| 78 101 | 
             
                    # List available agent templates
         | 
| 79 102 | 
             
                    print("Available Agent Templates:")
         | 
| 80 103 | 
             
                    print("-" * 80)
         | 
| @@ -114,21 +137,22 @@ def _list_agents(args, deployment_service): | |
| 114 137 |  | 
| 115 138 | 
             
                else:
         | 
| 116 139 | 
             
                    # Default: show usage
         | 
| 117 | 
            -
                    print("Use --system to list system agents  | 
| 140 | 
            +
                    print("Use --system to list system agents, --deployed to list deployed agents, or --by-tier to group by precedence")
         | 
| 118 141 |  | 
| 119 142 |  | 
| 120 143 | 
             
            def _deploy_agents(args, deployment_service, force=False):
         | 
| 121 144 | 
             
                """
         | 
| 122 | 
            -
                Deploy system agents.
         | 
| 145 | 
            +
                Deploy both system and project agents.
         | 
| 123 146 |  | 
| 124 147 | 
             
                WHY: Agents need to be deployed to the working directory for Claude Code to use them.
         | 
| 125 | 
            -
                This function handles both regular and forced deployment.
         | 
| 148 | 
            +
                This function handles both regular and forced deployment, including project-specific agents.
         | 
| 126 149 |  | 
| 127 150 | 
             
                Args:
         | 
| 128 151 | 
             
                    args: Command arguments with optional 'target' path
         | 
| 129 152 | 
             
                    deployment_service: Agent deployment service instance
         | 
| 130 153 | 
             
                    force: Whether to force rebuild all agents
         | 
| 131 154 | 
             
                """
         | 
| 155 | 
            +
                # Deploy system agents first
         | 
| 132 156 | 
             
                if force:
         | 
| 133 157 | 
             
                    print("Force deploying all system agents...")
         | 
| 134 158 | 
             
                else:
         | 
| @@ -136,6 +160,43 @@ def _deploy_agents(args, deployment_service, force=False): | |
| 136 160 |  | 
| 137 161 | 
             
                results = deployment_service.deploy_agents(args.target, force_rebuild=force)
         | 
| 138 162 |  | 
| 163 | 
            +
                # Also deploy project agents if they exist
         | 
| 164 | 
            +
                from pathlib import Path
         | 
| 165 | 
            +
                import os
         | 
| 166 | 
            +
                
         | 
| 167 | 
            +
                # Use the user's working directory if available
         | 
| 168 | 
            +
                if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 169 | 
            +
                    project_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 170 | 
            +
                else:
         | 
| 171 | 
            +
                    project_dir = Path.cwd()
         | 
| 172 | 
            +
                
         | 
| 173 | 
            +
                project_agents_dir = project_dir / '.claude-mpm' / 'agents'
         | 
| 174 | 
            +
                if project_agents_dir.exists():
         | 
| 175 | 
            +
                    json_files = list(project_agents_dir.glob('*.json'))
         | 
| 176 | 
            +
                    if json_files:
         | 
| 177 | 
            +
                        print(f"\nDeploying {len(json_files)} project agents...")
         | 
| 178 | 
            +
                        from claude_mpm.services.agents.deployment.agent_deployment import AgentDeploymentService
         | 
| 179 | 
            +
                        project_service = AgentDeploymentService(
         | 
| 180 | 
            +
                            templates_dir=project_agents_dir,
         | 
| 181 | 
            +
                            base_agent_path=project_agents_dir / 'base_agent.json' if (project_agents_dir / 'base_agent.json').exists() else None,
         | 
| 182 | 
            +
                            working_directory=project_dir  # Pass the project directory
         | 
| 183 | 
            +
                        )
         | 
| 184 | 
            +
                        project_results = project_service.deploy_agents(
         | 
| 185 | 
            +
                            target_dir=args.target if args.target else Path.cwd() / '.claude' / 'agents',
         | 
| 186 | 
            +
                            force_rebuild=force,
         | 
| 187 | 
            +
                            deployment_mode='project'
         | 
| 188 | 
            +
                        )
         | 
| 189 | 
            +
                        
         | 
| 190 | 
            +
                        # Merge project results into main results
         | 
| 191 | 
            +
                        if project_results.get('deployed'):
         | 
| 192 | 
            +
                            results['deployed'].extend(project_results['deployed'])
         | 
| 193 | 
            +
                            print(f"✓ Deployed {len(project_results['deployed'])} project agents")
         | 
| 194 | 
            +
                        if project_results.get('updated'):
         | 
| 195 | 
            +
                            results['updated'].extend(project_results['updated'])
         | 
| 196 | 
            +
                            print(f"✓ Updated {len(project_results['updated'])} project agents")
         | 
| 197 | 
            +
                        if project_results.get('errors'):
         | 
| 198 | 
            +
                            results['errors'].extend(project_results['errors'])
         | 
| 199 | 
            +
                
         | 
| 139 200 | 
             
                if results["deployed"]:
         | 
| 140 201 | 
             
                    print(f"\n✓ Successfully deployed {len(results['deployed'])} agents to {results['target_dir']}")
         | 
| 141 202 | 
             
                    for agent in results["deployed"]:
         | 
| @@ -188,4 +249,309 @@ def _clean_agents(args, deployment_service): | |
| 188 249 | 
             
                if results["errors"]:
         | 
| 189 250 | 
             
                    print("\n❌ Errors during cleanup:")
         | 
| 190 251 | 
             
                    for error in results["errors"]:
         | 
| 191 | 
            -
                        print(f"  - {error}")
         | 
| 252 | 
            +
                        print(f"  - {error}")
         | 
| 253 | 
            +
             | 
| 254 | 
            +
             | 
| 255 | 
            +
            def _list_agents_by_tier():
         | 
| 256 | 
            +
                """
         | 
| 257 | 
            +
                List agents grouped by precedence tier.
         | 
| 258 | 
            +
                
         | 
| 259 | 
            +
                WHY: Users need to understand which agents are active across different tiers
         | 
| 260 | 
            +
                and which version takes precedence when multiple versions exist.
         | 
| 261 | 
            +
                """
         | 
| 262 | 
            +
                try:
         | 
| 263 | 
            +
                    adapter = AgentRegistryAdapter()
         | 
| 264 | 
            +
                    if not adapter.registry:
         | 
| 265 | 
            +
                        print("❌ Could not initialize agent registry")
         | 
| 266 | 
            +
                        return
         | 
| 267 | 
            +
                    
         | 
| 268 | 
            +
                    # Get all agents and group by tier
         | 
| 269 | 
            +
                    all_agents = adapter.registry.list_agents()
         | 
| 270 | 
            +
                    
         | 
| 271 | 
            +
                    # Group agents by tier and name
         | 
| 272 | 
            +
                    tiers = {'project': {}, 'user': {}, 'system': {}}
         | 
| 273 | 
            +
                    agent_names = set()
         | 
| 274 | 
            +
                    
         | 
| 275 | 
            +
                    for agent_id, metadata in all_agents.items():
         | 
| 276 | 
            +
                        tier = metadata.get('tier', 'system')
         | 
| 277 | 
            +
                        if tier in tiers:
         | 
| 278 | 
            +
                            tiers[tier][agent_id] = metadata
         | 
| 279 | 
            +
                            agent_names.add(agent_id)
         | 
| 280 | 
            +
                    
         | 
| 281 | 
            +
                    # Display header
         | 
| 282 | 
            +
                    print("\n" + "=" * 80)
         | 
| 283 | 
            +
                    print(" " * 25 + "AGENT HIERARCHY BY TIER")
         | 
| 284 | 
            +
                    print("=" * 80)
         | 
| 285 | 
            +
                    print("\nPrecedence: PROJECT > USER > SYSTEM")
         | 
| 286 | 
            +
                    print("(Agents in higher tiers override those in lower tiers)\n")
         | 
| 287 | 
            +
                    
         | 
| 288 | 
            +
                    # Display each tier
         | 
| 289 | 
            +
                    tier_order = [('PROJECT', 'project'), ('USER', 'user'), ('SYSTEM', 'system')]
         | 
| 290 | 
            +
                    
         | 
| 291 | 
            +
                    for tier_display, tier_key in tier_order:
         | 
| 292 | 
            +
                        agents = tiers[tier_key]
         | 
| 293 | 
            +
                        print(f"\n{'─' * 35} {tier_display} TIER {'─' * 35}")
         | 
| 294 | 
            +
                        
         | 
| 295 | 
            +
                        if not agents:
         | 
| 296 | 
            +
                            print(f"  No agents at {tier_key} level")
         | 
| 297 | 
            +
                        else:
         | 
| 298 | 
            +
                            # Check paths to determine actual locations
         | 
| 299 | 
            +
                            if tier_key == 'project':
         | 
| 300 | 
            +
                                print(f"  Location: .claude-mpm/agents/ (in current project)")
         | 
| 301 | 
            +
                            elif tier_key == 'user':
         | 
| 302 | 
            +
                                print(f"  Location: ~/.claude-mpm/agents/")
         | 
| 303 | 
            +
                            else:
         | 
| 304 | 
            +
                                print(f"  Location: Built-in framework agents")
         | 
| 305 | 
            +
                            
         | 
| 306 | 
            +
                            print(f"\n  Found {len(agents)} agent(s):\n")
         | 
| 307 | 
            +
                            
         | 
| 308 | 
            +
                            for agent_id, metadata in sorted(agents.items()):
         | 
| 309 | 
            +
                                # Check if this agent is overridden by higher tiers
         | 
| 310 | 
            +
                                is_active = True
         | 
| 311 | 
            +
                                overridden_by = []
         | 
| 312 | 
            +
                                
         | 
| 313 | 
            +
                                for check_tier_display, check_tier_key in tier_order:
         | 
| 314 | 
            +
                                    if check_tier_key == tier_key:
         | 
| 315 | 
            +
                                        break
         | 
| 316 | 
            +
                                    if agent_id in tiers[check_tier_key]:
         | 
| 317 | 
            +
                                        is_active = False
         | 
| 318 | 
            +
                                        overridden_by.append(check_tier_display)
         | 
| 319 | 
            +
                                
         | 
| 320 | 
            +
                                # Display agent info
         | 
| 321 | 
            +
                                status = "✓ ACTIVE" if is_active else f"⊗ OVERRIDDEN by {', '.join(overridden_by)}"
         | 
| 322 | 
            +
                                print(f"    📄 {agent_id:<20} [{status}]")
         | 
| 323 | 
            +
                                
         | 
| 324 | 
            +
                                # Show metadata
         | 
| 325 | 
            +
                                if 'description' in metadata:
         | 
| 326 | 
            +
                                    print(f"       Description: {metadata['description']}")
         | 
| 327 | 
            +
                                if 'path' in metadata:
         | 
| 328 | 
            +
                                    path = Path(metadata['path'])
         | 
| 329 | 
            +
                                    print(f"       File: {path.name}")
         | 
| 330 | 
            +
                                print()
         | 
| 331 | 
            +
                    
         | 
| 332 | 
            +
                    # Summary
         | 
| 333 | 
            +
                    print("\n" + "=" * 80)
         | 
| 334 | 
            +
                    print("SUMMARY:")
         | 
| 335 | 
            +
                    print(f"  Total unique agents: {len(agent_names)}")
         | 
| 336 | 
            +
                    print(f"  Project agents: {len(tiers['project'])}")
         | 
| 337 | 
            +
                    print(f"  User agents: {len(tiers['user'])}")
         | 
| 338 | 
            +
                    print(f"  System agents: {len(tiers['system'])}")
         | 
| 339 | 
            +
                    print("=" * 80 + "\n")
         | 
| 340 | 
            +
                    
         | 
| 341 | 
            +
                except Exception as e:
         | 
| 342 | 
            +
                    print(f"❌ Error listing agents by tier: {e}")
         | 
| 343 | 
            +
             | 
| 344 | 
            +
             | 
| 345 | 
            +
            def _view_agent(args):
         | 
| 346 | 
            +
                """
         | 
| 347 | 
            +
                View detailed information about a specific agent.
         | 
| 348 | 
            +
                
         | 
| 349 | 
            +
                WHY: Users need to inspect agent configurations, frontmatter, and instructions
         | 
| 350 | 
            +
                to understand what an agent does and how it's configured.
         | 
| 351 | 
            +
                
         | 
| 352 | 
            +
                Args:
         | 
| 353 | 
            +
                    args: Command arguments with 'agent_name' attribute
         | 
| 354 | 
            +
                """
         | 
| 355 | 
            +
                if not hasattr(args, 'agent_name') or not args.agent_name:
         | 
| 356 | 
            +
                    print("❌ Please specify an agent name to view")
         | 
| 357 | 
            +
                    print("Usage: claude-mpm agents view <agent_name>")
         | 
| 358 | 
            +
                    return
         | 
| 359 | 
            +
                
         | 
| 360 | 
            +
                try:
         | 
| 361 | 
            +
                    adapter = AgentRegistryAdapter()
         | 
| 362 | 
            +
                    if not adapter.registry:
         | 
| 363 | 
            +
                        print("❌ Could not initialize agent registry")
         | 
| 364 | 
            +
                        return
         | 
| 365 | 
            +
                    
         | 
| 366 | 
            +
                    # Get the agent
         | 
| 367 | 
            +
                    agent = adapter.registry.get_agent(args.agent_name)
         | 
| 368 | 
            +
                    if not agent:
         | 
| 369 | 
            +
                        print(f"❌ Agent '{args.agent_name}' not found")
         | 
| 370 | 
            +
                        print("\nAvailable agents:")
         | 
| 371 | 
            +
                        all_agents = adapter.registry.list_agents()
         | 
| 372 | 
            +
                        for agent_id in sorted(all_agents.keys()):
         | 
| 373 | 
            +
                            print(f"  - {agent_id}")
         | 
| 374 | 
            +
                        return
         | 
| 375 | 
            +
                    
         | 
| 376 | 
            +
                    # Read the agent file
         | 
| 377 | 
            +
                    agent_path = Path(agent.path)
         | 
| 378 | 
            +
                    if not agent_path.exists():
         | 
| 379 | 
            +
                        print(f"❌ Agent file not found: {agent_path}")
         | 
| 380 | 
            +
                        return
         | 
| 381 | 
            +
                    
         | 
| 382 | 
            +
                    with open(agent_path, 'r') as f:
         | 
| 383 | 
            +
                        content = f.read()
         | 
| 384 | 
            +
                    
         | 
| 385 | 
            +
                    # Display agent information
         | 
| 386 | 
            +
                    print("\n" + "=" * 80)
         | 
| 387 | 
            +
                    print(f" AGENT: {agent.name}")
         | 
| 388 | 
            +
                    print("=" * 80)
         | 
| 389 | 
            +
                    
         | 
| 390 | 
            +
                    # Basic info
         | 
| 391 | 
            +
                    print(f"\n📋 BASIC INFORMATION:")
         | 
| 392 | 
            +
                    print(f"  Name: {agent.name}")
         | 
| 393 | 
            +
                    print(f"  Type: {agent.type}")
         | 
| 394 | 
            +
                    print(f"  Tier: {agent.tier.upper()}")
         | 
| 395 | 
            +
                    print(f"  Path: {agent_path}")
         | 
| 396 | 
            +
                    if agent.description:
         | 
| 397 | 
            +
                        print(f"  Description: {agent.description}")
         | 
| 398 | 
            +
                    if agent.specializations:
         | 
| 399 | 
            +
                        print(f"  Specializations: {', '.join(agent.specializations)}")
         | 
| 400 | 
            +
                    
         | 
| 401 | 
            +
                    # Extract and display frontmatter
         | 
| 402 | 
            +
                    if content.startswith("---"):
         | 
| 403 | 
            +
                        try:
         | 
| 404 | 
            +
                            end_marker = content.find("\n---\n", 4)
         | 
| 405 | 
            +
                            if end_marker == -1:
         | 
| 406 | 
            +
                                end_marker = content.find("\n---\r\n", 4)
         | 
| 407 | 
            +
                            
         | 
| 408 | 
            +
                            if end_marker != -1:
         | 
| 409 | 
            +
                                frontmatter_str = content[4:end_marker]
         | 
| 410 | 
            +
                                frontmatter = yaml.safe_load(frontmatter_str)
         | 
| 411 | 
            +
                                
         | 
| 412 | 
            +
                                print(f"\n📝 FRONTMATTER:")
         | 
| 413 | 
            +
                                for key, value in frontmatter.items():
         | 
| 414 | 
            +
                                    if isinstance(value, list):
         | 
| 415 | 
            +
                                        print(f"  {key}: [{', '.join(str(v) for v in value)}]")
         | 
| 416 | 
            +
                                    elif isinstance(value, dict):
         | 
| 417 | 
            +
                                        print(f"  {key}:")
         | 
| 418 | 
            +
                                        for k, v in value.items():
         | 
| 419 | 
            +
                                            print(f"    {k}: {v}")
         | 
| 420 | 
            +
                                    else:
         | 
| 421 | 
            +
                                        print(f"  {key}: {value}")
         | 
| 422 | 
            +
                                
         | 
| 423 | 
            +
                                # Extract instructions preview
         | 
| 424 | 
            +
                                instructions_start = end_marker + 5
         | 
| 425 | 
            +
                                instructions = content[instructions_start:].strip()
         | 
| 426 | 
            +
                                
         | 
| 427 | 
            +
                                if instructions:
         | 
| 428 | 
            +
                                    print(f"\n📖 INSTRUCTIONS PREVIEW (first 500 chars):")
         | 
| 429 | 
            +
                                    print("  " + "-" * 76)
         | 
| 430 | 
            +
                                    preview = instructions[:500]
         | 
| 431 | 
            +
                                    if len(instructions) > 500:
         | 
| 432 | 
            +
                                        preview += "...\n\n  [Truncated - {:.1f}KB total]".format(len(instructions) / 1024)
         | 
| 433 | 
            +
                                    
         | 
| 434 | 
            +
                                    for line in preview.split('\n'):
         | 
| 435 | 
            +
                                        print(f"  {line}")
         | 
| 436 | 
            +
                                    print("  " + "-" * 76)
         | 
| 437 | 
            +
                        except Exception as e:
         | 
| 438 | 
            +
                            print(f"\n⚠️  Could not parse frontmatter: {e}")
         | 
| 439 | 
            +
                    else:
         | 
| 440 | 
            +
                        print(f"\n⚠️  No frontmatter found in agent file")
         | 
| 441 | 
            +
                    
         | 
| 442 | 
            +
                    # File stats
         | 
| 443 | 
            +
                    import os
         | 
| 444 | 
            +
                    stat = os.stat(agent_path)
         | 
| 445 | 
            +
                    from datetime import datetime
         | 
| 446 | 
            +
                    modified = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
         | 
| 447 | 
            +
                    print(f"\n📊 FILE STATS:")
         | 
| 448 | 
            +
                    print(f"  Size: {stat.st_size:,} bytes")
         | 
| 449 | 
            +
                    print(f"  Last modified: {modified}")
         | 
| 450 | 
            +
                    
         | 
| 451 | 
            +
                    print("\n" + "=" * 80 + "\n")
         | 
| 452 | 
            +
                    
         | 
| 453 | 
            +
                except Exception as e:
         | 
| 454 | 
            +
                    print(f"❌ Error viewing agent: {e}")
         | 
| 455 | 
            +
             | 
| 456 | 
            +
             | 
| 457 | 
            +
            def _fix_agents(args):
         | 
| 458 | 
            +
                """
         | 
| 459 | 
            +
                Fix agent frontmatter issues using FrontmatterValidator.
         | 
| 460 | 
            +
                
         | 
| 461 | 
            +
                WHY: Agent files may have formatting issues in their frontmatter that prevent
         | 
| 462 | 
            +
                proper loading. This command automatically fixes common issues.
         | 
| 463 | 
            +
                
         | 
| 464 | 
            +
                Args:
         | 
| 465 | 
            +
                    args: Command arguments with 'agent_name', 'dry_run', and 'all' flags
         | 
| 466 | 
            +
                """
         | 
| 467 | 
            +
                validator = FrontmatterValidator()
         | 
| 468 | 
            +
                
         | 
| 469 | 
            +
                try:
         | 
| 470 | 
            +
                    adapter = AgentRegistryAdapter()
         | 
| 471 | 
            +
                    if not adapter.registry:
         | 
| 472 | 
            +
                        print("❌ Could not initialize agent registry")
         | 
| 473 | 
            +
                        return
         | 
| 474 | 
            +
                    
         | 
| 475 | 
            +
                    # Determine which agents to fix
         | 
| 476 | 
            +
                    agents_to_fix = []
         | 
| 477 | 
            +
                    
         | 
| 478 | 
            +
                    if hasattr(args, 'all') and args.all:
         | 
| 479 | 
            +
                        # Fix all agents
         | 
| 480 | 
            +
                        all_agents = adapter.registry.list_agents()
         | 
| 481 | 
            +
                        for agent_id, metadata in all_agents.items():
         | 
| 482 | 
            +
                            agents_to_fix.append((agent_id, metadata['path']))
         | 
| 483 | 
            +
                        print(f"\n🔧 Checking {len(agents_to_fix)} agent(s) for frontmatter issues...\n")
         | 
| 484 | 
            +
                    elif hasattr(args, 'agent_name') and args.agent_name:
         | 
| 485 | 
            +
                        # Fix specific agent
         | 
| 486 | 
            +
                        agent = adapter.registry.get_agent(args.agent_name)
         | 
| 487 | 
            +
                        if not agent:
         | 
| 488 | 
            +
                            print(f"❌ Agent '{args.agent_name}' not found")
         | 
| 489 | 
            +
                            return
         | 
| 490 | 
            +
                        agents_to_fix.append((agent.name, agent.path))
         | 
| 491 | 
            +
                        print(f"\n🔧 Checking agent '{agent.name}' for frontmatter issues...\n")
         | 
| 492 | 
            +
                    else:
         | 
| 493 | 
            +
                        print("❌ Please specify an agent name or use --all to fix all agents")
         | 
| 494 | 
            +
                        print("Usage: claude-mpm agents fix [agent_name] [--dry-run] [--all]")
         | 
| 495 | 
            +
                        return
         | 
| 496 | 
            +
                    
         | 
| 497 | 
            +
                    dry_run = hasattr(args, 'dry_run') and args.dry_run
         | 
| 498 | 
            +
                    if dry_run:
         | 
| 499 | 
            +
                        print("🔍 DRY RUN MODE - No changes will be made\n")
         | 
| 500 | 
            +
                    
         | 
| 501 | 
            +
                    # Process each agent
         | 
| 502 | 
            +
                    total_issues = 0
         | 
| 503 | 
            +
                    total_fixed = 0
         | 
| 504 | 
            +
                    
         | 
| 505 | 
            +
                    for agent_name, agent_path in agents_to_fix:
         | 
| 506 | 
            +
                        path = Path(agent_path)
         | 
| 507 | 
            +
                        if not path.exists():
         | 
| 508 | 
            +
                            print(f"⚠️  Skipping {agent_name}: File not found at {path}")
         | 
| 509 | 
            +
                            continue
         | 
| 510 | 
            +
                        
         | 
| 511 | 
            +
                        print(f"📄 {agent_name}:")
         | 
| 512 | 
            +
                        
         | 
| 513 | 
            +
                        # Validate and potentially fix
         | 
| 514 | 
            +
                        result = validator.correct_file(path, dry_run=dry_run)
         | 
| 515 | 
            +
                        
         | 
| 516 | 
            +
                        if result.is_valid and not result.corrections:
         | 
| 517 | 
            +
                            print("  ✓ No issues found")
         | 
| 518 | 
            +
                        else:
         | 
| 519 | 
            +
                            if result.errors:
         | 
| 520 | 
            +
                                print("  ❌ Errors:")
         | 
| 521 | 
            +
                                for error in result.errors:
         | 
| 522 | 
            +
                                    print(f"    - {error}")
         | 
| 523 | 
            +
                                total_issues += len(result.errors)
         | 
| 524 | 
            +
                            
         | 
| 525 | 
            +
                            if result.warnings:
         | 
| 526 | 
            +
                                print("  ⚠️  Warnings:")
         | 
| 527 | 
            +
                                for warning in result.warnings:
         | 
| 528 | 
            +
                                    print(f"    - {warning}")
         | 
| 529 | 
            +
                                total_issues += len(result.warnings)
         | 
| 530 | 
            +
                            
         | 
| 531 | 
            +
                            if result.corrections:
         | 
| 532 | 
            +
                                if dry_run:
         | 
| 533 | 
            +
                                    print("  🔧 Would fix:")
         | 
| 534 | 
            +
                                else:
         | 
| 535 | 
            +
                                    print("  ✓ Fixed:")
         | 
| 536 | 
            +
                                    total_fixed += len(result.corrections)
         | 
| 537 | 
            +
                                for correction in result.corrections:
         | 
| 538 | 
            +
                                    print(f"    - {correction}")
         | 
| 539 | 
            +
                        
         | 
| 540 | 
            +
                        print()
         | 
| 541 | 
            +
                    
         | 
| 542 | 
            +
                    # Summary
         | 
| 543 | 
            +
                    print("=" * 80)
         | 
| 544 | 
            +
                    print("SUMMARY:")
         | 
| 545 | 
            +
                    print(f"  Agents checked: {len(agents_to_fix)}")
         | 
| 546 | 
            +
                    print(f"  Total issues found: {total_issues}")
         | 
| 547 | 
            +
                    if dry_run:
         | 
| 548 | 
            +
                        print(f"  Issues that would be fixed: {sum(1 for _, path in agents_to_fix if validator.validate_file(Path(path)).corrections)}")
         | 
| 549 | 
            +
                        print("\n💡 Run without --dry-run to apply fixes")
         | 
| 550 | 
            +
                    else:
         | 
| 551 | 
            +
                        print(f"  Issues fixed: {total_fixed}")
         | 
| 552 | 
            +
                        if total_fixed > 0:
         | 
| 553 | 
            +
                            print("\n✓ Frontmatter issues have been fixed!")
         | 
| 554 | 
            +
                    print("=" * 80 + "\n")
         | 
| 555 | 
            +
                    
         | 
| 556 | 
            +
                except Exception as e:
         | 
| 557 | 
            +
                    print(f"❌ Error fixing agents: {e}")
         | 
    
        claude_mpm/cli/commands/run.py
    CHANGED
    
    | @@ -281,17 +281,10 @@ def run_session(args): | |
| 281 281 | 
             
                    websocket_port=websocket_port
         | 
| 282 282 | 
             
                )
         | 
| 283 283 |  | 
| 284 | 
            -
                #  | 
| 285 | 
            -
                #  | 
| 286 | 
            -
                 | 
| 287 | 
            -
             | 
| 288 | 
            -
                    project_markers = ['.git', 'pyproject.toml', 'package.json', 'requirements.txt']
         | 
| 289 | 
            -
                    cwd = Path.cwd()
         | 
| 290 | 
            -
                    is_project = any((cwd / marker).exists() for marker in project_markers)
         | 
| 291 | 
            -
                    
         | 
| 292 | 
            -
                    if is_project:
         | 
| 293 | 
            -
                        logger.debug("Detected project directory, ensuring agents are available locally")
         | 
| 294 | 
            -
                        runner.ensure_project_agents()
         | 
| 284 | 
            +
                # Agent deployment is handled by ClaudeRunner.setup_agents() and 
         | 
| 285 | 
            +
                # ClaudeRunner.deploy_project_agents_to_claude() which are called
         | 
| 286 | 
            +
                # in both run_interactive() and run_oneshot() methods.
         | 
| 287 | 
            +
                # No need for redundant deployment here.
         | 
| 295 288 |  | 
| 296 289 | 
             
                # Set browser opening flag for monitor mode
         | 
| 297 290 | 
             
                if monitor_mode:
         | 
    
        claude_mpm/cli/parser.py
    CHANGED
    
    | @@ -306,6 +306,42 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp | |
| 306 306 | 
             
                    action="store_true", 
         | 
| 307 307 | 
             
                    help="List deployed agents"
         | 
| 308 308 | 
             
                )
         | 
| 309 | 
            +
                list_agents_parser.add_argument(
         | 
| 310 | 
            +
                    "--by-tier",
         | 
| 311 | 
            +
                    action="store_true",
         | 
| 312 | 
            +
                    help="List agents grouped by precedence tier (PROJECT > USER > SYSTEM)"
         | 
| 313 | 
            +
                )
         | 
| 314 | 
            +
                
         | 
| 315 | 
            +
                # View agent details
         | 
| 316 | 
            +
                view_agent_parser = agents_subparsers.add_parser(
         | 
| 317 | 
            +
                    AgentCommands.VIEW.value,
         | 
| 318 | 
            +
                    help="View detailed information about a specific agent"
         | 
| 319 | 
            +
                )
         | 
| 320 | 
            +
                view_agent_parser.add_argument(
         | 
| 321 | 
            +
                    "agent_name",
         | 
| 322 | 
            +
                    help="Name of the agent to view"
         | 
| 323 | 
            +
                )
         | 
| 324 | 
            +
                
         | 
| 325 | 
            +
                # Fix agent frontmatter
         | 
| 326 | 
            +
                fix_agents_parser = agents_subparsers.add_parser(
         | 
| 327 | 
            +
                    AgentCommands.FIX.value,
         | 
| 328 | 
            +
                    help="Fix agent frontmatter issues"
         | 
| 329 | 
            +
                )
         | 
| 330 | 
            +
                fix_agents_parser.add_argument(
         | 
| 331 | 
            +
                    "agent_name",
         | 
| 332 | 
            +
                    nargs="?",
         | 
| 333 | 
            +
                    help="Name of specific agent to fix (fix all if not specified with --all)"
         | 
| 334 | 
            +
                )
         | 
| 335 | 
            +
                fix_agents_parser.add_argument(
         | 
| 336 | 
            +
                    "--dry-run",
         | 
| 337 | 
            +
                    action="store_true",
         | 
| 338 | 
            +
                    help="Preview changes without applying them"
         | 
| 339 | 
            +
                )
         | 
| 340 | 
            +
                fix_agents_parser.add_argument(
         | 
| 341 | 
            +
                    "--all",
         | 
| 342 | 
            +
                    action="store_true",
         | 
| 343 | 
            +
                    help="Fix all agents"
         | 
| 344 | 
            +
                )
         | 
| 309 345 |  | 
| 310 346 | 
             
                # Deploy agents
         | 
| 311 347 | 
             
                deploy_agents_parser = agents_subparsers.add_parser(
         | 
    
        claude_mpm/cli/utils.py
    CHANGED
    
    | @@ -61,7 +61,15 @@ def get_agent_versions_display() -> Optional[str]: | |
| 61 61 | 
             
                """
         | 
| 62 62 | 
             
                try:
         | 
| 63 63 | 
             
                    from ..services import AgentDeploymentService
         | 
| 64 | 
            -
                     | 
| 64 | 
            +
                    import os
         | 
| 65 | 
            +
                    from pathlib import Path
         | 
| 66 | 
            +
                    
         | 
| 67 | 
            +
                    # Determine the user's working directory from environment
         | 
| 68 | 
            +
                    user_working_dir = None
         | 
| 69 | 
            +
                    if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 70 | 
            +
                        user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 71 | 
            +
                    
         | 
| 72 | 
            +
                    deployment_service = AgentDeploymentService(working_directory=user_working_dir)
         | 
| 65 73 |  | 
| 66 74 | 
             
                    # Get deployed agents
         | 
| 67 75 | 
             
                    verification = deployment_service.verify_deployment()
         |