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,213 @@
|
|
|
1
|
+
"""Ticket management using ai-trackdown-pytools."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Dict, Any, List
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from ..core.logger import get_logger
|
|
9
|
+
except ImportError:
|
|
10
|
+
from core.logger import get_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TicketManager:
|
|
14
|
+
"""
|
|
15
|
+
Manage ticket creation using ai-trackdown-pytools.
|
|
16
|
+
|
|
17
|
+
This wraps the ai-trackdown-pytools API for creating tickets
|
|
18
|
+
in the standard tickets/ directory structure.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, project_path: Optional[Path] = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize ticket manager.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
project_path: Project root (defaults to current directory)
|
|
27
|
+
"""
|
|
28
|
+
self.logger = get_logger("ticket_manager")
|
|
29
|
+
self.project_path = project_path or Path.cwd()
|
|
30
|
+
self.task_manager = self._init_task_manager()
|
|
31
|
+
|
|
32
|
+
def _init_task_manager(self):
|
|
33
|
+
"""Initialize ai-trackdown-pytools TaskManager."""
|
|
34
|
+
try:
|
|
35
|
+
from ai_trackdown_pytools.core.task import TaskManager
|
|
36
|
+
from ai_trackdown_pytools import Config, Project
|
|
37
|
+
|
|
38
|
+
# First, ensure tickets directory exists
|
|
39
|
+
tickets_dir = self.project_path / "tickets"
|
|
40
|
+
if not tickets_dir.exists():
|
|
41
|
+
tickets_dir.mkdir(exist_ok=True)
|
|
42
|
+
(tickets_dir / "epics").mkdir(exist_ok=True)
|
|
43
|
+
(tickets_dir / "issues").mkdir(exist_ok=True)
|
|
44
|
+
(tickets_dir / "tasks").mkdir(exist_ok=True)
|
|
45
|
+
self.logger.info(f"Created tickets directory structure at: {tickets_dir}")
|
|
46
|
+
|
|
47
|
+
# Check if we need to configure ai-trackdown
|
|
48
|
+
config_file = self.project_path / ".trackdown.yaml"
|
|
49
|
+
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")
|
|
58
|
+
|
|
59
|
+
# Initialize TaskManager directly with the project path
|
|
60
|
+
# TaskManager will handle project initialization internally
|
|
61
|
+
task_manager = TaskManager(self.project_path)
|
|
62
|
+
|
|
63
|
+
# Verify it's using the right directory
|
|
64
|
+
if hasattr(task_manager, 'tasks_dir'):
|
|
65
|
+
self.logger.info(f"TaskManager using tasks directory: {task_manager.tasks_dir}")
|
|
66
|
+
else:
|
|
67
|
+
self.logger.info(f"Initialized TaskManager for: {self.project_path}")
|
|
68
|
+
|
|
69
|
+
return task_manager
|
|
70
|
+
|
|
71
|
+
except ImportError:
|
|
72
|
+
self.logger.error("ai-trackdown-pytools not installed")
|
|
73
|
+
self.logger.info("Install with: pip install ai-trackdown-pytools")
|
|
74
|
+
return None
|
|
75
|
+
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)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def create_ticket(
|
|
81
|
+
self,
|
|
82
|
+
title: str,
|
|
83
|
+
ticket_type: str = "task",
|
|
84
|
+
description: str = "",
|
|
85
|
+
priority: str = "medium",
|
|
86
|
+
tags: Optional[List[str]] = None,
|
|
87
|
+
source: str = "claude-mpm",
|
|
88
|
+
parent_epic: Optional[str] = None,
|
|
89
|
+
parent_issue: Optional[str] = None,
|
|
90
|
+
**kwargs
|
|
91
|
+
) -> Optional[str]:
|
|
92
|
+
"""
|
|
93
|
+
Create a ticket using ai-trackdown-pytools.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
title: Ticket title
|
|
97
|
+
ticket_type: Type (task, bug, feature, etc.)
|
|
98
|
+
description: Detailed description
|
|
99
|
+
priority: Priority level (low, medium, high)
|
|
100
|
+
tags: List of tags/labels
|
|
101
|
+
source: Source identifier
|
|
102
|
+
**kwargs: Additional metadata
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Ticket ID if created, None on failure
|
|
106
|
+
"""
|
|
107
|
+
if not self.task_manager:
|
|
108
|
+
self.logger.error("TaskManager not available")
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Prepare tags
|
|
113
|
+
if tags is None:
|
|
114
|
+
tags = []
|
|
115
|
+
|
|
116
|
+
# Add type and source tags
|
|
117
|
+
tags.extend([ticket_type, f"source:{source}", "auto-extracted"])
|
|
118
|
+
|
|
119
|
+
# Remove duplicates
|
|
120
|
+
tags = list(set(tags))
|
|
121
|
+
|
|
122
|
+
# Prepare task data
|
|
123
|
+
task_data = {
|
|
124
|
+
'title': title,
|
|
125
|
+
'description': description or f"Auto-extracted {ticket_type} from Claude MPM session",
|
|
126
|
+
'status': 'open',
|
|
127
|
+
'priority': priority.lower(),
|
|
128
|
+
'assignees': [],
|
|
129
|
+
'tags': tags,
|
|
130
|
+
'metadata': {
|
|
131
|
+
'source': source,
|
|
132
|
+
'ticket_type': ticket_type,
|
|
133
|
+
'created_by': 'claude-mpm',
|
|
134
|
+
'extracted_at': datetime.now().isoformat(),
|
|
135
|
+
**kwargs
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Create the task
|
|
140
|
+
task = self.task_manager.create_task(**task_data)
|
|
141
|
+
|
|
142
|
+
self.logger.info(f"Created ticket: {task.id} - {title}")
|
|
143
|
+
return task.id
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.logger.error(f"Failed to create ticket: {e}")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def list_recent_tickets(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
150
|
+
"""
|
|
151
|
+
List recent tickets.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
limit: Maximum number of tickets to return
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
List of ticket summaries
|
|
158
|
+
"""
|
|
159
|
+
if not self.task_manager:
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
tasks = self.task_manager.get_recent_tasks(limit=limit)
|
|
164
|
+
|
|
165
|
+
tickets = []
|
|
166
|
+
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
|
+
})
|
|
175
|
+
|
|
176
|
+
return tickets
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
self.logger.error(f"Failed to list tickets: {e}")
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
def get_ticket(self, ticket_id: str) -> Optional[Dict[str, Any]]:
|
|
183
|
+
"""
|
|
184
|
+
Get a specific ticket.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
ticket_id: Ticket ID
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Ticket data or None
|
|
191
|
+
"""
|
|
192
|
+
if not self.task_manager:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
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,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.logger.error(f"Failed to get ticket {ticket_id}: {e}")
|
|
213
|
+
return None
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Ticket Manager with Dependency Injection support.
|
|
3
|
+
|
|
4
|
+
This version demonstrates proper constructor injection and testability.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional, Dict, Any, List
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from ..core.injectable_service import InjectableService
|
|
12
|
+
from ..core.config import Config
|
|
13
|
+
from ..core.logger import get_logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ITaskManagerAdapter:
|
|
17
|
+
"""Interface for task manager adapter."""
|
|
18
|
+
|
|
19
|
+
def create_task(self, **kwargs) -> Any:
|
|
20
|
+
"""Create a task."""
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def get_recent_tasks(self, limit: int) -> List[Any]:
|
|
24
|
+
"""Get recent tasks."""
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
def load_task(self, task_id: str) -> Any:
|
|
28
|
+
"""Load a specific task."""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AITrackdownAdapter(ITaskManagerAdapter):
|
|
33
|
+
"""Adapter for ai-trackdown-pytools."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, project_path: Path):
|
|
36
|
+
"""Initialize the adapter."""
|
|
37
|
+
self.project_path = project_path
|
|
38
|
+
self.logger = get_logger("ai_trackdown_adapter")
|
|
39
|
+
self._task_manager = self._init_task_manager()
|
|
40
|
+
|
|
41
|
+
def _init_task_manager(self):
|
|
42
|
+
"""Initialize ai-trackdown-pytools TaskManager."""
|
|
43
|
+
try:
|
|
44
|
+
from ai_trackdown_pytools.core.task import TaskManager
|
|
45
|
+
from ai_trackdown_pytools import Config as TrackdownConfig, Project
|
|
46
|
+
|
|
47
|
+
# Ensure tickets directory exists
|
|
48
|
+
tickets_dir = self.project_path / "tickets"
|
|
49
|
+
if not tickets_dir.exists():
|
|
50
|
+
tickets_dir.mkdir(exist_ok=True)
|
|
51
|
+
(tickets_dir / "epics").mkdir(exist_ok=True)
|
|
52
|
+
(tickets_dir / "issues").mkdir(exist_ok=True)
|
|
53
|
+
(tickets_dir / "tasks").mkdir(exist_ok=True)
|
|
54
|
+
self.logger.info(f"Created tickets directory structure at: {tickets_dir}")
|
|
55
|
+
|
|
56
|
+
# Configure ai-trackdown if needed
|
|
57
|
+
config_file = self.project_path / ".trackdown.yaml"
|
|
58
|
+
if not config_file.exists():
|
|
59
|
+
config = TrackdownConfig.create_default(config_file)
|
|
60
|
+
config.set("paths.tickets_dir", "tickets")
|
|
61
|
+
config.set("paths.epics_dir", "tickets/epics")
|
|
62
|
+
config.set("paths.issues_dir", "tickets/issues")
|
|
63
|
+
config.set("paths.tasks_dir", "tickets/tasks")
|
|
64
|
+
config.save()
|
|
65
|
+
self.logger.info("Created .trackdown.yaml configuration")
|
|
66
|
+
|
|
67
|
+
# Initialize task manager directly
|
|
68
|
+
return TaskManager(self.project_path)
|
|
69
|
+
|
|
70
|
+
except ImportError:
|
|
71
|
+
self.logger.error("ai-trackdown-pytools not installed")
|
|
72
|
+
return None
|
|
73
|
+
except Exception as e:
|
|
74
|
+
self.logger.error(f"Failed to initialize TaskManager: {e}")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def create_task(self, **kwargs) -> Any:
|
|
78
|
+
"""Create a task."""
|
|
79
|
+
if not self._task_manager:
|
|
80
|
+
raise RuntimeError("Task manager not available")
|
|
81
|
+
return self._task_manager.create_task(**kwargs)
|
|
82
|
+
|
|
83
|
+
def get_recent_tasks(self, limit: int) -> List[Any]:
|
|
84
|
+
"""Get recent tasks."""
|
|
85
|
+
if not self._task_manager:
|
|
86
|
+
return []
|
|
87
|
+
return self._task_manager.get_recent_tasks(limit=limit)
|
|
88
|
+
|
|
89
|
+
def load_task(self, task_id: str) -> Any:
|
|
90
|
+
"""Load a specific task."""
|
|
91
|
+
if not self._task_manager:
|
|
92
|
+
raise RuntimeError("Task manager not available")
|
|
93
|
+
return self._task_manager.load_task(task_id)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TicketManagerDI(InjectableService):
|
|
97
|
+
"""
|
|
98
|
+
Enhanced Ticket Manager with Dependency Injection.
|
|
99
|
+
|
|
100
|
+
This version demonstrates:
|
|
101
|
+
- Constructor injection of dependencies
|
|
102
|
+
- Interface-based design for testability
|
|
103
|
+
- Configuration injection
|
|
104
|
+
- Easy mocking for tests
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Type annotations for dependency injection
|
|
108
|
+
config: Config
|
|
109
|
+
task_adapter: Optional[ITaskManagerAdapter]
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
name: str = "ticket_manager",
|
|
114
|
+
config: Optional[Config] = None,
|
|
115
|
+
task_adapter: Optional[ITaskManagerAdapter] = None,
|
|
116
|
+
project_path: Optional[Path] = None,
|
|
117
|
+
**kwargs
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Initialize ticket manager with dependency injection.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: Service name
|
|
124
|
+
config: Configuration service (injected)
|
|
125
|
+
task_adapter: Task manager adapter (injected)
|
|
126
|
+
project_path: Project path override
|
|
127
|
+
**kwargs: Additional arguments for base class
|
|
128
|
+
"""
|
|
129
|
+
# Call parent constructor
|
|
130
|
+
super().__init__(name=name, config=config, **kwargs)
|
|
131
|
+
|
|
132
|
+
# Use injected adapter or create default
|
|
133
|
+
if task_adapter:
|
|
134
|
+
self.task_adapter = task_adapter
|
|
135
|
+
else:
|
|
136
|
+
# Get project path from config or parameter
|
|
137
|
+
if project_path is None:
|
|
138
|
+
project_path = Path(self.config.get('project.path', '.'))
|
|
139
|
+
self.task_adapter = AITrackdownAdapter(project_path)
|
|
140
|
+
|
|
141
|
+
self.logger.info(f"Initialized {name} with DI support")
|
|
142
|
+
|
|
143
|
+
async def _initialize(self) -> None:
|
|
144
|
+
"""Initialize the service."""
|
|
145
|
+
self.logger.info("TicketManager service initialized")
|
|
146
|
+
|
|
147
|
+
async def _cleanup(self) -> None:
|
|
148
|
+
"""Cleanup service resources."""
|
|
149
|
+
self.logger.info("TicketManager service cleaned up")
|
|
150
|
+
|
|
151
|
+
def create_ticket(
|
|
152
|
+
self,
|
|
153
|
+
title: str,
|
|
154
|
+
ticket_type: str = "task",
|
|
155
|
+
description: str = "",
|
|
156
|
+
priority: str = "medium",
|
|
157
|
+
tags: Optional[List[str]] = None,
|
|
158
|
+
source: str = "claude-mpm",
|
|
159
|
+
parent_epic: Optional[str] = None,
|
|
160
|
+
parent_issue: Optional[str] = None,
|
|
161
|
+
**kwargs
|
|
162
|
+
) -> Optional[str]:
|
|
163
|
+
"""
|
|
164
|
+
Create a ticket.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
title: Ticket title
|
|
168
|
+
ticket_type: Type (task, bug, feature, etc.)
|
|
169
|
+
description: Detailed description
|
|
170
|
+
priority: Priority level
|
|
171
|
+
tags: List of tags
|
|
172
|
+
source: Source identifier
|
|
173
|
+
**kwargs: Additional metadata
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Ticket ID if created, None on failure
|
|
177
|
+
"""
|
|
178
|
+
if not self.task_adapter:
|
|
179
|
+
self.logger.error("Task adapter not available")
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Prepare tags
|
|
184
|
+
if tags is None:
|
|
185
|
+
tags = []
|
|
186
|
+
|
|
187
|
+
# Add standard tags
|
|
188
|
+
tags.extend([ticket_type, f"source:{source}", "auto-extracted"])
|
|
189
|
+
tags = list(set(tags)) # Remove duplicates
|
|
190
|
+
|
|
191
|
+
# Prepare task data
|
|
192
|
+
task_data = {
|
|
193
|
+
'title': title,
|
|
194
|
+
'description': description or f"Auto-extracted {ticket_type}",
|
|
195
|
+
'status': 'open',
|
|
196
|
+
'priority': priority.lower(),
|
|
197
|
+
'assignees': [],
|
|
198
|
+
'tags': tags,
|
|
199
|
+
'metadata': {
|
|
200
|
+
'source': source,
|
|
201
|
+
'ticket_type': ticket_type,
|
|
202
|
+
'created_by': 'claude-mpm',
|
|
203
|
+
'extracted_at': datetime.now().isoformat(),
|
|
204
|
+
**kwargs
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# Add parent references if provided
|
|
209
|
+
if parent_epic:
|
|
210
|
+
task_data['metadata']['parent_epic'] = parent_epic
|
|
211
|
+
if parent_issue:
|
|
212
|
+
task_data['metadata']['parent_issue'] = parent_issue
|
|
213
|
+
|
|
214
|
+
# Create the task
|
|
215
|
+
task = self.task_adapter.create_task(**task_data)
|
|
216
|
+
|
|
217
|
+
# Update metrics
|
|
218
|
+
self.update_metrics(tickets_created=self._metrics.custom_metrics.get('tickets_created', 0) + 1)
|
|
219
|
+
|
|
220
|
+
self.logger.info(f"Created ticket: {task.id} - {title}")
|
|
221
|
+
return task.id
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
self.logger.error(f"Failed to create ticket: {e}")
|
|
225
|
+
self.update_metrics(tickets_failed=self._metrics.custom_metrics.get('tickets_failed', 0) + 1)
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
def list_recent_tickets(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
229
|
+
"""
|
|
230
|
+
List recent tickets.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
limit: Maximum number of tickets
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of ticket summaries
|
|
237
|
+
"""
|
|
238
|
+
if not self.task_adapter:
|
|
239
|
+
return []
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
tasks = self.task_adapter.get_recent_tasks(limit=limit)
|
|
243
|
+
|
|
244
|
+
tickets = []
|
|
245
|
+
for task in tasks:
|
|
246
|
+
tickets.append({
|
|
247
|
+
'id': task.id,
|
|
248
|
+
'title': task.title,
|
|
249
|
+
'status': task.status,
|
|
250
|
+
'priority': task.priority,
|
|
251
|
+
'tags': task.tags,
|
|
252
|
+
'created_at': task.created_at,
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return tickets
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.logger.error(f"Failed to list tickets: {e}")
|
|
259
|
+
return []
|
|
260
|
+
|
|
261
|
+
def get_ticket(self, ticket_id: str) -> Optional[Dict[str, Any]]:
|
|
262
|
+
"""
|
|
263
|
+
Get a specific ticket.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
ticket_id: Ticket ID
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Ticket data or None
|
|
270
|
+
"""
|
|
271
|
+
if not self.task_adapter:
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
task = self.task_adapter.load_task(ticket_id)
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
'id': task.id,
|
|
279
|
+
'title': task.title,
|
|
280
|
+
'description': task.description,
|
|
281
|
+
'status': task.status,
|
|
282
|
+
'priority': task.priority,
|
|
283
|
+
'tags': task.tags,
|
|
284
|
+
'assignees': task.assignees,
|
|
285
|
+
'created_at': task.created_at,
|
|
286
|
+
'updated_at': task.updated_at,
|
|
287
|
+
'metadata': task.metadata,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
self.logger.error(f"Failed to get ticket {ticket_id}: {e}")
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
async def _health_check(self) -> Dict[str, bool]:
|
|
295
|
+
"""Perform custom health checks."""
|
|
296
|
+
checks = {
|
|
297
|
+
'task_adapter_available': self.task_adapter is not None,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# Check if we can access the ticket directory
|
|
301
|
+
try:
|
|
302
|
+
project_path = Path(self.config.get('project.path', '.'))
|
|
303
|
+
tickets_dir = project_path / "tickets"
|
|
304
|
+
checks['tickets_directory_accessible'] = tickets_dir.exists()
|
|
305
|
+
except Exception:
|
|
306
|
+
checks['tickets_directory_accessible'] = False
|
|
307
|
+
|
|
308
|
+
return checks
|
|
309
|
+
|
|
310
|
+
async def _collect_custom_metrics(self) -> None:
|
|
311
|
+
"""Collect custom metrics."""
|
|
312
|
+
# Count tickets if possible
|
|
313
|
+
try:
|
|
314
|
+
if self.task_adapter:
|
|
315
|
+
recent_tickets = self.task_adapter.get_recent_tasks(limit=100)
|
|
316
|
+
self._metrics.custom_metrics['total_tickets'] = len(recent_tickets)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
self.logger.warning(f"Failed to collect ticket metrics: {e}")
|