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
| @@ -163,21 +163,77 @@ class FrameworkLoader: | |
| 163 163 | 
             
                    """
         | 
| 164 164 | 
             
                    Load INSTRUCTIONS.md or legacy CLAUDE.md from working directory.
         | 
| 165 165 |  | 
| 166 | 
            +
                    NOTE: We no longer load CLAUDE.md since Claude Code already picks it up automatically.
         | 
| 167 | 
            +
                    This prevents duplication of instructions.
         | 
| 168 | 
            +
                    
         | 
| 166 169 | 
             
                    Args:
         | 
| 167 170 | 
             
                        content: Dictionary to update with loaded instructions
         | 
| 168 171 | 
             
                    """
         | 
| 169 | 
            -
                     | 
| 170 | 
            -
                     | 
| 172 | 
            +
                    # Disabled - Claude Code already reads CLAUDE.md automatically
         | 
| 173 | 
            +
                    # We don't need to duplicate it in the PM instructions
         | 
| 174 | 
            +
                    pass
         | 
| 175 | 
            +
                
         | 
| 176 | 
            +
                def _load_workflow_instructions(self, content: Dict[str, Any]) -> None:
         | 
| 177 | 
            +
                    """
         | 
| 178 | 
            +
                    Load WORKFLOW.md with project-specific override support.
         | 
| 171 179 |  | 
| 172 | 
            -
                     | 
| 173 | 
            -
             | 
| 180 | 
            +
                    Precedence:
         | 
| 181 | 
            +
                    1. Project-specific: .claude-mpm/agents/WORKFLOW.md
         | 
| 182 | 
            +
                    2. System default: src/claude_mpm/agents/WORKFLOW.md
         | 
| 183 | 
            +
                    
         | 
| 184 | 
            +
                    Args:
         | 
| 185 | 
            +
                        content: Dictionary to update with workflow instructions
         | 
| 186 | 
            +
                    """
         | 
| 187 | 
            +
                    # Check for project-specific workflow first
         | 
| 188 | 
            +
                    project_workflow_path = Path.cwd() / ".claude-mpm" / "agents" / "WORKFLOW.md"
         | 
| 189 | 
            +
                    if project_workflow_path.exists():
         | 
| 190 | 
            +
                        loaded_content = self._try_load_file(project_workflow_path, "project-specific WORKFLOW.md")
         | 
| 174 191 | 
             
                        if loaded_content:
         | 
| 175 | 
            -
                            content[" | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 192 | 
            +
                            content["workflow_instructions"] = loaded_content
         | 
| 193 | 
            +
                            content["project_workflow"] = "project"
         | 
| 194 | 
            +
                            self.logger.info("Using project-specific WORKFLOW.md")
         | 
| 195 | 
            +
                            return
         | 
| 196 | 
            +
                    
         | 
| 197 | 
            +
                    # Fall back to system workflow
         | 
| 198 | 
            +
                    if self.framework_path:
         | 
| 199 | 
            +
                        system_workflow_path = self.framework_path / "src" / "claude_mpm" / "agents" / "WORKFLOW.md"
         | 
| 200 | 
            +
                        if system_workflow_path.exists():
         | 
| 201 | 
            +
                            loaded_content = self._try_load_file(system_workflow_path, "system WORKFLOW.md")
         | 
| 202 | 
            +
                            if loaded_content:
         | 
| 203 | 
            +
                                content["workflow_instructions"] = loaded_content
         | 
| 204 | 
            +
                                content["project_workflow"] = "system"
         | 
| 205 | 
            +
                                self.logger.info("Using system WORKFLOW.md")
         | 
| 206 | 
            +
                
         | 
| 207 | 
            +
                def _load_memory_instructions(self, content: Dict[str, Any]) -> None:
         | 
| 208 | 
            +
                    """
         | 
| 209 | 
            +
                    Load MEMORY.md with project-specific override support.
         | 
| 210 | 
            +
                    
         | 
| 211 | 
            +
                    Precedence:
         | 
| 212 | 
            +
                    1. Project-specific: .claude-mpm/agents/MEMORY.md
         | 
| 213 | 
            +
                    2. System default: src/claude_mpm/agents/MEMORY.md
         | 
| 214 | 
            +
                    
         | 
| 215 | 
            +
                    Args:
         | 
| 216 | 
            +
                        content: Dictionary to update with memory instructions
         | 
| 217 | 
            +
                    """
         | 
| 218 | 
            +
                    # Check for project-specific memory instructions first
         | 
| 219 | 
            +
                    project_memory_path = Path.cwd() / ".claude-mpm" / "agents" / "MEMORY.md"
         | 
| 220 | 
            +
                    if project_memory_path.exists():
         | 
| 221 | 
            +
                        loaded_content = self._try_load_file(project_memory_path, "project-specific MEMORY.md")
         | 
| 179 222 | 
             
                        if loaded_content:
         | 
| 180 | 
            -
                            content[" | 
| 223 | 
            +
                            content["memory_instructions"] = loaded_content
         | 
| 224 | 
            +
                            content["project_memory"] = "project"
         | 
| 225 | 
            +
                            self.logger.info("Using project-specific MEMORY.md")
         | 
| 226 | 
            +
                            return
         | 
| 227 | 
            +
                    
         | 
| 228 | 
            +
                    # Fall back to system memory instructions
         | 
| 229 | 
            +
                    if self.framework_path:
         | 
| 230 | 
            +
                        system_memory_path = self.framework_path / "src" / "claude_mpm" / "agents" / "MEMORY.md"
         | 
| 231 | 
            +
                        if system_memory_path.exists():
         | 
| 232 | 
            +
                            loaded_content = self._try_load_file(system_memory_path, "system MEMORY.md")
         | 
| 233 | 
            +
                            if loaded_content:
         | 
| 234 | 
            +
                                content["memory_instructions"] = loaded_content
         | 
| 235 | 
            +
                                content["project_memory"] = "system"
         | 
| 236 | 
            +
                                self.logger.info("Using system MEMORY.md")
         | 
| 181 237 |  | 
| 182 238 | 
             
                def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
         | 
| 183 239 | 
             
                    """
         | 
| @@ -250,7 +306,11 @@ class FrameworkLoader: | |
| 250 306 | 
             
                        "version": "unknown",
         | 
| 251 307 | 
             
                        "loaded": False,
         | 
| 252 308 | 
             
                        "working_claude_md": "",
         | 
| 253 | 
            -
                        "framework_instructions": ""
         | 
| 309 | 
            +
                        "framework_instructions": "",
         | 
| 310 | 
            +
                        "workflow_instructions": "",
         | 
| 311 | 
            +
                        "project_workflow": "",
         | 
| 312 | 
            +
                        "memory_instructions": "",
         | 
| 313 | 
            +
                        "project_memory": ""
         | 
| 254 314 | 
             
                    }
         | 
| 255 315 |  | 
| 256 316 | 
             
                    # Load instructions file from working directory
         | 
| @@ -281,6 +341,12 @@ class FrameworkLoader: | |
| 281 341 | 
             
                        if base_pm_content:
         | 
| 282 342 | 
             
                            content["base_pm_instructions"] = base_pm_content
         | 
| 283 343 |  | 
| 344 | 
            +
                    # Load WORKFLOW.md - check for project-specific first, then system
         | 
| 345 | 
            +
                    self._load_workflow_instructions(content)
         | 
| 346 | 
            +
                    
         | 
| 347 | 
            +
                    # Load MEMORY.md - check for project-specific first, then system
         | 
| 348 | 
            +
                    self._load_memory_instructions(content)
         | 
| 349 | 
            +
                    
         | 
| 284 350 | 
             
                    # Discover agent directories
         | 
| 285 351 | 
             
                    agents_dir, templates_dir, main_dir = self._discover_framework_paths()
         | 
| 286 352 |  | 
| @@ -296,24 +362,49 @@ class FrameworkLoader: | |
| 296 362 | 
             
                    Returns:
         | 
| 297 363 | 
             
                        Complete framework instructions ready for injection
         | 
| 298 364 | 
             
                    """
         | 
| 299 | 
            -
                    if self.framework_content["loaded"] | 
| 365 | 
            +
                    if self.framework_content["loaded"]:
         | 
| 300 366 | 
             
                        # Build framework from components
         | 
| 301 367 | 
             
                        return self._format_full_framework()
         | 
| 302 368 | 
             
                    else:
         | 
| 303 369 | 
             
                        # Use minimal fallback
         | 
| 304 370 | 
             
                        return self._format_minimal_framework()
         | 
| 305 371 |  | 
| 372 | 
            +
                def _strip_metadata_comments(self, content: str) -> str:
         | 
| 373 | 
            +
                    """Strip metadata HTML comments from content.
         | 
| 374 | 
            +
                    
         | 
| 375 | 
            +
                    Removes comments like:
         | 
| 376 | 
            +
                    <!-- FRAMEWORK_VERSION: 0010 -->
         | 
| 377 | 
            +
                    <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
         | 
| 378 | 
            +
                    """
         | 
| 379 | 
            +
                    import re
         | 
| 380 | 
            +
                    # Remove HTML comments that contain metadata
         | 
| 381 | 
            +
                    cleaned = re.sub(r'<!--\s*(FRAMEWORK_VERSION|LAST_MODIFIED|WORKFLOW_VERSION|PROJECT_WORKFLOW_VERSION|CUSTOM_PROJECT_WORKFLOW)[^>]*-->\n?', '', content)
         | 
| 382 | 
            +
                    # Also remove any leading blank lines that might result
         | 
| 383 | 
            +
                    cleaned = cleaned.lstrip('\n')
         | 
| 384 | 
            +
                    return cleaned
         | 
| 385 | 
            +
                
         | 
| 306 386 | 
             
                def _format_full_framework(self) -> str:
         | 
| 307 387 | 
             
                    """Format full framework instructions."""
         | 
| 308 388 | 
             
                    from datetime import datetime
         | 
| 309 389 |  | 
| 310 390 | 
             
                    # If we have the full framework INSTRUCTIONS.md, use it
         | 
| 311 391 | 
             
                    if self.framework_content.get("framework_instructions"):
         | 
| 312 | 
            -
                        instructions = self.framework_content["framework_instructions"]
         | 
| 392 | 
            +
                        instructions = self._strip_metadata_comments(self.framework_content["framework_instructions"])
         | 
| 393 | 
            +
                        
         | 
| 394 | 
            +
                        # Note: We don't add working directory CLAUDE.md here since Claude Code
         | 
| 395 | 
            +
                        # already picks it up automatically. This prevents duplication.
         | 
| 313 396 |  | 
| 314 | 
            -
                        # Add  | 
| 315 | 
            -
                        if self.framework_content | 
| 316 | 
            -
                             | 
| 397 | 
            +
                        # Add WORKFLOW.md after instructions
         | 
| 398 | 
            +
                        if self.framework_content.get("workflow_instructions"):
         | 
| 399 | 
            +
                            workflow_content = self._strip_metadata_comments(self.framework_content['workflow_instructions'])
         | 
| 400 | 
            +
                            instructions += f"\n\n{workflow_content}\n"
         | 
| 401 | 
            +
                            # Note: project-specific workflow is being used (logged elsewhere)
         | 
| 402 | 
            +
                        
         | 
| 403 | 
            +
                        # Add MEMORY.md after workflow instructions
         | 
| 404 | 
            +
                        if self.framework_content.get("memory_instructions"):
         | 
| 405 | 
            +
                            memory_content = self._strip_metadata_comments(self.framework_content['memory_instructions'])
         | 
| 406 | 
            +
                            instructions += f"\n\n{memory_content}\n"
         | 
| 407 | 
            +
                            # Note: project-specific memory instructions being used (logged elsewhere)
         | 
| 317 408 |  | 
| 318 409 | 
             
                        # Add dynamic agent capabilities section
         | 
| 319 410 | 
             
                        instructions += self._generate_agent_capabilities_section()
         | 
| @@ -324,17 +415,16 @@ class FrameworkLoader: | |
| 324 415 |  | 
| 325 416 | 
             
                        # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
         | 
| 326 417 | 
             
                        if self.framework_content.get("base_pm_instructions"):
         | 
| 327 | 
            -
                             | 
| 418 | 
            +
                            base_pm = self._strip_metadata_comments(self.framework_content['base_pm_instructions'])
         | 
| 419 | 
            +
                            instructions += f"\n\n{base_pm}"
         | 
| 420 | 
            +
                        
         | 
| 421 | 
            +
                        # Clean up any trailing whitespace
         | 
| 422 | 
            +
                        instructions = instructions.rstrip() + "\n"
         | 
| 328 423 |  | 
| 329 424 | 
             
                        return instructions
         | 
| 330 425 |  | 
| 331 426 | 
             
                    # Otherwise fall back to generating framework
         | 
| 332 | 
            -
                    instructions =  | 
| 333 | 
            -
            <!-- Framework injected by Claude MPM -->
         | 
| 334 | 
            -
            <!-- Version: {self.framework_content['version']} -->
         | 
| 335 | 
            -
            <!-- Timestamp: {datetime.now().isoformat()} -->
         | 
| 336 | 
            -
             | 
| 337 | 
            -
            # Claude MPM Framework Instructions
         | 
| 427 | 
            +
                    instructions = """# Claude MPM Framework Instructions
         | 
| 338 428 |  | 
| 339 429 | 
             
            You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
         | 
| 340 430 |  | 
| @@ -348,13 +438,8 @@ You are a multi-agent orchestrator. Your primary responsibilities are: | |
| 348 438 |  | 
| 349 439 | 
             
            """
         | 
| 350 440 |  | 
| 351 | 
            -
                    #  | 
| 352 | 
            -
                     | 
| 353 | 
            -
                        instructions += f"""
         | 
| 354 | 
            -
            ## Working Directory Instructions
         | 
| 355 | 
            -
            {self.framework_content["working_claude_md"]}
         | 
| 356 | 
            -
             | 
| 357 | 
            -
            """
         | 
| 441 | 
            +
                    # Note: We don't add working directory CLAUDE.md here since Claude Code
         | 
| 442 | 
            +
                    # already picks it up automatically. This prevents duplication.
         | 
| 358 443 |  | 
| 359 444 | 
             
                    # Add agent definitions
         | 
| 360 445 | 
             
                    if self.framework_content["agents"]:
         | 
| @@ -440,7 +525,6 @@ Extract tickets from these patterns: | |
| 440 525 | 
             
                        import yaml
         | 
| 441 526 |  | 
| 442 527 | 
             
                        # Read directly from deployed agents in .claude/agents/
         | 
| 443 | 
            -
                        # This ensures we show the exact agent IDs that work with the Task tool
         | 
| 444 528 | 
             
                        agents_dir = Path.cwd() / ".claude" / "agents"
         | 
| 445 529 |  | 
| 446 530 | 
             
                        if not agents_dir.exists():
         | 
| @@ -449,77 +533,57 @@ Extract tickets from these patterns: | |
| 449 533 |  | 
| 450 534 | 
             
                        # Build capabilities section
         | 
| 451 535 | 
             
                        section = "\n\n## Available Agent Capabilities\n\n"
         | 
| 452 | 
            -
                        section += "You have the following specialized agents available for delegation:\n\n"
         | 
| 453 536 |  | 
| 454 537 | 
             
                        # Collect deployed agents
         | 
| 455 538 | 
             
                        deployed_agents = []
         | 
| 456 539 | 
             
                        for agent_file in agents_dir.glob("*.md"):
         | 
| 457 | 
            -
                            # Skip hidden files and system files
         | 
| 458 540 | 
             
                            if agent_file.name.startswith('.'):
         | 
| 459 541 | 
             
                                continue
         | 
| 460 542 |  | 
| 461 | 
            -
                            #  | 
| 462 | 
            -
                             | 
| 463 | 
            -
                             | 
| 464 | 
            -
             | 
| 465 | 
            -
                            # Try to read agent metadata from frontmatter
         | 
| 466 | 
            -
                            agent_name = agent_id.replace('_', ' ').title()
         | 
| 467 | 
            -
                            agent_desc = "Specialized agent"
         | 
| 468 | 
            -
                            
         | 
| 469 | 
            -
                            try:
         | 
| 470 | 
            -
                                with open(agent_file, 'r') as f:
         | 
| 471 | 
            -
                                    content = f.read()
         | 
| 472 | 
            -
                                    # Extract YAML frontmatter if present
         | 
| 473 | 
            -
                                    if content.startswith('---'):
         | 
| 474 | 
            -
                                        end_marker = content.find('---', 3)
         | 
| 475 | 
            -
                                        if end_marker > 0:
         | 
| 476 | 
            -
                                            frontmatter = content[3:end_marker]
         | 
| 477 | 
            -
                                            metadata = yaml.safe_load(frontmatter)
         | 
| 478 | 
            -
                                            if metadata:
         | 
| 479 | 
            -
                                                agent_name = metadata.get('name', agent_name)
         | 
| 480 | 
            -
                                                agent_desc = metadata.get('description', agent_desc)
         | 
| 481 | 
            -
                            except Exception as e:
         | 
| 482 | 
            -
                                self.logger.debug(f"Could not read metadata from {agent_file}: {e}")
         | 
| 483 | 
            -
                            
         | 
| 484 | 
            -
                            deployed_agents.append((agent_id, agent_name, agent_desc))
         | 
| 543 | 
            +
                            # Parse agent metadata
         | 
| 544 | 
            +
                            agent_data = self._parse_agent_metadata(agent_file)
         | 
| 545 | 
            +
                            if agent_data:
         | 
| 546 | 
            +
                                deployed_agents.append(agent_data)
         | 
| 485 547 |  | 
| 486 548 | 
             
                        if not deployed_agents:
         | 
| 487 549 | 
             
                            return self._get_fallback_capabilities()
         | 
| 488 550 |  | 
| 489 | 
            -
                        # Sort agents  | 
| 490 | 
            -
                        deployed_agents.sort(key=lambda x: x[ | 
| 551 | 
            +
                        # Sort agents alphabetically by ID
         | 
| 552 | 
            +
                        deployed_agents.sort(key=lambda x: x['id'])
         | 
| 491 553 |  | 
| 492 | 
            -
                        #  | 
| 493 | 
            -
                         | 
| 494 | 
            -
             | 
| 554 | 
            +
                        # Display all agents with their rich descriptions
         | 
| 555 | 
            +
                        for agent in deployed_agents:
         | 
| 556 | 
            +
                            # Clean up display name - handle common acronyms
         | 
| 557 | 
            +
                            display_name = agent['display_name']
         | 
| 558 | 
            +
                            display_name = display_name.replace('Qa ', 'QA ').replace('Ui ', 'UI ').replace('Api ', 'API ')
         | 
| 559 | 
            +
                            if display_name.lower() == 'qa agent':
         | 
| 560 | 
            +
                                display_name = 'QA Agent'
         | 
| 561 | 
            +
                            
         | 
| 562 | 
            +
                            section += f"\n### {display_name} (`{agent['id']}`)\n"
         | 
| 563 | 
            +
                            section += f"{agent['description']}\n"
         | 
| 564 | 
            +
                            
         | 
| 565 | 
            +
                            # Add any additional metadata if present
         | 
| 566 | 
            +
                            if agent.get('authority'):
         | 
| 567 | 
            +
                                section += f"- **Authority**: {agent['authority']}\n"
         | 
| 568 | 
            +
                            if agent.get('primary_function'):
         | 
| 569 | 
            +
                                section += f"- **Primary Function**: {agent['primary_function']}\n"
         | 
| 570 | 
            +
                            if agent.get('handoff_to'):
         | 
| 571 | 
            +
                                section += f"- **Handoff To**: {agent['handoff_to']}\n"
         | 
| 572 | 
            +
                            if agent.get('tools') and agent['tools'] != 'standard':
         | 
| 573 | 
            +
                                section += f"- **Tools**: {agent['tools']}\n"
         | 
| 574 | 
            +
                            if agent.get('model') and agent['model'] != 'opus':
         | 
| 575 | 
            +
                                section += f"- **Model**: {agent['model']}\n"
         | 
| 495 576 |  | 
| 496 | 
            -
                         | 
| 497 | 
            -
             | 
| 577 | 
            +
                        # Add simple Context-Aware Agent Selection
         | 
| 578 | 
            +
                        section += "\n## Context-Aware Agent Selection\n\n"
         | 
| 579 | 
            +
                        section += "Select agents based on their descriptions above. Key principles:\n"
         | 
| 580 | 
            +
                        section += "- **PM questions** → Answer directly (only exception)\n"
         | 
| 581 | 
            +
                        section += "- Match task requirements to agent descriptions and authority\n"
         | 
| 582 | 
            +
                        section += "- Consider agent handoff recommendations\n"
         | 
| 583 | 
            +
                        section += "- Use the agent ID in parentheses when delegating via Task tool\n"
         | 
| 498 584 |  | 
| 499 | 
            -
                         | 
| 500 | 
            -
                            if agent_id in core_types:
         | 
| 501 | 
            -
                                core_agents.append((agent_id, name, desc))
         | 
| 502 | 
            -
                            else:
         | 
| 503 | 
            -
                                other_agents.append((agent_id, name, desc))
         | 
| 504 | 
            -
                        
         | 
| 505 | 
            -
                        # Display core agents first
         | 
| 506 | 
            -
                        if core_agents:
         | 
| 507 | 
            -
                            section += "### Engineering Agents\n"
         | 
| 508 | 
            -
                            for agent_id, name, desc in core_agents:
         | 
| 509 | 
            -
                                # Format: Name (agent_id) - use Name for TodoWrite, agent_id for Task tool
         | 
| 510 | 
            -
                                clean_name = name.replace(' Agent', '').replace('-', ' ')
         | 
| 511 | 
            -
                                section += f"- **{clean_name}** (`{agent_id}`): {desc}\n"
         | 
| 512 | 
            -
                        
         | 
| 513 | 
            -
                        # Display other/custom agents
         | 
| 514 | 
            -
                        if other_agents:
         | 
| 515 | 
            -
                            section += "\n### Research Agents\n"
         | 
| 516 | 
            -
                            for agent_id, name, desc in other_agents:
         | 
| 517 | 
            -
                                clean_name = name.replace(' Agent', '').replace('-', ' ')
         | 
| 518 | 
            -
                                section += f"- **{clean_name}** (`{agent_id}`): {desc}\n"
         | 
| 519 | 
            -
                        
         | 
| 520 | 
            -
                        # Add summary and usage instructions
         | 
| 585 | 
            +
                        # Add summary
         | 
| 521 586 | 
             
                        section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
         | 
| 522 | 
            -
                        section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
         | 
| 523 587 |  | 
| 524 588 | 
             
                        return section
         | 
| 525 589 |  | 
| @@ -527,6 +591,98 @@ Extract tickets from these patterns: | |
| 527 591 | 
             
                        self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
         | 
| 528 592 | 
             
                        return self._get_fallback_capabilities()
         | 
| 529 593 |  | 
| 594 | 
            +
                def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
         | 
| 595 | 
            +
                    """Parse agent metadata from deployed agent file.
         | 
| 596 | 
            +
                    
         | 
| 597 | 
            +
                    Returns:
         | 
| 598 | 
            +
                        Dictionary with agent metadata directly from YAML frontmatter.
         | 
| 599 | 
            +
                    """
         | 
| 600 | 
            +
                    try:
         | 
| 601 | 
            +
                        import yaml
         | 
| 602 | 
            +
                        
         | 
| 603 | 
            +
                        with open(agent_file, 'r') as f:
         | 
| 604 | 
            +
                            content = f.read()
         | 
| 605 | 
            +
                            
         | 
| 606 | 
            +
                        # Default values
         | 
| 607 | 
            +
                        agent_data = {
         | 
| 608 | 
            +
                            'id': agent_file.stem,
         | 
| 609 | 
            +
                            'display_name': agent_file.stem.replace('_', ' ').replace('-', ' ').title(),
         | 
| 610 | 
            +
                            'description': 'Specialized agent'
         | 
| 611 | 
            +
                        }
         | 
| 612 | 
            +
                        
         | 
| 613 | 
            +
                        # Extract YAML frontmatter if present
         | 
| 614 | 
            +
                        if content.startswith('---'):
         | 
| 615 | 
            +
                            end_marker = content.find('---', 3)
         | 
| 616 | 
            +
                            if end_marker > 0:
         | 
| 617 | 
            +
                                frontmatter = content[3:end_marker]
         | 
| 618 | 
            +
                                metadata = yaml.safe_load(frontmatter)
         | 
| 619 | 
            +
                                if metadata:
         | 
| 620 | 
            +
                                    # Use name as ID for Task tool
         | 
| 621 | 
            +
                                    agent_data['id'] = metadata.get('name', agent_data['id'])
         | 
| 622 | 
            +
                                    agent_data['display_name'] = metadata.get('name', agent_data['display_name']).replace('-', ' ').title()
         | 
| 623 | 
            +
                                    
         | 
| 624 | 
            +
                                    # Copy all metadata fields directly
         | 
| 625 | 
            +
                                    for key, value in metadata.items():
         | 
| 626 | 
            +
                                        if key not in ['name']:  # Skip already processed fields
         | 
| 627 | 
            +
                                            agent_data[key] = value
         | 
| 628 | 
            +
                                    
         | 
| 629 | 
            +
                                    # IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
         | 
| 630 | 
            +
                                    # Tools must remain as comma-separated without spaces: "Read,Write,Edit"
         | 
| 631 | 
            +
                        
         | 
| 632 | 
            +
                        return agent_data
         | 
| 633 | 
            +
                        
         | 
| 634 | 
            +
                    except Exception as e:
         | 
| 635 | 
            +
                        self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
         | 
| 636 | 
            +
                        return None
         | 
| 637 | 
            +
                
         | 
| 638 | 
            +
                def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
         | 
| 639 | 
            +
                    """Generate Context-Aware Agent Selection guide from deployed agents.
         | 
| 640 | 
            +
                    
         | 
| 641 | 
            +
                    Creates a mapping of task types to appropriate agents based on their
         | 
| 642 | 
            +
                    descriptions and capabilities.
         | 
| 643 | 
            +
                    """
         | 
| 644 | 
            +
                    guide = ""
         | 
| 645 | 
            +
                    
         | 
| 646 | 
            +
                    # Build selection mapping based on deployed agents
         | 
| 647 | 
            +
                    selection_map = {}
         | 
| 648 | 
            +
                    
         | 
| 649 | 
            +
                    for agent in deployed_agents:
         | 
| 650 | 
            +
                        agent_id = agent['id']
         | 
| 651 | 
            +
                        desc_lower = agent['description'].lower()
         | 
| 652 | 
            +
                        
         | 
| 653 | 
            +
                        # Map task types to agents based on their descriptions
         | 
| 654 | 
            +
                        if 'implementation' in desc_lower or ('engineer' in agent_id and 'data' not in agent_id):
         | 
| 655 | 
            +
                            selection_map['Implementation tasks'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 656 | 
            +
                        if 'codebase analysis' in desc_lower or 'research' in agent_id:
         | 
| 657 | 
            +
                            selection_map['Codebase analysis'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 658 | 
            +
                        if 'testing' in desc_lower or 'qa' in agent_id:
         | 
| 659 | 
            +
                            selection_map['Testing/quality'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 660 | 
            +
                        if 'documentation' in desc_lower:
         | 
| 661 | 
            +
                            selection_map['Documentation'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 662 | 
            +
                        if 'security' in desc_lower or 'sast' in desc_lower:
         | 
| 663 | 
            +
                            selection_map['Security operations'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 664 | 
            +
                        if 'deployment' in desc_lower or 'infrastructure' in desc_lower or 'ops' in agent_id:
         | 
| 665 | 
            +
                            selection_map['Deployment/infrastructure'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 666 | 
            +
                        if 'data' in desc_lower and ('pipeline' in desc_lower or 'etl' in desc_lower):
         | 
| 667 | 
            +
                            selection_map['Data pipeline/ETL'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 668 | 
            +
                        if 'git' in desc_lower or 'version control' in desc_lower:
         | 
| 669 | 
            +
                            selection_map['Version control'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 670 | 
            +
                        if 'ticket' in desc_lower or 'epic' in desc_lower:
         | 
| 671 | 
            +
                            selection_map['Ticket/issue management'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 672 | 
            +
                        if 'browser' in desc_lower or 'e2e' in desc_lower:
         | 
| 673 | 
            +
                            selection_map['Browser/E2E testing'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 674 | 
            +
                        if 'frontend' in desc_lower or 'ui' in desc_lower or 'html' in desc_lower:
         | 
| 675 | 
            +
                            selection_map['Frontend/UI development'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 676 | 
            +
                    
         | 
| 677 | 
            +
                    # Always include PM questions
         | 
| 678 | 
            +
                    selection_map['PM questions'] = "Answer directly (only exception)"
         | 
| 679 | 
            +
                    
         | 
| 680 | 
            +
                    # Format the selection guide
         | 
| 681 | 
            +
                    for task_type, agent_info in selection_map.items():
         | 
| 682 | 
            +
                        guide += f"- **{task_type}** → {agent_info}\n"
         | 
| 683 | 
            +
                    
         | 
| 684 | 
            +
                    return guide
         | 
| 685 | 
            +
                
         | 
| 530 686 | 
             
                def _get_fallback_capabilities(self) -> str:
         | 
| 531 687 | 
             
                    """Return fallback capabilities when dynamic discovery fails."""
         | 
| 532 688 | 
             
                    return """
         | 
| @@ -536,13 +692,13 @@ Extract tickets from these patterns: | |
| 536 692 | 
             
            You have the following specialized agents available for delegation:
         | 
| 537 693 |  | 
| 538 694 | 
             
            - **Engineer** (`engineer`): Code implementation and development
         | 
| 539 | 
            -
            - **Research** (`research`): Investigation and analysis  
         | 
| 540 | 
            -
            - **QA** (`qa`): Testing and quality assurance
         | 
| 541 | 
            -
            - **Documentation** (`documentation`): Documentation creation and maintenance
         | 
| 542 | 
            -
            - **Security** (`security`): Security analysis and protection
         | 
| 543 | 
            -
            - **Data Engineer** (` | 
| 544 | 
            -
            - **Ops** (`ops`): Deployment and operations
         | 
| 545 | 
            -
            - **Version Control** (` | 
| 695 | 
            +
            - **Research** (`research-agent`): Investigation and analysis  
         | 
| 696 | 
            +
            - **QA** (`qa-agent`): Testing and quality assurance
         | 
| 697 | 
            +
            - **Documentation** (`documentation-agent`): Documentation creation and maintenance
         | 
| 698 | 
            +
            - **Security** (`security-agent`): Security analysis and protection
         | 
| 699 | 
            +
            - **Data Engineer** (`data-engineer`): Data management and pipelines
         | 
| 700 | 
            +
            - **Ops** (`ops-agent`): Deployment and operations
         | 
| 701 | 
            +
            - **Version Control** (`version-control`): Git operations and version management
         | 
| 546 702 |  | 
| 547 703 | 
             
            **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
         | 
| 548 704 | 
             
            """
         |