mcp-ticketer 0.1.30__py3-none-any.whl → 1.2.11__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 (109) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/adapters/__init__.py +2 -0
  4. mcp_ticketer/adapters/aitrackdown.py +796 -46
  5. mcp_ticketer/adapters/asana/__init__.py +15 -0
  6. mcp_ticketer/adapters/asana/adapter.py +1416 -0
  7. mcp_ticketer/adapters/asana/client.py +292 -0
  8. mcp_ticketer/adapters/asana/mappers.py +348 -0
  9. mcp_ticketer/adapters/asana/types.py +146 -0
  10. mcp_ticketer/adapters/github.py +879 -129
  11. mcp_ticketer/adapters/hybrid.py +11 -11
  12. mcp_ticketer/adapters/jira.py +973 -73
  13. mcp_ticketer/adapters/linear/__init__.py +24 -0
  14. mcp_ticketer/adapters/linear/adapter.py +2732 -0
  15. mcp_ticketer/adapters/linear/client.py +344 -0
  16. mcp_ticketer/adapters/linear/mappers.py +420 -0
  17. mcp_ticketer/adapters/linear/queries.py +479 -0
  18. mcp_ticketer/adapters/linear/types.py +360 -0
  19. mcp_ticketer/adapters/linear.py +10 -2315
  20. mcp_ticketer/analysis/__init__.py +23 -0
  21. mcp_ticketer/analysis/orphaned.py +218 -0
  22. mcp_ticketer/analysis/similarity.py +224 -0
  23. mcp_ticketer/analysis/staleness.py +266 -0
  24. mcp_ticketer/cache/memory.py +9 -8
  25. mcp_ticketer/cli/adapter_diagnostics.py +421 -0
  26. mcp_ticketer/cli/auggie_configure.py +116 -15
  27. mcp_ticketer/cli/codex_configure.py +274 -82
  28. mcp_ticketer/cli/configure.py +888 -151
  29. mcp_ticketer/cli/diagnostics.py +400 -157
  30. mcp_ticketer/cli/discover.py +297 -26
  31. mcp_ticketer/cli/gemini_configure.py +119 -26
  32. mcp_ticketer/cli/init_command.py +880 -0
  33. mcp_ticketer/cli/instruction_commands.py +435 -0
  34. mcp_ticketer/cli/linear_commands.py +616 -0
  35. mcp_ticketer/cli/main.py +203 -1165
  36. mcp_ticketer/cli/mcp_configure.py +474 -90
  37. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  38. mcp_ticketer/cli/migrate_config.py +12 -8
  39. mcp_ticketer/cli/platform_commands.py +123 -0
  40. mcp_ticketer/cli/platform_detection.py +418 -0
  41. mcp_ticketer/cli/platform_installer.py +513 -0
  42. mcp_ticketer/cli/python_detection.py +126 -0
  43. mcp_ticketer/cli/queue_commands.py +15 -15
  44. mcp_ticketer/cli/setup_command.py +639 -0
  45. mcp_ticketer/cli/simple_health.py +90 -65
  46. mcp_ticketer/cli/ticket_commands.py +1013 -0
  47. mcp_ticketer/cli/update_checker.py +313 -0
  48. mcp_ticketer/cli/utils.py +114 -66
  49. mcp_ticketer/core/__init__.py +24 -1
  50. mcp_ticketer/core/adapter.py +250 -16
  51. mcp_ticketer/core/config.py +145 -37
  52. mcp_ticketer/core/env_discovery.py +101 -22
  53. mcp_ticketer/core/env_loader.py +349 -0
  54. mcp_ticketer/core/exceptions.py +160 -0
  55. mcp_ticketer/core/http_client.py +26 -26
  56. mcp_ticketer/core/instructions.py +405 -0
  57. mcp_ticketer/core/label_manager.py +732 -0
  58. mcp_ticketer/core/mappers.py +42 -30
  59. mcp_ticketer/core/models.py +280 -28
  60. mcp_ticketer/core/onepassword_secrets.py +379 -0
  61. mcp_ticketer/core/project_config.py +183 -49
  62. mcp_ticketer/core/registry.py +3 -3
  63. mcp_ticketer/core/session_state.py +171 -0
  64. mcp_ticketer/core/state_matcher.py +592 -0
  65. mcp_ticketer/core/url_parser.py +425 -0
  66. mcp_ticketer/core/validators.py +69 -0
  67. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  68. mcp_ticketer/mcp/__init__.py +29 -1
  69. mcp_ticketer/mcp/__main__.py +60 -0
  70. mcp_ticketer/mcp/server/__init__.py +25 -0
  71. mcp_ticketer/mcp/server/__main__.py +60 -0
  72. mcp_ticketer/mcp/server/constants.py +58 -0
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/dto.py +195 -0
  75. mcp_ticketer/mcp/server/main.py +1343 -0
  76. mcp_ticketer/mcp/server/response_builder.py +206 -0
  77. mcp_ticketer/mcp/server/routing.py +655 -0
  78. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  79. mcp_ticketer/mcp/server/tools/__init__.py +56 -0
  80. mcp_ticketer/mcp/server/tools/analysis_tools.py +495 -0
  81. mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
  82. mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
  83. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  84. mcp_ticketer/mcp/server/tools/config_tools.py +1439 -0
  85. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  86. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +921 -0
  87. mcp_ticketer/mcp/server/tools/instruction_tools.py +300 -0
  88. mcp_ticketer/mcp/server/tools/label_tools.py +948 -0
  89. mcp_ticketer/mcp/server/tools/pr_tools.py +152 -0
  90. mcp_ticketer/mcp/server/tools/search_tools.py +215 -0
  91. mcp_ticketer/mcp/server/tools/session_tools.py +170 -0
  92. mcp_ticketer/mcp/server/tools/ticket_tools.py +1268 -0
  93. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +547 -0
  94. mcp_ticketer/queue/__init__.py +1 -0
  95. mcp_ticketer/queue/health_monitor.py +168 -136
  96. mcp_ticketer/queue/manager.py +95 -25
  97. mcp_ticketer/queue/queue.py +40 -21
  98. mcp_ticketer/queue/run_worker.py +6 -1
  99. mcp_ticketer/queue/ticket_registry.py +213 -155
  100. mcp_ticketer/queue/worker.py +109 -49
  101. mcp_ticketer-1.2.11.dist-info/METADATA +792 -0
  102. mcp_ticketer-1.2.11.dist-info/RECORD +110 -0
  103. mcp_ticketer/mcp/server.py +0 -1895
  104. mcp_ticketer-0.1.30.dist-info/METADATA +0 -413
  105. mcp_ticketer-0.1.30.dist-info/RECORD +0 -49
  106. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/WHEEL +0 -0
  107. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/entry_points.txt +0 -0
  108. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/licenses/LICENSE +0 -0
  109. {mcp_ticketer-0.1.30.dist-info → mcp_ticketer-1.2.11.dist-info}/top_level.txt +0 -0
@@ -1,72 +1,82 @@
1
1
  """Comprehensive diagnostics and self-diagnosis functionality for MCP Ticketer."""
2
2
 
3
- import asyncio
4
3
  import json
5
4
  import logging
6
- import os
7
5
  import sys
8
6
  from datetime import datetime, timedelta
9
7
  from pathlib import Path
10
- from typing import Any, Dict, List, Optional, Tuple
8
+ from typing import Any
11
9
 
12
10
  import typer
13
11
  from rich.console import Console
14
- from rich.panel import Panel
15
12
  from rich.table import Table
16
- from rich.text import Text
17
13
 
18
- def safe_import_config():
19
- """Safely import configuration with fallback."""
20
- try:
21
- from ..core.config import get_config
22
- return get_config
23
- except ImportError:
24
- # Create a minimal config fallback
25
- class MockConfig:
26
- def get_enabled_adapters(self):
27
- return {}
28
14
 
29
- @property
30
- def default_adapter(self):
31
- return "aitrackdown"
15
+ def get_config() -> Any:
16
+ """Get configuration using the real configuration system."""
17
+ from ..core.config import ConfigurationManager
32
18
 
33
- def get_config():
34
- return MockConfig()
19
+ config_manager = ConfigurationManager()
20
+ return config_manager.load_config()
35
21
 
36
- return get_config
37
22
 
38
- def safe_import_registry():
23
+ def safe_import_registry() -> type:
39
24
  """Safely import adapter registry with fallback."""
40
25
  try:
41
26
  from ..core.registry import AdapterRegistry
27
+
42
28
  return AdapterRegistry
43
29
  except ImportError:
30
+
44
31
  class MockRegistry:
45
32
  @staticmethod
46
- def get_adapter(adapter_type):
33
+ def get_adapter(adapter_type: str) -> None:
47
34
  raise ImportError(f"Adapter {adapter_type} not available")
48
35
 
49
36
  return MockRegistry
50
37
 
51
- def safe_import_queue_manager():
52
- """Safely import queue manager with fallback."""
38
+
39
+ def safe_import_queue_manager() -> type:
40
+ """Safely import worker manager with fallback."""
53
41
  try:
54
- from ..queue.manager import QueueManager
55
- return QueueManager
42
+ from ..queue.manager import WorkerManager as RealWorkerManager
43
+
44
+ # Test if the real worker manager works
45
+ try:
46
+ wm = RealWorkerManager()
47
+ # Test a basic operation
48
+ wm.get_status()
49
+ return RealWorkerManager
50
+ except Exception:
51
+ # Real worker manager failed, use fallback
52
+ pass
53
+
56
54
  except ImportError:
57
- class MockQueueManager:
58
- def get_worker_status(self):
59
- return {"running": False, "pid": None}
55
+ pass
56
+
57
+ class MockWorkerManager:
58
+ def get_status(self) -> dict[str, Any]:
59
+ return {"running": False, "pid": None, "status": "fallback_mode"}
60
+
61
+ def get_worker_status(self) -> dict[str, Any]:
62
+ return {"running": False, "pid": None, "status": "fallback_mode"}
60
63
 
61
- def get_queue_stats(self):
62
- return {"total": 0, "failed": 0}
64
+ def get_queue_stats(self) -> dict[str, Any]:
65
+ return {"total": 0, "failed": 0, "pending": 0, "completed": 0}
66
+
67
+ def health_check(self) -> dict[str, Any]:
68
+ return {
69
+ "status": "degraded",
70
+ "score": 50,
71
+ "details": "Running in fallback mode",
72
+ }
73
+
74
+ return MockWorkerManager
63
75
 
64
- return MockQueueManager
65
76
 
66
77
  # Initialize with safe imports
67
- get_config = safe_import_config()
68
78
  AdapterRegistry = safe_import_registry()
69
- QueueManager = safe_import_queue_manager()
79
+ WorkerManager = safe_import_queue_manager()
70
80
 
71
81
  console = Console()
72
82
  logger = logging.getLogger(__name__)
@@ -75,39 +85,30 @@ logger = logging.getLogger(__name__)
75
85
  class SystemDiagnostics:
76
86
  """Comprehensive system diagnostics and health reporting."""
77
87
 
78
- def __init__(self):
88
+ def __init__(self) -> None:
79
89
  # Initialize lists first
80
- self.issues = []
81
- self.warnings = []
82
- self.successes = []
90
+ self.issues: list[str] = []
91
+ self.warnings: list[str] = []
92
+ self.successes: list[str] = []
83
93
 
84
94
  try:
85
95
  self.config = get_config()
86
- # Check if this is a mock config
87
- if hasattr(self.config, '__class__') and 'Mock' in self.config.__class__.__name__:
88
- self.config_available = False
89
- self.warnings.append("Configuration system using fallback mode")
90
- else:
91
- self.config_available = True
96
+ self.config_available = True
92
97
  except Exception as e:
93
98
  self.config = None
94
99
  self.config_available = False
95
- console.print(f"āš ļø Could not load configuration: {e}")
100
+ console.print(f"āŒ Could not load configuration: {e}")
101
+ raise e
96
102
 
97
103
  try:
98
- self.queue_manager = QueueManager()
99
- # Check if this is a mock queue manager
100
- if hasattr(self.queue_manager, '__class__') and 'Mock' in self.queue_manager.__class__.__name__:
101
- self.queue_available = False
102
- self.warnings.append("Queue system using fallback mode")
103
- else:
104
- self.queue_available = True
104
+ self.worker_manager = WorkerManager()
105
+ self.queue_available = True
105
106
  except Exception as e:
106
- self.queue_manager = None
107
+ self.worker_manager = None
107
108
  self.queue_available = False
108
- console.print(f"āš ļø Could not initialize queue manager: {e}")
109
+ console.print(f"āš ļø Could not initialize worker manager: {e}")
109
110
 
110
- async def run_full_diagnosis(self) -> Dict[str, Any]:
111
+ async def run_full_diagnosis(self) -> dict[str, Any]:
111
112
  """Run complete system diagnosis and return detailed report."""
112
113
  console.print("\nšŸ” [bold blue]MCP Ticketer System Diagnosis[/bold blue]")
113
114
  console.print("=" * 60)
@@ -131,24 +132,29 @@ class SystemDiagnostics:
131
132
  """Get current version information."""
132
133
  try:
133
134
  from ..__version__ import __version__
135
+
134
136
  return __version__
135
137
  except ImportError:
136
138
  return "unknown"
137
139
 
138
- def _get_system_info(self) -> Dict[str, Any]:
140
+ def _get_system_info(self) -> dict[str, Any]:
139
141
  """Gather system information."""
140
142
  return {
141
143
  "python_version": sys.version,
142
144
  "platform": sys.platform,
143
145
  "working_directory": str(Path.cwd()),
144
- "config_path": str(self.config.config_file) if hasattr(self.config, 'config_file') else "unknown",
146
+ "config_path": (
147
+ str(self.config.config_file)
148
+ if hasattr(self.config, "config_file")
149
+ else "unknown"
150
+ ),
145
151
  }
146
152
 
147
- async def _diagnose_configuration(self) -> Dict[str, Any]:
153
+ async def _diagnose_configuration(self) -> dict[str, Any]:
148
154
  """Diagnose configuration issues."""
149
155
  console.print("\nšŸ“‹ [yellow]Configuration Analysis[/yellow]")
150
156
 
151
- config_status = {
157
+ config_status: dict[str, Any] = {
152
158
  "status": "healthy",
153
159
  "adapters_configured": 0,
154
160
  "default_adapter": None,
@@ -172,6 +178,7 @@ class SystemDiagnostics:
172
178
 
173
179
  # Try to detect adapters from environment variables
174
180
  import os
181
+
175
182
  env_adapters = []
176
183
  if os.getenv("LINEAR_API_KEY"):
177
184
  env_adapters.append("linear")
@@ -184,39 +191,48 @@ class SystemDiagnostics:
184
191
  config_status["default_adapter"] = "aitrackdown"
185
192
 
186
193
  if env_adapters:
187
- console.print(f"ā„¹ļø Detected {len(env_adapters)} adapter(s) from environment: {', '.join(env_adapters)}")
194
+ console.print(
195
+ f"ā„¹ļø Detected {len(env_adapters)} adapter(s) from environment: {', '.join(env_adapters)}"
196
+ )
188
197
  else:
189
- console.print("ā„¹ļø No adapter environment variables detected, using aitrackdown")
198
+ console.print(
199
+ "ā„¹ļø No adapter environment variables detected, using aitrackdown"
200
+ )
190
201
 
191
202
  return config_status
192
203
 
193
204
  try:
194
- # Check adapter configurations
195
- adapters = self.config.get_enabled_adapters()
196
- config_status["adapters_configured"] = len(adapters)
197
- config_status["default_adapter"] = self.config.default_adapter
205
+ # Check adapter configurations using the same approach as working commands
206
+ from .utils import CommonPatterns
207
+
208
+ raw_config = CommonPatterns.load_config()
209
+ adapters_config = raw_config.get("adapters", {})
210
+ config_status["adapters_configured"] = len(adapters_config)
211
+ config_status["default_adapter"] = raw_config.get("default_adapter")
198
212
 
199
- if not adapters:
213
+ if not adapters_config:
200
214
  issue = "No adapters configured"
201
215
  config_status["issues"].append(issue)
202
216
  config_status["status"] = "critical"
203
217
  self.issues.append(issue)
204
218
  console.print(f"āŒ {issue}")
205
219
  else:
206
- console.print(f"āœ… {len(adapters)} adapter(s) configured")
220
+ console.print(f"āœ… {len(adapters_config)} adapter(s) configured")
207
221
 
208
222
  # Check each adapter configuration
209
- for name, adapter_config in adapters.items():
223
+ for name, _adapter_config in adapters_config.items():
210
224
  try:
211
- adapter_class = AdapterRegistry.get_adapter(adapter_config.type.value)
212
- adapter = adapter_class(adapter_config.dict())
213
-
225
+ # Use the same adapter creation approach as working commands
226
+ adapter = CommonPatterns.get_adapter(override_adapter=name)
227
+
214
228
  # Test adapter validation if available
215
- if hasattr(adapter, 'validate_credentials'):
229
+ if hasattr(adapter, "validate_credentials"):
216
230
  is_valid, error = adapter.validate_credentials()
217
231
  if is_valid:
218
232
  console.print(f"āœ… {name}: credentials valid")
219
- self.successes.append(f"{name} adapter configured correctly")
233
+ self.successes.append(
234
+ f"{name} adapter configured correctly"
235
+ )
220
236
  else:
221
237
  issue = f"{name}: credential validation failed - {error}"
222
238
  config_status["issues"].append(issue)
@@ -240,11 +256,11 @@ class SystemDiagnostics:
240
256
 
241
257
  return config_status
242
258
 
243
- async def _diagnose_adapters(self) -> Dict[str, Any]:
259
+ async def _diagnose_adapters(self) -> dict[str, Any]:
244
260
  """Diagnose adapter functionality."""
245
261
  console.print("\nšŸ”Œ [yellow]Adapter Diagnosis[/yellow]")
246
-
247
- adapter_status = {
262
+
263
+ adapter_status: dict[str, Any] = {
248
264
  "total_adapters": 0,
249
265
  "healthy_adapters": 0,
250
266
  "failed_adapters": 0,
@@ -252,24 +268,32 @@ class SystemDiagnostics:
252
268
  }
253
269
 
254
270
  try:
255
- adapters = self.config.get_enabled_adapters()
256
- adapter_status["total_adapters"] = len(adapters)
271
+ # Use the same configuration loading approach as working commands
272
+ from .utils import CommonPatterns
257
273
 
258
- for name, adapter_config in adapters.items():
259
- details = {
260
- "type": adapter_config.type.value,
274
+ raw_config = CommonPatterns.load_config()
275
+ adapters_config = raw_config.get("adapters", {})
276
+ adapter_status["total_adapters"] = len(adapters_config)
277
+
278
+ for name, adapter_config in adapters_config.items():
279
+ adapter_type = adapter_config.get("type", name)
280
+
281
+ details: dict[str, Any] = {
282
+ "type": adapter_type,
261
283
  "status": "unknown",
262
284
  "last_test": None,
263
285
  "error": None,
264
286
  }
265
287
 
266
288
  try:
267
- adapter_class = AdapterRegistry.get_adapter(adapter_config.type.value)
268
- adapter = adapter_class(adapter_config.dict())
269
-
289
+ # Use the same adapter creation approach as working commands
290
+ from .utils import CommonPatterns
291
+
292
+ adapter = CommonPatterns.get_adapter(override_adapter=adapter_type)
293
+
270
294
  # Test basic adapter functionality
271
295
  test_start = datetime.now()
272
-
296
+
273
297
  # Try to list tickets (non-destructive test)
274
298
  try:
275
299
  await adapter.list(limit=1)
@@ -302,53 +326,79 @@ class SystemDiagnostics:
302
326
 
303
327
  return adapter_status
304
328
 
305
- async def _diagnose_queue_system(self) -> Dict[str, Any]:
306
- """Diagnose queue system health."""
329
+ async def _diagnose_queue_system(self) -> dict[str, Any]:
330
+ """Diagnose queue system health with active testing."""
307
331
  console.print("\n⚔ [yellow]Queue System Diagnosis[/yellow]")
308
-
309
- queue_status = {
332
+
333
+ queue_status: dict[str, Any] = {
310
334
  "worker_running": False,
311
335
  "worker_pid": None,
312
336
  "queue_stats": {},
313
337
  "recent_failures": [],
314
338
  "failure_rate": 0.0,
315
339
  "health_score": 0,
340
+ "worker_start_test": {"attempted": False, "success": False, "error": None},
341
+ "queue_operation_test": {
342
+ "attempted": False,
343
+ "success": False,
344
+ "error": None,
345
+ },
316
346
  }
317
347
 
318
348
  try:
319
349
  if not self.queue_available:
320
- warning = "Queue system in fallback mode - limited functionality"
350
+ warning = "Queue system in fallback mode - testing basic functionality"
321
351
  self.warnings.append(warning)
322
352
  console.print(f"āš ļø {warning}")
323
- queue_status["worker_running"] = False
324
- queue_status["worker_pid"] = None
325
- queue_status["health_score"] = 50 # Degraded but not critical
353
+
354
+ # Even in fallback mode, test if we can create a basic queue operation
355
+ test_result = await self._test_basic_queue_functionality()
356
+ queue_status["queue_operation_test"] = test_result
357
+ queue_status["health_score"] = 50 if test_result["success"] else 25
326
358
  return queue_status
327
359
 
328
- # Check worker status
329
- worker_status = self.queue_manager.get_worker_status()
360
+ # Test 1: Check current worker status
361
+ console.print("šŸ” Checking current worker status...")
362
+ worker_status = self.worker_manager.get_status()
330
363
  queue_status["worker_running"] = worker_status.get("running", False)
331
364
  queue_status["worker_pid"] = worker_status.get("pid")
332
365
 
333
366
  if queue_status["worker_running"]:
334
- console.print(f"āœ… Queue worker running (PID: {queue_status['worker_pid']})")
367
+ console.print(
368
+ f"āœ… Queue worker running (PID: {queue_status['worker_pid']})"
369
+ )
335
370
  self.successes.append("Queue worker is running")
336
371
  else:
337
- issue = "Queue worker not running"
338
- self.issues.append(issue)
339
- console.print(f"āŒ {issue}")
372
+ console.print("āš ļø Queue worker not running - attempting to start...")
340
373
 
341
- # Get queue statistics
342
- stats = self.queue_manager.get_queue_stats()
374
+ # Test 2: Try to start worker
375
+ start_test = await self._test_worker_startup()
376
+ queue_status["worker_start_test"] = start_test
377
+
378
+ if start_test["success"]:
379
+ console.print("āœ… Successfully started queue worker")
380
+ queue_status["worker_running"] = True
381
+ self.successes.append("Queue worker started successfully")
382
+ else:
383
+ console.print(
384
+ f"āŒ Failed to start queue worker: {start_test['error']}"
385
+ )
386
+ self.issues.append(
387
+ f"Queue worker startup failed: {start_test['error']}"
388
+ )
389
+
390
+ # Test 3: Get queue statistics
391
+ console.print("šŸ” Analyzing queue statistics...")
392
+ stats = self.worker_manager.queue.get_stats()
343
393
  queue_status["queue_stats"] = stats
344
-
394
+
345
395
  total_items = stats.get("total", 0)
346
396
  failed_items = stats.get("failed", 0)
347
-
397
+
348
398
  if total_items > 0:
349
399
  failure_rate = (failed_items / total_items) * 100
350
400
  queue_status["failure_rate"] = failure_rate
351
-
401
+
352
402
  if failure_rate > 50:
353
403
  issue = f"High failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})"
354
404
  self.issues.append(issue)
@@ -358,16 +408,43 @@ class SystemDiagnostics:
358
408
  self.warnings.append(warning)
359
409
  console.print(f"āš ļø {warning}")
360
410
  else:
361
- console.print(f"āœ… Queue failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})")
362
-
363
- # Calculate health score
411
+ console.print(
412
+ f"āœ… Queue failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})"
413
+ )
414
+
415
+ # Test 4: Test actual queue operations
416
+ console.print("šŸ” Testing queue operations...")
417
+ operation_test = await self._test_queue_operations()
418
+ queue_status["queue_operation_test"] = operation_test
419
+
420
+ if operation_test["success"]:
421
+ console.print("āœ… Queue operations test passed")
422
+ self.successes.append("Queue operations working correctly")
423
+ else:
424
+ console.print(
425
+ f"āŒ Queue operations test failed: {operation_test['error']}"
426
+ )
427
+ self.issues.append(
428
+ f"Queue operations failed: {operation_test['error']}"
429
+ )
430
+
431
+ # Calculate health score based on actual tests
364
432
  health_score = 100
365
433
  if not queue_status["worker_running"]:
366
- health_score -= 50
367
- health_score -= min(queue_status["failure_rate"], 50)
434
+ health_score -= 30
435
+ if (
436
+ not queue_status["worker_start_test"]["success"]
437
+ and queue_status["worker_start_test"]["attempted"]
438
+ ):
439
+ health_score -= 20
440
+ if not queue_status["queue_operation_test"]["success"]:
441
+ health_score -= 30
442
+ health_score -= min(queue_status["failure_rate"], 20)
368
443
  queue_status["health_score"] = max(0, health_score)
369
444
 
370
- console.print(f"šŸ“Š Queue health score: {queue_status['health_score']}/100")
445
+ console.print(
446
+ f"šŸ“Š Queue health score: {queue_status['health_score']}/100 (based on active testing)"
447
+ )
371
448
 
372
449
  except Exception as e:
373
450
  issue = f"Queue system diagnosis failed: {str(e)}"
@@ -376,11 +453,130 @@ class SystemDiagnostics:
376
453
 
377
454
  return queue_status
378
455
 
379
- async def _analyze_recent_logs(self) -> Dict[str, Any]:
456
+ async def _test_worker_startup(self) -> dict[str, Any]:
457
+ """Test starting a queue worker."""
458
+ test_result: dict[str, Any] = {
459
+ "attempted": True,
460
+ "success": False,
461
+ "error": None,
462
+ "details": None,
463
+ }
464
+
465
+ try:
466
+ # Try to start worker using the worker manager
467
+ if hasattr(self.worker_manager, "start"):
468
+ result = self.worker_manager.start()
469
+ test_result["success"] = result
470
+ test_result["details"] = (
471
+ "Worker started successfully"
472
+ if result
473
+ else "Worker failed to start"
474
+ )
475
+ else:
476
+ # Try alternative method - use CLI command
477
+ import subprocess
478
+
479
+ result = subprocess.run(
480
+ ["mcp-ticketer", "queue", "worker", "start"],
481
+ capture_output=True,
482
+ text=True,
483
+ timeout=10,
484
+ )
485
+ if result.returncode == 0:
486
+ test_result["success"] = True
487
+ test_result["details"] = "Worker started via CLI"
488
+ else:
489
+ test_result["error"] = f"CLI start failed: {result.stderr}"
490
+
491
+ except subprocess.TimeoutExpired:
492
+ test_result["error"] = "Worker startup timed out"
493
+ except Exception as e:
494
+ test_result["error"] = str(e)
495
+
496
+ return test_result
497
+
498
+ async def _test_queue_operations(self) -> dict[str, Any]:
499
+ """Test basic queue operations."""
500
+ test_result: dict[str, Any] = {
501
+ "attempted": True,
502
+ "success": False,
503
+ "error": None,
504
+ "details": None,
505
+ }
506
+
507
+ try:
508
+ # Test creating a simple queue item (diagnostic test)
509
+ from ..core.models import Priority, Task
510
+ from ..queue.queue import Queue
511
+
512
+ test_task = Task( # type: ignore[call-arg]
513
+ title="[DIAGNOSTIC TEST] Queue functionality test",
514
+ description="This is a diagnostic test - safe to ignore",
515
+ priority=Priority.LOW,
516
+ )
517
+
518
+ # Try to queue the test task using the correct Queue.add() method
519
+ queue = Queue()
520
+ queue_id = queue.add(
521
+ ticket_data=test_task.model_dump(),
522
+ adapter="aitrackdown",
523
+ operation="create",
524
+ )
525
+ test_result["success"] = True
526
+ test_result["details"] = f"Test task queued successfully: {queue_id}"
527
+
528
+ except Exception as e:
529
+ test_result["error"] = str(e)
530
+
531
+ return test_result
532
+
533
+ async def _test_basic_queue_functionality(self) -> dict[str, Any]:
534
+ """Test basic queue functionality in fallback mode."""
535
+ test_result: dict[str, Any] = {
536
+ "attempted": True,
537
+ "success": False,
538
+ "error": None,
539
+ "details": None,
540
+ }
541
+
542
+ try:
543
+ # Test if we can at least create a task directly (bypass queue)
544
+ from ..adapters.aitrackdown import AITrackdownAdapter
545
+ from ..core.models import Priority, Task
546
+
547
+ test_task = Task( # type: ignore[call-arg]
548
+ title="[DIAGNOSTIC TEST] Direct adapter test",
549
+ description="Testing direct adapter functionality",
550
+ priority=Priority.LOW,
551
+ )
552
+
553
+ # Try direct adapter creation
554
+ adapter_config = {
555
+ "type": "aitrackdown",
556
+ "enabled": True,
557
+ "base_path": "/tmp/mcp-ticketer-diagnostic-test",
558
+ }
559
+
560
+ adapter = AITrackdownAdapter(adapter_config)
561
+ result = await adapter.create(test_task)
562
+
563
+ test_result["success"] = True
564
+ test_result["details"] = f"Direct adapter test passed: {result.id}"
565
+
566
+ # Clean up test
567
+ if result.id:
568
+ await adapter.delete(result.id)
569
+
570
+ except Exception as e:
571
+ test_result["error"] = str(e)
572
+
573
+ return test_result
574
+
575
+ async def _analyze_recent_logs(self) -> dict[str, Any]:
380
576
  """Analyze recent log entries for issues."""
381
577
  console.print("\nšŸ“ [yellow]Recent Log Analysis[/yellow]")
382
-
383
- log_analysis = {
578
+
579
+ log_analysis: dict[str, Any] = {
384
580
  "log_files_found": [],
385
581
  "recent_errors": [],
386
582
  "recent_warnings": [],
@@ -403,7 +599,9 @@ class SystemDiagnostics:
403
599
  if not log_analysis["log_files_found"]:
404
600
  console.print("ā„¹ļø No log files found in standard locations")
405
601
  else:
406
- console.print(f"āœ… Found logs in {len(log_analysis['log_files_found'])} location(s)")
602
+ console.print(
603
+ f"āœ… Found logs in {len(log_analysis['log_files_found'])} location(s)"
604
+ )
407
605
 
408
606
  except Exception as e:
409
607
  issue = f"Log analysis failed: {str(e)}"
@@ -412,21 +610,28 @@ class SystemDiagnostics:
412
610
 
413
611
  return log_analysis
414
612
 
415
- async def _analyze_log_directory(self, log_path: Path, log_analysis: Dict[str, Any]):
613
+ async def _analyze_log_directory(
614
+ self, log_path: Path, log_analysis: dict[str, Any]
615
+ ) -> None:
416
616
  """Analyze logs in a specific directory."""
417
617
  try:
418
618
  for log_file in log_path.glob("*.log"):
419
- if log_file.stat().st_mtime > (datetime.now() - timedelta(hours=24)).timestamp():
619
+ if (
620
+ log_file.stat().st_mtime
621
+ > (datetime.now() - timedelta(hours=24)).timestamp()
622
+ ):
420
623
  await self._parse_log_file(log_file, log_analysis)
421
624
  except Exception as e:
422
625
  self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
423
626
 
424
- async def _parse_log_file(self, log_file: Path, log_analysis: Dict[str, Any]):
627
+ async def _parse_log_file(
628
+ self, log_file: Path, log_analysis: dict[str, Any]
629
+ ) -> None:
425
630
  """Parse individual log file for issues."""
426
631
  try:
427
- with open(log_file, 'r') as f:
632
+ with open(log_file) as f:
428
633
  lines = f.readlines()[-100:] # Last 100 lines
429
-
634
+
430
635
  for line in lines:
431
636
  if "ERROR" in line:
432
637
  log_analysis["recent_errors"].append(line.strip())
@@ -436,11 +641,11 @@ class SystemDiagnostics:
436
641
  except Exception as e:
437
642
  self.warnings.append(f"Could not parse {log_file}: {str(e)}")
438
643
 
439
- async def _analyze_performance(self) -> Dict[str, Any]:
644
+ async def _analyze_performance(self) -> dict[str, Any]:
440
645
  """Analyze system performance metrics."""
441
646
  console.print("\n⚔ [yellow]Performance Analysis[/yellow]")
442
-
443
- performance = {
647
+
648
+ performance: dict[str, Any] = {
444
649
  "response_times": {},
445
650
  "throughput": {},
446
651
  "resource_usage": {},
@@ -448,17 +653,17 @@ class SystemDiagnostics:
448
653
 
449
654
  try:
450
655
  # Test basic operations performance
451
- start_time = datetime.now()
452
-
656
+ datetime.now()
657
+
453
658
  # Test configuration loading
454
659
  config_start = datetime.now()
455
660
  _ = get_config()
456
661
  config_time = (datetime.now() - config_start).total_seconds()
457
662
  performance["response_times"]["config_load"] = config_time
458
-
663
+
459
664
  if config_time > 1.0:
460
665
  self.warnings.append(f"Slow configuration loading: {config_time:.2f}s")
461
-
666
+
462
667
  console.print(f"šŸ“Š Configuration load time: {config_time:.3f}s")
463
668
 
464
669
  except Exception as e:
@@ -468,32 +673,42 @@ class SystemDiagnostics:
468
673
 
469
674
  return performance
470
675
 
471
- def _generate_recommendations(self) -> List[str]:
676
+ def _generate_recommendations(self) -> list[str]:
472
677
  """Generate actionable recommendations based on diagnosis."""
473
- recommendations = []
678
+ recommendations: list[str] = []
474
679
 
475
680
  if self.issues:
476
- recommendations.append("🚨 Critical issues detected - immediate attention required")
477
-
681
+ recommendations.append(
682
+ "🚨 Critical issues detected - immediate attention required"
683
+ )
684
+
478
685
  if any("Queue worker not running" in issue for issue in self.issues):
479
- recommendations.append("• Restart queue worker: mcp-ticketer queue worker restart")
480
-
686
+ recommendations.append(
687
+ "• Restart queue worker: mcp-ticketer queue worker restart"
688
+ )
689
+
481
690
  if any("failure rate" in issue.lower() for issue in self.issues):
482
691
  recommendations.append("• Check queue system logs for error patterns")
483
- recommendations.append("• Consider clearing failed queue items: mcp-ticketer queue clear --failed")
484
-
692
+ recommendations.append(
693
+ "• Consider clearing failed queue items: mcp-ticketer queue clear --failed"
694
+ )
695
+
485
696
  if any("No adapters configured" in issue for issue in self.issues):
486
- recommendations.append("• Configure at least one adapter: mcp-ticketer init-aitrackdown")
697
+ recommendations.append(
698
+ "• Configure at least one adapter: mcp-ticketer init-aitrackdown"
699
+ )
487
700
 
488
701
  if self.warnings:
489
702
  recommendations.append("āš ļø Warnings detected - monitoring recommended")
490
703
 
491
704
  if not self.issues and not self.warnings:
492
- recommendations.append("āœ… System appears healthy - no immediate action required")
705
+ recommendations.append(
706
+ "āœ… System appears healthy - no immediate action required"
707
+ )
493
708
 
494
709
  return recommendations
495
710
 
496
- def _display_diagnosis_summary(self, report: Dict[str, Any]):
711
+ def _display_diagnosis_summary(self, report: dict[str, Any]) -> None:
497
712
  """Display a comprehensive diagnosis summary."""
498
713
  console.print("\n" + "=" * 60)
499
714
  console.print("šŸ“‹ [bold green]DIAGNOSIS SUMMARY[/bold green]")
@@ -513,7 +728,9 @@ class SystemDiagnostics:
513
728
  status_text = "HEALTHY"
514
729
  status_icon = "āœ…"
515
730
 
516
- console.print(f"\n{status_icon} [bold {status_color}]System Status: {status_text}[/bold {status_color}]")
731
+ console.print(
732
+ f"\n{status_icon} [bold {status_color}]System Status: {status_text}[/bold {status_color}]"
733
+ )
517
734
 
518
735
  # Statistics
519
736
  stats_table = Table(show_header=True, header_style="bold blue")
@@ -522,40 +739,66 @@ class SystemDiagnostics:
522
739
  stats_table.add_column("Details")
523
740
 
524
741
  # Add component statuses
525
- config_status = "āœ… OK" if not any("configuration" in issue.lower() for issue in self.issues) else "āŒ FAILED"
526
- stats_table.add_row("Configuration", config_status, f"{report['configuration']['adapters_configured']} adapters")
527
-
528
- queue_health = report['queue_system']['health_score']
529
- queue_status = "āœ… OK" if queue_health > 80 else "āš ļø DEGRADED" if queue_health > 50 else "āŒ FAILED"
530
- stats_table.add_row("Queue System", queue_status, f"{queue_health}/100 health score")
531
-
532
- adapter_stats = report['adapters']
533
- adapter_status = "āœ… OK" if adapter_stats['failed_adapters'] == 0 else "āŒ FAILED"
534
- stats_table.add_row("Adapters", adapter_status, f"{adapter_stats['healthy_adapters']}/{adapter_stats['total_adapters']} healthy")
742
+ config_status = (
743
+ "āœ… OK"
744
+ if not any("configuration" in issue.lower() for issue in self.issues)
745
+ else "āŒ FAILED"
746
+ )
747
+ stats_table.add_row(
748
+ "Configuration",
749
+ config_status,
750
+ f"{report['configuration']['adapters_configured']} adapters",
751
+ )
752
+
753
+ queue_health = report["queue_system"]["health_score"]
754
+ queue_status = (
755
+ "āœ… OK"
756
+ if queue_health > 80
757
+ else "āš ļø DEGRADED" if queue_health > 50 else "āŒ FAILED"
758
+ )
759
+ stats_table.add_row(
760
+ "Queue System", queue_status, f"{queue_health}/100 health score"
761
+ )
762
+
763
+ adapter_stats = report["adapters"]
764
+ adapter_status = (
765
+ "āœ… OK" if adapter_stats["failed_adapters"] == 0 else "āŒ FAILED"
766
+ )
767
+ stats_table.add_row(
768
+ "Adapters",
769
+ adapter_status,
770
+ f"{adapter_stats['healthy_adapters']}/{adapter_stats['total_adapters']} healthy",
771
+ )
535
772
 
536
773
  console.print(stats_table)
537
774
 
538
775
  # Issues and recommendations
539
776
  if self.issues:
540
- console.print(f"\n🚨 [bold red]Critical Issues ({len(self.issues)}):[/bold red]")
777
+ console.print(
778
+ f"\n🚨 [bold red]Critical Issues ({len(self.issues)}):[/bold red]"
779
+ )
541
780
  for issue in self.issues:
542
781
  console.print(f" • {issue}")
543
782
 
544
783
  if self.warnings:
545
- console.print(f"\nāš ļø [bold yellow]Warnings ({len(self.warnings)}):[/bold yellow]")
784
+ console.print(
785
+ f"\nāš ļø [bold yellow]Warnings ({len(self.warnings)}):[/bold yellow]"
786
+ )
546
787
  for warning in self.warnings:
547
788
  console.print(f" • {warning}")
548
789
 
549
- if report['recommendations']:
550
- console.print(f"\nšŸ’” [bold blue]Recommendations:[/bold blue]")
551
- for rec in report['recommendations']:
790
+ if report["recommendations"]:
791
+ console.print("\nšŸ’” [bold blue]Recommendations:[/bold blue]")
792
+ for rec in report["recommendations"]:
552
793
  console.print(f" {rec}")
553
794
 
554
- console.print(f"\nšŸ“Š [bold]Summary:[/bold] {len(self.successes)} successes, {len(self.warnings)} warnings, {len(self.issues)} critical issues")
795
+ console.print(
796
+ f"\nšŸ“Š [bold]Summary:[/bold] {len(self.successes)} successes, {len(self.warnings)} warnings, {len(self.issues)} critical issues"
797
+ )
555
798
 
556
799
 
557
800
  async def run_diagnostics(
558
- output_file: Optional[str] = None,
801
+ output_file: str | None = None,
559
802
  json_output: bool = False,
560
803
  ) -> None:
561
804
  """Run comprehensive system diagnostics."""
@@ -563,7 +806,7 @@ async def run_diagnostics(
563
806
  report = await diagnostics.run_full_diagnosis()
564
807
 
565
808
  if output_file:
566
- with open(output_file, 'w') as f:
809
+ with open(output_file, "w") as f:
567
810
  json.dump(report, f, indent=2)
568
811
  console.print(f"\nšŸ“„ Full report saved to: {output_file}")
569
812