mcp-ticketer 0.3.1__py3-none-any.whl → 0.3.3__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 (41) hide show
  1. mcp_ticketer/__version__.py +1 -1
  2. mcp_ticketer/adapters/aitrackdown.py +164 -36
  3. mcp_ticketer/adapters/github.py +11 -8
  4. mcp_ticketer/adapters/jira.py +29 -28
  5. mcp_ticketer/adapters/linear/__init__.py +1 -1
  6. mcp_ticketer/adapters/linear/adapter.py +105 -104
  7. mcp_ticketer/adapters/linear/client.py +78 -59
  8. mcp_ticketer/adapters/linear/mappers.py +93 -73
  9. mcp_ticketer/adapters/linear/queries.py +28 -7
  10. mcp_ticketer/adapters/linear/types.py +67 -60
  11. mcp_ticketer/adapters/linear.py +2 -2
  12. mcp_ticketer/cli/adapter_diagnostics.py +87 -52
  13. mcp_ticketer/cli/codex_configure.py +6 -6
  14. mcp_ticketer/cli/diagnostics.py +180 -88
  15. mcp_ticketer/cli/linear_commands.py +156 -113
  16. mcp_ticketer/cli/main.py +153 -82
  17. mcp_ticketer/cli/simple_health.py +74 -51
  18. mcp_ticketer/cli/utils.py +15 -10
  19. mcp_ticketer/core/config.py +23 -19
  20. mcp_ticketer/core/env_discovery.py +5 -4
  21. mcp_ticketer/core/env_loader.py +114 -91
  22. mcp_ticketer/core/exceptions.py +22 -20
  23. mcp_ticketer/core/models.py +9 -0
  24. mcp_ticketer/core/project_config.py +1 -1
  25. mcp_ticketer/mcp/constants.py +58 -0
  26. mcp_ticketer/mcp/dto.py +195 -0
  27. mcp_ticketer/mcp/response_builder.py +206 -0
  28. mcp_ticketer/mcp/server.py +361 -1182
  29. mcp_ticketer/queue/health_monitor.py +166 -135
  30. mcp_ticketer/queue/manager.py +70 -19
  31. mcp_ticketer/queue/queue.py +24 -5
  32. mcp_ticketer/queue/run_worker.py +1 -1
  33. mcp_ticketer/queue/ticket_registry.py +203 -145
  34. mcp_ticketer/queue/worker.py +79 -43
  35. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/METADATA +1 -1
  36. mcp_ticketer-0.3.3.dist-info/RECORD +62 -0
  37. mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
  38. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/WHEEL +0 -0
  39. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/entry_points.txt +0 -0
  40. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/licenses/LICENSE +0 -0
  41. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/top_level.txt +0 -0
@@ -2,10 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import asyncio
6
5
  import os
7
- from datetime import datetime
8
- from typing import Any, Dict, List, Optional, Union
6
+ from typing import Any
9
7
 
10
8
  try:
11
9
  from gql import gql
@@ -14,23 +12,15 @@ except ImportError:
14
12
  gql = None
15
13
  TransportQueryError = Exception
16
14
 
15
+ import builtins
16
+
17
17
  from ...core.adapter import BaseAdapter
18
- from ...core.models import (
19
- Comment,
20
- Epic,
21
- Priority,
22
- SearchQuery,
23
- Task,
24
- TicketState,
25
- TicketType,
26
- )
18
+ from ...core.models import Comment, Epic, SearchQuery, Task, TicketState
27
19
  from ...core.registry import AdapterRegistry
28
-
29
20
  from .client import LinearGraphQLClient
30
21
  from .mappers import (
31
22
  build_linear_issue_input,
32
23
  build_linear_issue_update_input,
33
- extract_child_issue_ids,
34
24
  map_linear_comment_to_comment,
35
25
  map_linear_issue_to_task,
36
26
  map_linear_project_to_epic,
@@ -38,7 +28,6 @@ from .mappers import (
38
28
  from .queries import (
39
29
  ALL_FRAGMENTS,
40
30
  CREATE_ISSUE_MUTATION,
41
- GET_CURRENT_USER_QUERY,
42
31
  LIST_ISSUES_QUERY,
43
32
  SEARCH_ISSUES_QUERY,
44
33
  UPDATE_ISSUE_MUTATION,
@@ -49,22 +38,21 @@ from .types import (
49
38
  build_issue_filter,
50
39
  get_linear_priority,
51
40
  get_linear_state_type,
52
- get_universal_state,
53
41
  )
54
42
 
55
43
 
56
44
  class LinearAdapter(BaseAdapter[Task]):
57
45
  """Adapter for Linear issue tracking system using native GraphQL API.
58
-
46
+
59
47
  This adapter provides comprehensive integration with Linear's GraphQL API,
60
48
  supporting all major ticket management operations including:
61
-
49
+
62
50
  - CRUD operations for issues and projects
63
51
  - State transitions and workflow management
64
52
  - User assignment and search functionality
65
53
  - Comment management
66
54
  - Epic/Issue/Task hierarchy support
67
-
55
+
68
56
  The adapter is organized into multiple modules for better maintainability:
69
57
  - client.py: GraphQL client management
70
58
  - queries.py: GraphQL queries and fragments
@@ -72,7 +60,7 @@ class LinearAdapter(BaseAdapter[Task]):
72
60
  - mappers.py: Data transformation logic
73
61
  """
74
62
 
75
- def __init__(self, config: Dict[str, Any]):
63
+ def __init__(self, config: dict[str, Any]):
76
64
  """Initialize Linear adapter.
77
65
 
78
66
  Args:
@@ -85,89 +73,94 @@ class LinearAdapter(BaseAdapter[Task]):
85
73
 
86
74
  Raises:
87
75
  ValueError: If required configuration is missing
76
+
88
77
  """
89
78
  # Initialize instance variables before calling super().__init__
90
79
  # because parent constructor calls _get_state_mapping()
91
- self._team_data: Optional[Dict[str, Any]] = None
92
- self._workflow_states: Optional[Dict[str, Dict[str, Any]]] = None
93
- self._labels_cache: Optional[List[Dict[str, Any]]] = None
94
- self._users_cache: Optional[Dict[str, Dict[str, Any]]] = None
80
+ self._team_data: dict[str, Any] | None = None
81
+ self._workflow_states: dict[str, dict[str, Any]] | None = None
82
+ self._labels_cache: list[dict[str, Any]] | None = None
83
+ self._users_cache: dict[str, dict[str, Any]] | None = None
95
84
  self._initialized = False
96
85
 
97
86
  super().__init__(config)
98
-
87
+
99
88
  # Extract configuration
100
89
  self.api_key = config.get("api_key") or os.getenv("LINEAR_API_KEY")
101
90
  if not self.api_key:
102
- raise ValueError("Linear API key is required (api_key or LINEAR_API_KEY env var)")
103
-
104
- # Ensure API key has Bearer prefix
105
- if not self.api_key.startswith("Bearer "):
106
- self.api_key = f"Bearer {self.api_key}"
107
-
91
+ raise ValueError(
92
+ "Linear API key is required (api_key or LINEAR_API_KEY env var)"
93
+ )
94
+
95
+ # Clean API key - remove Bearer prefix if accidentally included in config
96
+ # (The client will add it back when making requests)
97
+ if self.api_key.startswith("Bearer "):
98
+ self.api_key = self.api_key.replace("Bearer ", "")
99
+
108
100
  self.workspace = config.get("workspace", "")
109
101
  self.team_key = config.get("team_key")
110
102
  self.team_id = config.get("team_id")
111
103
  self.api_url = config.get("api_url", "https://api.linear.app/graphql")
112
-
104
+
113
105
  # Validate team configuration
114
106
  if not self.team_key and not self.team_id:
115
107
  raise ValueError("Either team_key or team_id must be provided")
116
-
117
- # Initialize client
118
- api_key_clean = self.api_key.replace("Bearer ", "")
119
- self.client = LinearGraphQLClient(api_key_clean)
108
+
109
+ # Initialize client with clean API key
110
+ self.client = LinearGraphQLClient(self.api_key)
120
111
 
121
112
  def validate_credentials(self) -> tuple[bool, str]:
122
113
  """Validate Linear API credentials.
123
-
114
+
124
115
  Returns:
125
116
  Tuple of (is_valid, error_message)
117
+
126
118
  """
127
119
  if not self.api_key:
128
120
  return False, "Linear API key is required"
129
-
121
+
130
122
  if not self.team_key and not self.team_id:
131
123
  return False, "Either team_key or team_id must be provided"
132
-
124
+
133
125
  return True, ""
134
126
 
135
127
  async def initialize(self) -> None:
136
128
  """Initialize adapter by preloading team, states, and labels data concurrently."""
137
129
  if self._initialized:
138
130
  return
139
-
131
+
140
132
  try:
141
133
  # Test connection first
142
134
  if not await self.client.test_connection():
143
135
  raise ValueError("Failed to connect to Linear API - check credentials")
144
-
136
+
145
137
  # Load team data and workflow states concurrently
146
138
  team_id = await self._ensure_team_id()
147
-
139
+
148
140
  # Load workflow states for the team
149
141
  await self._load_workflow_states(team_id)
150
-
142
+
151
143
  self._initialized = True
152
-
144
+
153
145
  except Exception as e:
154
146
  raise ValueError(f"Failed to initialize Linear adapter: {e}")
155
147
 
156
148
  async def _ensure_team_id(self) -> str:
157
149
  """Ensure we have a team ID, resolving from team_key if needed.
158
-
150
+
159
151
  Returns:
160
152
  Linear team UUID
161
-
153
+
162
154
  Raises:
163
155
  ValueError: If team cannot be found or resolved
156
+
164
157
  """
165
158
  if self.team_id:
166
159
  return self.team_id
167
-
160
+
168
161
  if not self.team_key:
169
162
  raise ValueError("Either team_id or team_key must be provided")
170
-
163
+
171
164
  # Query team by key
172
165
  query = """
173
166
  query GetTeamByKey($key: String!) {
@@ -181,35 +174,35 @@ class LinearAdapter(BaseAdapter[Task]):
181
174
  }
182
175
  }
183
176
  """
184
-
177
+
185
178
  try:
186
179
  result = await self.client.execute_query(query, {"key": self.team_key})
187
180
  teams = result.get("teams", {}).get("nodes", [])
188
-
181
+
189
182
  if not teams:
190
183
  raise ValueError(f"Team with key '{self.team_key}' not found")
191
-
184
+
192
185
  team = teams[0]
193
186
  self.team_id = team["id"]
194
187
  self._team_data = team
195
-
188
+
196
189
  return self.team_id
197
-
190
+
198
191
  except Exception as e:
199
192
  raise ValueError(f"Failed to resolve team '{self.team_key}': {e}")
200
193
 
201
194
  async def _load_workflow_states(self, team_id: str) -> None:
202
195
  """Load and cache workflow states for the team.
203
-
196
+
204
197
  Args:
205
198
  team_id: Linear team ID
199
+
206
200
  """
207
201
  try:
208
202
  result = await self.client.execute_query(
209
- WORKFLOW_STATES_QUERY,
210
- {"teamId": team_id}
203
+ WORKFLOW_STATES_QUERY, {"teamId": team_id}
211
204
  )
212
-
205
+
213
206
  workflow_states = {}
214
207
  for state in result["workflowStates"]["nodes"]:
215
208
  state_type = state["type"].lower()
@@ -217,23 +210,24 @@ class LinearAdapter(BaseAdapter[Task]):
217
210
  workflow_states[state_type] = state
218
211
  elif state["position"] < workflow_states[state_type]["position"]:
219
212
  workflow_states[state_type] = state
220
-
213
+
221
214
  self._workflow_states = workflow_states
222
-
215
+
223
216
  except Exception as e:
224
217
  raise ValueError(f"Failed to load workflow states: {e}")
225
218
 
226
- def _get_state_mapping(self) -> Dict[TicketState, str]:
219
+ def _get_state_mapping(self) -> dict[TicketState, str]:
227
220
  """Get mapping from universal states to Linear workflow state IDs.
228
-
221
+
229
222
  Returns:
230
223
  Dictionary mapping TicketState to Linear state ID
224
+
231
225
  """
232
226
  if not self._workflow_states:
233
227
  # Return type-based mapping if states not loaded
234
228
  return {
235
229
  TicketState.OPEN: "unstarted",
236
- TicketState.IN_PROGRESS: "started",
230
+ TicketState.IN_PROGRESS: "started",
237
231
  TicketState.READY: "unstarted",
238
232
  TicketState.TESTED: "started",
239
233
  TicketState.DONE: "completed",
@@ -241,7 +235,7 @@ class LinearAdapter(BaseAdapter[Task]):
241
235
  TicketState.WAITING: "unstarted",
242
236
  TicketState.BLOCKED: "unstarted",
243
237
  }
244
-
238
+
245
239
  # Return ID-based mapping using cached workflow states
246
240
  mapping = {}
247
241
  for universal_state, linear_type in LinearStateMapping.TO_LINEAR.items():
@@ -250,30 +244,31 @@ class LinearAdapter(BaseAdapter[Task]):
250
244
  else:
251
245
  # Fallback to type name
252
246
  mapping[universal_state] = linear_type
253
-
247
+
254
248
  return mapping
255
249
 
256
- async def _get_user_id(self, user_identifier: str) -> Optional[str]:
250
+ async def _get_user_id(self, user_identifier: str) -> str | None:
257
251
  """Get Linear user ID from email or display name.
258
-
252
+
259
253
  Args:
260
254
  user_identifier: Email address or display name
261
-
255
+
262
256
  Returns:
263
257
  Linear user ID or None if not found
258
+
264
259
  """
265
260
  # Try to get user by email first
266
261
  user = await self.client.get_user_by_email(user_identifier)
267
262
  if user:
268
263
  return user["id"]
269
-
264
+
270
265
  # If not found by email, could implement search by display name
271
266
  # For now, assume the identifier is already a user ID
272
267
  return user_identifier if user_identifier else None
273
268
 
274
269
  # CRUD Operations
275
270
 
276
- async def create(self, ticket: Union[Epic, Task]) -> Union[Epic, Task]:
271
+ async def create(self, ticket: Epic | Task) -> Epic | Task:
277
272
  """Create a new Linear issue or project with full field support.
278
273
 
279
274
  Args:
@@ -284,6 +279,7 @@ class LinearAdapter(BaseAdapter[Task]):
284
279
 
285
280
  Raises:
286
281
  ValueError: If credentials are invalid or creation fails
282
+
287
283
  """
288
284
  # Validate credentials before attempting operation
289
285
  is_valid, error_message = self.validate_credentials()
@@ -308,6 +304,7 @@ class LinearAdapter(BaseAdapter[Task]):
308
304
 
309
305
  Returns:
310
306
  Created task with Linear metadata
307
+
311
308
  """
312
309
  team_id = await self._ensure_team_id()
313
310
 
@@ -322,8 +319,7 @@ class LinearAdapter(BaseAdapter[Task]):
322
319
 
323
320
  try:
324
321
  result = await self.client.execute_mutation(
325
- CREATE_ISSUE_MUTATION,
326
- {"input": issue_input}
322
+ CREATE_ISSUE_MUTATION, {"input": issue_input}
327
323
  )
328
324
 
329
325
  if not result["issueCreate"]["success"]:
@@ -343,6 +339,7 @@ class LinearAdapter(BaseAdapter[Task]):
343
339
 
344
340
  Returns:
345
341
  Created epic with Linear metadata
342
+
346
343
  """
347
344
  team_id = await self._ensure_team_id()
348
345
 
@@ -387,8 +384,7 @@ class LinearAdapter(BaseAdapter[Task]):
387
384
 
388
385
  try:
389
386
  result = await self.client.execute_mutation(
390
- create_query,
391
- {"input": project_input}
387
+ create_query, {"input": project_input}
392
388
  )
393
389
 
394
390
  if not result["projectCreate"]["success"]:
@@ -400,7 +396,7 @@ class LinearAdapter(BaseAdapter[Task]):
400
396
  except Exception as e:
401
397
  raise ValueError(f"Failed to create Linear project: {e}")
402
398
 
403
- async def read(self, ticket_id: str) -> Optional[Task]:
399
+ async def read(self, ticket_id: str) -> Task | None:
404
400
  """Read a Linear issue by identifier with full details.
405
401
 
406
402
  Args:
@@ -408,25 +404,26 @@ class LinearAdapter(BaseAdapter[Task]):
408
404
 
409
405
  Returns:
410
406
  Task with full details or None if not found
407
+
411
408
  """
412
409
  # Validate credentials before attempting operation
413
410
  is_valid, error_message = self.validate_credentials()
414
411
  if not is_valid:
415
412
  raise ValueError(error_message)
416
413
 
417
- query = ALL_FRAGMENTS + """
414
+ query = (
415
+ ALL_FRAGMENTS
416
+ + """
418
417
  query GetIssue($identifier: String!) {
419
418
  issue(id: $identifier) {
420
419
  ...IssueFullFields
421
420
  }
422
421
  }
423
422
  """
423
+ )
424
424
 
425
425
  try:
426
- result = await self.client.execute_query(
427
- query,
428
- {"identifier": ticket_id}
429
- )
426
+ result = await self.client.execute_query(query, {"identifier": ticket_id})
430
427
 
431
428
  if result.get("issue"):
432
429
  return map_linear_issue_to_task(result["issue"])
@@ -437,7 +434,7 @@ class LinearAdapter(BaseAdapter[Task]):
437
434
 
438
435
  return None
439
436
 
440
- async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[Task]:
437
+ async def update(self, ticket_id: str, updates: dict[str, Any]) -> Task | None:
441
438
  """Update a Linear issue with comprehensive field support.
442
439
 
443
440
  Args:
@@ -446,6 +443,7 @@ class LinearAdapter(BaseAdapter[Task]):
446
443
 
447
444
  Returns:
448
445
  Updated task or None if not found
446
+
449
447
  """
450
448
  # Validate credentials before attempting operation
451
449
  is_valid, error_message = self.validate_credentials()
@@ -463,8 +461,7 @@ class LinearAdapter(BaseAdapter[Task]):
463
461
 
464
462
  try:
465
463
  result = await self.client.execute_query(
466
- id_query,
467
- {"identifier": ticket_id}
464
+ id_query, {"identifier": ticket_id}
468
465
  )
469
466
 
470
467
  if not result.get("issue"):
@@ -477,7 +474,11 @@ class LinearAdapter(BaseAdapter[Task]):
477
474
 
478
475
  # Handle state transitions
479
476
  if "state" in updates:
480
- target_state = TicketState(updates["state"]) if isinstance(updates["state"], str) else updates["state"]
477
+ target_state = (
478
+ TicketState(updates["state"])
479
+ if isinstance(updates["state"], str)
480
+ else updates["state"]
481
+ )
481
482
  state_mapping = self._get_state_mapping()
482
483
  if target_state in state_mapping:
483
484
  update_input["stateId"] = state_mapping[target_state]
@@ -490,8 +491,7 @@ class LinearAdapter(BaseAdapter[Task]):
490
491
 
491
492
  # Execute update
492
493
  result = await self.client.execute_mutation(
493
- UPDATE_ISSUE_MUTATION,
494
- {"id": linear_id, "input": update_input}
494
+ UPDATE_ISSUE_MUTATION, {"id": linear_id, "input": update_input}
495
495
  )
496
496
 
497
497
  if not result["issueUpdate"]["success"]:
@@ -511,6 +511,7 @@ class LinearAdapter(BaseAdapter[Task]):
511
511
 
512
512
  Returns:
513
513
  True if successfully deleted/archived
514
+
514
515
  """
515
516
  # Linear doesn't support true deletion, so we archive the issue
516
517
  try:
@@ -520,11 +521,8 @@ class LinearAdapter(BaseAdapter[Task]):
520
521
  return False
521
522
 
522
523
  async def list(
523
- self,
524
- limit: int = 10,
525
- offset: int = 0,
526
- filters: Optional[Dict[str, Any]] = None
527
- ) -> List[Task]:
524
+ self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None
525
+ ) -> builtins.list[Task]:
528
526
  """List Linear issues with optional filtering.
529
527
 
530
528
  Args:
@@ -534,6 +532,7 @@ class LinearAdapter(BaseAdapter[Task]):
534
532
 
535
533
  Returns:
536
534
  List of tasks matching the criteria
535
+
537
536
  """
538
537
  # Validate credentials
539
538
  is_valid, error_message = self.validate_credentials()
@@ -548,7 +547,9 @@ class LinearAdapter(BaseAdapter[Task]):
548
547
  team_id=team_id,
549
548
  state=filters.get("state") if filters else None,
550
549
  priority=filters.get("priority") if filters else None,
551
- include_archived=filters.get("includeArchived", False) if filters else False,
550
+ include_archived=(
551
+ filters.get("includeArchived", False) if filters else False
552
+ ),
552
553
  )
553
554
 
554
555
  # Add additional filters
@@ -567,8 +568,7 @@ class LinearAdapter(BaseAdapter[Task]):
567
568
 
568
569
  try:
569
570
  result = await self.client.execute_query(
570
- LIST_ISSUES_QUERY,
571
- {"filter": issue_filter, "first": limit}
571
+ LIST_ISSUES_QUERY, {"filter": issue_filter, "first": limit}
572
572
  )
573
573
 
574
574
  tasks = []
@@ -580,7 +580,7 @@ class LinearAdapter(BaseAdapter[Task]):
580
580
  except Exception as e:
581
581
  raise ValueError(f"Failed to list Linear issues: {e}")
582
582
 
583
- async def search(self, query: SearchQuery) -> List[Task]:
583
+ async def search(self, query: SearchQuery) -> builtins.list[Task]:
584
584
  """Search Linear issues using comprehensive filters.
585
585
 
586
586
  Args:
@@ -588,6 +588,7 @@ class LinearAdapter(BaseAdapter[Task]):
588
588
 
589
589
  Returns:
590
590
  List of tasks matching the search criteria
591
+
591
592
  """
592
593
  # Validate credentials
593
594
  is_valid, error_message = self.validate_credentials()
@@ -631,8 +632,7 @@ class LinearAdapter(BaseAdapter[Task]):
631
632
 
632
633
  try:
633
634
  result = await self.client.execute_query(
634
- SEARCH_ISSUES_QUERY,
635
- {"filter": issue_filter, "first": query.limit}
635
+ SEARCH_ISSUES_QUERY, {"filter": issue_filter, "first": query.limit}
636
636
  )
637
637
 
638
638
  tasks = []
@@ -646,7 +646,7 @@ class LinearAdapter(BaseAdapter[Task]):
646
646
 
647
647
  async def transition_state(
648
648
  self, ticket_id: str, target_state: TicketState
649
- ) -> Optional[Task]:
649
+ ) -> Task | None:
650
650
  """Transition Linear issue to new state with workflow validation.
651
651
 
652
652
  Args:
@@ -655,6 +655,7 @@ class LinearAdapter(BaseAdapter[Task]):
655
655
 
656
656
  Returns:
657
657
  Updated task or None if transition failed
658
+
658
659
  """
659
660
  # Validate transition
660
661
  if not await self.validate_transition(ticket_id, target_state):
@@ -674,6 +675,7 @@ class LinearAdapter(BaseAdapter[Task]):
674
675
 
675
676
  Returns:
676
677
  True if transition is valid
678
+
677
679
  """
678
680
  # For now, allow all transitions
679
681
  # In practice, you might want to implement Linear's workflow rules
@@ -687,6 +689,7 @@ class LinearAdapter(BaseAdapter[Task]):
687
689
 
688
690
  Returns:
689
691
  Created comment with ID
692
+
690
693
  """
691
694
  # First get the Linear internal ID
692
695
  id_query = """
@@ -699,8 +702,7 @@ class LinearAdapter(BaseAdapter[Task]):
699
702
 
700
703
  try:
701
704
  result = await self.client.execute_query(
702
- id_query,
703
- {"identifier": comment.ticket_id}
705
+ id_query, {"identifier": comment.ticket_id}
704
706
  )
705
707
 
706
708
  if not result.get("issue"):
@@ -735,8 +737,7 @@ class LinearAdapter(BaseAdapter[Task]):
735
737
  }
736
738
 
737
739
  result = await self.client.execute_mutation(
738
- create_comment_query,
739
- {"input": comment_input}
740
+ create_comment_query, {"input": comment_input}
740
741
  )
741
742
 
742
743
  if not result["commentCreate"]["success"]:
@@ -750,7 +751,7 @@ class LinearAdapter(BaseAdapter[Task]):
750
751
 
751
752
  async def get_comments(
752
753
  self, ticket_id: str, limit: int = 10, offset: int = 0
753
- ) -> List[Comment]:
754
+ ) -> builtins.list[Comment]:
754
755
  """Get comments for a Linear issue.
755
756
 
756
757
  Args:
@@ -760,6 +761,7 @@ class LinearAdapter(BaseAdapter[Task]):
760
761
 
761
762
  Returns:
762
763
  List of comments for the issue
764
+
763
765
  """
764
766
  query = """
765
767
  query GetIssueComments($identifier: String!, $first: Int!) {
@@ -788,8 +790,7 @@ class LinearAdapter(BaseAdapter[Task]):
788
790
 
789
791
  try:
790
792
  result = await self.client.execute_query(
791
- query,
792
- {"identifier": ticket_id, "first": limit}
793
+ query, {"identifier": ticket_id, "first": limit}
793
794
  )
794
795
 
795
796
  if not result.get("issue"):