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
| @@ -70,13 +70,14 @@ class AgentDeploymentService: | |
| 70 70 | 
             
                - YAML generation capabilities
         | 
| 71 71 | 
             
                """
         | 
| 72 72 |  | 
| 73 | 
            -
                def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None):
         | 
| 73 | 
            +
                def __init__(self, templates_dir: Optional[Path] = None, base_agent_path: Optional[Path] = None, working_directory: Optional[Path] = None):
         | 
| 74 74 | 
             
                    """
         | 
| 75 75 | 
             
                    Initialize agent deployment service.
         | 
| 76 76 |  | 
| 77 77 | 
             
                    Args:
         | 
| 78 | 
            -
                        templates_dir: Directory containing agent  | 
| 78 | 
            +
                        templates_dir: Directory containing agent JSON files
         | 
| 79 79 | 
             
                        base_agent_path: Path to base_agent.md file
         | 
| 80 | 
            +
                        working_directory: User's working directory (for project agents)
         | 
| 80 81 |  | 
| 81 82 | 
             
                    METRICS OPPORTUNITY: Track initialization performance:
         | 
| 82 83 | 
             
                    - Template directory scan time
         | 
| @@ -100,11 +101,24 @@ class AgentDeploymentService: | |
| 100 101 | 
             
                        'deployment_errors': {}  # Track error types and frequencies
         | 
| 101 102 | 
             
                    }
         | 
| 102 103 |  | 
| 104 | 
            +
                    # Determine the actual working directory
         | 
| 105 | 
            +
                    # Priority: working_directory param > CLAUDE_MPM_USER_PWD env var > current directory
         | 
| 106 | 
            +
                    if working_directory:
         | 
| 107 | 
            +
                        self.working_directory = Path(working_directory)
         | 
| 108 | 
            +
                    elif 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 109 | 
            +
                        self.working_directory = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 110 | 
            +
                    else:
         | 
| 111 | 
            +
                        self.working_directory = Path.cwd()
         | 
| 112 | 
            +
                    
         | 
| 113 | 
            +
                    self.logger.info(f"Working directory for deployment: {self.working_directory}")
         | 
| 114 | 
            +
                    
         | 
| 103 115 | 
             
                    # Find templates directory using centralized path management
         | 
| 104 116 | 
             
                    if templates_dir:
         | 
| 105 117 | 
             
                        self.templates_dir = Path(templates_dir)
         | 
| 106 118 | 
             
                    else:
         | 
| 107 119 | 
             
                        # Use centralized paths instead of fragile parent calculations
         | 
| 120 | 
            +
                        # For system agents, still use templates subdirectory
         | 
| 121 | 
            +
                        # For project/user agents, this should be overridden with actual agents dir
         | 
| 108 122 | 
             
                        self.templates_dir = paths.agents_dir / "templates"
         | 
| 109 123 |  | 
| 110 124 | 
             
                    # Find base agent file
         | 
| @@ -135,6 +149,7 @@ class AgentDeploymentService: | |
| 135 149 | 
             
                    - Error type frequencies
         | 
| 136 150 |  | 
| 137 151 | 
             
                    OPERATIONAL FLOW:
         | 
| 152 | 
            +
                    0. Validates and repairs broken frontmatter in existing agents (Step 0)
         | 
| 138 153 | 
             
                    1. Validates target directory (creates if needed)
         | 
| 139 154 | 
             
                    2. Loads base agent configuration
         | 
| 140 155 | 
             
                    3. Discovers all agent templates
         | 
| @@ -176,13 +191,15 @@ class AgentDeploymentService: | |
| 176 191 | 
             
                        - skipped: List of unchanged agents
         | 
| 177 192 | 
             
                        - errors: List of deployment errors
         | 
| 178 193 | 
             
                        - total: Total number of agents processed
         | 
| 194 | 
            +
                        - repaired: List of agents with repaired frontmatter
         | 
| 179 195 | 
             
                    """
         | 
| 180 196 | 
             
                    # METRICS: Record deployment start time for performance tracking
         | 
| 181 197 | 
             
                    deployment_start_time = time.time()
         | 
| 182 198 |  | 
| 183 199 | 
             
                    if not target_dir:
         | 
| 184 | 
            -
                        # Default to  | 
| 185 | 
            -
                         | 
| 200 | 
            +
                        # Default to working directory's .claude/agents directory (not home)
         | 
| 201 | 
            +
                        # This ensures we deploy to the user's project directory, not the framework directory
         | 
| 202 | 
            +
                        agents_dir = self.working_directory / ".claude" / "agents"
         | 
| 186 203 | 
             
                    else:
         | 
| 187 204 | 
             
                        # If target_dir provided, use it directly (caller decides structure)
         | 
| 188 205 | 
             
                        # This allows for both passing a project dir or the full agents path
         | 
| @@ -209,6 +226,7 @@ class AgentDeploymentService: | |
| 209 226 | 
             
                        "updated": [],
         | 
| 210 227 | 
             
                        "migrated": [],  # Track agents migrated from old format
         | 
| 211 228 | 
             
                        "converted": [],  # Track YAML to MD conversions
         | 
| 229 | 
            +
                        "repaired": [],  # Track agents with repaired frontmatter
         | 
| 212 230 | 
             
                        "total": 0,
         | 
| 213 231 | 
             
                        # METRICS: Add detailed timing and performance data to results
         | 
| 214 232 | 
             
                        "metrics": {
         | 
| @@ -224,13 +242,30 @@ class AgentDeploymentService: | |
| 224 242 | 
             
                    try:
         | 
| 225 243 | 
             
                        # Create agents directory if needed
         | 
| 226 244 | 
             
                        agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 227 | 
            -
                         | 
| 245 | 
            +
                        
         | 
| 246 | 
            +
                        # STEP 0: Validate and repair broken frontmatter in existing agents
         | 
| 247 | 
            +
                        # This ensures all existing agents have valid YAML frontmatter before deploying new ones
         | 
| 248 | 
            +
                        repair_results = self._validate_and_repair_existing_agents(agents_dir)
         | 
| 249 | 
            +
                        if repair_results["repaired"]:
         | 
| 250 | 
            +
                            results["repaired"] = repair_results["repaired"]
         | 
| 251 | 
            +
                            self.logger.info(f"Repaired frontmatter in {len(repair_results['repaired'])} existing agents")
         | 
| 252 | 
            +
                            for agent_name in repair_results["repaired"]:
         | 
| 253 | 
            +
                                self.logger.debug(f"  - Repaired: {agent_name}")
         | 
| 254 | 
            +
                        
         | 
| 255 | 
            +
                        # Determine source tier for logging
         | 
| 256 | 
            +
                        source_tier = "SYSTEM"
         | 
| 257 | 
            +
                        if ".claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
         | 
| 258 | 
            +
                            source_tier = "PROJECT" 
         | 
| 259 | 
            +
                        elif "/.claude-mpm/agents" in str(self.templates_dir) and "/templates" not in str(self.templates_dir):
         | 
| 260 | 
            +
                            source_tier = "USER"
         | 
| 261 | 
            +
                        
         | 
| 262 | 
            +
                        self.logger.info(f"Building and deploying {source_tier} agents to: {agents_dir}")
         | 
| 228 263 |  | 
| 229 264 | 
             
                        # Note: System instructions are now loaded directly by SimpleClaudeRunner
         | 
| 230 265 |  | 
| 231 266 | 
             
                        # Check if templates directory exists
         | 
| 232 267 | 
             
                        if not self.templates_dir.exists():
         | 
| 233 | 
            -
                            error_msg = f" | 
| 268 | 
            +
                            error_msg = f"Agents directory not found: {self.templates_dir}"
         | 
| 234 269 | 
             
                            self.logger.error(error_msg)
         | 
| 235 270 | 
             
                            results["errors"].append(error_msg)
         | 
| 236 271 | 
             
                            return results
         | 
| @@ -260,7 +295,8 @@ class AgentDeploymentService: | |
| 260 295 | 
             
                        # Get all template files
         | 
| 261 296 | 
             
                        template_files = list(self.templates_dir.glob("*.json"))
         | 
| 262 297 | 
             
                        # Filter out non-agent files - exclude system files and uppercase special files
         | 
| 263 | 
            -
                         | 
| 298 | 
            +
                        # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
         | 
| 299 | 
            +
                        excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
         | 
| 264 300 | 
             
                        template_files = [
         | 
| 265 301 | 
             
                            f for f in template_files 
         | 
| 266 302 | 
             
                            if f.stem not in excluded_names 
         | 
| @@ -364,6 +400,7 @@ class AgentDeploymentService: | |
| 364 400 | 
             
                            f"updated {len(results['updated'])}, "
         | 
| 365 401 | 
             
                            f"migrated {len(results['migrated'])}, "
         | 
| 366 402 | 
             
                            f"converted {len(results['converted'])} YAML files, "
         | 
| 403 | 
            +
                            f"repaired {len(results['repaired'])} frontmatter, "
         | 
| 367 404 | 
             
                            f"skipped {len(results['skipped'])}, "
         | 
| 368 405 | 
             
                            f"errors: {len(results['errors'])}"
         | 
| 369 406 | 
             
                        )
         | 
| @@ -568,20 +605,36 @@ class AgentDeploymentService: | |
| 568 605 | 
             
                    # Simplify model name for Claude Code
         | 
| 569 606 | 
             
                    model_map = {
         | 
| 570 607 | 
             
                        'claude-4-sonnet-20250514': 'sonnet',
         | 
| 571 | 
            -
                        'claude-sonnet-4-20250514': 'sonnet', | 
| 608 | 
            +
                        'claude-sonnet-4-20250514': 'sonnet',
         | 
| 609 | 
            +
                        'claude-opus-4-20250514': 'opus',
         | 
| 572 610 | 
             
                        'claude-3-opus-20240229': 'opus',
         | 
| 573 611 | 
             
                        'claude-3-haiku-20240307': 'haiku',
         | 
| 574 612 | 
             
                        'claude-3.5-sonnet': 'sonnet',
         | 
| 575 613 | 
             
                        'claude-3-sonnet': 'sonnet'
         | 
| 576 614 | 
             
                    }
         | 
| 577 | 
            -
                     | 
| 615 | 
            +
                    # Better fallback: extract the model type (opus/sonnet/haiku) from the string
         | 
| 616 | 
            +
                    if model not in model_map:
         | 
| 617 | 
            +
                        if 'opus' in model.lower():
         | 
| 618 | 
            +
                            model = 'opus'
         | 
| 619 | 
            +
                        elif 'sonnet' in model.lower():
         | 
| 620 | 
            +
                            model = 'sonnet'
         | 
| 621 | 
            +
                        elif 'haiku' in model.lower():
         | 
| 622 | 
            +
                            model = 'haiku'
         | 
| 623 | 
            +
                        else:
         | 
| 624 | 
            +
                            # Last resort: try to extract from hyphenated format
         | 
| 625 | 
            +
                            model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
         | 
| 626 | 
            +
                    else:
         | 
| 627 | 
            +
                        model = model_map[model]
         | 
| 578 628 |  | 
| 579 629 | 
             
                    # Get response format from template or use base agent default
         | 
| 580 630 | 
             
                    response_format = template_data.get('response', {}).get('format', 'structured')
         | 
| 581 631 |  | 
| 582 632 | 
             
                    # Convert lists to space-separated strings for Claude Code compatibility
         | 
| 583 633 | 
             
                    tags_str = ' '.join(tags) if isinstance(tags, list) else tags
         | 
| 584 | 
            -
                     | 
| 634 | 
            +
                    
         | 
| 635 | 
            +
                    # Convert tools list to comma-separated string for Claude Code compatibility
         | 
| 636 | 
            +
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 637 | 
            +
                    tools_str = ','.join(tools) if isinstance(tools, list) else tools
         | 
| 585 638 |  | 
| 586 639 | 
             
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 587 640 | 
             
                    frontmatter_lines = [
         | 
| @@ -596,8 +649,13 @@ class AgentDeploymentService: | |
| 596 649 | 
             
                    ]
         | 
| 597 650 |  | 
| 598 651 | 
             
                    # Add optional fields if present
         | 
| 599 | 
            -
                     | 
| 600 | 
            -
             | 
| 652 | 
            +
                    # Check for color in metadata section (new format) or root (old format)
         | 
| 653 | 
            +
                    color = (
         | 
| 654 | 
            +
                        template_data.get('metadata', {}).get('color') or
         | 
| 655 | 
            +
                        template_data.get('color')
         | 
| 656 | 
            +
                    )
         | 
| 657 | 
            +
                    if color:
         | 
| 658 | 
            +
                        frontmatter_lines.append(f"color: {color}")
         | 
| 601 659 |  | 
| 602 660 | 
             
                    frontmatter_lines.append("---")
         | 
| 603 661 | 
             
                    frontmatter_lines.append("")
         | 
| @@ -834,7 +892,8 @@ temperature: {temperature}""" | |
| 834 892 | 
             
                        Dictionary of environment variables set for verification
         | 
| 835 893 | 
             
                    """
         | 
| 836 894 | 
             
                    if not config_dir:
         | 
| 837 | 
            -
                         | 
| 895 | 
            +
                        # Use the working directory determined during initialization
         | 
| 896 | 
            +
                        config_dir = self.working_directory / Paths.CLAUDE_CONFIG_DIR.value
         | 
| 838 897 |  | 
| 839 898 | 
             
                    env_vars = {}
         | 
| 840 899 |  | 
| @@ -900,7 +959,8 @@ temperature: {temperature}""" | |
| 900 959 | 
             
                        - warnings: List of potential issues
         | 
| 901 960 | 
             
                    """
         | 
| 902 961 | 
             
                    if not config_dir:
         | 
| 903 | 
            -
                         | 
| 962 | 
            +
                        # Use the working directory determined during initialization
         | 
| 963 | 
            +
                        config_dir = self.working_directory / ".claude"
         | 
| 904 964 |  | 
| 905 965 | 
             
                    results = {
         | 
| 906 966 | 
             
                        "config_dir": str(config_dir),
         | 
| @@ -1059,12 +1119,13 @@ temperature: {temperature}""" | |
| 1059 1119 | 
             
                    agents = []
         | 
| 1060 1120 |  | 
| 1061 1121 | 
             
                    if not self.templates_dir.exists():
         | 
| 1062 | 
            -
                        self.logger.warning(f" | 
| 1122 | 
            +
                        self.logger.warning(f"Agents directory not found: {self.templates_dir}")
         | 
| 1063 1123 | 
             
                        return agents
         | 
| 1064 1124 |  | 
| 1065 1125 | 
             
                    template_files = sorted(self.templates_dir.glob("*.json"))
         | 
| 1066 1126 | 
             
                    # Filter out non-agent files - exclude system files and uppercase special files
         | 
| 1067 | 
            -
                     | 
| 1127 | 
            +
                    # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
         | 
| 1128 | 
            +
                    excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README", "pm", "PM", "project_manager"}
         | 
| 1068 1129 | 
             
                    template_files = [
         | 
| 1069 1130 | 
             
                        f for f in template_files 
         | 
| 1070 1131 | 
             
                        if f.stem not in excluded_names 
         | 
| @@ -1253,7 +1314,8 @@ temperature: {temperature}""" | |
| 1253 1314 | 
             
                        Cleanup results
         | 
| 1254 1315 | 
             
                    """
         | 
| 1255 1316 | 
             
                    if not config_dir:
         | 
| 1256 | 
            -
                         | 
| 1317 | 
            +
                        # Use the working directory determined during initialization
         | 
| 1318 | 
            +
                        config_dir = self.working_directory / ".claude"
         | 
| 1257 1319 |  | 
| 1258 1320 | 
             
                    results = {
         | 
| 1259 1321 | 
             
                        "removed": [],
         | 
| @@ -1904,4 +1966,128 @@ metadata: | |
| 1904 1966 | 
             
                    except Exception as e:
         | 
| 1905 1967 | 
             
                        self.logger.warning(f"Error extracting YAML field '{field_name}': {e}")
         | 
| 1906 1968 |  | 
| 1907 | 
            -
                    return None
         | 
| 1969 | 
            +
                    return None
         | 
| 1970 | 
            +
                
         | 
| 1971 | 
            +
                def _validate_and_repair_existing_agents(self, agents_dir: Path) -> Dict[str, Any]:
         | 
| 1972 | 
            +
                    """
         | 
| 1973 | 
            +
                    Validate and repair broken frontmatter in existing agent files.
         | 
| 1974 | 
            +
                    
         | 
| 1975 | 
            +
                    This method scans existing .claude/agents/*.md files and validates their
         | 
| 1976 | 
            +
                    frontmatter. If the frontmatter is broken or missing, it attempts to repair
         | 
| 1977 | 
            +
                    it or marks the agent for replacement during deployment.
         | 
| 1978 | 
            +
                    
         | 
| 1979 | 
            +
                    WHY: Ensures all existing agents have valid YAML frontmatter before deployment,
         | 
| 1980 | 
            +
                    preventing runtime errors in Claude Code when loading agents.
         | 
| 1981 | 
            +
                    
         | 
| 1982 | 
            +
                    Args:
         | 
| 1983 | 
            +
                        agents_dir: Directory containing agent .md files
         | 
| 1984 | 
            +
                        
         | 
| 1985 | 
            +
                    Returns:
         | 
| 1986 | 
            +
                        Dictionary with validation results:
         | 
| 1987 | 
            +
                        - repaired: List of agent names that were repaired
         | 
| 1988 | 
            +
                        - replaced: List of agent names marked for replacement
         | 
| 1989 | 
            +
                        - errors: List of validation errors
         | 
| 1990 | 
            +
                    """
         | 
| 1991 | 
            +
                    results = {
         | 
| 1992 | 
            +
                        "repaired": [],
         | 
| 1993 | 
            +
                        "replaced": [],
         | 
| 1994 | 
            +
                        "errors": []
         | 
| 1995 | 
            +
                    }
         | 
| 1996 | 
            +
                    
         | 
| 1997 | 
            +
                    try:
         | 
| 1998 | 
            +
                        # Import frontmatter validator
         | 
| 1999 | 
            +
                        from claude_mpm.agents.frontmatter_validator import FrontmatterValidator
         | 
| 2000 | 
            +
                        validator = FrontmatterValidator()
         | 
| 2001 | 
            +
                        
         | 
| 2002 | 
            +
                        # Find existing agent files
         | 
| 2003 | 
            +
                        agent_files = list(agents_dir.glob("*.md"))
         | 
| 2004 | 
            +
                        
         | 
| 2005 | 
            +
                        if not agent_files:
         | 
| 2006 | 
            +
                            self.logger.debug("No existing agent files to validate")
         | 
| 2007 | 
            +
                            return results
         | 
| 2008 | 
            +
                        
         | 
| 2009 | 
            +
                        self.logger.debug(f"Validating frontmatter in {len(agent_files)} existing agents")
         | 
| 2010 | 
            +
                        
         | 
| 2011 | 
            +
                        for agent_file in agent_files:
         | 
| 2012 | 
            +
                            try:
         | 
| 2013 | 
            +
                                agent_name = agent_file.stem
         | 
| 2014 | 
            +
                                
         | 
| 2015 | 
            +
                                # Read agent file content
         | 
| 2016 | 
            +
                                content = agent_file.read_text()
         | 
| 2017 | 
            +
                                
         | 
| 2018 | 
            +
                                # Check if this is a system agent (authored by claude-mpm)
         | 
| 2019 | 
            +
                                # Only repair system agents, leave user agents alone
         | 
| 2020 | 
            +
                                if "author: claude-mpm" not in content and "author: 'claude-mpm'" not in content:
         | 
| 2021 | 
            +
                                    self.logger.debug(f"Skipping validation for user agent: {agent_name}")
         | 
| 2022 | 
            +
                                    continue
         | 
| 2023 | 
            +
                                
         | 
| 2024 | 
            +
                                # Extract and validate frontmatter
         | 
| 2025 | 
            +
                                if not content.startswith("---"):
         | 
| 2026 | 
            +
                                    # No frontmatter at all - mark for replacement
         | 
| 2027 | 
            +
                                    self.logger.warning(f"Agent {agent_name} has no frontmatter, marking for replacement")
         | 
| 2028 | 
            +
                                    results["replaced"].append(agent_name)
         | 
| 2029 | 
            +
                                    # Delete the file so it will be recreated
         | 
| 2030 | 
            +
                                    agent_file.unlink()
         | 
| 2031 | 
            +
                                    continue
         | 
| 2032 | 
            +
                                
         | 
| 2033 | 
            +
                                # Try to extract frontmatter
         | 
| 2034 | 
            +
                                try:
         | 
| 2035 | 
            +
                                    end_marker = content.find("\n---\n", 4)
         | 
| 2036 | 
            +
                                    if end_marker == -1:
         | 
| 2037 | 
            +
                                        end_marker = content.find("\n---\r\n", 4)
         | 
| 2038 | 
            +
                                    
         | 
| 2039 | 
            +
                                    if end_marker == -1:
         | 
| 2040 | 
            +
                                        # Broken frontmatter - mark for replacement
         | 
| 2041 | 
            +
                                        self.logger.warning(f"Agent {agent_name} has broken frontmatter, marking for replacement")
         | 
| 2042 | 
            +
                                        results["replaced"].append(agent_name)
         | 
| 2043 | 
            +
                                        # Delete the file so it will be recreated
         | 
| 2044 | 
            +
                                        agent_file.unlink()
         | 
| 2045 | 
            +
                                        continue
         | 
| 2046 | 
            +
                                    
         | 
| 2047 | 
            +
                                    # Validate frontmatter with the validator
         | 
| 2048 | 
            +
                                    validation_result = validator.validate_file(agent_file)
         | 
| 2049 | 
            +
                                    
         | 
| 2050 | 
            +
                                    if not validation_result.is_valid:
         | 
| 2051 | 
            +
                                        # Check if it can be corrected
         | 
| 2052 | 
            +
                                        if validation_result.corrected_frontmatter:
         | 
| 2053 | 
            +
                                            # Apply corrections
         | 
| 2054 | 
            +
                                            correction_result = validator.correct_file(agent_file, dry_run=False)
         | 
| 2055 | 
            +
                                            if correction_result.corrections:
         | 
| 2056 | 
            +
                                                results["repaired"].append(agent_name)
         | 
| 2057 | 
            +
                                                self.logger.info(f"Repaired frontmatter for agent {agent_name}")
         | 
| 2058 | 
            +
                                                for correction in correction_result.corrections:
         | 
| 2059 | 
            +
                                                    self.logger.debug(f"  - {correction}")
         | 
| 2060 | 
            +
                                        else:
         | 
| 2061 | 
            +
                                            # Cannot be corrected - mark for replacement
         | 
| 2062 | 
            +
                                            self.logger.warning(f"Agent {agent_name} has invalid frontmatter that cannot be repaired, marking for replacement")
         | 
| 2063 | 
            +
                                            results["replaced"].append(agent_name)
         | 
| 2064 | 
            +
                                            # Delete the file so it will be recreated
         | 
| 2065 | 
            +
                                            agent_file.unlink()
         | 
| 2066 | 
            +
                                    elif validation_result.warnings:
         | 
| 2067 | 
            +
                                        # Has warnings but is valid
         | 
| 2068 | 
            +
                                        for warning in validation_result.warnings:
         | 
| 2069 | 
            +
                                            self.logger.debug(f"Agent {agent_name} warning: {warning}")
         | 
| 2070 | 
            +
                                    
         | 
| 2071 | 
            +
                                except Exception as e:
         | 
| 2072 | 
            +
                                    # Any error in parsing - mark for replacement
         | 
| 2073 | 
            +
                                    self.logger.warning(f"Failed to parse frontmatter for {agent_name}: {e}, marking for replacement")
         | 
| 2074 | 
            +
                                    results["replaced"].append(agent_name)
         | 
| 2075 | 
            +
                                    # Delete the file so it will be recreated
         | 
| 2076 | 
            +
                                    try:
         | 
| 2077 | 
            +
                                        agent_file.unlink()
         | 
| 2078 | 
            +
                                    except Exception:
         | 
| 2079 | 
            +
                                        pass
         | 
| 2080 | 
            +
                                
         | 
| 2081 | 
            +
                            except Exception as e:
         | 
| 2082 | 
            +
                                error_msg = f"Failed to validate agent {agent_file.name}: {e}"
         | 
| 2083 | 
            +
                                self.logger.error(error_msg)
         | 
| 2084 | 
            +
                                results["errors"].append(error_msg)
         | 
| 2085 | 
            +
                        
         | 
| 2086 | 
            +
                    except ImportError:
         | 
| 2087 | 
            +
                        self.logger.warning("FrontmatterValidator not available, skipping validation")
         | 
| 2088 | 
            +
                    except Exception as e:
         | 
| 2089 | 
            +
                        error_msg = f"Agent validation failed: {e}"
         | 
| 2090 | 
            +
                        self.logger.error(error_msg)
         | 
| 2091 | 
            +
                        results["errors"].append(error_msg)
         | 
| 2092 | 
            +
                    
         | 
| 2093 | 
            +
                    return results
         | 
| @@ -57,7 +57,8 @@ class AgentManager: | |
| 57 57 |  | 
| 58 58 | 
             
                    if project_dir is None:
         | 
| 59 59 | 
             
                        project_root = PathResolver.get_project_root()
         | 
| 60 | 
            -
                         | 
| 60 | 
            +
                        # Use direct agents directory without subdirectory to match deployment expectations
         | 
| 61 | 
            +
                        self.project_dir = project_root / ConfigPaths.CONFIG_DIR / "agents"
         | 
| 61 62 | 
             
                    else:
         | 
| 62 63 | 
             
                        self.project_dir = project_dir
         | 
| 63 64 | 
             
                    self.version_manager = AgentVersionManager()
         | 
| @@ -31,6 +31,7 @@ from enum import Enum | |
| 31 31 |  | 
| 32 32 | 
             
            from claude_mpm.core.config_paths import ConfigPaths
         | 
| 33 33 | 
             
            from claude_mpm.services.memory.cache.simple_cache import SimpleCacheService
         | 
| 34 | 
            +
            from claude_mpm.agents.frontmatter_validator import FrontmatterValidator, ValidationResult
         | 
| 34 35 |  | 
| 35 36 | 
             
            logger = logging.getLogger(__name__)
         | 
| 36 37 |  | 
| @@ -130,6 +131,9 @@ class AgentRegistry: | |
| 130 131 |  | 
| 131 132 | 
             
                    self.model_selector = model_selector
         | 
| 132 133 |  | 
| 134 | 
            +
                    # Initialize frontmatter validator
         | 
| 135 | 
            +
                    self.frontmatter_validator = FrontmatterValidator()
         | 
| 136 | 
            +
                    
         | 
| 133 137 | 
             
                    # Registry storage
         | 
| 134 138 | 
             
                    self.registry: Dict[str, AgentMetadata] = {}
         | 
| 135 139 | 
             
                    self.discovery_paths: List[Path] = []
         | 
| @@ -337,10 +341,27 @@ class AgentRegistry: | |
| 337 341 | 
             
                                        if len(parts) >= 3:
         | 
| 338 342 | 
             
                                            frontmatter_text = parts[1].strip()
         | 
| 339 343 | 
             
                                            data = yaml.safe_load(frontmatter_text)
         | 
| 344 | 
            +
                                            
         | 
| 345 | 
            +
                                            # Validate and correct frontmatter
         | 
| 346 | 
            +
                                            validation_result = self.frontmatter_validator.validate_and_correct(data)
         | 
| 347 | 
            +
                                            if validation_result.corrections:
         | 
| 348 | 
            +
                                                logger.info(f"Applied corrections to {file_path.name}:")
         | 
| 349 | 
            +
                                                for correction in validation_result.corrections:
         | 
| 350 | 
            +
                                                    logger.info(f"  - {correction}")
         | 
| 351 | 
            +
                                                
         | 
| 352 | 
            +
                                                # Use corrected frontmatter if available
         | 
| 353 | 
            +
                                                if validation_result.corrected_frontmatter:
         | 
| 354 | 
            +
                                                    data = validation_result.corrected_frontmatter
         | 
| 355 | 
            +
                                            
         | 
| 356 | 
            +
                                            if validation_result.errors:
         | 
| 357 | 
            +
                                                logger.warning(f"Validation errors in {file_path.name}:")
         | 
| 358 | 
            +
                                                for error in validation_result.errors:
         | 
| 359 | 
            +
                                                    logger.warning(f"  - {error}")
         | 
| 360 | 
            +
                                            
         | 
| 340 361 | 
             
                                            description = data.get('description', '')
         | 
| 341 362 | 
             
                                            version = data.get('version', '0.0.0')
         | 
| 342 363 | 
             
                                            capabilities = data.get('tools', [])  # Tools in .md format
         | 
| 343 | 
            -
                                            metadata = data | 
| 364 | 
            +
                                            metadata = data
         | 
| 344 365 | 
             
                                        else:
         | 
| 345 366 | 
             
                                            # No frontmatter, use defaults
         | 
| 346 367 | 
             
                                            description = f"{file_path.stem} agent"
         | 
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            # Framework CLAUDE.md Generator
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This directory contains the refactored framework CLAUDE.md generator service, originally a single 1,267-line file.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Structure
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ### Core Modules
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            - **`__init__.py`** (~250 lines) - Main facade class that coordinates all functionality
         | 
| 10 | 
            +
            - **`version_manager.py`** (~100 lines) - Handles version parsing, incrementing, and comparison
         | 
| 11 | 
            +
            - **`content_assembler.py`** (~120 lines) - Assembles sections and applies template variables
         | 
| 12 | 
            +
            - **`content_validator.py`** (~80 lines) - Validates generated content structure and completeness
         | 
| 13 | 
            +
            - **`deployment_manager.py`** (~80 lines) - Handles deployment to parent directories
         | 
| 14 | 
            +
            - **`section_manager.py`** (~60 lines) - Manages section registration, ordering, and updates
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ### Section Generators
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            The `section_generators/` subdirectory contains individual generators for each section:
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            - **`__init__.py`** - Base classes and registry
         | 
| 21 | 
            +
            - **`header.py`** - Header with version metadata
         | 
| 22 | 
            +
            - **`role_designation.py`** - AI Assistant role designation
         | 
| 23 | 
            +
            - **`agents.py`** (~570 lines) - Comprehensive agents documentation (largest section)
         | 
| 24 | 
            +
            - **`todo_task_tools.py`** - Todo and Task Tools integration
         | 
| 25 | 
            +
            - **`claude_pm_init.py`** - Claude-PM initialization procedures
         | 
| 26 | 
            +
            - **`orchestration_principles.py`** - Core orchestration principles
         | 
| 27 | 
            +
            - **`subprocess_validation.py`** - Subprocess validation protocol
         | 
| 28 | 
            +
            - **`delegation_constraints.py`** - Critical delegation constraints
         | 
| 29 | 
            +
            - **`environment_config.py`** - Environment configuration
         | 
| 30 | 
            +
            - **`troubleshooting.py`** - Troubleshooting guide
         | 
| 31 | 
            +
            - **`core_responsibilities.py`** - Core responsibilities list
         | 
| 32 | 
            +
            - **`footer.py`** - Footer with metadata
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            ## Usage
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            The API remains unchanged from the original implementation:
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            ```python
         | 
| 39 | 
            +
            from claude_pm.services.framework_claude_md_generator import FrameworkClaudeMdGenerator
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            # Create generator instance
         | 
| 42 | 
            +
            generator = FrameworkClaudeMdGenerator()
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            # Generate content
         | 
| 45 | 
            +
            content = generator.generate(
         | 
| 46 | 
            +
                current_content=existing_content,  # Optional: for version auto-increment
         | 
| 47 | 
            +
                template_variables={'PLATFORM': 'darwin', 'PYTHON_CMD': 'python3'}
         | 
| 48 | 
            +
            )
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            # Deploy to parent directory
         | 
| 51 | 
            +
            success, message = generator.deploy_to_parent(Path('/parent/dir'))
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            # Update a section
         | 
| 54 | 
            +
            generator.update_section('agents', 'Custom agents content')
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            # Add custom section
         | 
| 57 | 
            +
            generator.add_custom_section('custom', 'Custom content', after='agents')
         | 
| 58 | 
            +
            ```
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            ## Design Benefits
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            1. **Modularity**: Each concern is separated into its own module
         | 
| 63 | 
            +
            2. **Maintainability**: Smaller, focused modules are easier to understand and modify
         | 
| 64 | 
            +
            3. **Testability**: Individual components can be tested in isolation
         | 
| 65 | 
            +
            4. **Extensibility**: New section generators can be added easily
         | 
| 66 | 
            +
            5. **Performance**: Section generators are loaded on demand
         | 
| 67 | 
            +
            6. **Backward Compatibility**: Original API preserved through facade pattern
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            ## Section Generator Pattern
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            To add a new section generator:
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            1. Create a new file in `section_generators/`
         | 
| 74 | 
            +
            2. Inherit from `BaseSectionGenerator`
         | 
| 75 | 
            +
            3. Implement the `generate()` method
         | 
| 76 | 
            +
            4. Register in `section_generators/__init__.py`
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            Example:
         | 
| 79 | 
            +
            ```python
         | 
| 80 | 
            +
            from . import BaseSectionGenerator
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            class CustomSectionGenerator(BaseSectionGenerator):
         | 
| 83 | 
            +
                def generate(self, data: Dict[str, Any]) -> str:
         | 
| 84 | 
            +
                    return "## Custom Section\n\nContent here..."
         | 
| 85 | 
            +
            ```
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            ## Refactoring Summary
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            - **Original**: 1,267 lines in a single file
         | 
| 90 | 
            +
            - **Refactored**: ~250 lines in main module + well-organized submodules
         | 
| 91 | 
            +
            - **Total line reduction**: Main module reduced by 80%
         | 
| 92 | 
            +
            - **Improved organization**: 13 focused modules instead of 1 large file
         | 
| @@ -75,8 +75,8 @@ class AgentsGenerator(BaseSectionGenerator): | |
| 75 75 | 
             
            **ALL AGENT OPERATIONS FOLLOW HIERARCHICAL PRECEDENCE**
         | 
| 76 76 |  | 
| 77 77 | 
             
            #### Agent Hierarchy (Highest to Lowest Priority)
         | 
| 78 | 
            -
            1. **Project Agents**: `$PROJECT/.claude-mpm/agents | 
| 79 | 
            -
               - Project | 
| 78 | 
            +
            1. **Project Agents**: `$PROJECT/.claude-mpm/agents/`
         | 
| 79 | 
            +
               - Project implementations and overrides
         | 
| 80 80 | 
             
               - Highest precedence for project context
         | 
| 81 81 | 
             
               - Custom agents tailored to project requirements
         | 
| 82 82 |  | 
| @@ -376,7 +376,7 @@ for agent_id, metadata in agents.items(): | |
| 376 376 | 
             
            #### Directory Precedence Rules and Agent Discovery
         | 
| 377 377 |  | 
| 378 378 | 
             
            **Enhanced Agent Discovery Pattern (Highest to Lowest Priority):**
         | 
| 379 | 
            -
            1. **Project Agents**: `$PROJECT/.claude-mpm/agents | 
| 379 | 
            +
            1. **Project Agents**: `$PROJECT/.claude-mpm/agents/`
         | 
| 380 380 | 
             
            2. **Current Directory User Agents**: `$PWD/.claude-mpm/agents/user-agents/`
         | 
| 381 381 | 
             
            3. **Parent Directory User Agents**: Walk up tree checking `../user-agents/`, `../../user-agents/`, etc.
         | 
| 382 382 | 
             
            4. **User Home Agents**: `~/.claude-mpm/agents/user-defined/`
         | 
| @@ -79,9 +79,9 @@ claude-pm init --verify | |
| 79 79 | 
             
               - Working directory context
         | 
| 80 80 |  | 
| 81 81 | 
             
            3. **Project Directory** (`$PROJECT_ROOT/.claude-mpm/`)
         | 
| 82 | 
            -
               - Project | 
| 82 | 
            +
               - Project agents in `agents/`
         | 
| 83 83 | 
             
               - User agents in `agents/user-agents/` with directory precedence
         | 
| 84 | 
            -
               - Project | 
| 84 | 
            +
               - Project configuration
         | 
| 85 85 |  | 
| 86 86 | 
             
            ### Health Validation and Deployment Procedures
         | 
| 87 87 |  | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            0.9.0
         | 
| @@ -53,6 +53,23 @@ class AgentValidator: | |
| 53 53 | 
             
                - Privilege escalation (via tool combinations)
         | 
| 54 54 | 
             
                """
         | 
| 55 55 |  | 
| 56 | 
            +
                # Model name mappings for normalization to tier names
         | 
| 57 | 
            +
                MODEL_MAPPINGS = {
         | 
| 58 | 
            +
                    # Sonnet variations
         | 
| 59 | 
            +
                    "claude-3-5-sonnet-20241022": "sonnet",
         | 
| 60 | 
            +
                    "claude-3-5-sonnet-20240620": "sonnet",
         | 
| 61 | 
            +
                    "claude-sonnet-4-20250514": "sonnet",
         | 
| 62 | 
            +
                    "claude-4-sonnet-20250514": "sonnet",
         | 
| 63 | 
            +
                    "claude-3-sonnet-20240229": "sonnet",
         | 
| 64 | 
            +
                    # Opus variations
         | 
| 65 | 
            +
                    "claude-3-opus-20240229": "opus",
         | 
| 66 | 
            +
                    "claude-opus-4-20250514": "opus",
         | 
| 67 | 
            +
                    "claude-4-opus-20250514": "opus",
         | 
| 68 | 
            +
                    # Haiku variations
         | 
| 69 | 
            +
                    "claude-3-haiku-20240307": "haiku",
         | 
| 70 | 
            +
                    "claude-3-5-haiku-20241022": "haiku",
         | 
| 71 | 
            +
                }
         | 
| 72 | 
            +
                
         | 
| 56 73 | 
             
                def __init__(self, schema_path: Optional[Path] = None):
         | 
| 57 74 | 
             
                    """Initialize the validator with the agent schema."""
         | 
| 58 75 | 
             
                    if schema_path is None:
         | 
| @@ -83,6 +100,33 @@ class AgentValidator: | |
| 83 100 | 
             
                        logger.error(f"Failed to load schema from {self.schema_path}: {e}")
         | 
| 84 101 | 
             
                        raise
         | 
| 85 102 |  | 
| 103 | 
            +
                def _normalize_model(self, model: str) -> str:
         | 
| 104 | 
            +
                    """Normalize model name to standard tier (opus, sonnet, haiku).
         | 
| 105 | 
            +
                    
         | 
| 106 | 
            +
                    Args:
         | 
| 107 | 
            +
                        model: Original model name
         | 
| 108 | 
            +
                        
         | 
| 109 | 
            +
                    Returns:
         | 
| 110 | 
            +
                        Normalized model tier name
         | 
| 111 | 
            +
                    """
         | 
| 112 | 
            +
                    # Direct mapping check
         | 
| 113 | 
            +
                    if model in self.MODEL_MAPPINGS:
         | 
| 114 | 
            +
                        return self.MODEL_MAPPINGS[model]
         | 
| 115 | 
            +
                    
         | 
| 116 | 
            +
                    # Already normalized
         | 
| 117 | 
            +
                    if model in {"opus", "sonnet", "haiku"}:
         | 
| 118 | 
            +
                        return model
         | 
| 119 | 
            +
                    
         | 
| 120 | 
            +
                    # Check if model contains tier name
         | 
| 121 | 
            +
                    model_lower = model.lower()
         | 
| 122 | 
            +
                    for tier in {"opus", "sonnet", "haiku"}:
         | 
| 123 | 
            +
                        if tier in model_lower:
         | 
| 124 | 
            +
                            return tier
         | 
| 125 | 
            +
                    
         | 
| 126 | 
            +
                    # Default to sonnet if unrecognized
         | 
| 127 | 
            +
                    logger.warning(f"Unrecognized model '{model}', defaulting to 'sonnet'")
         | 
| 128 | 
            +
                    return "sonnet"
         | 
| 129 | 
            +
                
         | 
| 86 130 | 
             
                def validate_agent(self, agent_data: Dict[str, Any]) -> ValidationResult:
         | 
| 87 131 | 
             
                    """
         | 
| 88 132 | 
             
                    Validate a single agent configuration against the schema.
         | 
| @@ -101,6 +145,14 @@ class AgentValidator: | |
| 101 145 | 
             
                    """
         | 
| 102 146 | 
             
                    result = ValidationResult(is_valid=True)
         | 
| 103 147 |  | 
| 148 | 
            +
                    # Normalize model name before validation
         | 
| 149 | 
            +
                    if "capabilities" in agent_data and "model" in agent_data["capabilities"]:
         | 
| 150 | 
            +
                        original_model = agent_data["capabilities"]["model"]
         | 
| 151 | 
            +
                        normalized_model = self._normalize_model(original_model)
         | 
| 152 | 
            +
                        if original_model != normalized_model:
         | 
| 153 | 
            +
                            agent_data["capabilities"]["model"] = normalized_model
         | 
| 154 | 
            +
                            result.warnings.append(f"Normalized model from '{original_model}' to '{normalized_model}'")
         | 
| 155 | 
            +
                    
         | 
| 104 156 | 
             
                    # Perform JSON schema validation
         | 
| 105 157 | 
             
                    try:
         | 
| 106 158 | 
             
                        validate(instance=agent_data, schema=self.schema)
         | 
| @@ -242,8 +294,11 @@ class AgentValidator: | |
| 242 294 | 
             
                    model = agent_data.get("capabilities", {}).get("model", "")
         | 
| 243 295 | 
             
                    tools = agent_data.get("capabilities", {}).get("tools", [])
         | 
| 244 296 |  | 
| 297 | 
            +
                    # Normalize model name for comparison
         | 
| 298 | 
            +
                    normalized_model = self._normalize_model(model)
         | 
| 299 | 
            +
                    
         | 
| 245 300 | 
             
                    # Haiku models shouldn't use resource-intensive tools
         | 
| 246 | 
            -
                    if "haiku" | 
| 301 | 
            +
                    if normalized_model == "haiku":
         | 
| 247 302 | 
             
                        intensive_tools = {"docker", "kubectl", "terraform", "aws", "gcloud", "azure"}
         | 
| 248 303 | 
             
                        used_intensive = set(tools) & intensive_tools
         | 
| 249 304 | 
             
                        if used_intensive:
         |