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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/research.json +39 -13
- claude_mpm/cli/__init__.py +2 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/configure.py +1221 -0
- claude_mpm/cli/commands/configure_tui.py +1921 -0
- claude_mpm/cli/commands/tickets.py +365 -784
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/configure_parser.py +119 -0
- claude_mpm/cli/startup_logging.py +39 -12
- claude_mpm/constants.py +1 -0
- claude_mpm/core/output_style_manager.py +24 -0
- claude_mpm/core/socketio_pool.py +35 -3
- claude_mpm/core/unified_agent_registry.py +46 -15
- claude_mpm/dashboard/static/css/connection-status.css +370 -0
- claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/js/connection-manager.js +536 -0
- claude_mpm/dashboard/templates/index.html +11 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
- claude_mpm/services/event_bus/direct_relay.py +173 -0
- claude_mpm/services/infrastructure/__init__.py +31 -5
- claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
- claude_mpm/services/infrastructure/monitoring/base.py +130 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +218 -0
- claude_mpm/services/infrastructure/monitoring/process.py +342 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
- claude_mpm/services/infrastructure/monitoring/service.py +367 -0
- claude_mpm/services/infrastructure/monitoring.py +67 -1030
- claude_mpm/services/project/analyzer.py +13 -4
- claude_mpm/services/project/analyzer_refactored.py +450 -0
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +410 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/connection_manager.py +516 -0
- claude_mpm/services/socketio/server/core.py +63 -0
- claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
- claude_mpm/services/socketio/server/main.py +27 -1
- claude_mpm/services/ticket_manager.py +5 -1
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +67 -46
- claude_mpm/agents/OUTPUT_STYLE.md +0 -73
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
- claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
- claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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)}
|