mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__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.

Files changed (42) hide show
  1. mcp_ticketer/__init__.py +7 -7
  2. mcp_ticketer/__version__.py +4 -2
  3. mcp_ticketer/adapters/__init__.py +4 -4
  4. mcp_ticketer/adapters/aitrackdown.py +66 -49
  5. mcp_ticketer/adapters/github.py +192 -125
  6. mcp_ticketer/adapters/hybrid.py +99 -53
  7. mcp_ticketer/adapters/jira.py +161 -151
  8. mcp_ticketer/adapters/linear.py +396 -246
  9. mcp_ticketer/cache/__init__.py +1 -1
  10. mcp_ticketer/cache/memory.py +15 -16
  11. mcp_ticketer/cli/__init__.py +1 -1
  12. mcp_ticketer/cli/configure.py +69 -93
  13. mcp_ticketer/cli/discover.py +43 -35
  14. mcp_ticketer/cli/main.py +283 -298
  15. mcp_ticketer/cli/mcp_configure.py +39 -15
  16. mcp_ticketer/cli/migrate_config.py +11 -13
  17. mcp_ticketer/cli/queue_commands.py +21 -58
  18. mcp_ticketer/cli/utils.py +121 -66
  19. mcp_ticketer/core/__init__.py +2 -2
  20. mcp_ticketer/core/adapter.py +46 -39
  21. mcp_ticketer/core/config.py +128 -92
  22. mcp_ticketer/core/env_discovery.py +69 -37
  23. mcp_ticketer/core/http_client.py +57 -40
  24. mcp_ticketer/core/mappers.py +98 -54
  25. mcp_ticketer/core/models.py +38 -24
  26. mcp_ticketer/core/project_config.py +145 -80
  27. mcp_ticketer/core/registry.py +16 -16
  28. mcp_ticketer/mcp/__init__.py +1 -1
  29. mcp_ticketer/mcp/server.py +199 -145
  30. mcp_ticketer/queue/__init__.py +2 -2
  31. mcp_ticketer/queue/__main__.py +1 -1
  32. mcp_ticketer/queue/manager.py +30 -26
  33. mcp_ticketer/queue/queue.py +147 -85
  34. mcp_ticketer/queue/run_worker.py +2 -3
  35. mcp_ticketer/queue/worker.py +55 -40
  36. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
  37. mcp_ticketer-0.1.23.dist-info/RECORD +42 -0
  38. mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
  39. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/WHEEL +0 -0
  40. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
  41. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
  42. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
1
  """Base adapter abstract class for ticket systems."""
2
2
 
3
+ import builtins
3
4
  from abc import ABC, abstractmethod
4
- from typing import List, Optional, Dict, Any, TypeVar, Generic, Union
5
- from .models import Epic, Task, Comment, SearchQuery, TicketState, TicketType
5
+ from typing import Any, Generic, Optional, TypeVar
6
+
7
+ from .models import Comment, Epic, SearchQuery, Task, TicketState, TicketType
6
8
 
7
9
  # Generic type for tickets
8
10
  T = TypeVar("T", Epic, Task)
@@ -11,21 +13,23 @@ T = TypeVar("T", Epic, Task)
11
13
  class BaseAdapter(ABC, Generic[T]):
12
14
  """Abstract base class for all ticket system adapters."""
13
15
 
14
- def __init__(self, config: Dict[str, Any]):
16
+ def __init__(self, config: dict[str, Any]):
15
17
  """Initialize adapter with configuration.
16
18
 
17
19
  Args:
18
20
  config: Adapter-specific configuration dictionary
21
+
19
22
  """
20
23
  self.config = config
21
24
  self._state_mapping = self._get_state_mapping()
22
25
 
23
26
  @abstractmethod
24
- def _get_state_mapping(self) -> Dict[TicketState, str]:
27
+ def _get_state_mapping(self) -> dict[TicketState, str]:
25
28
  """Get mapping from universal states to system-specific states.
26
29
 
27
30
  Returns:
28
31
  Dictionary mapping TicketState to system-specific state strings
32
+
29
33
  """
30
34
  pass
31
35
 
@@ -35,6 +39,7 @@ class BaseAdapter(ABC, Generic[T]):
35
39
 
36
40
  Returns:
37
41
  (is_valid, error_message) - Tuple of validation result and error message
42
+
38
43
  """
39
44
  pass
40
45
 
@@ -47,6 +52,7 @@ class BaseAdapter(ABC, Generic[T]):
47
52
 
48
53
  Returns:
49
54
  Created ticket with ID populated
55
+
50
56
  """
51
57
  pass
52
58
 
@@ -59,11 +65,12 @@ class BaseAdapter(ABC, Generic[T]):
59
65
 
60
66
  Returns:
61
67
  Ticket if found, None otherwise
68
+
62
69
  """
63
70
  pass
64
71
 
65
72
  @abstractmethod
66
- async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[T]:
73
+ async def update(self, ticket_id: str, updates: dict[str, Any]) -> Optional[T]:
67
74
  """Update a ticket.
68
75
 
69
76
  Args:
@@ -72,6 +79,7 @@ class BaseAdapter(ABC, Generic[T]):
72
79
 
73
80
  Returns:
74
81
  Updated ticket if successful, None otherwise
82
+
75
83
  """
76
84
  pass
77
85
 
@@ -84,16 +92,14 @@ class BaseAdapter(ABC, Generic[T]):
84
92
 
85
93
  Returns:
86
94
  True if deleted, False otherwise
95
+
87
96
  """
88
97
  pass
89
98
 
90
99
  @abstractmethod
91
100
  async def list(
92
- self,
93
- limit: int = 10,
94
- offset: int = 0,
95
- filters: Optional[Dict[str, Any]] = None
96
- ) -> List[T]:
101
+ self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
102
+ ) -> list[T]:
97
103
  """List tickets with pagination and filters.
98
104
 
99
105
  Args:
@@ -103,11 +109,12 @@ class BaseAdapter(ABC, Generic[T]):
103
109
 
104
110
  Returns:
105
111
  List of tickets matching criteria
112
+
106
113
  """
107
114
  pass
108
115
 
109
116
  @abstractmethod
110
- async def search(self, query: SearchQuery) -> List[T]:
117
+ async def search(self, query: SearchQuery) -> builtins.list[T]:
111
118
  """Search tickets using advanced query.
112
119
 
113
120
  Args:
@@ -115,14 +122,13 @@ class BaseAdapter(ABC, Generic[T]):
115
122
 
116
123
  Returns:
117
124
  List of tickets matching search criteria
125
+
118
126
  """
119
127
  pass
120
128
 
121
129
  @abstractmethod
122
130
  async def transition_state(
123
- self,
124
- ticket_id: str,
125
- target_state: TicketState
131
+ self, ticket_id: str, target_state: TicketState
126
132
  ) -> Optional[T]:
127
133
  """Transition ticket to a new state.
128
134
 
@@ -132,6 +138,7 @@ class BaseAdapter(ABC, Generic[T]):
132
138
 
133
139
  Returns:
134
140
  Updated ticket if transition successful, None otherwise
141
+
135
142
  """
136
143
  pass
137
144
 
@@ -144,16 +151,14 @@ class BaseAdapter(ABC, Generic[T]):
144
151
 
145
152
  Returns:
146
153
  Created comment with ID populated
154
+
147
155
  """
148
156
  pass
149
157
 
150
158
  @abstractmethod
151
159
  async def get_comments(
152
- self,
153
- ticket_id: str,
154
- limit: int = 10,
155
- offset: int = 0
156
- ) -> List[Comment]:
160
+ self, ticket_id: str, limit: int = 10, offset: int = 0
161
+ ) -> builtins.list[Comment]:
157
162
  """Get comments for a ticket.
158
163
 
159
164
  Args:
@@ -163,6 +168,7 @@ class BaseAdapter(ABC, Generic[T]):
163
168
 
164
169
  Returns:
165
170
  List of comments for the ticket
171
+
166
172
  """
167
173
  pass
168
174
 
@@ -174,6 +180,7 @@ class BaseAdapter(ABC, Generic[T]):
174
180
 
175
181
  Returns:
176
182
  System-specific state string
183
+
177
184
  """
178
185
  return self._state_mapping.get(state, state.value)
179
186
 
@@ -185,14 +192,13 @@ class BaseAdapter(ABC, Generic[T]):
185
192
 
186
193
  Returns:
187
194
  Universal ticket state
195
+
188
196
  """
189
197
  reverse_mapping = {v: k for k, v in self._state_mapping.items()}
190
198
  return reverse_mapping.get(system_state, TicketState.OPEN)
191
199
 
192
200
  async def validate_transition(
193
- self,
194
- ticket_id: str,
195
- target_state: TicketState
201
+ self, ticket_id: str, target_state: TicketState
196
202
  ) -> bool:
197
203
  """Validate if state transition is allowed.
198
204
 
@@ -202,6 +208,7 @@ class BaseAdapter(ABC, Generic[T]):
202
208
 
203
209
  Returns:
204
210
  True if transition is valid
211
+
205
212
  """
206
213
  ticket = await self.read(ticket_id)
207
214
  if not ticket:
@@ -218,10 +225,7 @@ class BaseAdapter(ABC, Generic[T]):
218
225
  # Epic/Issue/Task Hierarchy Methods
219
226
 
220
227
  async def create_epic(
221
- self,
222
- title: str,
223
- description: Optional[str] = None,
224
- **kwargs
228
+ self, title: str, description: Optional[str] = None, **kwargs
225
229
  ) -> Optional[Epic]:
226
230
  """Create epic (top-level grouping).
227
231
 
@@ -232,12 +236,13 @@ class BaseAdapter(ABC, Generic[T]):
232
236
 
233
237
  Returns:
234
238
  Created epic or None if failed
239
+
235
240
  """
236
241
  epic = Epic(
237
242
  title=title,
238
243
  description=description,
239
244
  ticket_type=TicketType.EPIC,
240
- **{k: v for k, v in kwargs.items() if k in Epic.__fields__}
245
+ **{k: v for k, v in kwargs.items() if k in Epic.__fields__},
241
246
  )
242
247
  result = await self.create(epic)
243
248
  if isinstance(result, Epic):
@@ -252,6 +257,7 @@ class BaseAdapter(ABC, Generic[T]):
252
257
 
253
258
  Returns:
254
259
  Epic if found, None otherwise
260
+
255
261
  """
256
262
  # Default implementation - subclasses should override for platform-specific logic
257
263
  result = await self.read(epic_id)
@@ -259,7 +265,7 @@ class BaseAdapter(ABC, Generic[T]):
259
265
  return result
260
266
  return None
261
267
 
262
- async def list_epics(self, **kwargs) -> List[Epic]:
268
+ async def list_epics(self, **kwargs) -> builtins.list[Epic]:
263
269
  """List all epics.
264
270
 
265
271
  Args:
@@ -267,6 +273,7 @@ class BaseAdapter(ABC, Generic[T]):
267
273
 
268
274
  Returns:
269
275
  List of epics
276
+
270
277
  """
271
278
  # Default implementation - subclasses should override
272
279
  filters = kwargs.copy()
@@ -279,7 +286,7 @@ class BaseAdapter(ABC, Generic[T]):
279
286
  title: str,
280
287
  description: Optional[str] = None,
281
288
  epic_id: Optional[str] = None,
282
- **kwargs
289
+ **kwargs,
283
290
  ) -> Optional[Task]:
284
291
  """Create issue, optionally linked to epic.
285
292
 
@@ -291,17 +298,18 @@ class BaseAdapter(ABC, Generic[T]):
291
298
 
292
299
  Returns:
293
300
  Created issue or None if failed
301
+
294
302
  """
295
303
  task = Task(
296
304
  title=title,
297
305
  description=description,
298
306
  ticket_type=TicketType.ISSUE,
299
307
  parent_epic=epic_id,
300
- **{k: v for k, v in kwargs.items() if k in Task.__fields__}
308
+ **{k: v for k, v in kwargs.items() if k in Task.__fields__},
301
309
  )
302
310
  return await self.create(task)
303
311
 
304
- async def list_issues_by_epic(self, epic_id: str) -> List[Task]:
312
+ async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
305
313
  """List all issues in epic.
306
314
 
307
315
  Args:
@@ -309,6 +317,7 @@ class BaseAdapter(ABC, Generic[T]):
309
317
 
310
318
  Returns:
311
319
  List of issues belonging to epic
320
+
312
321
  """
313
322
  # Default implementation - subclasses should override for efficiency
314
323
  filters = {"parent_epic": epic_id, "ticket_type": TicketType.ISSUE}
@@ -316,11 +325,7 @@ class BaseAdapter(ABC, Generic[T]):
316
325
  return [r for r in results if isinstance(r, Task) and r.is_issue()]
317
326
 
318
327
  async def create_task(
319
- self,
320
- title: str,
321
- parent_id: str,
322
- description: Optional[str] = None,
323
- **kwargs
328
+ self, title: str, parent_id: str, description: Optional[str] = None, **kwargs
324
329
  ) -> Optional[Task]:
325
330
  """Create task as sub-ticket of parent issue.
326
331
 
@@ -335,6 +340,7 @@ class BaseAdapter(ABC, Generic[T]):
335
340
 
336
341
  Raises:
337
342
  ValueError: If parent_id is not provided
343
+
338
344
  """
339
345
  if not parent_id:
340
346
  raise ValueError("Tasks must have a parent_id (issue)")
@@ -344,7 +350,7 @@ class BaseAdapter(ABC, Generic[T]):
344
350
  description=description,
345
351
  ticket_type=TicketType.TASK,
346
352
  parent_issue=parent_id,
347
- **{k: v for k, v in kwargs.items() if k in Task.__fields__}
353
+ **{k: v for k, v in kwargs.items() if k in Task.__fields__},
348
354
  )
349
355
 
350
356
  # Validate hierarchy before creating
@@ -354,7 +360,7 @@ class BaseAdapter(ABC, Generic[T]):
354
360
 
355
361
  return await self.create(task)
356
362
 
357
- async def list_tasks_by_issue(self, issue_id: str) -> List[Task]:
363
+ async def list_tasks_by_issue(self, issue_id: str) -> builtins.list[Task]:
358
364
  """List all tasks under an issue.
359
365
 
360
366
  Args:
@@ -362,6 +368,7 @@ class BaseAdapter(ABC, Generic[T]):
362
368
 
363
369
  Returns:
364
370
  List of tasks belonging to issue
371
+
365
372
  """
366
373
  # Default implementation - subclasses should override for efficiency
367
374
  filters = {"parent_issue": issue_id, "ticket_type": TicketType.TASK}
@@ -370,4 +377,4 @@ class BaseAdapter(ABC, Generic[T]):
370
377
 
371
378
  async def close(self) -> None:
372
379
  """Close adapter and cleanup resources."""
373
- pass
380
+ pass