mcp-ticketer 0.4.11__py3-none-any.whl → 0.12.0__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 (70) hide show
  1. mcp_ticketer/__version__.py +3 -3
  2. mcp_ticketer/adapters/__init__.py +2 -0
  3. mcp_ticketer/adapters/aitrackdown.py +9 -3
  4. mcp_ticketer/adapters/asana/__init__.py +15 -0
  5. mcp_ticketer/adapters/asana/adapter.py +1308 -0
  6. mcp_ticketer/adapters/asana/client.py +292 -0
  7. mcp_ticketer/adapters/asana/mappers.py +334 -0
  8. mcp_ticketer/adapters/asana/types.py +146 -0
  9. mcp_ticketer/adapters/github.py +313 -96
  10. mcp_ticketer/adapters/jira.py +251 -1
  11. mcp_ticketer/adapters/linear/adapter.py +524 -22
  12. mcp_ticketer/adapters/linear/client.py +61 -9
  13. mcp_ticketer/adapters/linear/mappers.py +9 -3
  14. mcp_ticketer/cache/memory.py +3 -3
  15. mcp_ticketer/cli/adapter_diagnostics.py +1 -1
  16. mcp_ticketer/cli/auggie_configure.py +1 -1
  17. mcp_ticketer/cli/codex_configure.py +80 -1
  18. mcp_ticketer/cli/configure.py +33 -43
  19. mcp_ticketer/cli/diagnostics.py +18 -16
  20. mcp_ticketer/cli/discover.py +288 -21
  21. mcp_ticketer/cli/gemini_configure.py +1 -1
  22. mcp_ticketer/cli/instruction_commands.py +429 -0
  23. mcp_ticketer/cli/linear_commands.py +99 -15
  24. mcp_ticketer/cli/main.py +1199 -227
  25. mcp_ticketer/cli/mcp_configure.py +1 -1
  26. mcp_ticketer/cli/migrate_config.py +12 -8
  27. mcp_ticketer/cli/platform_commands.py +6 -6
  28. mcp_ticketer/cli/platform_detection.py +412 -0
  29. mcp_ticketer/cli/queue_commands.py +15 -15
  30. mcp_ticketer/cli/simple_health.py +1 -1
  31. mcp_ticketer/cli/ticket_commands.py +14 -13
  32. mcp_ticketer/cli/update_checker.py +313 -0
  33. mcp_ticketer/cli/utils.py +45 -41
  34. mcp_ticketer/core/__init__.py +12 -0
  35. mcp_ticketer/core/adapter.py +4 -4
  36. mcp_ticketer/core/config.py +17 -10
  37. mcp_ticketer/core/env_discovery.py +33 -3
  38. mcp_ticketer/core/env_loader.py +7 -6
  39. mcp_ticketer/core/exceptions.py +3 -3
  40. mcp_ticketer/core/http_client.py +10 -10
  41. mcp_ticketer/core/instructions.py +405 -0
  42. mcp_ticketer/core/mappers.py +1 -1
  43. mcp_ticketer/core/models.py +1 -1
  44. mcp_ticketer/core/onepassword_secrets.py +379 -0
  45. mcp_ticketer/core/project_config.py +17 -1
  46. mcp_ticketer/core/registry.py +1 -1
  47. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  48. mcp_ticketer/mcp/__init__.py +2 -2
  49. mcp_ticketer/mcp/server/__init__.py +2 -2
  50. mcp_ticketer/mcp/server/main.py +82 -69
  51. mcp_ticketer/mcp/server/tools/__init__.py +9 -0
  52. mcp_ticketer/mcp/server/tools/attachment_tools.py +63 -16
  53. mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
  54. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +154 -5
  55. mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
  56. mcp_ticketer/mcp/server/tools/ticket_tools.py +157 -4
  57. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
  58. mcp_ticketer/queue/health_monitor.py +1 -0
  59. mcp_ticketer/queue/manager.py +4 -4
  60. mcp_ticketer/queue/queue.py +3 -3
  61. mcp_ticketer/queue/run_worker.py +1 -1
  62. mcp_ticketer/queue/ticket_registry.py +2 -2
  63. mcp_ticketer/queue/worker.py +14 -12
  64. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +106 -52
  65. mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
  66. mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
  67. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
  68. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
  69. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
  70. {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
@@ -69,7 +69,7 @@ class LinearGraphQLClient:
69
69
  return client
70
70
 
71
71
  except Exception as e:
72
- raise AdapterError(f"Failed to create Linear client: {e}", "linear")
72
+ raise AdapterError(f"Failed to create Linear client: {e}", "linear") from e
73
73
 
74
74
  async def execute_query(
75
75
  self,
@@ -110,15 +110,19 @@ class LinearGraphQLClient:
110
110
  status_code = e.response.status
111
111
 
112
112
  if status_code == 401:
113
- raise AuthenticationError("Invalid Linear API key", "linear")
113
+ raise AuthenticationError(
114
+ "Invalid Linear API key", "linear"
115
+ ) from e
114
116
  elif status_code == 403:
115
- raise AuthenticationError("Insufficient permissions", "linear")
117
+ raise AuthenticationError(
118
+ "Insufficient permissions", "linear"
119
+ ) from e
116
120
  elif status_code == 429:
117
121
  # Rate limit exceeded
118
122
  retry_after = e.response.headers.get("Retry-After", "60")
119
123
  raise RateLimitError(
120
124
  "Linear API rate limit exceeded", "linear", retry_after
121
- )
125
+ ) from e
122
126
  elif status_code >= 500:
123
127
  # Server error - retry
124
128
  if attempt < retries:
@@ -126,13 +130,13 @@ class LinearGraphQLClient:
126
130
  continue
127
131
  raise AdapterError(
128
132
  f"Linear API server error: {status_code}", "linear"
129
- )
133
+ ) from e
130
134
 
131
135
  # Network or other transport error
132
136
  if attempt < retries:
133
137
  await asyncio.sleep(2**attempt)
134
138
  continue
135
- raise AdapterError(f"Linear API transport error: {e}", "linear")
139
+ raise AdapterError(f"Linear API transport error: {e}", "linear") from e
136
140
 
137
141
  except Exception as e:
138
142
  # GraphQL or other errors
@@ -145,15 +149,19 @@ class LinearGraphQLClient:
145
149
  ):
146
150
  raise AuthenticationError(
147
151
  f"Linear authentication failed: {error_msg}", "linear"
148
- )
152
+ ) from e
149
153
  elif "rate limit" in error_msg.lower():
150
- raise RateLimitError("Linear API rate limit exceeded", "linear")
154
+ raise RateLimitError(
155
+ "Linear API rate limit exceeded", "linear"
156
+ ) from e
151
157
 
152
158
  # Generic error
153
159
  if attempt < retries:
154
160
  await asyncio.sleep(2**attempt)
155
161
  continue
156
- raise AdapterError(f"Linear GraphQL error: {error_msg}", "linear")
162
+ raise AdapterError(
163
+ f"Linear GraphQL error: {error_msg}", "linear"
164
+ ) from e
157
165
 
158
166
  # Should never reach here
159
167
  raise AdapterError("Maximum retries exceeded", "linear")
@@ -266,6 +274,50 @@ class LinearGraphQLClient:
266
274
  except Exception:
267
275
  return None
268
276
 
277
+ async def get_users_by_name(self, name: str) -> list[dict[str, Any]]:
278
+ """Search users by display name or full name.
279
+
280
+ Args:
281
+ name: Display name or full name to search for
282
+
283
+ Returns:
284
+ List of matching users (may be empty)
285
+
286
+ """
287
+ import logging
288
+
289
+ try:
290
+ query = """
291
+ query SearchUsers($nameFilter: String!) {
292
+ users(
293
+ filter: {
294
+ or: [
295
+ { displayName: { containsIgnoreCase: $nameFilter } }
296
+ { name: { containsIgnoreCase: $nameFilter } }
297
+ ]
298
+ }
299
+ first: 10
300
+ ) {
301
+ nodes {
302
+ id
303
+ name
304
+ email
305
+ displayName
306
+ avatarUrl
307
+ active
308
+ }
309
+ }
310
+ }
311
+ """
312
+
313
+ result = await self.execute_query(query, {"nameFilter": name})
314
+ users = result.get("users", {}).get("nodes", [])
315
+ return [u for u in users if u.get("active", True)] # Filter active users
316
+
317
+ except Exception as e:
318
+ logging.getLogger(__name__).warning(f"Failed to search users by name: {e}")
319
+ return []
320
+
269
321
  async def close(self) -> None:
270
322
  """Close the client connection.
271
323
 
@@ -10,7 +10,10 @@ from .types import extract_linear_metadata, get_universal_priority, get_universa
10
10
 
11
11
 
12
12
  def map_linear_issue_to_task(issue_data: dict[str, Any]) -> Task:
13
- """Convert Linear issue data to universal Task model.
13
+ """Convert Linear issue or sub-issue data to universal Task model.
14
+
15
+ Handles both top-level issues (no parent) and sub-issues (child items
16
+ with a parent issue).
14
17
 
15
18
  Args:
16
19
  issue_data: Raw Linear issue data from GraphQL
@@ -203,7 +206,10 @@ def map_linear_comment_to_comment(
203
206
 
204
207
 
205
208
  def build_linear_issue_input(task: Task, team_id: str) -> dict[str, Any]:
206
- """Build Linear issue input from universal Task model.
209
+ """Build Linear issue or sub-issue input from universal Task model.
210
+
211
+ Creates input for a top-level issue when task.parent_issue is not set,
212
+ or for a sub-issue when task.parent_issue is provided.
207
213
 
208
214
  Args:
209
215
  task: Universal Task model
@@ -215,7 +221,7 @@ def build_linear_issue_input(task: Task, team_id: str) -> dict[str, Any]:
215
221
  """
216
222
  from .types import get_linear_priority
217
223
 
218
- issue_input = {
224
+ issue_input: dict[str, Any] = {
219
225
  "title": task.title,
220
226
  "teamId": team_id,
221
227
  }
@@ -115,7 +115,7 @@ class MemoryCache:
115
115
  return len(self._cache)
116
116
 
117
117
  @staticmethod
118
- def generate_key(*args, **kwargs) -> str:
118
+ def generate_key(*args: Any, **kwargs: Any) -> str:
119
119
  """Generate cache key from arguments.
120
120
 
121
121
  Args:
@@ -139,7 +139,7 @@ def cache_decorator(
139
139
  key_prefix: str = "",
140
140
  cache_instance: MemoryCache | None = None,
141
141
  ) -> Callable:
142
- """Decorator for caching async function results.
142
+ """Decorate async function to cache its results.
143
143
 
144
144
  Args:
145
145
  ttl: TTL for cached results
@@ -155,7 +155,7 @@ def cache_decorator(
155
155
 
156
156
  def decorator(func: Callable) -> Callable:
157
157
  @wraps(func)
158
- async def wrapper(*args, **kwargs):
158
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
159
159
  # Generate cache key
160
160
  base_key = MemoryCache.generate_key(*args, **kwargs)
161
161
  cache_key = f"{key_prefix}:{func.__name__}:{base_key}"
@@ -343,7 +343,7 @@ def _provide_recommendations(console: Console) -> None:
343
343
  console.print("• For local files: [cyan]mcp-ticketer init aitrackdown[/cyan]")
344
344
 
345
345
  console.print("\n[bold]Test Configuration:[/bold]")
346
- console.print("• Run diagnostics: [cyan]mcp-ticketer diagnose[/cyan]")
346
+ console.print("• Run diagnostics: [cyan]mcp-ticketer doctor[/cyan]")
347
347
  console.print(
348
348
  "• Test ticket creation: [cyan]mcp-ticketer create 'Test ticket'[/cyan]"
349
349
  )
@@ -243,7 +243,7 @@ def configure_auggie_mcp(force: bool = False) -> None:
243
243
  "Could not find mcp-ticketer Python executable. "
244
244
  "Please ensure mcp-ticketer is installed.\n"
245
245
  "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
246
- )
246
+ ) from e
247
247
 
248
248
  # Step 2: Load project configuration
249
249
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -153,6 +153,75 @@ def create_codex_server_config(
153
153
  return config
154
154
 
155
155
 
156
+ def _test_configuration(adapter: str, project_config: dict) -> bool:
157
+ """Test the configuration by validating adapter credentials.
158
+
159
+ Args:
160
+ adapter: Adapter type (linear, github, jira, aitrackdown)
161
+ project_config: Project configuration dict
162
+
163
+ Returns:
164
+ True if validation passed, False otherwise
165
+
166
+ """
167
+ try:
168
+ from ..core import AdapterRegistry
169
+
170
+ # Get adapter configuration
171
+ adapters_config = project_config.get("adapters", {})
172
+ adapter_config = adapters_config.get(adapter, {})
173
+
174
+ # Test adapter instantiation
175
+ console.print(f" Testing {adapter} adapter...")
176
+
177
+ try:
178
+ adapter_instance = AdapterRegistry.get_adapter(adapter, adapter_config)
179
+ console.print(" [green]✓[/green] Adapter instantiated successfully")
180
+
181
+ # Test credentials if validation method exists
182
+ if hasattr(adapter_instance, "validate_credentials"):
183
+ console.print(f" Validating {adapter} credentials...")
184
+ is_valid, error_msg = adapter_instance.validate_credentials()
185
+
186
+ if is_valid:
187
+ console.print(" [green]✓[/green] Credentials are valid")
188
+ return True
189
+ else:
190
+ console.print(
191
+ f" [red]✗[/red] Credential validation failed: {error_msg}"
192
+ )
193
+ return False
194
+ else:
195
+ # No validation method, assume valid
196
+ console.print(
197
+ f" [yellow]○[/yellow] No credential validation available for {adapter}"
198
+ )
199
+ return True
200
+
201
+ except Exception as e:
202
+ console.print(f" [red]✗[/red] Adapter instantiation failed: {e}")
203
+
204
+ # Provide helpful error messages based on adapter type
205
+ if adapter == "linear":
206
+ console.print(
207
+ "\n [yellow]Linear requires:[/yellow] LINEAR_API_KEY and LINEAR_TEAM_ID"
208
+ )
209
+ elif adapter == "github":
210
+ console.print(
211
+ "\n [yellow]GitHub requires:[/yellow] GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO"
212
+ )
213
+ elif adapter == "jira":
214
+ console.print(
215
+ "\n [yellow]JIRA requires:[/yellow] JIRA_SERVER, JIRA_EMAIL, JIRA_API_TOKEN"
216
+ )
217
+
218
+ return False
219
+
220
+ except Exception as e:
221
+ console.print(f" [red]✗[/red] Configuration test error: {e}")
222
+ return False
223
+
224
+
156
225
  def remove_codex_mcp(dry_run: bool = False) -> None:
157
226
  """Remove mcp-ticketer from Codex CLI configuration.
158
227
 
@@ -248,7 +317,7 @@ def configure_codex_mcp(force: bool = False) -> None:
248
317
  "Could not find mcp-ticketer Python executable. "
249
318
  "Please ensure mcp-ticketer is installed.\n"
250
319
  "Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
251
- )
320
+ ) from e
252
321
 
253
322
  # Step 2: Load project configuration
254
323
  console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
@@ -317,6 +386,16 @@ def configure_codex_mcp(force: bool = False) -> None:
317
386
  f" Environment variables: {list(server_config['env'].keys())}"
318
387
  )
319
388
 
389
+ # Step 9: Test configuration
390
+ console.print("\n[cyan]🧪 Testing configuration...[/cyan]")
391
+ test_success = _test_configuration(adapter, project_config)
392
+
393
+ if not test_success:
394
+ console.print(
395
+ "[yellow]⚠ Configuration saved but validation failed. "
396
+ "Please check your credentials and settings.[/yellow]"
397
+ )
398
+
320
399
  # Next steps
321
400
  console.print("\n[bold cyan]Next Steps:[/bold cyan]")
322
401
  console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
@@ -1,6 +1,7 @@
1
1
  """Interactive configuration wizard for MCP Ticketer."""
2
2
 
3
3
  import os
4
+ from typing import Any
4
5
 
5
6
  import typer
6
7
  from rich.console import Console
@@ -45,24 +46,25 @@ def configure_wizard() -> None:
45
46
 
46
47
  # Step 2: Choose where to save
47
48
  console.print("\n[bold]Step 2: Configuration Scope[/bold]")
48
- console.print("1. Global (all projects): ~/.mcp-ticketer/config.json")
49
- console.print("2. Project-specific: .mcp-ticketer/config.json in project root")
49
+ console.print(
50
+ "1. Project-specific (recommended): .mcp-ticketer/config.json in project root"
51
+ )
52
+ console.print("2. Legacy global (deprecated): saves to project config for security")
50
53
 
51
- scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="2")
54
+ scope = Prompt.ask("Save configuration as", choices=["1", "2"], default="1")
52
55
 
53
56
  resolver = ConfigResolver()
54
57
 
55
- if scope == "1":
56
- # Save global
57
- resolver.save_global_config(config)
58
+ # Always save to project config (global config removed for security)
59
+ resolver.save_project_config(config)
60
+ config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
61
+
62
+ if scope == "2":
58
63
  console.print(
59
- f"\n[green]✓[/green] Configuration saved globally to {resolver.GLOBAL_CONFIG_PATH}"
64
+ "[yellow]Note: Global config is deprecated for security. Saving to project config instead.[/yellow]"
60
65
  )
61
- else:
62
- # Save project-specific
63
- resolver.save_project_config(config)
64
- config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
65
- console.print(f"\n[green]✓[/green] Configuration saved to {config_path}")
66
+
67
+ console.print(f"\n[green]✓[/green] Configuration saved to {config_path}")
66
68
 
67
69
  # Show usage instructions
68
70
  console.print("\n[bold]Usage:[/bold]")
@@ -153,7 +155,7 @@ def _configure_linear() -> AdapterConfig:
153
155
  is_valid, error = ConfigValidator.validate_linear_config(config_dict)
154
156
  if not is_valid:
155
157
  console.print(f"[red]Configuration error: {error}[/red]")
156
- raise typer.Exit(1)
158
+ raise typer.Exit(1) from None
157
159
 
158
160
  return AdapterConfig.from_dict(config_dict)
159
161
 
@@ -197,7 +199,7 @@ def _configure_jira() -> AdapterConfig:
197
199
  is_valid, error = ConfigValidator.validate_jira_config(config_dict)
198
200
  if not is_valid:
199
201
  console.print(f"[red]Configuration error: {error}[/red]")
200
- raise typer.Exit(1)
202
+ raise typer.Exit(1) from None
201
203
 
202
204
  return AdapterConfig.from_dict(config_dict)
203
205
 
@@ -245,7 +247,7 @@ def _configure_github() -> AdapterConfig:
245
247
  is_valid, error = ConfigValidator.validate_github_config(config_dict)
246
248
  if not is_valid:
247
249
  console.print(f"[red]Configuration error: {error}[/red]")
248
- raise typer.Exit(1)
250
+ raise typer.Exit(1) from None
249
251
 
250
252
  return AdapterConfig.from_dict(config_dict)
251
253
 
@@ -295,7 +297,7 @@ def _configure_hybrid_mode() -> TicketerConfig:
295
297
 
296
298
  if len(selected_adapters) < 2:
297
299
  console.print("[red]Hybrid mode requires at least 2 adapters[/red]")
298
- raise typer.Exit(1)
300
+ raise typer.Exit(1) from None
299
301
 
300
302
  # Configure each adapter
301
303
  adapters = {}
@@ -365,31 +367,17 @@ def show_current_config() -> None:
365
367
  resolver = ConfigResolver()
366
368
 
367
369
  # Try to load configs
368
- global_config = resolver.load_global_config()
369
370
  project_config = resolver.load_project_config()
370
371
 
371
372
  console.print("[bold]Current Configuration:[/bold]\n")
372
373
 
373
- # Global config
374
- if resolver.GLOBAL_CONFIG_PATH.exists():
375
- console.print(f"[cyan]Global:[/cyan] {resolver.GLOBAL_CONFIG_PATH}")
376
- console.print(f" Default adapter: {global_config.default_adapter}")
377
-
378
- if global_config.adapters:
379
- table = Table(title="Global Adapters")
380
- table.add_column("Adapter", style="cyan")
381
- table.add_column("Configured", style="green")
382
-
383
- for name, config in global_config.adapters.items():
384
- configured = "✓" if config.enabled else "✗"
385
- table.add_row(name, configured)
386
-
387
- console.print(table)
388
- else:
389
- console.print("[yellow]No global configuration found[/yellow]")
374
+ # Note about global config deprecation
375
+ console.print(
376
+ "[dim]Note: Global config has been deprecated for security reasons.[/dim]"
377
+ )
378
+ console.print("[dim]All configuration is now project-specific only.[/dim]\n")
390
379
 
391
380
  # Project config
392
- console.print()
393
381
  project_config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
394
382
  if project_config_path.exists():
395
383
  console.print(f"[cyan]Project:[/cyan] {project_config_path}")
@@ -444,7 +432,7 @@ def set_adapter_config(
444
432
  project_id: str | None = None,
445
433
  team_id: str | None = None,
446
434
  global_scope: bool = False,
447
- **kwargs,
435
+ **kwargs: Any,
448
436
  ) -> None:
449
437
  """Set specific adapter configuration values.
450
438
 
@@ -497,11 +485,13 @@ def set_adapter_config(
497
485
 
498
486
  console.print(f"[green]✓[/green] Updated {target_adapter} configuration")
499
487
 
500
- # Save config
488
+ # Save config (always to project config for security)
489
+ resolver.save_project_config(config)
490
+ config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
491
+
501
492
  if global_scope:
502
- resolver.save_global_config(config)
503
- console.print(f"[dim]Saved to {resolver.GLOBAL_CONFIG_PATH}[/dim]")
504
- else:
505
- resolver.save_project_config(config)
506
- config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
507
- console.print(f"[dim]Saved to {config_path}[/dim]")
493
+ console.print(
494
+ "[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
495
+ )
496
+
497
+ console.print(f"[dim]Saved to {config_path}[/dim]")
@@ -12,7 +12,7 @@ from rich.console import Console
12
12
  from rich.table import Table
13
13
 
14
14
 
15
- def get_config():
15
+ def get_config() -> Any:
16
16
  """Get configuration using the real configuration system."""
17
17
  from ..core.config import ConfigurationManager
18
18
 
@@ -20,7 +20,7 @@ def get_config():
20
20
  return config_manager.load_config()
21
21
 
22
22
 
23
- def safe_import_registry():
23
+ def safe_import_registry() -> type:
24
24
  """Safely import adapter registry with fallback."""
25
25
  try:
26
26
  from ..core.registry import AdapterRegistry
@@ -30,13 +30,13 @@ def safe_import_registry():
30
30
 
31
31
  class MockRegistry:
32
32
  @staticmethod
33
- def get_adapter(adapter_type):
33
+ def get_adapter(adapter_type: str) -> None:
34
34
  raise ImportError(f"Adapter {adapter_type} not available")
35
35
 
36
36
  return MockRegistry
37
37
 
38
38
 
39
- def safe_import_queue_manager():
39
+ def safe_import_queue_manager() -> type:
40
40
  """Safely import worker manager with fallback."""
41
41
  try:
42
42
  from ..queue.manager import WorkerManager as RealWorkerManager
@@ -55,16 +55,16 @@ def safe_import_queue_manager():
55
55
  pass
56
56
 
57
57
  class MockWorkerManager:
58
- def get_status(self):
58
+ def get_status(self) -> dict[str, Any]:
59
59
  return {"running": False, "pid": None, "status": "fallback_mode"}
60
60
 
61
- def get_worker_status(self):
61
+ def get_worker_status(self) -> dict[str, Any]:
62
62
  return {"running": False, "pid": None, "status": "fallback_mode"}
63
63
 
64
- def get_queue_stats(self):
64
+ def get_queue_stats(self) -> dict[str, Any]:
65
65
  return {"total": 0, "failed": 0, "pending": 0, "completed": 0}
66
66
 
67
- def health_check(self):
67
+ def health_check(self) -> dict[str, Any]:
68
68
  return {
69
69
  "status": "degraded",
70
70
  "score": 50,
@@ -85,11 +85,11 @@ logger = logging.getLogger(__name__)
85
85
  class SystemDiagnostics:
86
86
  """Comprehensive system diagnostics and health reporting."""
87
87
 
88
- def __init__(self):
88
+ def __init__(self) -> None:
89
89
  # Initialize lists first
90
- self.issues = []
91
- self.warnings = []
92
- self.successes = []
90
+ self.issues: list[str] = []
91
+ self.warnings: list[str] = []
92
+ self.successes: list[str] = []
93
93
 
94
94
  try:
95
95
  self.config = get_config()
@@ -611,7 +611,7 @@ class SystemDiagnostics:
611
611
 
612
612
  async def _analyze_log_directory(
613
613
  self, log_path: Path, log_analysis: dict[str, Any]
614
- ):
614
+ ) -> None:
615
615
  """Analyze logs in a specific directory."""
616
616
  try:
617
617
  for log_file in log_path.glob("*.log"):
@@ -623,7 +623,9 @@ class SystemDiagnostics:
623
623
  except Exception as e:
624
624
  self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
625
625
 
626
- async def _parse_log_file(self, log_file: Path, log_analysis: dict[str, Any]):
626
+ async def _parse_log_file(
627
+ self, log_file: Path, log_analysis: dict[str, Any]
628
+ ) -> None:
627
629
  """Parse individual log file for issues."""
628
630
  try:
629
631
  with open(log_file) as f:
@@ -672,7 +674,7 @@ class SystemDiagnostics:
672
674
 
673
675
  def _generate_recommendations(self) -> list[str]:
674
676
  """Generate actionable recommendations based on diagnosis."""
675
- recommendations = []
677
+ recommendations: list[str] = []
676
678
 
677
679
  if self.issues:
678
680
  recommendations.append(
@@ -705,7 +707,7 @@ class SystemDiagnostics:
705
707
 
706
708
  return recommendations
707
709
 
708
- def _display_diagnosis_summary(self, report: dict[str, Any]):
710
+ def _display_diagnosis_summary(self, report: dict[str, Any]) -> None:
709
711
  """Display a comprehensive diagnosis summary."""
710
712
  console.print("\n" + "=" * 60)
711
713
  console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")