mcp-ticketer 0.1.22__py3-none-any.whl → 0.1.24__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 (33) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +15 -14
  4. mcp_ticketer/adapters/github.py +21 -20
  5. mcp_ticketer/adapters/hybrid.py +13 -12
  6. mcp_ticketer/adapters/jira.py +32 -27
  7. mcp_ticketer/adapters/linear.py +29 -26
  8. mcp_ticketer/cache/memory.py +2 -2
  9. mcp_ticketer/cli/auggie_configure.py +237 -0
  10. mcp_ticketer/cli/codex_configure.py +257 -0
  11. mcp_ticketer/cli/gemini_configure.py +261 -0
  12. mcp_ticketer/cli/main.py +171 -10
  13. mcp_ticketer/cli/migrate_config.py +3 -7
  14. mcp_ticketer/cli/utils.py +8 -8
  15. mcp_ticketer/core/adapter.py +12 -11
  16. mcp_ticketer/core/config.py +17 -17
  17. mcp_ticketer/core/env_discovery.py +24 -24
  18. mcp_ticketer/core/http_client.py +13 -13
  19. mcp_ticketer/core/mappers.py +25 -25
  20. mcp_ticketer/core/models.py +10 -10
  21. mcp_ticketer/core/project_config.py +25 -22
  22. mcp_ticketer/core/registry.py +7 -7
  23. mcp_ticketer/mcp/server.py +18 -18
  24. mcp_ticketer/queue/manager.py +2 -2
  25. mcp_ticketer/queue/queue.py +7 -7
  26. mcp_ticketer/queue/worker.py +8 -8
  27. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/METADATA +58 -8
  28. mcp_ticketer-0.1.24.dist-info/RECORD +45 -0
  29. mcp_ticketer-0.1.22.dist-info/RECORD +0 -42
  30. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/WHEEL +0 -0
  31. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/entry_points.txt +0 -0
  32. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/licenses/LICENSE +0 -0
  33. {mcp_ticketer-0.1.22.dist-info → mcp_ticketer-0.1.24.dist-info}/top_level.txt +0 -0
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
@@ -1090,6 +1118,13 @@ app.add_typer(queue_app, name="queue")
1090
1118
  # Add discover command to main app
1091
1119
  app.add_typer(discover_app, name="discover")
1092
1120
 
1121
+ # Create MCP configuration command group
1122
+ mcp_app = typer.Typer(
1123
+ name="mcp",
1124
+ help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
1125
+ add_completion=False,
1126
+ )
1127
+
1093
1128
 
1094
1129
  @app.command()
1095
1130
  def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
@@ -1205,8 +1240,8 @@ def serve(
1205
1240
  sys.exit(1)
1206
1241
 
1207
1242
 
1208
- @app.command()
1209
- def mcp(
1243
+ @mcp_app.command(name="claude")
1244
+ def mcp_claude(
1210
1245
  global_config: bool = typer.Option(
1211
1246
  False,
1212
1247
  "--global",
@@ -1224,6 +1259,17 @@ def mcp(
1224
1259
 
1225
1260
  By default, configures project-level (.mcp/config.json).
1226
1261
  Use --global to configure Claude Desktop instead.
1262
+
1263
+ Examples:
1264
+ # Configure for current project (default)
1265
+ mcp-ticketer mcp claude
1266
+
1267
+ # Configure Claude Desktop globally
1268
+ mcp-ticketer mcp claude --global
1269
+
1270
+ # Force overwrite existing configuration
1271
+ mcp-ticketer mcp claude --force
1272
+
1227
1273
  """
1228
1274
  from ..cli.mcp_configure import configure_claude_mcp
1229
1275
 
@@ -1234,6 +1280,121 @@ def mcp(
1234
1280
  raise typer.Exit(1)
1235
1281
 
1236
1282
 
1283
+ @mcp_app.command(name="gemini")
1284
+ def mcp_gemini(
1285
+ scope: str = typer.Option(
1286
+ "project",
1287
+ "--scope",
1288
+ "-s",
1289
+ help="Configuration scope: 'project' (default) or 'user'",
1290
+ ),
1291
+ force: bool = typer.Option(
1292
+ False, "--force", "-f", help="Overwrite existing configuration"
1293
+ ),
1294
+ ):
1295
+ """Configure Gemini CLI to use mcp-ticketer MCP server.
1296
+
1297
+ Reads configuration from .mcp-ticketer/config.json and creates
1298
+ Gemini CLI settings file with mcp-ticketer configuration.
1299
+
1300
+ By default, configures project-level (.gemini/settings.json).
1301
+ Use --scope user to configure user-level (~/.gemini/settings.json).
1302
+
1303
+ Examples:
1304
+ # Configure for current project (default)
1305
+ mcp-ticketer mcp gemini
1306
+
1307
+ # Configure at user level
1308
+ mcp-ticketer mcp gemini --scope user
1309
+
1310
+ # Force overwrite existing configuration
1311
+ mcp-ticketer mcp gemini --force
1312
+
1313
+ """
1314
+ from ..cli.gemini_configure import configure_gemini_mcp
1315
+
1316
+ # Validate scope parameter
1317
+ if scope not in ["project", "user"]:
1318
+ console.print(
1319
+ f"[red]✗ Invalid scope:[/red] '{scope}'. Must be 'project' or 'user'"
1320
+ )
1321
+ raise typer.Exit(1)
1322
+
1323
+ try:
1324
+ configure_gemini_mcp(scope=scope, force=force) # type: ignore
1325
+ except Exception as e:
1326
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
1327
+ raise typer.Exit(1)
1328
+
1329
+
1330
+ @mcp_app.command(name="codex")
1331
+ def mcp_codex(
1332
+ force: bool = typer.Option(
1333
+ False, "--force", "-f", help="Overwrite existing configuration"
1334
+ ),
1335
+ ):
1336
+ """Configure Codex CLI to use mcp-ticketer MCP server.
1337
+
1338
+ Reads configuration from .mcp-ticketer/config.json and creates
1339
+ Codex CLI config.toml with mcp-ticketer configuration.
1340
+
1341
+ IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
1342
+ There is no project-level configuration support. After configuration,
1343
+ you must restart Codex CLI for changes to take effect.
1344
+
1345
+ Examples:
1346
+ # Configure Codex CLI globally
1347
+ mcp-ticketer mcp codex
1348
+
1349
+ # Force overwrite existing configuration
1350
+ mcp-ticketer mcp codex --force
1351
+
1352
+ """
1353
+ from ..cli.codex_configure import configure_codex_mcp
1354
+
1355
+ try:
1356
+ configure_codex_mcp(force=force)
1357
+ except Exception as e:
1358
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
1359
+ raise typer.Exit(1)
1360
+
1361
+
1362
+ @mcp_app.command(name="auggie")
1363
+ def mcp_auggie(
1364
+ force: bool = typer.Option(
1365
+ False, "--force", "-f", help="Overwrite existing configuration"
1366
+ ),
1367
+ ):
1368
+ """Configure Auggie CLI to use mcp-ticketer MCP server.
1369
+
1370
+ Reads configuration from .mcp-ticketer/config.json and creates
1371
+ Auggie CLI settings.json with mcp-ticketer configuration.
1372
+
1373
+ IMPORTANT: Auggie CLI ONLY supports global configuration at ~/.augment/settings.json.
1374
+ There is no project-level configuration support. After configuration,
1375
+ you must restart Auggie CLI for changes to take effect.
1376
+
1377
+ Examples:
1378
+ # Configure Auggie CLI globally
1379
+ mcp-ticketer mcp auggie
1380
+
1381
+ # Force overwrite existing configuration
1382
+ mcp-ticketer mcp auggie --force
1383
+
1384
+ """
1385
+ from ..cli.auggie_configure import configure_auggie_mcp
1386
+
1387
+ try:
1388
+ configure_auggie_mcp(force=force)
1389
+ except Exception as e:
1390
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
1391
+ raise typer.Exit(1)
1392
+
1393
+
1394
+ # Add MCP command group to main app (must be after all subcommands are defined)
1395
+ app.add_typer(mcp_app, name="mcp")
1396
+
1397
+
1237
1398
  def main():
1238
1399
  """Main entry point."""
1239
1400
  app()
@@ -3,16 +3,12 @@
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
10
10
 
11
- from ..core.project_config import (
12
- AdapterConfig,
13
- ConfigResolver,
14
- TicketerConfig,
15
- )
11
+ from ..core.project_config import AdapterConfig, ConfigResolver, TicketerConfig
16
12
 
17
13
  console = Console()
18
14
 
@@ -96,7 +92,7 @@ def migrate_config_command(dry_run: bool = False) -> None:
96
92
  console.print(f"[yellow]Old config backed up at: {backup_path}[/yellow]")
97
93
 
98
94
 
99
- def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
95
+ def _migrate_old_to_new(old_config: dict[str, Any]) -> TicketerConfig:
100
96
  """Migrate old configuration format to new format.
101
97
 
102
98
  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()