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.

Files changed (129) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/aitrackdown.py +507 -6
  5. mcp_ticketer/adapters/asana/adapter.py +229 -0
  6. mcp_ticketer/adapters/asana/mappers.py +14 -0
  7. mcp_ticketer/adapters/github/__init__.py +26 -0
  8. mcp_ticketer/adapters/github/adapter.py +3229 -0
  9. mcp_ticketer/adapters/github/client.py +335 -0
  10. mcp_ticketer/adapters/github/mappers.py +797 -0
  11. mcp_ticketer/adapters/github/queries.py +692 -0
  12. mcp_ticketer/adapters/github/types.py +460 -0
  13. mcp_ticketer/adapters/hybrid.py +47 -5
  14. mcp_ticketer/adapters/jira/__init__.py +35 -0
  15. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  16. mcp_ticketer/adapters/jira/client.py +271 -0
  17. mcp_ticketer/adapters/jira/mappers.py +246 -0
  18. mcp_ticketer/adapters/jira/queries.py +216 -0
  19. mcp_ticketer/adapters/jira/types.py +304 -0
  20. mcp_ticketer/adapters/linear/adapter.py +2730 -139
  21. mcp_ticketer/adapters/linear/client.py +175 -3
  22. mcp_ticketer/adapters/linear/mappers.py +203 -8
  23. mcp_ticketer/adapters/linear/queries.py +280 -3
  24. mcp_ticketer/adapters/linear/types.py +120 -4
  25. mcp_ticketer/analysis/__init__.py +56 -0
  26. mcp_ticketer/analysis/dependency_graph.py +255 -0
  27. mcp_ticketer/analysis/health_assessment.py +304 -0
  28. mcp_ticketer/analysis/orphaned.py +218 -0
  29. mcp_ticketer/analysis/project_status.py +594 -0
  30. mcp_ticketer/analysis/similarity.py +224 -0
  31. mcp_ticketer/analysis/staleness.py +266 -0
  32. mcp_ticketer/automation/__init__.py +11 -0
  33. mcp_ticketer/automation/project_updates.py +378 -0
  34. mcp_ticketer/cli/adapter_diagnostics.py +3 -1
  35. mcp_ticketer/cli/auggie_configure.py +17 -5
  36. mcp_ticketer/cli/codex_configure.py +97 -61
  37. mcp_ticketer/cli/configure.py +1288 -105
  38. mcp_ticketer/cli/cursor_configure.py +314 -0
  39. mcp_ticketer/cli/diagnostics.py +13 -12
  40. mcp_ticketer/cli/discover.py +5 -0
  41. mcp_ticketer/cli/gemini_configure.py +17 -5
  42. mcp_ticketer/cli/init_command.py +880 -0
  43. mcp_ticketer/cli/install_mcp_server.py +418 -0
  44. mcp_ticketer/cli/instruction_commands.py +6 -0
  45. mcp_ticketer/cli/main.py +267 -3175
  46. mcp_ticketer/cli/mcp_configure.py +821 -119
  47. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  48. mcp_ticketer/cli/platform_detection.py +77 -12
  49. mcp_ticketer/cli/platform_installer.py +545 -0
  50. mcp_ticketer/cli/project_update_commands.py +350 -0
  51. mcp_ticketer/cli/setup_command.py +795 -0
  52. mcp_ticketer/cli/simple_health.py +12 -10
  53. mcp_ticketer/cli/ticket_commands.py +705 -103
  54. mcp_ticketer/cli/utils.py +113 -0
  55. mcp_ticketer/core/__init__.py +56 -6
  56. mcp_ticketer/core/adapter.py +533 -2
  57. mcp_ticketer/core/config.py +21 -21
  58. mcp_ticketer/core/exceptions.py +7 -1
  59. mcp_ticketer/core/label_manager.py +732 -0
  60. mcp_ticketer/core/mappers.py +31 -19
  61. mcp_ticketer/core/milestone_manager.py +252 -0
  62. mcp_ticketer/core/models.py +480 -0
  63. mcp_ticketer/core/onepassword_secrets.py +1 -1
  64. mcp_ticketer/core/priority_matcher.py +463 -0
  65. mcp_ticketer/core/project_config.py +132 -14
  66. mcp_ticketer/core/project_utils.py +281 -0
  67. mcp_ticketer/core/project_validator.py +376 -0
  68. mcp_ticketer/core/session_state.py +176 -0
  69. mcp_ticketer/core/state_matcher.py +625 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/mcp/server/__main__.py +2 -1
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/main.py +106 -25
  75. mcp_ticketer/mcp/server/routing.py +723 -0
  76. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  77. mcp_ticketer/mcp/server/tools/__init__.py +33 -11
  78. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  79. mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
  80. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  81. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  82. mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
  83. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  84. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
  85. mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
  86. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  87. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  88. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  89. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  90. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  91. mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
  92. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  93. mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
  94. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
  95. mcp_ticketer/queue/queue.py +68 -0
  96. mcp_ticketer/queue/worker.py +1 -1
  97. mcp_ticketer/utils/__init__.py +5 -0
  98. mcp_ticketer/utils/token_utils.py +246 -0
  99. mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
  100. mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
  101. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  102. py_mcp_installer/examples/phase3_demo.py +178 -0
  103. py_mcp_installer/scripts/manage_version.py +54 -0
  104. py_mcp_installer/setup.py +6 -0
  105. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  106. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  107. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  108. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  109. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  110. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  111. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  112. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  113. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  114. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  115. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  116. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  117. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  118. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  119. py_mcp_installer/tests/__init__.py +0 -0
  120. py_mcp_installer/tests/platforms/__init__.py +0 -0
  121. py_mcp_installer/tests/test_platform_detector.py +17 -0
  122. mcp_ticketer/adapters/github.py +0 -1574
  123. mcp_ticketer/adapters/jira.py +0 -1258
  124. mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
  125. mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
  126. mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
  127. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  128. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  129. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
@@ -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 Comment, Epic, SearchQuery, Task, TicketState, TicketType
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
- return current_state.can_transition_to(target_state)
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
+ )