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
| @@ -4,13 +4,12 @@ from pathlib import Path | |
| 4 4 | 
             
            from typing import Optional, Dict, Any, List
         | 
| 5 5 | 
             
            from datetime import datetime
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
                from ..core.logger import get_logger
         | 
| 9 | 
            -
            except ImportError:
         | 
| 10 | 
            -
                from core.logger import get_logger
         | 
| 7 | 
            +
            from claude_mpm.core.logging_config import get_logger
         | 
| 11 8 |  | 
| 9 | 
            +
            from ..core.interfaces import TicketManagerInterface
         | 
| 12 10 |  | 
| 13 | 
            -
             | 
| 11 | 
            +
             | 
| 12 | 
            +
            class TicketManager(TicketManagerInterface):
         | 
| 14 13 | 
             
                """
         | 
| 15 14 | 
             
                Manage ticket creation using ai-trackdown-pytools.
         | 
| 16 15 |  | 
| @@ -25,7 +24,7 @@ class TicketManager: | |
| 25 24 | 
             
                    Args:
         | 
| 26 25 | 
             
                        project_path: Project root (defaults to current directory)
         | 
| 27 26 | 
             
                    """
         | 
| 28 | 
            -
                    self.logger = get_logger( | 
| 27 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 29 28 | 
             
                    self.project_path = project_path or Path.cwd()
         | 
| 30 29 | 
             
                    self.task_manager = self._init_task_manager()
         | 
| 31 30 |  | 
| @@ -44,21 +43,28 @@ class TicketManager: | |
| 44 43 | 
             
                            (tickets_dir / "tasks").mkdir(exist_ok=True)
         | 
| 45 44 | 
             
                            self.logger.info(f"Created tickets directory structure at: {tickets_dir}")
         | 
| 46 45 |  | 
| 47 | 
            -
                        #  | 
| 46 | 
            +
                        # Use standardized config format (.trackdown.yaml)
         | 
| 48 47 | 
             
                        config_file = self.project_path / ".trackdown.yaml"
         | 
| 48 | 
            +
                        
         | 
| 49 | 
            +
                        # Check if config exists, create if needed
         | 
| 49 50 | 
             
                        if not config_file.exists():
         | 
| 50 | 
            -
                             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 51 | 
            +
                            try:
         | 
| 52 | 
            +
                                config = Config.create_default(str(config_file))
         | 
| 53 | 
            +
                                config.set("paths.tickets_dir", "tickets")
         | 
| 54 | 
            +
                                config.set("paths.epics_dir", "tickets/epics")
         | 
| 55 | 
            +
                                config.set("paths.issues_dir", "tickets/issues")
         | 
| 56 | 
            +
                                config.set("paths.tasks_dir", "tickets/tasks")
         | 
| 57 | 
            +
                                config.save()
         | 
| 58 | 
            +
                                self.logger.info("Created .trackdown.yaml configuration")
         | 
| 59 | 
            +
                            except Exception as config_error:
         | 
| 60 | 
            +
                                self.logger.warning(f"Could not create config file: {config_error}")
         | 
| 61 | 
            +
                                self.logger.info("Proceeding without config file - using defaults")
         | 
| 62 | 
            +
                        else:
         | 
| 63 | 
            +
                            self.logger.info(f"Using configuration from: {config_file}")
         | 
| 58 64 |  | 
| 59 65 | 
             
                        # Initialize TaskManager directly with the project path
         | 
| 60 66 | 
             
                        # TaskManager will handle project initialization internally
         | 
| 61 | 
            -
                        task_manager = TaskManager(self.project_path)
         | 
| 67 | 
            +
                        task_manager = TaskManager(str(self.project_path))
         | 
| 62 68 |  | 
| 63 69 | 
             
                        # Verify it's using the right directory
         | 
| 64 70 | 
             
                        if hasattr(task_manager, 'tasks_dir'):
         | 
| @@ -68,13 +74,41 @@ class TicketManager: | |
| 68 74 |  | 
| 69 75 | 
             
                        return task_manager
         | 
| 70 76 |  | 
| 71 | 
            -
                    except ImportError:
         | 
| 72 | 
            -
                         | 
| 73 | 
            -
                         | 
| 77 | 
            +
                    except ImportError as e:
         | 
| 78 | 
            +
                        import_msg = str(e)
         | 
| 79 | 
            +
                        if "ai_trackdown_pytools" in import_msg.lower():
         | 
| 80 | 
            +
                            self.logger.error("ai-trackdown-pytools is not installed")
         | 
| 81 | 
            +
                            self.logger.info("Install with: pip install ai-trackdown-pytools")
         | 
| 82 | 
            +
                        else:
         | 
| 83 | 
            +
                            self.logger.error(f"Missing dependency: {import_msg}")
         | 
| 84 | 
            +
                            self.logger.info("Ensure all required packages are installed")
         | 
| 85 | 
            +
                        self.logger.debug(f"Import error details: {import_msg}")
         | 
| 86 | 
            +
                        return None
         | 
| 87 | 
            +
                    except AttributeError as e:
         | 
| 88 | 
            +
                        attr_msg = str(e)
         | 
| 89 | 
            +
                        if "TaskManager" in attr_msg:
         | 
| 90 | 
            +
                            self.logger.error("TaskManager class not found in ai-trackdown-pytools")
         | 
| 91 | 
            +
                            self.logger.info("This may indicate an incompatible version")
         | 
| 92 | 
            +
                        else:
         | 
| 93 | 
            +
                            self.logger.error(f"ai-trackdown-pytools API mismatch: {attr_msg}")
         | 
| 94 | 
            +
                        self.logger.info("Try updating: pip install --upgrade ai-trackdown-pytools")
         | 
| 95 | 
            +
                        return None
         | 
| 96 | 
            +
                    except FileNotFoundError as e:
         | 
| 97 | 
            +
                        self.logger.error(f"Required file or directory not found: {e}")
         | 
| 98 | 
            +
                        self.logger.info("Ensure the project directory exists and is accessible")
         | 
| 99 | 
            +
                        return None
         | 
| 100 | 
            +
                    except PermissionError as e:
         | 
| 101 | 
            +
                        self.logger.error(f"Permission denied accessing ticket files: {e}")
         | 
| 102 | 
            +
                        self.logger.info("Check file permissions in the tickets/ directory")
         | 
| 103 | 
            +
                        self.logger.info("You may need to run with appropriate permissions")
         | 
| 74 104 | 
             
                        return None
         | 
| 75 105 | 
             
                    except Exception as e:
         | 
| 76 | 
            -
                        self.logger.error(f" | 
| 77 | 
            -
                        self.logger. | 
| 106 | 
            +
                        self.logger.error(f"Unexpected error initializing TaskManager: {e.__class__.__name__}: {e}")
         | 
| 107 | 
            +
                        self.logger.info("This could be due to:")
         | 
| 108 | 
            +
                        self.logger.info("  - Corrupted configuration files")
         | 
| 109 | 
            +
                        self.logger.info("  - Incompatible ai-trackdown-pytools version")
         | 
| 110 | 
            +
                        self.logger.info("  - Missing dependencies")
         | 
| 111 | 
            +
                        self.logger.debug(f"Full error details:", exc_info=True)
         | 
| 78 112 | 
             
                        return None
         | 
| 79 113 |  | 
| 80 114 | 
             
                def create_ticket(
         | 
| @@ -96,19 +130,41 @@ class TicketManager: | |
| 96 130 | 
             
                        title: Ticket title
         | 
| 97 131 | 
             
                        ticket_type: Type (task, bug, feature, etc.)
         | 
| 98 132 | 
             
                        description: Detailed description
         | 
| 99 | 
            -
                        priority: Priority level (low, medium, high)
         | 
| 133 | 
            +
                        priority: Priority level (low, medium, high, critical)
         | 
| 100 134 | 
             
                        tags: List of tags/labels
         | 
| 101 135 | 
             
                        source: Source identifier
         | 
| 136 | 
            +
                        parent_epic: Parent epic ID (optional)
         | 
| 137 | 
            +
                        parent_issue: Parent issue ID (optional)
         | 
| 102 138 | 
             
                        **kwargs: Additional metadata
         | 
| 103 139 |  | 
| 104 140 | 
             
                    Returns:
         | 
| 105 141 | 
             
                        Ticket ID if created, None on failure
         | 
| 106 142 | 
             
                    """
         | 
| 107 143 | 
             
                    if not self.task_manager:
         | 
| 108 | 
            -
                        self.logger.error("TaskManager not available")
         | 
| 144 | 
            +
                        self.logger.error("TaskManager not available - cannot create ticket")
         | 
| 145 | 
            +
                        self.logger.info("Please ensure ai-trackdown-pytools is installed and properly configured")
         | 
| 146 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 109 147 | 
             
                        return None
         | 
| 110 148 |  | 
| 111 149 | 
             
                    try:
         | 
| 150 | 
            +
                        # Validate input
         | 
| 151 | 
            +
                        if not title or not title.strip():
         | 
| 152 | 
            +
                            self.logger.error("Cannot create ticket with empty title")
         | 
| 153 | 
            +
                            return None
         | 
| 154 | 
            +
                        
         | 
| 155 | 
            +
                        # Validate priority
         | 
| 156 | 
            +
                        valid_priorities = ['low', 'medium', 'high', 'critical']
         | 
| 157 | 
            +
                        if priority.lower() not in valid_priorities:
         | 
| 158 | 
            +
                            self.logger.warning(f"Invalid priority '{priority}', using 'medium'")
         | 
| 159 | 
            +
                            self.logger.info(f"Valid priorities are: {', '.join(valid_priorities)}")
         | 
| 160 | 
            +
                            priority = 'medium'
         | 
| 161 | 
            +
                        
         | 
| 162 | 
            +
                        # Validate ticket type
         | 
| 163 | 
            +
                        valid_types = ['task', 'bug', 'feature', 'issue', 'enhancement', 'documentation']
         | 
| 164 | 
            +
                        if ticket_type.lower() not in valid_types:
         | 
| 165 | 
            +
                            self.logger.warning(f"Non-standard ticket type '{ticket_type}'")
         | 
| 166 | 
            +
                            self.logger.info(f"Common types are: {', '.join(valid_types)}")
         | 
| 167 | 
            +
                        
         | 
| 112 168 | 
             
                        # Prepare tags
         | 
| 113 169 | 
             
                        if tags is None:
         | 
| 114 170 | 
             
                            tags = []
         | 
| @@ -121,7 +177,7 @@ class TicketManager: | |
| 121 177 |  | 
| 122 178 | 
             
                        # Prepare task data
         | 
| 123 179 | 
             
                        task_data = {
         | 
| 124 | 
            -
                            'title': title,
         | 
| 180 | 
            +
                            'title': title.strip(),
         | 
| 125 181 | 
             
                            'description': description or f"Auto-extracted {ticket_type} from Claude MPM session",
         | 
| 126 182 | 
             
                            'status': 'open',
         | 
| 127 183 | 
             
                            'priority': priority.lower(),
         | 
| @@ -136,14 +192,49 @@ class TicketManager: | |
| 136 192 | 
             
                            }
         | 
| 137 193 | 
             
                        }
         | 
| 138 194 |  | 
| 195 | 
            +
                        # Add parent references if provided
         | 
| 196 | 
            +
                        if parent_epic:
         | 
| 197 | 
            +
                            task_data['metadata']['parent_epic'] = parent_epic
         | 
| 198 | 
            +
                            self.logger.debug(f"Linking to parent epic: {parent_epic}")
         | 
| 199 | 
            +
                        if parent_issue:
         | 
| 200 | 
            +
                            task_data['metadata']['parent_issue'] = parent_issue
         | 
| 201 | 
            +
                            self.logger.debug(f"Linking to parent issue: {parent_issue}")
         | 
| 202 | 
            +
                        
         | 
| 139 203 | 
             
                        # Create the task
         | 
| 140 204 | 
             
                        task = self.task_manager.create_task(**task_data)
         | 
| 141 205 |  | 
| 142 | 
            -
                         | 
| 143 | 
            -
             | 
| 206 | 
            +
                        if task and hasattr(task, 'id'):
         | 
| 207 | 
            +
                            self.logger.info(f"Successfully created ticket: {task.id} - {title}")
         | 
| 208 | 
            +
                            return task.id
         | 
| 209 | 
            +
                        else:
         | 
| 210 | 
            +
                            self.logger.error(f"Task creation failed - no ID returned")
         | 
| 211 | 
            +
                            self.logger.info("The task may have been created but without proper ID assignment")
         | 
| 212 | 
            +
                            return None
         | 
| 144 213 |  | 
| 214 | 
            +
                    except AttributeError as e:
         | 
| 215 | 
            +
                        attr_msg = str(e)
         | 
| 216 | 
            +
                        if "create_task" in attr_msg:
         | 
| 217 | 
            +
                            self.logger.error("create_task method not found in TaskManager")
         | 
| 218 | 
            +
                            self.logger.info("The ai-trackdown-pytools API may have changed")
         | 
| 219 | 
            +
                            self.logger.info("Check for updates or API documentation")
         | 
| 220 | 
            +
                        else:
         | 
| 221 | 
            +
                            self.logger.error(f"API mismatch when creating ticket: {attr_msg}")
         | 
| 222 | 
            +
                        return None
         | 
| 223 | 
            +
                    except ValueError as e:
         | 
| 224 | 
            +
                        self.logger.error(f"Invalid data provided for ticket: {e}")
         | 
| 225 | 
            +
                        self.logger.info("Check that all required fields are provided correctly")
         | 
| 226 | 
            +
                        return None
         | 
| 227 | 
            +
                    except PermissionError as e:
         | 
| 228 | 
            +
                        self.logger.error(f"Permission denied when creating ticket: {e}")
         | 
| 229 | 
            +
                        self.logger.info("Check write permissions for the tickets/ directory")
         | 
| 230 | 
            +
                        return None
         | 
| 145 231 | 
             
                    except Exception as e:
         | 
| 146 | 
            -
                        self.logger.error(f" | 
| 232 | 
            +
                        self.logger.error(f"Unexpected error creating ticket: {e.__class__.__name__}: {e}")
         | 
| 233 | 
            +
                        self.logger.info("This could be due to:")
         | 
| 234 | 
            +
                        self.logger.info("  - Disk full or quota exceeded")
         | 
| 235 | 
            +
                        self.logger.info("  - Invalid characters in title or description")
         | 
| 236 | 
            +
                        self.logger.info("  - Network issues (if using remote storage)")
         | 
| 237 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 147 238 | 
             
                        return None
         | 
| 148 239 |  | 
| 149 240 | 
             
                def list_recent_tickets(self, limit: int = 10) -> List[Dict[str, Any]]:
         | 
| @@ -157,26 +248,66 @@ class TicketManager: | |
| 157 248 | 
             
                        List of ticket summaries
         | 
| 158 249 | 
             
                    """
         | 
| 159 250 | 
             
                    if not self.task_manager:
         | 
| 251 | 
            +
                        self.logger.warning("TaskManager not available - cannot list tickets")
         | 
| 252 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 160 253 | 
             
                        return []
         | 
| 161 254 |  | 
| 162 255 | 
             
                    try:
         | 
| 256 | 
            +
                        # Validate limit
         | 
| 257 | 
            +
                        if limit < 1:
         | 
| 258 | 
            +
                            self.logger.warning(f"Invalid limit {limit}, using 10")
         | 
| 259 | 
            +
                            limit = 10
         | 
| 260 | 
            +
                        elif limit > 100:
         | 
| 261 | 
            +
                            self.logger.warning(f"Limit {limit} too high, capping at 100")
         | 
| 262 | 
            +
                            limit = 100
         | 
| 263 | 
            +
                        
         | 
| 163 264 | 
             
                        tasks = self.task_manager.get_recent_tasks(limit=limit)
         | 
| 164 265 |  | 
| 266 | 
            +
                        if not tasks:
         | 
| 267 | 
            +
                            self.logger.info("No tickets found in the system")
         | 
| 268 | 
            +
                            return []
         | 
| 269 | 
            +
                        
         | 
| 165 270 | 
             
                        tickets = []
         | 
| 166 271 | 
             
                        for task in tasks:
         | 
| 167 | 
            -
                             | 
| 168 | 
            -
                                 | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 272 | 
            +
                            try:
         | 
| 273 | 
            +
                                ticket_data = {
         | 
| 274 | 
            +
                                    'id': getattr(task, 'id', 'UNKNOWN'),
         | 
| 275 | 
            +
                                    'title': getattr(task, 'title', 'Untitled'),
         | 
| 276 | 
            +
                                    'status': getattr(task, 'status', 'unknown'),
         | 
| 277 | 
            +
                                    'priority': getattr(task, 'priority', 'medium'),
         | 
| 278 | 
            +
                                    'tags': getattr(task, 'tags', []),
         | 
| 279 | 
            +
                                    'created_at': getattr(task, 'created_at', 'N/A'),
         | 
| 280 | 
            +
                                }
         | 
| 281 | 
            +
                                tickets.append(ticket_data)
         | 
| 282 | 
            +
                            except Exception as task_error:
         | 
| 283 | 
            +
                                self.logger.warning(f"Error processing task: {task_error}")
         | 
| 284 | 
            +
                                self.logger.debug(f"Task object type: {type(task)}")
         | 
| 285 | 
            +
                                continue
         | 
| 175 286 |  | 
| 287 | 
            +
                        self.logger.info(f"Retrieved {len(tickets)} tickets")
         | 
| 176 288 | 
             
                        return tickets
         | 
| 177 289 |  | 
| 290 | 
            +
                    except AttributeError as e:
         | 
| 291 | 
            +
                        attr_msg = str(e)
         | 
| 292 | 
            +
                        if "get_recent_tasks" in attr_msg:
         | 
| 293 | 
            +
                            self.logger.error("get_recent_tasks method not found in TaskManager")
         | 
| 294 | 
            +
                            self.logger.info("This method may not be available in your version")
         | 
| 295 | 
            +
                            self.logger.info("Try using the CLI directly: aitrackdown task list")
         | 
| 296 | 
            +
                        else:
         | 
| 297 | 
            +
                            self.logger.error(f"API mismatch when listing tickets: {attr_msg}")
         | 
| 298 | 
            +
                        return []
         | 
| 299 | 
            +
                    except FileNotFoundError as e:
         | 
| 300 | 
            +
                        self.logger.error(f"Tickets directory not found: {e}")
         | 
| 301 | 
            +
                        self.logger.info("Ensure the tickets/ directory exists")
         | 
| 302 | 
            +
                        return []
         | 
| 303 | 
            +
                    except PermissionError as e:
         | 
| 304 | 
            +
                        self.logger.error(f"Permission denied reading tickets: {e}")
         | 
| 305 | 
            +
                        self.logger.info("Check read permissions for the tickets/ directory")
         | 
| 306 | 
            +
                        return []
         | 
| 178 307 | 
             
                    except Exception as e:
         | 
| 179 | 
            -
                        self.logger.error(f"Failed to list tickets: {e}")
         | 
| 308 | 
            +
                        self.logger.error(f"Failed to list tickets: {e.__class__.__name__}: {e}")
         | 
| 309 | 
            +
                        self.logger.info("Try using the CLI directly: aitrackdown task list")
         | 
| 310 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 180 311 | 
             
                        return []
         | 
| 181 312 |  | 
| 182 313 | 
             
                def get_ticket(self, ticket_id: str) -> Optional[Dict[str, Any]]:
         | 
| @@ -190,24 +321,219 @@ class TicketManager: | |
| 190 321 | 
             
                        Ticket data or None
         | 
| 191 322 | 
             
                    """
         | 
| 192 323 | 
             
                    if not self.task_manager:
         | 
| 324 | 
            +
                        self.logger.error("TaskManager not available - cannot retrieve ticket")
         | 
| 325 | 
            +
                        self.logger.info("Run: pip install ai-trackdown-pytools")
         | 
| 326 | 
            +
                        return None
         | 
| 327 | 
            +
                    
         | 
| 328 | 
            +
                    if not ticket_id or not ticket_id.strip():
         | 
| 329 | 
            +
                        self.logger.error("Invalid ticket ID provided")
         | 
| 193 330 | 
             
                        return None
         | 
| 194 331 |  | 
| 195 332 | 
             
                    try:
         | 
| 196 | 
            -
                        task = self.task_manager.load_task(ticket_id)
         | 
| 197 | 
            -
                        
         | 
| 198 | 
            -
                         | 
| 199 | 
            -
                             | 
| 200 | 
            -
                             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
                            ' | 
| 205 | 
            -
                            ' | 
| 206 | 
            -
                            ' | 
| 207 | 
            -
                            ' | 
| 208 | 
            -
                            ' | 
| 333 | 
            +
                        task = self.task_manager.load_task(ticket_id.strip())
         | 
| 334 | 
            +
                        
         | 
| 335 | 
            +
                        if not task:
         | 
| 336 | 
            +
                            self.logger.warning(f"Ticket {ticket_id} not found")
         | 
| 337 | 
            +
                            return None
         | 
| 338 | 
            +
                        
         | 
| 339 | 
            +
                        # Safely extract all fields with defaults
         | 
| 340 | 
            +
                        ticket_data = {
         | 
| 341 | 
            +
                            'id': getattr(task, 'id', ticket_id),
         | 
| 342 | 
            +
                            'title': getattr(task, 'title', 'Untitled'),
         | 
| 343 | 
            +
                            'description': getattr(task, 'description', ''),
         | 
| 344 | 
            +
                            'status': getattr(task, 'status', 'unknown'),
         | 
| 345 | 
            +
                            'priority': getattr(task, 'priority', 'medium'),
         | 
| 346 | 
            +
                            'tags': getattr(task, 'tags', []),
         | 
| 347 | 
            +
                            'assignees': getattr(task, 'assignees', []),
         | 
| 348 | 
            +
                            'created_at': getattr(task, 'created_at', 'N/A'),
         | 
| 349 | 
            +
                            'updated_at': getattr(task, 'updated_at', 'N/A'),
         | 
| 350 | 
            +
                            'metadata': getattr(task, 'metadata', {}),
         | 
| 209 351 | 
             
                        }
         | 
| 210 352 |  | 
| 353 | 
            +
                        self.logger.info(f"Successfully retrieved ticket: {ticket_id}")
         | 
| 354 | 
            +
                        return ticket_data
         | 
| 355 | 
            +
                        
         | 
| 356 | 
            +
                    except AttributeError as e:
         | 
| 357 | 
            +
                        attr_msg = str(e)
         | 
| 358 | 
            +
                        if "load_task" in attr_msg:
         | 
| 359 | 
            +
                            self.logger.error("load_task method not found in TaskManager")
         | 
| 360 | 
            +
                            self.logger.info("The API may have changed or this method is not available")
         | 
| 361 | 
            +
                        else:
         | 
| 362 | 
            +
                            self.logger.error(f"Error accessing ticket attributes: {attr_msg}")
         | 
| 363 | 
            +
                        return None
         | 
| 364 | 
            +
                    except FileNotFoundError as e:
         | 
| 365 | 
            +
                        self.logger.error(f"Ticket file not found: {ticket_id}")
         | 
| 366 | 
            +
                        self.logger.info(f"The ticket may have been deleted or the ID is incorrect")
         | 
| 367 | 
            +
                        return None
         | 
| 368 | 
            +
                    except PermissionError as e:
         | 
| 369 | 
            +
                        self.logger.error(f"Permission denied reading ticket {ticket_id}: {e}")
         | 
| 370 | 
            +
                        return None
         | 
| 211 371 | 
             
                    except Exception as e:
         | 
| 212 | 
            -
                        self.logger.error(f"Failed to get ticket {ticket_id}: {e}")
         | 
| 213 | 
            -
                         | 
| 372 | 
            +
                        self.logger.error(f"Failed to get ticket {ticket_id}: {e.__class__.__name__}: {e}")
         | 
| 373 | 
            +
                        self.logger.info(f"Try using the CLI: aitrackdown show {ticket_id}")
         | 
| 374 | 
            +
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 375 | 
            +
                        return None
         | 
| 376 | 
            +
                
         | 
| 377 | 
            +
                # ================================================================================
         | 
| 378 | 
            +
                # Interface Adapter Methods
         | 
| 379 | 
            +
                # ================================================================================
         | 
| 380 | 
            +
                # These methods adapt the existing implementation to comply with TicketManagerInterface
         | 
| 381 | 
            +
                
         | 
| 382 | 
            +
                def create_task(self, title: str, description: str, **kwargs) -> Optional[str]:
         | 
| 383 | 
            +
                    """Create a new task ticket.
         | 
| 384 | 
            +
                    
         | 
| 385 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 386 | 
            +
                    the underlying task manager's create functionality.
         | 
| 387 | 
            +
                    
         | 
| 388 | 
            +
                    Args:
         | 
| 389 | 
            +
                        title: Task title
         | 
| 390 | 
            +
                        description: Task description
         | 
| 391 | 
            +
                        **kwargs: Additional task properties
         | 
| 392 | 
            +
                        
         | 
| 393 | 
            +
                    Returns:
         | 
| 394 | 
            +
                        Task ID if created successfully, None otherwise
         | 
| 395 | 
            +
                    """
         | 
| 396 | 
            +
                    if not self.task_manager:
         | 
| 397 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 398 | 
            +
                        return None
         | 
| 399 | 
            +
                    
         | 
| 400 | 
            +
                    try:
         | 
| 401 | 
            +
                        # Create task using ai-trackdown-pytools
         | 
| 402 | 
            +
                        from ai_trackdown_pytools.core.task import Task
         | 
| 403 | 
            +
                        
         | 
| 404 | 
            +
                        task = Task(
         | 
| 405 | 
            +
                            title=title,
         | 
| 406 | 
            +
                            description=description,
         | 
| 407 | 
            +
                            status=kwargs.get('status', 'open'),
         | 
| 408 | 
            +
                            priority=kwargs.get('priority', 'medium'),
         | 
| 409 | 
            +
                            tags=kwargs.get('tags', []),
         | 
| 410 | 
            +
                            assignees=kwargs.get('assignees', [])
         | 
| 411 | 
            +
                        )
         | 
| 412 | 
            +
                        
         | 
| 413 | 
            +
                        # Save the task
         | 
| 414 | 
            +
                        task_id = self.task_manager.create_task(task)
         | 
| 415 | 
            +
                        self.logger.info(f"Created task {task_id}: {title}")
         | 
| 416 | 
            +
                        return task_id
         | 
| 417 | 
            +
                        
         | 
| 418 | 
            +
                    except ImportError:
         | 
| 419 | 
            +
                        self.logger.error("ai-trackdown-pytools not available")
         | 
| 420 | 
            +
                        return None
         | 
| 421 | 
            +
                    except Exception as e:
         | 
| 422 | 
            +
                        self.logger.error(f"Failed to create task: {e}")
         | 
| 423 | 
            +
                        return None
         | 
| 424 | 
            +
                
         | 
| 425 | 
            +
                def update_task(self, task_id: str, **updates) -> bool:
         | 
| 426 | 
            +
                    """Update an existing task.
         | 
| 427 | 
            +
                    
         | 
| 428 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 429 | 
            +
                    task update operations.
         | 
| 430 | 
            +
                    
         | 
| 431 | 
            +
                    Args:
         | 
| 432 | 
            +
                        task_id: ID of task to update
         | 
| 433 | 
            +
                        **updates: Fields to update
         | 
| 434 | 
            +
                        
         | 
| 435 | 
            +
                    Returns:
         | 
| 436 | 
            +
                        True if update successful
         | 
| 437 | 
            +
                    """
         | 
| 438 | 
            +
                    if not self.task_manager:
         | 
| 439 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 440 | 
            +
                        return False
         | 
| 441 | 
            +
                    
         | 
| 442 | 
            +
                    try:
         | 
| 443 | 
            +
                        # Get the existing task
         | 
| 444 | 
            +
                        task = self.task_manager.get_task(task_id)
         | 
| 445 | 
            +
                        if not task:
         | 
| 446 | 
            +
                            self.logger.error(f"Task {task_id} not found")
         | 
| 447 | 
            +
                            return False
         | 
| 448 | 
            +
                        
         | 
| 449 | 
            +
                        # Apply updates
         | 
| 450 | 
            +
                        for key, value in updates.items():
         | 
| 451 | 
            +
                            if hasattr(task, key):
         | 
| 452 | 
            +
                                setattr(task, key, value)
         | 
| 453 | 
            +
                        
         | 
| 454 | 
            +
                        # Save the updated task
         | 
| 455 | 
            +
                        self.task_manager.update_task(task)
         | 
| 456 | 
            +
                        self.logger.info(f"Updated task {task_id}")
         | 
| 457 | 
            +
                        return True
         | 
| 458 | 
            +
                        
         | 
| 459 | 
            +
                    except Exception as e:
         | 
| 460 | 
            +
                        self.logger.error(f"Failed to update task {task_id}: {e}")
         | 
| 461 | 
            +
                        return False
         | 
| 462 | 
            +
                
         | 
| 463 | 
            +
                def get_task(self, task_id: str) -> Optional[Dict[str, Any]]:
         | 
| 464 | 
            +
                    """Get task details.
         | 
| 465 | 
            +
                    
         | 
| 466 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 467 | 
            +
                    the existing get_ticket method.
         | 
| 468 | 
            +
                    
         | 
| 469 | 
            +
                    Args:
         | 
| 470 | 
            +
                        task_id: ID of task to retrieve
         | 
| 471 | 
            +
                        
         | 
| 472 | 
            +
                    Returns:
         | 
| 473 | 
            +
                        Task data dictionary or None if not found
         | 
| 474 | 
            +
                    """
         | 
| 475 | 
            +
                    # Use existing get_ticket method which already returns dict format
         | 
| 476 | 
            +
                    return self.get_ticket(task_id)
         | 
| 477 | 
            +
                
         | 
| 478 | 
            +
                def list_tasks(self, status: Optional[str] = None, **filters) -> List[Dict[str, Any]]:
         | 
| 479 | 
            +
                    """List tasks with optional filtering.
         | 
| 480 | 
            +
                    
         | 
| 481 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 482 | 
            +
                    task listing operations.
         | 
| 483 | 
            +
                    
         | 
| 484 | 
            +
                    Args:
         | 
| 485 | 
            +
                        status: Optional status filter
         | 
| 486 | 
            +
                        **filters: Additional filter criteria
         | 
| 487 | 
            +
                        
         | 
| 488 | 
            +
                    Returns:
         | 
| 489 | 
            +
                        List of task dictionaries
         | 
| 490 | 
            +
                    """
         | 
| 491 | 
            +
                    if not self.task_manager:
         | 
| 492 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 493 | 
            +
                        return []
         | 
| 494 | 
            +
                    
         | 
| 495 | 
            +
                    try:
         | 
| 496 | 
            +
                        # Get all tasks
         | 
| 497 | 
            +
                        tasks = self.task_manager.list_tasks()
         | 
| 498 | 
            +
                        
         | 
| 499 | 
            +
                        # Apply filters
         | 
| 500 | 
            +
                        filtered_tasks = []
         | 
| 501 | 
            +
                        for task in tasks:
         | 
| 502 | 
            +
                            # Check status filter
         | 
| 503 | 
            +
                            if status and task.get('status') != status:
         | 
| 504 | 
            +
                                continue
         | 
| 505 | 
            +
                            
         | 
| 506 | 
            +
                            # Check additional filters
         | 
| 507 | 
            +
                            match = True
         | 
| 508 | 
            +
                            for key, value in filters.items():
         | 
| 509 | 
            +
                                if task.get(key) != value:
         | 
| 510 | 
            +
                                    match = False
         | 
| 511 | 
            +
                                    break
         | 
| 512 | 
            +
                            
         | 
| 513 | 
            +
                            if match:
         | 
| 514 | 
            +
                                filtered_tasks.append(task)
         | 
| 515 | 
            +
                        
         | 
| 516 | 
            +
                        return filtered_tasks
         | 
| 517 | 
            +
                        
         | 
| 518 | 
            +
                    except Exception as e:
         | 
| 519 | 
            +
                        self.logger.error(f"Failed to list tasks: {e}")
         | 
| 520 | 
            +
                        return []
         | 
| 521 | 
            +
                
         | 
| 522 | 
            +
                def close_task(self, task_id: str, resolution: Optional[str] = None) -> bool:
         | 
| 523 | 
            +
                    """Close a task.
         | 
| 524 | 
            +
                    
         | 
| 525 | 
            +
                    WHY: This adapter method provides interface compliance by updating
         | 
| 526 | 
            +
                    task status to closed.
         | 
| 527 | 
            +
                    
         | 
| 528 | 
            +
                    Args:
         | 
| 529 | 
            +
                        task_id: ID of task to close
         | 
| 530 | 
            +
                        resolution: Optional resolution description
         | 
| 531 | 
            +
                        
         | 
| 532 | 
            +
                    Returns:
         | 
| 533 | 
            +
                        True if close successful
         | 
| 534 | 
            +
                    """
         | 
| 535 | 
            +
                    updates = {'status': 'closed'}
         | 
| 536 | 
            +
                    if resolution:
         | 
| 537 | 
            +
                        updates['resolution'] = resolution
         | 
| 538 | 
            +
                    
         | 
| 539 | 
            +
                    return self.update_task(task_id, **updates)
         | 
| @@ -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 |  | 
| @@ -6,7 +6,7 @@ Inspired by awesome-claude-code's comprehensive error handling approach. | |
| 6 6 |  | 
| 7 7 | 
             
            import logging
         | 
| 8 8 | 
             
            import sys
         | 
| 9 | 
            -
            from typing import Optional, Type, Callable, Any, Dict
         | 
| 9 | 
            +
            from typing import Optional, Type, Callable, Any, Dict, List
         | 
| 10 10 | 
             
            from functools import wraps
         | 
| 11 11 | 
             
            import traceback
         | 
| 12 12 | 
             
            from datetime import datetime
         |