claude-mpm 3.7.1__py3-none-any.whl → 3.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +18 -0
- claude_mpm/agents/frontmatter_validator.py +116 -17
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/{dashboard → agents}/templates/.claude-mpm/memories/engineer_agent.md +1 -1
- claude_mpm/{dashboard/templates/.claude-mpm/memories/version_control_agent.md → agents/templates/.claude-mpm/memories/qa_agent.md} +2 -2
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +39 -0
- claude_mpm/agents/templates/code_analyzer.json +34 -12
- claude_mpm/agents/templates/data_engineer.json +5 -8
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +6 -6
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +12 -9
- claude_mpm/agents/templates/security.json +4 -7
- claude_mpm/agents/templates/ticketing.json +161 -0
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +214 -0
- claude_mpm/agents/templates/web_ui.json +176 -0
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/parser.py +11 -0
- claude_mpm/cli/ticket_cli.py +31 -0
- claude_mpm/core/framework_loader.py +102 -49
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +9 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +174 -13
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/ticket_manager.py +207 -44
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/robust_installer.py +587 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +37 -46
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/agents/templates/test_integration.json +0 -113
- claude_mpm/cli/README.md +0 -108
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/config/async_logging_config.yaml +0 -145
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
- claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/README.md +0 -121
- claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/hooks/README.md +0 -96
- claude_mpm/schemas/agent_schema.json +0 -435
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/version_control/VERSION +0 -1
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.1.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
| @@ -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 |  |