claude-mpm 0.3.0__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ticketing Service
|
|
4
|
+
=================
|
|
5
|
+
|
|
6
|
+
Core service that wraps ai-trackdown-pytools for simplified ticket management.
|
|
7
|
+
Provides a clean interface for PM orchestration and agent ticket operations.
|
|
8
|
+
|
|
9
|
+
Key Features:
|
|
10
|
+
- Singleton pattern for consistent ticket management
|
|
11
|
+
- Simplified API wrapping ai-trackdown-pytools
|
|
12
|
+
- Automatic ticket directory management
|
|
13
|
+
- Thread-safe operations
|
|
14
|
+
- Comprehensive error handling and logging
|
|
15
|
+
- Integration with Claude PM Framework
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
from claude_pm.services.ticketing_service import TicketingService
|
|
19
|
+
|
|
20
|
+
# Get singleton instance
|
|
21
|
+
ticketing = TicketingService.get_instance()
|
|
22
|
+
|
|
23
|
+
# Create a ticket
|
|
24
|
+
ticket = ticketing.create_ticket(
|
|
25
|
+
title="Implement new feature",
|
|
26
|
+
description="Detailed description",
|
|
27
|
+
priority="high"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# List tickets
|
|
31
|
+
tickets = ticketing.list_tickets(status="open")
|
|
32
|
+
|
|
33
|
+
# Update ticket
|
|
34
|
+
ticketing.update_ticket("CLAUDE-001", status="in_progress")
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import asyncio
|
|
38
|
+
import json
|
|
39
|
+
import logging
|
|
40
|
+
import os
|
|
41
|
+
import threading
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from datetime import datetime
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Any, Dict, List, Optional, Union
|
|
46
|
+
|
|
47
|
+
# Import ai-trackdown-pytools
|
|
48
|
+
try:
|
|
49
|
+
from ai_trackdown_pytools import Task, Project
|
|
50
|
+
from ai_trackdown_pytools.core.task import TaskManager
|
|
51
|
+
AI_TRACKDOWN_AVAILABLE = True
|
|
52
|
+
# Map to expected names
|
|
53
|
+
Ticket = Task
|
|
54
|
+
TicketManager = TaskManager
|
|
55
|
+
except ImportError:
|
|
56
|
+
AI_TRACKDOWN_AVAILABLE = False
|
|
57
|
+
# Define fallback classes for type hints
|
|
58
|
+
class TicketStatus:
|
|
59
|
+
OPEN = "open"
|
|
60
|
+
IN_PROGRESS = "in_progress"
|
|
61
|
+
RESOLVED = "resolved"
|
|
62
|
+
CLOSED = "closed"
|
|
63
|
+
|
|
64
|
+
class TicketPriority:
|
|
65
|
+
LOW = "low"
|
|
66
|
+
MEDIUM = "medium"
|
|
67
|
+
HIGH = "high"
|
|
68
|
+
CRITICAL = "critical"
|
|
69
|
+
|
|
70
|
+
import logging
|
|
71
|
+
|
|
72
|
+
logger = logging.getLogger(__name__)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class TicketData:
|
|
77
|
+
"""Simplified ticket data structure for easy use."""
|
|
78
|
+
id: str
|
|
79
|
+
title: str
|
|
80
|
+
description: str
|
|
81
|
+
status: str = "open"
|
|
82
|
+
priority: str = "medium"
|
|
83
|
+
assignee: Optional[str] = None
|
|
84
|
+
labels: List[str] = field(default_factory=list)
|
|
85
|
+
created_at: Optional[datetime] = None
|
|
86
|
+
updated_at: Optional[datetime] = None
|
|
87
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TicketingService:
|
|
91
|
+
"""
|
|
92
|
+
Core ticketing service wrapping ai-trackdown-pytools.
|
|
93
|
+
|
|
94
|
+
Provides simplified interface for ticket management operations
|
|
95
|
+
within the Claude PM Framework.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
_instance = None
|
|
99
|
+
_lock = threading.Lock()
|
|
100
|
+
|
|
101
|
+
def __new__(cls):
|
|
102
|
+
"""Implement singleton pattern."""
|
|
103
|
+
if cls._instance is None:
|
|
104
|
+
with cls._lock:
|
|
105
|
+
if cls._instance is None:
|
|
106
|
+
cls._instance = super().__new__(cls)
|
|
107
|
+
return cls._instance
|
|
108
|
+
|
|
109
|
+
def __init__(self):
|
|
110
|
+
"""Initialize the ticketing service."""
|
|
111
|
+
if not hasattr(self, '_initialized'):
|
|
112
|
+
self._initialized = True
|
|
113
|
+
self._ticket_manager = None
|
|
114
|
+
self._tickets_dir = None
|
|
115
|
+
self._setup_service()
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def get_instance(cls) -> 'TicketingService':
|
|
119
|
+
"""Get singleton instance of TicketingService."""
|
|
120
|
+
return cls()
|
|
121
|
+
|
|
122
|
+
def _setup_service(self):
|
|
123
|
+
"""Set up the ticketing service."""
|
|
124
|
+
try:
|
|
125
|
+
# Find or create tickets directory
|
|
126
|
+
self._tickets_dir = self._find_tickets_directory()
|
|
127
|
+
|
|
128
|
+
if AI_TRACKDOWN_AVAILABLE:
|
|
129
|
+
# Initialize ai-trackdown ticket manager
|
|
130
|
+
# TaskManager expects project_path (parent of tickets directory)
|
|
131
|
+
project_path = self._tickets_dir.parent
|
|
132
|
+
self._ticket_manager = TicketManager(project_path)
|
|
133
|
+
logger.info(f"Ticketing service initialized with project path: {project_path}")
|
|
134
|
+
else:
|
|
135
|
+
logger.warning("ai-trackdown-pytools not available, using stub implementation")
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logger.error(f"Failed to initialize ticketing service: {e}")
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
def _find_tickets_directory(self) -> Path:
|
|
142
|
+
"""Find or create the tickets directory."""
|
|
143
|
+
# Check current directory first
|
|
144
|
+
current_dir = Path.cwd()
|
|
145
|
+
tickets_path = current_dir / "tickets"
|
|
146
|
+
|
|
147
|
+
if tickets_path.exists():
|
|
148
|
+
return tickets_path
|
|
149
|
+
|
|
150
|
+
# Check for .claude-pm directory
|
|
151
|
+
claude_pm_dir = current_dir / ".claude-pm"
|
|
152
|
+
if claude_pm_dir.exists():
|
|
153
|
+
tickets_path = claude_pm_dir / "tickets"
|
|
154
|
+
tickets_path.mkdir(exist_ok=True)
|
|
155
|
+
return tickets_path
|
|
156
|
+
|
|
157
|
+
# Create in current directory
|
|
158
|
+
tickets_path.mkdir(exist_ok=True)
|
|
159
|
+
return tickets_path
|
|
160
|
+
|
|
161
|
+
# Core ticket operations
|
|
162
|
+
|
|
163
|
+
def create_ticket(
|
|
164
|
+
self,
|
|
165
|
+
title: str,
|
|
166
|
+
description: str,
|
|
167
|
+
priority: str = "medium",
|
|
168
|
+
assignee: Optional[str] = None,
|
|
169
|
+
labels: Optional[List[str]] = None,
|
|
170
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
171
|
+
) -> TicketData:
|
|
172
|
+
"""
|
|
173
|
+
Create a new ticket.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
title: Ticket title
|
|
177
|
+
description: Detailed description
|
|
178
|
+
priority: Priority level (low, medium, high, critical)
|
|
179
|
+
assignee: Optional assignee
|
|
180
|
+
labels: Optional list of labels
|
|
181
|
+
metadata: Optional metadata dictionary
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
TicketData object with created ticket information
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
188
|
+
# Stub implementation
|
|
189
|
+
ticket_id = f"CLAUDE-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
|
190
|
+
return TicketData(
|
|
191
|
+
id=ticket_id,
|
|
192
|
+
title=title,
|
|
193
|
+
description=description,
|
|
194
|
+
status="open",
|
|
195
|
+
priority=priority,
|
|
196
|
+
assignee=assignee,
|
|
197
|
+
labels=labels or [],
|
|
198
|
+
created_at=datetime.now(),
|
|
199
|
+
updated_at=datetime.now(),
|
|
200
|
+
metadata=metadata or {}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Use ai-trackdown to create task
|
|
204
|
+
# create_task expects: title, description, status, priority, assignees, tags, metadata
|
|
205
|
+
task_data = {
|
|
206
|
+
'title': title,
|
|
207
|
+
'description': description,
|
|
208
|
+
'status': 'open',
|
|
209
|
+
'priority': priority.lower(),
|
|
210
|
+
'assignees': [assignee] if assignee else [],
|
|
211
|
+
'tags': labels or [],
|
|
212
|
+
'metadata': metadata or {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ticket = self._ticket_manager.create_task(**task_data)
|
|
216
|
+
|
|
217
|
+
return self._convert_to_ticket_data(ticket)
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"Failed to create ticket: {e}")
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
def get_ticket(self, ticket_id: str) -> Optional[TicketData]:
|
|
224
|
+
"""
|
|
225
|
+
Get a ticket by ID.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
ticket_id: Ticket identifier
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
TicketData object or None if not found
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
ticket = self._ticket_manager.get_ticket(ticket_id)
|
|
238
|
+
if ticket:
|
|
239
|
+
return self._convert_to_ticket_data(ticket)
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(f"Failed to get ticket {ticket_id}: {e}")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def list_tickets(
|
|
247
|
+
self,
|
|
248
|
+
status: Optional[str] = None,
|
|
249
|
+
priority: Optional[str] = None,
|
|
250
|
+
assignee: Optional[str] = None,
|
|
251
|
+
labels: Optional[List[str]] = None,
|
|
252
|
+
limit: Optional[int] = None
|
|
253
|
+
) -> List[TicketData]:
|
|
254
|
+
"""
|
|
255
|
+
List tickets with optional filters.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
status: Filter by status (open, in_progress, resolved, closed)
|
|
259
|
+
priority: Filter by priority (low, medium, high, critical)
|
|
260
|
+
assignee: Filter by assignee
|
|
261
|
+
labels: Filter by labels (tickets must have all specified labels)
|
|
262
|
+
limit: Maximum number of tickets to return
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List of TicketData objects
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
269
|
+
return []
|
|
270
|
+
|
|
271
|
+
# Build filter criteria
|
|
272
|
+
filters = {}
|
|
273
|
+
if status:
|
|
274
|
+
filters['status'] = getattr(TicketStatus, status.upper(), None)
|
|
275
|
+
if priority:
|
|
276
|
+
filters['priority'] = getattr(TicketPriority, priority.upper(), None)
|
|
277
|
+
if assignee:
|
|
278
|
+
filters['assignee'] = assignee
|
|
279
|
+
if labels:
|
|
280
|
+
filters['labels'] = labels
|
|
281
|
+
|
|
282
|
+
tickets = self._ticket_manager.list_tickets(**filters)
|
|
283
|
+
|
|
284
|
+
# Convert and limit results
|
|
285
|
+
result = [self._convert_to_ticket_data(t) for t in tickets]
|
|
286
|
+
if limit:
|
|
287
|
+
result = result[:limit]
|
|
288
|
+
|
|
289
|
+
return result
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.error(f"Failed to list tickets: {e}")
|
|
293
|
+
return []
|
|
294
|
+
|
|
295
|
+
def update_ticket(
|
|
296
|
+
self,
|
|
297
|
+
ticket_id: str,
|
|
298
|
+
title: Optional[str] = None,
|
|
299
|
+
description: Optional[str] = None,
|
|
300
|
+
status: Optional[str] = None,
|
|
301
|
+
priority: Optional[str] = None,
|
|
302
|
+
assignee: Optional[str] = None,
|
|
303
|
+
labels: Optional[List[str]] = None,
|
|
304
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
305
|
+
) -> Optional[TicketData]:
|
|
306
|
+
"""
|
|
307
|
+
Update an existing ticket.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
ticket_id: Ticket identifier
|
|
311
|
+
title: New title (optional)
|
|
312
|
+
description: New description (optional)
|
|
313
|
+
status: New status (optional)
|
|
314
|
+
priority: New priority (optional)
|
|
315
|
+
assignee: New assignee (optional)
|
|
316
|
+
labels: New labels (optional, replaces existing)
|
|
317
|
+
metadata: New metadata (optional, merges with existing)
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Updated TicketData object or None if not found
|
|
321
|
+
"""
|
|
322
|
+
try:
|
|
323
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
# Build update data
|
|
327
|
+
updates = {}
|
|
328
|
+
if title is not None:
|
|
329
|
+
updates['title'] = title
|
|
330
|
+
if description is not None:
|
|
331
|
+
updates['description'] = description
|
|
332
|
+
if status is not None:
|
|
333
|
+
updates['status'] = getattr(TicketStatus, status.upper(), None)
|
|
334
|
+
if priority is not None:
|
|
335
|
+
updates['priority'] = getattr(TicketPriority, priority.upper(), None)
|
|
336
|
+
if assignee is not None:
|
|
337
|
+
updates['assignee'] = assignee
|
|
338
|
+
if labels is not None:
|
|
339
|
+
updates['labels'] = labels
|
|
340
|
+
if metadata is not None:
|
|
341
|
+
updates['metadata'] = metadata
|
|
342
|
+
|
|
343
|
+
ticket = self._ticket_manager.update_ticket(ticket_id, **updates)
|
|
344
|
+
if ticket:
|
|
345
|
+
return self._convert_to_ticket_data(ticket)
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(f"Failed to update ticket {ticket_id}: {e}")
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
def add_comment(
|
|
353
|
+
self,
|
|
354
|
+
ticket_id: str,
|
|
355
|
+
comment: str,
|
|
356
|
+
author: Optional[str] = None
|
|
357
|
+
) -> bool:
|
|
358
|
+
"""
|
|
359
|
+
Add a comment to a ticket.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
ticket_id: Ticket identifier
|
|
363
|
+
comment: Comment text
|
|
364
|
+
author: Comment author (optional)
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
True if successful, False otherwise
|
|
368
|
+
"""
|
|
369
|
+
try:
|
|
370
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
371
|
+
return False
|
|
372
|
+
|
|
373
|
+
self._ticket_manager.add_comment(
|
|
374
|
+
ticket_id=ticket_id,
|
|
375
|
+
comment=comment,
|
|
376
|
+
author=author or "claude-pm"
|
|
377
|
+
)
|
|
378
|
+
return True
|
|
379
|
+
|
|
380
|
+
except Exception as e:
|
|
381
|
+
logger.error(f"Failed to add comment to ticket {ticket_id}: {e}")
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def close_ticket(
|
|
385
|
+
self,
|
|
386
|
+
ticket_id: str,
|
|
387
|
+
resolution: Optional[str] = None
|
|
388
|
+
) -> Optional[TicketData]:
|
|
389
|
+
"""
|
|
390
|
+
Close a ticket.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
ticket_id: Ticket identifier
|
|
394
|
+
resolution: Optional resolution description
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Updated TicketData object or None if not found
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
if resolution:
|
|
401
|
+
self.add_comment(ticket_id, f"Resolution: {resolution}")
|
|
402
|
+
|
|
403
|
+
return self.update_ticket(ticket_id, status="closed")
|
|
404
|
+
|
|
405
|
+
except Exception as e:
|
|
406
|
+
logger.error(f"Failed to close ticket {ticket_id}: {e}")
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
# Utility methods
|
|
410
|
+
|
|
411
|
+
def search_tickets(self, query: str, limit: Optional[int] = None) -> List[TicketData]:
|
|
412
|
+
"""
|
|
413
|
+
Search tickets by text query.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
query: Search query (searches in title and description)
|
|
417
|
+
limit: Maximum number of results
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
List of matching TicketData objects
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
424
|
+
return []
|
|
425
|
+
|
|
426
|
+
tickets = self._ticket_manager.search_tickets(query)
|
|
427
|
+
result = [self._convert_to_ticket_data(t) for t in tickets]
|
|
428
|
+
|
|
429
|
+
if limit:
|
|
430
|
+
result = result[:limit]
|
|
431
|
+
|
|
432
|
+
return result
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.error(f"Failed to search tickets: {e}")
|
|
436
|
+
return []
|
|
437
|
+
|
|
438
|
+
def get_ticket_statistics(self) -> Dict[str, Any]:
|
|
439
|
+
"""
|
|
440
|
+
Get ticket statistics.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Dictionary with ticket statistics
|
|
444
|
+
"""
|
|
445
|
+
try:
|
|
446
|
+
if not AI_TRACKDOWN_AVAILABLE:
|
|
447
|
+
return {
|
|
448
|
+
"total": 0,
|
|
449
|
+
"by_status": {},
|
|
450
|
+
"by_priority": {},
|
|
451
|
+
"unassigned": 0
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
stats = self._ticket_manager.get_statistics()
|
|
455
|
+
return stats
|
|
456
|
+
|
|
457
|
+
except Exception as e:
|
|
458
|
+
logger.error(f"Failed to get ticket statistics: {e}")
|
|
459
|
+
return {}
|
|
460
|
+
|
|
461
|
+
def _convert_to_ticket_data(self, ticket: Any) -> TicketData:
|
|
462
|
+
"""Convert ai-trackdown ticket to TicketData."""
|
|
463
|
+
# Task uses 'assignees' (list) not 'assignee' (single)
|
|
464
|
+
assignee = ticket.assignees[0] if ticket.assignees else None
|
|
465
|
+
|
|
466
|
+
# Task uses 'tags' not 'labels'
|
|
467
|
+
labels = ticket.tags if hasattr(ticket, 'tags') else []
|
|
468
|
+
|
|
469
|
+
return TicketData(
|
|
470
|
+
id=ticket.id,
|
|
471
|
+
title=ticket.title,
|
|
472
|
+
description=ticket.description,
|
|
473
|
+
status=ticket.status if isinstance(ticket.status, str) else str(ticket.status),
|
|
474
|
+
priority=ticket.priority if isinstance(ticket.priority, str) else str(ticket.priority),
|
|
475
|
+
assignee=assignee,
|
|
476
|
+
labels=labels,
|
|
477
|
+
created_at=ticket.created_at,
|
|
478
|
+
updated_at=ticket.updated_at if hasattr(ticket, 'updated_at') else ticket.created_at,
|
|
479
|
+
metadata=ticket.metadata or {}
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Async support for PM orchestration
|
|
483
|
+
|
|
484
|
+
async def acreate_ticket(self, **kwargs) -> TicketData:
|
|
485
|
+
"""Async version of create_ticket."""
|
|
486
|
+
loop = asyncio.get_event_loop()
|
|
487
|
+
return await loop.run_in_executor(None, self.create_ticket, **kwargs)
|
|
488
|
+
|
|
489
|
+
async def aget_ticket(self, ticket_id: str) -> Optional[TicketData]:
|
|
490
|
+
"""Async version of get_ticket."""
|
|
491
|
+
loop = asyncio.get_event_loop()
|
|
492
|
+
return await loop.run_in_executor(None, self.get_ticket, ticket_id)
|
|
493
|
+
|
|
494
|
+
async def alist_tickets(self, **kwargs) -> List[TicketData]:
|
|
495
|
+
"""Async version of list_tickets."""
|
|
496
|
+
loop = asyncio.get_event_loop()
|
|
497
|
+
return await loop.run_in_executor(None, self.list_tickets, **kwargs)
|
|
498
|
+
|
|
499
|
+
async def aupdate_ticket(self, ticket_id: str, **kwargs) -> Optional[TicketData]:
|
|
500
|
+
"""Async version of update_ticket."""
|
|
501
|
+
loop = asyncio.get_event_loop()
|
|
502
|
+
return await loop.run_in_executor(None, self.update_ticket, ticket_id, **kwargs)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# Convenience functions for direct import
|
|
506
|
+
def get_ticketing_service() -> TicketingService:
|
|
507
|
+
"""Get the singleton TicketingService instance."""
|
|
508
|
+
return TicketingService.get_instance()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version Control Services - Modular services for Git and version management.
|
|
3
|
+
|
|
4
|
+
This package provides modular services for the Version Control Agent including:
|
|
5
|
+
- Git operations management
|
|
6
|
+
- Semantic versioning
|
|
7
|
+
- Branch strategy implementation
|
|
8
|
+
- Conflict resolution
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .git_operations import (
|
|
12
|
+
GitOperationsManager,
|
|
13
|
+
GitBranchInfo,
|
|
14
|
+
GitOperationResult,
|
|
15
|
+
GitOperationError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .semantic_versioning import (
|
|
19
|
+
SemanticVersionManager,
|
|
20
|
+
SemanticVersion,
|
|
21
|
+
VersionBumpType,
|
|
22
|
+
VersionMetadata,
|
|
23
|
+
ChangeAnalysis,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from .branch_strategy import (
|
|
27
|
+
BranchStrategyManager,
|
|
28
|
+
BranchStrategyType,
|
|
29
|
+
BranchType,
|
|
30
|
+
BranchWorkflow,
|
|
31
|
+
BranchNamingRule,
|
|
32
|
+
BranchLifecycleRule,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .conflict_resolution import (
|
|
36
|
+
ConflictResolutionManager,
|
|
37
|
+
ConflictType,
|
|
38
|
+
ResolutionStrategy,
|
|
39
|
+
FileConflict,
|
|
40
|
+
ConflictResolution,
|
|
41
|
+
ConflictAnalysis,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Git Operations
|
|
46
|
+
"GitOperationsManager",
|
|
47
|
+
"GitBranchInfo",
|
|
48
|
+
"GitOperationResult",
|
|
49
|
+
"GitOperationError",
|
|
50
|
+
# Semantic Versioning
|
|
51
|
+
"SemanticVersionManager",
|
|
52
|
+
"SemanticVersion",
|
|
53
|
+
"VersionBumpType",
|
|
54
|
+
"VersionMetadata",
|
|
55
|
+
"ChangeAnalysis",
|
|
56
|
+
# Branch Strategy
|
|
57
|
+
"BranchStrategyManager",
|
|
58
|
+
"BranchStrategyType",
|
|
59
|
+
"BranchType",
|
|
60
|
+
"BranchWorkflow",
|
|
61
|
+
"BranchNamingRule",
|
|
62
|
+
"BranchLifecycleRule",
|
|
63
|
+
# Conflict Resolution
|
|
64
|
+
"ConflictResolutionManager",
|
|
65
|
+
"ConflictType",
|
|
66
|
+
"ResolutionStrategy",
|
|
67
|
+
"FileConflict",
|
|
68
|
+
"ConflictResolution",
|
|
69
|
+
"ConflictAnalysis",
|
|
70
|
+
]
|