mcp-ticketer 0.1.11__py3-none-any.whl → 0.1.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.

@@ -1,6 +1,6 @@
1
1
  """Core models and abstractions for MCP Ticketer."""
2
2
 
3
- from .models import Epic, Task, Comment, TicketState, Priority
3
+ from .models import Epic, Task, Comment, TicketState, Priority, TicketType
4
4
  from .adapter import BaseAdapter
5
5
  from .registry import AdapterRegistry
6
6
 
@@ -10,6 +10,7 @@ __all__ = [
10
10
  "Comment",
11
11
  "TicketState",
12
12
  "Priority",
13
+ "TicketType",
13
14
  "BaseAdapter",
14
15
  "AdapterRegistry",
15
16
  ]
@@ -1,8 +1,8 @@
1
1
  """Base adapter abstract class for ticket systems."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from typing import List, Optional, Dict, Any, TypeVar, Generic
5
- from .models import Epic, Task, Comment, SearchQuery, TicketState
4
+ from typing import List, Optional, Dict, Any, TypeVar, Generic, Union
5
+ from .models import Epic, Task, Comment, SearchQuery, TicketState, TicketType
6
6
 
7
7
  # Generic type for tickets
8
8
  T = TypeVar("T", Epic, Task)
@@ -206,6 +206,159 @@ class BaseAdapter(ABC, Generic[T]):
206
206
  return False
207
207
  return current_state.can_transition_to(target_state)
208
208
 
209
+ # Epic/Issue/Task Hierarchy Methods
210
+
211
+ async def create_epic(
212
+ self,
213
+ title: str,
214
+ description: Optional[str] = None,
215
+ **kwargs
216
+ ) -> Optional[Epic]:
217
+ """Create epic (top-level grouping).
218
+
219
+ Args:
220
+ title: Epic title
221
+ description: Epic description
222
+ **kwargs: Additional adapter-specific fields
223
+
224
+ Returns:
225
+ Created epic or None if failed
226
+ """
227
+ epic = Epic(
228
+ title=title,
229
+ description=description,
230
+ ticket_type=TicketType.EPIC,
231
+ **{k: v for k, v in kwargs.items() if k in Epic.__fields__}
232
+ )
233
+ result = await self.create(epic)
234
+ if isinstance(result, Epic):
235
+ return result
236
+ return None
237
+
238
+ async def get_epic(self, epic_id: str) -> Optional[Epic]:
239
+ """Get epic by ID.
240
+
241
+ Args:
242
+ epic_id: Epic identifier
243
+
244
+ Returns:
245
+ Epic if found, None otherwise
246
+ """
247
+ # Default implementation - subclasses should override for platform-specific logic
248
+ result = await self.read(epic_id)
249
+ if isinstance(result, Epic):
250
+ return result
251
+ return None
252
+
253
+ async def list_epics(self, **kwargs) -> List[Epic]:
254
+ """List all epics.
255
+
256
+ Args:
257
+ **kwargs: Adapter-specific filter parameters
258
+
259
+ Returns:
260
+ List of epics
261
+ """
262
+ # Default implementation - subclasses should override
263
+ filters = kwargs.copy()
264
+ filters["ticket_type"] = TicketType.EPIC
265
+ results = await self.list(filters=filters)
266
+ return [r for r in results if isinstance(r, Epic)]
267
+
268
+ async def create_issue(
269
+ self,
270
+ title: str,
271
+ description: Optional[str] = None,
272
+ epic_id: Optional[str] = None,
273
+ **kwargs
274
+ ) -> Optional[Task]:
275
+ """Create issue, optionally linked to epic.
276
+
277
+ Args:
278
+ title: Issue title
279
+ description: Issue description
280
+ epic_id: Optional parent epic ID
281
+ **kwargs: Additional adapter-specific fields
282
+
283
+ Returns:
284
+ Created issue or None if failed
285
+ """
286
+ task = Task(
287
+ title=title,
288
+ description=description,
289
+ ticket_type=TicketType.ISSUE,
290
+ parent_epic=epic_id,
291
+ **{k: v for k, v in kwargs.items() if k in Task.__fields__}
292
+ )
293
+ return await self.create(task)
294
+
295
+ async def list_issues_by_epic(self, epic_id: str) -> List[Task]:
296
+ """List all issues in epic.
297
+
298
+ Args:
299
+ epic_id: Epic identifier
300
+
301
+ Returns:
302
+ List of issues belonging to epic
303
+ """
304
+ # Default implementation - subclasses should override for efficiency
305
+ filters = {"parent_epic": epic_id, "ticket_type": TicketType.ISSUE}
306
+ results = await self.list(filters=filters)
307
+ return [r for r in results if isinstance(r, Task) and r.is_issue()]
308
+
309
+ async def create_task(
310
+ self,
311
+ title: str,
312
+ parent_id: str,
313
+ description: Optional[str] = None,
314
+ **kwargs
315
+ ) -> Optional[Task]:
316
+ """Create task as sub-ticket of parent issue.
317
+
318
+ Args:
319
+ title: Task title
320
+ parent_id: Required parent issue ID
321
+ description: Task description
322
+ **kwargs: Additional adapter-specific fields
323
+
324
+ Returns:
325
+ Created task or None if failed
326
+
327
+ Raises:
328
+ ValueError: If parent_id is not provided
329
+ """
330
+ if not parent_id:
331
+ raise ValueError("Tasks must have a parent_id (issue)")
332
+
333
+ task = Task(
334
+ title=title,
335
+ description=description,
336
+ ticket_type=TicketType.TASK,
337
+ parent_issue=parent_id,
338
+ **{k: v for k, v in kwargs.items() if k in Task.__fields__}
339
+ )
340
+
341
+ # Validate hierarchy before creating
342
+ errors = task.validate_hierarchy()
343
+ if errors:
344
+ raise ValueError(f"Invalid task hierarchy: {'; '.join(errors)}")
345
+
346
+ return await self.create(task)
347
+
348
+ async def list_tasks_by_issue(self, issue_id: str) -> List[Task]:
349
+ """List all tasks under an issue.
350
+
351
+ Args:
352
+ issue_id: Issue identifier
353
+
354
+ Returns:
355
+ List of tasks belonging to issue
356
+ """
357
+ # Default implementation - subclasses should override for efficiency
358
+ filters = {"parent_issue": issue_id, "ticket_type": TicketType.TASK}
359
+ results = await self.list(filters=filters)
360
+ return [r for r in results if isinstance(r, Task) and r.is_task()]
361
+
209
362
  async def close(self) -> None:
210
363
  """Close adapter and cleanup resources."""
211
364
  pass