claude-mpm 3.9.8__py3-none-any.whl → 3.9.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.
Files changed (61) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/agents/templates/memory_manager.json +155 -0
  4. claude_mpm/cli/__init__.py +18 -3
  5. claude_mpm/cli/commands/__init__.py +6 -1
  6. claude_mpm/cli/commands/cleanup.py +21 -1
  7. claude_mpm/cli/commands/mcp.py +967 -0
  8. claude_mpm/cli/commands/run_guarded.py +511 -0
  9. claude_mpm/cli/parser.py +156 -3
  10. claude_mpm/config/experimental_features.py +219 -0
  11. claude_mpm/config/memory_guardian_config.py +325 -0
  12. claude_mpm/config/memory_guardian_yaml.py +335 -0
  13. claude_mpm/constants.py +14 -0
  14. claude_mpm/core/memory_aware_runner.py +353 -0
  15. claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
  16. claude_mpm/models/state_models.py +433 -0
  17. claude_mpm/services/communication/__init__.py +2 -2
  18. claude_mpm/services/communication/socketio.py +18 -16
  19. claude_mpm/services/infrastructure/__init__.py +4 -1
  20. claude_mpm/services/infrastructure/context_preservation.py +537 -0
  21. claude_mpm/services/infrastructure/graceful_degradation.py +616 -0
  22. claude_mpm/services/infrastructure/health_monitor.py +775 -0
  23. claude_mpm/services/infrastructure/logging.py +3 -3
  24. claude_mpm/services/infrastructure/memory_dashboard.py +479 -0
  25. claude_mpm/services/infrastructure/memory_guardian.py +944 -0
  26. claude_mpm/services/infrastructure/restart_protection.py +642 -0
  27. claude_mpm/services/infrastructure/state_manager.py +774 -0
  28. claude_mpm/services/mcp_gateway/__init__.py +39 -23
  29. claude_mpm/services/mcp_gateway/core/__init__.py +2 -2
  30. claude_mpm/services/mcp_gateway/core/interfaces.py +10 -9
  31. claude_mpm/services/mcp_gateway/main.py +356 -0
  32. claude_mpm/services/mcp_gateway/manager.py +334 -0
  33. claude_mpm/services/mcp_gateway/registry/__init__.py +6 -3
  34. claude_mpm/services/mcp_gateway/registry/service_registry.py +393 -0
  35. claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
  36. claude_mpm/services/mcp_gateway/server/__init__.py +9 -3
  37. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +431 -0
  38. claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
  39. claude_mpm/services/mcp_gateway/tools/__init__.py +16 -3
  40. claude_mpm/services/mcp_gateway/tools/base_adapter.py +496 -0
  41. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
  42. claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
  43. claude_mpm/services/ticket_manager.py +8 -8
  44. claude_mpm/services/ticket_manager_di.py +5 -5
  45. claude_mpm/storage/__init__.py +9 -0
  46. claude_mpm/storage/state_storage.py +556 -0
  47. claude_mpm/utils/file_utils.py +293 -0
  48. claude_mpm/utils/platform_memory.py +524 -0
  49. claude_mpm/utils/subprocess_utils.py +305 -0
  50. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/METADATA +27 -2
  51. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/RECORD +56 -32
  52. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  53. claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  54. claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
  55. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
  56. claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  57. /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
  58. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/WHEEL +0 -0
  59. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/entry_points.txt +0 -0
  60. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/licenses/LICENSE +0 -0
  61. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
1
+ """Experimental features configuration for Claude MPM.
2
+
3
+ WHY: This module manages experimental and beta features, providing a centralized
4
+ way to control feature flags and display appropriate warnings to users.
5
+
6
+ DESIGN DECISION: Use a simple configuration class with static defaults that can
7
+ be overridden through environment variables or config files. This allows for
8
+ gradual rollout of experimental features while maintaining stability in production.
9
+ """
10
+
11
+ import os
12
+ from typing import Dict, Any, Optional
13
+ from pathlib import Path
14
+ import json
15
+
16
+
17
+ class ExperimentalFeatures:
18
+ """Manages experimental feature flags and warnings.
19
+
20
+ WHY: Experimental features need special handling to ensure users understand
21
+ they are using beta functionality that may change or have issues.
22
+
23
+ DESIGN DECISION: Use environment variables for quick override during development,
24
+ but also support configuration files for persistent settings.
25
+ """
26
+
27
+ # Default feature flags
28
+ DEFAULTS = {
29
+ 'enable_memory_guardian': False, # Memory Guardian is experimental
30
+ 'enable_mcp_gateway': False, # MCP Gateway is experimental
31
+ 'enable_advanced_aggregation': False, # Advanced aggregation features
32
+ 'show_experimental_warnings': True, # Show warnings for experimental features
33
+ 'require_experimental_acceptance': True, # Require explicit acceptance
34
+ }
35
+
36
+ # Warning messages for experimental features
37
+ WARNINGS = {
38
+ 'memory_guardian': (
39
+ "⚠️ EXPERIMENTAL FEATURE: Memory Guardian is in beta.\n"
40
+ " This feature may change or have issues. Use with caution in production.\n"
41
+ " Report issues at: https://github.com/bluescreen10/claude-mpm/issues"
42
+ ),
43
+ 'mcp_gateway': (
44
+ "⚠️ EXPERIMENTAL FEATURE: MCP Gateway is in early access.\n"
45
+ " Tool integration may be unstable. Not recommended for production use."
46
+ ),
47
+ 'advanced_aggregation': (
48
+ "⚠️ EXPERIMENTAL FEATURE: Advanced aggregation is under development.\n"
49
+ " Results may vary. Please verify outputs manually."
50
+ ),
51
+ }
52
+
53
+ def __init__(self, config_file: Optional[Path] = None):
54
+ """Initialize experimental features configuration.
55
+
56
+ Args:
57
+ config_file: Optional path to configuration file
58
+ """
59
+ self._features = self.DEFAULTS.copy()
60
+ self._config_file = config_file
61
+ self._load_configuration()
62
+ self._apply_environment_overrides()
63
+
64
+ def _load_configuration(self):
65
+ """Load configuration from file if it exists.
66
+
67
+ WHY: Allow persistent configuration of experimental features without
68
+ requiring environment variables to be set every time.
69
+ """
70
+ if self._config_file and self._config_file.exists():
71
+ try:
72
+ with open(self._config_file, 'r') as f:
73
+ config = json.load(f)
74
+ experimental = config.get('experimental_features', {})
75
+ self._features.update(experimental)
76
+ except Exception:
77
+ # Silently ignore configuration errors for experimental features
78
+ pass
79
+
80
+ def _apply_environment_overrides(self):
81
+ """Apply environment variable overrides.
82
+
83
+ WHY: Environment variables provide a quick way to enable/disable features
84
+ during development and testing without modifying configuration files.
85
+
86
+ Format: CLAUDE_MPM_EXPERIMENTAL_<FEATURE_NAME>=true/false
87
+ """
88
+ for key in self._features:
89
+ env_key = f"CLAUDE_MPM_EXPERIMENTAL_{key.upper()}"
90
+ if env_key in os.environ:
91
+ value = os.environ[env_key].lower()
92
+ self._features[key] = value in ('true', '1', 'yes', 'on')
93
+
94
+ def is_enabled(self, feature: str) -> bool:
95
+ """Check if a feature is enabled.
96
+
97
+ Args:
98
+ feature: Feature name (without 'enable_' prefix)
99
+
100
+ Returns:
101
+ True if the feature is enabled
102
+ """
103
+ key = f"enable_{feature}" if not feature.startswith('enable_') else feature
104
+ return self._features.get(key, False)
105
+
106
+ def get_warning(self, feature: str) -> Optional[str]:
107
+ """Get warning message for a feature.
108
+
109
+ Args:
110
+ feature: Feature name
111
+
112
+ Returns:
113
+ Warning message or None if no warning exists
114
+ """
115
+ return self.WARNINGS.get(feature)
116
+
117
+ def should_show_warning(self, feature: str) -> bool:
118
+ """Check if warning should be shown for a feature.
119
+
120
+ Args:
121
+ feature: Feature name
122
+
123
+ Returns:
124
+ True if warning should be displayed
125
+ """
126
+ if not self._features.get('show_experimental_warnings', True):
127
+ return False
128
+
129
+ # Check if user has already accepted this feature
130
+ accepted_file = Path.home() / '.claude-mpm' / '.experimental_accepted'
131
+ if accepted_file.exists():
132
+ try:
133
+ with open(accepted_file, 'r') as f:
134
+ accepted = json.load(f)
135
+ if feature in accepted.get('features', []):
136
+ return False
137
+ except Exception:
138
+ pass
139
+
140
+ return True
141
+
142
+ def mark_accepted(self, feature: str):
143
+ """Mark a feature as accepted by the user.
144
+
145
+ WHY: Once a user accepts the experimental status, we don't need to
146
+ warn them every time they use the feature.
147
+
148
+ Args:
149
+ feature: Feature name to mark as accepted
150
+ """
151
+ accepted_file = Path.home() / '.claude-mpm' / '.experimental_accepted'
152
+ accepted_file.parent.mkdir(parents=True, exist_ok=True)
153
+
154
+ try:
155
+ if accepted_file.exists():
156
+ with open(accepted_file, 'r') as f:
157
+ data = json.load(f)
158
+ else:
159
+ data = {'features': [], 'timestamp': {}}
160
+
161
+ if feature not in data['features']:
162
+ data['features'].append(feature)
163
+ data['timestamp'][feature] = os.environ.get('CLAUDE_MPM_TIMESTAMP',
164
+ str(Path.cwd()))
165
+
166
+ with open(accepted_file, 'w') as f:
167
+ json.dump(data, f, indent=2)
168
+ except Exception:
169
+ # Silently ignore errors in acceptance tracking
170
+ pass
171
+
172
+ def requires_acceptance(self) -> bool:
173
+ """Check if experimental features require explicit acceptance.
174
+
175
+ Returns:
176
+ True if acceptance is required
177
+ """
178
+ return self._features.get('require_experimental_acceptance', True)
179
+
180
+ def get_all_features(self) -> Dict[str, bool]:
181
+ """Get all feature flags and their current values.
182
+
183
+ Returns:
184
+ Dictionary of feature flags and their values
185
+ """
186
+ return self._features.copy()
187
+
188
+
189
+ # Global instance for easy access
190
+ _experimental_features = None
191
+
192
+
193
+ def get_experimental_features(config_file: Optional[Path] = None) -> ExperimentalFeatures:
194
+ """Get the global experimental features instance.
195
+
196
+ WHY: Provide a singleton-like access pattern to experimental features
197
+ configuration to ensure consistency across the application.
198
+
199
+ Args:
200
+ config_file: Optional configuration file path
201
+
202
+ Returns:
203
+ ExperimentalFeatures instance
204
+ """
205
+ global _experimental_features
206
+ if _experimental_features is None:
207
+ # Check for config file in standard locations
208
+ if config_file is None:
209
+ for path in [
210
+ Path.cwd() / '.claude-mpm' / 'experimental.json',
211
+ Path.home() / '.claude-mpm' / 'experimental.json',
212
+ ]:
213
+ if path.exists():
214
+ config_file = path
215
+ break
216
+
217
+ _experimental_features = ExperimentalFeatures(config_file)
218
+
219
+ return _experimental_features
@@ -0,0 +1,325 @@
1
+ """Memory Guardian configuration for managing Claude Code memory usage.
2
+
3
+ This module provides configuration management for the MemoryGuardian service
4
+ that monitors and manages Claude Code subprocess memory consumption.
5
+
6
+ Design Principles:
7
+ - Platform-agnostic configuration with OS-specific overrides
8
+ - Environment-based configuration for different deployment scenarios
9
+ - Flexible thresholds for memory monitoring
10
+ - Support for both psutil and fallback monitoring methods
11
+ """
12
+
13
+ import os
14
+ import platform
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Dict, Any, Optional, List
18
+
19
+
20
+ @dataclass
21
+ class MemoryThresholds:
22
+ """Memory threshold configuration in MB."""
23
+
24
+ # Memory thresholds in MB (defaults for 24GB system)
25
+ warning: int = 12288 # 12GB - Start monitoring closely
26
+ critical: int = 15360 # 15GB - Consider restart
27
+ emergency: int = 18432 # 18GB - Force restart
28
+
29
+ # Percentage-based thresholds (as fallback when system memory is detected)
30
+ warning_percent: float = 50.0 # 50% of system memory
31
+ critical_percent: float = 65.0 # 65% of system memory
32
+ emergency_percent: float = 75.0 # 75% of system memory
33
+
34
+ def adjust_for_system_memory(self, total_memory_mb: int) -> None:
35
+ """Adjust thresholds based on available system memory."""
36
+ if total_memory_mb > 0:
37
+ self.warning = int(total_memory_mb * (self.warning_percent / 100))
38
+ self.critical = int(total_memory_mb * (self.critical_percent / 100))
39
+ self.emergency = int(total_memory_mb * (self.emergency_percent / 100))
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Convert thresholds to dictionary."""
43
+ return {
44
+ 'warning_mb': self.warning,
45
+ 'critical_mb': self.critical,
46
+ 'emergency_mb': self.emergency,
47
+ 'warning_percent': self.warning_percent,
48
+ 'critical_percent': self.critical_percent,
49
+ 'emergency_percent': self.emergency_percent
50
+ }
51
+
52
+
53
+ @dataclass
54
+ class RestartPolicy:
55
+ """Configuration for process restart behavior."""
56
+
57
+ # Restart attempts
58
+ max_attempts: int = 3 # Maximum restart attempts before giving up
59
+ attempt_window: int = 3600 # Window in seconds to count attempts (1 hour)
60
+
61
+ # Cooldown periods
62
+ initial_cooldown: int = 30 # Initial cooldown after restart (seconds)
63
+ max_cooldown: int = 300 # Maximum cooldown period (5 minutes)
64
+ cooldown_multiplier: float = 2.0 # Multiply cooldown on each retry
65
+
66
+ # Graceful shutdown
67
+ graceful_timeout: int = 30 # Time to wait for graceful shutdown (seconds)
68
+ force_kill_timeout: int = 10 # Time to wait before SIGKILL after SIGTERM
69
+
70
+ def get_cooldown(self, attempt: int) -> int:
71
+ """Calculate cooldown period for given attempt number."""
72
+ cooldown = self.initial_cooldown * (self.cooldown_multiplier ** (attempt - 1))
73
+ return min(int(cooldown), self.max_cooldown)
74
+
75
+ def to_dict(self) -> Dict[str, Any]:
76
+ """Convert restart policy to dictionary."""
77
+ return {
78
+ 'max_attempts': self.max_attempts,
79
+ 'attempt_window': self.attempt_window,
80
+ 'initial_cooldown': self.initial_cooldown,
81
+ 'max_cooldown': self.max_cooldown,
82
+ 'cooldown_multiplier': self.cooldown_multiplier,
83
+ 'graceful_timeout': self.graceful_timeout,
84
+ 'force_kill_timeout': self.force_kill_timeout
85
+ }
86
+
87
+
88
+ @dataclass
89
+ class MonitoringConfig:
90
+ """Configuration for memory monitoring behavior."""
91
+
92
+ # Check intervals (seconds)
93
+ normal_interval: int = 30 # Normal check interval
94
+ warning_interval: int = 15 # Check interval when in warning state
95
+ critical_interval: int = 5 # Check interval when in critical state
96
+
97
+ # Monitoring method preferences
98
+ prefer_psutil: bool = True # Prefer psutil if available
99
+ fallback_methods: List[str] = field(default_factory=lambda: [
100
+ 'platform_specific', # Use OS-specific commands
101
+ 'resource_module', # Use resource module as last resort
102
+ ])
103
+
104
+ # Logging and reporting
105
+ log_memory_stats: bool = True # Log memory statistics
106
+ log_interval: int = 300 # Log summary every 5 minutes
107
+ detailed_logging: bool = False # Enable detailed debug logging
108
+
109
+ def get_check_interval(self, memory_state: str) -> int:
110
+ """Get check interval based on current memory state."""
111
+ if memory_state == 'critical':
112
+ return self.critical_interval
113
+ elif memory_state == 'warning':
114
+ return self.warning_interval
115
+ else:
116
+ return self.normal_interval
117
+
118
+ def to_dict(self) -> Dict[str, Any]:
119
+ """Convert monitoring config to dictionary."""
120
+ return {
121
+ 'normal_interval': self.normal_interval,
122
+ 'warning_interval': self.warning_interval,
123
+ 'critical_interval': self.critical_interval,
124
+ 'prefer_psutil': self.prefer_psutil,
125
+ 'fallback_methods': self.fallback_methods,
126
+ 'log_memory_stats': self.log_memory_stats,
127
+ 'log_interval': self.log_interval,
128
+ 'detailed_logging': self.detailed_logging
129
+ }
130
+
131
+
132
+ @dataclass
133
+ class PlatformOverrides:
134
+ """Platform-specific configuration overrides."""
135
+
136
+ # macOS specific
137
+ macos_use_activity_monitor: bool = False # Use Activity Monitor data if available
138
+ macos_memory_pressure_check: bool = True # Check system memory pressure
139
+
140
+ # Linux specific
141
+ linux_use_proc: bool = True # Use /proc filesystem
142
+ linux_check_oom_score: bool = True # Monitor OOM killer score
143
+
144
+ # Windows specific
145
+ windows_use_wmi: bool = True # Use WMI for monitoring
146
+ windows_use_performance_counter: bool = False # Use performance counters
147
+
148
+ def to_dict(self) -> Dict[str, Any]:
149
+ """Convert platform overrides to dictionary."""
150
+ return {
151
+ 'macos_use_activity_monitor': self.macos_use_activity_monitor,
152
+ 'macos_memory_pressure_check': self.macos_memory_pressure_check,
153
+ 'linux_use_proc': self.linux_use_proc,
154
+ 'linux_check_oom_score': self.linux_check_oom_score,
155
+ 'windows_use_wmi': self.windows_use_wmi,
156
+ 'windows_use_performance_counter': self.windows_use_performance_counter
157
+ }
158
+
159
+
160
+ @dataclass
161
+ class MemoryGuardianConfig:
162
+ """Complete configuration for MemoryGuardian service."""
163
+
164
+ # Core configurations
165
+ thresholds: MemoryThresholds = field(default_factory=MemoryThresholds)
166
+ restart_policy: RestartPolicy = field(default_factory=RestartPolicy)
167
+ monitoring: MonitoringConfig = field(default_factory=MonitoringConfig)
168
+ platform_overrides: PlatformOverrides = field(default_factory=PlatformOverrides)
169
+
170
+ # Process configuration
171
+ process_command: List[str] = field(default_factory=lambda: ['claude-code'])
172
+ process_args: List[str] = field(default_factory=list)
173
+ process_env: Dict[str, str] = field(default_factory=dict)
174
+ working_directory: Optional[str] = None
175
+
176
+ # Service configuration
177
+ enabled: bool = True # Enable memory guardian
178
+ auto_start: bool = True # Auto-start monitored process
179
+ persist_state: bool = True # Persist state across restarts
180
+ state_file: Optional[str] = None # State file path
181
+
182
+ @classmethod
183
+ def from_env(cls) -> 'MemoryGuardianConfig':
184
+ """Create configuration from environment variables."""
185
+ config = cls()
186
+
187
+ # Memory thresholds
188
+ if warning := os.getenv('CLAUDE_MPM_MEMORY_WARNING_MB'):
189
+ config.thresholds.warning = int(warning)
190
+ if critical := os.getenv('CLAUDE_MPM_MEMORY_CRITICAL_MB'):
191
+ config.thresholds.critical = int(critical)
192
+ if emergency := os.getenv('CLAUDE_MPM_MEMORY_EMERGENCY_MB'):
193
+ config.thresholds.emergency = int(emergency)
194
+
195
+ # Restart policy
196
+ if max_attempts := os.getenv('CLAUDE_MPM_RESTART_MAX_ATTEMPTS'):
197
+ config.restart_policy.max_attempts = int(max_attempts)
198
+ if cooldown := os.getenv('CLAUDE_MPM_RESTART_COOLDOWN'):
199
+ config.restart_policy.initial_cooldown = int(cooldown)
200
+
201
+ # Monitoring intervals
202
+ if interval := os.getenv('CLAUDE_MPM_MONITOR_INTERVAL'):
203
+ config.monitoring.normal_interval = int(interval)
204
+ if log_interval := os.getenv('CLAUDE_MPM_LOG_INTERVAL'):
205
+ config.monitoring.log_interval = int(log_interval)
206
+
207
+ # Service settings
208
+ config.enabled = os.getenv('CLAUDE_MPM_MEMORY_GUARDIAN_ENABLED', 'true').lower() == 'true'
209
+ config.auto_start = os.getenv('CLAUDE_MPM_AUTO_START', 'true').lower() == 'true'
210
+
211
+ # Process command
212
+ if command := os.getenv('CLAUDE_MPM_PROCESS_COMMAND'):
213
+ config.process_command = command.split()
214
+
215
+ return config
216
+
217
+ @classmethod
218
+ def for_development(cls) -> 'MemoryGuardianConfig':
219
+ """Configuration optimized for development."""
220
+ config = cls()
221
+ config.thresholds.warning = 8192 # 8GB for dev machines
222
+ config.thresholds.critical = 10240 # 10GB
223
+ config.thresholds.emergency = 12288 # 12GB
224
+ config.monitoring.normal_interval = 60 # Check less frequently
225
+ config.monitoring.detailed_logging = True
226
+ return config
227
+
228
+ @classmethod
229
+ def for_production(cls) -> 'MemoryGuardianConfig':
230
+ """Configuration optimized for production."""
231
+ config = cls()
232
+ config.monitoring.normal_interval = 30
233
+ config.monitoring.log_memory_stats = True
234
+ config.persist_state = True
235
+ config.restart_policy.max_attempts = 5
236
+ return config
237
+
238
+ @classmethod
239
+ def for_platform(cls, platform_name: Optional[str] = None) -> 'MemoryGuardianConfig':
240
+ """Get platform-specific configuration."""
241
+ if platform_name is None:
242
+ platform_name = platform.system().lower()
243
+
244
+ config = cls()
245
+
246
+ if platform_name == 'darwin': # macOS
247
+ config.platform_overrides.macos_memory_pressure_check = True
248
+ elif platform_name == 'linux':
249
+ config.platform_overrides.linux_use_proc = True
250
+ config.platform_overrides.linux_check_oom_score = True
251
+ elif platform_name == 'windows':
252
+ config.platform_overrides.windows_use_wmi = True
253
+
254
+ return config
255
+
256
+ def to_dict(self) -> Dict[str, Any]:
257
+ """Convert configuration to dictionary."""
258
+ return {
259
+ 'thresholds': self.thresholds.to_dict(),
260
+ 'restart_policy': self.restart_policy.to_dict(),
261
+ 'monitoring': self.monitoring.to_dict(),
262
+ 'platform_overrides': self.platform_overrides.to_dict(),
263
+ 'process_command': self.process_command,
264
+ 'process_args': self.process_args,
265
+ 'process_env': self.process_env,
266
+ 'working_directory': self.working_directory,
267
+ 'enabled': self.enabled,
268
+ 'auto_start': self.auto_start,
269
+ 'persist_state': self.persist_state,
270
+ 'state_file': self.state_file
271
+ }
272
+
273
+ def validate(self) -> List[str]:
274
+ """Validate configuration and return list of issues."""
275
+ issues = []
276
+
277
+ # Validate thresholds are in correct order
278
+ if self.thresholds.warning >= self.thresholds.critical:
279
+ issues.append("Warning threshold must be less than critical threshold")
280
+ if self.thresholds.critical >= self.thresholds.emergency:
281
+ issues.append("Critical threshold must be less than emergency threshold")
282
+
283
+ # Validate intervals
284
+ if self.monitoring.normal_interval <= 0:
285
+ issues.append("Normal monitoring interval must be positive")
286
+ if self.monitoring.warning_interval <= 0:
287
+ issues.append("Warning monitoring interval must be positive")
288
+ if self.monitoring.critical_interval <= 0:
289
+ issues.append("Critical monitoring interval must be positive")
290
+
291
+ # Validate restart policy
292
+ if self.restart_policy.max_attempts < 0:
293
+ issues.append("Max restart attempts cannot be negative")
294
+ if self.restart_policy.initial_cooldown <= 0:
295
+ issues.append("Initial cooldown must be positive")
296
+
297
+ # Validate process command
298
+ if not self.process_command:
299
+ issues.append("Process command cannot be empty")
300
+
301
+ return issues
302
+
303
+
304
+ def get_default_config() -> MemoryGuardianConfig:
305
+ """Get default configuration adjusted for current platform."""
306
+ config = MemoryGuardianConfig.for_platform()
307
+
308
+ # Try to adjust for available system memory
309
+ try:
310
+ import psutil
311
+ total_memory_mb = psutil.virtual_memory().total // (1024 * 1024)
312
+ config.thresholds.adjust_for_system_memory(total_memory_mb)
313
+ except ImportError:
314
+ # psutil not available, use defaults
315
+ pass
316
+ except Exception:
317
+ # Any other error, use defaults
318
+ pass
319
+
320
+ # Override with environment variables
321
+ env_config = MemoryGuardianConfig.from_env()
322
+ if env_config.enabled != config.enabled:
323
+ config = env_config
324
+
325
+ return config