mcp-ticketer 0.3.1__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 +153 -82
  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 +5 -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.1.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.1.dist-info/RECORD +0 -59
  34. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
  35. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
  36. {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
  37. {mcp_ticketer-0.3.1.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(
@@ -571,6 +574,7 @@ def init(
571
574
 
572
575
  # First try our improved .env configuration loader
573
576
  from ..mcp.server import _load_env_configuration
577
+
574
578
  env_config = _load_env_configuration()
575
579
 
576
580
  if env_config:
@@ -580,8 +584,8 @@ def init(
580
584
  )
581
585
 
582
586
  # Show what was discovered
583
- console.print(f"\n[dim]Configuration found in: .env files[/dim]")
584
- console.print(f"[dim]Confidence: 100%[/dim]")
587
+ console.print("\n[dim]Configuration found in: .env files[/dim]")
588
+ console.print("[dim]Confidence: 100%[/dim]")
585
589
 
586
590
  # Ask user to confirm auto-detected adapter
587
591
  if not typer.confirm(
@@ -640,7 +644,7 @@ def init(
640
644
  if adapter_type == "aitrackdown":
641
645
  config["adapters"]["aitrackdown"] = {
642
646
  "type": "aitrackdown",
643
- "base_path": base_path or ".aitrackdown"
647
+ "base_path": base_path or ".aitrackdown",
644
648
  }
645
649
 
646
650
  elif adapter_type == "linear":
@@ -653,11 +657,12 @@ def init(
653
657
  if not linear_api_key and not discovered:
654
658
  console.print("\n[bold]Linear Configuration[/bold]")
655
659
  console.print("You need a Linear API key to connect to Linear.")
656
- 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
+ )
657
663
 
658
664
  linear_api_key = typer.prompt(
659
- "Enter your Linear API key",
660
- hide_input=True
665
+ "Enter your Linear API key", hide_input=True
661
666
  )
662
667
 
663
668
  if linear_api_key:
@@ -675,8 +680,12 @@ def init(
675
680
  linear_config["team_id"] = linear_team_id
676
681
 
677
682
  if not linear_config.get("api_key") or not linear_config.get("team_id"):
678
- console.print("[red]Error:[/red] Linear requires both API key and team ID")
679
- 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
+ )
680
689
  raise typer.Exit(1)
681
690
 
682
691
  linear_config["type"] = "linear"
@@ -704,18 +713,17 @@ def init(
704
713
 
705
714
  if not token and not discovered:
706
715
  console.print("\nYou need a JIRA API token.")
707
- console.print("[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n")
708
-
709
- token = typer.prompt(
710
- "Enter your JIRA API token",
711
- hide_input=True
716
+ console.print(
717
+ "[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n"
712
718
  )
713
719
 
720
+ token = typer.prompt("Enter your JIRA API token", hide_input=True)
721
+
714
722
  if not project and not discovered:
715
723
  project = typer.prompt(
716
724
  "Default JIRA project key (optional, press Enter to skip)",
717
725
  default="",
718
- show_default=False
726
+ show_default=False,
719
727
  )
720
728
 
721
729
  # Validate required fields
@@ -735,7 +743,7 @@ def init(
735
743
  "server": server,
736
744
  "email": email,
737
745
  "api_token": token,
738
- "type": "jira"
746
+ "type": "jira",
739
747
  }
740
748
 
741
749
  if project:
@@ -755,19 +763,24 @@ def init(
755
763
  console.print("\n[bold]GitHub Configuration[/bold]")
756
764
  console.print("Enter your GitHub repository details.\n")
757
765
 
758
- owner = typer.prompt("GitHub repository owner (username or organization)")
766
+ owner = typer.prompt(
767
+ "GitHub repository owner (username or organization)"
768
+ )
759
769
 
760
770
  if not repo and not discovered:
761
771
  repo = typer.prompt("GitHub repository name")
762
772
 
763
773
  if not token and not discovered:
764
774
  console.print("\nYou need a GitHub Personal Access Token.")
765
- console.print("[dim]Create one at: https://github.com/settings/tokens/new[/dim]")
766
- 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
+ )
767
781
 
768
782
  token = typer.prompt(
769
- "Enter your GitHub Personal Access Token",
770
- hide_input=True
783
+ "Enter your GitHub Personal Access Token", hide_input=True
771
784
  )
772
785
 
773
786
  # Validate required fields
@@ -780,14 +793,16 @@ def init(
780
793
  raise typer.Exit(1)
781
794
 
782
795
  if not token:
783
- 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
+ )
784
799
  raise typer.Exit(1)
785
800
 
786
801
  config["adapters"]["github"] = {
787
802
  "owner": owner,
788
803
  "repo": repo,
789
804
  "token": token,
790
- "type": "github"
805
+ "type": "github",
791
806
  }
792
807
 
793
808
  # 5. Save to appropriate location
@@ -831,13 +846,16 @@ def init(
831
846
  _show_next_steps(console, adapter_type, config_file_path)
832
847
 
833
848
 
834
- 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:
835
852
  """Show helpful next steps after initialization.
836
853
 
837
854
  Args:
838
855
  console: Rich console for output
839
856
  adapter_type: Type of adapter that was configured
840
857
  config_file_path: Path to the configuration file
858
+
841
859
  """
842
860
  console.print("\n[bold green]🎉 Setup Complete![/bold green]")
843
861
  console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
@@ -849,7 +867,9 @@ def _show_next_steps(console: Console, adapter_type: str, config_file_path: Path
849
867
  console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
850
868
 
851
869
  if adapter_type != "aitrackdown":
852
- 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
+ )
853
873
 
854
874
  if adapter_type == "linear":
855
875
  console.print(" Check your Linear workspace for the new ticket")
@@ -1155,11 +1175,14 @@ def status_command():
1155
1175
 
1156
1176
  @app.command()
1157
1177
  def health(
1158
- auto_repair: bool = typer.Option(False, "--auto-repair", help="Attempt automatic repair of issues"),
1159
- 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
+ ),
1160
1184
  ) -> None:
1161
1185
  """Check queue system health and detect issues immediately."""
1162
-
1163
1186
  health_monitor = QueueHealthMonitor()
1164
1187
  health = health_monitor.check_health()
1165
1188
 
@@ -1168,14 +1191,14 @@ def health(
1168
1191
  HealthStatus.HEALTHY: "green",
1169
1192
  HealthStatus.WARNING: "yellow",
1170
1193
  HealthStatus.CRITICAL: "red",
1171
- HealthStatus.FAILED: "red"
1194
+ HealthStatus.FAILED: "red",
1172
1195
  }
1173
1196
 
1174
1197
  status_icon = {
1175
1198
  HealthStatus.HEALTHY: "✓",
1176
1199
  HealthStatus.WARNING: "⚠️",
1177
1200
  HealthStatus.CRITICAL: "🚨",
1178
- HealthStatus.FAILED: "❌"
1201
+ HealthStatus.FAILED: "❌",
1179
1202
  }
1180
1203
 
1181
1204
  color = status_color.get(health["status"], "white")
@@ -1198,7 +1221,10 @@ def health(
1198
1221
  console.print("\n[green]✓ No issues detected[/green]")
1199
1222
 
1200
1223
  # Auto-repair if requested
1201
- 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
+ ]:
1202
1228
  console.print("\n[yellow]Attempting automatic repair...[/yellow]")
1203
1229
  repair_result = health_monitor.auto_repair()
1204
1230
 
@@ -1212,7 +1238,9 @@ def health(
1212
1238
  new_health = health_monitor.check_health()
1213
1239
  new_color = status_color.get(new_health["status"], "white")
1214
1240
  new_icon = status_icon.get(new_health["status"], "?")
1215
- 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
+ )
1216
1244
  else:
1217
1245
  console.print("[yellow]No repair actions available[/yellow]")
1218
1246
 
@@ -1243,7 +1271,6 @@ def create(
1243
1271
  ),
1244
1272
  ) -> None:
1245
1273
  """Create a new ticket with comprehensive health checks."""
1246
-
1247
1274
  # IMMEDIATE HEALTH CHECK - Critical for reliability
1248
1275
  health_monitor = QueueHealthMonitor()
1249
1276
  health = health_monitor.check_health()
@@ -1266,13 +1293,21 @@ def create(
1266
1293
  # Re-check health after repair
1267
1294
  health = health_monitor.check_health()
1268
1295
  if health["status"] == HealthStatus.CRITICAL:
1269
- console.print("[red]❌ Auto-repair failed. Manual intervention required.[/red]")
1270
- 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
+ )
1271
1302
  raise typer.Exit(1)
1272
1303
  else:
1273
- 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
+ )
1274
1307
  else:
1275
- 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
+ )
1276
1311
  raise typer.Exit(1)
1277
1312
 
1278
1313
  elif health["status"] == HealthStatus.WARNING:
@@ -1316,7 +1351,9 @@ def create(
1316
1351
 
1317
1352
  # WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
1318
1353
  if adapter_name == "linear":
1319
- 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
+ )
1320
1357
  try:
1321
1358
  # Load configuration and create adapter directly
1322
1359
  config = load_config()
@@ -1324,20 +1361,27 @@ def create(
1324
1361
 
1325
1362
  # Import and create adapter
1326
1363
  from ..core.registry import AdapterRegistry
1364
+
1327
1365
  adapter = AdapterRegistry.get_adapter(adapter_name, adapter_config)
1328
1366
 
1329
1367
  # Create task directly
1330
- from ..core.models import Task, Priority
1368
+ from ..core.models import Priority, Task
1369
+
1331
1370
  task = Task(
1332
1371
  title=task_data["title"],
1333
1372
  description=task_data.get("description"),
1334
- 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
+ ),
1335
1378
  tags=task_data.get("tags", []),
1336
- assignee=task_data.get("assignee")
1379
+ assignee=task_data.get("assignee"),
1337
1380
  )
1338
1381
 
1339
1382
  # Create ticket synchronously
1340
1383
  import asyncio
1384
+
1341
1385
  result = asyncio.run(adapter.create(task))
1342
1386
 
1343
1387
  console.print(f"[green]✓[/green] Ticket created successfully: {result.id}")
@@ -1345,7 +1389,11 @@ def create(
1345
1389
  console.print(f" Priority: {result.priority}")
1346
1390
  console.print(f" State: {result.state}")
1347
1391
  # Get URL from metadata if available
1348
- 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
+ ):
1349
1397
  console.print(f" URL: {result.metadata['linear']['url']}")
1350
1398
 
1351
1399
  return result.id
@@ -1360,12 +1408,14 @@ def create(
1360
1408
  ticket_data=task_data,
1361
1409
  adapter=adapter_name,
1362
1410
  operation="create",
1363
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1411
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1364
1412
  )
1365
1413
 
1366
1414
  # Register in ticket registry for tracking
1367
1415
  registry = TicketRegistry()
1368
- 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
+ )
1369
1419
 
1370
1420
  console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
1371
1421
  console.print(f" Title: {title}")
@@ -1382,6 +1432,7 @@ def create(
1382
1432
 
1383
1433
  # Give immediate feedback on processing
1384
1434
  import time
1435
+
1385
1436
  time.sleep(1) # Brief pause to let worker start
1386
1437
 
1387
1438
  # Check if item is being processed
@@ -1391,15 +1442,23 @@ def create(
1391
1442
  elif item and item.status == QueueStatus.PENDING:
1392
1443
  console.print("[yellow]⏳ Item is queued for processing[/yellow]")
1393
1444
  else:
1394
- 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
+ )
1395
1448
  else:
1396
1449
  # Worker didn't start - this is a problem
1397
1450
  pending_count = queue.get_pending_count()
1398
1451
  if pending_count > 1: # More than just this item
1399
- console.print(f"[red]❌ Worker failed to start with {pending_count} pending items![/red]")
1400
- 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
+ )
1401
1458
  else:
1402
- 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
+ )
1403
1462
 
1404
1463
 
1405
1464
  @app.command("list")
@@ -1444,7 +1503,7 @@ def list_tickets(
1444
1503
 
1445
1504
  for ticket in tickets:
1446
1505
  # Handle assignee field - Epic doesn't have assignee, Task does
1447
- assignee = getattr(ticket, 'assignee', None) or "-"
1506
+ assignee = getattr(ticket, "assignee", None) or "-"
1448
1507
 
1449
1508
  table.add_row(
1450
1509
  ticket.id or "N/A",
@@ -1526,7 +1585,7 @@ def comment(
1526
1585
  comment = Comment(
1527
1586
  ticket_id=ticket_id,
1528
1587
  content=content,
1529
- author="cli-user" # Could be made configurable
1588
+ author="cli-user", # Could be made configurable
1530
1589
  )
1531
1590
 
1532
1591
  result = await adapter_instance.add_comment(comment)
@@ -1534,7 +1593,7 @@ def comment(
1534
1593
 
1535
1594
  try:
1536
1595
  result = asyncio.run(_comment())
1537
- console.print(f"[green]✓[/green] Comment added successfully")
1596
+ console.print("[green]✓[/green] Comment added successfully")
1538
1597
  if result.id:
1539
1598
  console.print(f"Comment ID: {result.id}")
1540
1599
  console.print(f"Content: {content}")
@@ -1592,7 +1651,7 @@ def update(
1592
1651
  ticket_data=updates,
1593
1652
  adapter=adapter_name,
1594
1653
  operation="update",
1595
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1654
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1596
1655
  )
1597
1656
 
1598
1657
  console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
@@ -1660,7 +1719,7 @@ def transition(
1660
1719
  },
1661
1720
  adapter=adapter_name,
1662
1721
  operation="transition",
1663
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
1722
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
1664
1723
  )
1665
1724
 
1666
1725
  console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
@@ -1722,30 +1781,42 @@ app.add_typer(queue_app, name="queue")
1722
1781
  # Add discover command to main app
1723
1782
  app.add_typer(discover_app, name="discover")
1724
1783
 
1784
+
1725
1785
  # Add diagnostics command
1726
1786
  @app.command()
1727
1787
  def diagnose(
1728
- output_file: Optional[str] = typer.Option(None, "--output", "-o", help="Save full report to file"),
1729
- json_output: bool = typer.Option(False, "--json", help="Output report in JSON format"),
1730
- 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
+ ),
1731
1797
  ) -> None:
1732
1798
  """Run comprehensive system diagnostics and health check."""
1733
1799
  if simple:
1734
1800
  from .simple_health import simple_diagnose
1801
+
1735
1802
  report = simple_diagnose()
1736
1803
  if output_file:
1737
1804
  import json
1738
- with open(output_file, 'w') as f:
1805
+
1806
+ with open(output_file, "w") as f:
1739
1807
  json.dump(report, f, indent=2)
1740
1808
  console.print(f"\n📄 Report saved to: {output_file}")
1741
1809
  if json_output:
1742
1810
  import json
1811
+
1743
1812
  console.print("\n" + json.dumps(report, indent=2))
1744
1813
  if report["issues"]:
1745
1814
  raise typer.Exit(1)
1746
1815
  else:
1747
1816
  try:
1748
- 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
+ )
1749
1820
  except typer.Exit:
1750
1821
  # typer.Exit is expected - don't fall back to simple diagnostics
1751
1822
  raise
@@ -1753,6 +1824,7 @@ def diagnose(
1753
1824
  console.print(f"⚠️ Full diagnostics failed: {e}")
1754
1825
  console.print("🔄 Falling back to simple diagnostics...")
1755
1826
  from .simple_health import simple_diagnose
1827
+
1756
1828
  report = simple_diagnose()
1757
1829
  if report["issues"]:
1758
1830
  raise typer.Exit(1)
@@ -1767,6 +1839,7 @@ def health() -> None:
1767
1839
  if result != 0:
1768
1840
  raise typer.Exit(result)
1769
1841
 
1842
+
1770
1843
  # Create MCP configuration command group
1771
1844
  mcp_app = typer.Typer(
1772
1845
  name="mcp",
@@ -1817,9 +1890,6 @@ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
1817
1890
  console.print(f"\nRetry Count: {item.retry_count}")
1818
1891
 
1819
1892
 
1820
-
1821
-
1822
-
1823
1893
  @app.command()
1824
1894
  def serve(
1825
1895
  adapter: Optional[AdapterType] = typer.Option(
@@ -1857,6 +1927,7 @@ def serve(
1857
1927
  else:
1858
1928
  # Priority 2: .env files
1859
1929
  from ..mcp.server import _load_env_configuration
1930
+
1860
1931
  env_config = _load_env_configuration()
1861
1932
  if env_config:
1862
1933
  adapter_type = env_config["adapter_type"]