claude-mpm 3.7.4__py3-none-any.whl → 3.7.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +18 -0
  3. claude_mpm/agents/schema/agent_schema.json +1 -1
  4. claude_mpm/agents/templates/code_analyzer.json +26 -11
  5. claude_mpm/agents/templates/data_engineer.json +3 -6
  6. claude_mpm/agents/templates/documentation.json +2 -2
  7. claude_mpm/agents/templates/engineer.json +1 -1
  8. claude_mpm/agents/templates/ops.json +3 -8
  9. claude_mpm/agents/templates/qa.json +2 -3
  10. claude_mpm/agents/templates/research.json +1 -2
  11. claude_mpm/agents/templates/security.json +2 -5
  12. claude_mpm/agents/templates/ticketing.json +3 -3
  13. claude_mpm/agents/templates/version_control.json +3 -3
  14. claude_mpm/agents/templates/web_qa.json +3 -3
  15. claude_mpm/agents/templates/web_ui.json +3 -3
  16. claude_mpm/cli/commands/agents.py +118 -1
  17. claude_mpm/cli/parser.py +11 -0
  18. claude_mpm/core/framework_loader.py +8 -7
  19. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  20. claude_mpm/dashboard/templates/index.html +5 -5
  21. claude_mpm/services/agents/deployment/agent_deployment.py +5 -1
  22. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  23. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  24. claude_mpm/services/ticket_manager.py +207 -44
  25. claude_mpm/utils/agent_dependency_loader.py +66 -15
  26. claude_mpm/utils/robust_installer.py +587 -0
  27. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/METADATA +17 -21
  28. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/RECORD +32 -47
  29. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  30. claude_mpm/agents/agent-template.yaml +0 -83
  31. claude_mpm/cli/README.md +0 -108
  32. claude_mpm/cli_module/refactoring_guide.md +0 -253
  33. claude_mpm/config/async_logging_config.yaml +0 -145
  34. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  35. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  36. claude_mpm/dashboard/README.md +0 -121
  37. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  38. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  39. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  40. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  41. claude_mpm/hooks/README.md +0 -96
  42. claude_mpm/schemas/agent_schema.json +0 -435
  43. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  44. claude_mpm/services/version_control/VERSION +0 -1
  45. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/WHEEL +0 -0
  46. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-3.7.4.dist-info → claude_mpm-3.7.8.dist-info}/top_level.txt +0 -0
@@ -328,8 +328,20 @@ class FileToolTracker {
328
328
  */
329
329
  isFileOperation(event) {
330
330
  // File operations are tool events with tools that operate on files
331
- const fileTools = ['Read', 'Write', 'Edit', 'Grep', 'MultiEdit'];
332
- return event.tool_name && fileTools.includes(event.tool_name);
331
+ // Check case-insensitively since tool names can come in different cases
332
+ const fileTools = ['read', 'write', 'edit', 'grep', 'multiedit', 'glob', 'ls', 'bash', 'notebookedit'];
333
+ const toolName = event.tool_name ? event.tool_name.toLowerCase() : '';
334
+
335
+ // Also check if Bash commands involve file operations
336
+ if (toolName === 'bash' && event.tool_parameters) {
337
+ const command = event.tool_parameters.command || '';
338
+ // Check for common file operations in bash commands
339
+ if (command.match(/\b(cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find)\b/)) {
340
+ return true;
341
+ }
342
+ }
343
+
344
+ return toolName && fileTools.includes(toolName);
333
345
  }
334
346
 
335
347
  /**
@@ -341,11 +353,28 @@ class FileToolTracker {
341
353
  // Try various locations where file path might be stored
342
354
  if (event.tool_parameters?.file_path) return event.tool_parameters.file_path;
343
355
  if (event.tool_parameters?.path) return event.tool_parameters.path;
356
+ if (event.tool_parameters?.notebook_path) return event.tool_parameters.notebook_path;
344
357
  if (event.data?.tool_parameters?.file_path) return event.data.tool_parameters.file_path;
345
358
  if (event.data?.tool_parameters?.path) return event.data.tool_parameters.path;
359
+ if (event.data?.tool_parameters?.notebook_path) return event.data.tool_parameters.notebook_path;
346
360
  if (event.file_path) return event.file_path;
347
361
  if (event.path) return event.path;
348
362
 
363
+ // For Glob tool, use the pattern as a pseudo-path
364
+ if (event.tool_name?.toLowerCase() === 'glob' && event.tool_parameters?.pattern) {
365
+ return `[glob] ${event.tool_parameters.pattern}`;
366
+ }
367
+
368
+ // For Bash commands, try to extract file paths from the command
369
+ if (event.tool_name?.toLowerCase() === 'bash' && event.tool_parameters?.command) {
370
+ const command = event.tool_parameters.command;
371
+ // Try to extract file paths from common patterns
372
+ const fileMatch = command.match(/(?:cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find|echo.*>|sed|awk|grep)\s+([^\s;|&]+)/);
373
+ if (fileMatch && fileMatch[1]) {
374
+ return fileMatch[1];
375
+ }
376
+ }
377
+
349
378
  return null;
350
379
  }
351
380
 
@@ -383,7 +412,22 @@ class FileToolTracker {
383
412
  case 'write': return 'write';
384
413
  case 'edit': return 'edit';
385
414
  case 'multiedit': return 'edit';
415
+ case 'notebookedit': return 'edit';
386
416
  case 'grep': return 'search';
417
+ case 'glob': return 'search';
418
+ case 'ls': return 'list';
419
+ case 'bash':
420
+ // Check bash command for file operation type
421
+ const command = event.tool_parameters?.command || '';
422
+ if (command.match(/\b(cat|less|more|head|tail)\b/)) return 'read';
423
+ if (command.match(/\b(touch|echo.*>|tee)\b/)) return 'write';
424
+ if (command.match(/\b(sed|awk)\b/)) return 'edit';
425
+ if (command.match(/\b(grep|find)\b/)) return 'search';
426
+ if (command.match(/\b(ls|dir)\b/)) return 'list';
427
+ if (command.match(/\b(mv|cp)\b/)) return 'copy/move';
428
+ if (command.match(/\b(rm|rmdir)\b/)) return 'delete';
429
+ if (command.match(/\b(mkdir)\b/)) return 'create';
430
+ return 'bash';
387
431
  default: return toolName;
388
432
  }
389
433
  }
@@ -334,11 +334,11 @@
334
334
  <!-- New modular components -->
335
335
  <script src="/static/js/components/socket-manager.js"></script>
336
336
  <script src="/static/js/components/agent-inference.js"></script>
337
- <script src="/static/js/components/ui-state-manager.js"></script>
338
- <script src="/static/js/components/working-directory.js"></script>
339
- <script src="/static/js/components/file-tool-tracker.js"></script>
340
- <script src="/static/js/components/event-processor.js"></script>
341
- <script src="/static/js/components/export-manager.js"></script>
337
+ <script src="/static/js/components/ui-state-manager.js?v=1.0"></script>
338
+ <script src="/static/js/components/working-directory.js?v=1.0"></script>
339
+ <script src="/static/js/components/file-tool-tracker.js?v=1.1"></script>
340
+ <script src="/static/js/components/event-processor.js?v=1.0"></script>
341
+ <script src="/static/js/components/export-manager.js?v=1.0"></script>
342
342
 
343
343
  <!-- Main dashboard coordinator -->
344
344
  <script src="/static/js/dashboard.js"></script>
@@ -744,10 +744,14 @@ class AgentDeploymentService:
744
744
  agent_id = template_data.get('agent_id', agent_name)
745
745
  display_name = template_data.get('metadata', {}).get('name', agent_id)
746
746
 
747
+ # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
748
+ # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
749
+ claude_code_name = agent_id.replace('_', '-').lower()
750
+
747
751
  # Build frontmatter with only the fields Claude Code uses
748
752
  frontmatter_lines = [
749
753
  "---",
750
- f"name: {agent_id}",
754
+ f"name: {claude_code_name}",
751
755
  f"description: {description}",
752
756
  f"version: {version_string}",
753
757
  f"base_version: {self._format_version_display(base_version)}",
@@ -462,10 +462,14 @@ class AsyncAgentDeploymentService:
462
462
  # IMPORTANT: No spaces after commas - Claude Code requires exact format
463
463
  tools_str = ','.join(tools) if isinstance(tools, list) else str(tools)
464
464
 
465
+ # Convert agent_id to Claude Code compatible name (replace underscores with hyphens)
466
+ # Claude Code requires name to match pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
467
+ claude_code_name = agent_id.replace('_', '-').lower()
468
+
465
469
  # Build frontmatter with only the fields Claude Code uses
466
470
  frontmatter_lines = [
467
471
  "---",
468
- f"name: {agent_id}",
472
+ f"name: {claude_code_name}",
469
473
  f"description: {description}",
470
474
  f"version: {version_string}",
471
475
  f"base_version: {self._format_version_display(base_version)}",
@@ -116,9 +116,12 @@ class AgentCapabilitiesGenerator:
116
116
  if len(capability_text) > 100:
117
117
  capability_text = capability_text[:97] + '...'
118
118
 
119
+ # Clean up the agent name for TodoWrite usage
120
+ clean_name = agent['name'].replace(' Agent', '').replace('-', ' ')
121
+
119
122
  capabilities.append({
120
- 'name': agent['name'],
121
- 'id': agent['id'],
123
+ 'name': clean_name, # Clean name for TodoWrite
124
+ 'id': agent['id'], # Agent ID for Task tool
122
125
  'capability_text': capability_text,
123
126
  'tools': ', '.join(agent.get('tools', [])[:5]) # First 5 tools
124
127
  })
@@ -132,26 +135,33 @@ class AgentCapabilitiesGenerator:
132
135
  Configured Jinja2 template
133
136
  """
134
137
  template_content = """
135
- ## Agent Names & Capabilities
136
- **Core Agents**: {{ core_agents }}
138
+ ## Available Agent Capabilities
139
+
140
+ You have the following specialized agents available for delegation:
137
141
 
138
142
  {% if agents_by_tier.project %}
139
143
  ### Project-Specific Agents
140
144
  {% for agent in agents_by_tier.project %}
141
- - **{{ agent.name }}** ({{ agent.id }}): {{ agent.description }}
145
+ - **{{ agent.name|replace(' Agent', '')|replace('-', ' ') }}** (`{{ agent.id }}`): {{ agent.description }}
142
146
  {% endfor %}
143
147
 
144
148
  {% endif %}
145
- **Agent Capabilities**:
149
+ ### Engineering Agents
146
150
  {% for cap in detailed_capabilities %}
147
- - **{{ cap.name }}**: {{ cap.capability_text }}
151
+ {% if cap.id in ['engineer', 'data_engineer', 'documentation', 'ops', 'security', 'ticketing', 'version_control', 'web_ui'] %}
152
+ - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
153
+ {% endif %}
148
154
  {% endfor %}
149
155
 
150
- **Agent Name Formats** (both valid):
151
- - Capitalized: {{ detailed_capabilities | map(attribute='name') | join('", "') }}
152
- - Lowercase-hyphenated: {{ detailed_capabilities | map(attribute='id') | join('", "') }}
156
+ ### Research Agents
157
+ {% for cap in detailed_capabilities %}
158
+ {% if cap.id in ['code_analyzer', 'qa', 'research', 'web_qa'] %}
159
+ - **{{ cap.name }}** (`{{ cap.id }}`): {{ cap.capability_text }}
160
+ {% endif %}
161
+ {% endfor %}
153
162
 
154
- *Generated from {{ total_agents }} deployed agents*
163
+ **Total Available Agents**: {{ total_agents }}
164
+ Use the agent ID in parentheses when delegating tasks via the Task tool.
155
165
  """.strip()
156
166
 
157
167
  return Template(template_content)
@@ -44,21 +44,28 @@ class TicketManager:
44
44
  (tickets_dir / "tasks").mkdir(exist_ok=True)
45
45
  self.logger.info(f"Created tickets directory structure at: {tickets_dir}")
46
46
 
47
- # Check if we need to configure ai-trackdown
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
- # Create default config that uses tickets/ directory
51
- config = Config.create_default(config_file)
52
- config.set("paths.tickets_dir", "tickets")
53
- config.set("paths.epics_dir", "tickets/epics")
54
- config.set("paths.issues_dir", "tickets/issues")
55
- config.set("paths.tasks_dir", "tickets/tasks")
56
- config.save()
57
- self.logger.info("Created .trackdown.yaml configuration")
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
- self.logger.error("ai-trackdown-pytools not installed")
73
- self.logger.info("Install with: pip install ai-trackdown-pytools")
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"Failed to initialize TaskManager: {e}")
77
- self.logger.debug(f"Error details: {str(e)}", exc_info=True)
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
- self.logger.info(f"Created ticket: {task.id} - {title}")
143
- return task.id
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"Failed to create ticket: {e}")
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
- tickets.append({
168
- 'id': task.id,
169
- 'title': task.title,
170
- 'status': task.status,
171
- 'priority': task.priority,
172
- 'tags': task.tags,
173
- 'created_at': task.created_at,
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
- return {
199
- 'id': task.id,
200
- 'title': task.title,
201
- 'description': task.description,
202
- 'status': task.status,
203
- 'priority': task.priority,
204
- 'tags': task.tags,
205
- 'assignees': task.assignees,
206
- 'created_at': task.created_at,
207
- 'updated_at': task.updated_at,
208
- 'metadata': task.metadata,
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
- cmd = [sys.executable, "-m", "pip", "install"] + compatible
305
- logger.info(f"Installing {len(compatible)} compatible dependencies...")
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
- result = subprocess.run(
310
- cmd,
311
- capture_output=True,
312
- text=True,
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
- if result.returncode == 0:
317
- logger.info("Successfully installed compatible dependencies")
318
- if incompatible:
319
- return True, f"Installed {len(compatible)} packages, skipped {len(incompatible)} incompatible"
320
- return True, ""
321
- else:
322
- error_msg = f"Installation failed: {result.stderr}"
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