claude-mpm 4.0.29__py3-none-any.whl → 4.0.31__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 (65) 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/documentation.json +16 -3
  5. claude_mpm/agents/templates/engineer.json +19 -5
  6. claude_mpm/agents/templates/ops.json +19 -5
  7. claude_mpm/agents/templates/qa.json +16 -3
  8. claude_mpm/agents/templates/refactoring_engineer.json +25 -7
  9. claude_mpm/agents/templates/research.json +19 -5
  10. claude_mpm/cli/__init__.py +2 -0
  11. claude_mpm/cli/commands/__init__.py +2 -0
  12. claude_mpm/cli/commands/agent_manager.py +10 -6
  13. claude_mpm/cli/commands/agents.py +2 -1
  14. claude_mpm/cli/commands/cleanup.py +1 -1
  15. claude_mpm/cli/commands/doctor.py +209 -0
  16. claude_mpm/cli/commands/mcp.py +3 -3
  17. claude_mpm/cli/commands/mcp_install_commands.py +12 -30
  18. claude_mpm/cli/commands/mcp_server_commands.py +9 -9
  19. claude_mpm/cli/commands/run.py +31 -2
  20. claude_mpm/cli/commands/run_config_checker.py +1 -1
  21. claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
  22. claude_mpm/cli/parsers/base_parser.py +5 -1
  23. claude_mpm/cli/parsers/mcp_parser.py +1 -1
  24. claude_mpm/cli/parsers/run_parser.py +1 -1
  25. claude_mpm/cli/startup_logging.py +463 -0
  26. claude_mpm/constants.py +1 -0
  27. claude_mpm/core/claude_runner.py +78 -0
  28. claude_mpm/core/framework_loader.py +45 -11
  29. claude_mpm/core/interactive_session.py +82 -3
  30. claude_mpm/core/output_style_manager.py +6 -6
  31. claude_mpm/core/unified_paths.py +128 -0
  32. claude_mpm/scripts/mcp_server.py +2 -2
  33. claude_mpm/services/agents/deployment/agent_validator.py +1 -0
  34. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
  35. claude_mpm/services/agents/deployment/system_instructions_deployer.py +2 -2
  36. claude_mpm/services/diagnostics/__init__.py +18 -0
  37. claude_mpm/services/diagnostics/checks/__init__.py +30 -0
  38. claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
  39. claude_mpm/services/diagnostics/checks/base_check.py +64 -0
  40. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
  41. claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
  42. claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
  43. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  44. claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
  45. claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
  46. claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
  47. claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
  48. claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
  49. claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
  50. claude_mpm/services/diagnostics/models.py +120 -0
  51. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
  52. claude_mpm/services/mcp_gateway/main.py +1 -1
  53. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
  54. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  55. claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
  56. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
  57. claude_mpm/services/socketio/handlers/registry.py +39 -7
  58. claude_mpm/services/socketio/server/core.py +72 -22
  59. claude_mpm/validation/frontmatter_validator.py +1 -1
  60. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/METADATA +4 -1
  61. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/RECORD +65 -48
  62. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/WHEEL +0 -0
  63. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/entry_points.txt +0 -0
  64. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/licenses/LICENSE +0 -0
  65. {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,354 @@
1
+ """
2
+ Check for common known issues.
3
+
4
+ WHY: Proactively detect and report common problems that users encounter,
5
+ providing specific fixes for each issue.
6
+ """
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Dict, Any, List
11
+
12
+ from ..models import DiagnosticResult, DiagnosticStatus
13
+ from .base_check import BaseDiagnosticCheck
14
+
15
+
16
+ class CommonIssuesCheck(BaseDiagnosticCheck):
17
+ """Check for common known issues."""
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return "common_issues_check"
22
+
23
+ @property
24
+ def category(self) -> str:
25
+ return "Common Issues"
26
+
27
+ def run(self) -> DiagnosticResult:
28
+ """Run common issues diagnostics."""
29
+ try:
30
+ issues_found = []
31
+ warnings_found = []
32
+ details = {}
33
+
34
+ # Check for large .claude.json file
35
+ claude_json_result = self._check_claude_json_size()
36
+ if claude_json_result.has_issues:
37
+ if claude_json_result.status == DiagnosticStatus.ERROR:
38
+ issues_found.append(claude_json_result)
39
+ else:
40
+ warnings_found.append(claude_json_result)
41
+
42
+ # Check for memory leaks
43
+ memory_result = self._check_memory_issues()
44
+ if memory_result.has_issues:
45
+ warnings_found.append(memory_result)
46
+
47
+ # Check for stale lock files
48
+ lock_result = self._check_lock_files()
49
+ if lock_result.has_issues:
50
+ warnings_found.append(lock_result)
51
+
52
+ # Check for conflicting configurations
53
+ conflict_result = self._check_config_conflicts()
54
+ if conflict_result.has_issues:
55
+ warnings_found.append(conflict_result)
56
+
57
+ # Check for outdated cache
58
+ cache_result = self._check_cache_issues()
59
+ if cache_result.has_issues:
60
+ warnings_found.append(cache_result)
61
+
62
+ # Aggregate results
63
+ total_issues = len(issues_found) + len(warnings_found)
64
+
65
+ if issues_found:
66
+ status = DiagnosticStatus.ERROR
67
+ message = f"{len(issues_found)} critical issue(s), {len(warnings_found)} warning(s)"
68
+ elif warnings_found:
69
+ status = DiagnosticStatus.WARNING
70
+ message = f"{len(warnings_found)} known issue(s) detected"
71
+ else:
72
+ status = DiagnosticStatus.OK
73
+ message = "No known issues detected"
74
+
75
+ details = {
76
+ "total_issues": total_issues,
77
+ "critical": len(issues_found),
78
+ "warnings": len(warnings_found)
79
+ }
80
+
81
+ sub_results = issues_found + warnings_found
82
+
83
+ return DiagnosticResult(
84
+ category=self.category,
85
+ status=status,
86
+ message=message,
87
+ details=details,
88
+ sub_results=sub_results if self.verbose else []
89
+ )
90
+
91
+ except Exception as e:
92
+ return DiagnosticResult(
93
+ category=self.category,
94
+ status=DiagnosticStatus.ERROR,
95
+ message=f"Common issues check failed: {str(e)}",
96
+ details={"error": str(e)}
97
+ )
98
+
99
+ def _check_claude_json_size(self) -> DiagnosticResult:
100
+ """Check for large .claude.json file causing memory issues."""
101
+ claude_json_path = Path.cwd() / ".claude.json"
102
+
103
+ if not claude_json_path.exists():
104
+ return DiagnosticResult(
105
+ category="Large .claude.json",
106
+ status=DiagnosticStatus.OK,
107
+ message="No .claude.json file",
108
+ details={"exists": False}
109
+ )
110
+
111
+ try:
112
+ size_bytes = claude_json_path.stat().st_size
113
+ size_mb = size_bytes / (1024 * 1024)
114
+
115
+ # Try to count conversations
116
+ conversation_count = 0
117
+ try:
118
+ with open(claude_json_path, 'r') as f:
119
+ data = json.load(f)
120
+ if isinstance(data, dict) and "conversations" in data:
121
+ conversation_count = len(data["conversations"])
122
+ except Exception:
123
+ pass
124
+
125
+ details = {
126
+ "path": str(claude_json_path),
127
+ "size_mb": round(size_mb, 2),
128
+ "size_bytes": size_bytes,
129
+ "conversations": conversation_count
130
+ }
131
+
132
+ if size_mb > 10: # Critical: >10MB
133
+ return DiagnosticResult(
134
+ category="Large .claude.json",
135
+ status=DiagnosticStatus.ERROR,
136
+ message=f"Critical: .claude.json is {size_mb:.1f}MB (causes memory issues)",
137
+ details=details,
138
+ fix_command="claude-mpm cleanup-memory",
139
+ fix_description="Clean up conversation history to prevent memory issues"
140
+ )
141
+ elif size_mb > 1: # Warning: >1MB
142
+ return DiagnosticResult(
143
+ category="Large .claude.json",
144
+ status=DiagnosticStatus.WARNING,
145
+ message=f".claude.json is {size_mb:.1f}MB (may cause memory issues)",
146
+ details=details,
147
+ fix_command="claude-mpm cleanup-memory",
148
+ fix_description="Consider cleaning up old conversations"
149
+ )
150
+
151
+ return DiagnosticResult(
152
+ category="Large .claude.json",
153
+ status=DiagnosticStatus.OK,
154
+ message=f".claude.json size acceptable ({size_mb:.2f}MB)",
155
+ details=details
156
+ )
157
+
158
+ except Exception as e:
159
+ return DiagnosticResult(
160
+ category="Large .claude.json",
161
+ status=DiagnosticStatus.WARNING,
162
+ message=f"Could not check .claude.json: {str(e)}",
163
+ details={"error": str(e)}
164
+ )
165
+
166
+ def _check_memory_issues(self) -> DiagnosticResult:
167
+ """Check for potential memory leaks."""
168
+ memory_dir = Path.home() / ".claude" / "memory"
169
+
170
+ if not memory_dir.exists():
171
+ return DiagnosticResult(
172
+ category="Memory Usage",
173
+ status=DiagnosticStatus.OK,
174
+ message="No memory directory",
175
+ details={"exists": False}
176
+ )
177
+
178
+ try:
179
+ # Count and size memory files
180
+ memory_files = list(memory_dir.glob("**/*.json"))
181
+ total_size = sum(f.stat().st_size for f in memory_files)
182
+ size_mb = total_size / (1024 * 1024)
183
+
184
+ # Check for old memory files
185
+ import time
186
+ current_time = time.time()
187
+ old_files = []
188
+ for f in memory_files:
189
+ age_days = (current_time - f.stat().st_mtime) / (24 * 3600)
190
+ if age_days > 30:
191
+ old_files.append(f.name)
192
+
193
+ details = {
194
+ "file_count": len(memory_files),
195
+ "total_size_mb": round(size_mb, 2),
196
+ "old_files": len(old_files)
197
+ }
198
+
199
+ if size_mb > 100: # >100MB of memory files
200
+ return DiagnosticResult(
201
+ category="Memory Usage",
202
+ status=DiagnosticStatus.WARNING,
203
+ message=f"High memory usage: {size_mb:.1f}MB in {len(memory_files)} files",
204
+ details=details,
205
+ fix_command="claude-mpm memory clean --days 30",
206
+ fix_description="Clean up old memory files"
207
+ )
208
+ elif old_files:
209
+ return DiagnosticResult(
210
+ category="Memory Usage",
211
+ status=DiagnosticStatus.WARNING,
212
+ message=f"{len(old_files)} memory file(s) older than 30 days",
213
+ details=details,
214
+ fix_command="claude-mpm memory clean --days 30",
215
+ fix_description="Clean up old memory files"
216
+ )
217
+
218
+ return DiagnosticResult(
219
+ category="Memory Usage",
220
+ status=DiagnosticStatus.OK,
221
+ message=f"Memory usage normal ({size_mb:.1f}MB)",
222
+ details=details
223
+ )
224
+
225
+ except Exception as e:
226
+ return DiagnosticResult(
227
+ category="Memory Usage",
228
+ status=DiagnosticStatus.WARNING,
229
+ message=f"Could not check memory: {str(e)}",
230
+ details={"error": str(e)}
231
+ )
232
+
233
+ def _check_lock_files(self) -> DiagnosticResult:
234
+ """Check for stale lock files."""
235
+ lock_locations = [
236
+ Path.home() / ".claude" / "*.lock",
237
+ Path.cwd() / ".claude" / "*.lock",
238
+ Path("/tmp") / "claude-mpm-*.lock"
239
+ ]
240
+
241
+ stale_locks = []
242
+
243
+ for pattern in lock_locations:
244
+ try:
245
+ for lock_file in pattern.parent.glob(pattern.name):
246
+ # Check if lock is stale (older than 1 hour)
247
+ import time
248
+ age_hours = (time.time() - lock_file.stat().st_mtime) / 3600
249
+ if age_hours > 1:
250
+ stale_locks.append(str(lock_file))
251
+ except Exception:
252
+ pass
253
+
254
+ if stale_locks:
255
+ return DiagnosticResult(
256
+ category="Lock Files",
257
+ status=DiagnosticStatus.WARNING,
258
+ message=f"{len(stale_locks)} stale lock file(s) found",
259
+ details={"stale_locks": stale_locks},
260
+ fix_command=f"rm {' '.join(stale_locks)}",
261
+ fix_description="Remove stale lock files"
262
+ )
263
+
264
+ return DiagnosticResult(
265
+ category="Lock Files",
266
+ status=DiagnosticStatus.OK,
267
+ message="No stale lock files",
268
+ details={"stale_locks": []}
269
+ )
270
+
271
+ def _check_config_conflicts(self) -> DiagnosticResult:
272
+ """Check for conflicting configurations."""
273
+ conflicts = []
274
+
275
+ # Check for both user and project configs with same settings
276
+ user_config = Path.home() / ".claude" / "claude-mpm.yaml"
277
+ project_config = Path.cwd() / ".claude" / "claude-mpm.yaml"
278
+
279
+ if user_config.exists() and project_config.exists():
280
+ try:
281
+ import yaml
282
+
283
+ with open(user_config, 'r') as f:
284
+ user_data = yaml.safe_load(f) or {}
285
+
286
+ with open(project_config, 'r') as f:
287
+ project_data = yaml.safe_load(f) or {}
288
+
289
+ # Check for conflicting keys
290
+ for key in user_data:
291
+ if key in project_data and user_data[key] != project_data[key]:
292
+ conflicts.append(f"'{key}' defined in both user and project config")
293
+
294
+ except Exception:
295
+ pass
296
+
297
+ if conflicts:
298
+ return DiagnosticResult(
299
+ category="Config Conflicts",
300
+ status=DiagnosticStatus.WARNING,
301
+ message=f"{len(conflicts)} configuration conflict(s)",
302
+ details={"conflicts": conflicts},
303
+ fix_description="Resolve conflicting configurations"
304
+ )
305
+
306
+ return DiagnosticResult(
307
+ category="Config Conflicts",
308
+ status=DiagnosticStatus.OK,
309
+ message="No configuration conflicts",
310
+ details={"conflicts": []}
311
+ )
312
+
313
+ def _check_cache_issues(self) -> DiagnosticResult:
314
+ """Check for cache-related issues."""
315
+ cache_dir = Path.home() / ".cache" / "claude-mpm"
316
+
317
+ if not cache_dir.exists():
318
+ return DiagnosticResult(
319
+ category="Cache",
320
+ status=DiagnosticStatus.OK,
321
+ message="No cache directory",
322
+ details={"exists": False}
323
+ )
324
+
325
+ try:
326
+ # Check cache size
327
+ cache_files = list(cache_dir.glob("**/*"))
328
+ total_size = sum(f.stat().st_size for f in cache_files if f.is_file())
329
+ size_mb = total_size / (1024 * 1024)
330
+
331
+ if size_mb > 500: # >500MB cache
332
+ return DiagnosticResult(
333
+ category="Cache",
334
+ status=DiagnosticStatus.WARNING,
335
+ message=f"Large cache: {size_mb:.1f}MB",
336
+ details={"size_mb": round(size_mb, 2), "file_count": len(cache_files)},
337
+ fix_command=f"rm -rf {cache_dir}",
338
+ fix_description="Clear cache to free up space"
339
+ )
340
+
341
+ return DiagnosticResult(
342
+ category="Cache",
343
+ status=DiagnosticStatus.OK,
344
+ message=f"Cache size normal ({size_mb:.1f}MB)",
345
+ details={"size_mb": round(size_mb, 2), "file_count": len(cache_files)}
346
+ )
347
+
348
+ except Exception as e:
349
+ return DiagnosticResult(
350
+ category="Cache",
351
+ status=DiagnosticStatus.WARNING,
352
+ message=f"Could not check cache: {str(e)}",
353
+ details={"error": str(e)}
354
+ )
@@ -0,0 +1,300 @@
1
+ """
2
+ Check claude-mpm configuration files.
3
+
4
+ WHY: Verify that user and project configurations are valid, accessible,
5
+ and properly structured.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Dict, Any, Optional
11
+
12
+ import yaml
13
+
14
+ from ..models import DiagnosticResult, DiagnosticStatus
15
+ from .base_check import BaseDiagnosticCheck
16
+
17
+
18
+ class ConfigurationCheck(BaseDiagnosticCheck):
19
+ """Check configuration files and settings."""
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ return "configuration_check"
24
+
25
+ @property
26
+ def category(self) -> str:
27
+ return "Configuration"
28
+
29
+ def run(self) -> DiagnosticResult:
30
+ """Run configuration diagnostics."""
31
+ try:
32
+ sub_results = []
33
+ details = {}
34
+
35
+ # Check user configuration
36
+ user_result = self._check_user_config()
37
+ sub_results.append(user_result)
38
+ details["user_config"] = user_result.details
39
+
40
+ # Check project configuration
41
+ project_result = self._check_project_config()
42
+ sub_results.append(project_result)
43
+ details["project_config"] = project_result.details
44
+
45
+ # Check environment variables
46
+ env_result = self._check_environment_variables()
47
+ sub_results.append(env_result)
48
+ details["environment"] = env_result.details
49
+
50
+ # Check configuration permissions
51
+ perm_result = self._check_config_permissions()
52
+ sub_results.append(perm_result)
53
+
54
+ # Determine overall status
55
+ if any(r.status == DiagnosticStatus.ERROR for r in sub_results):
56
+ status = DiagnosticStatus.ERROR
57
+ message = "Configuration has critical issues"
58
+ elif any(r.status == DiagnosticStatus.WARNING for r in sub_results):
59
+ status = DiagnosticStatus.WARNING
60
+ message = "Configuration has minor issues"
61
+ else:
62
+ status = DiagnosticStatus.OK
63
+ message = "Configuration is valid"
64
+
65
+ return DiagnosticResult(
66
+ category=self.category,
67
+ status=status,
68
+ message=message,
69
+ details=details,
70
+ sub_results=sub_results if self.verbose else []
71
+ )
72
+
73
+ except Exception as e:
74
+ return DiagnosticResult(
75
+ category=self.category,
76
+ status=DiagnosticStatus.ERROR,
77
+ message=f"Configuration check failed: {str(e)}",
78
+ details={"error": str(e)}
79
+ )
80
+
81
+ def _check_user_config(self) -> DiagnosticResult:
82
+ """Check user-level configuration."""
83
+ config_path = Path.home() / ".claude" / "claude-mpm.yaml"
84
+
85
+ if not config_path.exists():
86
+ return DiagnosticResult(
87
+ category="User Config",
88
+ status=DiagnosticStatus.OK,
89
+ message="No user configuration (using defaults)",
90
+ details={"path": str(config_path), "exists": False}
91
+ )
92
+
93
+ try:
94
+ with open(config_path, 'r') as f:
95
+ config = yaml.safe_load(f)
96
+
97
+ issues = self._validate_config_structure(config)
98
+ if issues:
99
+ return DiagnosticResult(
100
+ category="User Config",
101
+ status=DiagnosticStatus.WARNING,
102
+ message=f"User config has issues: {', '.join(issues)}",
103
+ details={"path": str(config_path), "issues": issues}
104
+ )
105
+
106
+ return DiagnosticResult(
107
+ category="User Config",
108
+ status=DiagnosticStatus.OK,
109
+ message="User configuration valid",
110
+ details={
111
+ "path": str(config_path),
112
+ "size": config_path.stat().st_size,
113
+ "exists": True
114
+ }
115
+ )
116
+
117
+ except yaml.YAMLError as e:
118
+ return DiagnosticResult(
119
+ category="User Config",
120
+ status=DiagnosticStatus.ERROR,
121
+ message="User config has invalid YAML",
122
+ details={"path": str(config_path), "error": str(e)},
123
+ fix_description="Fix YAML syntax errors in the configuration file"
124
+ )
125
+ except Exception as e:
126
+ return DiagnosticResult(
127
+ category="User Config",
128
+ status=DiagnosticStatus.WARNING,
129
+ message=f"Could not read user config: {str(e)}",
130
+ details={"path": str(config_path), "error": str(e)}
131
+ )
132
+
133
+ def _check_project_config(self) -> DiagnosticResult:
134
+ """Check project-level configuration."""
135
+ config_path = Path.cwd() / ".claude" / "claude-mpm.yaml"
136
+
137
+ if not config_path.exists():
138
+ return DiagnosticResult(
139
+ category="Project Config",
140
+ status=DiagnosticStatus.OK,
141
+ message="No project configuration (using defaults)",
142
+ details={"path": str(config_path), "exists": False}
143
+ )
144
+
145
+ try:
146
+ with open(config_path, 'r') as f:
147
+ config = yaml.safe_load(f)
148
+
149
+ issues = self._validate_config_structure(config)
150
+ if issues:
151
+ return DiagnosticResult(
152
+ category="Project Config",
153
+ status=DiagnosticStatus.WARNING,
154
+ message=f"Project config has issues: {', '.join(issues)}",
155
+ details={"path": str(config_path), "issues": issues}
156
+ )
157
+
158
+ # Check for deprecated keys
159
+ deprecated = self._check_deprecated_keys(config)
160
+ if deprecated:
161
+ return DiagnosticResult(
162
+ category="Project Config",
163
+ status=DiagnosticStatus.WARNING,
164
+ message=f"Using deprecated config keys: {', '.join(deprecated)}",
165
+ details={
166
+ "path": str(config_path),
167
+ "deprecated_keys": deprecated
168
+ },
169
+ fix_description="Remove deprecated configuration keys"
170
+ )
171
+
172
+ return DiagnosticResult(
173
+ category="Project Config",
174
+ status=DiagnosticStatus.OK,
175
+ message="Project configuration valid",
176
+ details={
177
+ "path": str(config_path),
178
+ "size": config_path.stat().st_size,
179
+ "exists": True
180
+ }
181
+ )
182
+
183
+ except yaml.YAMLError as e:
184
+ return DiagnosticResult(
185
+ category="Project Config",
186
+ status=DiagnosticStatus.ERROR,
187
+ message="Project config has invalid YAML",
188
+ details={"path": str(config_path), "error": str(e)},
189
+ fix_description="Fix YAML syntax errors in the configuration file"
190
+ )
191
+ except Exception as e:
192
+ return DiagnosticResult(
193
+ category="Project Config",
194
+ status=DiagnosticStatus.WARNING,
195
+ message=f"Could not read project config: {str(e)}",
196
+ details={"path": str(config_path), "error": str(e)}
197
+ )
198
+
199
+ def _check_environment_variables(self) -> DiagnosticResult:
200
+ """Check relevant environment variables."""
201
+ env_vars = {
202
+ "CLAUDE_MPM_CONFIG": os.environ.get("CLAUDE_MPM_CONFIG"),
203
+ "CLAUDE_MPM_LOG_LEVEL": os.environ.get("CLAUDE_MPM_LOG_LEVEL"),
204
+ "CLAUDE_MPM_PROJECT_DIR": os.environ.get("CLAUDE_MPM_PROJECT_DIR"),
205
+ "PYTHONPATH": os.environ.get("PYTHONPATH"),
206
+ }
207
+
208
+ set_vars = {k: v for k, v in env_vars.items() if v is not None}
209
+
210
+ if not set_vars:
211
+ return DiagnosticResult(
212
+ category="Environment Variables",
213
+ status=DiagnosticStatus.OK,
214
+ message="No claude-mpm environment variables set",
215
+ details={"variables": {}}
216
+ )
217
+
218
+ return DiagnosticResult(
219
+ category="Environment Variables",
220
+ status=DiagnosticStatus.OK,
221
+ message=f"{len(set_vars)} environment variable(s) configured",
222
+ details={"variables": set_vars}
223
+ )
224
+
225
+ def _check_config_permissions(self) -> DiagnosticResult:
226
+ """Check configuration file permissions."""
227
+ issues = []
228
+ paths_checked = []
229
+
230
+ for config_path in [
231
+ Path.home() / ".claude" / "claude-mpm.yaml",
232
+ Path.cwd() / ".claude" / "claude-mpm.yaml"
233
+ ]:
234
+ if config_path.exists():
235
+ paths_checked.append(str(config_path))
236
+
237
+ # Check readability
238
+ if not os.access(config_path, os.R_OK):
239
+ issues.append(f"{config_path.name} is not readable")
240
+
241
+ # Check if world-writable (security concern)
242
+ stat_info = config_path.stat()
243
+ if stat_info.st_mode & 0o002:
244
+ issues.append(f"{config_path.name} is world-writable (security risk)")
245
+
246
+ if not paths_checked:
247
+ return DiagnosticResult(
248
+ category="Config Permissions",
249
+ status=DiagnosticStatus.OK,
250
+ message="No configuration files to check",
251
+ details={"paths_checked": []}
252
+ )
253
+
254
+ if issues:
255
+ return DiagnosticResult(
256
+ category="Config Permissions",
257
+ status=DiagnosticStatus.WARNING,
258
+ message=f"Permission issues: {', '.join(issues)}",
259
+ details={"issues": issues, "paths_checked": paths_checked},
260
+ fix_command="chmod 644 ~/.claude/claude-mpm.yaml",
261
+ fix_description="Fix file permissions for configuration files"
262
+ )
263
+
264
+ return DiagnosticResult(
265
+ category="Config Permissions",
266
+ status=DiagnosticStatus.OK,
267
+ message="Configuration file permissions are correct",
268
+ details={"paths_checked": paths_checked}
269
+ )
270
+
271
+ def _validate_config_structure(self, config: Dict[str, Any]) -> list:
272
+ """Validate configuration structure and return issues."""
273
+ issues = []
274
+
275
+ if not isinstance(config, dict):
276
+ issues.append("Configuration is not a dictionary")
277
+ return issues
278
+
279
+ # Check for invalid top-level keys
280
+ valid_keys = {
281
+ "response_logging", "agent_deployment", "memory_management",
282
+ "monitoring", "mcp", "hooks", "paths", "debug"
283
+ }
284
+
285
+ invalid_keys = set(config.keys()) - valid_keys
286
+ if invalid_keys:
287
+ issues.append(f"Unknown keys: {', '.join(invalid_keys)}")
288
+
289
+ return issues
290
+
291
+ def _check_deprecated_keys(self, config: Dict[str, Any]) -> list:
292
+ """Check for deprecated configuration keys."""
293
+ deprecated_keys = ["legacy_mode", "old_agent_format", "deprecated_logging"]
294
+ found_deprecated = []
295
+
296
+ for key in deprecated_keys:
297
+ if key in config:
298
+ found_deprecated.append(key)
299
+
300
+ return found_deprecated