claude-mpm 3.7.4__py3-none-any.whl → 3.8.1__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 -78
- claude_mpm/agents/MEMORY.md +88 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +26 -11
- claude_mpm/agents/templates/data_engineer.json +4 -7
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +2 -3
- claude_mpm/agents/templates/security.json +3 -6
- claude_mpm/agents/templates/ticketing.json +4 -9
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +4 -4
- claude_mpm/agents/templates/web_ui.json +4 -4
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +228 -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 +5 -1
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +461 -22
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +208 -93
- claude_mpm/core/interactive_session.py +432 -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/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- 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 +592 -269
- claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
- 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/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/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 +377 -51
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/utils/robust_installer.py +587 -0
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/cli/README.md +0 -108
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/config/async_logging_config.yaml +0 -145
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
- claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/README.md +0 -121
- claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- claude_mpm/hooks/README.md +0 -96
- claude_mpm/schemas/agent_schema.json +0 -435
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/version_control/VERSION +0 -1
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
| @@ -163,21 +163,46 @@ 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 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
                     | 
| 177 | 
            -
                         | 
| 178 | 
            -
             | 
| 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")
         | 
| 179 191 | 
             
                        if loaded_content:
         | 
| 180 | 
            -
                            content[" | 
| 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")
         | 
| 181 206 |  | 
| 182 207 | 
             
                def _load_single_agent(self, agent_file: Path) -> tuple[Optional[str], Optional[str]]:
         | 
| 183 208 | 
             
                    """
         | 
| @@ -250,7 +275,9 @@ class FrameworkLoader: | |
| 250 275 | 
             
                        "version": "unknown",
         | 
| 251 276 | 
             
                        "loaded": False,
         | 
| 252 277 | 
             
                        "working_claude_md": "",
         | 
| 253 | 
            -
                        "framework_instructions": ""
         | 
| 278 | 
            +
                        "framework_instructions": "",
         | 
| 279 | 
            +
                        "workflow_instructions": "",
         | 
| 280 | 
            +
                        "project_workflow": ""
         | 
| 254 281 | 
             
                    }
         | 
| 255 282 |  | 
| 256 283 | 
             
                    # Load instructions file from working directory
         | 
| @@ -281,6 +308,9 @@ class FrameworkLoader: | |
| 281 308 | 
             
                        if base_pm_content:
         | 
| 282 309 | 
             
                            content["base_pm_instructions"] = base_pm_content
         | 
| 283 310 |  | 
| 311 | 
            +
                    # Load WORKFLOW.md - check for project-specific first, then system
         | 
| 312 | 
            +
                    self._load_workflow_instructions(content)
         | 
| 313 | 
            +
                    
         | 
| 284 314 | 
             
                    # Discover agent directories
         | 
| 285 315 | 
             
                    agents_dir, templates_dir, main_dir = self._discover_framework_paths()
         | 
| 286 316 |  | 
| @@ -296,24 +326,43 @@ class FrameworkLoader: | |
| 296 326 | 
             
                    Returns:
         | 
| 297 327 | 
             
                        Complete framework instructions ready for injection
         | 
| 298 328 | 
             
                    """
         | 
| 299 | 
            -
                    if self.framework_content["loaded"] | 
| 329 | 
            +
                    if self.framework_content["loaded"]:
         | 
| 300 330 | 
             
                        # Build framework from components
         | 
| 301 331 | 
             
                        return self._format_full_framework()
         | 
| 302 332 | 
             
                    else:
         | 
| 303 333 | 
             
                        # Use minimal fallback
         | 
| 304 334 | 
             
                        return self._format_minimal_framework()
         | 
| 305 335 |  | 
| 336 | 
            +
                def _strip_metadata_comments(self, content: str) -> str:
         | 
| 337 | 
            +
                    """Strip metadata HTML comments from content.
         | 
| 338 | 
            +
                    
         | 
| 339 | 
            +
                    Removes comments like:
         | 
| 340 | 
            +
                    <!-- FRAMEWORK_VERSION: 0010 -->
         | 
| 341 | 
            +
                    <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
         | 
| 342 | 
            +
                    """
         | 
| 343 | 
            +
                    import re
         | 
| 344 | 
            +
                    # Remove HTML comments that contain metadata
         | 
| 345 | 
            +
                    cleaned = re.sub(r'<!--\s*(FRAMEWORK_VERSION|LAST_MODIFIED|WORKFLOW_VERSION|PROJECT_WORKFLOW_VERSION|CUSTOM_PROJECT_WORKFLOW)[^>]*-->\n?', '', content)
         | 
| 346 | 
            +
                    # Also remove any leading blank lines that might result
         | 
| 347 | 
            +
                    cleaned = cleaned.lstrip('\n')
         | 
| 348 | 
            +
                    return cleaned
         | 
| 349 | 
            +
                
         | 
| 306 350 | 
             
                def _format_full_framework(self) -> str:
         | 
| 307 351 | 
             
                    """Format full framework instructions."""
         | 
| 308 352 | 
             
                    from datetime import datetime
         | 
| 309 353 |  | 
| 310 354 | 
             
                    # If we have the full framework INSTRUCTIONS.md, use it
         | 
| 311 355 | 
             
                    if self.framework_content.get("framework_instructions"):
         | 
| 312 | 
            -
                        instructions = self.framework_content["framework_instructions"]
         | 
| 356 | 
            +
                        instructions = self._strip_metadata_comments(self.framework_content["framework_instructions"])
         | 
| 313 357 |  | 
| 314 | 
            -
                        #  | 
| 315 | 
            -
                         | 
| 316 | 
            -
             | 
| 358 | 
            +
                        # Note: We don't add working directory CLAUDE.md here since Claude Code
         | 
| 359 | 
            +
                        # already picks it up automatically. This prevents duplication.
         | 
| 360 | 
            +
                        
         | 
| 361 | 
            +
                        # Add WORKFLOW.md after instructions
         | 
| 362 | 
            +
                        if self.framework_content.get("workflow_instructions"):
         | 
| 363 | 
            +
                            workflow_content = self._strip_metadata_comments(self.framework_content['workflow_instructions'])
         | 
| 364 | 
            +
                            instructions += f"\n\n{workflow_content}\n"
         | 
| 365 | 
            +
                            # Note: project-specific workflow is being used (logged elsewhere)
         | 
| 317 366 |  | 
| 318 367 | 
             
                        # Add dynamic agent capabilities section
         | 
| 319 368 | 
             
                        instructions += self._generate_agent_capabilities_section()
         | 
| @@ -324,17 +373,16 @@ class FrameworkLoader: | |
| 324 373 |  | 
| 325 374 | 
             
                        # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
         | 
| 326 375 | 
             
                        if self.framework_content.get("base_pm_instructions"):
         | 
| 327 | 
            -
                             | 
| 376 | 
            +
                            base_pm = self._strip_metadata_comments(self.framework_content['base_pm_instructions'])
         | 
| 377 | 
            +
                            instructions += f"\n\n{base_pm}"
         | 
| 378 | 
            +
                        
         | 
| 379 | 
            +
                        # Clean up any trailing whitespace
         | 
| 380 | 
            +
                        instructions = instructions.rstrip() + "\n"
         | 
| 328 381 |  | 
| 329 382 | 
             
                        return instructions
         | 
| 330 383 |  | 
| 331 384 | 
             
                    # 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
         | 
| 385 | 
            +
                    instructions = """# Claude MPM Framework Instructions
         | 
| 338 386 |  | 
| 339 387 | 
             
            You are operating within the Claude Multi-Agent Project Manager (MPM) framework.
         | 
| 340 388 |  | 
| @@ -348,13 +396,8 @@ You are a multi-agent orchestrator. Your primary responsibilities are: | |
| 348 396 |  | 
| 349 397 | 
             
            """
         | 
| 350 398 |  | 
| 351 | 
            -
                    #  | 
| 352 | 
            -
                     | 
| 353 | 
            -
                        instructions += f"""
         | 
| 354 | 
            -
            ## Working Directory Instructions
         | 
| 355 | 
            -
            {self.framework_content["working_claude_md"]}
         | 
| 356 | 
            -
             | 
| 357 | 
            -
            """
         | 
| 399 | 
            +
                    # Note: We don't add working directory CLAUDE.md here since Claude Code
         | 
| 400 | 
            +
                    # already picks it up automatically. This prevents duplication.
         | 
| 358 401 |  | 
| 359 402 | 
             
                    # Add agent definitions
         | 
| 360 403 | 
             
                    if self.framework_content["agents"]:
         | 
| @@ -440,7 +483,6 @@ Extract tickets from these patterns: | |
| 440 483 | 
             
                        import yaml
         | 
| 441 484 |  | 
| 442 485 | 
             
                        # Read directly from deployed agents in .claude/agents/
         | 
| 443 | 
            -
                        # This ensures we show the exact agent IDs that work with the Task tool
         | 
| 444 486 | 
             
                        agents_dir = Path.cwd() / ".claude" / "agents"
         | 
| 445 487 |  | 
| 446 488 | 
             
                        if not agents_dir.exists():
         | 
| @@ -449,76 +491,57 @@ Extract tickets from these patterns: | |
| 449 491 |  | 
| 450 492 | 
             
                        # Build capabilities section
         | 
| 451 493 | 
             
                        section = "\n\n## Available Agent Capabilities\n\n"
         | 
| 452 | 
            -
                        section += "You have the following specialized agents available for delegation:\n\n"
         | 
| 453 494 |  | 
| 454 495 | 
             
                        # Collect deployed agents
         | 
| 455 496 | 
             
                        deployed_agents = []
         | 
| 456 497 | 
             
                        for agent_file in agents_dir.glob("*.md"):
         | 
| 457 | 
            -
                            # Skip hidden files and system files
         | 
| 458 498 | 
             
                            if agent_file.name.startswith('.'):
         | 
| 459 499 | 
             
                                continue
         | 
| 460 500 |  | 
| 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))
         | 
| 501 | 
            +
                            # Parse agent metadata
         | 
| 502 | 
            +
                            agent_data = self._parse_agent_metadata(agent_file)
         | 
| 503 | 
            +
                            if agent_data:
         | 
| 504 | 
            +
                                deployed_agents.append(agent_data)
         | 
| 485 505 |  | 
| 486 506 | 
             
                        if not deployed_agents:
         | 
| 487 507 | 
             
                            return self._get_fallback_capabilities()
         | 
| 488 508 |  | 
| 489 | 
            -
                        # Sort agents  | 
| 490 | 
            -
                        deployed_agents.sort(key=lambda x: x[ | 
| 491 | 
            -
                        
         | 
| 492 | 
            -
                        # Group common agent types
         | 
| 493 | 
            -
                        core_agents = []
         | 
| 494 | 
            -
                        other_agents = []
         | 
| 495 | 
            -
                        
         | 
| 496 | 
            -
                        core_types = ['engineer', 'research', 'qa', 'documentation', 'security', 
         | 
| 497 | 
            -
                                     'data_engineer', 'ops', 'version_control']
         | 
| 498 | 
            -
                        
         | 
| 499 | 
            -
                        for agent_id, name, desc in deployed_agents:
         | 
| 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))
         | 
| 509 | 
            +
                        # Sort agents alphabetically by ID
         | 
| 510 | 
            +
                        deployed_agents.sort(key=lambda x: x['id'])
         | 
| 504 511 |  | 
| 505 | 
            -
                        # Display  | 
| 506 | 
            -
                         | 
| 507 | 
            -
                             | 
| 508 | 
            -
                             | 
| 509 | 
            -
             | 
| 512 | 
            +
                        # Display all agents with their rich descriptions
         | 
| 513 | 
            +
                        for agent in deployed_agents:
         | 
| 514 | 
            +
                            # Clean up display name - handle common acronyms
         | 
| 515 | 
            +
                            display_name = agent['display_name']
         | 
| 516 | 
            +
                            display_name = display_name.replace('Qa ', 'QA ').replace('Ui ', 'UI ').replace('Api ', 'API ')
         | 
| 517 | 
            +
                            if display_name.lower() == 'qa agent':
         | 
| 518 | 
            +
                                display_name = 'QA Agent'
         | 
| 519 | 
            +
                            
         | 
| 520 | 
            +
                            section += f"\n### {display_name} (`{agent['id']}`)\n"
         | 
| 521 | 
            +
                            section += f"{agent['description']}\n"
         | 
| 522 | 
            +
                            
         | 
| 523 | 
            +
                            # Add any additional metadata if present
         | 
| 524 | 
            +
                            if agent.get('authority'):
         | 
| 525 | 
            +
                                section += f"- **Authority**: {agent['authority']}\n"
         | 
| 526 | 
            +
                            if agent.get('primary_function'):
         | 
| 527 | 
            +
                                section += f"- **Primary Function**: {agent['primary_function']}\n"
         | 
| 528 | 
            +
                            if agent.get('handoff_to'):
         | 
| 529 | 
            +
                                section += f"- **Handoff To**: {agent['handoff_to']}\n"
         | 
| 530 | 
            +
                            if agent.get('tools') and agent['tools'] != 'standard':
         | 
| 531 | 
            +
                                section += f"- **Tools**: {agent['tools']}\n"
         | 
| 532 | 
            +
                            if agent.get('model') and agent['model'] != 'opus':
         | 
| 533 | 
            +
                                section += f"- **Model**: {agent['model']}\n"
         | 
| 510 534 |  | 
| 511 | 
            -
                        #  | 
| 512 | 
            -
                         | 
| 513 | 
            -
             | 
| 514 | 
            -
             | 
| 515 | 
            -
             | 
| 535 | 
            +
                        # Add simple Context-Aware Agent Selection
         | 
| 536 | 
            +
                        section += "\n## Context-Aware Agent Selection\n\n"
         | 
| 537 | 
            +
                        section += "Select agents based on their descriptions above. Key principles:\n"
         | 
| 538 | 
            +
                        section += "- **PM questions** → Answer directly (only exception)\n"
         | 
| 539 | 
            +
                        section += "- Match task requirements to agent descriptions and authority\n"
         | 
| 540 | 
            +
                        section += "- Consider agent handoff recommendations\n"
         | 
| 541 | 
            +
                        section += "- Use the agent ID in parentheses when delegating via Task tool\n"
         | 
| 516 542 |  | 
| 517 | 
            -
                        # Add summary | 
| 543 | 
            +
                        # Add summary
         | 
| 518 544 | 
             
                        section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
         | 
| 519 | 
            -
                        section += "\n**IMPORTANT**: Use the exact agent ID shown in parentheses when delegating tasks.\n"
         | 
| 520 | 
            -
                        section += "For example: `**research**: Analyze the codebase architecture`\n"
         | 
| 521 | 
            -
                        section += "NOT: `**research_agent**: ...` or `**Research Agent**: ...`\n"
         | 
| 522 545 |  | 
| 523 546 | 
             
                        return section
         | 
| 524 547 |  | 
| @@ -526,6 +549,98 @@ Extract tickets from these patterns: | |
| 526 549 | 
             
                        self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
         | 
| 527 550 | 
             
                        return self._get_fallback_capabilities()
         | 
| 528 551 |  | 
| 552 | 
            +
                def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
         | 
| 553 | 
            +
                    """Parse agent metadata from deployed agent file.
         | 
| 554 | 
            +
                    
         | 
| 555 | 
            +
                    Returns:
         | 
| 556 | 
            +
                        Dictionary with agent metadata directly from YAML frontmatter.
         | 
| 557 | 
            +
                    """
         | 
| 558 | 
            +
                    try:
         | 
| 559 | 
            +
                        import yaml
         | 
| 560 | 
            +
                        
         | 
| 561 | 
            +
                        with open(agent_file, 'r') as f:
         | 
| 562 | 
            +
                            content = f.read()
         | 
| 563 | 
            +
                            
         | 
| 564 | 
            +
                        # Default values
         | 
| 565 | 
            +
                        agent_data = {
         | 
| 566 | 
            +
                            'id': agent_file.stem,
         | 
| 567 | 
            +
                            'display_name': agent_file.stem.replace('_', ' ').replace('-', ' ').title(),
         | 
| 568 | 
            +
                            'description': 'Specialized agent'
         | 
| 569 | 
            +
                        }
         | 
| 570 | 
            +
                        
         | 
| 571 | 
            +
                        # Extract YAML frontmatter if present
         | 
| 572 | 
            +
                        if content.startswith('---'):
         | 
| 573 | 
            +
                            end_marker = content.find('---', 3)
         | 
| 574 | 
            +
                            if end_marker > 0:
         | 
| 575 | 
            +
                                frontmatter = content[3:end_marker]
         | 
| 576 | 
            +
                                metadata = yaml.safe_load(frontmatter)
         | 
| 577 | 
            +
                                if metadata:
         | 
| 578 | 
            +
                                    # Use name as ID for Task tool
         | 
| 579 | 
            +
                                    agent_data['id'] = metadata.get('name', agent_data['id'])
         | 
| 580 | 
            +
                                    agent_data['display_name'] = metadata.get('name', agent_data['display_name']).replace('-', ' ').title()
         | 
| 581 | 
            +
                                    
         | 
| 582 | 
            +
                                    # Copy all metadata fields directly
         | 
| 583 | 
            +
                                    for key, value in metadata.items():
         | 
| 584 | 
            +
                                        if key not in ['name']:  # Skip already processed fields
         | 
| 585 | 
            +
                                            agent_data[key] = value
         | 
| 586 | 
            +
                                    
         | 
| 587 | 
            +
                                    # IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
         | 
| 588 | 
            +
                                    # Tools must remain as comma-separated without spaces: "Read,Write,Edit"
         | 
| 589 | 
            +
                        
         | 
| 590 | 
            +
                        return agent_data
         | 
| 591 | 
            +
                        
         | 
| 592 | 
            +
                    except Exception as e:
         | 
| 593 | 
            +
                        self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
         | 
| 594 | 
            +
                        return None
         | 
| 595 | 
            +
                
         | 
| 596 | 
            +
                def _generate_agent_selection_guide(self, deployed_agents: list) -> str:
         | 
| 597 | 
            +
                    """Generate Context-Aware Agent Selection guide from deployed agents.
         | 
| 598 | 
            +
                    
         | 
| 599 | 
            +
                    Creates a mapping of task types to appropriate agents based on their
         | 
| 600 | 
            +
                    descriptions and capabilities.
         | 
| 601 | 
            +
                    """
         | 
| 602 | 
            +
                    guide = ""
         | 
| 603 | 
            +
                    
         | 
| 604 | 
            +
                    # Build selection mapping based on deployed agents
         | 
| 605 | 
            +
                    selection_map = {}
         | 
| 606 | 
            +
                    
         | 
| 607 | 
            +
                    for agent in deployed_agents:
         | 
| 608 | 
            +
                        agent_id = agent['id']
         | 
| 609 | 
            +
                        desc_lower = agent['description'].lower()
         | 
| 610 | 
            +
                        
         | 
| 611 | 
            +
                        # Map task types to agents based on their descriptions
         | 
| 612 | 
            +
                        if 'implementation' in desc_lower or ('engineer' in agent_id and 'data' not in agent_id):
         | 
| 613 | 
            +
                            selection_map['Implementation tasks'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 614 | 
            +
                        if 'codebase analysis' in desc_lower or 'research' in agent_id:
         | 
| 615 | 
            +
                            selection_map['Codebase analysis'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 616 | 
            +
                        if 'testing' in desc_lower or 'qa' in agent_id:
         | 
| 617 | 
            +
                            selection_map['Testing/quality'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 618 | 
            +
                        if 'documentation' in desc_lower:
         | 
| 619 | 
            +
                            selection_map['Documentation'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 620 | 
            +
                        if 'security' in desc_lower or 'sast' in desc_lower:
         | 
| 621 | 
            +
                            selection_map['Security operations'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 622 | 
            +
                        if 'deployment' in desc_lower or 'infrastructure' in desc_lower or 'ops' in agent_id:
         | 
| 623 | 
            +
                            selection_map['Deployment/infrastructure'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 624 | 
            +
                        if 'data' in desc_lower and ('pipeline' in desc_lower or 'etl' in desc_lower):
         | 
| 625 | 
            +
                            selection_map['Data pipeline/ETL'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 626 | 
            +
                        if 'git' in desc_lower or 'version control' in desc_lower:
         | 
| 627 | 
            +
                            selection_map['Version control'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 628 | 
            +
                        if 'ticket' in desc_lower or 'epic' in desc_lower:
         | 
| 629 | 
            +
                            selection_map['Ticket/issue management'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 630 | 
            +
                        if 'browser' in desc_lower or 'e2e' in desc_lower:
         | 
| 631 | 
            +
                            selection_map['Browser/E2E testing'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 632 | 
            +
                        if 'frontend' in desc_lower or 'ui' in desc_lower or 'html' in desc_lower:
         | 
| 633 | 
            +
                            selection_map['Frontend/UI development'] = f"{agent['display_name']} (`{agent_id}`)"
         | 
| 634 | 
            +
                    
         | 
| 635 | 
            +
                    # Always include PM questions
         | 
| 636 | 
            +
                    selection_map['PM questions'] = "Answer directly (only exception)"
         | 
| 637 | 
            +
                    
         | 
| 638 | 
            +
                    # Format the selection guide
         | 
| 639 | 
            +
                    for task_type, agent_info in selection_map.items():
         | 
| 640 | 
            +
                        guide += f"- **{task_type}** → {agent_info}\n"
         | 
| 641 | 
            +
                    
         | 
| 642 | 
            +
                    return guide
         | 
| 643 | 
            +
                
         | 
| 529 644 | 
             
                def _get_fallback_capabilities(self) -> str:
         | 
| 530 645 | 
             
                    """Return fallback capabilities when dynamic discovery fails."""
         | 
| 531 646 | 
             
                    return """
         | 
| @@ -535,13 +650,13 @@ Extract tickets from these patterns: | |
| 535 650 | 
             
            You have the following specialized agents available for delegation:
         | 
| 536 651 |  | 
| 537 652 | 
             
            - **Engineer** (`engineer`): Code implementation and development
         | 
| 538 | 
            -
            - **Research** (`research`): Investigation and analysis  
         | 
| 539 | 
            -
            - **QA** (`qa`): Testing and quality assurance
         | 
| 540 | 
            -
            - **Documentation** (`documentation`): Documentation creation and maintenance
         | 
| 541 | 
            -
            - **Security** (`security`): Security analysis and protection
         | 
| 542 | 
            -
            - **Data Engineer** (` | 
| 543 | 
            -
            - **Ops** (`ops`): Deployment and operations
         | 
| 544 | 
            -
            - **Version Control** (` | 
| 653 | 
            +
            - **Research** (`research-agent`): Investigation and analysis  
         | 
| 654 | 
            +
            - **QA** (`qa-agent`): Testing and quality assurance
         | 
| 655 | 
            +
            - **Documentation** (`documentation-agent`): Documentation creation and maintenance
         | 
| 656 | 
            +
            - **Security** (`security-agent`): Security analysis and protection
         | 
| 657 | 
            +
            - **Data Engineer** (`data-engineer`): Data management and pipelines
         | 
| 658 | 
            +
            - **Ops** (`ops-agent`): Deployment and operations
         | 
| 659 | 
            +
            - **Version Control** (`version-control`): Git operations and version management
         | 
| 545 660 |  | 
| 546 661 | 
             
            **IMPORTANT**: Use the exact agent ID in parentheses when delegating tasks.
         | 
| 547 662 | 
             
            """
         |