claude-mpm 4.0.28__py3-none-any.whl → 4.0.30__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.
Files changed (89) hide show
  1. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +48 -3
  2. claude_mpm/agents/BASE_PM.md +20 -15
  3. claude_mpm/agents/INSTRUCTIONS.md +12 -2
  4. claude_mpm/agents/templates/agent-manager.json +24 -0
  5. claude_mpm/agents/templates/agent-manager.md +304 -0
  6. claude_mpm/agents/templates/documentation.json +16 -3
  7. claude_mpm/agents/templates/engineer.json +19 -5
  8. claude_mpm/agents/templates/ops.json +19 -5
  9. claude_mpm/agents/templates/qa.json +16 -3
  10. claude_mpm/agents/templates/refactoring_engineer.json +25 -7
  11. claude_mpm/agents/templates/research.json +19 -5
  12. claude_mpm/cli/__init__.py +4 -0
  13. claude_mpm/cli/commands/__init__.py +4 -0
  14. claude_mpm/cli/commands/agent_manager.py +521 -0
  15. claude_mpm/cli/commands/agents.py +2 -1
  16. claude_mpm/cli/commands/cleanup.py +1 -1
  17. claude_mpm/cli/commands/doctor.py +209 -0
  18. claude_mpm/cli/commands/mcp.py +3 -3
  19. claude_mpm/cli/commands/mcp_install_commands.py +12 -30
  20. claude_mpm/cli/commands/mcp_server_commands.py +9 -9
  21. claude_mpm/cli/commands/memory.py +1 -1
  22. claude_mpm/cli/commands/run.py +31 -2
  23. claude_mpm/cli/commands/run_config_checker.py +1 -1
  24. claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
  25. claude_mpm/cli/parsers/base_parser.py +12 -1
  26. claude_mpm/cli/parsers/mcp_parser.py +1 -1
  27. claude_mpm/cli/parsers/run_parser.py +1 -1
  28. claude_mpm/cli/shared/__init__.py +1 -1
  29. claude_mpm/cli/startup_logging.py +463 -0
  30. claude_mpm/constants.py +2 -0
  31. claude_mpm/core/claude_runner.py +81 -2
  32. claude_mpm/core/constants.py +2 -2
  33. claude_mpm/core/framework_loader.py +45 -11
  34. claude_mpm/core/interactive_session.py +82 -3
  35. claude_mpm/core/output_style_manager.py +6 -6
  36. claude_mpm/core/socketio_pool.py +2 -2
  37. claude_mpm/core/unified_paths.py +128 -0
  38. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  41. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  42. claude_mpm/dashboard/static/css/dashboard.css +170 -0
  43. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  44. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  45. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
  47. claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
  48. claude_mpm/dashboard/static/js/dashboard.js +116 -0
  49. claude_mpm/dashboard/static/js/socket-client.js +0 -1
  50. claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
  51. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
  52. claude_mpm/scripts/mcp_server.py +2 -2
  53. claude_mpm/services/agents/agent_builder.py +455 -0
  54. claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
  55. claude_mpm/services/agents/deployment/agent_validator.py +1 -0
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
  57. claude_mpm/services/diagnostics/__init__.py +18 -0
  58. claude_mpm/services/diagnostics/checks/__init__.py +30 -0
  59. claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
  60. claude_mpm/services/diagnostics/checks/base_check.py +64 -0
  61. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
  62. claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
  63. claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
  64. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  65. claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
  66. claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
  67. claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
  68. claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
  69. claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
  70. claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
  71. claude_mpm/services/diagnostics/models.py +120 -0
  72. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
  73. claude_mpm/services/mcp_gateway/main.py +1 -1
  74. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
  75. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  76. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
  77. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
  78. claude_mpm/services/memory/__init__.py +2 -0
  79. claude_mpm/services/socketio/handlers/connection.py +27 -33
  80. claude_mpm/services/socketio/handlers/registry.py +39 -7
  81. claude_mpm/services/socketio/server/core.py +72 -22
  82. claude_mpm/validation/frontmatter_validator.py +1 -1
  83. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
  84. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
  85. /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
  86. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
  87. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
  88. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
  89. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,322 @@
1
+ """
2
+ Check startup log health and analyze for common issues.
3
+
4
+ WHY: Startup logs contain valuable diagnostic information about agent deployment,
5
+ MCP configuration, memory loading, and other startup issues that may not be
6
+ immediately visible to users.
7
+
8
+ DESIGN DECISIONS:
9
+ - Parse the most recent startup log file
10
+ - Identify common error patterns and provide fixes
11
+ - Count errors, warnings, and info messages
12
+ - Provide actionable recommendations based on log content
13
+ """
14
+
15
+ import re
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import Dict, Any, List, Optional, Tuple
19
+
20
+ from ..models import DiagnosticResult, DiagnosticStatus
21
+ from .base_check import BaseDiagnosticCheck
22
+
23
+
24
+ class StartupLogCheck(BaseDiagnosticCheck):
25
+ """Analyze startup logs for errors and issues."""
26
+
27
+ # Common error patterns and their fixes
28
+ ERROR_PATTERNS = {
29
+ r"Agent deployment.*failed": (
30
+ "Agent deployment failure",
31
+ "Check agent configuration in .claude/agents/ and run 'claude-mpm deploy'"
32
+ ),
33
+ r"MCP.*not found": (
34
+ "MCP server not found",
35
+ "Install MCP server: npm install -g @modelcontextprotocol/server"
36
+ ),
37
+ r"Port \d+ .*in use": (
38
+ "Port binding conflict",
39
+ "Kill the process using the port or use --websocket-port to specify a different port"
40
+ ),
41
+ r"Memory loading.*error": (
42
+ "Memory loading failure",
43
+ "Check .claude-mpm/memory/ permissions and run 'claude-mpm memory validate'"
44
+ ),
45
+ r"Permission denied": (
46
+ "Permission error",
47
+ "Check file permissions in project directory and .claude-mpm/"
48
+ ),
49
+ r"ModuleNotFoundError|ImportError": (
50
+ "Missing Python dependency",
51
+ "Install missing dependencies: pip install -e . or pip install claude-mpm[agents]"
52
+ ),
53
+ r"Socket\.IO.*failed|socketio.*error": (
54
+ "Socket.IO initialization failure",
55
+ "Install monitor dependencies: pip install claude-mpm[monitor]"
56
+ ),
57
+ r"Configuration.*invalid|yaml.*error": (
58
+ "Configuration file error",
59
+ "Validate configuration: claude-mpm config validate"
60
+ ),
61
+ r"claude\.json.*large|memory.*issue": (
62
+ "Large .claude.json file",
63
+ "Run 'claude-mpm cleanup-memory' to archive old conversations"
64
+ ),
65
+ r"Failed to start.*daemon": (
66
+ "Daemon startup failure",
67
+ "Check system resources and try: claude-mpm monitor stop && claude-mpm monitor start"
68
+ ),
69
+ }
70
+
71
+ # Warning patterns that should be noted
72
+ WARNING_PATTERNS = {
73
+ r"agent.*source.*tracking": "Agent source tracking warning",
74
+ r"dependency.*missing": "Missing optional dependency",
75
+ r"deprecated": "Deprecation warning",
76
+ r"Socket\.IO.*not available": "Monitor mode unavailable",
77
+ r"Response logging.*disabled": "Response logging is disabled",
78
+ }
79
+
80
+ @property
81
+ def name(self) -> str:
82
+ return "startup_log_check"
83
+
84
+ @property
85
+ def category(self) -> str:
86
+ return "Startup Log"
87
+
88
+ def run(self) -> DiagnosticResult:
89
+ """Run startup log diagnostics."""
90
+ try:
91
+ # Find the latest startup log
92
+ log_file = self._find_latest_log()
93
+
94
+ if not log_file:
95
+ return DiagnosticResult(
96
+ category=self.category,
97
+ status=DiagnosticStatus.WARNING,
98
+ message="No startup logs found",
99
+ details={
100
+ "recommendation": "Startup logging will be created on next run"
101
+ }
102
+ )
103
+
104
+ # Parse the log file
105
+ analysis = self._analyze_log_file(log_file)
106
+
107
+ # Determine status based on findings
108
+ status = self._determine_status(analysis)
109
+ message = self._create_message(analysis)
110
+
111
+ # Build details
112
+ details = {
113
+ "log_file": str(log_file),
114
+ "last_startup": analysis["timestamp"],
115
+ "errors": analysis["error_count"],
116
+ "warnings": analysis["warning_count"],
117
+ "info_messages": analysis["info_count"],
118
+ }
119
+
120
+ # Add specific issues found
121
+ if analysis["errors_found"]:
122
+ details["errors_found"] = analysis["errors_found"]
123
+
124
+ if analysis["warnings_found"]:
125
+ details["warnings_found"] = analysis["warnings_found"]
126
+
127
+ # Add recommendations
128
+ if analysis["recommendations"]:
129
+ details["recommendations"] = analysis["recommendations"]
130
+
131
+ # Create sub-results if verbose
132
+ sub_results = []
133
+ if self.verbose and analysis["errors_found"]:
134
+ for error_type, fix in analysis["errors_found"]:
135
+ sub_results.append(
136
+ DiagnosticResult(
137
+ category="Error",
138
+ status=DiagnosticStatus.ERROR,
139
+ message=error_type,
140
+ details={"fix": fix}
141
+ )
142
+ )
143
+
144
+ return DiagnosticResult(
145
+ category=self.category,
146
+ status=status,
147
+ message=message,
148
+ details=details,
149
+ sub_results=sub_results if self.verbose else [],
150
+ fix_command=self._get_fix_command(analysis)
151
+ )
152
+
153
+ except Exception as e:
154
+ return DiagnosticResult(
155
+ category=self.category,
156
+ status=DiagnosticStatus.ERROR,
157
+ message=f"Startup log check failed: {str(e)}",
158
+ details={"error": str(e)}
159
+ )
160
+
161
+ def _find_latest_log(self) -> Optional[Path]:
162
+ """Find the most recent startup log file."""
163
+ log_dir = Path.cwd() / ".claude-mpm" / "logs" / "startup"
164
+
165
+ if not log_dir.exists():
166
+ return None
167
+
168
+ log_files = sorted(
169
+ log_dir.glob("startup-*.log"),
170
+ key=lambda p: p.stat().st_mtime,
171
+ reverse=True
172
+ )
173
+
174
+ return log_files[0] if log_files else None
175
+
176
+ def _analyze_log_file(self, log_file: Path) -> Dict[str, Any]:
177
+ """Analyze the contents of a startup log file."""
178
+ analysis = {
179
+ "timestamp": None,
180
+ "error_count": 0,
181
+ "warning_count": 0,
182
+ "info_count": 0,
183
+ "debug_count": 0,
184
+ "errors_found": [],
185
+ "warnings_found": [],
186
+ "recommendations": [],
187
+ "startup_successful": False,
188
+ }
189
+
190
+ try:
191
+ with open(log_file, 'r', encoding='utf-8') as f:
192
+ lines = f.readlines()
193
+
194
+ # Extract timestamp from filename or first line
195
+ match = re.search(r'(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})', log_file.name)
196
+ if match:
197
+ timestamp_str = match.group(1)
198
+ # Convert to readable format
199
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H-%M-%S")
200
+ analysis["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
201
+
202
+ # Process each line
203
+ for line in lines:
204
+ # Count log levels
205
+ if " - ERROR - " in line:
206
+ analysis["error_count"] += 1
207
+ self._check_error_patterns(line, analysis)
208
+ elif " - WARNING - " in line:
209
+ analysis["warning_count"] += 1
210
+ self._check_warning_patterns(line, analysis)
211
+ elif " - INFO - " in line:
212
+ analysis["info_count"] += 1
213
+ elif " - DEBUG - " in line:
214
+ analysis["debug_count"] += 1
215
+
216
+ # Check for successful startup indicators
217
+ if "Claude session completed successfully" in line:
218
+ analysis["startup_successful"] = True
219
+ elif "Starting Claude MPM session" in line:
220
+ analysis["startup_successful"] = True # At least startup began
221
+
222
+ # Generate recommendations based on findings
223
+ self._generate_recommendations(analysis)
224
+
225
+ except Exception as e:
226
+ analysis["error_count"] += 1
227
+ analysis["errors_found"].append(("Failed to parse log", str(e)))
228
+
229
+ return analysis
230
+
231
+ def _check_error_patterns(self, line: str, analysis: Dict[str, Any]) -> None:
232
+ """Check line for known error patterns."""
233
+ for pattern, (error_type, fix) in self.ERROR_PATTERNS.items():
234
+ if re.search(pattern, line, re.IGNORECASE):
235
+ # Avoid duplicates
236
+ if (error_type, fix) not in analysis["errors_found"]:
237
+ analysis["errors_found"].append((error_type, fix))
238
+ break
239
+
240
+ def _check_warning_patterns(self, line: str, analysis: Dict[str, Any]) -> None:
241
+ """Check line for known warning patterns."""
242
+ for pattern, warning_type in self.WARNING_PATTERNS.items():
243
+ if re.search(pattern, line, re.IGNORECASE):
244
+ # Avoid duplicates
245
+ if warning_type not in analysis["warnings_found"]:
246
+ analysis["warnings_found"].append(warning_type)
247
+ break
248
+
249
+ def _generate_recommendations(self, analysis: Dict[str, Any]) -> None:
250
+ """Generate recommendations based on analysis."""
251
+ recommendations = []
252
+
253
+ # High error count
254
+ if analysis["error_count"] > 5:
255
+ recommendations.append(
256
+ "High error count detected. Review full log for systematic issues."
257
+ )
258
+
259
+ # No successful startup
260
+ if not analysis["startup_successful"] and analysis["error_count"] > 0:
261
+ recommendations.append(
262
+ "Startup may have failed. Check error messages above."
263
+ )
264
+
265
+ # Specific error recommendations
266
+ if any("MCP" in error[0] for error in analysis["errors_found"]):
267
+ recommendations.append(
268
+ "MCP issues detected. Run 'claude-mpm doctor' for full diagnostics."
269
+ )
270
+
271
+ if any("Agent" in error[0] for error in analysis["errors_found"]):
272
+ recommendations.append(
273
+ "Agent issues detected. Run 'claude-mpm agents validate' to check agents."
274
+ )
275
+
276
+ if any("memory" in error[0].lower() for error in analysis["errors_found"]):
277
+ recommendations.append(
278
+ "Memory issues detected. Run 'claude-mpm cleanup-memory' to free space."
279
+ )
280
+
281
+ analysis["recommendations"] = recommendations
282
+
283
+ def _determine_status(self, analysis: Dict[str, Any]) -> DiagnosticStatus:
284
+ """Determine overall status based on analysis."""
285
+ if analysis["error_count"] > 0:
286
+ return DiagnosticStatus.ERROR
287
+ elif analysis["warning_count"] > 3:
288
+ return DiagnosticStatus.WARNING
289
+ elif analysis["warning_count"] > 0:
290
+ return DiagnosticStatus.WARNING
291
+ else:
292
+ return DiagnosticStatus.OK
293
+
294
+ def _create_message(self, analysis: Dict[str, Any]) -> str:
295
+ """Create summary message based on analysis."""
296
+ if analysis["error_count"] > 0:
297
+ return f"Startup has {analysis['error_count']} error(s)"
298
+ elif analysis["warning_count"] > 0:
299
+ return f"Startup successful with {analysis['warning_count']} warning(s)"
300
+ elif analysis["startup_successful"]:
301
+ return "Last startup was successful"
302
+ else:
303
+ return "Startup log is clean"
304
+
305
+ def _get_fix_command(self, analysis: Dict[str, Any]) -> Optional[str]:
306
+ """Get the most relevant fix command based on errors found."""
307
+ if not analysis["errors_found"]:
308
+ return None
309
+
310
+ # Priority order for fix commands
311
+ for error_type, fix in analysis["errors_found"]:
312
+ if "claude-mpm" in fix:
313
+ # Extract claude-mpm command from fix suggestion
314
+ match = re.search(r'(claude-mpm [^\'"\n]+)', fix)
315
+ if match:
316
+ return match.group(1)
317
+ elif "pip install" in fix:
318
+ match = re.search(r'(pip install [^\'"\n]+)', fix)
319
+ if match:
320
+ return match.group(1)
321
+
322
+ return None
@@ -0,0 +1,247 @@
1
+ """
2
+ Diagnostic runner service for orchestrating health checks.
3
+
4
+ WHY: Coordinate execution of all diagnostic checks, handle errors gracefully,
5
+ and aggregate results for reporting.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ from concurrent.futures import ThreadPoolExecutor, as_completed
11
+ from typing import List, Optional, Type
12
+
13
+ from .checks import (
14
+ AgentCheck,
15
+ BaseDiagnosticCheck,
16
+ ClaudeDesktopCheck,
17
+ CommonIssuesCheck,
18
+ ConfigurationCheck,
19
+ FilesystemCheck,
20
+ InstallationCheck,
21
+ MCPCheck,
22
+ MonitorCheck,
23
+ StartupLogCheck,
24
+ )
25
+ from .models import DiagnosticResult, DiagnosticStatus, DiagnosticSummary
26
+
27
+
28
+ class DiagnosticRunner:
29
+ """Orchestrate diagnostic checks and aggregate results.
30
+
31
+ WHY: Provides a single entry point for running all diagnostics with
32
+ proper error handling, parallel execution, and result aggregation.
33
+ """
34
+
35
+ def __init__(self, verbose: bool = False, fix: bool = False):
36
+ """Initialize diagnostic runner.
37
+
38
+ Args:
39
+ verbose: Include detailed information in results
40
+ fix: Attempt to fix issues automatically (future feature)
41
+ """
42
+ self.verbose = verbose
43
+ self.fix = fix
44
+ self.logger = logging.getLogger(__name__)
45
+
46
+ # Define check order (dependencies first)
47
+ self.check_classes: List[Type[BaseDiagnosticCheck]] = [
48
+ InstallationCheck,
49
+ ConfigurationCheck,
50
+ FilesystemCheck,
51
+ ClaudeDesktopCheck,
52
+ AgentCheck,
53
+ MCPCheck,
54
+ MonitorCheck,
55
+ StartupLogCheck, # Check startup logs for recent issues
56
+ CommonIssuesCheck,
57
+ ]
58
+
59
+ def run_diagnostics(self) -> DiagnosticSummary:
60
+ """Run all diagnostic checks synchronously.
61
+
62
+ Returns:
63
+ DiagnosticSummary with all results
64
+ """
65
+ summary = DiagnosticSummary()
66
+
67
+ # Run checks in order
68
+ for check_class in self.check_classes:
69
+ try:
70
+ check = check_class(verbose=self.verbose)
71
+
72
+ # Skip if check shouldn't run
73
+ if not check.should_run():
74
+ self.logger.debug(f"Skipping {check.name}")
75
+ continue
76
+
77
+ self.logger.debug(f"Running {check.name}")
78
+ result = check.run()
79
+ summary.add_result(result)
80
+
81
+ # If fix mode is enabled and there's a fix available
82
+ if self.fix and result.has_issues and result.fix_command:
83
+ self._attempt_fix(result)
84
+
85
+ except Exception as e:
86
+ self.logger.error(f"Check {check_class.__name__} failed: {e}")
87
+ error_result = DiagnosticResult(
88
+ category=check_class.__name__.replace("Check", ""),
89
+ status=DiagnosticStatus.ERROR,
90
+ message=f"Check failed: {str(e)}",
91
+ details={"error": str(e)}
92
+ )
93
+ summary.add_result(error_result)
94
+
95
+ return summary
96
+
97
+ def run_diagnostics_parallel(self) -> DiagnosticSummary:
98
+ """Run diagnostic checks in parallel for faster execution.
99
+
100
+ WHY: Some checks may involve I/O or network operations, running them
101
+ in parallel can significantly speed up the overall diagnostic process.
102
+
103
+ Returns:
104
+ DiagnosticSummary with all results
105
+ """
106
+ summary = DiagnosticSummary()
107
+
108
+ # Group checks by dependency level
109
+ # Level 1: No dependencies
110
+ level1 = [InstallationCheck, FilesystemCheck, ConfigurationCheck]
111
+ # Level 2: May depend on level 1
112
+ level2 = [ClaudeDesktopCheck, AgentCheck, MCPCheck, MonitorCheck]
113
+ # Level 3: Depends on others
114
+ level3 = [CommonIssuesCheck]
115
+
116
+ for level in [level1, level2, level3]:
117
+ level_results = self._run_level_parallel(level)
118
+ for result in level_results:
119
+ summary.add_result(result)
120
+
121
+ return summary
122
+
123
+ def _run_level_parallel(self, check_classes: List[Type[BaseDiagnosticCheck]]) -> List[DiagnosticResult]:
124
+ """Run a group of checks in parallel.
125
+
126
+ Args:
127
+ check_classes: List of check classes to run
128
+
129
+ Returns:
130
+ List of DiagnosticResults
131
+ """
132
+ results = []
133
+
134
+ with ThreadPoolExecutor(max_workers=len(check_classes)) as executor:
135
+ future_to_check = {}
136
+
137
+ for check_class in check_classes:
138
+ try:
139
+ check = check_class(verbose=self.verbose)
140
+ if check.should_run():
141
+ future = executor.submit(check.run)
142
+ future_to_check[future] = check_class.__name__
143
+ except Exception as e:
144
+ self.logger.error(f"Failed to create check {check_class.__name__}: {e}")
145
+ results.append(DiagnosticResult(
146
+ category=check_class.__name__.replace("Check", ""),
147
+ status=DiagnosticStatus.ERROR,
148
+ message=f"Check initialization failed: {str(e)}",
149
+ details={"error": str(e)}
150
+ ))
151
+
152
+ for future in as_completed(future_to_check):
153
+ check_name = future_to_check[future]
154
+ try:
155
+ result = future.result(timeout=10)
156
+ results.append(result)
157
+ except Exception as e:
158
+ self.logger.error(f"Check {check_name} failed: {e}")
159
+ results.append(DiagnosticResult(
160
+ category=check_name.replace("Check", ""),
161
+ status=DiagnosticStatus.ERROR,
162
+ message=f"Check execution failed: {str(e)}",
163
+ details={"error": str(e)}
164
+ ))
165
+
166
+ return results
167
+
168
+ def run_specific_checks(self, check_names: List[str]) -> DiagnosticSummary:
169
+ """Run only specific diagnostic checks.
170
+
171
+ Args:
172
+ check_names: List of check names to run (e.g., ["installation", "agents"])
173
+
174
+ Returns:
175
+ DiagnosticSummary with results from specified checks
176
+ """
177
+ summary = DiagnosticSummary()
178
+
179
+ # Map check names to classes
180
+ check_map = {
181
+ "installation": InstallationCheck,
182
+ "configuration": ConfigurationCheck,
183
+ "config": ConfigurationCheck,
184
+ "filesystem": FilesystemCheck,
185
+ "fs": FilesystemCheck,
186
+ "claude": ClaudeDesktopCheck,
187
+ "claude_desktop": ClaudeDesktopCheck,
188
+ "agents": AgentCheck,
189
+ "agent": AgentCheck,
190
+ "mcp": MCPCheck,
191
+ "monitor": MonitorCheck,
192
+ "monitoring": MonitorCheck,
193
+ "common": CommonIssuesCheck,
194
+ "issues": CommonIssuesCheck,
195
+ }
196
+
197
+ for name in check_names:
198
+ check_class = check_map.get(name.lower())
199
+ if not check_class:
200
+ self.logger.warning(f"Unknown check: {name}")
201
+ continue
202
+
203
+ try:
204
+ check = check_class(verbose=self.verbose)
205
+ if check.should_run():
206
+ result = check.run()
207
+ summary.add_result(result)
208
+ except Exception as e:
209
+ self.logger.error(f"Check {name} failed: {e}")
210
+ error_result = DiagnosticResult(
211
+ category=check_class.__name__.replace("Check", ""),
212
+ status=DiagnosticStatus.ERROR,
213
+ message=f"Check failed: {str(e)}",
214
+ details={"error": str(e)}
215
+ )
216
+ summary.add_result(error_result)
217
+
218
+ return summary
219
+
220
+ def _attempt_fix(self, result: DiagnosticResult):
221
+ """Attempt to fix an issue automatically.
222
+
223
+ Args:
224
+ result: DiagnosticResult with fix_command
225
+ """
226
+ if not result.fix_command:
227
+ return
228
+
229
+ self.logger.info(f"Attempting to fix: {result.message}")
230
+ self.logger.info(f"Running: {result.fix_command}")
231
+
232
+ # In a real implementation, this would execute the fix command
233
+ # For now, we just log it
234
+ # TODO: Implement actual fix execution with proper safeguards
235
+
236
+ async def run_diagnostics_async(self) -> DiagnosticSummary:
237
+ """Run diagnostics asynchronously (future enhancement).
238
+
239
+ WHY: For integration with async frameworks and better performance
240
+ with I/O-bound checks.
241
+
242
+ Returns:
243
+ DiagnosticSummary with all results
244
+ """
245
+ # Convert sync execution to async for now
246
+ loop = asyncio.get_event_loop()
247
+ return await loop.run_in_executor(None, self.run_diagnostics)