claude-mpm 3.5.4__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +96 -23
- claude_mpm/agents/BASE_PM.md +273 -0
- claude_mpm/agents/INSTRUCTIONS.md +114 -102
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +36 -1
- claude_mpm/agents/async_agent_loader.py +421 -0
- claude_mpm/agents/templates/code_analyzer.json +81 -0
- claude_mpm/agents/templates/data_engineer.json +18 -3
- claude_mpm/agents/templates/documentation.json +18 -3
- claude_mpm/agents/templates/engineer.json +19 -4
- claude_mpm/agents/templates/ops.json +18 -3
- claude_mpm/agents/templates/qa.json +20 -4
- claude_mpm/agents/templates/research.json +20 -4
- claude_mpm/agents/templates/security.json +18 -3
- claude_mpm/agents/templates/version_control.json +16 -3
- claude_mpm/cli/README.md +108 -0
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +233 -6
- claude_mpm/cli/commands/aggregate.py +462 -0
- claude_mpm/cli/commands/config.py +277 -0
- claude_mpm/cli/commands/run.py +228 -47
- claude_mpm/cli/parser.py +176 -1
- 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 +19 -0
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
- claude_mpm/core/claude_runner.py +413 -76
- claude_mpm/core/config.py +161 -4
- claude_mpm/core/config_paths.py +0 -1
- claude_mpm/core/factories.py +9 -3
- claude_mpm/core/framework_loader.py +81 -0
- 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/hooks/claude_hooks/hook_handler.py +391 -9
- claude_mpm/init.py +123 -18
- claude_mpm/models/agent_session.py +511 -0
- claude_mpm/schemas/agent_schema.json +435 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/start_activity_logging.py +86 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +326 -24
- claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
- claude_mpm/services/agents/management/agent_management_service.py +2 -1
- claude_mpm/services/event_aggregator.py +547 -0
- 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/utils/agent_dependency_loader.py +655 -0
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/dependency_cache.py +376 -0
- claude_mpm/utils/dependency_strategies.py +343 -0
- claude_mpm/utils/environment_context.py +310 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/METADATA +87 -1
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/RECORD +67 -37
- claude_mpm/agents/templates/pm.json +0 -122
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/top_level.txt +0 -0
| @@ -37,6 +37,7 @@ from typing import Optional, List, Dict, Any | |
| 37 37 | 
             
            from claude_mpm.core.logger import get_logger
         | 
| 38 38 | 
             
            from claude_mpm.constants import EnvironmentVars, Paths, AgentMetadata
         | 
| 39 39 | 
             
            from claude_mpm.config.paths import paths
         | 
| 40 | 
            +
            from claude_mpm.core.config import Config
         | 
| 40 41 |  | 
| 41 42 |  | 
| 42 43 | 
             
            class AgentDeploymentService:
         | 
| @@ -70,13 +71,14 @@ class AgentDeploymentService: | |
| 70 71 | 
             
                - YAML generation capabilities
         | 
| 71 72 | 
             
                """
         | 
| 72 73 |  | 
| 73 | 
            -
                def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None):
         | 
| 74 | 
            +
                def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None, working_directory: Optional[Path] = None):
         | 
| 74 75 | 
             
                    """
         | 
| 75 76 | 
             
                    Initialize agent deployment service.
         | 
| 76 77 |  | 
| 77 78 | 
             
                    Args:
         | 
| 78 79 | 
             
                        templates_dir: Directory containing agent JSON files
         | 
| 79 80 | 
             
                        base_agent_path: Path to base_agent.md file
         | 
| 81 | 
            +
                        working_directory: User's working directory (for project agents)
         | 
| 80 82 |  | 
| 81 83 | 
             
                    METRICS OPPORTUNITY: Track initialization performance:
         | 
| 82 84 | 
             
                    - Template directory scan time
         | 
| @@ -100,6 +102,17 @@ class AgentDeploymentService: | |
| 100 102 | 
             
                        'deployment_errors': {}  # Track error types and frequencies
         | 
| 101 103 | 
             
                    }
         | 
| 102 104 |  | 
| 105 | 
            +
                    # Determine the actual working directory
         | 
| 106 | 
            +
                    # Priority: working_directory param > CLAUDE_MPM_USER_PWD env var > current directory
         | 
| 107 | 
            +
                    if working_directory:
         | 
| 108 | 
            +
                        self.working_directory = Path(working_directory)
         | 
| 109 | 
            +
                    elif 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 110 | 
            +
                        self.working_directory = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 111 | 
            +
                    else:
         | 
| 112 | 
            +
                        self.working_directory = Path.cwd()
         | 
| 113 | 
            +
                    
         | 
| 114 | 
            +
                    self.logger.info(f"Working directory for deployment: {self.working_directory}")
         | 
| 115 | 
            +
                    
         | 
| 103 116 | 
             
                    # Find templates directory using centralized path management
         | 
| 104 117 | 
             
                    if templates_dir:
         | 
| 105 118 | 
             
                        self.templates_dir = Path(templates_dir)
         | 
| @@ -119,7 +132,7 @@ class AgentDeploymentService: | |
| 119 132 | 
             
                    self.logger.info(f"Templates directory: {self.templates_dir}")
         | 
| 120 133 | 
             
                    self.logger.info(f"Base agent path: {self.base_agent_path}")
         | 
| 121 134 |  | 
| 122 | 
            -
                def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update") -> Dict[str, Any]:
         | 
| 135 | 
            +
                def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update", config: Optional[Config] = None, use_async: bool = True) -> Dict[str, Any]:
         | 
| 123 136 | 
             
                    """
         | 
| 124 137 | 
             
                    Build and deploy agents by combining base_agent.md with templates.
         | 
| 125 138 | 
             
                    Also deploys system instructions for PM framework.
         | 
| @@ -128,6 +141,12 @@ class AgentDeploymentService: | |
| 128 141 | 
             
                    - "update": Normal update mode - skip agents with matching versions (default)
         | 
| 129 142 | 
             
                    - "project": Project deployment mode - always deploy all agents regardless of version
         | 
| 130 143 |  | 
| 144 | 
            +
                    CONFIGURATION:
         | 
| 145 | 
            +
                    The config parameter or default configuration is used to determine:
         | 
| 146 | 
            +
                    - Which agents to exclude from deployment
         | 
| 147 | 
            +
                    - Case sensitivity for agent name matching
         | 
| 148 | 
            +
                    - Whether to exclude agent dependencies
         | 
| 149 | 
            +
                    
         | 
| 131 150 | 
             
                    METRICS COLLECTED:
         | 
| 132 151 | 
             
                    - Deployment start/end timestamps
         | 
| 133 152 | 
             
                    - Individual agent deployment durations
         | 
| @@ -137,6 +156,7 @@ class AgentDeploymentService: | |
| 137 156 | 
             
                    - Error type frequencies
         | 
| 138 157 |  | 
| 139 158 | 
             
                    OPERATIONAL FLOW:
         | 
| 159 | 
            +
                    0. Validates and repairs broken frontmatter in existing agents (Step 0)
         | 
| 140 160 | 
             
                    1. Validates target directory (creates if needed)
         | 
| 141 161 | 
             
                    2. Loads base agent configuration
         | 
| 142 162 | 
             
                    3. Discovers all agent templates
         | 
| @@ -168,6 +188,8 @@ class AgentDeploymentService: | |
| 168 188 | 
             
                        target_dir: Target directory for agents (default: .claude/agents/)
         | 
| 169 189 | 
             
                        force_rebuild: Force rebuild even if agents exist (useful for troubleshooting)
         | 
| 170 190 | 
             
                        deployment_mode: "update" for version-aware updates, "project" for always deploy
         | 
| 191 | 
            +
                        config: Optional configuration object (loads default if not provided)
         | 
| 192 | 
            +
                        use_async: Use async operations for 50-70% faster deployment (default: True)
         | 
| 171 193 |  | 
| 172 194 | 
             
                    Returns:
         | 
| 173 195 | 
             
                        Dictionary with deployment results:
         | 
| @@ -178,13 +200,73 @@ class AgentDeploymentService: | |
| 178 200 | 
             
                        - skipped: List of unchanged agents
         | 
| 179 201 | 
             
                        - errors: List of deployment errors
         | 
| 180 202 | 
             
                        - total: Total number of agents processed
         | 
| 203 | 
            +
                        - repaired: List of agents with repaired frontmatter
         | 
| 181 204 | 
             
                    """
         | 
| 182 205 | 
             
                    # METRICS: Record deployment start time for performance tracking
         | 
| 183 206 | 
             
                    deployment_start_time = time.time()
         | 
| 184 207 |  | 
| 208 | 
            +
                    # Try async deployment for better performance if requested
         | 
| 209 | 
            +
                    if use_async:
         | 
| 210 | 
            +
                        try:
         | 
| 211 | 
            +
                            from .async_agent_deployment import deploy_agents_async_wrapper
         | 
| 212 | 
            +
                            self.logger.info("Using async deployment for improved performance")
         | 
| 213 | 
            +
                            
         | 
| 214 | 
            +
                            # Run async deployment
         | 
| 215 | 
            +
                            results = deploy_agents_async_wrapper(
         | 
| 216 | 
            +
                                templates_dir=self.templates_dir,
         | 
| 217 | 
            +
                                base_agent_path=self.base_agent_path,
         | 
| 218 | 
            +
                                working_directory=self.working_directory,
         | 
| 219 | 
            +
                                target_dir=target_dir,
         | 
| 220 | 
            +
                                force_rebuild=force_rebuild,
         | 
| 221 | 
            +
                                config=config
         | 
| 222 | 
            +
                            )
         | 
| 223 | 
            +
                            
         | 
| 224 | 
            +
                            # Add metrics about async vs sync
         | 
| 225 | 
            +
                            if 'metrics' in results:
         | 
| 226 | 
            +
                                results['metrics']['deployment_method'] = 'async'
         | 
| 227 | 
            +
                                duration_ms = results['metrics'].get('duration_ms', 0)
         | 
| 228 | 
            +
                                self.logger.info(f"Async deployment completed in {duration_ms:.1f}ms")
         | 
| 229 | 
            +
                                
         | 
| 230 | 
            +
                                # Update internal metrics
         | 
| 231 | 
            +
                                self._deployment_metrics['total_deployments'] += 1
         | 
| 232 | 
            +
                                if not results.get('errors'):
         | 
| 233 | 
            +
                                    self._deployment_metrics['successful_deployments'] += 1
         | 
| 234 | 
            +
                                else:
         | 
| 235 | 
            +
                                    self._deployment_metrics['failed_deployments'] += 1
         | 
| 236 | 
            +
                            
         | 
| 237 | 
            +
                            return results
         | 
| 238 | 
            +
                            
         | 
| 239 | 
            +
                        except ImportError:
         | 
| 240 | 
            +
                            self.logger.warning("Async deployment not available, falling back to sync")
         | 
| 241 | 
            +
                        except Exception as e:
         | 
| 242 | 
            +
                            self.logger.warning(f"Async deployment failed, falling back to sync: {e}")
         | 
| 243 | 
            +
                    
         | 
| 244 | 
            +
                    # Continue with synchronous deployment
         | 
| 245 | 
            +
                    self.logger.info("Using synchronous deployment")
         | 
| 246 | 
            +
                    
         | 
| 247 | 
            +
                    # Load configuration if not provided
         | 
| 248 | 
            +
                    if config is None:
         | 
| 249 | 
            +
                        config = Config()
         | 
| 250 | 
            +
                    
         | 
| 251 | 
            +
                    # Get agent exclusion configuration
         | 
| 252 | 
            +
                    excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 253 | 
            +
                    case_sensitive = config.get('agent_deployment.case_sensitive', False)
         | 
| 254 | 
            +
                    exclude_dependencies = config.get('agent_deployment.exclude_dependencies', False)
         | 
| 255 | 
            +
                    
         | 
| 256 | 
            +
                    # Normalize excluded agents list for comparison
         | 
| 257 | 
            +
                    if not case_sensitive:
         | 
| 258 | 
            +
                        excluded_agents = [agent.lower() for agent in excluded_agents]
         | 
| 259 | 
            +
                    
         | 
| 260 | 
            +
                    # Log exclusion configuration if agents are being excluded
         | 
| 261 | 
            +
                    if excluded_agents:
         | 
| 262 | 
            +
                        self.logger.info(f"Excluding agents from deployment: {excluded_agents}")
         | 
| 263 | 
            +
                        self.logger.debug(f"Case sensitive matching: {case_sensitive}")
         | 
| 264 | 
            +
                        self.logger.debug(f"Exclude dependencies: {exclude_dependencies}")
         | 
| 265 | 
            +
                    
         | 
| 185 266 | 
             
                    if not target_dir:
         | 
| 186 | 
            -
                        # Default to  | 
| 187 | 
            -
                         | 
| 267 | 
            +
                        # Default to working directory's .claude/agents directory (not home)
         | 
| 268 | 
            +
                        # This ensures we deploy to the user's project directory, not the framework directory
         | 
| 269 | 
            +
                        agents_dir = self.working_directory / ".claude" / "agents"
         | 
| 188 270 | 
             
                    else:
         | 
| 189 271 | 
             
                        # If target_dir provided, use it directly (caller decides structure)
         | 
| 190 272 | 
             
                        # This allows for both passing a project dir or the full agents path
         | 
| @@ -211,6 +293,7 @@ class AgentDeploymentService: | |
| 211 293 | 
             
                        "updated": [],
         | 
| 212 294 | 
             
                        "migrated": [],  # Track agents migrated from old format
         | 
| 213 295 | 
             
                        "converted": [],  # Track YAML to MD conversions
         | 
| 296 | 
            +
                        "repaired": [],  # Track agents with repaired frontmatter
         | 
| 214 297 | 
             
                        "total": 0,
         | 
| 215 298 | 
             
                        # METRICS: Add detailed timing and performance data to results
         | 
| 216 299 | 
             
                        "metrics": {
         | 
| @@ -226,6 +309,16 @@ class AgentDeploymentService: | |
| 226 309 | 
             
                    try:
         | 
| 227 310 | 
             
                        # Create agents directory if needed
         | 
| 228 311 | 
             
                        agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 312 | 
            +
                        
         | 
| 313 | 
            +
                        # STEP 0: Validate and repair broken frontmatter in existing agents
         | 
| 314 | 
            +
                        # This ensures all existing agents have valid YAML frontmatter before deploying new ones
         | 
| 315 | 
            +
                        repair_results = self._validate_and_repair_existing_agents(agents_dir)
         | 
| 316 | 
            +
                        if repair_results["repaired"]:
         | 
| 317 | 
            +
                            results["repaired"] = repair_results["repaired"]
         | 
| 318 | 
            +
                            self.logger.info(f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents")
         | 
| 319 | 
            +
                            for agent_name in repair_results["repaired"]:
         | 
| 320 | 
            +
                                self.logger.debug(f"  - Repaired: {agent_name}")
         | 
| 321 | 
            +
                        
         | 
| 229 322 | 
             
                        # Determine source tier for logging
         | 
| 230 323 | 
             
                        source_tier = "SYSTEM"
         | 
| 231 324 | 
             
                        if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
         | 
| @@ -268,16 +361,54 @@ class AgentDeploymentService: | |
| 268 361 |  | 
| 269 362 | 
             
                        # Get all template files
         | 
| 270 363 | 
             
                        template_files = list(self.templates_dir.glob("*.json"))
         | 
| 271 | 
            -
                         | 
| 272 | 
            -
                         | 
| 273 | 
            -
                         | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
             | 
| 278 | 
            -
                         | 
| 364 | 
            +
                        
         | 
| 365 | 
            +
                        # Build the combined exclusion set
         | 
| 366 | 
            +
                        # Start with hardcoded exclusions (these are ALWAYS excluded)
         | 
| 367 | 
            +
                        hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
         | 
| 368 | 
            +
                        
         | 
| 369 | 
            +
                        # Add user-configured exclusions
         | 
| 370 | 
            +
                        user_exclusions = set()
         | 
| 371 | 
            +
                        for agent in excluded_agents:
         | 
| 372 | 
            +
                            if case_sensitive:
         | 
| 373 | 
            +
                                user_exclusions.add(agent)
         | 
| 374 | 
            +
                            else:
         | 
| 375 | 
            +
                                # For case-insensitive, we'll check during filtering
         | 
| 376 | 
            +
                                user_exclusions.add(agent.lower())
         | 
| 377 | 
            +
                        
         | 
| 378 | 
            +
                        # Filter out excluded agents
         | 
| 379 | 
            +
                        filtered_files = []
         | 
| 380 | 
            +
                        excluded_count = 0
         | 
| 381 | 
            +
                        for f in template_files:
         | 
| 382 | 
            +
                            agent_name = f.stem
         | 
| 383 | 
            +
                            
         | 
| 384 | 
            +
                            # Check hardcoded exclusions (always case-sensitive)
         | 
| 385 | 
            +
                            if agent_name in hardcoded_exclusions:
         | 
| 386 | 
            +
                                self.logger.debug(f"Excluding {agent_name}: hardcoded system exclusion")
         | 
| 387 | 
            +
                                excluded_count += 1
         | 
| 388 | 
            +
                                continue
         | 
| 389 | 
            +
                            
         | 
| 390 | 
            +
                            # Check file patterns
         | 
| 391 | 
            +
                            if agent_name.startswith(".") or agent_name.endswith(".backup"):
         | 
| 392 | 
            +
                                self.logger.debug(f"Excluding {agent_name}: file pattern exclusion")
         | 
| 393 | 
            +
                                excluded_count += 1
         | 
| 394 | 
            +
                                continue
         | 
| 395 | 
            +
                            
         | 
| 396 | 
            +
                            # Check user-configured exclusions
         | 
| 397 | 
            +
                            compare_name = agent_name.lower() if not case_sensitive else agent_name
         | 
| 398 | 
            +
                            if compare_name in user_exclusions:
         | 
| 399 | 
            +
                                self.logger.info(f"Excluding {agent_name}: user-configured exclusion")
         | 
| 400 | 
            +
                                excluded_count += 1
         | 
| 401 | 
            +
                                continue
         | 
| 402 | 
            +
                            
         | 
| 403 | 
            +
                            # Agent is not excluded, add to filtered list
         | 
| 404 | 
            +
                            filtered_files.append(f)
         | 
| 405 | 
            +
                        
         | 
| 406 | 
            +
                        template_files = filtered_files
         | 
| 279 407 | 
             
                        results["total"] = len(template_files)
         | 
| 280 408 |  | 
| 409 | 
            +
                        if excluded_count > 0:
         | 
| 410 | 
            +
                            self.logger.info(f"Excluded {excluded_count} agents from deployment")
         | 
| 411 | 
            +
                        
         | 
| 281 412 | 
             
                        for template_file in template_files:
         | 
| 282 413 | 
             
                            try:
         | 
| 283 414 | 
             
                                # METRICS: Track individual agent deployment time
         | 
| @@ -373,6 +504,7 @@ class AgentDeploymentService: | |
| 373 504 | 
             
                            f"updated {len(results['updated'])}, "
         | 
| 374 505 | 
             
                            f"migrated {len(results['migrated'])}, "
         | 
| 375 506 | 
             
                            f"converted {len(results['converted'])} YAML files, "
         | 
| 507 | 
            +
                            f"repaired {len(results['repaired'])} frontmatter, "
         | 
| 376 508 | 
             
                            f"skipped {len(results['skipped'])}, "
         | 
| 377 509 | 
             
                            f"errors: {len(results['errors'])}"
         | 
| 378 510 | 
             
                        )
         | 
| @@ -864,7 +996,8 @@ temperature: {temperature}""" | |
| 864 996 | 
             
                        Dictionary of environment variables set for verification
         | 
| 865 997 | 
             
                    """
         | 
| 866 998 | 
             
                    if not config_dir:
         | 
| 867 | 
            -
                         | 
| 999 | 
            +
                        # Use the working directory determined during initialization
         | 
| 1000 | 
            +
                        config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
         | 
| 868 1001 |  | 
| 869 1002 | 
             
                    env_vars = {}
         | 
| 870 1003 |  | 
| @@ -930,7 +1063,8 @@ temperature: {temperature}""" | |
| 930 1063 | 
             
                        - warnings: List of potential issues
         | 
| 931 1064 | 
             
                    """
         | 
| 932 1065 | 
             
                    if not config_dir:
         | 
| 933 | 
            -
                         | 
| 1066 | 
            +
                        # Use the working directory determined during initialization
         | 
| 1067 | 
            +
                        config_dir = self.working_directory / ".claude"
         | 
| 934 1068 |  | 
| 935 1069 | 
             
                    results = {
         | 
| 936 1070 | 
             
                        "config_dir": str(config_dir),
         | 
| @@ -953,6 +1087,17 @@ temperature: {temperature}""" | |
| 953 1087 |  | 
| 954 1088 | 
             
                    # List deployed agents
         | 
| 955 1089 | 
             
                    agent_files = list(agents_dir.glob("*.md"))
         | 
| 1090 | 
            +
                    
         | 
| 1091 | 
            +
                    # Get exclusion configuration for logging purposes
         | 
| 1092 | 
            +
                    try:
         | 
| 1093 | 
            +
                        from claude_mpm.core.config import Config
         | 
| 1094 | 
            +
                        config = Config()
         | 
| 1095 | 
            +
                        excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 1096 | 
            +
                        if excluded_agents:
         | 
| 1097 | 
            +
                            self.logger.debug(f"Note: The following agents are configured for exclusion: {excluded_agents}")
         | 
| 1098 | 
            +
                    except Exception:
         | 
| 1099 | 
            +
                        pass  # Ignore config loading errors in verification
         | 
| 1100 | 
            +
                    
         | 
| 956 1101 | 
             
                    for agent_file in agent_files:
         | 
| 957 1102 | 
             
                        try:
         | 
| 958 1103 | 
             
                            # Read first few lines to get agent name from YAML
         | 
| @@ -1093,14 +1238,46 @@ temperature: {temperature}""" | |
| 1093 1238 | 
             
                        return agents
         | 
| 1094 1239 |  | 
| 1095 1240 | 
             
                    template_files = sorted(self.templates_dir.glob("*.json"))
         | 
| 1096 | 
            -
                     | 
| 1097 | 
            -
                     | 
| 1098 | 
            -
                     | 
| 1099 | 
            -
                         | 
| 1100 | 
            -
                         | 
| 1101 | 
            -
                         | 
| 1102 | 
            -
                         | 
| 1103 | 
            -
             | 
| 1241 | 
            +
                    
         | 
| 1242 | 
            +
                    # Load configuration for exclusions
         | 
| 1243 | 
            +
                    try:
         | 
| 1244 | 
            +
                        from claude_mpm.core.config import Config
         | 
| 1245 | 
            +
                        config = Config()
         | 
| 1246 | 
            +
                        excluded_agents = config.get('agent_deployment.excluded_agents', [])
         | 
| 1247 | 
            +
                        case_sensitive = config.get('agent_deployment.case_sensitive', False)
         | 
| 1248 | 
            +
                        
         | 
| 1249 | 
            +
                        # Normalize excluded agents for comparison
         | 
| 1250 | 
            +
                        if not case_sensitive:
         | 
| 1251 | 
            +
                            excluded_agents = [agent.lower() for agent in excluded_agents]
         | 
| 1252 | 
            +
                    except Exception:
         | 
| 1253 | 
            +
                        # If config loading fails, use empty exclusion list
         | 
| 1254 | 
            +
                        excluded_agents = []
         | 
| 1255 | 
            +
                        case_sensitive = False
         | 
| 1256 | 
            +
                    
         | 
| 1257 | 
            +
                    # Build combined exclusion set
         | 
| 1258 | 
            +
                    hardcoded_exclusions = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
         | 
| 1259 | 
            +
                    
         | 
| 1260 | 
            +
                    # Filter out excluded agents
         | 
| 1261 | 
            +
                    filtered_files = []
         | 
| 1262 | 
            +
                    for f in template_files:
         | 
| 1263 | 
            +
                        agent_name = f.stem
         | 
| 1264 | 
            +
                        
         | 
| 1265 | 
            +
                        # Check hardcoded exclusions
         | 
| 1266 | 
            +
                        if agent_name in hardcoded_exclusions:
         | 
| 1267 | 
            +
                            continue
         | 
| 1268 | 
            +
                        
         | 
| 1269 | 
            +
                        # Check file patterns
         | 
| 1270 | 
            +
                        if agent_name.startswith(".") or agent_name.endswith(".backup"):
         | 
| 1271 | 
            +
                            continue
         | 
| 1272 | 
            +
                        
         | 
| 1273 | 
            +
                        # Check user-configured exclusions
         | 
| 1274 | 
            +
                        compare_name = agent_name.lower() if not case_sensitive else agent_name
         | 
| 1275 | 
            +
                        if compare_name in excluded_agents:
         | 
| 1276 | 
            +
                            continue
         | 
| 1277 | 
            +
                        
         | 
| 1278 | 
            +
                        filtered_files.append(f)
         | 
| 1279 | 
            +
                    
         | 
| 1280 | 
            +
                    template_files = filtered_files
         | 
| 1104 1281 |  | 
| 1105 1282 | 
             
                    for template_file in template_files:
         | 
| 1106 1283 | 
             
                        try:
         | 
| @@ -1283,7 +1460,8 @@ temperature: {temperature}""" | |
| 1283 1460 | 
             
                        Cleanup results
         | 
| 1284 1461 | 
             
                    """
         | 
| 1285 1462 | 
             
                    if not config_dir:
         | 
| 1286 | 
            -
                         | 
| 1463 | 
            +
                        # Use the working directory determined during initialization
         | 
| 1464 | 
            +
                        config_dir = self.working_directory / ".claude"
         | 
| 1287 1465 |  | 
| 1288 1466 | 
             
                    results = {
         | 
| 1289 1467 | 
             
                        "removed": [],
         | 
| @@ -1934,4 +2112,128 @@ metadata: | |
| 1934 2112 | 
             
                    except Exception as e:
         | 
| 1935 2113 | 
             
                        self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
         | 
| 1936 2114 |  | 
| 1937 | 
            -
                    return None
         | 
| 2115 | 
            +
                    return None
         | 
| 2116 | 
            +
                
         | 
| 2117 | 
            +
                def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
         | 
| 2118 | 
            +
                    """
         | 
| 2119 | 
            +
                    Validate and repair broken frontmatter in existing agent files.
         | 
| 2120 | 
            +
                    
         | 
| 2121 | 
            +
                    This method scans existing .claude/agents/*.md files and validates their
         | 
| 2122 | 
            +
                    frontmatter. If the frontmatter is broken or missing, it attempts to repair
         | 
| 2123 | 
            +
                    it or marks the agent for replacement during deployment.
         | 
| 2124 | 
            +
                    
         | 
| 2125 | 
            +
                    WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
         | 
| 2126 | 
            +
                    preventing runtime errors in Claude Code when loading agents.
         | 
| 2127 | 
            +
                    
         | 
| 2128 | 
            +
                    Args:
         | 
| 2129 | 
            +
                        agents_dir: Directory containing agent .md files
         | 
| 2130 | 
            +
                        
         | 
| 2131 | 
            +
                    Returns:
         | 
| 2132 | 
            +
                        Dictionary with validation results:
         | 
| 2133 | 
            +
                        - repaired: List of agent names that were repaired
         | 
| 2134 | 
            +
                        - replaced: List of agent names marked for replacement
         | 
| 2135 | 
            +
                        - errors: List of validation errors
         | 
| 2136 | 
            +
                    """
         | 
| 2137 | 
            +
                    results = {
         | 
| 2138 | 
            +
                        "repaired": [],
         | 
| 2139 | 
            +
                        "replaced": [],
         | 
| 2140 | 
            +
                        "errors": []
         | 
| 2141 | 
            +
                    }
         | 
| 2142 | 
            +
                    
         | 
| 2143 | 
            +
                    try:
         | 
| 2144 | 
            +
                        # Import frontmatter validator
         | 
| 2145 | 
            +
                        from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
         | 
| 2146 | 
            +
                        validator = FrontmatterValidator()
         | 
| 2147 | 
            +
                        
         | 
| 2148 | 
            +
                        # Find existing agent files
         | 
| 2149 | 
            +
                        agent_files = list(agents_dir.glob("*.md"))
         | 
| 2150 | 
            +
                        
         | 
| 2151 | 
            +
                        if not agent_files:
         | 
| 2152 | 
            +
                            self.logger.debug("No existing agent files to validate")
         | 
| 2153 | 
            +
                            return results
         | 
| 2154 | 
            +
                        
         | 
| 2155 | 
            +
                        self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
         | 
| 2156 | 
            +
                        
         | 
| 2157 | 
            +
                        for agent_file in agent_files:
         | 
| 2158 | 
            +
                            try:
         | 
| 2159 | 
            +
                                agent_name = agent_file.stem
         | 
| 2160 | 
            +
                                
         | 
| 2161 | 
            +
                                # Read agent file content
         | 
| 2162 | 
            +
                                content = agent_file.read_text()
         | 
| 2163 | 
            +
                                
         | 
| 2164 | 
            +
                                # Check if this is a system agent (authored by claude-mpm)
         | 
| 2165 | 
            +
                                # Only repair system agents, leave user agents alone
         | 
| 2166 | 
            +
                                if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
         | 
| 2167 | 
            +
                                    self.logger.debug(f"Skipping validation for user agent: {agent_name}")
         | 
| 2168 | 
            +
                                    continue
         | 
| 2169 | 
            +
                                
         | 
| 2170 | 
            +
                                # Extract and validate frontmatter
         | 
| 2171 | 
            +
                                if not content.startswith("---"):
         | 
| 2172 | 
            +
                                    # No frontmatter at all - mark for replacement
         | 
| 2173 | 
            +
                                    self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
         | 
| 2174 | 
            +
                                    results["replaced"].append(agent_name)
         | 
| 2175 | 
            +
                                    # Delete the file so it will be recreated
         | 
| 2176 | 
            +
                                    agent_file.unlink()
         | 
| 2177 | 
            +
                                    continue
         | 
| 2178 | 
            +
                                
         | 
| 2179 | 
            +
                                # Try to extract frontmatter
         | 
| 2180 | 
            +
                                try:
         | 
| 2181 | 
            +
                                    end_marker = content.find("\n---\n", 4)
         | 
| 2182 | 
            +
                                    if end_marker == -1:
         | 
| 2183 | 
            +
                                        end_marker = content.find("\n---\r\n", 4)
         | 
| 2184 | 
            +
                                    
         | 
| 2185 | 
            +
                                    if end_marker == -1:
         | 
| 2186 | 
            +
                                        # Broken frontmatter - mark for replacement
         | 
| 2187 | 
            +
                                        self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
         | 
| 2188 | 
            +
                                        results["replaced"].append(agent_name)
         | 
| 2189 | 
            +
                                        # Delete the file so it will be recreated
         | 
| 2190 | 
            +
                                        agent_file.unlink()
         | 
| 2191 | 
            +
                                        continue
         | 
| 2192 | 
            +
                                    
         | 
| 2193 | 
            +
                                    # Validate frontmatter with the validator
         | 
| 2194 | 
            +
                                    validation_result = validator.validate_file(agent_file)
         | 
| 2195 | 
            +
                                    
         | 
| 2196 | 
            +
                                    if not validation_result.is_valid:
         | 
| 2197 | 
            +
                                        # Check if it can be corrected
         | 
| 2198 | 
            +
                                        if validation_result.corrected_frontmatter:
         | 
| 2199 | 
            +
                                            # Apply corrections
         | 
| 2200 | 
            +
                                            correction_result = validator.correct_file(agent_file, dry_run=False)
         | 
| 2201 | 
            +
                                            if correction_result.corrections:
         | 
| 2202 | 
            +
                                                results["repaired"].append(agent_name)
         | 
| 2203 | 
            +
                                                self.logger.info(f"Repaired frontmatter for agent {agent_name}")
         | 
| 2204 | 
            +
                                                for correction in correction_result.corrections:
         | 
| 2205 | 
            +
                                                    self.logger.debug(f"  - {correction}")
         | 
| 2206 | 
            +
                                        else:
         | 
| 2207 | 
            +
                                            # Cannot be corrected - mark for replacement
         | 
| 2208 | 
            +
                                            self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
         | 
| 2209 | 
            +
                                            results["replaced"].append(agent_name)
         | 
| 2210 | 
            +
                                            # Delete the file so it will be recreated
         | 
| 2211 | 
            +
                                            agent_file.unlink()
         | 
| 2212 | 
            +
                                    elif validation_result.warnings:
         | 
| 2213 | 
            +
                                        # Has warnings but is valid
         | 
| 2214 | 
            +
                                        for warning in validation_result.warnings:
         | 
| 2215 | 
            +
                                            self.logger.debug(f"Agent {agent_name} warning: {warning}")
         | 
| 2216 | 
            +
                                    
         | 
| 2217 | 
            +
                                except Exception as e:
         | 
| 2218 | 
            +
                                    # Any error in parsing - mark for replacement
         | 
| 2219 | 
            +
                                    self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
         | 
| 2220 | 
            +
                                    results["replaced"].append(agent_name)
         | 
| 2221 | 
            +
                                    # Delete the file so it will be recreated
         | 
| 2222 | 
            +
                                    try:
         | 
| 2223 | 
            +
                                        agent_file.unlink()
         | 
| 2224 | 
            +
                                    except Exception:
         | 
| 2225 | 
            +
                                        pass
         | 
| 2226 | 
            +
                                
         | 
| 2227 | 
            +
                            except Exception as e:
         | 
| 2228 | 
            +
                                error_msg = f"Failed to validate agent {agent_file.name}: {e}"
         | 
| 2229 | 
            +
                                self.logger.error(error_msg)
         | 
| 2230 | 
            +
                                results["errors"].append(error_msg)
         | 
| 2231 | 
            +
                        
         | 
| 2232 | 
            +
                    except ImportError:
         | 
| 2233 | 
            +
                        self.logger.warning("FrontmatterValidator not available, skipping validation")
         | 
| 2234 | 
            +
                    except Exception as e:
         | 
| 2235 | 
            +
                        error_msg = f"Agent validation failed: {e}"
         | 
| 2236 | 
            +
                        self.logger.error(error_msg)
         | 
| 2237 | 
            +
                        results["errors"].append(error_msg)
         | 
| 2238 | 
            +
                    
         | 
| 2239 | 
            +
                    return results
         |