mcp-ticketer 0.3.1__py3-none-any.whl → 0.3.3__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.
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +164 -36
- mcp_ticketer/adapters/github.py +11 -8
- mcp_ticketer/adapters/jira.py +29 -28
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +105 -104
- mcp_ticketer/adapters/linear/client.py +78 -59
- mcp_ticketer/adapters/linear/mappers.py +93 -73
- mcp_ticketer/adapters/linear/queries.py +28 -7
- mcp_ticketer/adapters/linear/types.py +67 -60
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/cli/adapter_diagnostics.py +87 -52
- mcp_ticketer/cli/codex_configure.py +6 -6
- mcp_ticketer/cli/diagnostics.py +180 -88
- mcp_ticketer/cli/linear_commands.py +156 -113
- mcp_ticketer/cli/main.py +153 -82
- mcp_ticketer/cli/simple_health.py +74 -51
- mcp_ticketer/cli/utils.py +15 -10
- mcp_ticketer/core/config.py +23 -19
- mcp_ticketer/core/env_discovery.py +5 -4
- mcp_ticketer/core/env_loader.py +114 -91
- mcp_ticketer/core/exceptions.py +22 -20
- mcp_ticketer/core/models.py +9 -0
- mcp_ticketer/core/project_config.py +1 -1
- mcp_ticketer/mcp/constants.py +58 -0
- mcp_ticketer/mcp/dto.py +195 -0
- mcp_ticketer/mcp/response_builder.py +206 -0
- mcp_ticketer/mcp/server.py +361 -1182
- mcp_ticketer/queue/health_monitor.py +166 -135
- mcp_ticketer/queue/manager.py +70 -19
- mcp_ticketer/queue/queue.py +24 -5
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +203 -145
- mcp_ticketer/queue/worker.py +79 -43
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/METADATA +1 -1
- mcp_ticketer-0.3.3.dist-info/RECORD +62 -0
- mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.3.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/diagnostics.py
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
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,
|
|
8
|
+
from typing import Any, Optional
|
|
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
|
-
|
|
13
|
+
|
|
17
14
|
|
|
18
15
|
def get_config():
|
|
19
16
|
"""Get configuration using the real configuration system."""
|
|
20
17
|
from ..core.config import ConfigurationManager
|
|
18
|
+
|
|
21
19
|
config_manager = ConfigurationManager()
|
|
22
20
|
return config_manager.load_config()
|
|
23
21
|
|
|
22
|
+
|
|
24
23
|
def safe_import_registry():
|
|
25
24
|
"""Safely import adapter registry with fallback."""
|
|
26
25
|
try:
|
|
27
26
|
from ..core.registry import AdapterRegistry
|
|
27
|
+
|
|
28
28
|
return AdapterRegistry
|
|
29
29
|
except ImportError:
|
|
30
|
+
|
|
30
31
|
class MockRegistry:
|
|
31
32
|
@staticmethod
|
|
32
33
|
def get_adapter(adapter_type):
|
|
@@ -34,6 +35,7 @@ def safe_import_registry():
|
|
|
34
35
|
|
|
35
36
|
return MockRegistry
|
|
36
37
|
|
|
38
|
+
|
|
37
39
|
def safe_import_queue_manager():
|
|
38
40
|
"""Safely import worker manager with fallback."""
|
|
39
41
|
try:
|
|
@@ -63,10 +65,15 @@ def safe_import_queue_manager():
|
|
|
63
65
|
return {"total": 0, "failed": 0, "pending": 0, "completed": 0}
|
|
64
66
|
|
|
65
67
|
def health_check(self):
|
|
66
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
"status": "degraded",
|
|
70
|
+
"score": 50,
|
|
71
|
+
"details": "Running in fallback mode",
|
|
72
|
+
}
|
|
67
73
|
|
|
68
74
|
return MockWorkerManager
|
|
69
75
|
|
|
76
|
+
|
|
70
77
|
# Initialize with safe imports
|
|
71
78
|
AdapterRegistry = safe_import_registry()
|
|
72
79
|
WorkerManager = safe_import_queue_manager()
|
|
@@ -101,7 +108,7 @@ class SystemDiagnostics:
|
|
|
101
108
|
self.queue_available = False
|
|
102
109
|
console.print(f"⚠️ Could not initialize worker manager: {e}")
|
|
103
110
|
|
|
104
|
-
async def run_full_diagnosis(self) ->
|
|
111
|
+
async def run_full_diagnosis(self) -> dict[str, Any]:
|
|
105
112
|
"""Run complete system diagnosis and return detailed report."""
|
|
106
113
|
console.print("\n🔍 [bold blue]MCP Ticketer System Diagnosis[/bold blue]")
|
|
107
114
|
console.print("=" * 60)
|
|
@@ -125,20 +132,25 @@ class SystemDiagnostics:
|
|
|
125
132
|
"""Get current version information."""
|
|
126
133
|
try:
|
|
127
134
|
from ..__version__ import __version__
|
|
135
|
+
|
|
128
136
|
return __version__
|
|
129
137
|
except ImportError:
|
|
130
138
|
return "unknown"
|
|
131
139
|
|
|
132
|
-
def _get_system_info(self) ->
|
|
140
|
+
def _get_system_info(self) -> dict[str, Any]:
|
|
133
141
|
"""Gather system information."""
|
|
134
142
|
return {
|
|
135
143
|
"python_version": sys.version,
|
|
136
144
|
"platform": sys.platform,
|
|
137
145
|
"working_directory": str(Path.cwd()),
|
|
138
|
-
"config_path":
|
|
146
|
+
"config_path": (
|
|
147
|
+
str(self.config.config_file)
|
|
148
|
+
if hasattr(self.config, "config_file")
|
|
149
|
+
else "unknown"
|
|
150
|
+
),
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
async def _diagnose_configuration(self) ->
|
|
153
|
+
async def _diagnose_configuration(self) -> dict[str, Any]:
|
|
142
154
|
"""Diagnose configuration issues."""
|
|
143
155
|
console.print("\n📋 [yellow]Configuration Analysis[/yellow]")
|
|
144
156
|
|
|
@@ -166,6 +178,7 @@ class SystemDiagnostics:
|
|
|
166
178
|
|
|
167
179
|
# Try to detect adapters from environment variables
|
|
168
180
|
import os
|
|
181
|
+
|
|
169
182
|
env_adapters = []
|
|
170
183
|
if os.getenv("LINEAR_API_KEY"):
|
|
171
184
|
env_adapters.append("linear")
|
|
@@ -178,15 +191,20 @@ class SystemDiagnostics:
|
|
|
178
191
|
config_status["default_adapter"] = "aitrackdown"
|
|
179
192
|
|
|
180
193
|
if env_adapters:
|
|
181
|
-
console.print(
|
|
194
|
+
console.print(
|
|
195
|
+
f"ℹ️ Detected {len(env_adapters)} adapter(s) from environment: {', '.join(env_adapters)}"
|
|
196
|
+
)
|
|
182
197
|
else:
|
|
183
|
-
console.print(
|
|
198
|
+
console.print(
|
|
199
|
+
"ℹ️ No adapter environment variables detected, using aitrackdown"
|
|
200
|
+
)
|
|
184
201
|
|
|
185
202
|
return config_status
|
|
186
203
|
|
|
187
204
|
try:
|
|
188
205
|
# Check adapter configurations using the same approach as working commands
|
|
189
206
|
from .utils import CommonPatterns
|
|
207
|
+
|
|
190
208
|
raw_config = CommonPatterns.load_config()
|
|
191
209
|
adapters_config = raw_config.get("adapters", {})
|
|
192
210
|
config_status["adapters_configured"] = len(adapters_config)
|
|
@@ -202,17 +220,19 @@ class SystemDiagnostics:
|
|
|
202
220
|
console.print(f"✅ {len(adapters_config)} adapter(s) configured")
|
|
203
221
|
|
|
204
222
|
# Check each adapter configuration
|
|
205
|
-
for name,
|
|
223
|
+
for name, _adapter_config in adapters_config.items():
|
|
206
224
|
try:
|
|
207
225
|
# Use the same adapter creation approach as working commands
|
|
208
226
|
adapter = CommonPatterns.get_adapter(override_adapter=name)
|
|
209
|
-
|
|
227
|
+
|
|
210
228
|
# Test adapter validation if available
|
|
211
|
-
if hasattr(adapter,
|
|
229
|
+
if hasattr(adapter, "validate_credentials"):
|
|
212
230
|
is_valid, error = adapter.validate_credentials()
|
|
213
231
|
if is_valid:
|
|
214
232
|
console.print(f"✅ {name}: credentials valid")
|
|
215
|
-
self.successes.append(
|
|
233
|
+
self.successes.append(
|
|
234
|
+
f"{name} adapter configured correctly"
|
|
235
|
+
)
|
|
216
236
|
else:
|
|
217
237
|
issue = f"{name}: credential validation failed - {error}"
|
|
218
238
|
config_status["issues"].append(issue)
|
|
@@ -236,10 +256,10 @@ class SystemDiagnostics:
|
|
|
236
256
|
|
|
237
257
|
return config_status
|
|
238
258
|
|
|
239
|
-
async def _diagnose_adapters(self) ->
|
|
259
|
+
async def _diagnose_adapters(self) -> dict[str, Any]:
|
|
240
260
|
"""Diagnose adapter functionality."""
|
|
241
261
|
console.print("\n🔌 [yellow]Adapter Diagnosis[/yellow]")
|
|
242
|
-
|
|
262
|
+
|
|
243
263
|
adapter_status = {
|
|
244
264
|
"total_adapters": 0,
|
|
245
265
|
"healthy_adapters": 0,
|
|
@@ -250,13 +270,13 @@ class SystemDiagnostics:
|
|
|
250
270
|
try:
|
|
251
271
|
# Use the same configuration loading approach as working commands
|
|
252
272
|
from .utils import CommonPatterns
|
|
273
|
+
|
|
253
274
|
raw_config = CommonPatterns.load_config()
|
|
254
275
|
adapters_config = raw_config.get("adapters", {})
|
|
255
276
|
adapter_status["total_adapters"] = len(adapters_config)
|
|
256
277
|
|
|
257
278
|
for name, adapter_config in adapters_config.items():
|
|
258
279
|
adapter_type = adapter_config.get("type", name)
|
|
259
|
-
config_dict = adapter_config
|
|
260
280
|
|
|
261
281
|
details = {
|
|
262
282
|
"type": adapter_type,
|
|
@@ -268,11 +288,12 @@ class SystemDiagnostics:
|
|
|
268
288
|
try:
|
|
269
289
|
# Use the same adapter creation approach as working commands
|
|
270
290
|
from .utils import CommonPatterns
|
|
291
|
+
|
|
271
292
|
adapter = CommonPatterns.get_adapter(override_adapter=adapter_type)
|
|
272
|
-
|
|
293
|
+
|
|
273
294
|
# Test basic adapter functionality
|
|
274
295
|
test_start = datetime.now()
|
|
275
|
-
|
|
296
|
+
|
|
276
297
|
# Try to list tickets (non-destructive test)
|
|
277
298
|
try:
|
|
278
299
|
await adapter.list(limit=1)
|
|
@@ -305,7 +326,7 @@ class SystemDiagnostics:
|
|
|
305
326
|
|
|
306
327
|
return adapter_status
|
|
307
328
|
|
|
308
|
-
async def _diagnose_queue_system(self) ->
|
|
329
|
+
async def _diagnose_queue_system(self) -> dict[str, Any]:
|
|
309
330
|
"""Diagnose queue system health with active testing."""
|
|
310
331
|
console.print("\n⚡ [yellow]Queue System Diagnosis[/yellow]")
|
|
311
332
|
|
|
@@ -317,7 +338,11 @@ class SystemDiagnostics:
|
|
|
317
338
|
"failure_rate": 0.0,
|
|
318
339
|
"health_score": 0,
|
|
319
340
|
"worker_start_test": {"attempted": False, "success": False, "error": None},
|
|
320
|
-
"queue_operation_test": {
|
|
341
|
+
"queue_operation_test": {
|
|
342
|
+
"attempted": False,
|
|
343
|
+
"success": False,
|
|
344
|
+
"error": None,
|
|
345
|
+
},
|
|
321
346
|
}
|
|
322
347
|
|
|
323
348
|
try:
|
|
@@ -339,7 +364,9 @@ class SystemDiagnostics:
|
|
|
339
364
|
queue_status["worker_pid"] = worker_status.get("pid")
|
|
340
365
|
|
|
341
366
|
if queue_status["worker_running"]:
|
|
342
|
-
console.print(
|
|
367
|
+
console.print(
|
|
368
|
+
f"✅ Queue worker running (PID: {queue_status['worker_pid']})"
|
|
369
|
+
)
|
|
343
370
|
self.successes.append("Queue worker is running")
|
|
344
371
|
else:
|
|
345
372
|
console.print("⚠️ Queue worker not running - attempting to start...")
|
|
@@ -353,8 +380,12 @@ class SystemDiagnostics:
|
|
|
353
380
|
queue_status["worker_running"] = True
|
|
354
381
|
self.successes.append("Queue worker started successfully")
|
|
355
382
|
else:
|
|
356
|
-
console.print(
|
|
357
|
-
|
|
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
|
+
)
|
|
358
389
|
|
|
359
390
|
# Test 3: Get queue statistics
|
|
360
391
|
console.print("🔍 Analyzing queue statistics...")
|
|
@@ -377,7 +408,9 @@ class SystemDiagnostics:
|
|
|
377
408
|
self.warnings.append(warning)
|
|
378
409
|
console.print(f"⚠️ {warning}")
|
|
379
410
|
else:
|
|
380
|
-
console.print(
|
|
411
|
+
console.print(
|
|
412
|
+
f"✅ Queue failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})"
|
|
413
|
+
)
|
|
381
414
|
|
|
382
415
|
# Test 4: Test actual queue operations
|
|
383
416
|
console.print("🔍 Testing queue operations...")
|
|
@@ -388,21 +421,30 @@ class SystemDiagnostics:
|
|
|
388
421
|
console.print("✅ Queue operations test passed")
|
|
389
422
|
self.successes.append("Queue operations working correctly")
|
|
390
423
|
else:
|
|
391
|
-
console.print(
|
|
392
|
-
|
|
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
|
+
)
|
|
393
430
|
|
|
394
431
|
# Calculate health score based on actual tests
|
|
395
432
|
health_score = 100
|
|
396
433
|
if not queue_status["worker_running"]:
|
|
397
434
|
health_score -= 30
|
|
398
|
-
if
|
|
435
|
+
if (
|
|
436
|
+
not queue_status["worker_start_test"]["success"]
|
|
437
|
+
and queue_status["worker_start_test"]["attempted"]
|
|
438
|
+
):
|
|
399
439
|
health_score -= 20
|
|
400
440
|
if not queue_status["queue_operation_test"]["success"]:
|
|
401
441
|
health_score -= 30
|
|
402
442
|
health_score -= min(queue_status["failure_rate"], 20)
|
|
403
443
|
queue_status["health_score"] = max(0, health_score)
|
|
404
444
|
|
|
405
|
-
console.print(
|
|
445
|
+
console.print(
|
|
446
|
+
f"📊 Queue health score: {queue_status['health_score']}/100 (based on active testing)"
|
|
447
|
+
)
|
|
406
448
|
|
|
407
449
|
except Exception as e:
|
|
408
450
|
issue = f"Queue system diagnosis failed: {str(e)}"
|
|
@@ -411,29 +453,34 @@ class SystemDiagnostics:
|
|
|
411
453
|
|
|
412
454
|
return queue_status
|
|
413
455
|
|
|
414
|
-
async def _test_worker_startup(self) ->
|
|
456
|
+
async def _test_worker_startup(self) -> dict[str, Any]:
|
|
415
457
|
"""Test starting a queue worker."""
|
|
416
458
|
test_result = {
|
|
417
459
|
"attempted": True,
|
|
418
460
|
"success": False,
|
|
419
461
|
"error": None,
|
|
420
|
-
"details": None
|
|
462
|
+
"details": None,
|
|
421
463
|
}
|
|
422
464
|
|
|
423
465
|
try:
|
|
424
466
|
# Try to start worker using the worker manager
|
|
425
|
-
if hasattr(self.worker_manager,
|
|
467
|
+
if hasattr(self.worker_manager, "start"):
|
|
426
468
|
result = self.worker_manager.start()
|
|
427
469
|
test_result["success"] = result
|
|
428
|
-
test_result["details"] =
|
|
470
|
+
test_result["details"] = (
|
|
471
|
+
"Worker started successfully"
|
|
472
|
+
if result
|
|
473
|
+
else "Worker failed to start"
|
|
474
|
+
)
|
|
429
475
|
else:
|
|
430
476
|
# Try alternative method - use CLI command
|
|
431
477
|
import subprocess
|
|
478
|
+
|
|
432
479
|
result = subprocess.run(
|
|
433
480
|
["mcp-ticketer", "queue", "worker", "start"],
|
|
434
481
|
capture_output=True,
|
|
435
482
|
text=True,
|
|
436
|
-
timeout=10
|
|
483
|
+
timeout=10,
|
|
437
484
|
)
|
|
438
485
|
if result.returncode == 0:
|
|
439
486
|
test_result["success"] = True
|
|
@@ -448,24 +495,24 @@ class SystemDiagnostics:
|
|
|
448
495
|
|
|
449
496
|
return test_result
|
|
450
497
|
|
|
451
|
-
async def _test_queue_operations(self) ->
|
|
498
|
+
async def _test_queue_operations(self) -> dict[str, Any]:
|
|
452
499
|
"""Test basic queue operations."""
|
|
453
500
|
test_result = {
|
|
454
501
|
"attempted": True,
|
|
455
502
|
"success": False,
|
|
456
503
|
"error": None,
|
|
457
|
-
"details": None
|
|
504
|
+
"details": None,
|
|
458
505
|
}
|
|
459
506
|
|
|
460
507
|
try:
|
|
461
508
|
# Test creating a simple queue item (diagnostic test)
|
|
462
|
-
from ..core.models import
|
|
509
|
+
from ..core.models import Priority, Task
|
|
463
510
|
from ..queue.queue import Queue
|
|
464
511
|
|
|
465
512
|
test_task = Task(
|
|
466
513
|
title="[DIAGNOSTIC TEST] Queue functionality test",
|
|
467
514
|
description="This is a diagnostic test - safe to ignore",
|
|
468
|
-
priority=Priority.LOW
|
|
515
|
+
priority=Priority.LOW,
|
|
469
516
|
)
|
|
470
517
|
|
|
471
518
|
# Try to queue the test task using the correct Queue.add() method
|
|
@@ -473,7 +520,7 @@ class SystemDiagnostics:
|
|
|
473
520
|
queue_id = queue.add(
|
|
474
521
|
ticket_data=test_task.model_dump(),
|
|
475
522
|
adapter="aitrackdown",
|
|
476
|
-
operation="create"
|
|
523
|
+
operation="create",
|
|
477
524
|
)
|
|
478
525
|
test_result["success"] = True
|
|
479
526
|
test_result["details"] = f"Test task queued successfully: {queue_id}"
|
|
@@ -483,31 +530,31 @@ class SystemDiagnostics:
|
|
|
483
530
|
|
|
484
531
|
return test_result
|
|
485
532
|
|
|
486
|
-
async def _test_basic_queue_functionality(self) ->
|
|
533
|
+
async def _test_basic_queue_functionality(self) -> dict[str, Any]:
|
|
487
534
|
"""Test basic queue functionality in fallback mode."""
|
|
488
535
|
test_result = {
|
|
489
536
|
"attempted": True,
|
|
490
537
|
"success": False,
|
|
491
538
|
"error": None,
|
|
492
|
-
"details": None
|
|
539
|
+
"details": None,
|
|
493
540
|
}
|
|
494
541
|
|
|
495
542
|
try:
|
|
496
543
|
# Test if we can at least create a task directly (bypass queue)
|
|
497
|
-
from ..core.models import Task, Priority
|
|
498
544
|
from ..adapters.aitrackdown import AITrackdownAdapter
|
|
545
|
+
from ..core.models import Priority, Task
|
|
499
546
|
|
|
500
547
|
test_task = Task(
|
|
501
548
|
title="[DIAGNOSTIC TEST] Direct adapter test",
|
|
502
549
|
description="Testing direct adapter functionality",
|
|
503
|
-
priority=Priority.LOW
|
|
550
|
+
priority=Priority.LOW,
|
|
504
551
|
)
|
|
505
552
|
|
|
506
553
|
# Try direct adapter creation
|
|
507
554
|
adapter_config = {
|
|
508
555
|
"type": "aitrackdown",
|
|
509
556
|
"enabled": True,
|
|
510
|
-
"base_path": "/tmp/mcp-ticketer-diagnostic-test"
|
|
557
|
+
"base_path": "/tmp/mcp-ticketer-diagnostic-test",
|
|
511
558
|
}
|
|
512
559
|
|
|
513
560
|
adapter = AITrackdownAdapter(adapter_config)
|
|
@@ -524,10 +571,10 @@ class SystemDiagnostics:
|
|
|
524
571
|
|
|
525
572
|
return test_result
|
|
526
573
|
|
|
527
|
-
async def _analyze_recent_logs(self) ->
|
|
574
|
+
async def _analyze_recent_logs(self) -> dict[str, Any]:
|
|
528
575
|
"""Analyze recent log entries for issues."""
|
|
529
576
|
console.print("\n📝 [yellow]Recent Log Analysis[/yellow]")
|
|
530
|
-
|
|
577
|
+
|
|
531
578
|
log_analysis = {
|
|
532
579
|
"log_files_found": [],
|
|
533
580
|
"recent_errors": [],
|
|
@@ -551,7 +598,9 @@ class SystemDiagnostics:
|
|
|
551
598
|
if not log_analysis["log_files_found"]:
|
|
552
599
|
console.print("ℹ️ No log files found in standard locations")
|
|
553
600
|
else:
|
|
554
|
-
console.print(
|
|
601
|
+
console.print(
|
|
602
|
+
f"✅ Found logs in {len(log_analysis['log_files_found'])} location(s)"
|
|
603
|
+
)
|
|
555
604
|
|
|
556
605
|
except Exception as e:
|
|
557
606
|
issue = f"Log analysis failed: {str(e)}"
|
|
@@ -560,21 +609,26 @@ class SystemDiagnostics:
|
|
|
560
609
|
|
|
561
610
|
return log_analysis
|
|
562
611
|
|
|
563
|
-
async def _analyze_log_directory(
|
|
612
|
+
async def _analyze_log_directory(
|
|
613
|
+
self, log_path: Path, log_analysis: dict[str, Any]
|
|
614
|
+
):
|
|
564
615
|
"""Analyze logs in a specific directory."""
|
|
565
616
|
try:
|
|
566
617
|
for log_file in log_path.glob("*.log"):
|
|
567
|
-
if
|
|
618
|
+
if (
|
|
619
|
+
log_file.stat().st_mtime
|
|
620
|
+
> (datetime.now() - timedelta(hours=24)).timestamp()
|
|
621
|
+
):
|
|
568
622
|
await self._parse_log_file(log_file, log_analysis)
|
|
569
623
|
except Exception as e:
|
|
570
624
|
self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
|
|
571
625
|
|
|
572
|
-
async def _parse_log_file(self, log_file: Path, log_analysis:
|
|
626
|
+
async def _parse_log_file(self, log_file: Path, log_analysis: dict[str, Any]):
|
|
573
627
|
"""Parse individual log file for issues."""
|
|
574
628
|
try:
|
|
575
|
-
with open(log_file
|
|
629
|
+
with open(log_file) as f:
|
|
576
630
|
lines = f.readlines()[-100:] # Last 100 lines
|
|
577
|
-
|
|
631
|
+
|
|
578
632
|
for line in lines:
|
|
579
633
|
if "ERROR" in line:
|
|
580
634
|
log_analysis["recent_errors"].append(line.strip())
|
|
@@ -584,10 +638,10 @@ class SystemDiagnostics:
|
|
|
584
638
|
except Exception as e:
|
|
585
639
|
self.warnings.append(f"Could not parse {log_file}: {str(e)}")
|
|
586
640
|
|
|
587
|
-
async def _analyze_performance(self) ->
|
|
641
|
+
async def _analyze_performance(self) -> dict[str, Any]:
|
|
588
642
|
"""Analyze system performance metrics."""
|
|
589
643
|
console.print("\n⚡ [yellow]Performance Analysis[/yellow]")
|
|
590
|
-
|
|
644
|
+
|
|
591
645
|
performance = {
|
|
592
646
|
"response_times": {},
|
|
593
647
|
"throughput": {},
|
|
@@ -596,17 +650,17 @@ class SystemDiagnostics:
|
|
|
596
650
|
|
|
597
651
|
try:
|
|
598
652
|
# Test basic operations performance
|
|
599
|
-
|
|
600
|
-
|
|
653
|
+
datetime.now()
|
|
654
|
+
|
|
601
655
|
# Test configuration loading
|
|
602
656
|
config_start = datetime.now()
|
|
603
657
|
_ = get_config()
|
|
604
658
|
config_time = (datetime.now() - config_start).total_seconds()
|
|
605
659
|
performance["response_times"]["config_load"] = config_time
|
|
606
|
-
|
|
660
|
+
|
|
607
661
|
if config_time > 1.0:
|
|
608
662
|
self.warnings.append(f"Slow configuration loading: {config_time:.2f}s")
|
|
609
|
-
|
|
663
|
+
|
|
610
664
|
console.print(f"📊 Configuration load time: {config_time:.3f}s")
|
|
611
665
|
|
|
612
666
|
except Exception as e:
|
|
@@ -616,32 +670,42 @@ class SystemDiagnostics:
|
|
|
616
670
|
|
|
617
671
|
return performance
|
|
618
672
|
|
|
619
|
-
def _generate_recommendations(self) ->
|
|
673
|
+
def _generate_recommendations(self) -> list[str]:
|
|
620
674
|
"""Generate actionable recommendations based on diagnosis."""
|
|
621
675
|
recommendations = []
|
|
622
676
|
|
|
623
677
|
if self.issues:
|
|
624
|
-
recommendations.append(
|
|
625
|
-
|
|
678
|
+
recommendations.append(
|
|
679
|
+
"🚨 Critical issues detected - immediate attention required"
|
|
680
|
+
)
|
|
681
|
+
|
|
626
682
|
if any("Queue worker not running" in issue for issue in self.issues):
|
|
627
|
-
recommendations.append(
|
|
628
|
-
|
|
683
|
+
recommendations.append(
|
|
684
|
+
"• Restart queue worker: mcp-ticketer queue worker restart"
|
|
685
|
+
)
|
|
686
|
+
|
|
629
687
|
if any("failure rate" in issue.lower() for issue in self.issues):
|
|
630
688
|
recommendations.append("• Check queue system logs for error patterns")
|
|
631
|
-
recommendations.append(
|
|
632
|
-
|
|
689
|
+
recommendations.append(
|
|
690
|
+
"• Consider clearing failed queue items: mcp-ticketer queue clear --failed"
|
|
691
|
+
)
|
|
692
|
+
|
|
633
693
|
if any("No adapters configured" in issue for issue in self.issues):
|
|
634
|
-
recommendations.append(
|
|
694
|
+
recommendations.append(
|
|
695
|
+
"• Configure at least one adapter: mcp-ticketer init-aitrackdown"
|
|
696
|
+
)
|
|
635
697
|
|
|
636
698
|
if self.warnings:
|
|
637
699
|
recommendations.append("⚠️ Warnings detected - monitoring recommended")
|
|
638
700
|
|
|
639
701
|
if not self.issues and not self.warnings:
|
|
640
|
-
recommendations.append(
|
|
702
|
+
recommendations.append(
|
|
703
|
+
"✅ System appears healthy - no immediate action required"
|
|
704
|
+
)
|
|
641
705
|
|
|
642
706
|
return recommendations
|
|
643
707
|
|
|
644
|
-
def _display_diagnosis_summary(self, report:
|
|
708
|
+
def _display_diagnosis_summary(self, report: dict[str, Any]):
|
|
645
709
|
"""Display a comprehensive diagnosis summary."""
|
|
646
710
|
console.print("\n" + "=" * 60)
|
|
647
711
|
console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")
|
|
@@ -661,7 +725,9 @@ class SystemDiagnostics:
|
|
|
661
725
|
status_text = "HEALTHY"
|
|
662
726
|
status_icon = "✅"
|
|
663
727
|
|
|
664
|
-
console.print(
|
|
728
|
+
console.print(
|
|
729
|
+
f"\n{status_icon} [bold {status_color}]System Status: {status_text}[/bold {status_color}]"
|
|
730
|
+
)
|
|
665
731
|
|
|
666
732
|
# Statistics
|
|
667
733
|
stats_table = Table(show_header=True, header_style="bold blue")
|
|
@@ -670,36 +736,62 @@ class SystemDiagnostics:
|
|
|
670
736
|
stats_table.add_column("Details")
|
|
671
737
|
|
|
672
738
|
# Add component statuses
|
|
673
|
-
config_status =
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
stats_table.add_row(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
739
|
+
config_status = (
|
|
740
|
+
"✅ OK"
|
|
741
|
+
if not any("configuration" in issue.lower() for issue in self.issues)
|
|
742
|
+
else "❌ FAILED"
|
|
743
|
+
)
|
|
744
|
+
stats_table.add_row(
|
|
745
|
+
"Configuration",
|
|
746
|
+
config_status,
|
|
747
|
+
f"{report['configuration']['adapters_configured']} adapters",
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
queue_health = report["queue_system"]["health_score"]
|
|
751
|
+
queue_status = (
|
|
752
|
+
"✅ OK"
|
|
753
|
+
if queue_health > 80
|
|
754
|
+
else "⚠️ DEGRADED" if queue_health > 50 else "❌ FAILED"
|
|
755
|
+
)
|
|
756
|
+
stats_table.add_row(
|
|
757
|
+
"Queue System", queue_status, f"{queue_health}/100 health score"
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
adapter_stats = report["adapters"]
|
|
761
|
+
adapter_status = (
|
|
762
|
+
"✅ OK" if adapter_stats["failed_adapters"] == 0 else "❌ FAILED"
|
|
763
|
+
)
|
|
764
|
+
stats_table.add_row(
|
|
765
|
+
"Adapters",
|
|
766
|
+
adapter_status,
|
|
767
|
+
f"{adapter_stats['healthy_adapters']}/{adapter_stats['total_adapters']} healthy",
|
|
768
|
+
)
|
|
683
769
|
|
|
684
770
|
console.print(stats_table)
|
|
685
771
|
|
|
686
772
|
# Issues and recommendations
|
|
687
773
|
if self.issues:
|
|
688
|
-
console.print(
|
|
774
|
+
console.print(
|
|
775
|
+
f"\n🚨 [bold red]Critical Issues ({len(self.issues)}):[/bold red]"
|
|
776
|
+
)
|
|
689
777
|
for issue in self.issues:
|
|
690
778
|
console.print(f" • {issue}")
|
|
691
779
|
|
|
692
780
|
if self.warnings:
|
|
693
|
-
console.print(
|
|
781
|
+
console.print(
|
|
782
|
+
f"\n⚠️ [bold yellow]Warnings ({len(self.warnings)}):[/bold yellow]"
|
|
783
|
+
)
|
|
694
784
|
for warning in self.warnings:
|
|
695
785
|
console.print(f" • {warning}")
|
|
696
786
|
|
|
697
|
-
if report[
|
|
698
|
-
console.print(
|
|
699
|
-
for rec in report[
|
|
787
|
+
if report["recommendations"]:
|
|
788
|
+
console.print("\n💡 [bold blue]Recommendations:[/bold blue]")
|
|
789
|
+
for rec in report["recommendations"]:
|
|
700
790
|
console.print(f" {rec}")
|
|
701
791
|
|
|
702
|
-
console.print(
|
|
792
|
+
console.print(
|
|
793
|
+
f"\n📊 [bold]Summary:[/bold] {len(self.successes)} successes, {len(self.warnings)} warnings, {len(self.issues)} critical issues"
|
|
794
|
+
)
|
|
703
795
|
|
|
704
796
|
|
|
705
797
|
async def run_diagnostics(
|
|
@@ -711,7 +803,7 @@ async def run_diagnostics(
|
|
|
711
803
|
report = await diagnostics.run_full_diagnosis()
|
|
712
804
|
|
|
713
805
|
if output_file:
|
|
714
|
-
with open(output_file,
|
|
806
|
+
with open(output_file, "w") as f:
|
|
715
807
|
json.dump(report, f, indent=2)
|
|
716
808
|
console.print(f"\n📄 Full report saved to: {output_file}")
|
|
717
809
|
|