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.
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/__init__.py +8 -1
- mcp_ticketer/adapters/aitrackdown.py +9 -5
- mcp_ticketer/adapters/hybrid.py +505 -0
- mcp_ticketer/adapters/linear.py +427 -3
- mcp_ticketer/cli/configure.py +532 -0
- mcp_ticketer/cli/discover.py +402 -0
- mcp_ticketer/cli/main.py +85 -0
- mcp_ticketer/cli/migrate_config.py +204 -0
- mcp_ticketer/core/__init__.py +2 -1
- mcp_ticketer/core/adapter.py +155 -2
- mcp_ticketer/core/env_discovery.py +555 -0
- mcp_ticketer/core/models.py +58 -6
- mcp_ticketer/core/project_config.py +606 -0
- mcp_ticketer/queue/queue.py +4 -1
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/METADATA +1 -1
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/RECORD +21 -15
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.11.dist-info → mcp_ticketer-0.1.13.dist-info}/top_level.txt +0 -0
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -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
|
]
|
mcp_ticketer/core/adapter.py
CHANGED
|
@@ -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
|