claude-mpm 3.7.1__py3-none-any.whl → 3.7.8__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/INSTRUCTIONS.md +18 -0
- claude_mpm/agents/frontmatter_validator.py +116 -17
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/{dashboard → agents}/templates/.claude-mpm/memories/engineer_agent.md +1 -1
- claude_mpm/{dashboard/templates/.claude-mpm/memories/version_control_agent.md → agents/templates/.claude-mpm/memories/qa_agent.md} +2 -2
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +39 -0
- claude_mpm/agents/templates/code_analyzer.json +34 -12
- claude_mpm/agents/templates/data_engineer.json +5 -8
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +6 -6
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +12 -9
- claude_mpm/agents/templates/security.json +4 -7
- claude_mpm/agents/templates/ticketing.json +161 -0
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +214 -0
- claude_mpm/agents/templates/web_ui.json +176 -0
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/parser.py +11 -0
- claude_mpm/cli/ticket_cli.py +31 -0
- claude_mpm/core/framework_loader.py +102 -49
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +9 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +174 -13
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/ticket_manager.py +207 -44
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/robust_installer.py +587 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +37 -46
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/agents/templates/test_integration.json +0 -113
- 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/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.1.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
| @@ -328,8 +328,20 @@ class FileToolTracker { | |
| 328 328 | 
             
                 */
         | 
| 329 329 | 
             
                isFileOperation(event) {
         | 
| 330 330 | 
             
                    // File operations are tool events with tools that operate on files
         | 
| 331 | 
            -
                     | 
| 332 | 
            -
                     | 
| 331 | 
            +
                    // Check case-insensitively since tool names can come in different cases
         | 
| 332 | 
            +
                    const fileTools = ['read', 'write', 'edit', 'grep', 'multiedit', 'glob', 'ls', 'bash', 'notebookedit'];
         | 
| 333 | 
            +
                    const toolName = event.tool_name ? event.tool_name.toLowerCase() : '';
         | 
| 334 | 
            +
                    
         | 
| 335 | 
            +
                    // Also check if Bash commands involve file operations
         | 
| 336 | 
            +
                    if (toolName === 'bash' && event.tool_parameters) {
         | 
| 337 | 
            +
                        const command = event.tool_parameters.command || '';
         | 
| 338 | 
            +
                        // Check for common file operations in bash commands
         | 
| 339 | 
            +
                        if (command.match(/\b(cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find)\b/)) {
         | 
| 340 | 
            +
                            return true;
         | 
| 341 | 
            +
                        }
         | 
| 342 | 
            +
                    }
         | 
| 343 | 
            +
                    
         | 
| 344 | 
            +
                    return toolName && fileTools.includes(toolName);
         | 
| 333 345 | 
             
                }
         | 
| 334 346 |  | 
| 335 347 | 
             
                /**
         | 
| @@ -341,11 +353,28 @@ class FileToolTracker { | |
| 341 353 | 
             
                    // Try various locations where file path might be stored
         | 
| 342 354 | 
             
                    if (event.tool_parameters?.file_path) return event.tool_parameters.file_path;
         | 
| 343 355 | 
             
                    if (event.tool_parameters?.path) return event.tool_parameters.path;
         | 
| 356 | 
            +
                    if (event.tool_parameters?.notebook_path) return event.tool_parameters.notebook_path;
         | 
| 344 357 | 
             
                    if (event.data?.tool_parameters?.file_path) return event.data.tool_parameters.file_path;
         | 
| 345 358 | 
             
                    if (event.data?.tool_parameters?.path) return event.data.tool_parameters.path;
         | 
| 359 | 
            +
                    if (event.data?.tool_parameters?.notebook_path) return event.data.tool_parameters.notebook_path;
         | 
| 346 360 | 
             
                    if (event.file_path) return event.file_path;
         | 
| 347 361 | 
             
                    if (event.path) return event.path;
         | 
| 348 362 |  | 
| 363 | 
            +
                    // For Glob tool, use the pattern as a pseudo-path
         | 
| 364 | 
            +
                    if (event.tool_name?.toLowerCase() === 'glob' && event.tool_parameters?.pattern) {
         | 
| 365 | 
            +
                        return `[glob] ${event.tool_parameters.pattern}`;
         | 
| 366 | 
            +
                    }
         | 
| 367 | 
            +
                    
         | 
| 368 | 
            +
                    // For Bash commands, try to extract file paths from the command
         | 
| 369 | 
            +
                    if (event.tool_name?.toLowerCase() === 'bash' && event.tool_parameters?.command) {
         | 
| 370 | 
            +
                        const command = event.tool_parameters.command;
         | 
| 371 | 
            +
                        // Try to extract file paths from common patterns
         | 
| 372 | 
            +
                        const fileMatch = command.match(/(?:cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find|echo.*>|sed|awk|grep)\s+([^\s;|&]+)/);
         | 
| 373 | 
            +
                        if (fileMatch && fileMatch[1]) {
         | 
| 374 | 
            +
                            return fileMatch[1];
         | 
| 375 | 
            +
                        }
         | 
| 376 | 
            +
                    }
         | 
| 377 | 
            +
                    
         | 
| 349 378 | 
             
                    return null;
         | 
| 350 379 | 
             
                }
         | 
| 351 380 |  | 
| @@ -383,7 +412,22 @@ class FileToolTracker { | |
| 383 412 | 
             
                        case 'write': return 'write';
         | 
| 384 413 | 
             
                        case 'edit': return 'edit';
         | 
| 385 414 | 
             
                        case 'multiedit': return 'edit';
         | 
| 415 | 
            +
                        case 'notebookedit': return 'edit';
         | 
| 386 416 | 
             
                        case 'grep': return 'search';
         | 
| 417 | 
            +
                        case 'glob': return 'search';
         | 
| 418 | 
            +
                        case 'ls': return 'list';
         | 
| 419 | 
            +
                        case 'bash': 
         | 
| 420 | 
            +
                            // Check bash command for file operation type
         | 
| 421 | 
            +
                            const command = event.tool_parameters?.command || '';
         | 
| 422 | 
            +
                            if (command.match(/\b(cat|less|more|head|tail)\b/)) return 'read';
         | 
| 423 | 
            +
                            if (command.match(/\b(touch|echo.*>|tee)\b/)) return 'write';
         | 
| 424 | 
            +
                            if (command.match(/\b(sed|awk)\b/)) return 'edit';
         | 
| 425 | 
            +
                            if (command.match(/\b(grep|find)\b/)) return 'search';
         | 
| 426 | 
            +
                            if (command.match(/\b(ls|dir)\b/)) return 'list';
         | 
| 427 | 
            +
                            if (command.match(/\b(mv|cp)\b/)) return 'copy/move';
         | 
| 428 | 
            +
                            if (command.match(/\b(rm|rmdir)\b/)) return 'delete';
         | 
| 429 | 
            +
                            if (command.match(/\b(mkdir)\b/)) return 'create';
         | 
| 430 | 
            +
                            return 'bash';
         | 
| 387 431 | 
             
                        default: return toolName;
         | 
| 388 432 | 
             
                    }
         | 
| 389 433 | 
             
                }
         | 
| @@ -334,11 +334,11 @@ | |
| 334 334 | 
             
                <!-- New modular components -->
         | 
| 335 335 | 
             
                <script src="/static/js/components/socket-manager.js"></script>
         | 
| 336 336 | 
             
                <script src="/static/js/components/agent-inference.js"></script>
         | 
| 337 | 
            -
                <script src="/static/js/components/ui-state-manager.js"></script>
         | 
| 338 | 
            -
                <script src="/static/js/components/working-directory.js"></script>
         | 
| 339 | 
            -
                <script src="/static/js/components/file-tool-tracker.js"></script>
         | 
| 340 | 
            -
                <script src="/static/js/components/event-processor.js"></script>
         | 
| 341 | 
            -
                <script src="/static/js/components/export-manager.js"></script>
         | 
| 337 | 
            +
                <script src="/static/js/components/ui-state-manager.js?v=1.0"></script>
         | 
| 338 | 
            +
                <script src="/static/js/components/working-directory.js?v=1.0"></script>
         | 
| 339 | 
            +
                <script src="/static/js/components/file-tool-tracker.js?v=1.1"></script>
         | 
| 340 | 
            +
                <script src="/static/js/components/event-processor.js?v=1.0"></script>
         | 
| 341 | 
            +
                <script src="/static/js/components/export-manager.js?v=1.0"></script>
         | 
| 342 342 |  | 
| 343 343 | 
             
                <!-- Main dashboard coordinator -->
         | 
| 344 344 | 
             
                <script src="/static/js/dashboard.js"></script>
         | 
| @@ -740,10 +740,18 @@ class AgentDeploymentService: | |
| 740 740 | 
             
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 741 741 | 
             
                    tools_str = ','.join(tools) if isinstance(tools, list) else tools
         | 
| 742 742 |  | 
| 743 | 
            +
                    # Extract proper agent_id and name from template
         | 
| 744 | 
            +
                    agent_id = template_data.get('agent_id', agent_name)
         | 
| 745 | 
            +
                    display_name = template_data.get('metadata', {}).get('name', agent_id)
         | 
| 746 | 
            +
                    
         | 
| 747 | 
            +
                    # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
         | 
| 748 | 
            +
                    # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
         | 
| 749 | 
            +
                    claude_code_name = agent_id.replace('_', '-').lower()
         | 
| 750 | 
            +
                    
         | 
| 743 751 | 
             
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 744 752 | 
             
                    frontmatter_lines = [
         | 
| 745 753 | 
             
                        "---",
         | 
| 746 | 
            -
                        f"name: { | 
| 754 | 
            +
                        f"name: {claude_code_name}",
         | 
| 747 755 | 
             
                        f"description: {description}",
         | 
| 748 756 | 
             
                        f"version: {version_string}",
         | 
| 749 757 | 
             
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| @@ -387,20 +387,181 @@ class AsyncAgentDeploymentService: | |
| 387 387 | 
             
                    return filtered
         | 
| 388 388 |  | 
| 389 389 | 
             
                def _build_agent_markdown_sync(self, agent_data: Dict[str, Any]) -> str:
         | 
| 390 | 
            -
                    """Build agent markdown content  | 
| 391 | 
            -
                     | 
| 390 | 
            +
                    """Build agent markdown content matching the synchronous deployment format."""
         | 
| 391 | 
            +
                    from datetime import datetime
         | 
| 392 | 
            +
                    
         | 
| 393 | 
            +
                    # Extract agent info from the loaded JSON data
         | 
| 392 394 | 
             
                    agent_name = agent_data.get('_agent_name', 'unknown')
         | 
| 393 | 
            -
                     | 
| 394 | 
            -
                     | 
| 395 | 
            -
                    
         | 
| 396 | 
            -
                     | 
| 397 | 
            -
             | 
| 398 | 
            -
            version | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 401 | 
            -
             | 
| 402 | 
            -
             | 
| 403 | 
            -
             | 
| 395 | 
            +
                    
         | 
| 396 | 
            +
                    # Extract proper agent_id from template data (not filename)
         | 
| 397 | 
            +
                    agent_id = agent_data.get('agent_id', agent_name)
         | 
| 398 | 
            +
                    
         | 
| 399 | 
            +
                    # Handle both 'agent_version' (new format) and 'version' (old format)
         | 
| 400 | 
            +
                    agent_version = self._parse_version(agent_data.get('agent_version') or agent_data.get('version', '1.0.0'))
         | 
| 401 | 
            +
                    base_version = (0, 1, 0)  # Default base version for async deployment
         | 
| 402 | 
            +
                    
         | 
| 403 | 
            +
                    # Format version string as semantic version
         | 
| 404 | 
            +
                    version_string = self._format_version_display(agent_version)
         | 
| 405 | 
            +
                    
         | 
| 406 | 
            +
                    # Extract metadata using the same logic as synchronous deployment
         | 
| 407 | 
            +
                    # Check new format first (metadata.description), then old format
         | 
| 408 | 
            +
                    description = (
         | 
| 409 | 
            +
                        agent_data.get('metadata', {}).get('description') or
         | 
| 410 | 
            +
                        agent_data.get('configuration_fields', {}).get('description') or
         | 
| 411 | 
            +
                        agent_data.get('description') or
         | 
| 412 | 
            +
                        'Agent for specialized tasks'
         | 
| 413 | 
            +
                    )
         | 
| 414 | 
            +
                    
         | 
| 415 | 
            +
                    # Get tags from new format (metadata.tags) or old format
         | 
| 416 | 
            +
                    tags = (
         | 
| 417 | 
            +
                        agent_data.get('metadata', {}).get('tags') or
         | 
| 418 | 
            +
                        agent_data.get('configuration_fields', {}).get('tags') or
         | 
| 419 | 
            +
                        agent_data.get('tags') or
         | 
| 420 | 
            +
                        [agent_id, 'mpm-framework']
         | 
| 421 | 
            +
                    )
         | 
| 422 | 
            +
                    
         | 
| 423 | 
            +
                    # Get tools from capabilities.tools in new format
         | 
| 424 | 
            +
                    tools = (
         | 
| 425 | 
            +
                        agent_data.get('capabilities', {}).get('tools') or
         | 
| 426 | 
            +
                        agent_data.get('configuration_fields', {}).get('tools') or
         | 
| 427 | 
            +
                        ["Read", "Write", "Edit", "Grep", "Glob", "LS"]  # Default fallback
         | 
| 428 | 
            +
                    )
         | 
| 429 | 
            +
                    
         | 
| 430 | 
            +
                    # Get model from capabilities.model in new format
         | 
| 431 | 
            +
                    model = (
         | 
| 432 | 
            +
                        agent_data.get('capabilities', {}).get('model') or
         | 
| 433 | 
            +
                        agent_data.get('configuration_fields', {}).get('model') or
         | 
| 434 | 
            +
                        "sonnet"  # Default fallback
         | 
| 435 | 
            +
                    )
         | 
| 436 | 
            +
                    
         | 
| 437 | 
            +
                    # Simplify model name for Claude Code
         | 
| 438 | 
            +
                    model_map = {
         | 
| 439 | 
            +
                        'claude-4-sonnet-20250514': 'sonnet',
         | 
| 440 | 
            +
                        'claude-sonnet-4-20250514': 'sonnet',
         | 
| 441 | 
            +
                        'claude-opus-4-20250514': 'opus',
         | 
| 442 | 
            +
                        'claude-3-opus-20240229': 'opus',
         | 
| 443 | 
            +
                        'claude-3-haiku-20240307': 'haiku',
         | 
| 444 | 
            +
                        'claude-3.5-sonnet': 'sonnet',
         | 
| 445 | 
            +
                        'claude-3-sonnet': 'sonnet'
         | 
| 446 | 
            +
                    }
         | 
| 447 | 
            +
                    # Better fallback: extract the model type (opus/sonnet/haiku) from the string
         | 
| 448 | 
            +
                    if model not in model_map:
         | 
| 449 | 
            +
                        if 'opus' in model.lower():
         | 
| 450 | 
            +
                            model = 'opus'
         | 
| 451 | 
            +
                        elif 'sonnet' in model.lower():
         | 
| 452 | 
            +
                            model = 'sonnet'
         | 
| 453 | 
            +
                        elif 'haiku' in model.lower():
         | 
| 454 | 
            +
                            model = 'haiku'
         | 
| 455 | 
            +
                        else:
         | 
| 456 | 
            +
                            # Last resort: try to extract from hyphenated format
         | 
| 457 | 
            +
                            model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
         | 
| 458 | 
            +
                    else:
         | 
| 459 | 
            +
                        model = model_map[model]
         | 
| 460 | 
            +
                    
         | 
| 461 | 
            +
                    # Convert tools list to comma-separated string for Claude Code compatibility
         | 
| 462 | 
            +
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 463 | 
            +
                    tools_str = ','.join(tools) if isinstance(tools, list) else str(tools)
         | 
| 464 | 
            +
                    
         | 
| 465 | 
            +
                    # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
         | 
| 466 | 
            +
                    # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
         | 
| 467 | 
            +
                    claude_code_name = agent_id.replace('_', '-').lower()
         | 
| 468 | 
            +
                    
         | 
| 469 | 
            +
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 470 | 
            +
                    frontmatter_lines = [
         | 
| 471 | 
            +
                        "---",
         | 
| 472 | 
            +
                        f"name: {claude_code_name}",
         | 
| 473 | 
            +
                        f"description: {description}",
         | 
| 474 | 
            +
                        f"version: {version_string}",
         | 
| 475 | 
            +
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| 476 | 
            +
                        f"author: claude-mpm",  # Identify as system agent for deployment
         | 
| 477 | 
            +
                        f"tools: {tools_str}",
         | 
| 478 | 
            +
                        f"model: {model}"
         | 
| 479 | 
            +
                    ]
         | 
| 480 | 
            +
                    
         | 
| 481 | 
            +
                    # Add optional fields if present
         | 
| 482 | 
            +
                    # Check for color in metadata section (new format) or root (old format)
         | 
| 483 | 
            +
                    color = (
         | 
| 484 | 
            +
                        agent_data.get('metadata', {}).get('color') or
         | 
| 485 | 
            +
                        agent_data.get('color')
         | 
| 486 | 
            +
                    )
         | 
| 487 | 
            +
                    if color:
         | 
| 488 | 
            +
                        frontmatter_lines.append(f"color: {color}")
         | 
| 489 | 
            +
                    
         | 
| 490 | 
            +
                    frontmatter_lines.append("---")
         | 
| 491 | 
            +
                    frontmatter_lines.append("")
         | 
| 492 | 
            +
                    frontmatter_lines.append("")
         | 
| 493 | 
            +
                    
         | 
| 494 | 
            +
                    frontmatter = '\n'.join(frontmatter_lines)
         | 
| 495 | 
            +
                    
         | 
| 496 | 
            +
                    # Get the main content (instructions)
         | 
| 497 | 
            +
                    # Check multiple possible locations for instructions
         | 
| 498 | 
            +
                    content = (
         | 
| 499 | 
            +
                        agent_data.get('instructions') or
         | 
| 500 | 
            +
                        agent_data.get('narrative_fields', {}).get('instructions') or
         | 
| 501 | 
            +
                        agent_data.get('content') or
         | 
| 502 | 
            +
                        f"You are the {agent_id} agent. Perform tasks related to {agent_data.get('description', 'your specialization')}."
         | 
| 503 | 
            +
                    )
         | 
| 504 | 
            +
                    
         | 
| 505 | 
            +
                    return frontmatter + content
         | 
| 506 | 
            +
                
         | 
| 507 | 
            +
                def _parse_version(self, version_value: Any) -> tuple:
         | 
| 508 | 
            +
                    """
         | 
| 509 | 
            +
                    Parse version from various formats to semantic version tuple.
         | 
| 510 | 
            +
                    
         | 
| 511 | 
            +
                    Handles:
         | 
| 512 | 
            +
                    - Integer values: 5 -> (0, 5, 0)
         | 
| 513 | 
            +
                    - String integers: "5" -> (0, 5, 0)
         | 
| 514 | 
            +
                    - Semantic versions: "2.1.0" -> (2, 1, 0)
         | 
| 515 | 
            +
                    - Invalid formats: returns (0, 0, 0)
         | 
| 516 | 
            +
                    
         | 
| 517 | 
            +
                    Args:
         | 
| 518 | 
            +
                        version_value: Version in various formats
         | 
| 519 | 
            +
                        
         | 
| 520 | 
            +
                    Returns:
         | 
| 521 | 
            +
                        Tuple of (major, minor, patch) for comparison
         | 
| 522 | 
            +
                    """
         | 
| 523 | 
            +
                    if isinstance(version_value, int):
         | 
| 524 | 
            +
                        # Legacy integer version - treat as minor version
         | 
| 525 | 
            +
                        return (0, version_value, 0)
         | 
| 526 | 
            +
                        
         | 
| 527 | 
            +
                    if isinstance(version_value, str):
         | 
| 528 | 
            +
                        # Try to parse as simple integer
         | 
| 529 | 
            +
                        if version_value.isdigit():
         | 
| 530 | 
            +
                            return (0, int(version_value), 0)
         | 
| 531 | 
            +
                        
         | 
| 532 | 
            +
                        # Try to parse semantic version (e.g., "2.1.0" or "v2.1.0")
         | 
| 533 | 
            +
                        import re
         | 
| 534 | 
            +
                        sem_ver_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)', version_value)
         | 
| 535 | 
            +
                        if sem_ver_match:
         | 
| 536 | 
            +
                            major = int(sem_ver_match.group(1))
         | 
| 537 | 
            +
                            minor = int(sem_ver_match.group(2))
         | 
| 538 | 
            +
                            patch = int(sem_ver_match.group(3))
         | 
| 539 | 
            +
                            return (major, minor, patch)
         | 
| 540 | 
            +
                        
         | 
| 541 | 
            +
                        # Try to extract first number from string as minor version
         | 
| 542 | 
            +
                        num_match = re.search(r'(\d+)', version_value)
         | 
| 543 | 
            +
                        if num_match:
         | 
| 544 | 
            +
                            return (0, int(num_match.group(1)), 0)
         | 
| 545 | 
            +
                    
         | 
| 546 | 
            +
                    # Default to 0.0.0 for invalid formats
         | 
| 547 | 
            +
                    return (0, 0, 0)
         | 
| 548 | 
            +
                
         | 
| 549 | 
            +
                def _format_version_display(self, version_tuple: tuple) -> str:
         | 
| 550 | 
            +
                    """
         | 
| 551 | 
            +
                    Format version tuple for display.
         | 
| 552 | 
            +
                    
         | 
| 553 | 
            +
                    Args:
         | 
| 554 | 
            +
                        version_tuple: Tuple of (major, minor, patch)
         | 
| 555 | 
            +
                        
         | 
| 556 | 
            +
                    Returns:
         | 
| 557 | 
            +
                        Formatted version string
         | 
| 558 | 
            +
                    """
         | 
| 559 | 
            +
                    if isinstance(version_tuple, tuple) and len(version_tuple) == 3:
         | 
| 560 | 
            +
                        major, minor, patch = version_tuple
         | 
| 561 | 
            +
                        return f"{major}.{minor}.{patch}"
         | 
| 562 | 
            +
                    else:
         | 
| 563 | 
            +
                        # Fallback for legacy format
         | 
| 564 | 
            +
                        return str(version_tuple)
         | 
| 404 565 |  | 
| 405 566 | 
             
                async def cleanup(self):
         | 
| 406 567 | 
             
                    """Clean up resources."""
         | 
| @@ -116,9 +116,12 @@ class AgentCapabilitiesGenerator: | |
| 116 116 | 
             
                        if len(capability_text) > 100:
         | 
| 117 117 | 
             
                            capability_text = capability_text[:97] + '...'
         | 
| 118 118 |  | 
| 119 | 
            +
                        # Clean up the agent name for TodoWrite usage
         | 
| 120 | 
            +
                        clean_name = agent['name'].replace(' Agent', '').replace('-', ' ')
         | 
| 121 | 
            +
                        
         | 
| 119 122 | 
             
                        capabilities.append({
         | 
| 120 | 
            -
                            'name':  | 
| 121 | 
            -
                            'id': agent['id'],
         | 
| 123 | 
            +
                            'name': clean_name,  # Clean name for TodoWrite
         | 
| 124 | 
            +
                            'id': agent['id'],    # Agent ID for Task tool
         | 
| 122 125 | 
             
                            'capability_text': capability_text,
         | 
| 123 126 | 
             
                            'tools': ', '.join(agent.get('tools', [])[:5])  # First 5 tools
         | 
| 124 127 | 
             
                        })
         | 
| @@ -132,26 +135,33 @@ class AgentCapabilitiesGenerator: | |
| 132 135 | 
             
                        Configured Jinja2 template
         | 
| 133 136 | 
             
                    """
         | 
| 134 137 | 
             
                    template_content = """
         | 
| 135 | 
            -
            ## Agent  | 
| 136 | 
            -
             | 
| 138 | 
            +
            ## Available Agent Capabilities
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            You have the following specialized agents available for delegation:
         | 
| 137 141 |  | 
| 138 142 | 
             
            {% if agents_by_tier.project %}
         | 
| 139 143 | 
             
            ### Project-Specific Agents
         | 
| 140 144 | 
             
            {% for agent in agents_by_tier.project %}
         | 
| 141 | 
            -
            - **{{ agent.name }}** ({{ agent.id }}): {{ agent.description }}
         | 
| 145 | 
            +
            - **{{ agent.name|replace(' Agent', '')|replace('-', ' ') }}** (`{{ agent.id }}`): {{ agent.description }}
         | 
| 142 146 | 
             
            {% endfor %}
         | 
| 143 147 |  | 
| 144 148 | 
             
            {% endif %}
         | 
| 145 | 
            -
             | 
| 149 | 
            +
            ### Engineering Agents
         | 
| 146 150 | 
             
            {% for cap in detailed_capabilities %}
         | 
| 147 | 
            -
             | 
| 151 | 
            +
            {% if cap.id in ['engineer', 'data_engineer', 'documentation', 'ops', 'security', 'ticketing', 'version_control', 'web_ui'] %}
         | 
| 152 | 
            +
            - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
         | 
| 153 | 
            +
            {% endif %}
         | 
| 148 154 | 
             
            {% endfor %}
         | 
| 149 155 |  | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 156 | 
            +
            ### Research Agents
         | 
| 157 | 
            +
            {% for cap in detailed_capabilities %}
         | 
| 158 | 
            +
            {% if cap.id in ['code_analyzer', 'qa', 'research', 'web_qa'] %}
         | 
| 159 | 
            +
            - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
         | 
| 160 | 
            +
            {% endif %}
         | 
| 161 | 
            +
            {% endfor %}
         | 
| 153 162 |  | 
| 154 | 
            -
             | 
| 163 | 
            +
            **Total Available Agents**: {{ total_agents }}
         | 
| 164 | 
            +
            Use the agent ID in parentheses when delegating tasks via the Task tool.
         | 
| 155 165 | 
             
            """.strip()
         | 
| 156 166 |  | 
| 157 167 | 
             
                    return Template(template_content)
         |