claude-mpm 3.7.8__py3-none-any.whl → 3.9.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/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -96
- claude_mpm/agents/MEMORY.md +94 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +3 -8
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +2 -2
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +8 -3
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +217 -5
- claude_mpm/config/__init__.py +30 -39
- claude_mpm/config/socketio_config.py +8 -5
- claude_mpm/constants.py +13 -0
- claude_mpm/core/__init__.py +8 -18
- claude_mpm/core/cache.py +596 -0
- claude_mpm/core/claude_runner.py +166 -622
- claude_mpm/core/config.py +7 -3
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +548 -38
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +249 -93
- claude_mpm/core/interactive_session.py +479 -0
- claude_mpm/core/interfaces.py +424 -0
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/logging_config.py +444 -0
- claude_mpm/core/oneshot_session.py +465 -0
- claude_mpm/core/optimized_agent_loader.py +485 -0
- claude_mpm/core/optimized_startup.py +490 -0
- claude_mpm/core/service_registry.py +52 -26
- claude_mpm/core/socketio_pool.py +162 -5
- claude_mpm/core/types.py +292 -0
- claude_mpm/core/typing_utils.py +477 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
- claude_mpm/init.py +2 -1
- claude_mpm/services/__init__.py +78 -14
- claude_mpm/services/agent/__init__.py +24 -0
- claude_mpm/services/agent/deployment.py +2548 -0
- claude_mpm/services/agent/management.py +598 -0
- claude_mpm/services/agent/registry.py +813 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
- claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
- claude_mpm/services/async_session_logger.py +8 -3
- claude_mpm/services/communication/__init__.py +21 -0
- claude_mpm/services/communication/socketio.py +1933 -0
- claude_mpm/services/communication/websocket.py +479 -0
- claude_mpm/services/core/__init__.py +123 -0
- claude_mpm/services/core/base.py +247 -0
- claude_mpm/services/core/interfaces.py +951 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
- claude_mpm/services/framework_claude_md_generator.py +3 -2
- claude_mpm/services/health_monitor.py +4 -3
- claude_mpm/services/hook_service.py +64 -4
- claude_mpm/services/infrastructure/__init__.py +21 -0
- claude_mpm/services/infrastructure/logging.py +202 -0
- claude_mpm/services/infrastructure/monitoring.py +893 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/project/__init__.py +21 -0
- claude_mpm/services/project/analyzer.py +864 -0
- claude_mpm/services/project/registry.py +608 -0
- claude_mpm/services/project_analyzer.py +95 -2
- claude_mpm/services/recovery_manager.py +15 -9
- claude_mpm/services/response_tracker.py +3 -5
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/base.py +121 -0
- claude_mpm/services/socketio/handlers/connection.py +198 -0
- claude_mpm/services/socketio/handlers/file.py +213 -0
- claude_mpm/services/socketio/handlers/git.py +723 -0
- claude_mpm/services/socketio/handlers/memory.py +27 -0
- claude_mpm/services/socketio/handlers/project.py +25 -0
- claude_mpm/services/socketio/handlers/registry.py +145 -0
- claude_mpm/services/socketio_client_manager.py +12 -7
- claude_mpm/services/socketio_server.py +156 -30
- claude_mpm/services/ticket_manager.py +172 -9
- claude_mpm/services/ticket_manager_di.py +1 -1
- claude_mpm/services/version_control/semantic_versioning.py +80 -7
- claude_mpm/services/version_control/version_parser.py +528 -0
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,598 @@ | |
| 1 | 
            +
            #!/usr/bin/env python3
         | 
| 2 | 
            +
            """
         | 
| 3 | 
            +
            Agent Management Service
         | 
| 4 | 
            +
            ========================
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Comprehensive service for managing agent definitions with CRUD operations,
         | 
| 7 | 
            +
            section extraction/updates, and version management.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Uses python-frontmatter and mistune for markdown parsing as recommended.
         | 
| 10 | 
            +
            """
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            import os
         | 
| 13 | 
            +
            import re
         | 
| 14 | 
            +
            import json
         | 
| 15 | 
            +
            import yaml
         | 
| 16 | 
            +
            import logging
         | 
| 17 | 
            +
            from pathlib import Path
         | 
| 18 | 
            +
            from typing import Dict, List, Optional, Tuple, Any
         | 
| 19 | 
            +
            from datetime import datetime
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            import frontmatter
         | 
| 22 | 
            +
            import mistune
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            from claude_mpm.models.agent_definition import (
         | 
| 25 | 
            +
                AgentDefinition, AgentMetadata, AgentType, 
         | 
| 26 | 
            +
                AgentSection, AgentWorkflow, AgentPermissions
         | 
| 27 | 
            +
            )
         | 
| 28 | 
            +
            from ..deployment.agent_versioning import AgentVersionManager
         | 
| 29 | 
            +
            from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
         | 
| 30 | 
            +
            from claude_mpm.utils.paths import PathResolver
         | 
| 31 | 
            +
            from claude_mpm.core.config_paths import ConfigPaths
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            logger = logging.getLogger(__name__)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
             | 
| 36 | 
            +
            class AgentManager:
         | 
| 37 | 
            +
                """Manages agent definitions with CRUD operations and versioning."""
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                def __init__(self, framework_dir: Optional[Path] = None, project_dir: Optional[Path] = None):
         | 
| 40 | 
            +
                    """
         | 
| 41 | 
            +
                    Initialize AgentManager.
         | 
| 42 | 
            +
                    
         | 
| 43 | 
            +
                    Args:
         | 
| 44 | 
            +
                        framework_dir: Path to agents templates directory
         | 
| 45 | 
            +
                        project_dir: Path to project-specific agents directory
         | 
| 46 | 
            +
                    """
         | 
| 47 | 
            +
                    # Use PathResolver for consistent path discovery
         | 
| 48 | 
            +
                    if framework_dir is None:
         | 
| 49 | 
            +
                        try:
         | 
| 50 | 
            +
                            # Use agents templates directory
         | 
| 51 | 
            +
                            self.framework_dir = Path(__file__).parent.parent / "agents" / "templates"
         | 
| 52 | 
            +
                        except Exception:
         | 
| 53 | 
            +
                            # Fallback to agents directory
         | 
| 54 | 
            +
                            self.framework_dir = PathResolver.get_agents_dir()
         | 
| 55 | 
            +
                    else:
         | 
| 56 | 
            +
                        self.framework_dir = framework_dir
         | 
| 57 | 
            +
                        
         | 
| 58 | 
            +
                    if project_dir is None:
         | 
| 59 | 
            +
                        project_root = PathResolver.get_project_root()
         | 
| 60 | 
            +
                        # Use direct agents directory without subdirectory to match deployment expectations
         | 
| 61 | 
            +
                        self.project_dir = project_root / ConfigPaths.CONFIG_DIR / "agents"
         | 
| 62 | 
            +
                    else:
         | 
| 63 | 
            +
                        self.project_dir = project_dir
         | 
| 64 | 
            +
                    self.version_manager = AgentVersionManager()
         | 
| 65 | 
            +
                    self.cache = SharedPromptCache.get_instance()
         | 
| 66 | 
            +
                    self._markdown = mistune.create_markdown()
         | 
| 67 | 
            +
                    
         | 
| 68 | 
            +
                def create_agent(self, name: str, definition: AgentDefinition, location: str = "project") -> Path:
         | 
| 69 | 
            +
                    """
         | 
| 70 | 
            +
                    Create a new agent definition file.
         | 
| 71 | 
            +
                    
         | 
| 72 | 
            +
                    Args:
         | 
| 73 | 
            +
                        name: Agent name (e.g., "performance-agent")
         | 
| 74 | 
            +
                        definition: Agent definition object
         | 
| 75 | 
            +
                        location: "project" or "framework"
         | 
| 76 | 
            +
                        
         | 
| 77 | 
            +
                    Returns:
         | 
| 78 | 
            +
                        Path to created file
         | 
| 79 | 
            +
                    """
         | 
| 80 | 
            +
                    # Determine target directory
         | 
| 81 | 
            +
                    target_dir = self.project_dir if location == "project" else self.framework_dir
         | 
| 82 | 
            +
                    target_dir.mkdir(parents=True, exist_ok=True)
         | 
| 83 | 
            +
                    
         | 
| 84 | 
            +
                    # Generate markdown content
         | 
| 85 | 
            +
                    content = self._definition_to_markdown(definition)
         | 
| 86 | 
            +
                    
         | 
| 87 | 
            +
                    # Write file
         | 
| 88 | 
            +
                    file_path = target_dir / f"{name}.md"
         | 
| 89 | 
            +
                    file_path.write_text(content, encoding='utf-8')
         | 
| 90 | 
            +
                    
         | 
| 91 | 
            +
                    # Clear cache
         | 
| 92 | 
            +
                    self._clear_agent_cache(name)
         | 
| 93 | 
            +
                    
         | 
| 94 | 
            +
                    logger.info(f"Created agent '{name}' at {file_path}")
         | 
| 95 | 
            +
                    return file_path
         | 
| 96 | 
            +
                
         | 
| 97 | 
            +
                def read_agent(self, name: str) -> Optional[AgentDefinition]:
         | 
| 98 | 
            +
                    """
         | 
| 99 | 
            +
                    Read an agent definition.
         | 
| 100 | 
            +
                    
         | 
| 101 | 
            +
                    Args:
         | 
| 102 | 
            +
                        name: Agent name (without .md extension)
         | 
| 103 | 
            +
                        
         | 
| 104 | 
            +
                    Returns:
         | 
| 105 | 
            +
                        AgentDefinition or None if not found
         | 
| 106 | 
            +
                    """
         | 
| 107 | 
            +
                    # Try to find the agent file
         | 
| 108 | 
            +
                    agent_path = self._find_agent_file(name)
         | 
| 109 | 
            +
                    if not agent_path:
         | 
| 110 | 
            +
                        logger.warning(f"Agent '{name}' not found")
         | 
| 111 | 
            +
                        return None
         | 
| 112 | 
            +
                    
         | 
| 113 | 
            +
                    try:
         | 
| 114 | 
            +
                        # Read and parse the file
         | 
| 115 | 
            +
                        content = agent_path.read_text(encoding='utf-8')
         | 
| 116 | 
            +
                        return self._parse_agent_markdown(content, name, str(agent_path))
         | 
| 117 | 
            +
                    except Exception as e:
         | 
| 118 | 
            +
                        logger.error(f"Error reading agent '{name}': {e}")
         | 
| 119 | 
            +
                        return None
         | 
| 120 | 
            +
                
         | 
| 121 | 
            +
                def update_agent(self, name: str, updates: Dict[str, Any], 
         | 
| 122 | 
            +
                                increment_version: bool = True) -> Optional[AgentDefinition]:
         | 
| 123 | 
            +
                    """
         | 
| 124 | 
            +
                    Update an agent definition.
         | 
| 125 | 
            +
                    
         | 
| 126 | 
            +
                    Args:
         | 
| 127 | 
            +
                        name: Agent name
         | 
| 128 | 
            +
                        updates: Dictionary of updates to apply
         | 
| 129 | 
            +
                        increment_version: Whether to increment serial version
         | 
| 130 | 
            +
                        
         | 
| 131 | 
            +
                    Returns:
         | 
| 132 | 
            +
                        Updated AgentDefinition or None if failed
         | 
| 133 | 
            +
                    """
         | 
| 134 | 
            +
                    # Read current definition
         | 
| 135 | 
            +
                    agent_def = self.read_agent(name)
         | 
| 136 | 
            +
                    if not agent_def:
         | 
| 137 | 
            +
                        return None
         | 
| 138 | 
            +
                    
         | 
| 139 | 
            +
                    # Apply updates
         | 
| 140 | 
            +
                    for key, value in updates.items():
         | 
| 141 | 
            +
                        if hasattr(agent_def, key):
         | 
| 142 | 
            +
                            setattr(agent_def, key, value)
         | 
| 143 | 
            +
                        elif key in ["type", "model_preference", "tags", "specializations"]:
         | 
| 144 | 
            +
                            setattr(agent_def.metadata, key, value)
         | 
| 145 | 
            +
                    
         | 
| 146 | 
            +
                    # Increment version if requested
         | 
| 147 | 
            +
                    if increment_version:
         | 
| 148 | 
            +
                        agent_def.metadata.increment_serial_version()
         | 
| 149 | 
            +
                        agent_def.metadata.last_updated = datetime.now()
         | 
| 150 | 
            +
                    
         | 
| 151 | 
            +
                    # Write back
         | 
| 152 | 
            +
                    agent_path = self._find_agent_file(name)
         | 
| 153 | 
            +
                    if agent_path:
         | 
| 154 | 
            +
                        content = self._definition_to_markdown(agent_def)
         | 
| 155 | 
            +
                        agent_path.write_text(content, encoding='utf-8')
         | 
| 156 | 
            +
                        
         | 
| 157 | 
            +
                        # Clear cache
         | 
| 158 | 
            +
                        self._clear_agent_cache(name)
         | 
| 159 | 
            +
                        
         | 
| 160 | 
            +
                        logger.info(f"Updated agent '{name}' to version {agent_def.metadata.version}")
         | 
| 161 | 
            +
                        return agent_def
         | 
| 162 | 
            +
                    
         | 
| 163 | 
            +
                    return None
         | 
| 164 | 
            +
                
         | 
| 165 | 
            +
                def update_section(self, name: str, section: AgentSection, content: str,
         | 
| 166 | 
            +
                                  increment_version: bool = True) -> Optional[AgentDefinition]:
         | 
| 167 | 
            +
                    """
         | 
| 168 | 
            +
                    Update a specific section of an agent.
         | 
| 169 | 
            +
                    
         | 
| 170 | 
            +
                    Args:
         | 
| 171 | 
            +
                        name: Agent name
         | 
| 172 | 
            +
                        section: Section to update
         | 
| 173 | 
            +
                        content: New section content
         | 
| 174 | 
            +
                        increment_version: Whether to increment version
         | 
| 175 | 
            +
                        
         | 
| 176 | 
            +
                    Returns:
         | 
| 177 | 
            +
                        Updated AgentDefinition or None
         | 
| 178 | 
            +
                    """
         | 
| 179 | 
            +
                    agent_def = self.read_agent(name)
         | 
| 180 | 
            +
                    if not agent_def:
         | 
| 181 | 
            +
                        return None
         | 
| 182 | 
            +
                    
         | 
| 183 | 
            +
                    # Map section to attribute
         | 
| 184 | 
            +
                    section_map = {
         | 
| 185 | 
            +
                        AgentSection.PRIMARY_ROLE: "primary_role",
         | 
| 186 | 
            +
                        AgentSection.CAPABILITIES: "capabilities",
         | 
| 187 | 
            +
                        AgentSection.TOOLS: "tools_commands",
         | 
| 188 | 
            +
                        AgentSection.ESCALATION: "escalation_triggers",
         | 
| 189 | 
            +
                        AgentSection.KPI: "kpis",
         | 
| 190 | 
            +
                        AgentSection.DEPENDENCIES: "dependencies"
         | 
| 191 | 
            +
                    }
         | 
| 192 | 
            +
                    
         | 
| 193 | 
            +
                    if section in section_map:
         | 
| 194 | 
            +
                        attr_name = section_map[section]
         | 
| 195 | 
            +
                        if section in [AgentSection.CAPABILITIES, AgentSection.ESCALATION, 
         | 
| 196 | 
            +
                                      AgentSection.KPI, AgentSection.DEPENDENCIES]:
         | 
| 197 | 
            +
                            # Parse list content
         | 
| 198 | 
            +
                            setattr(agent_def, attr_name, self._parse_list_content(content))
         | 
| 199 | 
            +
                        else:
         | 
| 200 | 
            +
                            setattr(agent_def, attr_name, content.strip())
         | 
| 201 | 
            +
                    
         | 
| 202 | 
            +
                    # Special handling for complex sections
         | 
| 203 | 
            +
                    elif section == AgentSection.WHEN_TO_USE:
         | 
| 204 | 
            +
                        agent_def.when_to_use = self._parse_when_to_use(content)
         | 
| 205 | 
            +
                    elif section == AgentSection.AUTHORITY:
         | 
| 206 | 
            +
                        agent_def.authority = self._parse_authority(content)
         | 
| 207 | 
            +
                    elif section == AgentSection.WORKFLOWS:
         | 
| 208 | 
            +
                        agent_def.workflows = self._parse_workflows(content)
         | 
| 209 | 
            +
                    
         | 
| 210 | 
            +
                    # Update raw section
         | 
| 211 | 
            +
                    agent_def.raw_sections[section.value] = content
         | 
| 212 | 
            +
                    
         | 
| 213 | 
            +
                    # Increment version
         | 
| 214 | 
            +
                    if increment_version:
         | 
| 215 | 
            +
                        agent_def.metadata.increment_serial_version()
         | 
| 216 | 
            +
                        agent_def.metadata.last_updated = datetime.now()
         | 
| 217 | 
            +
                    
         | 
| 218 | 
            +
                    # Write back
         | 
| 219 | 
            +
                    return self.update_agent(name, {}, increment_version=False)
         | 
| 220 | 
            +
                
         | 
| 221 | 
            +
                def delete_agent(self, name: str) -> bool:
         | 
| 222 | 
            +
                    """
         | 
| 223 | 
            +
                    Delete an agent definition.
         | 
| 224 | 
            +
                    
         | 
| 225 | 
            +
                    Args:
         | 
| 226 | 
            +
                        name: Agent name
         | 
| 227 | 
            +
                        
         | 
| 228 | 
            +
                    Returns:
         | 
| 229 | 
            +
                        True if deleted, False otherwise
         | 
| 230 | 
            +
                    """
         | 
| 231 | 
            +
                    agent_path = self._find_agent_file(name)
         | 
| 232 | 
            +
                    if not agent_path:
         | 
| 233 | 
            +
                        return False
         | 
| 234 | 
            +
                    
         | 
| 235 | 
            +
                    try:
         | 
| 236 | 
            +
                        agent_path.unlink()
         | 
| 237 | 
            +
                        self._clear_agent_cache(name)
         | 
| 238 | 
            +
                        logger.info(f"Deleted agent '{name}'")
         | 
| 239 | 
            +
                        return True
         | 
| 240 | 
            +
                    except Exception as e:
         | 
| 241 | 
            +
                        logger.error(f"Error deleting agent '{name}': {e}")
         | 
| 242 | 
            +
                        return False
         | 
| 243 | 
            +
                
         | 
| 244 | 
            +
                def list_agents(self, location: Optional[str] = None) -> Dict[str, Dict[str, Any]]:
         | 
| 245 | 
            +
                    """
         | 
| 246 | 
            +
                    List all available agents.
         | 
| 247 | 
            +
                    
         | 
| 248 | 
            +
                    Args:
         | 
| 249 | 
            +
                        location: Filter by location ("project", "framework", or None for all)
         | 
| 250 | 
            +
                        
         | 
| 251 | 
            +
                    Returns:
         | 
| 252 | 
            +
                        Dictionary of agent info
         | 
| 253 | 
            +
                    """
         | 
| 254 | 
            +
                    agents = {}
         | 
| 255 | 
            +
                    
         | 
| 256 | 
            +
                    # Check framework agents
         | 
| 257 | 
            +
                    if location in [None, "framework"]:
         | 
| 258 | 
            +
                        for agent_file in self.framework_dir.glob("*.md"):
         | 
| 259 | 
            +
                            if agent_file.name != "base_agent.md":
         | 
| 260 | 
            +
                                agent_name = agent_file.stem
         | 
| 261 | 
            +
                                agent_def = self.read_agent(agent_name)
         | 
| 262 | 
            +
                                if agent_def:
         | 
| 263 | 
            +
                                    agents[agent_name] = {
         | 
| 264 | 
            +
                                        "location": "framework",
         | 
| 265 | 
            +
                                        "path": str(agent_file),
         | 
| 266 | 
            +
                                        "version": agent_def.metadata.version,
         | 
| 267 | 
            +
                                        "type": agent_def.metadata.type.value,
         | 
| 268 | 
            +
                                        "specializations": agent_def.metadata.specializations
         | 
| 269 | 
            +
                                    }
         | 
| 270 | 
            +
                    
         | 
| 271 | 
            +
                    # Check project agents
         | 
| 272 | 
            +
                    if location in [None, "project"] and self.project_dir.exists():
         | 
| 273 | 
            +
                        for agent_file in self.project_dir.glob("*.md"):
         | 
| 274 | 
            +
                            agent_name = agent_file.stem
         | 
| 275 | 
            +
                            agent_def = self.read_agent(agent_name)
         | 
| 276 | 
            +
                            if agent_def:
         | 
| 277 | 
            +
                                agents[agent_name] = {
         | 
| 278 | 
            +
                                    "location": "project",
         | 
| 279 | 
            +
                                    "path": str(agent_file),
         | 
| 280 | 
            +
                                    "version": agent_def.metadata.version,
         | 
| 281 | 
            +
                                    "type": agent_def.metadata.type.value,
         | 
| 282 | 
            +
                                    "specializations": agent_def.metadata.specializations
         | 
| 283 | 
            +
                                }
         | 
| 284 | 
            +
                    
         | 
| 285 | 
            +
                    return agents
         | 
| 286 | 
            +
                
         | 
| 287 | 
            +
                def get_agent_api(self, name: str) -> Optional[Dict[str, Any]]:
         | 
| 288 | 
            +
                    """
         | 
| 289 | 
            +
                    Get agent data in API-friendly format.
         | 
| 290 | 
            +
                    
         | 
| 291 | 
            +
                    Args:
         | 
| 292 | 
            +
                        name: Agent name
         | 
| 293 | 
            +
                        
         | 
| 294 | 
            +
                    Returns:
         | 
| 295 | 
            +
                        Agent data dictionary or None
         | 
| 296 | 
            +
                    """
         | 
| 297 | 
            +
                    agent_def = self.read_agent(name)
         | 
| 298 | 
            +
                    if not agent_def:
         | 
| 299 | 
            +
                        return None
         | 
| 300 | 
            +
                    
         | 
| 301 | 
            +
                    return agent_def.to_dict()
         | 
| 302 | 
            +
                
         | 
| 303 | 
            +
                # Private helper methods
         | 
| 304 | 
            +
                
         | 
| 305 | 
            +
                def _find_agent_file(self, name: str) -> Optional[Path]:
         | 
| 306 | 
            +
                    """Find agent file in project or framework directories."""
         | 
| 307 | 
            +
                    # Check project first (higher precedence)
         | 
| 308 | 
            +
                    if self.project_dir.exists():
         | 
| 309 | 
            +
                        project_path = self.project_dir / f"{name}.md"
         | 
| 310 | 
            +
                        if project_path.exists():
         | 
| 311 | 
            +
                            return project_path
         | 
| 312 | 
            +
                    
         | 
| 313 | 
            +
                    # Check framework
         | 
| 314 | 
            +
                    framework_path = self.framework_dir / f"{name}.md"
         | 
| 315 | 
            +
                    if framework_path.exists():
         | 
| 316 | 
            +
                        return framework_path
         | 
| 317 | 
            +
                    
         | 
| 318 | 
            +
                    return None
         | 
| 319 | 
            +
                
         | 
| 320 | 
            +
                def _parse_agent_markdown(self, content: str, name: str, file_path: str) -> AgentDefinition:
         | 
| 321 | 
            +
                    """Parse markdown content into AgentDefinition."""
         | 
| 322 | 
            +
                    # Parse frontmatter
         | 
| 323 | 
            +
                    post = frontmatter.loads(content)
         | 
| 324 | 
            +
                    
         | 
| 325 | 
            +
                    # Extract metadata
         | 
| 326 | 
            +
                    metadata = AgentMetadata(
         | 
| 327 | 
            +
                        type=AgentType(post.metadata.get("type", "core")),
         | 
| 328 | 
            +
                        model_preference=post.metadata.get("model_preference", "claude-3-sonnet"),
         | 
| 329 | 
            +
                        version=post.metadata.get("version", "1.0.0"),
         | 
| 330 | 
            +
                        last_updated=post.metadata.get("last_updated"),
         | 
| 331 | 
            +
                        author=post.metadata.get("author"),
         | 
| 332 | 
            +
                        tags=post.metadata.get("tags", []),
         | 
| 333 | 
            +
                        specializations=post.metadata.get("specializations", [])
         | 
| 334 | 
            +
                    )
         | 
| 335 | 
            +
                    
         | 
| 336 | 
            +
                    # Extract version from content if not in frontmatter
         | 
| 337 | 
            +
                    if not post.metadata.get("version"):
         | 
| 338 | 
            +
                        version = self.version_manager.extract_version_from_markdown(content)
         | 
| 339 | 
            +
                        if version:
         | 
| 340 | 
            +
                            metadata.version = version
         | 
| 341 | 
            +
                    
         | 
| 342 | 
            +
                    # Parse sections
         | 
| 343 | 
            +
                    sections = self._extract_sections(post.content)
         | 
| 344 | 
            +
                    
         | 
| 345 | 
            +
                    # Extract title
         | 
| 346 | 
            +
                    title_match = re.search(r'^#\s+(.+)$', post.content, re.MULTILINE)
         | 
| 347 | 
            +
                    title = title_match.group(1) if title_match else name.replace('-', ' ').title()
         | 
| 348 | 
            +
                    
         | 
| 349 | 
            +
                    # Build definition
         | 
| 350 | 
            +
                    definition = AgentDefinition(
         | 
| 351 | 
            +
                        name=name,
         | 
| 352 | 
            +
                        title=title,
         | 
| 353 | 
            +
                        file_path=file_path,
         | 
| 354 | 
            +
                        metadata=metadata,
         | 
| 355 | 
            +
                        primary_role=sections.get("Primary Role", ""),
         | 
| 356 | 
            +
                        when_to_use=self._parse_when_to_use(sections.get("When to Use This Agent", "")),
         | 
| 357 | 
            +
                        capabilities=self._parse_list_content(sections.get("Core Capabilities", "")),
         | 
| 358 | 
            +
                        authority=self._parse_authority(sections.get("Authority & Permissions", "")),
         | 
| 359 | 
            +
                        workflows=self._parse_workflows(sections.get("Agent-Specific Workflows", "")),
         | 
| 360 | 
            +
                        escalation_triggers=self._parse_list_content(sections.get("Unique Escalation Triggers", "")),
         | 
| 361 | 
            +
                        kpis=self._parse_list_content(sections.get("Key Performance Indicators", "")),
         | 
| 362 | 
            +
                        dependencies=self._parse_list_content(sections.get("Critical Dependencies", "")),
         | 
| 363 | 
            +
                        tools_commands=sections.get("Specialized Tools/Commands", ""),
         | 
| 364 | 
            +
                        raw_content=content,
         | 
| 365 | 
            +
                        raw_sections=sections
         | 
| 366 | 
            +
                    )
         | 
| 367 | 
            +
                    
         | 
| 368 | 
            +
                    return definition
         | 
| 369 | 
            +
                
         | 
| 370 | 
            +
                def _extract_sections(self, content: str) -> Dict[str, str]:
         | 
| 371 | 
            +
                    """Extract sections from markdown content."""
         | 
| 372 | 
            +
                    sections = {}
         | 
| 373 | 
            +
                    current_section = None
         | 
| 374 | 
            +
                    current_content = []
         | 
| 375 | 
            +
                    
         | 
| 376 | 
            +
                    # Split into lines
         | 
| 377 | 
            +
                    lines = content.split('\n')
         | 
| 378 | 
            +
                    
         | 
| 379 | 
            +
                    for line in lines:
         | 
| 380 | 
            +
                        # Check if this is a section header
         | 
| 381 | 
            +
                        header_match = re.match(r'^##\s+(?:🎯|🔧|🔑|📋|🚨|📊|🔄|🛠️)?\s*(.+)$', line)
         | 
| 382 | 
            +
                        if header_match:
         | 
| 383 | 
            +
                            # Save previous section
         | 
| 384 | 
            +
                            if current_section:
         | 
| 385 | 
            +
                                sections[current_section] = '\n'.join(current_content).strip()
         | 
| 386 | 
            +
                            
         | 
| 387 | 
            +
                            # Start new section
         | 
| 388 | 
            +
                            current_section = header_match.group(1).strip()
         | 
| 389 | 
            +
                            current_content = []
         | 
| 390 | 
            +
                        else:
         | 
| 391 | 
            +
                            # Add to current section
         | 
| 392 | 
            +
                            if current_section:
         | 
| 393 | 
            +
                                current_content.append(line)
         | 
| 394 | 
            +
                    
         | 
| 395 | 
            +
                    # Save last section
         | 
| 396 | 
            +
                    if current_section:
         | 
| 397 | 
            +
                        sections[current_section] = '\n'.join(current_content).strip()
         | 
| 398 | 
            +
                    
         | 
| 399 | 
            +
                    return sections
         | 
| 400 | 
            +
                
         | 
| 401 | 
            +
                def _parse_list_content(self, content: str) -> List[str]:
         | 
| 402 | 
            +
                    """Parse bullet point or numbered list content."""
         | 
| 403 | 
            +
                    items = []
         | 
| 404 | 
            +
                    for line in content.split('\n'):
         | 
| 405 | 
            +
                        # Match bullet points or numbered items
         | 
| 406 | 
            +
                        match = re.match(r'^[-*•]\s+(.+)$|^\d+\.\s+(.+)$', line.strip())
         | 
| 407 | 
            +
                        if match:
         | 
| 408 | 
            +
                            item = match.group(1) or match.group(2)
         | 
| 409 | 
            +
                            items.append(item.strip())
         | 
| 410 | 
            +
                    return items
         | 
| 411 | 
            +
                
         | 
| 412 | 
            +
                def _parse_when_to_use(self, content: str) -> Dict[str, List[str]]:
         | 
| 413 | 
            +
                    """Parse When to Use section."""
         | 
| 414 | 
            +
                    result = {"select": [], "do_not_select": []}
         | 
| 415 | 
            +
                    current_mode = None
         | 
| 416 | 
            +
                    
         | 
| 417 | 
            +
                    for line in content.split('\n'):
         | 
| 418 | 
            +
                        if "Select this agent when:" in line or "**Select this agent when:**" in line:
         | 
| 419 | 
            +
                            current_mode = "select"
         | 
| 420 | 
            +
                        elif "Do NOT select for:" in line or "**Do NOT select for:**" in line:
         | 
| 421 | 
            +
                            current_mode = "do_not_select"
         | 
| 422 | 
            +
                        elif current_mode and line.strip().startswith('-'):
         | 
| 423 | 
            +
                            item = line.strip()[1:].strip()
         | 
| 424 | 
            +
                            result[current_mode].append(item)
         | 
| 425 | 
            +
                    
         | 
| 426 | 
            +
                    return result
         | 
| 427 | 
            +
                
         | 
| 428 | 
            +
                def _parse_authority(self, content: str) -> AgentPermissions:
         | 
| 429 | 
            +
                    """Parse Authority & Permissions section."""
         | 
| 430 | 
            +
                    permissions = AgentPermissions()
         | 
| 431 | 
            +
                    current_section = None
         | 
| 432 | 
            +
                    
         | 
| 433 | 
            +
                    for line in content.split('\n'):
         | 
| 434 | 
            +
                        if "Exclusive Write Access" in line:
         | 
| 435 | 
            +
                            current_section = "write"
         | 
| 436 | 
            +
                        elif "Forbidden Operations" in line:
         | 
| 437 | 
            +
                            current_section = "forbidden"
         | 
| 438 | 
            +
                        elif "Read Access" in line:
         | 
| 439 | 
            +
                            current_section = "read"
         | 
| 440 | 
            +
                        elif current_section and line.strip().startswith('-'):
         | 
| 441 | 
            +
                            item = line.strip()[1:].strip()
         | 
| 442 | 
            +
                            # Remove inline comments
         | 
| 443 | 
            +
                            item = re.sub(r'\s*#.*$', '', item).strip()
         | 
| 444 | 
            +
                            
         | 
| 445 | 
            +
                            if current_section == "write":
         | 
| 446 | 
            +
                                permissions.exclusive_write_access.append(item)
         | 
| 447 | 
            +
                            elif current_section == "forbidden":
         | 
| 448 | 
            +
                                permissions.forbidden_operations.append(item)
         | 
| 449 | 
            +
                            elif current_section == "read":
         | 
| 450 | 
            +
                                permissions.read_access.append(item)
         | 
| 451 | 
            +
                    
         | 
| 452 | 
            +
                    return permissions
         | 
| 453 | 
            +
                
         | 
| 454 | 
            +
                def _parse_workflows(self, content: str) -> List[AgentWorkflow]:
         | 
| 455 | 
            +
                    """Parse workflows from YAML blocks."""
         | 
| 456 | 
            +
                    workflows = []
         | 
| 457 | 
            +
                    
         | 
| 458 | 
            +
                    # Find all YAML blocks
         | 
| 459 | 
            +
                    yaml_blocks = re.findall(r'```yaml\n(.*?)\n```', content, re.DOTALL)
         | 
| 460 | 
            +
                    
         | 
| 461 | 
            +
                    for block in yaml_blocks:
         | 
| 462 | 
            +
                        try:
         | 
| 463 | 
            +
                            data = yaml.safe_load(block)
         | 
| 464 | 
            +
                            if isinstance(data, dict) and all(k in data for k in ["trigger", "process", "output"]):
         | 
| 465 | 
            +
                                # Extract workflow name from preceding heading if available
         | 
| 466 | 
            +
                                name_match = re.search(r'###\s+(.+)\n```yaml\n' + re.escape(block), content)
         | 
| 467 | 
            +
                                name = name_match.group(1) if name_match else "Unnamed Workflow"
         | 
| 468 | 
            +
                                
         | 
| 469 | 
            +
                                workflow = AgentWorkflow(
         | 
| 470 | 
            +
                                    name=name,
         | 
| 471 | 
            +
                                    trigger=data["trigger"],
         | 
| 472 | 
            +
                                    process=data["process"] if isinstance(data["process"], list) else [data["process"]],
         | 
| 473 | 
            +
                                    output=data["output"],
         | 
| 474 | 
            +
                                    raw_yaml=block
         | 
| 475 | 
            +
                                )
         | 
| 476 | 
            +
                                workflows.append(workflow)
         | 
| 477 | 
            +
                        except yaml.YAMLError:
         | 
| 478 | 
            +
                            logger.warning("Failed to parse YAML workflow block")
         | 
| 479 | 
            +
                    
         | 
| 480 | 
            +
                    return workflows
         | 
| 481 | 
            +
                
         | 
| 482 | 
            +
                def _definition_to_markdown(self, definition: AgentDefinition) -> str:
         | 
| 483 | 
            +
                    """Convert AgentDefinition back to markdown."""
         | 
| 484 | 
            +
                    # Start with frontmatter
         | 
| 485 | 
            +
                    frontmatter_data = {
         | 
| 486 | 
            +
                        "type": definition.metadata.type.value,
         | 
| 487 | 
            +
                        "model_preference": definition.metadata.model_preference,
         | 
| 488 | 
            +
                        "version": definition.metadata.version,
         | 
| 489 | 
            +
                        "last_updated": definition.metadata.last_updated,
         | 
| 490 | 
            +
                        "author": definition.metadata.author,
         | 
| 491 | 
            +
                        "tags": definition.metadata.tags,
         | 
| 492 | 
            +
                        "specializations": definition.metadata.specializations
         | 
| 493 | 
            +
                    }
         | 
| 494 | 
            +
                    
         | 
| 495 | 
            +
                    # Remove None values
         | 
| 496 | 
            +
                    frontmatter_data = {k: v for k, v in frontmatter_data.items() if v is not None}
         | 
| 497 | 
            +
                    
         | 
| 498 | 
            +
                    # Build content
         | 
| 499 | 
            +
                    content = []
         | 
| 500 | 
            +
                    content.append(f"# {definition.title}\n")
         | 
| 501 | 
            +
                    
         | 
| 502 | 
            +
                    # Primary Role
         | 
| 503 | 
            +
                    content.append("## 🎯 Primary Role")
         | 
| 504 | 
            +
                    content.append(definition.primary_role)
         | 
| 505 | 
            +
                    content.append("")
         | 
| 506 | 
            +
                    
         | 
| 507 | 
            +
                    # When to Use
         | 
| 508 | 
            +
                    content.append("## 🎯 When to Use This Agent")
         | 
| 509 | 
            +
                    content.append("")
         | 
| 510 | 
            +
                    content.append("**Select this agent when:**")
         | 
| 511 | 
            +
                    for item in definition.when_to_use.get("select", []):
         | 
| 512 | 
            +
                        content.append(f"- {item}")
         | 
| 513 | 
            +
                    content.append("")
         | 
| 514 | 
            +
                    content.append("**Do NOT select for:**")
         | 
| 515 | 
            +
                    for item in definition.when_to_use.get("do_not_select", []):
         | 
| 516 | 
            +
                        content.append(f"- {item}")
         | 
| 517 | 
            +
                    content.append("")
         | 
| 518 | 
            +
                    
         | 
| 519 | 
            +
                    # Capabilities
         | 
| 520 | 
            +
                    content.append("## 🔧 Core Capabilities")
         | 
| 521 | 
            +
                    for capability in definition.capabilities:
         | 
| 522 | 
            +
                        content.append(f"- {capability}")
         | 
| 523 | 
            +
                    content.append("")
         | 
| 524 | 
            +
                    
         | 
| 525 | 
            +
                    # Authority
         | 
| 526 | 
            +
                    content.append("## 🔑 Authority & Permissions")
         | 
| 527 | 
            +
                    content.append("")
         | 
| 528 | 
            +
                    content.append("### ✅ Exclusive Write Access")
         | 
| 529 | 
            +
                    for item in definition.authority.exclusive_write_access:
         | 
| 530 | 
            +
                        content.append(f"- {item}")
         | 
| 531 | 
            +
                    content.append("")
         | 
| 532 | 
            +
                    content.append("### ❌ Forbidden Operations")
         | 
| 533 | 
            +
                    for item in definition.authority.forbidden_operations:
         | 
| 534 | 
            +
                        content.append(f"- {item}")
         | 
| 535 | 
            +
                    content.append("")
         | 
| 536 | 
            +
                    
         | 
| 537 | 
            +
                    # Workflows
         | 
| 538 | 
            +
                    if definition.workflows:
         | 
| 539 | 
            +
                        content.append("## 📋 Agent-Specific Workflows")
         | 
| 540 | 
            +
                        content.append("")
         | 
| 541 | 
            +
                        for workflow in definition.workflows:
         | 
| 542 | 
            +
                            content.append(f"### {workflow.name}")
         | 
| 543 | 
            +
                            content.append("```yaml")
         | 
| 544 | 
            +
                            yaml_content = {
         | 
| 545 | 
            +
                                "trigger": workflow.trigger,
         | 
| 546 | 
            +
                                "process": workflow.process,
         | 
| 547 | 
            +
                                "output": workflow.output
         | 
| 548 | 
            +
                            }
         | 
| 549 | 
            +
                            content.append(yaml.dump(yaml_content, default_flow_style=False).strip())
         | 
| 550 | 
            +
                            content.append("```")
         | 
| 551 | 
            +
                            content.append("")
         | 
| 552 | 
            +
                    
         | 
| 553 | 
            +
                    # Escalation
         | 
| 554 | 
            +
                    if definition.escalation_triggers:
         | 
| 555 | 
            +
                        content.append("## 🚨 Unique Escalation Triggers")
         | 
| 556 | 
            +
                        for trigger in definition.escalation_triggers:
         | 
| 557 | 
            +
                            content.append(f"- {trigger}")
         | 
| 558 | 
            +
                        content.append("")
         | 
| 559 | 
            +
                    
         | 
| 560 | 
            +
                    # KPIs
         | 
| 561 | 
            +
                    if definition.kpis:
         | 
| 562 | 
            +
                        content.append("## 📊 Key Performance Indicators")
         | 
| 563 | 
            +
                        for i, kpi in enumerate(definition.kpis, 1):
         | 
| 564 | 
            +
                            content.append(f"{i}. {kpi}")
         | 
| 565 | 
            +
                        content.append("")
         | 
| 566 | 
            +
                    
         | 
| 567 | 
            +
                    # Dependencies
         | 
| 568 | 
            +
                    if definition.dependencies:
         | 
| 569 | 
            +
                        content.append("## 🔄 Critical Dependencies")
         | 
| 570 | 
            +
                        for dep in definition.dependencies:
         | 
| 571 | 
            +
                            content.append(f"- {dep}")
         | 
| 572 | 
            +
                        content.append("")
         | 
| 573 | 
            +
                    
         | 
| 574 | 
            +
                    # Tools
         | 
| 575 | 
            +
                    if definition.tools_commands:
         | 
| 576 | 
            +
                        content.append("## 🛠️ Specialized Tools/Commands")
         | 
| 577 | 
            +
                        content.append(definition.tools_commands)
         | 
| 578 | 
            +
                        content.append("")
         | 
| 579 | 
            +
                    
         | 
| 580 | 
            +
                    # Footer
         | 
| 581 | 
            +
                    content.append("---")
         | 
| 582 | 
            +
                    content.append(f"**Agent Type**: {definition.metadata.type.value}")
         | 
| 583 | 
            +
                    content.append(f"**Model Preference**: {definition.metadata.model_preference}")
         | 
| 584 | 
            +
                    content.append(f"**Version**: {definition.metadata.version}")
         | 
| 585 | 
            +
                    if definition.metadata.last_updated:
         | 
| 586 | 
            +
                        content.append(f"**Last Updated**: {definition.metadata.last_updated.strftime('%Y-%m-%d %H:%M:%S')}")
         | 
| 587 | 
            +
                    
         | 
| 588 | 
            +
                    # Combine with frontmatter
         | 
| 589 | 
            +
                    post = frontmatter.Post('\n'.join(content), **frontmatter_data)
         | 
| 590 | 
            +
                    return frontmatter.dumps(post)
         | 
| 591 | 
            +
                
         | 
| 592 | 
            +
                def _clear_agent_cache(self, name: str):
         | 
| 593 | 
            +
                    """Clear cache for a specific agent."""
         | 
| 594 | 
            +
                    try:
         | 
| 595 | 
            +
                        cache_key = f"agent_prompt:{name}:md"
         | 
| 596 | 
            +
                        self.cache.invalidate(cache_key)
         | 
| 597 | 
            +
                    except Exception as e:
         | 
| 598 | 
            +
                        logger.warning(f"Failed to clear cache for agent '{name}': {e}")
         |