mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__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 mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/adapter.py +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +1288 -105
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
mcp_ticketer/core/adapter.py
CHANGED
|
@@ -4,9 +4,23 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import builtins
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
8
9
|
|
|
9
|
-
from .models import
|
|
10
|
+
from .models import (
|
|
11
|
+
Comment,
|
|
12
|
+
Epic,
|
|
13
|
+
Milestone,
|
|
14
|
+
Project,
|
|
15
|
+
ProjectScope,
|
|
16
|
+
ProjectState,
|
|
17
|
+
ProjectStatistics,
|
|
18
|
+
SearchQuery,
|
|
19
|
+
Task,
|
|
20
|
+
TicketState,
|
|
21
|
+
TicketType,
|
|
22
|
+
)
|
|
23
|
+
from .state_matcher import get_state_matcher
|
|
10
24
|
|
|
11
25
|
if TYPE_CHECKING:
|
|
12
26
|
from .models import Attachment
|
|
@@ -22,17 +36,52 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
22
36
|
"""Initialize adapter with configuration.
|
|
23
37
|
|
|
24
38
|
Args:
|
|
39
|
+
----
|
|
25
40
|
config: Adapter-specific configuration dictionary
|
|
26
41
|
|
|
27
42
|
"""
|
|
28
43
|
self.config = config
|
|
29
44
|
self._state_mapping = self._get_state_mapping()
|
|
30
45
|
|
|
46
|
+
@property
|
|
47
|
+
def adapter_type(self) -> str:
|
|
48
|
+
"""Return lowercase adapter type identifier.
|
|
49
|
+
|
|
50
|
+
This identifier is used in MCP responses to show which adapter
|
|
51
|
+
handled the operation (e.g., "linear", "github", "jira", "asana").
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
-------
|
|
55
|
+
Lowercase adapter type (e.g., "linear", "github")
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
# Extract adapter type from class name
|
|
59
|
+
# LinearAdapter -> linear, GitHubAdapter -> github
|
|
60
|
+
class_name = self.__class__.__name__
|
|
61
|
+
if class_name.endswith("Adapter"):
|
|
62
|
+
adapter_name = class_name[: -len("Adapter")]
|
|
63
|
+
else:
|
|
64
|
+
adapter_name = class_name
|
|
65
|
+
|
|
66
|
+
return adapter_name.lower()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def adapter_display_name(self) -> str:
|
|
70
|
+
"""Return human-readable adapter name.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
-------
|
|
74
|
+
Title-cased adapter name (e.g., "Linear", "Github", "Jira")
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
return self.adapter_type.title()
|
|
78
|
+
|
|
31
79
|
@abstractmethod
|
|
32
80
|
def _get_state_mapping(self) -> dict[TicketState, str]:
|
|
33
81
|
"""Get mapping from universal states to system-specific states.
|
|
34
82
|
|
|
35
83
|
Returns:
|
|
84
|
+
-------
|
|
36
85
|
Dictionary mapping TicketState to system-specific state strings
|
|
37
86
|
|
|
38
87
|
"""
|
|
@@ -43,6 +92,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
43
92
|
"""Validate that required credentials are present.
|
|
44
93
|
|
|
45
94
|
Returns:
|
|
95
|
+
-------
|
|
46
96
|
(is_valid, error_message) - Tuple of validation result and error message
|
|
47
97
|
|
|
48
98
|
"""
|
|
@@ -53,9 +103,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
53
103
|
"""Create a new ticket.
|
|
54
104
|
|
|
55
105
|
Args:
|
|
106
|
+
----
|
|
56
107
|
ticket: Ticket to create (Epic or Task)
|
|
57
108
|
|
|
58
109
|
Returns:
|
|
110
|
+
-------
|
|
59
111
|
Created ticket with ID populated
|
|
60
112
|
|
|
61
113
|
"""
|
|
@@ -66,9 +118,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
66
118
|
"""Read a ticket by ID.
|
|
67
119
|
|
|
68
120
|
Args:
|
|
121
|
+
----
|
|
69
122
|
ticket_id: Unique ticket identifier
|
|
70
123
|
|
|
71
124
|
Returns:
|
|
125
|
+
-------
|
|
72
126
|
Ticket if found, None otherwise
|
|
73
127
|
|
|
74
128
|
"""
|
|
@@ -79,10 +133,12 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
79
133
|
"""Update a ticket.
|
|
80
134
|
|
|
81
135
|
Args:
|
|
136
|
+
----
|
|
82
137
|
ticket_id: Ticket identifier
|
|
83
138
|
updates: Fields to update
|
|
84
139
|
|
|
85
140
|
Returns:
|
|
141
|
+
-------
|
|
86
142
|
Updated ticket if successful, None otherwise
|
|
87
143
|
|
|
88
144
|
"""
|
|
@@ -93,9 +149,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
93
149
|
"""Delete a ticket.
|
|
94
150
|
|
|
95
151
|
Args:
|
|
152
|
+
----
|
|
96
153
|
ticket_id: Ticket identifier
|
|
97
154
|
|
|
98
155
|
Returns:
|
|
156
|
+
-------
|
|
99
157
|
True if deleted, False otherwise
|
|
100
158
|
|
|
101
159
|
"""
|
|
@@ -108,11 +166,13 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
108
166
|
"""List tickets with pagination and filters.
|
|
109
167
|
|
|
110
168
|
Args:
|
|
169
|
+
----
|
|
111
170
|
limit: Maximum number of tickets
|
|
112
171
|
offset: Skip this many tickets
|
|
113
172
|
filters: Optional filter criteria
|
|
114
173
|
|
|
115
174
|
Returns:
|
|
175
|
+
-------
|
|
116
176
|
List of tickets matching criteria
|
|
117
177
|
|
|
118
178
|
"""
|
|
@@ -123,9 +183,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
123
183
|
"""Search tickets using advanced query.
|
|
124
184
|
|
|
125
185
|
Args:
|
|
186
|
+
----
|
|
126
187
|
query: Search parameters
|
|
127
188
|
|
|
128
189
|
Returns:
|
|
190
|
+
-------
|
|
129
191
|
List of tickets matching search criteria
|
|
130
192
|
|
|
131
193
|
"""
|
|
@@ -138,10 +200,12 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
138
200
|
"""Transition ticket to a new state.
|
|
139
201
|
|
|
140
202
|
Args:
|
|
203
|
+
----
|
|
141
204
|
ticket_id: Ticket identifier
|
|
142
205
|
target_state: Target state
|
|
143
206
|
|
|
144
207
|
Returns:
|
|
208
|
+
-------
|
|
145
209
|
Updated ticket if transition successful, None otherwise
|
|
146
210
|
|
|
147
211
|
"""
|
|
@@ -152,9 +216,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
152
216
|
"""Add a comment to a ticket.
|
|
153
217
|
|
|
154
218
|
Args:
|
|
219
|
+
----
|
|
155
220
|
comment: Comment to add
|
|
156
221
|
|
|
157
222
|
Returns:
|
|
223
|
+
-------
|
|
158
224
|
Created comment with ID populated
|
|
159
225
|
|
|
160
226
|
"""
|
|
@@ -167,11 +233,13 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
167
233
|
"""Get comments for a ticket.
|
|
168
234
|
|
|
169
235
|
Args:
|
|
236
|
+
----
|
|
170
237
|
ticket_id: Ticket identifier
|
|
171
238
|
limit: Maximum number of comments
|
|
172
239
|
offset: Skip this many comments
|
|
173
240
|
|
|
174
241
|
Returns:
|
|
242
|
+
-------
|
|
175
243
|
List of comments for the ticket
|
|
176
244
|
|
|
177
245
|
"""
|
|
@@ -181,9 +249,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
181
249
|
"""Map universal state to system-specific state.
|
|
182
250
|
|
|
183
251
|
Args:
|
|
252
|
+
----
|
|
184
253
|
state: Universal ticket state
|
|
185
254
|
|
|
186
255
|
Returns:
|
|
256
|
+
-------
|
|
187
257
|
System-specific state string
|
|
188
258
|
|
|
189
259
|
"""
|
|
@@ -193,31 +263,88 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
193
263
|
"""Map system-specific state to universal state.
|
|
194
264
|
|
|
195
265
|
Args:
|
|
266
|
+
----
|
|
196
267
|
system_state: System-specific state string
|
|
197
268
|
|
|
198
269
|
Returns:
|
|
270
|
+
-------
|
|
199
271
|
Universal ticket state
|
|
200
272
|
|
|
201
273
|
"""
|
|
202
274
|
reverse_mapping = {v: k for k, v in self._state_mapping.items()}
|
|
203
275
|
return reverse_mapping.get(system_state, TicketState.OPEN)
|
|
204
276
|
|
|
277
|
+
def get_available_states(self) -> list[str]:
|
|
278
|
+
"""Get list of adapter-specific available states.
|
|
279
|
+
|
|
280
|
+
Returns adapter-specific state names that can be used for
|
|
281
|
+
semantic state matching. Override in subclasses to provide
|
|
282
|
+
platform-specific state names.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
-------
|
|
286
|
+
List of adapter-specific state names
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
-------
|
|
290
|
+
>>> # Linear adapter override
|
|
291
|
+
>>> def get_available_states(self):
|
|
292
|
+
... return ["Backlog", "Todo", "In Progress", "Done", "Canceled"]
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
# Default: return universal state values
|
|
296
|
+
return [state.value for state in TicketState]
|
|
297
|
+
|
|
298
|
+
def resolve_state(self, user_input: str) -> TicketState:
|
|
299
|
+
"""Resolve user input to universal state using semantic matcher.
|
|
300
|
+
|
|
301
|
+
Uses the semantic state matcher to interpret natural language
|
|
302
|
+
inputs and resolve them to universal TicketState values.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
----
|
|
306
|
+
user_input: Natural language state input (e.g., "working on it")
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
-------
|
|
310
|
+
Resolved universal TicketState
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
-------
|
|
314
|
+
>>> adapter = get_adapter()
|
|
315
|
+
>>> state = adapter.resolve_state("working on it")
|
|
316
|
+
>>> print(state)
|
|
317
|
+
TicketState.IN_PROGRESS
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
matcher = get_state_matcher()
|
|
321
|
+
adapter_states = self.get_available_states()
|
|
322
|
+
result = matcher.match_state(user_input, adapter_states)
|
|
323
|
+
return result.state
|
|
324
|
+
|
|
205
325
|
async def validate_transition(
|
|
206
326
|
self, ticket_id: str, target_state: TicketState
|
|
207
327
|
) -> bool:
|
|
208
328
|
"""Validate if state transition is allowed.
|
|
209
329
|
|
|
330
|
+
Validates both workflow rules and parent/child state constraints:
|
|
331
|
+
- Parent issues must remain at least as complete as their most complete child
|
|
332
|
+
- Standard workflow transitions must be valid
|
|
333
|
+
|
|
210
334
|
Args:
|
|
335
|
+
----
|
|
211
336
|
ticket_id: Ticket identifier
|
|
212
337
|
target_state: Target state
|
|
213
338
|
|
|
214
339
|
Returns:
|
|
340
|
+
-------
|
|
215
341
|
True if transition is valid
|
|
216
342
|
|
|
217
343
|
"""
|
|
218
344
|
ticket = await self.read(ticket_id)
|
|
219
345
|
if not ticket:
|
|
220
346
|
return False
|
|
347
|
+
|
|
221
348
|
# Handle case where state might be stored as string due to use_enum_values=True
|
|
222
349
|
current_state = ticket.state
|
|
223
350
|
if isinstance(current_state, str):
|
|
@@ -225,7 +352,35 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
225
352
|
current_state = TicketState(current_state)
|
|
226
353
|
except ValueError:
|
|
227
354
|
return False
|
|
228
|
-
|
|
355
|
+
|
|
356
|
+
# Check workflow transition validity
|
|
357
|
+
if not current_state.can_transition_to(target_state):
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
# Check parent/child state constraint
|
|
361
|
+
# If this ticket has children, ensure target state >= max child state
|
|
362
|
+
if isinstance(ticket, Task):
|
|
363
|
+
# Get all children
|
|
364
|
+
children = await self.list_tasks_by_issue(ticket_id)
|
|
365
|
+
if children:
|
|
366
|
+
# Find max child completion level
|
|
367
|
+
max_child_level = 0
|
|
368
|
+
for child in children:
|
|
369
|
+
child_state = child.state
|
|
370
|
+
if isinstance(child_state, str):
|
|
371
|
+
try:
|
|
372
|
+
child_state = TicketState(child_state)
|
|
373
|
+
except ValueError:
|
|
374
|
+
continue
|
|
375
|
+
max_child_level = max(
|
|
376
|
+
max_child_level, child_state.completion_level()
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Target state must be at least as complete as most complete child
|
|
380
|
+
if target_state.completion_level() < max_child_level:
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
return True
|
|
229
384
|
|
|
230
385
|
# Epic/Issue/Task Hierarchy Methods
|
|
231
386
|
|
|
@@ -235,11 +390,13 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
235
390
|
"""Create epic (top-level grouping).
|
|
236
391
|
|
|
237
392
|
Args:
|
|
393
|
+
----
|
|
238
394
|
title: Epic title
|
|
239
395
|
description: Epic description
|
|
240
396
|
**kwargs: Additional adapter-specific fields
|
|
241
397
|
|
|
242
398
|
Returns:
|
|
399
|
+
-------
|
|
243
400
|
Created epic or None if failed
|
|
244
401
|
|
|
245
402
|
"""
|
|
@@ -258,9 +415,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
258
415
|
"""Get epic by ID.
|
|
259
416
|
|
|
260
417
|
Args:
|
|
418
|
+
----
|
|
261
419
|
epic_id: Epic identifier
|
|
262
420
|
|
|
263
421
|
Returns:
|
|
422
|
+
-------
|
|
264
423
|
Epic if found, None otherwise
|
|
265
424
|
|
|
266
425
|
"""
|
|
@@ -274,9 +433,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
274
433
|
"""List all epics.
|
|
275
434
|
|
|
276
435
|
Args:
|
|
436
|
+
----
|
|
277
437
|
**kwargs: Adapter-specific filter parameters
|
|
278
438
|
|
|
279
439
|
Returns:
|
|
440
|
+
-------
|
|
280
441
|
List of epics
|
|
281
442
|
|
|
282
443
|
"""
|
|
@@ -296,12 +457,14 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
296
457
|
"""Create issue, optionally linked to epic.
|
|
297
458
|
|
|
298
459
|
Args:
|
|
460
|
+
----
|
|
299
461
|
title: Issue title
|
|
300
462
|
description: Issue description
|
|
301
463
|
epic_id: Optional parent epic ID
|
|
302
464
|
**kwargs: Additional adapter-specific fields
|
|
303
465
|
|
|
304
466
|
Returns:
|
|
467
|
+
-------
|
|
305
468
|
Created issue or None if failed
|
|
306
469
|
|
|
307
470
|
"""
|
|
@@ -318,9 +481,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
318
481
|
"""List all issues in epic.
|
|
319
482
|
|
|
320
483
|
Args:
|
|
484
|
+
----
|
|
321
485
|
epic_id: Epic identifier
|
|
322
486
|
|
|
323
487
|
Returns:
|
|
488
|
+
-------
|
|
324
489
|
List of issues belonging to epic
|
|
325
490
|
|
|
326
491
|
"""
|
|
@@ -335,15 +500,18 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
335
500
|
"""Create task as sub-ticket of parent issue.
|
|
336
501
|
|
|
337
502
|
Args:
|
|
503
|
+
----
|
|
338
504
|
title: Task title
|
|
339
505
|
parent_id: Required parent issue ID
|
|
340
506
|
description: Task description
|
|
341
507
|
**kwargs: Additional adapter-specific fields
|
|
342
508
|
|
|
343
509
|
Returns:
|
|
510
|
+
-------
|
|
344
511
|
Created task or None if failed
|
|
345
512
|
|
|
346
513
|
Raises:
|
|
514
|
+
------
|
|
347
515
|
ValueError: If parent_id is not provided
|
|
348
516
|
|
|
349
517
|
"""
|
|
@@ -369,9 +537,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
369
537
|
"""List all tasks under an issue.
|
|
370
538
|
|
|
371
539
|
Args:
|
|
540
|
+
----
|
|
372
541
|
issue_id: Issue identifier
|
|
373
542
|
|
|
374
543
|
Returns:
|
|
544
|
+
-------
|
|
375
545
|
List of tasks belonging to issue
|
|
376
546
|
|
|
377
547
|
"""
|
|
@@ -390,14 +560,17 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
390
560
|
"""Attach a file to a ticket.
|
|
391
561
|
|
|
392
562
|
Args:
|
|
563
|
+
----
|
|
393
564
|
ticket_id: Ticket identifier
|
|
394
565
|
file_path: Local file path to upload
|
|
395
566
|
description: Optional attachment description
|
|
396
567
|
|
|
397
568
|
Returns:
|
|
569
|
+
-------
|
|
398
570
|
Created Attachment with metadata
|
|
399
571
|
|
|
400
572
|
Raises:
|
|
573
|
+
------
|
|
401
574
|
NotImplementedError: If adapter doesn't support attachments
|
|
402
575
|
FileNotFoundError: If file doesn't exist
|
|
403
576
|
ValueError: If ticket doesn't exist or upload fails
|
|
@@ -412,9 +585,11 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
412
585
|
"""Get all attachments for a ticket.
|
|
413
586
|
|
|
414
587
|
Args:
|
|
588
|
+
----
|
|
415
589
|
ticket_id: Ticket identifier
|
|
416
590
|
|
|
417
591
|
Returns:
|
|
592
|
+
-------
|
|
418
593
|
List of attachments (empty if none or not supported)
|
|
419
594
|
|
|
420
595
|
"""
|
|
@@ -430,13 +605,16 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
430
605
|
"""Delete an attachment (optional implementation).
|
|
431
606
|
|
|
432
607
|
Args:
|
|
608
|
+
----
|
|
433
609
|
ticket_id: Ticket identifier
|
|
434
610
|
attachment_id: Attachment identifier
|
|
435
611
|
|
|
436
612
|
Returns:
|
|
613
|
+
-------
|
|
437
614
|
True if deleted, False otherwise
|
|
438
615
|
|
|
439
616
|
Raises:
|
|
617
|
+
------
|
|
440
618
|
NotImplementedError: If adapter doesn't support deletion
|
|
441
619
|
|
|
442
620
|
"""
|
|
@@ -447,3 +625,356 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
447
625
|
async def close(self) -> None:
|
|
448
626
|
"""Close adapter and cleanup resources."""
|
|
449
627
|
pass
|
|
628
|
+
|
|
629
|
+
# Milestone Operations (Phase 1 - Abstract methods)
|
|
630
|
+
|
|
631
|
+
@abstractmethod
|
|
632
|
+
async def milestone_create(
|
|
633
|
+
self,
|
|
634
|
+
name: str,
|
|
635
|
+
target_date: datetime | None = None,
|
|
636
|
+
labels: list[str] | None = None,
|
|
637
|
+
description: str = "",
|
|
638
|
+
project_id: str | None = None,
|
|
639
|
+
) -> Milestone:
|
|
640
|
+
"""Create a new milestone.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
----
|
|
644
|
+
name: Milestone name
|
|
645
|
+
target_date: Target completion date (ISO format: YYYY-MM-DD)
|
|
646
|
+
labels: Labels that define this milestone
|
|
647
|
+
description: Milestone description
|
|
648
|
+
project_id: Associated project ID
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
-------
|
|
652
|
+
Created Milestone object
|
|
653
|
+
|
|
654
|
+
"""
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
@abstractmethod
|
|
658
|
+
async def milestone_get(self, milestone_id: str) -> Milestone | None:
|
|
659
|
+
"""Get milestone by ID with progress calculation.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
----
|
|
663
|
+
milestone_id: Milestone identifier
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
-------
|
|
667
|
+
Milestone object with calculated progress, None if not found
|
|
668
|
+
|
|
669
|
+
"""
|
|
670
|
+
pass
|
|
671
|
+
|
|
672
|
+
@abstractmethod
|
|
673
|
+
async def milestone_list(
|
|
674
|
+
self,
|
|
675
|
+
project_id: str | None = None,
|
|
676
|
+
state: str | None = None,
|
|
677
|
+
) -> builtins.list[Milestone]:
|
|
678
|
+
"""List milestones with optional filters.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
----
|
|
682
|
+
project_id: Filter by project
|
|
683
|
+
state: Filter by state (open, active, completed, closed)
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
-------
|
|
687
|
+
List of Milestone objects
|
|
688
|
+
|
|
689
|
+
"""
|
|
690
|
+
pass
|
|
691
|
+
|
|
692
|
+
@abstractmethod
|
|
693
|
+
async def milestone_update(
|
|
694
|
+
self,
|
|
695
|
+
milestone_id: str,
|
|
696
|
+
name: str | None = None,
|
|
697
|
+
target_date: datetime | None = None,
|
|
698
|
+
state: str | None = None,
|
|
699
|
+
labels: list[str] | None = None,
|
|
700
|
+
description: str | None = None,
|
|
701
|
+
) -> Milestone | None:
|
|
702
|
+
"""Update milestone properties.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
----
|
|
706
|
+
milestone_id: Milestone identifier
|
|
707
|
+
name: New name (optional)
|
|
708
|
+
target_date: New target date (optional)
|
|
709
|
+
state: New state (optional)
|
|
710
|
+
labels: New labels (optional)
|
|
711
|
+
description: New description (optional)
|
|
712
|
+
|
|
713
|
+
Returns:
|
|
714
|
+
-------
|
|
715
|
+
Updated Milestone object, None if not found
|
|
716
|
+
|
|
717
|
+
"""
|
|
718
|
+
pass
|
|
719
|
+
|
|
720
|
+
@abstractmethod
|
|
721
|
+
async def milestone_delete(self, milestone_id: str) -> bool:
|
|
722
|
+
"""Delete milestone.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
----
|
|
726
|
+
milestone_id: Milestone identifier
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
-------
|
|
730
|
+
True if deleted successfully, False otherwise
|
|
731
|
+
|
|
732
|
+
"""
|
|
733
|
+
pass
|
|
734
|
+
|
|
735
|
+
@abstractmethod
|
|
736
|
+
async def milestone_get_issues(
|
|
737
|
+
self,
|
|
738
|
+
milestone_id: str,
|
|
739
|
+
state: str | None = None,
|
|
740
|
+
) -> builtins.list[Task]:
|
|
741
|
+
"""Get issues associated with milestone.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
----
|
|
745
|
+
milestone_id: Milestone identifier
|
|
746
|
+
state: Filter by issue state (optional)
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
-------
|
|
750
|
+
List of Task objects (issues)
|
|
751
|
+
|
|
752
|
+
"""
|
|
753
|
+
pass
|
|
754
|
+
|
|
755
|
+
# Project Operations (Phase 1 - Abstract methods)
|
|
756
|
+
# These methods are optional - adapters that don't support projects
|
|
757
|
+
# can raise NotImplementedError with a helpful message
|
|
758
|
+
|
|
759
|
+
async def project_list(
|
|
760
|
+
self,
|
|
761
|
+
scope: ProjectScope | None = None,
|
|
762
|
+
state: ProjectState | None = None,
|
|
763
|
+
limit: int = 50,
|
|
764
|
+
offset: int = 0,
|
|
765
|
+
) -> builtins.list[Project]:
|
|
766
|
+
"""List projects with optional filters.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
----
|
|
770
|
+
scope: Filter by project scope (user, team, org, repo)
|
|
771
|
+
state: Filter by project state
|
|
772
|
+
limit: Maximum results (default: 50)
|
|
773
|
+
offset: Pagination offset (default: 0)
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
-------
|
|
777
|
+
List of Project objects
|
|
778
|
+
|
|
779
|
+
Raises:
|
|
780
|
+
------
|
|
781
|
+
NotImplementedError: If adapter doesn't support projects
|
|
782
|
+
|
|
783
|
+
"""
|
|
784
|
+
raise NotImplementedError(
|
|
785
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
786
|
+
"Use Epic operations for this adapter."
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
async def project_get(self, project_id: str) -> Project | None:
|
|
790
|
+
"""Get project by ID.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
----
|
|
794
|
+
project_id: Project identifier (platform-specific or unified)
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
-------
|
|
798
|
+
Project object if found, None otherwise
|
|
799
|
+
|
|
800
|
+
Raises:
|
|
801
|
+
------
|
|
802
|
+
NotImplementedError: If adapter doesn't support projects
|
|
803
|
+
|
|
804
|
+
"""
|
|
805
|
+
raise NotImplementedError(
|
|
806
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
807
|
+
"Use get_epic() for this adapter."
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
async def project_create(
|
|
811
|
+
self,
|
|
812
|
+
name: str,
|
|
813
|
+
description: str | None = None,
|
|
814
|
+
state: ProjectState = ProjectState.PLANNED,
|
|
815
|
+
target_date: datetime | None = None,
|
|
816
|
+
**kwargs: Any,
|
|
817
|
+
) -> Project:
|
|
818
|
+
"""Create new project.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
----
|
|
822
|
+
name: Project name (required)
|
|
823
|
+
description: Project description
|
|
824
|
+
state: Initial project state (default: PLANNED)
|
|
825
|
+
target_date: Target completion date
|
|
826
|
+
**kwargs: Platform-specific additional fields
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
-------
|
|
830
|
+
Created Project object
|
|
831
|
+
|
|
832
|
+
Raises:
|
|
833
|
+
------
|
|
834
|
+
NotImplementedError: If adapter doesn't support projects
|
|
835
|
+
|
|
836
|
+
"""
|
|
837
|
+
raise NotImplementedError(
|
|
838
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
839
|
+
"Use create_epic() for this adapter."
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
async def project_update(
|
|
843
|
+
self,
|
|
844
|
+
project_id: str,
|
|
845
|
+
name: str | None = None,
|
|
846
|
+
description: str | None = None,
|
|
847
|
+
state: ProjectState | None = None,
|
|
848
|
+
**kwargs: Any,
|
|
849
|
+
) -> Project | None:
|
|
850
|
+
"""Update project properties.
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
----
|
|
854
|
+
project_id: Project identifier
|
|
855
|
+
name: New name (optional)
|
|
856
|
+
description: New description (optional)
|
|
857
|
+
state: New state (optional)
|
|
858
|
+
**kwargs: Platform-specific fields to update
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
-------
|
|
862
|
+
Updated Project object, None if not found
|
|
863
|
+
|
|
864
|
+
Raises:
|
|
865
|
+
------
|
|
866
|
+
NotImplementedError: If adapter doesn't support projects
|
|
867
|
+
|
|
868
|
+
"""
|
|
869
|
+
raise NotImplementedError(
|
|
870
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
async def project_delete(self, project_id: str) -> bool:
|
|
874
|
+
"""Delete or archive project.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
----
|
|
878
|
+
project_id: Project identifier
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
-------
|
|
882
|
+
True if deleted successfully, False otherwise
|
|
883
|
+
|
|
884
|
+
Raises:
|
|
885
|
+
------
|
|
886
|
+
NotImplementedError: If adapter doesn't support projects
|
|
887
|
+
|
|
888
|
+
"""
|
|
889
|
+
raise NotImplementedError(
|
|
890
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
async def project_get_issues(
|
|
894
|
+
self, project_id: str, state: TicketState | None = None
|
|
895
|
+
) -> builtins.list[Task]:
|
|
896
|
+
"""Get all issues in project.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
----
|
|
900
|
+
project_id: Project identifier
|
|
901
|
+
state: Filter by issue state (optional)
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
-------
|
|
905
|
+
List of Task objects (issues in project)
|
|
906
|
+
|
|
907
|
+
Raises:
|
|
908
|
+
------
|
|
909
|
+
NotImplementedError: If adapter doesn't support projects
|
|
910
|
+
|
|
911
|
+
"""
|
|
912
|
+
raise NotImplementedError(
|
|
913
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
914
|
+
"Use list_issues_by_epic() for this adapter."
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
async def project_add_issue(self, project_id: str, issue_id: str) -> bool:
|
|
918
|
+
"""Add issue to project.
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
----
|
|
922
|
+
project_id: Project identifier
|
|
923
|
+
issue_id: Issue identifier to add
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
-------
|
|
927
|
+
True if added successfully, False otherwise
|
|
928
|
+
|
|
929
|
+
Raises:
|
|
930
|
+
------
|
|
931
|
+
NotImplementedError: If adapter doesn't support projects
|
|
932
|
+
|
|
933
|
+
"""
|
|
934
|
+
raise NotImplementedError(
|
|
935
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
async def project_remove_issue(self, project_id: str, issue_id: str) -> bool:
|
|
939
|
+
"""Remove issue from project.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
----
|
|
943
|
+
project_id: Project identifier
|
|
944
|
+
issue_id: Issue identifier to remove
|
|
945
|
+
|
|
946
|
+
Returns:
|
|
947
|
+
-------
|
|
948
|
+
True if removed successfully, False otherwise
|
|
949
|
+
|
|
950
|
+
Raises:
|
|
951
|
+
------
|
|
952
|
+
NotImplementedError: If adapter doesn't support projects
|
|
953
|
+
|
|
954
|
+
"""
|
|
955
|
+
raise NotImplementedError(
|
|
956
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
async def project_get_statistics(self, project_id: str) -> ProjectStatistics:
|
|
960
|
+
"""Get project statistics and metrics.
|
|
961
|
+
|
|
962
|
+
Calculates or retrieves statistics including issue counts by state,
|
|
963
|
+
progress percentage, and velocity metrics.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
----
|
|
967
|
+
project_id: Project identifier
|
|
968
|
+
|
|
969
|
+
Returns:
|
|
970
|
+
-------
|
|
971
|
+
ProjectStatistics object with calculated metrics
|
|
972
|
+
|
|
973
|
+
Raises:
|
|
974
|
+
------
|
|
975
|
+
NotImplementedError: If adapter doesn't support projects
|
|
976
|
+
|
|
977
|
+
"""
|
|
978
|
+
raise NotImplementedError(
|
|
979
|
+
f"{self.__class__.__name__} does not support project statistics."
|
|
980
|
+
)
|