mcp-ticketer 0.4.11__py3-none-any.whl → 0.12.0__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 (70) hide show
  1. mcp_ticketer/__version__.py +3 -3
  2. mcp_ticketer/adapters/__init__.py +2 -0
  3. mcp_ticketer/adapters/aitrackdown.py +9 -3
  4. mcp_ticketer/adapters/asana/__init__.py +15 -0
  5. mcp_ticketer/adapters/asana/adapter.py +1308 -0
  6. mcp_ticketer/adapters/asana/client.py +292 -0
  7. mcp_ticketer/adapters/asana/mappers.py +334 -0
  8. mcp_ticketer/adapters/asana/types.py +146 -0
  9. mcp_ticketer/adapters/github.py +313 -96
  10. mcp_ticketer/adapters/jira.py +251 -1
  11. mcp_ticketer/adapters/linear/adapter.py +524 -22
  12. mcp_ticketer/adapters/linear/client.py +61 -9
  13. mcp_ticketer/adapters/linear/mappers.py +9 -3
  14. mcp_ticketer/cache/memory.py +3 -3
  15. mcp_ticketer/cli/adapter_diagnostics.py +1 -1
  16. mcp_ticketer/cli/auggie_configure.py +1 -1
  17. mcp_ticketer/cli/codex_configure.py +80 -1
  18. mcp_ticketer/cli/configure.py +33 -43
  19. mcp_ticketer/cli/diagnostics.py +18 -16
  20. mcp_ticketer/cli/discover.py +288 -21
  21. mcp_ticketer/cli/gemini_configure.py +1 -1
  22. mcp_ticketer/cli/instruction_commands.py +429 -0
  23. mcp_ticketer/cli/linear_commands.py +99 -15
  24. mcp_ticketer/cli/main.py +1199 -227
  25. mcp_ticketer/cli/mcp_configure.py +1 -1
  26. mcp_ticketer/cli/migrate_config.py +12 -8
  27. mcp_ticketer/cli/platform_commands.py +6 -6
  28. mcp_ticketer/cli/platform_detection.py +412 -0
  29. mcp_ticketer/cli/queue_commands.py +15 -15
  30. mcp_ticketer/cli/simple_health.py +1 -1
  31. mcp_ticketer/cli/ticket_commands.py +14 -13
  32. mcp_ticketer/cli/update_checker.py +313 -0
  33. mcp_ticketer/cli/utils.py +45 -41
  34. mcp_ticketer/core/__init__.py +12 -0
  35. mcp_ticketer/core/adapter.py +4 -4
  36. mcp_ticketer/core/config.py +17 -10
  37. mcp_ticketer/core/env_discovery.py +33 -3
  38. mcp_ticketer/core/env_loader.py +7 -6
  39. mcp_ticketer/core/exceptions.py +3 -3
  40. mcp_ticketer/core/http_client.py +10 -10
  41. mcp_ticketer/core/instructions.py +405 -0
  42. mcp_ticketer/core/mappers.py +1 -1
  43. mcp_ticketer/core/models.py +1 -1
  44. mcp_ticketer/core/onepassword_secrets.py +379 -0
  45. mcp_ticketer/core/project_config.py +17 -1
  46. mcp_ticketer/core/registry.py +1 -1
  47. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  48. mcp_ticketer/mcp/__init__.py +2 -2
  49. mcp_ticketer/mcp/server/__init__.py +2 -2
  50. mcp_ticketer/mcp/server/main.py +82 -69
  51. mcp_ticketer/mcp/server/tools/__init__.py +9 -0
  52. mcp_ticketer/mcp/server/tools/attachment_tools.py +63 -16
  53. mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
  54. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +154 -5
  55. mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
  56. mcp_ticketer/mcp/server/tools/ticket_tools.py +157 -4
  57. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
  58. mcp_ticketer/queue/health_monitor.py +1 -0
  59. mcp_ticketer/queue/manager.py +4 -4
  60. mcp_ticketer/queue/queue.py +3 -3
  61. mcp_ticketer/queue/run_worker.py +1 -1
  62. mcp_ticketer/queue/ticket_registry.py +2 -2
  63. mcp_ticketer/queue/worker.py +14 -12
  64. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +106 -52
  65. mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
  66. mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
  67. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
  68. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
  69. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
  70. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,382 @@
1
+ """User-specific ticket management tools.
2
+
3
+ This module provides tools for managing tickets from a user's perspective,
4
+ including listing assigned tickets and transitioning tickets through workflow
5
+ states with validation.
6
+
7
+ Design Decision: Workflow State Validation
8
+ ------------------------------------------
9
+ State transitions are validated using TicketState.can_transition_to() to ensure
10
+ tickets follow the defined workflow. This prevents invalid state changes that
11
+ could break integrations or confuse team members.
12
+
13
+ Valid workflow transitions:
14
+ - OPEN → IN_PROGRESS, WAITING, BLOCKED, CLOSED
15
+ - IN_PROGRESS → READY, WAITING, BLOCKED, OPEN
16
+ - READY → TESTED, IN_PROGRESS, BLOCKED
17
+ - TESTED → DONE, IN_PROGRESS
18
+ - DONE → CLOSED
19
+ - WAITING/BLOCKED → OPEN, IN_PROGRESS, CLOSED
20
+ - CLOSED → (no transitions, terminal state)
21
+
22
+ Performance Considerations:
23
+ - get_my_tickets uses adapter's native filtering when available
24
+ - Falls back to client-side filtering for adapters without assignee filter
25
+ - State transition validation is O(1) lookup in predefined state machine
26
+ """
27
+
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ from ....core.models import TicketState
32
+ from ....core.project_config import ConfigResolver, TicketerConfig
33
+ from ..server_sdk import get_adapter, mcp
34
+
35
+
36
+ def get_config_resolver() -> ConfigResolver:
37
+ """Get configuration resolver for current project.
38
+
39
+ Returns:
40
+ ConfigResolver instance for current working directory
41
+
42
+ """
43
+ return ConfigResolver(project_path=Path.cwd())
44
+
45
+
46
+ @mcp.tool()
47
+ async def get_my_tickets(
48
+ state: str | None = None,
49
+ limit: int = 10,
50
+ ) -> dict[str, Any]:
51
+ """Get tickets assigned to the configured default user.
52
+
53
+ Retrieves tickets assigned to the user specified in default_user configuration.
54
+ Requires default_user to be set via config_set_default_user().
55
+
56
+ Args:
57
+ state: Optional state filter - must be one of: open, in_progress, ready,
58
+ tested, done, closed, waiting, blocked
59
+ limit: Maximum number of tickets to return (default: 10, max: 100)
60
+
61
+ Returns:
62
+ Dictionary containing:
63
+ - status: "completed" or "error"
64
+ - tickets: List of ticket objects assigned to user
65
+ - count: Number of tickets returned
66
+ - user: User ID that was queried
67
+ - state_filter: State filter applied (if any)
68
+ - error: Error details (if failed)
69
+
70
+ Example:
71
+ >>> result = await get_my_tickets(state="in_progress", limit=5)
72
+ >>> print(result)
73
+ {
74
+ "status": "completed",
75
+ "tickets": [
76
+ {"id": "TICKET-1", "title": "Fix bug", "state": "in_progress"},
77
+ {"id": "TICKET-2", "title": "Add feature", "state": "in_progress"}
78
+ ],
79
+ "count": 2,
80
+ "user": "user@example.com",
81
+ "state_filter": "in_progress"
82
+ }
83
+
84
+ Error Conditions:
85
+ - No default user configured: Returns error with setup instructions
86
+ - Invalid state: Returns error with valid state options
87
+ - Adapter query failure: Returns error with details
88
+
89
+ Usage Notes:
90
+ - Requires default_user to be set in configuration
91
+ - Use config_set_default_user() to configure the user first
92
+ - Limit is capped at 100 to prevent performance issues
93
+
94
+ """
95
+ try:
96
+ # Validate limit
97
+ if limit > 100:
98
+ limit = 100
99
+
100
+ # Load configuration to get default user
101
+ resolver = get_config_resolver()
102
+ config = resolver.load_project_config() or TicketerConfig()
103
+
104
+ if not config.default_user:
105
+ return {
106
+ "status": "error",
107
+ "error": "No default user configured. Use config_set_default_user() to set a default user first.",
108
+ "setup_command": "config_set_default_user",
109
+ }
110
+
111
+ # Validate state if provided
112
+ state_filter = None
113
+ if state is not None:
114
+ try:
115
+ state_filter = TicketState(state.lower())
116
+ except ValueError:
117
+ valid_states = [s.value for s in TicketState]
118
+ return {
119
+ "status": "error",
120
+ "error": f"Invalid state '{state}'. Must be one of: {', '.join(valid_states)}",
121
+ "valid_states": valid_states,
122
+ }
123
+
124
+ # Build filters
125
+ filters: dict[str, Any] = {"assignee": config.default_user}
126
+ if state_filter:
127
+ filters["state"] = state_filter
128
+
129
+ # Query adapter
130
+ adapter = get_adapter()
131
+ tickets = await adapter.list(limit=limit, offset=0, filters=filters)
132
+
133
+ return {
134
+ "status": "completed",
135
+ "tickets": [ticket.model_dump() for ticket in tickets],
136
+ "count": len(tickets),
137
+ "user": config.default_user,
138
+ "state_filter": state if state else "all",
139
+ "limit": limit,
140
+ }
141
+ except Exception as e:
142
+ return {
143
+ "status": "error",
144
+ "error": f"Failed to retrieve tickets: {str(e)}",
145
+ }
146
+
147
+
148
+ @mcp.tool()
149
+ async def get_available_transitions(ticket_id: str) -> dict[str, Any]:
150
+ """Get valid next states for a ticket based on workflow rules.
151
+
152
+ Retrieves the ticket's current state and returns all valid target states
153
+ according to the defined workflow state machine. This helps AI agents and
154
+ users understand which state transitions are allowed.
155
+
156
+ Args:
157
+ ticket_id: Unique identifier of the ticket
158
+
159
+ Returns:
160
+ Dictionary containing:
161
+ - status: "completed" or "error"
162
+ - ticket_id: ID of the queried ticket
163
+ - current_state: Current workflow state
164
+ - available_transitions: List of valid target states
165
+ - transition_descriptions: Human-readable descriptions of each transition
166
+ - error: Error details (if failed)
167
+
168
+ Example:
169
+ >>> result = await get_available_transitions("TICKET-123")
170
+ >>> print(result)
171
+ {
172
+ "status": "completed",
173
+ "ticket_id": "TICKET-123",
174
+ "current_state": "in_progress",
175
+ "available_transitions": ["ready", "waiting", "blocked", "open"],
176
+ "transition_descriptions": {
177
+ "ready": "Mark work as complete and ready for review",
178
+ "waiting": "Pause work while waiting for external dependency",
179
+ "blocked": "Work is blocked by an impediment",
180
+ "open": "Move back to backlog"
181
+ }
182
+ }
183
+
184
+ Error Conditions:
185
+ - Ticket not found: Returns error with ticket ID
186
+ - Adapter query failure: Returns error with details
187
+ - Terminal state (CLOSED): Returns empty transitions list
188
+
189
+ Usage Notes:
190
+ - CLOSED is a terminal state with no valid transitions
191
+ - Use this before ticket_transition() to validate intended state change
192
+ - Transition validation prevents workflow violations
193
+
194
+ """
195
+ try:
196
+ # Get ticket from adapter
197
+ adapter = get_adapter()
198
+ ticket = await adapter.read(ticket_id)
199
+
200
+ if ticket is None:
201
+ return {
202
+ "status": "error",
203
+ "error": f"Ticket {ticket_id} not found",
204
+ }
205
+
206
+ # Get current state
207
+ current_state = ticket.state
208
+
209
+ # Get valid transitions from state machine
210
+ valid_transitions = TicketState.valid_transitions()
211
+ # Handle both TicketState enum and string values
212
+ if isinstance(current_state, str):
213
+ current_state = TicketState(current_state)
214
+ available = valid_transitions.get(current_state, [])
215
+
216
+ # Create human-readable descriptions
217
+ descriptions = {
218
+ TicketState.OPEN: "Move to backlog (not yet started)",
219
+ TicketState.IN_PROGRESS: "Begin active work on ticket",
220
+ TicketState.READY: "Mark as complete and ready for review/testing",
221
+ TicketState.TESTED: "Mark as tested and verified",
222
+ TicketState.DONE: "Mark as complete and accepted",
223
+ TicketState.WAITING: "Pause work while waiting for external dependency",
224
+ TicketState.BLOCKED: "Work is blocked by an impediment",
225
+ TicketState.CLOSED: "Close and archive ticket (final state)",
226
+ }
227
+
228
+ transition_descriptions = {
229
+ state.value: descriptions.get(state, "") for state in available
230
+ }
231
+
232
+ return {
233
+ "status": "completed",
234
+ "ticket_id": ticket_id,
235
+ "current_state": current_state.value,
236
+ "available_transitions": [state.value for state in available],
237
+ "transition_descriptions": transition_descriptions,
238
+ "is_terminal": len(available) == 0,
239
+ }
240
+ except Exception as e:
241
+ return {
242
+ "status": "error",
243
+ "error": f"Failed to get available transitions: {str(e)}",
244
+ }
245
+
246
+
247
+ @mcp.tool()
248
+ async def ticket_transition(
249
+ ticket_id: str,
250
+ to_state: str,
251
+ comment: str | None = None,
252
+ ) -> dict[str, Any]:
253
+ """Move ticket through workflow with validation and optional comment.
254
+
255
+ Transitions a ticket to a new state, validating the transition against the
256
+ defined workflow rules. Optionally adds a comment explaining the transition.
257
+
258
+ Workflow State Machine:
259
+ OPEN → IN_PROGRESS, WAITING, BLOCKED, CLOSED
260
+ IN_PROGRESS → READY, WAITING, BLOCKED, OPEN
261
+ READY → TESTED, IN_PROGRESS, BLOCKED
262
+ TESTED → DONE, IN_PROGRESS
263
+ DONE → CLOSED
264
+ WAITING → OPEN, IN_PROGRESS, CLOSED
265
+ BLOCKED → OPEN, IN_PROGRESS, CLOSED
266
+ CLOSED → (no transitions)
267
+
268
+ Args:
269
+ ticket_id: Unique identifier of the ticket to transition
270
+ to_state: Target state - must be valid for current state
271
+ comment: Optional comment explaining the transition reason
272
+
273
+ Returns:
274
+ Dictionary containing:
275
+ - status: "completed" or "error"
276
+ - ticket: Updated ticket object with new state
277
+ - previous_state: State before transition
278
+ - new_state: State after transition
279
+ - comment_added: Whether a comment was added (if applicable)
280
+ - error: Error details (if failed)
281
+
282
+ Example:
283
+ >>> result = await ticket_transition(
284
+ ... "TICKET-123",
285
+ ... "ready",
286
+ ... "Work complete, ready for code review"
287
+ ... )
288
+ >>> print(result)
289
+ {
290
+ "status": "completed",
291
+ "ticket": {"id": "TICKET-123", "state": "ready", ...},
292
+ "previous_state": "in_progress",
293
+ "new_state": "ready",
294
+ "comment_added": True
295
+ }
296
+
297
+ Error Conditions:
298
+ - Ticket not found: Returns error with ticket ID
299
+ - Invalid transition: Returns error with valid options
300
+ - Invalid state name: Returns error with valid states
301
+ - Adapter update failure: Returns error with details
302
+
303
+ Usage Notes:
304
+ - Use get_available_transitions() first to see valid options
305
+ - Comments are adapter-dependent (some may not support them)
306
+ - Validation prevents workflow violations
307
+ - Terminal state (CLOSED) has no valid transitions
308
+
309
+ """
310
+ try:
311
+ # Get ticket from adapter
312
+ adapter = get_adapter()
313
+ ticket = await adapter.read(ticket_id)
314
+
315
+ if ticket is None:
316
+ return {
317
+ "status": "error",
318
+ "error": f"Ticket {ticket_id} not found",
319
+ }
320
+
321
+ # Validate target state
322
+ try:
323
+ target_state = TicketState(to_state.lower())
324
+ except ValueError:
325
+ valid_states = [s.value for s in TicketState]
326
+ return {
327
+ "status": "error",
328
+ "error": f"Invalid state '{to_state}'. Must be one of: {', '.join(valid_states)}",
329
+ "valid_states": valid_states,
330
+ }
331
+
332
+ # Store current state for response
333
+ current_state = ticket.state
334
+ # Handle both TicketState enum and string values
335
+ if isinstance(current_state, str):
336
+ current_state = TicketState(current_state)
337
+
338
+ # Validate transition
339
+ if not current_state.can_transition_to(target_state):
340
+ valid_transitions = TicketState.valid_transitions().get(current_state, [])
341
+ valid_values = [s.value for s in valid_transitions]
342
+ return {
343
+ "status": "error",
344
+ "error": f"Invalid transition from '{current_state.value}' to '{target_state.value}'",
345
+ "current_state": current_state.value,
346
+ "valid_transitions": valid_values,
347
+ "message": f"Cannot transition from {current_state.value} to {target_state.value}. "
348
+ f"Valid transitions: {', '.join(valid_values) if valid_values else 'none (terminal state)'}",
349
+ }
350
+
351
+ # Update ticket state
352
+ updated = await adapter.update(ticket_id, {"state": target_state})
353
+
354
+ if updated is None:
355
+ return {
356
+ "status": "error",
357
+ "error": f"Failed to update ticket {ticket_id}",
358
+ }
359
+
360
+ # Add comment if provided and adapter supports it
361
+ comment_added = False
362
+ if comment and hasattr(adapter, "add_comment"):
363
+ try:
364
+ await adapter.add_comment(ticket_id, comment)
365
+ comment_added = True
366
+ except Exception:
367
+ # Log but don't fail the transition
368
+ comment_added = False
369
+
370
+ return {
371
+ "status": "completed",
372
+ "ticket": updated.model_dump(),
373
+ "previous_state": current_state.value,
374
+ "new_state": target_state.value,
375
+ "comment_added": comment_added,
376
+ "message": f"Ticket {ticket_id} transitioned from {current_state.value} to {target_state.value}",
377
+ }
378
+ except Exception as e:
379
+ return {
380
+ "status": "error",
381
+ "error": f"Failed to transition ticket: {str(e)}",
382
+ }
@@ -39,6 +39,7 @@ class HealthAlert:
39
39
  self.timestamp = timestamp or datetime.now()
40
40
 
41
41
  def __str__(self) -> str:
42
+ """Return string representation of alert."""
42
43
  return f"[{self.level.upper()}] {self.message}"
43
44
 
44
45
 
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
19
19
  class WorkerManager:
20
20
  """Manages worker process with file-based locking."""
21
21
 
22
- def __init__(self):
22
+ def __init__(self) -> None:
23
23
  """Initialize worker manager."""
24
24
  # Lazy import to avoid circular dependency
25
25
  from .queue import Queue
@@ -54,7 +54,7 @@ class WorkerManager:
54
54
  # Lock already held
55
55
  return False
56
56
 
57
- def _release_lock(self):
57
+ def _release_lock(self) -> None:
58
58
  """Release worker lock."""
59
59
  if hasattr(self, "lock_fd"):
60
60
  fcntl.lockf(self.lock_fd, fcntl.LOCK_UN)
@@ -287,7 +287,7 @@ class WorkerManager:
287
287
  is_running = self.is_running()
288
288
  pid = self._get_pid() if is_running else None
289
289
 
290
- status = {"running": is_running, "pid": pid}
290
+ status: dict[str, Any] = {"running": is_running, "pid": pid}
291
291
 
292
292
  # Add process info if running
293
293
  if is_running and pid:
@@ -326,7 +326,7 @@ class WorkerManager:
326
326
  except (OSError, ValueError):
327
327
  return None
328
328
 
329
- def _cleanup(self):
329
+ def _cleanup(self) -> None:
330
330
  """Clean up lock and PID files."""
331
331
  self._release_lock()
332
332
  if self.pid_file.exists():
@@ -83,7 +83,7 @@ class Queue:
83
83
  self._lock = threading.Lock()
84
84
  self._init_database()
85
85
 
86
- def _init_database(self):
86
+ def _init_database(self) -> None:
87
87
  """Initialize database schema."""
88
88
  with sqlite3.connect(self.db_path) as conn:
89
89
  conn.execute(
@@ -462,7 +462,7 @@ class Queue:
462
462
 
463
463
  return cursor.fetchone()[0]
464
464
 
465
- def cleanup_old(self, days: int = 7):
465
+ def cleanup_old(self, days: int = 7) -> None:
466
466
  """Clean up old completed/failed items.
467
467
 
468
468
  Args:
@@ -487,7 +487,7 @@ class Queue:
487
487
  )
488
488
  conn.commit()
489
489
 
490
- def reset_stuck_items(self, timeout_minutes: int = 30):
490
+ def reset_stuck_items(self, timeout_minutes: int = 30) -> None:
491
491
  """Reset items stuck in processing state.
492
492
 
493
493
  Args:
@@ -13,7 +13,7 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- def main():
16
+ def main() -> None:
17
17
  """Run the worker process."""
18
18
  import os
19
19
 
@@ -27,7 +27,7 @@ class TicketRegistry:
27
27
  self._lock = threading.Lock()
28
28
  self._init_database()
29
29
 
30
- def _init_database(self):
30
+ def _init_database(self) -> None:
31
31
  """Initialize database schema."""
32
32
  with sqlite3.connect(self.db_path) as conn:
33
33
  # Ticket registry table
@@ -149,7 +149,7 @@ class TicketRegistry:
149
149
  with self._lock:
150
150
  with sqlite3.connect(self.db_path) as conn:
151
151
  update_fields = ["status = ?", "updated_at = ?"]
152
- values = [status, datetime.now().isoformat()]
152
+ values: list[Any] = [status, datetime.now().isoformat()]
153
153
 
154
154
  if ticket_id is not None:
155
155
  update_fields.append("ticket_id = ?")
@@ -97,12 +97,12 @@ class Worker:
97
97
  f"Worker initialized with batch_size={batch_size}, max_concurrent={max_concurrent}"
98
98
  )
99
99
 
100
- def _signal_handler(self, signum, frame):
100
+ def _signal_handler(self, signum: int, frame: Any) -> None:
101
101
  """Handle shutdown signals."""
102
102
  logger.info(f"Received signal {signum}, shutting down...")
103
103
  self.stop()
104
104
 
105
- def start(self, daemon: bool = True):
105
+ def start(self, daemon: bool = True) -> None:
106
106
  """Start the worker.
107
107
 
108
108
  Args:
@@ -126,14 +126,14 @@ class Worker:
126
126
  # Run in main thread
127
127
  self._run_loop()
128
128
 
129
- def stop(self):
129
+ def stop(self) -> None:
130
130
  """Stop the worker."""
131
131
  logger.info("Stopping worker...")
132
132
  self.running = False
133
133
  self.stop_event.set()
134
134
 
135
- def _run_loop(self):
136
- """Main worker loop with batch processing."""
135
+ def _run_loop(self) -> None:
136
+ """Run main worker loop with batch processing."""
137
137
  logger.info("Worker loop started")
138
138
 
139
139
  # Reset any stuck items on startup
@@ -174,7 +174,7 @@ class Worker:
174
174
  break
175
175
  return batch
176
176
 
177
- async def _process_batch(self, batch: list[QueueItem]):
177
+ async def _process_batch(self, batch: list[QueueItem]) -> None:
178
178
  """Process a batch of queue items with concurrency control.
179
179
 
180
180
  Args:
@@ -199,7 +199,9 @@ class Worker:
199
199
  # Wait for all adapter groups to complete
200
200
  await asyncio.gather(*tasks, return_exceptions=True)
201
201
 
202
- async def _process_adapter_group(self, adapter: str, items: list[QueueItem]):
202
+ async def _process_adapter_group(
203
+ self, adapter: str, items: list[QueueItem]
204
+ ) -> None:
203
205
  """Process items for a specific adapter with concurrency control.
204
206
 
205
207
  Args:
@@ -216,7 +218,7 @@ class Worker:
216
218
  semaphore = self.adapter_semaphores[adapter]
217
219
 
218
220
  # Process items with concurrency control
219
- async def process_with_semaphore(item):
221
+ async def process_with_semaphore(item: QueueItem) -> None:
220
222
  async with semaphore:
221
223
  await self._process_item(item)
222
224
 
@@ -226,7 +228,7 @@ class Worker:
226
228
  # Process with concurrency control
227
229
  await asyncio.gather(*tasks, return_exceptions=True)
228
230
 
229
- async def _process_item(self, item: QueueItem):
231
+ async def _process_item(self, item: QueueItem) -> None:
230
232
  """Process a single queue item.
231
233
 
232
234
  Args:
@@ -333,7 +335,7 @@ class Worker:
333
335
  self.stats["items_failed"] += 1
334
336
  logger.error(f"Max retries exceeded for {item.id}, marking as failed")
335
337
 
336
- async def _check_rate_limit(self, adapter: str):
338
+ async def _check_rate_limit(self, adapter: str) -> None:
337
339
  """Check and enforce rate limits.
338
340
 
339
341
  Args:
@@ -357,7 +359,7 @@ class Worker:
357
359
 
358
360
  self.last_request_times[adapter] = datetime.now()
359
361
 
360
- def _get_adapter(self, item: QueueItem):
362
+ def _get_adapter(self, item: QueueItem) -> Any:
361
363
  """Get adapter instance for item.
362
364
 
363
365
  Args:
@@ -442,7 +444,7 @@ class Worker:
442
444
 
443
445
  return adapter
444
446
 
445
- async def _execute_operation(self, adapter, item: QueueItem) -> dict[str, Any]:
447
+ async def _execute_operation(self, adapter: Any, item: QueueItem) -> dict[str, Any]:
446
448
  """Execute the queued operation.
447
449
 
448
450
  Args: