mcp-ticketer 0.1.22__py3-none-any.whl → 0.1.23__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,10 +1,11 @@
1
1
  """Linear adapter implementation using native GraphQL API with full feature support."""
2
2
 
3
3
  import asyncio
4
+ import builtins
4
5
  import os
5
6
  from datetime import date, datetime
6
7
  from enum import Enum
7
- from typing import Any, Dict, List, Optional, Union
8
+ from typing import Any, Optional, Union
8
9
 
9
10
  from gql import Client, gql
10
11
  from gql.transport.exceptions import TransportQueryError
@@ -278,7 +279,7 @@ ISSUE_LIST_FRAGMENTS = (
278
279
  class LinearAdapter(BaseAdapter[Task]):
279
280
  """Adapter for Linear issue tracking system using native GraphQL API."""
280
281
 
281
- def __init__(self, config: Dict[str, Any]):
282
+ def __init__(self, config: dict[str, Any]):
282
283
  """Initialize Linear adapter.
283
284
 
284
285
  Args:
@@ -315,9 +316,9 @@ class LinearAdapter(BaseAdapter[Task]):
315
316
 
316
317
  # Caches for frequently used data
317
318
  self._team_id: Optional[str] = None
318
- self._workflow_states: Optional[Dict[str, Dict[str, Any]]] = None
319
- self._labels: Optional[Dict[str, str]] = None # name -> id
320
- self._users: Optional[Dict[str, str]] = None # email -> id
319
+ self._workflow_states: Optional[dict[str, dict[str, Any]]] = None
320
+ self._labels: Optional[dict[str, str]] = None # name -> id
321
+ self._users: Optional[dict[str, str]] = None # email -> id
321
322
 
322
323
  # Initialize state mapping
323
324
  self._state_mapping = self._get_state_mapping()
@@ -435,7 +436,7 @@ class LinearAdapter(BaseAdapter[Task]):
435
436
 
436
437
  async def _fetch_workflow_states_data(
437
438
  self, team_id: str
438
- ) -> Dict[str, Dict[str, Any]]:
439
+ ) -> dict[str, dict[str, Any]]:
439
440
  """Fetch workflow states data."""
440
441
  query = gql(
441
442
  """
@@ -467,7 +468,7 @@ class LinearAdapter(BaseAdapter[Task]):
467
468
 
468
469
  return workflow_states
469
470
 
470
- async def _fetch_labels_data(self, team_id: str) -> Dict[str, str]:
471
+ async def _fetch_labels_data(self, team_id: str) -> dict[str, str]:
471
472
  """Fetch labels data."""
472
473
  query = gql(
473
474
  """
@@ -498,7 +499,7 @@ class LinearAdapter(BaseAdapter[Task]):
498
499
  await self._ensure_initialized()
499
500
  return self._team_id
500
501
 
501
- async def _get_workflow_states(self) -> Dict[str, Dict[str, Any]]:
502
+ async def _get_workflow_states(self) -> dict[str, dict[str, Any]]:
502
503
  """Get cached workflow states from Linear."""
503
504
  await self._ensure_initialized()
504
505
  return self._workflow_states
@@ -619,7 +620,7 @@ class LinearAdapter(BaseAdapter[Task]):
619
620
  )
620
621
  return True, ""
621
622
 
622
- def _get_state_mapping(self) -> Dict[TicketState, str]:
623
+ def _get_state_mapping(self) -> dict[TicketState, str]:
623
624
  """Get mapping from universal states to Linear state types.
624
625
 
625
626
  Required by BaseAdapter abstract method.
@@ -643,7 +644,7 @@ class LinearAdapter(BaseAdapter[Task]):
643
644
  return self._state_mapping.get(state, LinearStateType.BACKLOG)
644
645
 
645
646
  def _map_linear_state(
646
- self, state_data: Dict[str, Any], labels: List[str]
647
+ self, state_data: dict[str, Any], labels: list[str]
647
648
  ) -> TicketState:
648
649
  """Map Linear state and labels to universal state."""
649
650
  state_type = state_data.get("type", "").lower()
@@ -669,7 +670,7 @@ class LinearAdapter(BaseAdapter[Task]):
669
670
  }
670
671
  return state_mapping.get(state_type, TicketState.OPEN)
671
672
 
672
- def _task_from_linear_issue(self, issue: Dict[str, Any]) -> Task:
673
+ def _task_from_linear_issue(self, issue: dict[str, Any]) -> Task:
673
674
  """Convert Linear issue to universal Task."""
674
675
  # Extract labels
675
676
  tags = []
@@ -786,7 +787,7 @@ class LinearAdapter(BaseAdapter[Task]):
786
787
  metadata=metadata,
787
788
  )
788
789
 
789
- def _epic_from_linear_project(self, project: Dict[str, Any]) -> Epic:
790
+ def _epic_from_linear_project(self, project: dict[str, Any]) -> Epic:
790
791
  """Convert Linear project to universal Epic."""
791
792
  # Map project state to ticket state
792
793
  project_state = project.get("state", "planned").lower()
@@ -1005,7 +1006,7 @@ class LinearAdapter(BaseAdapter[Task]):
1005
1006
 
1006
1007
  return None
1007
1008
 
1008
- async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[Task]:
1009
+ async def update(self, ticket_id: str, updates: dict[str, Any]) -> Optional[Task]:
1009
1010
  """Update a Linear issue with comprehensive field support."""
1010
1011
  # Validate credentials before attempting operation
1011
1012
  is_valid, error_message = self.validate_credentials()
@@ -1162,8 +1163,8 @@ class LinearAdapter(BaseAdapter[Task]):
1162
1163
  return result.get("issueArchive", {}).get("success", False)
1163
1164
 
1164
1165
  async def list(
1165
- self, limit: int = 10, offset: int = 0, filters: Optional[Dict[str, Any]] = None
1166
- ) -> List[Task]:
1166
+ self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
1167
+ ) -> list[Task]:
1167
1168
  """List Linear issues with comprehensive filtering."""
1168
1169
  team_id = await self._ensure_team_id()
1169
1170
 
@@ -1271,7 +1272,7 @@ class LinearAdapter(BaseAdapter[Task]):
1271
1272
 
1272
1273
  return tasks
1273
1274
 
1274
- async def search(self, query: SearchQuery) -> List[Task]:
1275
+ async def search(self, query: SearchQuery) -> builtins.list[Task]:
1275
1276
  """Search Linear issues with advanced filtering and text search."""
1276
1277
  team_id = await self._ensure_team_id()
1277
1278
 
@@ -1445,7 +1446,7 @@ class LinearAdapter(BaseAdapter[Task]):
1445
1446
 
1446
1447
  async def get_comments(
1447
1448
  self, ticket_id: str, limit: int = 10, offset: int = 0
1448
- ) -> List[Comment]:
1449
+ ) -> builtins.list[Comment]:
1449
1450
  """Get comments for a Linear issue with pagination."""
1450
1451
  query = gql(
1451
1452
  USER_FRAGMENT
@@ -1546,7 +1547,7 @@ class LinearAdapter(BaseAdapter[Task]):
1546
1547
 
1547
1548
  return result["projectCreate"]["project"]["id"]
1548
1549
 
1549
- async def get_cycles(self, active_only: bool = True) -> List[Dict[str, Any]]:
1550
+ async def get_cycles(self, active_only: bool = True) -> builtins.list[dict[str, Any]]:
1550
1551
  """Get Linear cycles (sprints) for the team."""
1551
1552
  team_id = await self._ensure_team_id()
1552
1553
 
@@ -1638,7 +1639,7 @@ class LinearAdapter(BaseAdapter[Task]):
1638
1639
  ticket_id: str,
1639
1640
  pr_url: str,
1640
1641
  pr_number: Optional[int] = None,
1641
- ) -> Dict[str, Any]:
1642
+ ) -> dict[str, Any]:
1642
1643
  """Link a Linear issue to a GitHub pull request.
1643
1644
 
1644
1645
  Args:
@@ -1741,8 +1742,8 @@ class LinearAdapter(BaseAdapter[Task]):
1741
1742
  async def create_pull_request_for_issue(
1742
1743
  self,
1743
1744
  ticket_id: str,
1744
- github_config: Dict[str, Any],
1745
- ) -> Dict[str, Any]:
1745
+ github_config: dict[str, Any],
1746
+ ) -> dict[str, Any]:
1746
1747
  """Create a GitHub PR for a Linear issue using GitHub integration.
1747
1748
 
1748
1749
  This requires GitHub integration to be configured in Linear.
@@ -1837,7 +1838,7 @@ class LinearAdapter(BaseAdapter[Task]):
1837
1838
  else:
1838
1839
  raise ValueError(f"Failed to update issue {ticket_id} with branch name")
1839
1840
 
1840
- async def _search_by_identifier(self, identifier: str) -> Optional[Dict[str, Any]]:
1841
+ async def _search_by_identifier(self, identifier: str) -> Optional[dict[str, Any]]:
1841
1842
  """Search for an issue by its identifier."""
1842
1843
  search_query = gql(
1843
1844
  """
@@ -1950,7 +1951,7 @@ class LinearAdapter(BaseAdapter[Task]):
1950
1951
 
1951
1952
  return None
1952
1953
 
1953
- async def list_epics(self, **kwargs) -> List[Epic]:
1954
+ async def list_epics(self, **kwargs) -> builtins.list[Epic]:
1954
1955
  """List all Linear Projects (Epics).
1955
1956
 
1956
1957
  Args:
@@ -2037,7 +2038,7 @@ class LinearAdapter(BaseAdapter[Task]):
2037
2038
  # The existing create method handles project association via parent_epic field
2038
2039
  return await self.create(task)
2039
2040
 
2040
- async def list_issues_by_epic(self, epic_id: str) -> List[Task]:
2041
+ async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
2041
2042
  """List all issues in a Linear project (epic).
2042
2043
 
2043
2044
  Args:
@@ -2197,7 +2198,7 @@ class LinearAdapter(BaseAdapter[Task]):
2197
2198
  created_issue = result["issueCreate"]["issue"]
2198
2199
  return self._task_from_linear_issue(created_issue)
2199
2200
 
2200
- async def list_tasks_by_issue(self, issue_id: str) -> List[Task]:
2201
+ async def list_tasks_by_issue(self, issue_id: str) -> builtins.list[Task]:
2201
2202
  """List all tasks (sub-issues) under an issue.
2202
2203
 
2203
2204
  Args:
@@ -5,7 +5,7 @@ import hashlib
5
5
  import json
6
6
  import time
7
7
  from functools import wraps
8
- from typing import Any, Callable, Dict, Optional
8
+ from typing import Any, Callable, Optional
9
9
 
10
10
 
11
11
  class CacheEntry:
@@ -37,7 +37,7 @@ class MemoryCache:
37
37
  default_ttl: Default TTL in seconds (5 minutes)
38
38
 
39
39
  """
40
- self._cache: Dict[str, CacheEntry] = {}
40
+ self._cache: dict[str, CacheEntry] = {}
41
41
  self._default_ttl = default_ttl
42
42
  self._lock = asyncio.Lock()
43
43
 
mcp_ticketer/cli/main.py CHANGED
@@ -5,7 +5,7 @@ import json
5
5
  import os
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
- from typing import List, Optional
8
+ from typing import Optional
9
9
 
10
10
  import typer
11
11
  from dotenv import load_dotenv
@@ -59,8 +59,7 @@ def main_callback(
59
59
  help="Show version and exit",
60
60
  ),
61
61
  ):
62
- """MCP Ticketer - Universal ticket management interface.
63
- """
62
+ """MCP Ticketer - Universal ticket management interface."""
64
63
  pass
65
64
 
66
65
 
@@ -801,7 +800,7 @@ def create(
801
800
  priority: Priority = typer.Option(
802
801
  Priority.MEDIUM, "--priority", "-p", help="Priority level"
803
802
  ),
804
- tags: Optional[List[str]] = typer.Option(
803
+ tags: Optional[list[str]] = typer.Option(
805
804
  None, "--tag", "-t", help="Tags (can be specified multiple times)"
806
805
  ),
807
806
  assignee: Optional[str] = typer.Option(
@@ -1008,12 +1007,39 @@ def update(
1008
1007
  @app.command()
1009
1008
  def transition(
1010
1009
  ticket_id: str = typer.Argument(..., help="Ticket ID"),
1011
- state: TicketState = typer.Argument(..., help="Target state"),
1010
+ state_positional: Optional[TicketState] = typer.Argument(
1011
+ None, help="Target state (positional - deprecated, use --state instead)"
1012
+ ),
1013
+ state: Optional[TicketState] = typer.Option(
1014
+ None, "--state", "-s", help="Target state (recommended)"
1015
+ ),
1012
1016
  adapter: Optional[AdapterType] = typer.Option(
1013
1017
  None, "--adapter", help="Override default adapter"
1014
1018
  ),
1015
1019
  ) -> None:
1016
- """Change ticket state with validation."""
1020
+ """Change ticket state with validation.
1021
+
1022
+ Examples:
1023
+ # Recommended syntax with flag:
1024
+ mcp-ticketer transition BTA-215 --state done
1025
+ mcp-ticketer transition BTA-215 -s in_progress
1026
+
1027
+ # Legacy positional syntax (still supported):
1028
+ mcp-ticketer transition BTA-215 done
1029
+
1030
+ """
1031
+ # Determine which state to use (prefer flag over positional)
1032
+ target_state = state if state is not None else state_positional
1033
+
1034
+ if target_state is None:
1035
+ console.print("[red]Error: State is required[/red]")
1036
+ console.print(
1037
+ "Use either:\n"
1038
+ " - Flag syntax (recommended): mcp-ticketer transition TICKET-ID --state STATE\n"
1039
+ " - Positional syntax: mcp-ticketer transition TICKET-ID STATE"
1040
+ )
1041
+ raise typer.Exit(1)
1042
+
1017
1043
  # Get the adapter name
1018
1044
  config = load_config()
1019
1045
  adapter_name = (
@@ -1025,14 +1051,16 @@ def transition(
1025
1051
  queue_id = queue.add(
1026
1052
  ticket_data={
1027
1053
  "ticket_id": ticket_id,
1028
- "state": state.value if hasattr(state, "value") else state,
1054
+ "state": (
1055
+ target_state.value if hasattr(target_state, "value") else target_state
1056
+ ),
1029
1057
  },
1030
1058
  adapter=adapter_name,
1031
1059
  operation="transition",
1032
1060
  )
1033
1061
 
1034
1062
  console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
1035
- console.print(f" Ticket: {ticket_id} → {state}")
1063
+ console.print(f" Ticket: {ticket_id} → {target_state}")
1036
1064
  console.print("[dim]Use 'mcp-ticketer status {queue_id}' to check progress[/dim]")
1037
1065
 
1038
1066
  # Start worker if needed
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  import shutil
5
5
  from datetime import datetime
6
- from typing import Any, Dict
6
+ from typing import Any
7
7
 
8
8
  from rich.console import Console
9
9
  from rich.prompt import Confirm
@@ -96,7 +96,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
96
96
  console.print(f"[yellow]Old config backed up at: {backup_path}[/yellow]")
97
97
 
98
98
 
99
- def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
99
+ def _migrate_old_to_new(old_config: dict[str, Any]) -> TicketerConfig:
100
100
  """Migrate old configuration format to new format.
101
101
 
102
102
  Old format examples:
mcp_ticketer/cli/utils.py CHANGED
@@ -6,7 +6,7 @@ import logging
6
6
  import os
7
7
  from functools import wraps
8
8
  from pathlib import Path
9
- from typing import Any, Callable, Dict, List, Optional, TypeVar
9
+ from typing import Any, Callable, Optional, TypeVar
10
10
 
11
11
  import typer
12
12
  from rich.console import Console
@@ -166,7 +166,7 @@ class CommonPatterns:
166
166
 
167
167
  @staticmethod
168
168
  def queue_operation(
169
- ticket_data: Dict[str, Any],
169
+ ticket_data: dict[str, Any],
170
170
  operation: str,
171
171
  adapter_name: Optional[str] = None,
172
172
  show_progress: bool = True,
@@ -197,7 +197,7 @@ class CommonPatterns:
197
197
  return queue_id
198
198
 
199
199
  @staticmethod
200
- def display_ticket_table(tickets: List[Task], title: str = "Tickets") -> None:
200
+ def display_ticket_table(tickets: list[Task], title: str = "Tickets") -> None:
201
201
  """Display tickets in a formatted table."""
202
202
  if not tickets:
203
203
  console.print("[yellow]No tickets found[/yellow]")
@@ -222,7 +222,7 @@ class CommonPatterns:
222
222
  console.print(table)
223
223
 
224
224
  @staticmethod
225
- def display_ticket_details(ticket: Task, comments: Optional[List] = None) -> None:
225
+ def display_ticket_details(ticket: Task, comments: Optional[list] = None) -> None:
226
226
  """Display detailed ticket information."""
227
227
  console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
228
228
  console.print(f"Title: {ticket.title}")
@@ -367,7 +367,7 @@ class ConfigValidator:
367
367
  """Configuration validation utilities."""
368
368
 
369
369
  @staticmethod
370
- def validate_adapter_config(adapter_type: str, config: dict) -> List[str]:
370
+ def validate_adapter_config(adapter_type: str, config: dict) -> list[str]:
371
371
  """Validate adapter configuration and return list of issues."""
372
372
  issues = []
373
373
 
@@ -483,7 +483,7 @@ def create_standard_ticket_command(operation: str):
483
483
  priority: Optional[Priority] = None,
484
484
  state: Optional[TicketState] = None,
485
485
  assignee: Optional[str] = None,
486
- tags: Optional[List[str]] = None,
486
+ tags: Optional[list[str]] = None,
487
487
  adapter: Optional[str] = None,
488
488
  ):
489
489
  """Template for ticket commands."""
@@ -562,7 +562,7 @@ class TicketCommands:
562
562
  title: str,
563
563
  description: Optional[str] = None,
564
564
  priority: Priority = Priority.MEDIUM,
565
- tags: Optional[List[str]] = None,
565
+ tags: Optional[list[str]] = None,
566
566
  assignee: Optional[str] = None,
567
567
  adapter: Optional[str] = None,
568
568
  ) -> str:
@@ -579,7 +579,7 @@ class TicketCommands:
579
579
 
580
580
  @staticmethod
581
581
  def update_ticket(
582
- ticket_id: str, updates: Dict[str, Any], adapter: Optional[str] = None
582
+ ticket_id: str, updates: dict[str, Any], adapter: Optional[str] = None
583
583
  ) -> str:
584
584
  """Update a ticket."""
585
585
  if not updates:
@@ -1,7 +1,8 @@
1
1
  """Base adapter abstract class for ticket systems."""
2
2
 
3
+ import builtins
3
4
  from abc import ABC, abstractmethod
4
- from typing import Any, Dict, Generic, List, Optional, TypeVar
5
+ from typing import Any, Generic, Optional, TypeVar
5
6
 
6
7
  from .models import Comment, Epic, SearchQuery, Task, TicketState, TicketType
7
8
 
@@ -12,7 +13,7 @@ T = TypeVar("T", Epic, Task)
12
13
  class BaseAdapter(ABC, Generic[T]):
13
14
  """Abstract base class for all ticket system adapters."""
14
15
 
15
- def __init__(self, config: Dict[str, Any]):
16
+ def __init__(self, config: dict[str, Any]):
16
17
  """Initialize adapter with configuration.
17
18
 
18
19
  Args:
@@ -23,7 +24,7 @@ class BaseAdapter(ABC, Generic[T]):
23
24
  self._state_mapping = self._get_state_mapping()
24
25
 
25
26
  @abstractmethod
26
- def _get_state_mapping(self) -> Dict[TicketState, str]:
27
+ def _get_state_mapping(self) -> dict[TicketState, str]:
27
28
  """Get mapping from universal states to system-specific states.
28
29
 
29
30
  Returns:
@@ -69,7 +70,7 @@ class BaseAdapter(ABC, Generic[T]):
69
70
  pass
70
71
 
71
72
  @abstractmethod
72
- async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[T]:
73
+ async def update(self, ticket_id: str, updates: dict[str, Any]) -> Optional[T]:
73
74
  """Update a ticket.
74
75
 
75
76
  Args:
@@ -97,8 +98,8 @@ class BaseAdapter(ABC, Generic[T]):
97
98
 
98
99
  @abstractmethod
99
100
  async def list(
100
- self, limit: int = 10, offset: int = 0, filters: Optional[Dict[str, Any]] = None
101
- ) -> List[T]:
101
+ self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
102
+ ) -> list[T]:
102
103
  """List tickets with pagination and filters.
103
104
 
104
105
  Args:
@@ -113,7 +114,7 @@ class BaseAdapter(ABC, Generic[T]):
113
114
  pass
114
115
 
115
116
  @abstractmethod
116
- async def search(self, query: SearchQuery) -> List[T]:
117
+ async def search(self, query: SearchQuery) -> builtins.list[T]:
117
118
  """Search tickets using advanced query.
118
119
 
119
120
  Args:
@@ -157,7 +158,7 @@ class BaseAdapter(ABC, Generic[T]):
157
158
  @abstractmethod
158
159
  async def get_comments(
159
160
  self, ticket_id: str, limit: int = 10, offset: int = 0
160
- ) -> List[Comment]:
161
+ ) -> builtins.list[Comment]:
161
162
  """Get comments for a ticket.
162
163
 
163
164
  Args:
@@ -264,7 +265,7 @@ class BaseAdapter(ABC, Generic[T]):
264
265
  return result
265
266
  return None
266
267
 
267
- async def list_epics(self, **kwargs) -> List[Epic]:
268
+ async def list_epics(self, **kwargs) -> builtins.list[Epic]:
268
269
  """List all epics.
269
270
 
270
271
  Args:
@@ -308,7 +309,7 @@ class BaseAdapter(ABC, Generic[T]):
308
309
  )
309
310
  return await self.create(task)
310
311
 
311
- async def list_issues_by_epic(self, epic_id: str) -> List[Task]:
312
+ async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
312
313
  """List all issues in epic.
313
314
 
314
315
  Args:
@@ -359,7 +360,7 @@ class BaseAdapter(ABC, Generic[T]):
359
360
 
360
361
  return await self.create(task)
361
362
 
362
- async def list_tasks_by_issue(self, issue_id: str) -> List[Task]:
363
+ async def list_tasks_by_issue(self, issue_id: str) -> builtins.list[Task]:
363
364
  """List all tasks under an issue.
364
365
 
365
366
  Args:
@@ -6,7 +6,7 @@ import os
6
6
  from enum import Enum
7
7
  from functools import lru_cache
8
8
  from pathlib import Path
9
- from typing import Any, Dict, List, Optional, Union
9
+ from typing import Any, Optional, Union
10
10
 
11
11
  import yaml
12
12
  from pydantic import BaseModel, Field, root_validator, validator
@@ -31,7 +31,7 @@ class BaseAdapterConfig(BaseModel):
31
31
  enabled: bool = True
32
32
  timeout: float = 30.0
33
33
  max_retries: int = 3
34
- rate_limit: Optional[Dict[str, Any]] = None
34
+ rate_limit: Optional[dict[str, Any]] = None
35
35
 
36
36
 
37
37
  class GitHubConfig(BaseAdapterConfig):
@@ -43,10 +43,10 @@ class GitHubConfig(BaseAdapterConfig):
43
43
  repo: Optional[str] = Field(None, env="GITHUB_REPO")
44
44
  api_url: str = "https://api.github.com"
45
45
  use_projects_v2: bool = False
46
- custom_priority_scheme: Optional[Dict[str, List[str]]] = None
46
+ custom_priority_scheme: Optional[dict[str, list[str]]] = None
47
47
 
48
48
  @validator("token", pre=True, always=True)
49
- def validate_token(cls, v):
49
+ def validate_token(self, v):
50
50
  if not v:
51
51
  v = os.getenv("GITHUB_TOKEN")
52
52
  if not v:
@@ -54,7 +54,7 @@ class GitHubConfig(BaseAdapterConfig):
54
54
  return v
55
55
 
56
56
  @validator("owner", pre=True, always=True)
57
- def validate_owner(cls, v):
57
+ def validate_owner(self, v):
58
58
  if not v:
59
59
  v = os.getenv("GITHUB_OWNER")
60
60
  if not v:
@@ -62,7 +62,7 @@ class GitHubConfig(BaseAdapterConfig):
62
62
  return v
63
63
 
64
64
  @validator("repo", pre=True, always=True)
65
- def validate_repo(cls, v):
65
+ def validate_repo(self, v):
66
66
  if not v:
67
67
  v = os.getenv("GITHUB_REPO")
68
68
  if not v:
@@ -82,7 +82,7 @@ class JiraConfig(BaseAdapterConfig):
82
82
  verify_ssl: bool = True
83
83
 
84
84
  @validator("server", pre=True, always=True)
85
- def validate_server(cls, v):
85
+ def validate_server(self, v):
86
86
  if not v:
87
87
  v = os.getenv("JIRA_SERVER")
88
88
  if not v:
@@ -90,7 +90,7 @@ class JiraConfig(BaseAdapterConfig):
90
90
  return v.rstrip("/")
91
91
 
92
92
  @validator("email", pre=True, always=True)
93
- def validate_email(cls, v):
93
+ def validate_email(self, v):
94
94
  if not v:
95
95
  v = os.getenv("JIRA_EMAIL")
96
96
  if not v:
@@ -98,7 +98,7 @@ class JiraConfig(BaseAdapterConfig):
98
98
  return v
99
99
 
100
100
  @validator("api_token", pre=True, always=True)
101
- def validate_api_token(cls, v):
101
+ def validate_api_token(self, v):
102
102
  if not v:
103
103
  v = os.getenv("JIRA_API_TOKEN")
104
104
  if not v:
@@ -116,7 +116,7 @@ class LinearConfig(BaseAdapterConfig):
116
116
  api_url: str = "https://api.linear.app/graphql"
117
117
 
118
118
  @validator("api_key", pre=True, always=True)
119
- def validate_api_key(cls, v):
119
+ def validate_api_key(self, v):
120
120
  if not v:
121
121
  v = os.getenv("LINEAR_API_KEY")
122
122
  if not v:
@@ -155,7 +155,7 @@ class LoggingConfig(BaseModel):
155
155
  class AppConfig(BaseModel):
156
156
  """Main application configuration."""
157
157
 
158
- adapters: Dict[
158
+ adapters: dict[
159
159
  str, Union[GitHubConfig, JiraConfig, LinearConfig, AITrackdownConfig]
160
160
  ] = {}
161
161
  queue: QueueConfig = QueueConfig()
@@ -164,7 +164,7 @@ class AppConfig(BaseModel):
164
164
  default_adapter: Optional[str] = None
165
165
 
166
166
  @root_validator(skip_on_failure=True)
167
- def validate_adapters(cls, values):
167
+ def validate_adapters(self, values):
168
168
  """Validate adapter configurations."""
169
169
  adapters = values.get("adapters", {})
170
170
 
@@ -185,7 +185,7 @@ class AppConfig(BaseModel):
185
185
  """Get configuration for a specific adapter."""
186
186
  return self.adapters.get(adapter_name)
187
187
 
188
- def get_enabled_adapters(self) -> Dict[str, BaseAdapterConfig]:
188
+ def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
189
189
  """Get all enabled adapters."""
190
190
  return {
191
191
  name: config for name, config in self.adapters.items() if config.enabled
@@ -197,7 +197,7 @@ class ConfigurationManager:
197
197
 
198
198
  _instance: Optional["ConfigurationManager"] = None
199
199
  _config: Optional[AppConfig] = None
200
- _config_file_paths: List[Path] = []
200
+ _config_file_paths: list[Path] = []
201
201
 
202
202
  def __new__(cls) -> "ConfigurationManager":
203
203
  """Singleton pattern for global config access."""
@@ -209,7 +209,7 @@ class ConfigurationManager:
209
209
  """Initialize configuration manager."""
210
210
  if not hasattr(self, "_initialized"):
211
211
  self._initialized = True
212
- self._config_cache: Dict[str, Any] = {}
212
+ self._config_cache: dict[str, Any] = {}
213
213
  self._find_config_files()
214
214
 
215
215
  def _find_config_files(self) -> None:
@@ -302,7 +302,7 @@ class ConfigurationManager:
302
302
  self._config = AppConfig(**config_data)
303
303
  return self._config
304
304
 
305
- def _load_config_file(self, config_path: Path) -> Dict[str, Any]:
305
+ def _load_config_file(self, config_path: Path) -> dict[str, Any]:
306
306
  """Load configuration from YAML or JSON file."""
307
307
  try:
308
308
  with open(config_path, encoding="utf-8") as file:
@@ -332,7 +332,7 @@ class ConfigurationManager:
332
332
  config = self.get_config()
333
333
  return config.get_adapter_config(adapter_name)
334
334
 
335
- def get_enabled_adapters(self) -> Dict[str, BaseAdapterConfig]:
335
+ def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
336
336
  """Get all enabled adapter configurations."""
337
337
  config = self.get_config()
338
338
  return config.get_enabled_adapters()