claude-mpm 4.1.4__py3-none-any.whl → 4.1.6__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 (81) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/research.json +39 -13
  3. claude_mpm/cli/__init__.py +2 -0
  4. claude_mpm/cli/commands/__init__.py +2 -0
  5. claude_mpm/cli/commands/configure.py +1221 -0
  6. claude_mpm/cli/commands/configure_tui.py +1921 -0
  7. claude_mpm/cli/commands/tickets.py +365 -784
  8. claude_mpm/cli/parsers/base_parser.py +7 -0
  9. claude_mpm/cli/parsers/configure_parser.py +119 -0
  10. claude_mpm/cli/startup_logging.py +39 -12
  11. claude_mpm/constants.py +1 -0
  12. claude_mpm/core/output_style_manager.py +24 -0
  13. claude_mpm/core/socketio_pool.py +35 -3
  14. claude_mpm/core/unified_agent_registry.py +46 -15
  15. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  16. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  17. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  18. claude_mpm/dashboard/templates/index.html +11 -0
  19. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  20. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  21. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  22. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  23. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  24. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  25. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  26. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  27. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  28. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  29. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  30. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  31. claude_mpm/services/event_bus/direct_relay.py +173 -0
  32. claude_mpm/services/infrastructure/__init__.py +31 -5
  33. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  34. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  35. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  36. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  37. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  38. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  39. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  40. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  41. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  42. claude_mpm/services/project/analyzer.py +13 -4
  43. claude_mpm/services/project/analyzer_refactored.py +450 -0
  44. claude_mpm/services/project/analyzer_v2.py +566 -0
  45. claude_mpm/services/project/architecture_analyzer.py +461 -0
  46. claude_mpm/services/project/dependency_analyzer.py +462 -0
  47. claude_mpm/services/project/language_analyzer.py +265 -0
  48. claude_mpm/services/project/metrics_collector.py +410 -0
  49. claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
  50. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  51. claude_mpm/services/socketio/server/connection_manager.py +516 -0
  52. claude_mpm/services/socketio/server/core.py +63 -0
  53. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  54. claude_mpm/services/socketio/server/main.py +27 -1
  55. claude_mpm/services/ticket_manager.py +5 -1
  56. claude_mpm/services/ticket_services/__init__.py +26 -0
  57. claude_mpm/services/ticket_services/crud_service.py +328 -0
  58. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  59. claude_mpm/services/ticket_services/search_service.py +324 -0
  60. claude_mpm/services/ticket_services/validation_service.py +303 -0
  61. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  62. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
  63. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +67 -46
  64. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  65. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  66. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  67. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  68. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  69. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  70. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  71. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  72. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  73. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  74. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  75. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  76. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  77. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  78. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
  79. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
  80. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
  81. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/top_level.txt +0 -0
@@ -35,6 +35,7 @@ from ....core.logging_config import get_logger
35
35
  from ...core.interfaces.communication import SocketIOServiceInterface
36
36
  from ..handlers import EventHandlerRegistry, FileEventHandler, GitEventHandler
37
37
  from .broadcaster import SocketIOEventBroadcaster
38
+ from .connection_manager import ConnectionManager
38
39
  from .core import SocketIOServerCore
39
40
  from .eventbus_integration import EventBusIntegration
40
41
 
@@ -91,6 +92,9 @@ class SocketIOServer(SocketIOServiceInterface):
91
92
  # EventBus integration
92
93
  self.eventbus_integration = None
93
94
 
95
+ # Connection manager for robust connection tracking
96
+ self.connection_manager = None
97
+
94
98
  def start_sync(self):
95
99
  """Start the Socket.IO server in a background thread (synchronous version)."""
96
100
  if not SOCKETIO_AVAILABLE:
@@ -110,7 +114,13 @@ class SocketIOServer(SocketIOServiceInterface):
110
114
  # Start the core server
111
115
  self.core.start_sync()
112
116
 
113
- # Initialize broadcaster with core server components
117
+ # Initialize connection manager for robust connection tracking
118
+ self.connection_manager = ConnectionManager(
119
+ max_buffer_size=getattr(SystemLimits, "MAX_EVENTS_BUFFER", 1000),
120
+ event_ttl=300, # 5 minutes TTL for buffered events
121
+ )
122
+
123
+ # Initialize broadcaster with core server components and connection manager
114
124
  self.broadcaster = SocketIOEventBroadcaster(
115
125
  sio=self.core.sio,
116
126
  connected_clients=self.connected_clients,
@@ -119,6 +129,7 @@ class SocketIOServer(SocketIOServiceInterface):
119
129
  stats=self.stats,
120
130
  logger=self.logger,
121
131
  server=self, # Pass server reference for event history access
132
+ connection_manager=self.connection_manager, # Pass connection manager
122
133
  )
123
134
 
124
135
  # Wait for the event loop to be initialized in the background thread
@@ -146,6 +157,12 @@ class SocketIOServer(SocketIOServiceInterface):
146
157
  # Start the retry processor for resilient event delivery
147
158
  self.broadcaster.start_retry_processor()
148
159
 
160
+ # Start connection health monitoring
161
+ if self.connection_manager:
162
+ asyncio.run_coroutine_threadsafe(
163
+ self.connection_manager.start_health_monitoring(), self.core.loop
164
+ )
165
+
149
166
  # Register events
150
167
  self._register_events()
151
168
 
@@ -200,6 +217,15 @@ class SocketIOServer(SocketIOServiceInterface):
200
217
  if self.broadcaster:
201
218
  self.broadcaster.stop_retry_processor()
202
219
 
220
+ # Stop connection health monitoring
221
+ if self.connection_manager and self.core.loop:
222
+ try:
223
+ asyncio.run_coroutine_threadsafe(
224
+ self.connection_manager.stop_health_monitoring(), self.core.loop
225
+ ).result(timeout=2)
226
+ except Exception as e:
227
+ self.logger.warning(f"Error stopping health monitoring: {e}")
228
+
203
229
  # Teardown EventBus integration
204
230
  if self.eventbus_integration:
205
231
  try:
@@ -6,7 +6,11 @@ class TicketManager:
6
6
 
7
7
  def create_task(self, *args, **kwargs):
8
8
  """Stub method."""
9
- return
9
+ return "TSK-STUB-001" # Return a stub ticket ID
10
+
11
+ def create_ticket(self, *args, **kwargs):
12
+ """Stub method - alias for create_task."""
13
+ return self.create_task(*args, **kwargs)
10
14
 
11
15
  def list_recent_tickets(self, *args, **kwargs):
12
16
  """Stub method."""
@@ -0,0 +1,26 @@
1
+ """
2
+ Ticket services for clean separation of concerns.
3
+
4
+ WHY: Extract business logic from the god class TicketsCommand into
5
+ focused, testable service modules following SOLID principles.
6
+
7
+ DESIGN DECISIONS:
8
+ - Each service has a single responsibility
9
+ - Services use dependency injection for flexibility
10
+ - Clear interfaces for each service
11
+ - Services are stateless for better testability
12
+ """
13
+
14
+ from .crud_service import TicketCRUDService
15
+ from .formatter_service import TicketFormatterService
16
+ from .search_service import TicketSearchService
17
+ from .validation_service import TicketValidationService
18
+ from .workflow_service import TicketWorkflowService
19
+
20
+ __all__ = [
21
+ "TicketCRUDService",
22
+ "TicketFormatterService",
23
+ "TicketSearchService",
24
+ "TicketValidationService",
25
+ "TicketWorkflowService",
26
+ ]
@@ -0,0 +1,328 @@
1
+ """
2
+ CRUD operations service for tickets.
3
+
4
+ WHY: Centralizes all Create, Read, Update, Delete operations for tickets,
5
+ separating data access from presentation logic.
6
+
7
+ DESIGN DECISIONS:
8
+ - Uses TicketManager as backend (can be replaced with actual implementation)
9
+ - Returns standardized response objects
10
+ - Handles aitrackdown CLI fallback for operations not in TicketManager
11
+ - Provides consistent error handling
12
+ """
13
+
14
+ import json
15
+ import subprocess
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ from ...core.logger import get_logger
19
+
20
+
21
+ class TicketCRUDService:
22
+ """Service for ticket CRUD operations."""
23
+
24
+ def __init__(self, ticket_manager=None):
25
+ """
26
+ Initialize the CRUD service.
27
+
28
+ Args:
29
+ ticket_manager: Optional ticket manager instance for testing
30
+ """
31
+ self.logger = get_logger("services.ticket_crud")
32
+ self._ticket_manager = ticket_manager
33
+
34
+ @property
35
+ def ticket_manager(self):
36
+ """Lazy load ticket manager."""
37
+ if self._ticket_manager is None:
38
+ try:
39
+ from ..ticket_manager import TicketManager
40
+ except ImportError:
41
+ from claude_mpm.services.ticket_manager import TicketManager
42
+ self._ticket_manager = TicketManager()
43
+ return self._ticket_manager
44
+
45
+ def create_ticket(
46
+ self,
47
+ title: str,
48
+ ticket_type: str = "task",
49
+ priority: str = "medium",
50
+ description: str = "",
51
+ tags: Optional[List[str]] = None,
52
+ parent_epic: Optional[str] = None,
53
+ parent_issue: Optional[str] = None,
54
+ ) -> Dict[str, Any]:
55
+ """
56
+ Create a new ticket.
57
+
58
+ Returns:
59
+ Dict with success status and ticket_id or error message
60
+ """
61
+ try:
62
+ ticket_id = self.ticket_manager.create_ticket(
63
+ title=title,
64
+ ticket_type=ticket_type,
65
+ description=description,
66
+ priority=priority,
67
+ tags=tags or [],
68
+ source="claude-mpm-cli",
69
+ parent_epic=parent_epic,
70
+ parent_issue=parent_issue,
71
+ )
72
+
73
+ if ticket_id:
74
+ return {
75
+ "success": True,
76
+ "ticket_id": ticket_id,
77
+ "message": f"Created ticket: {ticket_id}",
78
+ }
79
+ return {"success": False, "error": "Failed to create ticket"}
80
+ except Exception as e:
81
+ self.logger.error(f"Error creating ticket: {e}")
82
+ return {"success": False, "error": str(e)}
83
+
84
+ def get_ticket(self, ticket_id: str) -> Optional[Dict[str, Any]]:
85
+ """
86
+ Get a specific ticket by ID.
87
+
88
+ Returns:
89
+ Ticket data or None if not found
90
+ """
91
+ try:
92
+ return self.ticket_manager.get_ticket(ticket_id)
93
+ except Exception as e:
94
+ self.logger.error(f"Error getting ticket {ticket_id}: {e}")
95
+ return None
96
+
97
+ def list_tickets(
98
+ self,
99
+ limit: int = 20,
100
+ page: int = 1,
101
+ page_size: int = 20,
102
+ type_filter: str = "all",
103
+ status_filter: str = "all",
104
+ ) -> Dict[str, Any]:
105
+ """
106
+ List tickets with pagination and filtering.
107
+
108
+ Returns:
109
+ Dict with tickets list and pagination info
110
+ """
111
+ try:
112
+ # Try aitrackdown CLI first for better pagination
113
+ tickets = self._list_via_aitrackdown(
114
+ limit, page, page_size, type_filter, status_filter
115
+ )
116
+
117
+ if tickets is None:
118
+ # Fallback to TicketManager
119
+ tickets = self._list_via_manager(
120
+ limit, page, page_size, type_filter, status_filter
121
+ )
122
+
123
+ return {
124
+ "success": True,
125
+ "tickets": tickets,
126
+ "page": page,
127
+ "page_size": page_size,
128
+ "total_shown": len(tickets),
129
+ }
130
+ except Exception as e:
131
+ self.logger.error(f"Error listing tickets: {e}")
132
+ return {"success": False, "error": str(e), "tickets": []}
133
+
134
+ def _list_via_aitrackdown(
135
+ self,
136
+ limit: int,
137
+ page: int,
138
+ page_size: int,
139
+ type_filter: str,
140
+ status_filter: str,
141
+ ) -> Optional[List[Dict]]:
142
+ """List tickets using aitrackdown CLI."""
143
+ try:
144
+ cmd = ["aitrackdown", "status", "tasks"]
145
+
146
+ # Calculate offset for pagination
147
+ offset = (page - 1) * page_size
148
+ total_needed = offset + page_size
149
+ cmd.extend(["--limit", str(total_needed * 2)])
150
+
151
+ # Add filters
152
+ if type_filter != "all":
153
+ cmd.extend(["--type", type_filter])
154
+ if status_filter != "all":
155
+ cmd.extend(["--status", status_filter])
156
+
157
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
158
+
159
+ if result.stdout.strip():
160
+ all_tickets = json.loads(result.stdout)
161
+ if isinstance(all_tickets, list):
162
+ # Apply pagination
163
+ return all_tickets[offset : offset + page_size]
164
+ return []
165
+ except (
166
+ subprocess.CalledProcessError,
167
+ FileNotFoundError,
168
+ json.JSONDecodeError,
169
+ ) as e:
170
+ self.logger.debug(f"aitrackdown not available: {e}")
171
+ return None
172
+
173
+ def _list_via_manager(
174
+ self,
175
+ limit: int,
176
+ page: int,
177
+ page_size: int,
178
+ type_filter: str,
179
+ status_filter: str,
180
+ ) -> List[Dict]:
181
+ """List tickets using TicketManager."""
182
+ all_tickets = self.ticket_manager.list_recent_tickets(limit=limit * 2)
183
+
184
+ # Apply filters
185
+ filtered_tickets = []
186
+ for ticket in all_tickets:
187
+ if type_filter != "all":
188
+ ticket_type = ticket.get("metadata", {}).get("ticket_type", "unknown")
189
+ if ticket_type != type_filter:
190
+ continue
191
+
192
+ if status_filter != "all":
193
+ if ticket.get("status") != status_filter:
194
+ continue
195
+
196
+ filtered_tickets.append(ticket)
197
+
198
+ # Apply pagination
199
+ offset = (page - 1) * page_size
200
+ return filtered_tickets[offset : offset + page_size]
201
+
202
+ def update_ticket(
203
+ self,
204
+ ticket_id: str,
205
+ status: Optional[str] = None,
206
+ priority: Optional[str] = None,
207
+ description: Optional[str] = None,
208
+ tags: Optional[List[str]] = None,
209
+ assignees: Optional[List[str]] = None,
210
+ ) -> Dict[str, Any]:
211
+ """
212
+ Update a ticket's properties.
213
+
214
+ Returns:
215
+ Dict with success status and message
216
+ """
217
+ try:
218
+ updates = {}
219
+ if status:
220
+ updates["status"] = status
221
+ if priority:
222
+ updates["priority"] = priority
223
+ if description:
224
+ updates["description"] = description
225
+ if tags:
226
+ updates["tags"] = tags
227
+ if assignees:
228
+ updates["assignees"] = assignees
229
+
230
+ if not updates:
231
+ return {"success": False, "error": "No updates specified"}
232
+
233
+ # Try TicketManager first
234
+ success = self.ticket_manager.update_task(ticket_id, **updates)
235
+
236
+ if success:
237
+ return {"success": True, "message": f"Updated ticket: {ticket_id}"}
238
+
239
+ # Fallback to aitrackdown CLI for status transitions
240
+ if status:
241
+ return self._update_via_aitrackdown(ticket_id, status, updates)
242
+
243
+ return {"success": False, "error": f"Failed to update ticket: {ticket_id}"}
244
+ except Exception as e:
245
+ self.logger.error(f"Error updating ticket {ticket_id}: {e}")
246
+ return {"success": False, "error": str(e)}
247
+
248
+ def _update_via_aitrackdown(
249
+ self, ticket_id: str, status: str, updates: Dict
250
+ ) -> Dict[str, Any]:
251
+ """Update ticket using aitrackdown CLI."""
252
+ try:
253
+ cmd = ["aitrackdown", "transition", ticket_id, status]
254
+
255
+ # Add comment with other updates
256
+ comment_parts = []
257
+ if updates.get("priority"):
258
+ comment_parts.append(f"Priority: {updates['priority']}")
259
+ if updates.get("assignees"):
260
+ comment_parts.append(f"Assigned to: {', '.join(updates['assignees'])}")
261
+ if updates.get("tags"):
262
+ comment_parts.append(f"Tags: {', '.join(updates['tags'])}")
263
+
264
+ if comment_parts:
265
+ comment = " | ".join(comment_parts)
266
+ cmd.extend(["--comment", comment])
267
+
268
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
269
+ return {"success": True, "message": f"Updated ticket: {ticket_id}"}
270
+ except subprocess.CalledProcessError as e:
271
+ self.logger.error(f"Failed to update via CLI: {e}")
272
+ return {"success": False, "error": f"Failed to update ticket: {ticket_id}"}
273
+
274
+ def close_ticket(
275
+ self, ticket_id: str, resolution: Optional[str] = None
276
+ ) -> Dict[str, Any]:
277
+ """
278
+ Close a ticket.
279
+
280
+ Returns:
281
+ Dict with success status and message
282
+ """
283
+ try:
284
+ # Try TicketManager first
285
+ success = self.ticket_manager.close_task(ticket_id, resolution=resolution)
286
+
287
+ if success:
288
+ return {"success": True, "message": f"Closed ticket: {ticket_id}"}
289
+
290
+ # Fallback to aitrackdown CLI
291
+ return self._close_via_aitrackdown(ticket_id, resolution)
292
+ except Exception as e:
293
+ self.logger.error(f"Error closing ticket {ticket_id}: {e}")
294
+ return {"success": False, "error": str(e)}
295
+
296
+ def _close_via_aitrackdown(
297
+ self, ticket_id: str, resolution: Optional[str]
298
+ ) -> Dict[str, Any]:
299
+ """Close ticket using aitrackdown CLI."""
300
+ try:
301
+ cmd = ["aitrackdown", "close", ticket_id]
302
+ if resolution:
303
+ cmd.extend(["--comment", resolution])
304
+
305
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
306
+ return {"success": True, "message": f"Closed ticket: {ticket_id}"}
307
+ except subprocess.CalledProcessError:
308
+ return {"success": False, "error": f"Failed to close ticket: {ticket_id}"}
309
+
310
+ def delete_ticket(self, ticket_id: str, force: bool = False) -> Dict[str, Any]:
311
+ """
312
+ Delete a ticket.
313
+
314
+ Returns:
315
+ Dict with success status and message
316
+ """
317
+ try:
318
+ cmd = ["aitrackdown", "delete", ticket_id]
319
+ if force:
320
+ cmd.append("--force")
321
+
322
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
323
+ return {"success": True, "message": f"Deleted ticket: {ticket_id}"}
324
+ except subprocess.CalledProcessError:
325
+ return {"success": False, "error": f"Failed to delete ticket: {ticket_id}"}
326
+ except Exception as e:
327
+ self.logger.error(f"Error deleting ticket {ticket_id}: {e}")
328
+ return {"success": False, "error": str(e)}