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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +796 -46
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1416 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +879 -129
- mcp_ticketer/adapters/hybrid.py +11 -11
- mcp_ticketer/adapters/jira.py +973 -73
- mcp_ticketer/adapters/linear/__init__.py +24 -0
- mcp_ticketer/adapters/linear/adapter.py +2732 -0
- mcp_ticketer/adapters/linear/client.py +344 -0
- mcp_ticketer/adapters/linear/mappers.py +420 -0
- mcp_ticketer/adapters/linear/queries.py +479 -0
- mcp_ticketer/adapters/linear/types.py +360 -0
- mcp_ticketer/adapters/linear.py +10 -2315
- mcp_ticketer/analysis/__init__.py +23 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +421 -0
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +888 -151
- mcp_ticketer/cli/diagnostics.py +400 -157
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +616 -0
- mcp_ticketer/cli/main.py +203 -1165
- mcp_ticketer/cli/mcp_configure.py +474 -90
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +418 -0
- mcp_ticketer/cli/platform_installer.py +513 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +90 -65
- mcp_ticketer/cli/ticket_commands.py +1013 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +114 -66
- mcp_ticketer/core/__init__.py +24 -1
- mcp_ticketer/core/adapter.py +250 -16
- mcp_ticketer/core/config.py +145 -37
- mcp_ticketer/core/env_discovery.py +101 -22
- mcp_ticketer/core/env_loader.py +349 -0
- mcp_ticketer/core/exceptions.py +160 -0
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/models.py +280 -28
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/project_config.py +183 -49
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +171 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +56 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +495 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1439 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +921 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +300 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +948 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +215 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +170 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1268 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +547 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +95 -25
- mcp_ticketer/queue/queue.py +40 -21
- mcp_ticketer/queue/run_worker.py +6 -1
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +109 -49
- mcp_ticketer-1.2.11.dist-info/METADATA +792 -0
- mcp_ticketer-1.2.11.dist-info/RECORD +110 -0
- mcp_ticketer/mcp/server.py +0 -1895
- mcp_ticketer-0.1.30.dist-info/METADATA +0 -413
- mcp_ticketer-0.1.30.dist-info/RECORD +0 -49
- {mcp_ticketer-0.1.30.dist-info ā mcp_ticketer-1.2.11.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.30.dist-info ā mcp_ticketer-1.2.11.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.30.dist-info ā mcp_ticketer-1.2.11.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.30.dist-info ā mcp_ticketer-1.2.11.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/diagnostics.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
def get_config() -> Any:
|
|
16
|
+
"""Get configuration using the real configuration system."""
|
|
17
|
+
from ..core.config import ConfigurationManager
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
52
|
-
|
|
38
|
+
|
|
39
|
+
def safe_import_queue_manager() -> type:
|
|
40
|
+
"""Safely import worker manager with fallback."""
|
|
53
41
|
try:
|
|
54
|
-
from ..queue.manager import
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
100
|
+
console.print(f"ā Could not load configuration: {e}")
|
|
101
|
+
raise e
|
|
96
102
|
|
|
97
103
|
try:
|
|
98
|
-
self.
|
|
99
|
-
|
|
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.
|
|
107
|
+
self.worker_manager = None
|
|
107
108
|
self.queue_available = False
|
|
108
|
-
console.print(f"ā ļø Could not initialize
|
|
109
|
+
console.print(f"ā ļø Could not initialize worker manager: {e}")
|
|
109
110
|
|
|
110
|
-
async def run_full_diagnosis(self) ->
|
|
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) ->
|
|
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":
|
|
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) ->
|
|
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(
|
|
194
|
+
console.print(
|
|
195
|
+
f"ā¹ļø Detected {len(env_adapters)} adapter(s) from environment: {', '.join(env_adapters)}"
|
|
196
|
+
)
|
|
188
197
|
else:
|
|
189
|
-
console.print(
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
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(
|
|
220
|
+
console.print(f"ā
{len(adapters_config)} adapter(s) configured")
|
|
207
221
|
|
|
208
222
|
# Check each adapter configuration
|
|
209
|
-
for name,
|
|
223
|
+
for name, _adapter_config in adapters_config.items():
|
|
210
224
|
try:
|
|
211
|
-
|
|
212
|
-
adapter =
|
|
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,
|
|
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(
|
|
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) ->
|
|
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
|
-
|
|
256
|
-
|
|
271
|
+
# Use the same configuration loading approach as working commands
|
|
272
|
+
from .utils import CommonPatterns
|
|
257
273
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
268
|
-
|
|
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) ->
|
|
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 -
|
|
350
|
+
warning = "Queue system in fallback mode - testing basic functionality"
|
|
321
351
|
self.warnings.append(warning)
|
|
322
352
|
console.print(f"ā ļø {warning}")
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
338
|
-
self.issues.append(issue)
|
|
339
|
-
console.print(f"ā {issue}")
|
|
372
|
+
console.print("ā ļø Queue worker not running - attempting to start...")
|
|
340
373
|
|
|
341
|
-
|
|
342
|
-
|
|
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(
|
|
362
|
-
|
|
363
|
-
|
|
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 -=
|
|
367
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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) ->
|
|
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
|
-
|
|
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) ->
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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 =
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
stats_table.add_row(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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(
|
|
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(
|
|
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[
|
|
550
|
-
console.print(
|
|
551
|
-
for rec in report[
|
|
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(
|
|
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:
|
|
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,
|
|
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
|
|