mcp-ticketer 0.3.0__py3-none-any.whl → 0.3.2__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 (37) hide show
  1. mcp_ticketer/__version__.py +1 -1
  2. mcp_ticketer/adapters/aitrackdown.py +12 -15
  3. mcp_ticketer/adapters/github.py +7 -4
  4. mcp_ticketer/adapters/jira.py +23 -22
  5. mcp_ticketer/adapters/linear/__init__.py +1 -1
  6. mcp_ticketer/adapters/linear/adapter.py +88 -89
  7. mcp_ticketer/adapters/linear/client.py +71 -52
  8. mcp_ticketer/adapters/linear/mappers.py +88 -68
  9. mcp_ticketer/adapters/linear/queries.py +28 -7
  10. mcp_ticketer/adapters/linear/types.py +57 -50
  11. mcp_ticketer/adapters/linear.py +2 -2
  12. mcp_ticketer/cli/adapter_diagnostics.py +86 -51
  13. mcp_ticketer/cli/diagnostics.py +165 -72
  14. mcp_ticketer/cli/linear_commands.py +156 -113
  15. mcp_ticketer/cli/main.py +194 -100
  16. mcp_ticketer/cli/simple_health.py +73 -45
  17. mcp_ticketer/cli/utils.py +15 -10
  18. mcp_ticketer/core/config.py +23 -19
  19. mcp_ticketer/core/env_discovery.py +33 -4
  20. mcp_ticketer/core/env_loader.py +109 -86
  21. mcp_ticketer/core/exceptions.py +20 -18
  22. mcp_ticketer/core/models.py +9 -0
  23. mcp_ticketer/core/project_config.py +1 -1
  24. mcp_ticketer/mcp/server.py +294 -139
  25. mcp_ticketer/queue/health_monitor.py +152 -121
  26. mcp_ticketer/queue/manager.py +11 -4
  27. mcp_ticketer/queue/queue.py +15 -3
  28. mcp_ticketer/queue/run_worker.py +1 -1
  29. mcp_ticketer/queue/ticket_registry.py +190 -132
  30. mcp_ticketer/queue/worker.py +54 -25
  31. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-0.3.2.dist-info}/METADATA +1 -1
  32. mcp_ticketer-0.3.2.dist-info/RECORD +59 -0
  33. mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
  34. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
  35. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
  36. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
  37. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-0.3.2.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py CHANGED
@@ -12,15 +12,15 @@ from dotenv import load_dotenv
12
12
  from rich.console import Console
13
13
  from rich.table import Table
14
14
 
15
+ # Import adapters module to trigger registration
16
+ import mcp_ticketer.adapters # noqa: F401
17
+
15
18
  from ..__version__ import __version__
16
19
  from ..core import AdapterRegistry, Priority, TicketState
17
- from ..core.models import SearchQuery, Comment
20
+ from ..core.models import Comment, SearchQuery
18
21
  from ..queue import Queue, QueueStatus, WorkerManager
19
- from ..queue.health_monitor import QueueHealthMonitor, HealthStatus
22
+ from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
20
23
  from ..queue.ticket_registry import TicketRegistry
21
-
22
- # Import adapters module to trigger registration
23
- import mcp_ticketer.adapters # noqa: F401
24
24
  from .configure import configure_wizard, set_adapter_config, show_current_config
25
25
  from .diagnostics import run_diagnostics
26
26
  from .discover import app as discover_app
@@ -150,8 +150,8 @@ def _discover_from_env_files() -> Optional[str]:
150
150
 
151
151
  Returns:
152
152
  Adapter name if discovered, None otherwise
153
+
153
154
  """
154
- import os
155
155
  import logging
156
156
  from pathlib import Path
157
157
 
@@ -166,12 +166,12 @@ def _discover_from_env_files() -> Optional[str]:
166
166
  try:
167
167
  # Simple .env parsing (key=value format)
168
168
  env_vars = {}
169
- with open(env_path, 'r') as f:
169
+ with open(env_path) as f:
170
170
  for line in f:
171
171
  line = line.strip()
172
- if line and not line.startswith('#') and '=' in line:
173
- key, value = line.split('=', 1)
174
- env_vars[key.strip()] = value.strip().strip('"\'')
172
+ if line and not line.startswith("#") and "=" in line:
173
+ key, value = line.split("=", 1)
174
+ env_vars[key.strip()] = value.strip().strip("\"'")
175
175
 
176
176
  # Check for adapter-specific variables
177
177
  if env_vars.get("LINEAR_API_KEY"):
@@ -195,6 +195,7 @@ def _save_adapter_to_config(adapter_name: str) -> None:
195
195
 
196
196
  Args:
197
197
  adapter_name: Name of the adapter to save as default
198
+
198
199
  """
199
200
  import logging
200
201
 
@@ -325,6 +326,7 @@ def _prompt_for_adapter_selection(console: Console) -> str:
325
326
 
326
327
  Returns:
327
328
  Selected adapter type
329
+
328
330
  """
329
331
  console.print("\n[bold blue]🚀 MCP Ticketer Setup[/bold blue]")
330
332
  console.print("Choose which ticket system you want to connect to:\n")
@@ -335,26 +337,26 @@ def _prompt_for_adapter_selection(console: Console) -> str:
335
337
  "name": "linear",
336
338
  "title": "Linear",
337
339
  "description": "Modern project management (linear.app)",
338
- "requirements": "API key and team ID"
340
+ "requirements": "API key and team ID",
339
341
  },
340
342
  {
341
343
  "name": "github",
342
344
  "title": "GitHub Issues",
343
345
  "description": "GitHub repository issues",
344
- "requirements": "Personal access token, owner, and repo"
346
+ "requirements": "Personal access token, owner, and repo",
345
347
  },
346
348
  {
347
349
  "name": "jira",
348
350
  "title": "JIRA",
349
351
  "description": "Atlassian JIRA project management",
350
- "requirements": "Server URL, email, and API token"
352
+ "requirements": "Server URL, email, and API token",
351
353
  },
352
354
  {
353
355
  "name": "aitrackdown",
354
356
  "title": "Local Files (AITrackdown)",
355
357
  "description": "Store tickets in local files (no external service)",
356
- "requirements": "None - works offline"
357
- }
358
+ "requirements": "None - works offline",
359
+ },
358
360
  ]
359
361
 
360
362
  # Display options
@@ -366,17 +368,17 @@ def _prompt_for_adapter_selection(console: Console) -> str:
366
368
  # Get user selection
367
369
  while True:
368
370
  try:
369
- choice = typer.prompt(
370
- "Select adapter (1-4)",
371
- type=int,
372
- default=1
373
- )
371
+ choice = typer.prompt("Select adapter (1-4)", type=int, default=1)
374
372
  if 1 <= choice <= len(adapters):
375
373
  selected_adapter = adapters[choice - 1]
376
- console.print(f"\n[green]✓ Selected: {selected_adapter['title']}[/green]")
374
+ console.print(
375
+ f"\n[green]✓ Selected: {selected_adapter['title']}[/green]"
376
+ )
377
377
  return selected_adapter["name"]
378
378
  else:
379
- console.print(f"[red]Please enter a number between 1 and {len(adapters)}[/red]")
379
+ console.print(
380
+ f"[red]Please enter a number between 1 and {len(adapters)}[/red]"
381
+ )
380
382
  except (ValueError, typer.Abort):
381
383
  console.print("[yellow]Setup cancelled.[/yellow]")
382
384
  raise typer.Exit(0)
@@ -447,6 +449,7 @@ def setup(
447
449
 
448
450
  # Setup for different project
449
451
  mcp-ticketer setup --path /path/to/project
452
+
450
453
  """
451
454
  # Call init with all parameters
452
455
  init(
@@ -568,32 +571,56 @@ def init(
568
571
  console.print(
569
572
  "[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
570
573
  )
571
- discovered = discover_config(proj_path)
572
574
 
573
- if discovered and discovered.adapters:
574
- primary = discovered.get_primary_adapter()
575
- if primary:
576
- adapter_type = primary.adapter_type
577
- console.print(
578
- f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
579
- )
575
+ # First try our improved .env configuration loader
576
+ from ..mcp.server import _load_env_configuration
580
577
 
581
- # Show what was discovered
582
- console.print(
583
- f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
584
- )
585
- console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
578
+ env_config = _load_env_configuration()
586
579
 
587
- # Ask user to confirm auto-detected adapter
588
- if not typer.confirm(
589
- f"Use detected {adapter_type} adapter?",
590
- default=True,
591
- ):
580
+ if env_config:
581
+ adapter_type = env_config["adapter_type"]
582
+ console.print(
583
+ f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
584
+ )
585
+
586
+ # Show what was discovered
587
+ console.print("\n[dim]Configuration found in: .env files[/dim]")
588
+ console.print("[dim]Confidence: 100%[/dim]")
589
+
590
+ # Ask user to confirm auto-detected adapter
591
+ if not typer.confirm(
592
+ f"Use detected {adapter_type} adapter?",
593
+ default=True,
594
+ ):
595
+ adapter_type = None # Will trigger interactive selection
596
+ else:
597
+ # Fallback to old discovery system for backward compatibility
598
+ discovered = discover_config(proj_path)
599
+
600
+ if discovered and discovered.adapters:
601
+ primary = discovered.get_primary_adapter()
602
+ if primary:
603
+ adapter_type = primary.adapter_type
604
+ console.print(
605
+ f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
606
+ )
607
+
608
+ # Show what was discovered
609
+ console.print(
610
+ f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
611
+ )
612
+ console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
613
+
614
+ # Ask user to confirm auto-detected adapter
615
+ if not typer.confirm(
616
+ f"Use detected {adapter_type} adapter?",
617
+ default=True,
618
+ ):
619
+ adapter_type = None # Will trigger interactive selection
620
+ else:
592
621
  adapter_type = None # Will trigger interactive selection
593
622
  else:
594
623
  adapter_type = None # Will trigger interactive selection
595
- else:
596
- adapter_type = None # Will trigger interactive selection
597
624
 
598
625
  # If no adapter determined, show interactive selection
599
626
  if not adapter_type:
@@ -617,7 +644,7 @@ def init(
617
644
  if adapter_type == "aitrackdown":
618
645
  config["adapters"]["aitrackdown"] = {
619
646
  "type": "aitrackdown",
620
- "base_path": base_path or ".aitrackdown"
647
+ "base_path": base_path or ".aitrackdown",
621
648
  }
622
649
 
623
650
  elif adapter_type == "linear":
@@ -630,11 +657,12 @@ def init(
630
657
  if not linear_api_key and not discovered:
631
658
  console.print("\n[bold]Linear Configuration[/bold]")
632
659
  console.print("You need a Linear API key to connect to Linear.")
633
- console.print("[dim]Get your API key at: https://linear.app/settings/api[/dim]\n")
660
+ console.print(
661
+ "[dim]Get your API key at: https://linear.app/settings/api[/dim]\n"
662
+ )
634
663
 
635
664
  linear_api_key = typer.prompt(
636
- "Enter your Linear API key",
637
- hide_input=True
665
+ "Enter your Linear API key", hide_input=True
638
666
  )
639
667
 
640
668
  if linear_api_key:
@@ -652,8 +680,12 @@ def init(
652
680
  linear_config["team_id"] = linear_team_id
653
681
 
654
682
  if not linear_config.get("api_key") or not linear_config.get("team_id"):
655
- console.print("[red]Error:[/red] Linear requires both API key and team ID")
656
- console.print("Run 'mcp-ticketer init --adapter linear' with proper credentials")
683
+ console.print(
684
+ "[red]Error:[/red] Linear requires both API key and team ID"
685
+ )
686
+ console.print(
687
+ "Run 'mcp-ticketer init --adapter linear' with proper credentials"
688
+ )
657
689
  raise typer.Exit(1)
658
690
 
659
691
  linear_config["type"] = "linear"
@@ -681,18 +713,17 @@ def init(
681
713
 
682
714
  if not token and not discovered:
683
715
  console.print("\nYou need a JIRA API token.")
684
- console.print("[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n")
685
-
686
- token = typer.prompt(
687
- "Enter your JIRA API token",
688
- hide_input=True
716
+ console.print(
717
+ "[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n"
689
718
  )
690
719
 
720
+ token = typer.prompt("Enter your JIRA API token", hide_input=True)
721
+
691
722
  if not project and not discovered:
692
723
  project = typer.prompt(
693
724
  "Default JIRA project key (optional, press Enter to skip)",
694
725
  default="",
695
- show_default=False
726
+ show_default=False,
696
727
  )
697
728
 
698
729
  # Validate required fields
@@ -712,7 +743,7 @@ def init(
712
743
  "server": server,
713
744
  "email": email,
714
745
  "api_token": token,
715
- "type": "jira"
746
+ "type": "jira",
716
747
  }
717
748
 
718
749
  if project:
@@ -732,19 +763,24 @@ def init(
732
763
  console.print("\n[bold]GitHub Configuration[/bold]")
733
764
  console.print("Enter your GitHub repository details.\n")
734
765
 
735
- owner = typer.prompt("GitHub repository owner (username or organization)")
766
+ owner = typer.prompt(
767
+ "GitHub repository owner (username or organization)"
768
+ )
736
769
 
737
770
  if not repo and not discovered:
738
771
  repo = typer.prompt("GitHub repository name")
739
772
 
740
773
  if not token and not discovered:
741
774
  console.print("\nYou need a GitHub Personal Access Token.")
742
- console.print("[dim]Create one at: https://github.com/settings/tokens/new[/dim]")
743
- console.print("[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]\n")
775
+ console.print(
776
+ "[dim]Create one at: https://github.com/settings/tokens/new[/dim]"
777
+ )
778
+ console.print(
779
+ "[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]\n"
780
+ )
744
781
 
745
782
  token = typer.prompt(
746
- "Enter your GitHub Personal Access Token",
747
- hide_input=True
783
+ "Enter your GitHub Personal Access Token", hide_input=True
748
784
  )
749
785
 
750
786
  # Validate required fields
@@ -757,14 +793,16 @@ def init(
757
793
  raise typer.Exit(1)
758
794
 
759
795
  if not token:
760
- console.print("[red]Error:[/red] GitHub Personal Access Token is required")
796
+ console.print(
797
+ "[red]Error:[/red] GitHub Personal Access Token is required"
798
+ )
761
799
  raise typer.Exit(1)
762
800
 
763
801
  config["adapters"]["github"] = {
764
802
  "owner": owner,
765
803
  "repo": repo,
766
804
  "token": token,
767
- "type": "github"
805
+ "type": "github",
768
806
  }
769
807
 
770
808
  # 5. Save to appropriate location
@@ -808,13 +846,16 @@ def init(
808
846
  _show_next_steps(console, adapter_type, config_file_path)
809
847
 
810
848
 
811
- def _show_next_steps(console: Console, adapter_type: str, config_file_path: Path) -> None:
849
+ def _show_next_steps(
850
+ console: Console, adapter_type: str, config_file_path: Path
851
+ ) -> None:
812
852
  """Show helpful next steps after initialization.
813
853
 
814
854
  Args:
815
855
  console: Rich console for output
816
856
  adapter_type: Type of adapter that was configured
817
857
  config_file_path: Path to the configuration file
858
+
818
859
  """
819
860
  console.print("\n[bold green]🎉 Setup Complete![/bold green]")
820
861
  console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
@@ -826,7 +867,9 @@ def _show_next_steps(console: Console, adapter_type: str, config_file_path: Path
826
867
  console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
827
868
 
828
869
  if adapter_type != "aitrackdown":
829
- console.print(f"\n3. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]")
870
+ console.print(
871
+ f"\n3. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]"
872
+ )
830
873
 
831
874
  if adapter_type == "linear":
832
875
  console.print(" Check your Linear workspace for the new ticket")
@@ -1132,11 +1175,14 @@ def status_command():
1132
1175
 
1133
1176
  @app.command()
1134
1177
  def health(
1135
- auto_repair: bool = typer.Option(False, "--auto-repair", help="Attempt automatic repair of issues"),
1136
- verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed health information")
1178
+ auto_repair: bool = typer.Option(
1179
+ False, "--auto-repair", help="Attempt automatic repair of issues"
1180
+ ),
1181
+ verbose: bool = typer.Option(
1182
+ False, "--verbose", "-v", help="Show detailed health information"
1183
+ ),
1137
1184
  ) -> None:
1138
1185
  """Check queue system health and detect issues immediately."""
1139
-
1140
1186
  health_monitor = QueueHealthMonitor()
1141
1187
  health = health_monitor.check_health()
1142
1188
 
@@ -1145,14 +1191,14 @@ def health(
1145
1191
  HealthStatus.HEALTHY: "green",
1146
1192
  HealthStatus.WARNING: "yellow",
1147
1193
  HealthStatus.CRITICAL: "red",
1148
- HealthStatus.FAILED: "red"
1194
+ HealthStatus.FAILED: "red",
1149
1195
  }
1150
1196
 
1151
1197
  status_icon = {
1152
1198
  HealthStatus.HEALTHY: "✓",
1153
1199
  HealthStatus.WARNING: "⚠️",
1154
1200
  HealthStatus.CRITICAL: "🚨",
1155
- HealthStatus.FAILED: "❌"
1201
+ HealthStatus.FAILED: "❌",
1156
1202
  }
1157
1203
 
1158
1204
  color = status_color.get(health["status"], "white")
@@ -1175,7 +1221,10 @@ def health(
1175
1221
  console.print("\n[green]✓ No issues detected[/green]")
1176
1222
 
1177
1223
  # Auto-repair if requested
1178
- if auto_repair and health["status"] in [HealthStatus.CRITICAL, HealthStatus.WARNING]:
1224
+ if auto_repair and health["status"] in [
1225
+ HealthStatus.CRITICAL,
1226
+ HealthStatus.WARNING,
1227
+ ]:
1179
1228
  console.print("\n[yellow]Attempting automatic repair...[/yellow]")
1180
1229
  repair_result = health_monitor.auto_repair()
1181
1230
 
@@ -1189,7 +1238,9 @@ def health(
1189
1238
  new_health = health_monitor.check_health()
1190
1239
  new_color = status_color.get(new_health["status"], "white")
1191
1240
  new_icon = status_icon.get(new_health["status"], "?")
1192
- console.print(f"[{new_color}]{new_icon} Updated Health: {new_health['status'].upper()}[/{new_color}]")
1241
+ console.print(
1242
+ f"[{new_color}]{new_icon} Updated Health: {new_health['status'].upper()}[/{new_color}]"
1243
+ )
1193
1244
  else:
1194
1245
  console.print("[yellow]No repair actions available[/yellow]")
1195
1246
 
@@ -1220,7 +1271,6 @@ def create(
1220
1271
  ),
1221
1272
  ) -> None:
1222
1273
  """Create a new ticket with comprehensive health checks."""
1223
-
1224
1274
  # IMMEDIATE HEALTH CHECK - Critical for reliability
1225
1275
  health_monitor = QueueHealthMonitor()
1226
1276
  health = health_monitor.check_health()
@@ -1243,13 +1293,21 @@ def create(
1243
1293
  # Re-check health after repair
1244
1294
  health = health_monitor.check_health()
1245
1295
  if health["status"] == HealthStatus.CRITICAL:
1246
- console.print("[red]❌ Auto-repair failed. Manual intervention required.[/red]")
1247
- console.print("[red]Cannot safely create ticket. Please check system status.[/red]")
1296
+ console.print(
1297
+ "[red] Auto-repair failed. Manual intervention required.[/red]"
1298
+ )
1299
+ console.print(
1300
+ "[red]Cannot safely create ticket. Please check system status.[/red]"
1301
+ )
1248
1302
  raise typer.Exit(1)
1249
1303
  else:
1250
- console.print("[green]✓ Auto-repair successful. Proceeding with ticket creation.[/green]")
1304
+ console.print(
1305
+ "[green]✓ Auto-repair successful. Proceeding with ticket creation.[/green]"
1306
+ )
1251
1307
  else:
1252
- console.print("[red]❌ No repair actions available. Manual intervention required.[/red]")
1308
+ console.print(
1309
+ "[red]❌ No repair actions available. Manual intervention required.[/red]"
1310
+ )
1253
1311
  raise typer.Exit(1)
1254
1312
 
1255
1313
  elif health["status"] == HealthStatus.WARNING:
@@ -1293,7 +1351,9 @@ def create(
1293
1351
 
1294
1352
  # WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
1295
1353
  if adapter_name == "linear":
1296
- console.print("[yellow]⚠️[/yellow] Using direct operation for Linear adapter (bypassing queue)")
1354
+ console.print(
1355
+ "[yellow]⚠️[/yellow] Using direct operation for Linear adapter (bypassing queue)"
1356
+ )
1297
1357
  try:
1298
1358
  # Load configuration and create adapter directly
1299
1359
  config = load_config()
@@ -1301,20 +1361,27 @@ def create(
1301
1361
 
1302
1362
  # Import and create adapter
1303
1363
  from ..core.registry import AdapterRegistry
1364
+
1304
1365
  adapter = AdapterRegistry.get_adapter(adapter_name, adapter_config)
1305
1366
 
1306
1367
  # Create task directly
1307
- from ..core.models import Task, Priority
1368
+ from ..core.models import Priority, Task
1369
+
1308
1370
  task = Task(
1309
1371
  title=task_data["title"],
1310
1372
  description=task_data.get("description"),
1311
- priority=Priority(task_data["priority"]) if task_data.get("priority") else Priority.MEDIUM,
1373
+ priority=(
1374
+ Priority(task_data["priority"])
1375
+ if task_data.get("priority")
1376
+ else Priority.MEDIUM
1377
+ ),
1312
1378
  tags=task_data.get("tags", []),
1313
- assignee=task_data.get("assignee")
1379
+ assignee=task_data.get("assignee"),
1314
1380
  )
1315
1381
 
1316
1382
  # Create ticket synchronously
1317
1383
  import asyncio
1384
+
1318
1385
  result = asyncio.run(adapter.create(task))
1319
1386
 
1320
1387
  console.print(f"[green]✓[/green] Ticket created successfully: {result.id}")
@@ -1322,7 +1389,11 @@ def create(
1322
1389
  console.print(f" Priority: {result.priority}")
1323
1390
  console.print(f" State: {result.state}")
1324
1391
  # Get URL from metadata if available
1325
- if result.metadata and 'linear' in result.metadata and 'url' in result.metadata['linear']:
1392
+ if (
1393
+ result.metadata
1394
+ and "linear" in result.metadata
1395
+ and "url" in result.metadata["linear"]
1396
+ ):
1326
1397
  console.print(f" URL: {result.metadata['linear']['url']}")
1327
1398
 
1328
1399
  return result.id
@@ -1337,12 +1408,14 @@ def create(
1337
1408
  ticket_data=task_data,
1338
1409
  adapter=adapter_name,
1339
1410
  operation="create",
1340
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1411
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1341
1412
  )
1342
1413
 
1343
1414
  # Register in ticket registry for tracking
1344
1415
  registry = TicketRegistry()
1345
- registry.register_ticket_operation(queue_id, adapter_name, "create", title, task_data)
1416
+ registry.register_ticket_operation(
1417
+ queue_id, adapter_name, "create", title, task_data
1418
+ )
1346
1419
 
1347
1420
  console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
1348
1421
  console.print(f" Title: {title}")
@@ -1359,6 +1432,7 @@ def create(
1359
1432
 
1360
1433
  # Give immediate feedback on processing
1361
1434
  import time
1435
+
1362
1436
  time.sleep(1) # Brief pause to let worker start
1363
1437
 
1364
1438
  # Check if item is being processed
@@ -1368,15 +1442,23 @@ def create(
1368
1442
  elif item and item.status == QueueStatus.PENDING:
1369
1443
  console.print("[yellow]⏳ Item is queued for processing[/yellow]")
1370
1444
  else:
1371
- console.print("[red]⚠️ Item status unclear - check with 'mcp-ticketer check {queue_id}'[/red]")
1445
+ console.print(
1446
+ "[red]⚠️ Item status unclear - check with 'mcp-ticketer check {queue_id}'[/red]"
1447
+ )
1372
1448
  else:
1373
1449
  # Worker didn't start - this is a problem
1374
1450
  pending_count = queue.get_pending_count()
1375
1451
  if pending_count > 1: # More than just this item
1376
- console.print(f"[red]❌ Worker failed to start with {pending_count} pending items![/red]")
1377
- console.print("[red]This is a critical issue. Try 'mcp-ticketer queue worker start' manually.[/red]")
1452
+ console.print(
1453
+ f"[red] Worker failed to start with {pending_count} pending items![/red]"
1454
+ )
1455
+ console.print(
1456
+ "[red]This is a critical issue. Try 'mcp-ticketer queue worker start' manually.[/red]"
1457
+ )
1378
1458
  else:
1379
- console.print("[yellow]Worker not started (no other pending items)[/yellow]")
1459
+ console.print(
1460
+ "[yellow]Worker not started (no other pending items)[/yellow]"
1461
+ )
1380
1462
 
1381
1463
 
1382
1464
  @app.command("list")
@@ -1421,7 +1503,7 @@ def list_tickets(
1421
1503
 
1422
1504
  for ticket in tickets:
1423
1505
  # Handle assignee field - Epic doesn't have assignee, Task does
1424
- assignee = getattr(ticket, 'assignee', None) or "-"
1506
+ assignee = getattr(ticket, "assignee", None) or "-"
1425
1507
 
1426
1508
  table.add_row(
1427
1509
  ticket.id or "N/A",
@@ -1503,7 +1585,7 @@ def comment(
1503
1585
  comment = Comment(
1504
1586
  ticket_id=ticket_id,
1505
1587
  content=content,
1506
- author="cli-user" # Could be made configurable
1588
+ author="cli-user", # Could be made configurable
1507
1589
  )
1508
1590
 
1509
1591
  result = await adapter_instance.add_comment(comment)
@@ -1511,7 +1593,7 @@ def comment(
1511
1593
 
1512
1594
  try:
1513
1595
  result = asyncio.run(_comment())
1514
- console.print(f"[green]✓[/green] Comment added successfully")
1596
+ console.print("[green]✓[/green] Comment added successfully")
1515
1597
  if result.id:
1516
1598
  console.print(f"Comment ID: {result.id}")
1517
1599
  console.print(f"Content: {content}")
@@ -1569,7 +1651,7 @@ def update(
1569
1651
  ticket_data=updates,
1570
1652
  adapter=adapter_name,
1571
1653
  operation="update",
1572
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1654
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1573
1655
  )
1574
1656
 
1575
1657
  console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
@@ -1637,7 +1719,7 @@ def transition(
1637
1719
  },
1638
1720
  adapter=adapter_name,
1639
1721
  operation="transition",
1640
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1722
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1641
1723
  )
1642
1724
 
1643
1725
  console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
@@ -1699,30 +1781,42 @@ app.add_typer(queue_app, name="queue")
1699
1781
  # Add discover command to main app
1700
1782
  app.add_typer(discover_app, name="discover")
1701
1783
 
1784
+
1702
1785
  # Add diagnostics command
1703
1786
  @app.command()
1704
1787
  def diagnose(
1705
- output_file: Optional[str] = typer.Option(None, "--output", "-o", help="Save full report to file"),
1706
- json_output: bool = typer.Option(False, "--json", help="Output report in JSON format"),
1707
- simple: bool = typer.Option(False, "--simple", help="Use simple diagnostics (no heavy dependencies)"),
1788
+ output_file: Optional[str] = typer.Option(
1789
+ None, "--output", "-o", help="Save full report to file"
1790
+ ),
1791
+ json_output: bool = typer.Option(
1792
+ False, "--json", help="Output report in JSON format"
1793
+ ),
1794
+ simple: bool = typer.Option(
1795
+ False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
1796
+ ),
1708
1797
  ) -> None:
1709
1798
  """Run comprehensive system diagnostics and health check."""
1710
1799
  if simple:
1711
1800
  from .simple_health import simple_diagnose
1801
+
1712
1802
  report = simple_diagnose()
1713
1803
  if output_file:
1714
1804
  import json
1715
- with open(output_file, 'w') as f:
1805
+
1806
+ with open(output_file, "w") as f:
1716
1807
  json.dump(report, f, indent=2)
1717
1808
  console.print(f"\n📄 Report saved to: {output_file}")
1718
1809
  if json_output:
1719
1810
  import json
1811
+
1720
1812
  console.print("\n" + json.dumps(report, indent=2))
1721
1813
  if report["issues"]:
1722
1814
  raise typer.Exit(1)
1723
1815
  else:
1724
1816
  try:
1725
- asyncio.run(run_diagnostics(output_file=output_file, json_output=json_output))
1817
+ asyncio.run(
1818
+ run_diagnostics(output_file=output_file, json_output=json_output)
1819
+ )
1726
1820
  except typer.Exit:
1727
1821
  # typer.Exit is expected - don't fall back to simple diagnostics
1728
1822
  raise
@@ -1730,6 +1824,7 @@ def diagnose(
1730
1824
  console.print(f"⚠️ Full diagnostics failed: {e}")
1731
1825
  console.print("🔄 Falling back to simple diagnostics...")
1732
1826
  from .simple_health import simple_diagnose
1827
+
1733
1828
  report = simple_diagnose()
1734
1829
  if report["issues"]:
1735
1830
  raise typer.Exit(1)
@@ -1744,6 +1839,7 @@ def health() -> None:
1744
1839
  if result != 0:
1745
1840
  raise typer.Exit(result)
1746
1841
 
1842
+
1747
1843
  # Create MCP configuration command group
1748
1844
  mcp_app = typer.Typer(
1749
1845
  name="mcp",
@@ -1794,9 +1890,6 @@ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
1794
1890
  console.print(f"\nRetry Count: {item.retry_count}")
1795
1891
 
1796
1892
 
1797
-
1798
-
1799
-
1800
1893
  @app.command()
1801
1894
  def serve(
1802
1895
  adapter: Optional[AdapterType] = typer.Option(
@@ -1834,6 +1927,7 @@ def serve(
1834
1927
  else:
1835
1928
  # Priority 2: .env files
1836
1929
  from ..mcp.server import _load_env_configuration
1930
+
1837
1931
  env_config = _load_env_configuration()
1838
1932
  if env_config:
1839
1933
  adapter_type = env_config["adapter_type"]