claude-mpm 3.7.4__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/schema/agent_schema.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +26 -11
- claude_mpm/agents/templates/data_engineer.json +3 -6
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +1 -2
- claude_mpm/agents/templates/security.json +2 -5
- claude_mpm/agents/templates/ticketing.json +3 -3
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +3 -3
- claude_mpm/agents/templates/web_ui.json +3 -3
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/parser.py +11 -0
- claude_mpm/core/framework_loader.py +8 -7
- 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 +5 -1
- 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/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.4.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +32 -47
- 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.7.8.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.4.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>
         | 
| @@ -744,10 +744,14 @@ class AgentDeploymentService: | |
| 744 744 | 
             
                    agent_id = template_data.get('agent_id', agent_name)
         | 
| 745 745 | 
             
                    display_name = template_data.get('metadata', {}).get('name', agent_id)
         | 
| 746 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 | 
            +
                    
         | 
| 747 751 | 
             
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 748 752 | 
             
                    frontmatter_lines = [
         | 
| 749 753 | 
             
                        "---",
         | 
| 750 | 
            -
                        f"name: { | 
| 754 | 
            +
                        f"name: {claude_code_name}",
         | 
| 751 755 | 
             
                        f"description: {description}",
         | 
| 752 756 | 
             
                        f"version: {version_string}",
         | 
| 753 757 | 
             
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| @@ -462,10 +462,14 @@ class AsyncAgentDeploymentService: | |
| 462 462 | 
             
                    # IMPORTANT: No spaces after commas - Claude Code requires exact format
         | 
| 463 463 | 
             
                    tools_str = ','.join(tools) if isinstance(tools, list) else str(tools)
         | 
| 464 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 | 
            +
                    
         | 
| 465 469 | 
             
                    # Build frontmatter with only the fields Claude Code uses
         | 
| 466 470 | 
             
                    frontmatter_lines = [
         | 
| 467 471 | 
             
                        "---",
         | 
| 468 | 
            -
                        f"name: { | 
| 472 | 
            +
                        f"name: {claude_code_name}",
         | 
| 469 473 | 
             
                        f"description: {description}",
         | 
| 470 474 | 
             
                        f"version: {version_string}",
         | 
| 471 475 | 
             
                        f"base_version: {self._format_version_display(base_version)}",
         | 
| @@ -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)
         | 
| @@ -44,21 +44,28 @@ class TicketManager: | |
| 44 44 | 
             
                            (tickets_dir / "tasks").mkdir(exist_ok=True)
         | 
| 45 45 | 
             
                            self.logger.info(f"Created tickets directory structure at: {tickets_dir}")
         | 
| 46 46 |  | 
| 47 | 
            -
                        #  | 
| 47 | 
            +
                        # Use standardized config format (.trackdown.yaml)
         | 
| 48 48 | 
             
                        config_file = self.project_path / ".trackdown.yaml"
         | 
| 49 | 
            +
                        
         | 
| 50 | 
            +
                        # Check if config exists, create if needed
         | 
| 49 51 | 
             
                        if not config_file.exists():
         | 
| 50 | 
            -
                             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 52 | 
            +
                            try:
         | 
| 53 | 
            +
                                config = Config.create_default(str(config_file))
         | 
| 54 | 
            +
                                config.set("paths.tickets_dir", "tickets")
         | 
| 55 | 
            +
                                config.set("paths.epics_dir", "tickets/epics")
         | 
| 56 | 
            +
                                config.set("paths.issues_dir", "tickets/issues")
         | 
| 57 | 
            +
                                config.set("paths.tasks_dir", "tickets/tasks")
         | 
| 58 | 
            +
                                config.save()
         | 
| 59 | 
            +
                                self.logger.info("Created .trackdown.yaml configuration")
         | 
| 60 | 
            +
                            except Exception as config_error:
         | 
| 61 | 
            +
                                self.logger.warning(f"Could not create config file: {config_error}")
         | 
| 62 | 
            +
                                self.logger.info("Proceeding without config file - using defaults")
         | 
| 63 | 
            +
                        else:
         | 
| 64 | 
            +
                            self.logger.info(f"Using configuration from: {config_file}")
         | 
| 58 65 |  | 
| 59 66 | 
             
                        # Initialize TaskManager directly with the project path
         | 
| 60 67 | 
             
                        # TaskManager will handle project initialization internally
         | 
| 61 | 
            -
                        task_manager = TaskManager(self.project_path)
         | 
| 68 | 
            +
                        task_manager = TaskManager(str(self.project_path))
         | 
| 62 69 |  | 
| 63 70 | 
             
                        # Verify it's using the right directory
         | 
| 64 71 | 
             
                        if hasattr(task_manager, 'tasks_dir'):
         | 
| @@ -68,13 +75,41 @@ class TicketManager: | |
| 68 75 |  | 
| 69 76 | 
             
                        return task_manager
         | 
| 70 77 |  | 
| 71 | 
            -
                    except ImportError:
         | 
| 72 | 
            -
                         | 
| 73 | 
            -
                         | 
| 78 | 
            +
                    except ImportError as e:
         | 
| 79 | 
            +
                        import_msg = str(e)
         | 
| 80 | 
            +
                        if "ai_trackdown_pytools" in import_msg.lower():
         | 
| 81 | 
            +
                            self.logger.error("ai-trackdown-pytools is not installed")
         | 
| 82 | 
            +
                            self.logger.info("Install with: pip install ai-trackdown-pytools")
         | 
| 83 | 
            +
                        else:
         | 
| 84 | 
            +
                            self.logger.error(f"Missing dependency: {import_msg}")
         | 
| 85 | 
            +
                            self.logger.info("Ensure all required packages are installed")
         | 
| 86 | 
            +
                        self.logger.debug(f"Import error details: {import_msg}")
         | 
| 87 | 
            +
                        return None
         | 
| 88 | 
            +
                    except AttributeError as e:
         | 
| 89 | 
            +
                        attr_msg = str(e)
         | 
| 90 | 
            +
                        if "TaskManager" in attr_msg:
         | 
| 91 | 
            +
                            self.logger.error("TaskManager class not found in ai-trackdown-pytools")
         | 
| 92 | 
            +
                            self.logger.info("This may indicate an incompatible version")
         | 
| 93 | 
            +
                        else:
         | 
| 94 | 
            +
                            self.logger.error(f"ai-trackdown-pytools API mismatch: {attr_msg}")
         | 
| 95 | 
            +
                        self.logger.info("Try updating: pip install --upgrade ai-trackdown-pytools")
         | 
| 96 | 
            +
                        return None
         | 
| 97 | 
            +
                    except FileNotFoundError as e:
         | 
| 98 | 
            +
                        self.logger.error(f"Required file or directory not found: {e}")
         | 
| 99 | 
            +
                        self.logger.info("Ensure the project directory exists and is accessible")
         | 
| 100 | 
            +
                        return None
         | 
| 101 | 
            +
                    except PermissionError as e:
         | 
| 102 | 
            +
                        self.logger.error(f"Permission denied accessing ticket files: {e}")
         | 
| 103 | 
            +
                        self.logger.info("Check file permissions in the tickets/ directory")
         | 
| 104 | 
            +
                        self.logger.info("You may need to run with appropriate permissions")
         | 
| 74 105 | 
             
                        return None
         | 
| 75 106 | 
             
                    except Exception as e:
         | 
| 76 | 
            -
                        self.logger.error(f" | 
| 77 | 
            -
                        self.logger. | 
| 107 | 
            +
                        self.logger.error(f"Unexpected error initializing TaskManager: {e.__class__.__name__}: {e}")
         | 
| 108 | 
            +
                        self.logger.info("This could be due to:")
         | 
| 109 | 
            +
                        self.logger.info("  - Corrupted configuration files")
         | 
| 110 | 
            +
                        self.logger.info("  - Incompatible ai-trackdown-pytools version")
         | 
| 111 | 
            +
                        self.logger.info("  - Missing dependencies")
         | 
| 112 | 
            +
                        self.logger.debug(f"Full error details:", exc_info=True)
         | 
| 78 113 | 
             
                        return None
         | 
| 79 114 |  | 
| 80 115 | 
             
                def create_ticket(
         | 
| @@ -96,19 +131,41 @@ class TicketManager: | |
| 96 131 | 
             
                        title: Ticket title
         | 
| 97 132 | 
             
                        ticket_type: Type (task, bug, feature, etc.)
         | 
| 98 133 | 
             
                        description: Detailed description
         | 
| 99 | 
            -
                        priority: Priority level (low, medium, high)
         | 
| 134 | 
            +
                        priority: Priority level (low, medium, high, critical)
         | 
| 100 135 | 
             
                        tags: List of tags/labels
         | 
| 101 136 | 
             
                        source: Source identifier
         | 
| 137 | 
            +
                        parent_epic: Parent epic ID (optional)
         | 
| 138 | 
            +
                        parent_issue: Parent issue ID (optional)
         | 
| 102 139 | 
             
                        **kwargs: Additional metadata
         | 
| 103 140 |  | 
| 104 141 | 
             
                    Returns:
         | 
| 105 142 | 
             
                        Ticket ID if created, None on failure
         | 
| 106 143 | 
             
                    """
         | 
| 107 144 | 
             
                    if not self.task_manager:
         | 
| 108 | 
            -
                        self.logger.error("TaskManager not available")
         | 
| 145 | 
            +
                        self.logger.error("TaskManager not available - cannot create ticket")
         | 
| 146 | 
            +
                        self.logger.info("Please ensure ai-trackdown-pytools is installed and properly configured")
         | 
| 147 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 109 148 | 
             
                        return None
         | 
| 110 149 |  | 
| 111 150 | 
             
                    try:
         | 
| 151 | 
            +
                        # Validate input
         | 
| 152 | 
            +
                        if not title or not title.strip():
         | 
| 153 | 
            +
                            self.logger.error("Cannot create ticket with empty title")
         | 
| 154 | 
            +
                            return None
         | 
| 155 | 
            +
                        
         | 
| 156 | 
            +
                        # Validate priority
         | 
| 157 | 
            +
                        valid_priorities = ['low', 'medium', 'high', 'critical']
         | 
| 158 | 
            +
                        if priority.lower() not in valid_priorities:
         | 
| 159 | 
            +
                            self.logger.warning(f"Invalid priority '{priority}', using 'medium'")
         | 
| 160 | 
            +
                            self.logger.info(f"Valid priorities are: {', '.join(valid_priorities)}")
         | 
| 161 | 
            +
                            priority = 'medium'
         | 
| 162 | 
            +
                        
         | 
| 163 | 
            +
                        # Validate ticket type
         | 
| 164 | 
            +
                        valid_types = ['task', 'bug', 'feature', 'issue', 'enhancement', 'documentation']
         | 
| 165 | 
            +
                        if ticket_type.lower() not in valid_types:
         | 
| 166 | 
            +
                            self.logger.warning(f"Non-standard ticket type '{ticket_type}'")
         | 
| 167 | 
            +
                            self.logger.info(f"Common types are: {', '.join(valid_types)}")
         | 
| 168 | 
            +
                        
         | 
| 112 169 | 
             
                        # Prepare tags
         | 
| 113 170 | 
             
                        if tags is None:
         | 
| 114 171 | 
             
                            tags = []
         | 
| @@ -121,7 +178,7 @@ class TicketManager: | |
| 121 178 |  | 
| 122 179 | 
             
                        # Prepare task data
         | 
| 123 180 | 
             
                        task_data = {
         | 
| 124 | 
            -
                            'title': title,
         | 
| 181 | 
            +
                            'title': title.strip(),
         | 
| 125 182 | 
             
                            'description': description or f"Auto-extracted {ticket_type} from Claude MPM session",
         | 
| 126 183 | 
             
                            'status': 'open',
         | 
| 127 184 | 
             
                            'priority': priority.lower(),
         | 
| @@ -136,14 +193,49 @@ class TicketManager: | |
| 136 193 | 
             
                            }
         | 
| 137 194 | 
             
                        }
         | 
| 138 195 |  | 
| 196 | 
            +
                        # Add parent references if provided
         | 
| 197 | 
            +
                        if parent_epic:
         | 
| 198 | 
            +
                            task_data['metadata']['parent_epic'] = parent_epic
         | 
| 199 | 
            +
                            self.logger.debug(f"Linking to parent epic: {parent_epic}")
         | 
| 200 | 
            +
                        if parent_issue:
         | 
| 201 | 
            +
                            task_data['metadata']['parent_issue'] = parent_issue
         | 
| 202 | 
            +
                            self.logger.debug(f"Linking to parent issue: {parent_issue}")
         | 
| 203 | 
            +
                        
         | 
| 139 204 | 
             
                        # Create the task
         | 
| 140 205 | 
             
                        task = self.task_manager.create_task(**task_data)
         | 
| 141 206 |  | 
| 142 | 
            -
                         | 
| 143 | 
            -
             | 
| 207 | 
            +
                        if task and hasattr(task, 'id'):
         | 
| 208 | 
            +
                            self.logger.info(f"Successfully created ticket: {task.id} - {title}")
         | 
| 209 | 
            +
                            return task.id
         | 
| 210 | 
            +
                        else:
         | 
| 211 | 
            +
                            self.logger.error(f"Task creation failed - no ID returned")
         | 
| 212 | 
            +
                            self.logger.info("The task may have been created but without proper ID assignment")
         | 
| 213 | 
            +
                            return None
         | 
| 144 214 |  | 
| 215 | 
            +
                    except AttributeError as e:
         | 
| 216 | 
            +
                        attr_msg = str(e)
         | 
| 217 | 
            +
                        if "create_task" in attr_msg:
         | 
| 218 | 
            +
                            self.logger.error("create_task method not found in TaskManager")
         | 
| 219 | 
            +
                            self.logger.info("The ai-trackdown-pytools API may have changed")
         | 
| 220 | 
            +
                            self.logger.info("Check for updates or API documentation")
         | 
| 221 | 
            +
                        else:
         | 
| 222 | 
            +
                            self.logger.error(f"API mismatch when creating ticket: {attr_msg}")
         | 
| 223 | 
            +
                        return None
         | 
| 224 | 
            +
                    except ValueError as e:
         | 
| 225 | 
            +
                        self.logger.error(f"Invalid data provided for ticket: {e}")
         | 
| 226 | 
            +
                        self.logger.info("Check that all required fields are provided correctly")
         | 
| 227 | 
            +
                        return None
         | 
| 228 | 
            +
                    except PermissionError as e:
         | 
| 229 | 
            +
                        self.logger.error(f"Permission denied when creating ticket: {e}")
         | 
| 230 | 
            +
                        self.logger.info("Check write permissions for the tickets/ directory")
         | 
| 231 | 
            +
                        return None
         | 
| 145 232 | 
             
                    except Exception as e:
         | 
| 146 | 
            -
                        self.logger.error(f" | 
| 233 | 
            +
                        self.logger.error(f"Unexpected error creating ticket: {e.__class__.__name__}: {e}")
         | 
| 234 | 
            +
                        self.logger.info("This could be due to:")
         | 
| 235 | 
            +
                        self.logger.info("  - Disk full or quota exceeded")
         | 
| 236 | 
            +
                        self.logger.info("  - Invalid characters in title or description")
         | 
| 237 | 
            +
                        self.logger.info("  - Network issues (if using remote storage)")
         | 
| 238 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 147 239 | 
             
                        return None
         | 
| 148 240 |  | 
| 149 241 | 
             
                def list_recent_tickets(self, limit: int = 10) -> List[Dict[str, Any]]:
         | 
| @@ -157,26 +249,66 @@ class TicketManager: | |
| 157 249 | 
             
                        List of ticket summaries
         | 
| 158 250 | 
             
                    """
         | 
| 159 251 | 
             
                    if not self.task_manager:
         | 
| 252 | 
            +
                        self.logger.warning("TaskManager not available - cannot list tickets")
         | 
| 253 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 160 254 | 
             
                        return []
         | 
| 161 255 |  | 
| 162 256 | 
             
                    try:
         | 
| 257 | 
            +
                        # Validate limit
         | 
| 258 | 
            +
                        if limit < 1:
         | 
| 259 | 
            +
                            self.logger.warning(f"Invalid limit {limit}, using 10")
         | 
| 260 | 
            +
                            limit = 10
         | 
| 261 | 
            +
                        elif limit > 100:
         | 
| 262 | 
            +
                            self.logger.warning(f"Limit {limit} too high, capping at 100")
         | 
| 263 | 
            +
                            limit = 100
         | 
| 264 | 
            +
                        
         | 
| 163 265 | 
             
                        tasks = self.task_manager.get_recent_tasks(limit=limit)
         | 
| 164 266 |  | 
| 267 | 
            +
                        if not tasks:
         | 
| 268 | 
            +
                            self.logger.info("No tickets found in the system")
         | 
| 269 | 
            +
                            return []
         | 
| 270 | 
            +
                        
         | 
| 165 271 | 
             
                        tickets = []
         | 
| 166 272 | 
             
                        for task in tasks:
         | 
| 167 | 
            -
                             | 
| 168 | 
            -
                                 | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 273 | 
            +
                            try:
         | 
| 274 | 
            +
                                ticket_data = {
         | 
| 275 | 
            +
                                    'id': getattr(task, 'id', 'UNKNOWN'),
         | 
| 276 | 
            +
                                    'title': getattr(task, 'title', 'Untitled'),
         | 
| 277 | 
            +
                                    'status': getattr(task, 'status', 'unknown'),
         | 
| 278 | 
            +
                                    'priority': getattr(task, 'priority', 'medium'),
         | 
| 279 | 
            +
                                    'tags': getattr(task, 'tags', []),
         | 
| 280 | 
            +
                                    'created_at': getattr(task, 'created_at', 'N/A'),
         | 
| 281 | 
            +
                                }
         | 
| 282 | 
            +
                                tickets.append(ticket_data)
         | 
| 283 | 
            +
                            except Exception as task_error:
         | 
| 284 | 
            +
                                self.logger.warning(f"Error processing task: {task_error}")
         | 
| 285 | 
            +
                                self.logger.debug(f"Task object type: {type(task)}")
         | 
| 286 | 
            +
                                continue
         | 
| 175 287 |  | 
| 288 | 
            +
                        self.logger.info(f"Retrieved {len(tickets)} tickets")
         | 
| 176 289 | 
             
                        return tickets
         | 
| 177 290 |  | 
| 291 | 
            +
                    except AttributeError as e:
         | 
| 292 | 
            +
                        attr_msg = str(e)
         | 
| 293 | 
            +
                        if "get_recent_tasks" in attr_msg:
         | 
| 294 | 
            +
                            self.logger.error("get_recent_tasks method not found in TaskManager")
         | 
| 295 | 
            +
                            self.logger.info("This method may not be available in your version")
         | 
| 296 | 
            +
                            self.logger.info("Try using the CLI directly: aitrackdown task list")
         | 
| 297 | 
            +
                        else:
         | 
| 298 | 
            +
                            self.logger.error(f"API mismatch when listing tickets: {attr_msg}")
         | 
| 299 | 
            +
                        return []
         | 
| 300 | 
            +
                    except FileNotFoundError as e:
         | 
| 301 | 
            +
                        self.logger.error(f"Tickets directory not found: {e}")
         | 
| 302 | 
            +
                        self.logger.info("Ensure the tickets/ directory exists")
         | 
| 303 | 
            +
                        return []
         | 
| 304 | 
            +
                    except PermissionError as e:
         | 
| 305 | 
            +
                        self.logger.error(f"Permission denied reading tickets: {e}")
         | 
| 306 | 
            +
                        self.logger.info("Check read permissions for the tickets/ directory")
         | 
| 307 | 
            +
                        return []
         | 
| 178 308 | 
             
                    except Exception as e:
         | 
| 179 | 
            -
                        self.logger.error(f"Failed to list tickets: {e}")
         | 
| 309 | 
            +
                        self.logger.error(f"Failed to list tickets: {e.__class__.__name__}: {e}")
         | 
| 310 | 
            +
                        self.logger.info("Try using the CLI directly: aitrackdown task list")
         | 
| 311 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 180 312 | 
             
                        return []
         | 
| 181 313 |  | 
| 182 314 | 
             
                def get_ticket(self, ticket_id: str) -> Optional[Dict[str, Any]]:
         | 
| @@ -190,24 +322,55 @@ class TicketManager: | |
| 190 322 | 
             
                        Ticket data or None
         | 
| 191 323 | 
             
                    """
         | 
| 192 324 | 
             
                    if not self.task_manager:
         | 
| 325 | 
            +
                        self.logger.error("TaskManager not available - cannot retrieve ticket")
         | 
| 326 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 327 | 
            +
                        return None
         | 
| 328 | 
            +
                    
         | 
| 329 | 
            +
                    if not ticket_id or not ticket_id.strip():
         | 
| 330 | 
            +
                        self.logger.error("Invalid ticket ID provided")
         | 
| 193 331 | 
             
                        return None
         | 
| 194 332 |  | 
| 195 333 | 
             
                    try:
         | 
| 196 | 
            -
                        task = self.task_manager.load_task(ticket_id)
         | 
| 197 | 
            -
                        
         | 
| 198 | 
            -
                         | 
| 199 | 
            -
                             | 
| 200 | 
            -
                             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
                            ' | 
| 205 | 
            -
                            ' | 
| 206 | 
            -
                            ' | 
| 207 | 
            -
                            ' | 
| 208 | 
            -
                            ' | 
| 334 | 
            +
                        task = self.task_manager.load_task(ticket_id.strip())
         | 
| 335 | 
            +
                        
         | 
| 336 | 
            +
                        if not task:
         | 
| 337 | 
            +
                            self.logger.warning(f"Ticket {ticket_id} not found")
         | 
| 338 | 
            +
                            return None
         | 
| 339 | 
            +
                        
         | 
| 340 | 
            +
                        # Safely extract all fields with defaults
         | 
| 341 | 
            +
                        ticket_data = {
         | 
| 342 | 
            +
                            'id': getattr(task, 'id', ticket_id),
         | 
| 343 | 
            +
                            'title': getattr(task, 'title', 'Untitled'),
         | 
| 344 | 
            +
                            'description': getattr(task, 'description', ''),
         | 
| 345 | 
            +
                            'status': getattr(task, 'status', 'unknown'),
         | 
| 346 | 
            +
                            'priority': getattr(task, 'priority', 'medium'),
         | 
| 347 | 
            +
                            'tags': getattr(task, 'tags', []),
         | 
| 348 | 
            +
                            'assignees': getattr(task, 'assignees', []),
         | 
| 349 | 
            +
                            'created_at': getattr(task, 'created_at', 'N/A'),
         | 
| 350 | 
            +
                            'updated_at': getattr(task, 'updated_at', 'N/A'),
         | 
| 351 | 
            +
                            'metadata': getattr(task, 'metadata', {}),
         | 
| 209 352 | 
             
                        }
         | 
| 210 353 |  | 
| 354 | 
            +
                        self.logger.info(f"Successfully retrieved ticket: {ticket_id}")
         | 
| 355 | 
            +
                        return ticket_data
         | 
| 356 | 
            +
                        
         | 
| 357 | 
            +
                    except AttributeError as e:
         | 
| 358 | 
            +
                        attr_msg = str(e)
         | 
| 359 | 
            +
                        if "load_task" in attr_msg:
         | 
| 360 | 
            +
                            self.logger.error("load_task method not found in TaskManager")
         | 
| 361 | 
            +
                            self.logger.info("The API may have changed or this method is not available")
         | 
| 362 | 
            +
                        else:
         | 
| 363 | 
            +
                            self.logger.error(f"Error accessing ticket attributes: {attr_msg}")
         | 
| 364 | 
            +
                        return None
         | 
| 365 | 
            +
                    except FileNotFoundError as e:
         | 
| 366 | 
            +
                        self.logger.error(f"Ticket file not found: {ticket_id}")
         | 
| 367 | 
            +
                        self.logger.info(f"The ticket may have been deleted or the ID is incorrect")
         | 
| 368 | 
            +
                        return None
         | 
| 369 | 
            +
                    except PermissionError as e:
         | 
| 370 | 
            +
                        self.logger.error(f"Permission denied reading ticket {ticket_id}: {e}")
         | 
| 371 | 
            +
                        return None
         | 
| 211 372 | 
             
                    except Exception as e:
         | 
| 212 | 
            -
                        self.logger.error(f"Failed to get ticket {ticket_id}: {e}")
         | 
| 373 | 
            +
                        self.logger.error(f"Failed to get ticket {ticket_id}: {e.__class__.__name__}: {e}")
         | 
| 374 | 
            +
                        self.logger.info(f"Try using the CLI: aitrackdown show {ticket_id}")
         | 
| 375 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 213 376 | 
             
                        return None
         | 
| @@ -278,7 +278,11 @@ class AgentDependencyLoader: | |
| 278 278 |  | 
| 279 279 | 
             
                def install_missing_dependencies(self, dependencies: List[str]) -> Tuple[bool, str]:
         | 
| 280 280 | 
             
                    """
         | 
| 281 | 
            -
                    Install missing Python dependencies.
         | 
| 281 | 
            +
                    Install missing Python dependencies using robust retry logic.
         | 
| 282 | 
            +
                    
         | 
| 283 | 
            +
                    WHY: Network issues and temporary package unavailability can cause
         | 
| 284 | 
            +
                    installation failures. Using the robust installer with retries
         | 
| 285 | 
            +
                    significantly improves success rate.
         | 
| 282 286 |  | 
| 283 287 | 
             
                    Args:
         | 
| 284 288 | 
             
                        dependencies: List of package specifications to install
         | 
| @@ -299,27 +303,74 @@ class AgentDependencyLoader: | |
| 299 303 |  | 
| 300 304 | 
             
                    if not compatible:
         | 
| 301 305 | 
             
                        return True, "No compatible packages to install"
         | 
| 302 | 
            -
             | 
| 306 | 
            +
                    
         | 
| 307 | 
            +
                    # Use robust installer with retry logic
         | 
| 303 308 | 
             
                    try:
         | 
| 304 | 
            -
                         | 
| 305 | 
            -
                         | 
| 309 | 
            +
                        from .robust_installer import RobustPackageInstaller
         | 
| 310 | 
            +
                        
         | 
| 311 | 
            +
                        logger.info(f"Installing {len(compatible)} compatible dependencies with retry logic...")
         | 
| 306 312 | 
             
                        if incompatible:
         | 
| 307 313 | 
             
                            logger.info(f"(Skipping {len(incompatible)} incompatible with Python {sys.version_info.major}.{sys.version_info.minor})")
         | 
| 308 314 |  | 
| 309 | 
            -
                         | 
| 310 | 
            -
             | 
| 311 | 
            -
                             | 
| 312 | 
            -
                             | 
| 315 | 
            +
                        # Create installer with sensible defaults
         | 
| 316 | 
            +
                        installer = RobustPackageInstaller(
         | 
| 317 | 
            +
                            max_retries=3,
         | 
| 318 | 
            +
                            retry_delay=2.0,
         | 
| 313 319 | 
             
                            timeout=300
         | 
| 314 320 | 
             
                        )
         | 
| 315 321 |  | 
| 316 | 
            -
                         | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
                             | 
| 321 | 
            -
             | 
| 322 | 
            -
                             | 
| 322 | 
            +
                        # Install packages
         | 
| 323 | 
            +
                        successful, failed, errors = installer.install_packages(compatible)
         | 
| 324 | 
            +
                        
         | 
| 325 | 
            +
                        if failed:
         | 
| 326 | 
            +
                            # Provide detailed error information
         | 
| 327 | 
            +
                            error_details = []
         | 
| 328 | 
            +
                            for pkg in failed:
         | 
| 329 | 
            +
                                error_details.append(f"{pkg}: {errors.get(pkg, 'Unknown error')}")
         | 
| 330 | 
            +
                            
         | 
| 331 | 
            +
                            error_msg = f"Failed to install {len(failed)} packages:\n" + "\n".join(error_details)
         | 
| 332 | 
            +
                            logger.error(error_msg)
         | 
| 333 | 
            +
                            
         | 
| 334 | 
            +
                            # Partial success handling
         | 
| 335 | 
            +
                            if successful:
         | 
| 336 | 
            +
                                partial_msg = f"Partially successful: installed {len(successful)} of {len(compatible)} packages"
         | 
| 337 | 
            +
                                logger.info(partial_msg)
         | 
| 338 | 
            +
                                if incompatible:
         | 
| 339 | 
            +
                                    return True, f"{partial_msg}. Also skipped {len(incompatible)} incompatible"
         | 
| 340 | 
            +
                                return True, partial_msg
         | 
| 341 | 
            +
                            
         | 
| 342 | 
            +
                            return False, error_msg
         | 
| 343 | 
            +
                        
         | 
| 344 | 
            +
                        logger.info(f"Successfully installed all {len(successful)} compatible dependencies")
         | 
| 345 | 
            +
                        if incompatible:
         | 
| 346 | 
            +
                            return True, f"Installed {len(compatible)} packages, skipped {len(incompatible)} incompatible"
         | 
| 347 | 
            +
                        return True, ""
         | 
| 348 | 
            +
                        
         | 
| 349 | 
            +
                    except ImportError:
         | 
| 350 | 
            +
                        # Fallback to simple installation if robust installer not available
         | 
| 351 | 
            +
                        logger.warning("Robust installer not available, falling back to simple installation")
         | 
| 352 | 
            +
                        try:
         | 
| 353 | 
            +
                            cmd = [sys.executable, "-m", "pip", "install"] + compatible
         | 
| 354 | 
            +
                            
         | 
| 355 | 
            +
                            result = subprocess.run(
         | 
| 356 | 
            +
                                cmd,
         | 
| 357 | 
            +
                                capture_output=True,
         | 
| 358 | 
            +
                                text=True,
         | 
| 359 | 
            +
                                timeout=300
         | 
| 360 | 
            +
                            )
         | 
| 361 | 
            +
                            
         | 
| 362 | 
            +
                            if result.returncode == 0:
         | 
| 363 | 
            +
                                logger.info("Successfully installed compatible dependencies")
         | 
| 364 | 
            +
                                if incompatible:
         | 
| 365 | 
            +
                                    return True, f"Installed {len(compatible)} packages, skipped {len(incompatible)} incompatible"
         | 
| 366 | 
            +
                                return True, ""
         | 
| 367 | 
            +
                            else:
         | 
| 368 | 
            +
                                error_msg = f"Installation failed: {result.stderr}"
         | 
| 369 | 
            +
                                logger.error(error_msg)
         | 
| 370 | 
            +
                                return False, error_msg
         | 
| 371 | 
            +
                                
         | 
| 372 | 
            +
                        except Exception as e:
         | 
| 373 | 
            +
                            error_msg = f"Failed to install dependencies: {e}"
         | 
| 323 374 | 
             
                            logger.error(error_msg)
         | 
| 324 375 | 
             
                            return False, error_msg
         | 
| 325 376 |  |