claude-mpm 3.9.8__py3-none-any.whl → 3.9.9__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 (44) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/cli/__init__.py +3 -1
  4. claude_mpm/cli/commands/__init__.py +3 -1
  5. claude_mpm/cli/commands/cleanup.py +21 -1
  6. claude_mpm/cli/commands/mcp.py +821 -0
  7. claude_mpm/cli/parser.py +148 -1
  8. claude_mpm/config/memory_guardian_config.py +325 -0
  9. claude_mpm/constants.py +13 -0
  10. claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
  11. claude_mpm/models/state_models.py +433 -0
  12. claude_mpm/services/communication/__init__.py +2 -2
  13. claude_mpm/services/communication/socketio.py +18 -16
  14. claude_mpm/services/infrastructure/__init__.py +4 -1
  15. claude_mpm/services/infrastructure/logging.py +3 -3
  16. claude_mpm/services/infrastructure/memory_guardian.py +770 -0
  17. claude_mpm/services/mcp_gateway/__init__.py +28 -12
  18. claude_mpm/services/mcp_gateway/main.py +326 -0
  19. claude_mpm/services/mcp_gateway/registry/__init__.py +6 -3
  20. claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
  21. claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
  22. claude_mpm/services/mcp_gateway/server/__init__.py +9 -3
  23. claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
  24. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
  25. claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
  26. claude_mpm/services/mcp_gateway/tools/__init__.py +16 -3
  27. claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
  28. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
  29. claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
  30. claude_mpm/utils/file_utils.py +293 -0
  31. claude_mpm/utils/platform_memory.py +524 -0
  32. claude_mpm/utils/subprocess_utils.py +305 -0
  33. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +3 -1
  34. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +39 -28
  35. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  36. claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  37. claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
  38. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
  39. claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  40. /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
  41. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
  42. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
  43. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
  44. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,433 @@
1
+ """State models for context preservation across Claude Code restarts.
2
+
3
+ This module defines data models for capturing and restoring various aspects
4
+ of the Claude Code execution state to enable seamless restarts.
5
+
6
+ Design Principles:
7
+ - Comprehensive state capture (process, conversation, project, restart info)
8
+ - Serialization-friendly data structures
9
+ - Validation and sanitization of sensitive data
10
+ - Platform-agnostic representations
11
+ """
12
+
13
+ import os
14
+ import json
15
+ from dataclasses import dataclass, field, asdict
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import Dict, Any, List, Optional, Set
19
+ from enum import Enum
20
+
21
+
22
+ class StateType(Enum):
23
+ """Types of state that can be captured."""
24
+ PROCESS = "process"
25
+ CONVERSATION = "conversation"
26
+ PROJECT = "project"
27
+ RESTART = "restart"
28
+ FULL = "full"
29
+
30
+
31
+ @dataclass
32
+ class ProcessState:
33
+ """Process execution state information.
34
+
35
+ Captures the runtime state of the Claude Code process including
36
+ environment, working directory, and command line arguments.
37
+ """
38
+
39
+ # Process identification
40
+ pid: int
41
+ parent_pid: int
42
+ process_name: str
43
+
44
+ # Execution context
45
+ command: List[str]
46
+ args: List[str]
47
+ working_directory: str
48
+
49
+ # Environment (filtered for security)
50
+ environment: Dict[str, str]
51
+
52
+ # Resource usage
53
+ memory_mb: float
54
+ cpu_percent: float
55
+ open_files: List[str]
56
+
57
+ # Timing
58
+ start_time: float
59
+ capture_time: float
60
+
61
+ def to_dict(self) -> Dict[str, Any]:
62
+ """Convert to dictionary for serialization."""
63
+ return {
64
+ 'pid': self.pid,
65
+ 'parent_pid': self.parent_pid,
66
+ 'process_name': self.process_name,
67
+ 'command': self.command,
68
+ 'args': self.args,
69
+ 'working_directory': self.working_directory,
70
+ 'environment': self._sanitize_environment(self.environment),
71
+ 'memory_mb': round(self.memory_mb, 2),
72
+ 'cpu_percent': round(self.cpu_percent, 2),
73
+ 'open_files': self.open_files[:100], # Limit to prevent huge lists
74
+ 'start_time': self.start_time,
75
+ 'start_time_iso': datetime.fromtimestamp(self.start_time).isoformat(),
76
+ 'capture_time': self.capture_time,
77
+ 'capture_time_iso': datetime.fromtimestamp(self.capture_time).isoformat()
78
+ }
79
+
80
+ @staticmethod
81
+ def _sanitize_environment(env: Dict[str, str]) -> Dict[str, str]:
82
+ """Remove sensitive environment variables."""
83
+ sensitive_patterns = [
84
+ 'TOKEN', 'KEY', 'SECRET', 'PASSWORD', 'CREDENTIAL',
85
+ 'API_KEY', 'AUTH', 'PRIVATE'
86
+ ]
87
+
88
+ sanitized = {}
89
+ for key, value in env.items():
90
+ # Check if key contains sensitive patterns
91
+ is_sensitive = any(pattern in key.upper() for pattern in sensitive_patterns)
92
+
93
+ if is_sensitive:
94
+ sanitized[key] = '***REDACTED***'
95
+ else:
96
+ sanitized[key] = value
97
+
98
+ return sanitized
99
+
100
+ @classmethod
101
+ def from_dict(cls, data: Dict[str, Any]) -> 'ProcessState':
102
+ """Create from dictionary."""
103
+ return cls(
104
+ pid=data['pid'],
105
+ parent_pid=data['parent_pid'],
106
+ process_name=data['process_name'],
107
+ command=data['command'],
108
+ args=data['args'],
109
+ working_directory=data['working_directory'],
110
+ environment=data['environment'],
111
+ memory_mb=data['memory_mb'],
112
+ cpu_percent=data['cpu_percent'],
113
+ open_files=data['open_files'],
114
+ start_time=data['start_time'],
115
+ capture_time=data['capture_time']
116
+ )
117
+
118
+
119
+ @dataclass
120
+ class ConversationContext:
121
+ """Represents a single conversation context."""
122
+
123
+ conversation_id: str
124
+ title: str
125
+ created_at: float
126
+ updated_at: float
127
+ message_count: int
128
+
129
+ # Context window information
130
+ total_tokens: int
131
+ max_tokens: int
132
+
133
+ # File references in conversation
134
+ referenced_files: List[str]
135
+ open_tabs: List[str]
136
+
137
+ # Conversation metadata
138
+ tags: List[str]
139
+ is_active: bool
140
+
141
+ def to_dict(self) -> Dict[str, Any]:
142
+ """Convert to dictionary."""
143
+ return asdict(self)
144
+
145
+ @classmethod
146
+ def from_dict(cls, data: Dict[str, Any]) -> 'ConversationContext':
147
+ """Create from dictionary."""
148
+ return cls(**data)
149
+
150
+
151
+ @dataclass
152
+ class ConversationState:
153
+ """Claude conversation state and context.
154
+
155
+ Captures the current conversation state including active conversations,
156
+ context windows, and file references.
157
+ """
158
+
159
+ # Active conversation
160
+ active_conversation_id: Optional[str]
161
+ active_conversation: Optional[ConversationContext]
162
+
163
+ # Recent conversations (for context)
164
+ recent_conversations: List[ConversationContext]
165
+
166
+ # Global context
167
+ total_conversations: int
168
+ total_storage_mb: float
169
+
170
+ # User preferences preserved
171
+ preferences: Dict[str, Any]
172
+
173
+ # File state
174
+ open_files: List[str]
175
+ recent_files: List[str]
176
+ pinned_files: List[str]
177
+
178
+ def to_dict(self) -> Dict[str, Any]:
179
+ """Convert to dictionary for serialization."""
180
+ return {
181
+ 'active_conversation_id': self.active_conversation_id,
182
+ 'active_conversation': self.active_conversation.to_dict() if self.active_conversation else None,
183
+ 'recent_conversations': [c.to_dict() for c in self.recent_conversations],
184
+ 'total_conversations': self.total_conversations,
185
+ 'total_storage_mb': round(self.total_storage_mb, 2),
186
+ 'preferences': self.preferences,
187
+ 'open_files': self.open_files,
188
+ 'recent_files': self.recent_files,
189
+ 'pinned_files': self.pinned_files
190
+ }
191
+
192
+ @classmethod
193
+ def from_dict(cls, data: Dict[str, Any]) -> 'ConversationState':
194
+ """Create from dictionary."""
195
+ return cls(
196
+ active_conversation_id=data.get('active_conversation_id'),
197
+ active_conversation=(
198
+ ConversationContext.from_dict(data['active_conversation'])
199
+ if data.get('active_conversation') else None
200
+ ),
201
+ recent_conversations=[
202
+ ConversationContext.from_dict(c)
203
+ for c in data.get('recent_conversations', [])
204
+ ],
205
+ total_conversations=data.get('total_conversations', 0),
206
+ total_storage_mb=data.get('total_storage_mb', 0.0),
207
+ preferences=data.get('preferences', {}),
208
+ open_files=data.get('open_files', []),
209
+ recent_files=data.get('recent_files', []),
210
+ pinned_files=data.get('pinned_files', [])
211
+ )
212
+
213
+
214
+ @dataclass
215
+ class ProjectState:
216
+ """Project and Git repository state.
217
+
218
+ Captures the current project context including Git branch,
219
+ modified files, and project metadata.
220
+ """
221
+
222
+ # Project identification
223
+ project_path: str
224
+ project_name: str
225
+
226
+ # Git state
227
+ git_branch: Optional[str]
228
+ git_commit: Optional[str]
229
+ git_status: Dict[str, List[str]] # staged, modified, untracked
230
+ git_remotes: Dict[str, str]
231
+
232
+ # File state
233
+ modified_files: List[str]
234
+ open_editors: List[str]
235
+ breakpoints: Dict[str, List[int]] # file -> line numbers
236
+
237
+ # Project metadata
238
+ project_type: str # python, node, go, etc.
239
+ dependencies: Dict[str, str] # package -> version
240
+ environment_vars: Dict[str, str] # project-specific env vars
241
+
242
+ # Build/test state
243
+ last_build_status: Optional[str]
244
+ last_test_results: Optional[Dict[str, Any]]
245
+
246
+ def to_dict(self) -> Dict[str, Any]:
247
+ """Convert to dictionary for serialization."""
248
+ return {
249
+ 'project_path': self.project_path,
250
+ 'project_name': self.project_name,
251
+ 'git_branch': self.git_branch,
252
+ 'git_commit': self.git_commit,
253
+ 'git_status': self.git_status,
254
+ 'git_remotes': self.git_remotes,
255
+ 'modified_files': self.modified_files,
256
+ 'open_editors': self.open_editors,
257
+ 'breakpoints': self.breakpoints,
258
+ 'project_type': self.project_type,
259
+ 'dependencies': self.dependencies,
260
+ 'environment_vars': ProcessState._sanitize_environment(self.environment_vars),
261
+ 'last_build_status': self.last_build_status,
262
+ 'last_test_results': self.last_test_results
263
+ }
264
+
265
+ @classmethod
266
+ def from_dict(cls, data: Dict[str, Any]) -> 'ProjectState':
267
+ """Create from dictionary."""
268
+ return cls(
269
+ project_path=data['project_path'],
270
+ project_name=data['project_name'],
271
+ git_branch=data.get('git_branch'),
272
+ git_commit=data.get('git_commit'),
273
+ git_status=data.get('git_status', {}),
274
+ git_remotes=data.get('git_remotes', {}),
275
+ modified_files=data.get('modified_files', []),
276
+ open_editors=data.get('open_editors', []),
277
+ breakpoints=data.get('breakpoints', {}),
278
+ project_type=data.get('project_type', 'unknown'),
279
+ dependencies=data.get('dependencies', {}),
280
+ environment_vars=data.get('environment_vars', {}),
281
+ last_build_status=data.get('last_build_status'),
282
+ last_test_results=data.get('last_test_results')
283
+ )
284
+
285
+
286
+ @dataclass
287
+ class RestartState:
288
+ """Information about the restart event.
289
+
290
+ Captures why and when a restart occurred, along with relevant
291
+ metrics at the time of restart.
292
+ """
293
+
294
+ # Restart identification
295
+ restart_id: str
296
+ restart_count: int
297
+
298
+ # Timing
299
+ timestamp: float
300
+ previous_uptime: float
301
+
302
+ # Reason and context
303
+ reason: str
304
+ trigger: str # manual, memory, crash, scheduled
305
+
306
+ # Metrics at restart
307
+ memory_mb: float
308
+ memory_limit_mb: float
309
+ cpu_percent: float
310
+
311
+ # Error information (if applicable)
312
+ error_type: Optional[str]
313
+ error_message: Optional[str]
314
+ error_traceback: Optional[str]
315
+
316
+ # Recovery information
317
+ recovery_attempted: bool
318
+ recovery_successful: bool
319
+ data_preserved: List[str] # Types of data preserved
320
+
321
+ def to_dict(self) -> Dict[str, Any]:
322
+ """Convert to dictionary for serialization."""
323
+ return {
324
+ 'restart_id': self.restart_id,
325
+ 'restart_count': self.restart_count,
326
+ 'timestamp': self.timestamp,
327
+ 'timestamp_iso': datetime.fromtimestamp(self.timestamp).isoformat(),
328
+ 'previous_uptime': self.previous_uptime,
329
+ 'reason': self.reason,
330
+ 'trigger': self.trigger,
331
+ 'memory_mb': round(self.memory_mb, 2),
332
+ 'memory_limit_mb': round(self.memory_limit_mb, 2),
333
+ 'cpu_percent': round(self.cpu_percent, 2),
334
+ 'error_type': self.error_type,
335
+ 'error_message': self.error_message,
336
+ 'error_traceback': self.error_traceback,
337
+ 'recovery_attempted': self.recovery_attempted,
338
+ 'recovery_successful': self.recovery_successful,
339
+ 'data_preserved': self.data_preserved
340
+ }
341
+
342
+ @classmethod
343
+ def from_dict(cls, data: Dict[str, Any]) -> 'RestartState':
344
+ """Create from dictionary."""
345
+ return cls(
346
+ restart_id=data['restart_id'],
347
+ restart_count=data['restart_count'],
348
+ timestamp=data['timestamp'],
349
+ previous_uptime=data['previous_uptime'],
350
+ reason=data['reason'],
351
+ trigger=data['trigger'],
352
+ memory_mb=data['memory_mb'],
353
+ memory_limit_mb=data['memory_limit_mb'],
354
+ cpu_percent=data['cpu_percent'],
355
+ error_type=data.get('error_type'),
356
+ error_message=data.get('error_message'),
357
+ error_traceback=data.get('error_traceback'),
358
+ recovery_attempted=data.get('recovery_attempted', False),
359
+ recovery_successful=data.get('recovery_successful', False),
360
+ data_preserved=data.get('data_preserved', [])
361
+ )
362
+
363
+
364
+ @dataclass
365
+ class CompleteState:
366
+ """Complete state snapshot combining all state components.
367
+
368
+ This is the main state object that gets serialized and restored
369
+ across Claude Code restarts.
370
+ """
371
+
372
+ # State components
373
+ process_state: ProcessState
374
+ conversation_state: ConversationState
375
+ project_state: ProjectState
376
+ restart_state: RestartState
377
+
378
+ # Metadata
379
+ state_version: str = "1.0.0"
380
+ state_id: str = field(default_factory=lambda: datetime.now().strftime("%Y%m%d_%H%M%S"))
381
+ created_at: float = field(default_factory=lambda: datetime.now().timestamp())
382
+
383
+ def to_dict(self) -> Dict[str, Any]:
384
+ """Convert to dictionary for serialization."""
385
+ return {
386
+ 'state_version': self.state_version,
387
+ 'state_id': self.state_id,
388
+ 'created_at': self.created_at,
389
+ 'created_at_iso': datetime.fromtimestamp(self.created_at).isoformat(),
390
+ 'process': self.process_state.to_dict(),
391
+ 'conversation': self.conversation_state.to_dict(),
392
+ 'project': self.project_state.to_dict(),
393
+ 'restart': self.restart_state.to_dict()
394
+ }
395
+
396
+ @classmethod
397
+ def from_dict(cls, data: Dict[str, Any]) -> 'CompleteState':
398
+ """Create from dictionary."""
399
+ return cls(
400
+ state_version=data.get('state_version', '1.0.0'),
401
+ state_id=data.get('state_id'),
402
+ created_at=data.get('created_at'),
403
+ process_state=ProcessState.from_dict(data['process']),
404
+ conversation_state=ConversationState.from_dict(data['conversation']),
405
+ project_state=ProjectState.from_dict(data['project']),
406
+ restart_state=RestartState.from_dict(data['restart'])
407
+ )
408
+
409
+ def validate(self) -> List[str]:
410
+ """Validate state data and return list of issues."""
411
+ issues = []
412
+
413
+ # Check required fields
414
+ if not self.state_id:
415
+ issues.append("State ID is required")
416
+
417
+ if not self.process_state.pid:
418
+ issues.append("Process PID is required")
419
+
420
+ if not self.project_state.project_path:
421
+ issues.append("Project path is required")
422
+
423
+ # Check path validity
424
+ if self.project_state.project_path:
425
+ project_path = Path(self.project_state.project_path)
426
+ if not project_path.exists():
427
+ issues.append(f"Project path does not exist: {project_path}")
428
+
429
+ # Check state version compatibility
430
+ if self.state_version != "1.0.0":
431
+ issues.append(f"Unsupported state version: {self.state_version}")
432
+
433
+ return issues
@@ -13,9 +13,9 @@ Services:
13
13
  """
14
14
 
15
15
  from .socketio import SocketIOServer
16
- from .websocket import SocketIOClientManager
16
+ # from .websocket import SocketIOClientManager # Module has import issues
17
17
 
18
18
  __all__ = [
19
19
  'SocketIOServer',
20
- 'SocketIOClientManager',
20
+ # 'SocketIOClientManager',
21
21
  ]
@@ -31,17 +31,17 @@ except ImportError:
31
31
  web = None
32
32
  # Don't print warnings at module level
33
33
 
34
- from ..core.logging_config import get_logger, log_operation, log_performance_context
35
- from ..deployment_paths import get_project_root, get_scripts_dir
36
- from .socketio.handlers import EventHandlerRegistry, FileEventHandler, GitEventHandler
37
- from ..core.constants import (
34
+ from claude_mpm.core.logging_config import get_logger, log_operation, log_performance_context
35
+ from claude_mpm.deployment_paths import get_project_root, get_scripts_dir
36
+ # from .socketio.handlers import EventHandlerRegistry, FileEventHandler, GitEventHandler # Module not found, commenting out
37
+ from claude_mpm.core.constants import (
38
38
  SystemLimits,
39
39
  NetworkConfig,
40
40
  TimeoutConfig,
41
41
  PerformanceConfig
42
42
  )
43
- from ..core.interfaces import SocketIOServiceInterface
44
- from ..exceptions import MPMConnectionError
43
+ from claude_mpm.core.interfaces import SocketIOServiceInterface
44
+ from claude_mpm.services.exceptions import SocketIOServerError as MPMConnectionError
45
45
 
46
46
 
47
47
  class SocketIOClientProxy:
@@ -1030,24 +1030,26 @@ class SocketIOServer(SocketIOServiceInterface):
1030
1030
  handlers in a modular way. Each handler focuses on a specific domain,
1031
1031
  reducing complexity and improving maintainability.
1032
1032
  """
1033
- # Initialize the event handler registry
1034
- self.event_registry = EventHandlerRegistry(self)
1035
- self.event_registry.initialize()
1033
+ # Handler registry not available - skip handler-based registration
1034
+ # # Initialize the event handler registry
1035
+ # self.event_registry = EventHandlerRegistry(self)
1036
+ # self.event_registry.initialize()
1036
1037
 
1037
- # Register all events from all handlers
1038
- self.event_registry.register_all_events()
1038
+ # # Register all events from all handlers
1039
+ # self.event_registry.register_all_events()
1039
1040
 
1040
- # Keep handler instances for HTTP endpoint compatibility
1041
- self.file_handler = self.event_registry.get_handler(FileEventHandler)
1042
- self.git_handler = self.event_registry.get_handler(GitEventHandler)
1041
+ # # Keep handler instances for HTTP endpoint compatibility
1042
+ # self.file_handler = self.event_registry.get_handler(FileEventHandler)
1043
+ # self.git_handler = self.event_registry.get_handler(GitEventHandler)
1043
1044
 
1044
- self.logger.info("All Socket.IO events registered via handler system")
1045
+ # self.logger.info("All Socket.IO events registered via handler system")
1045
1046
 
1046
1047
  # Note: The actual event registration is now handled by individual
1047
1048
  # handler classes in socketio/handlers/. This dramatically reduces
1048
1049
  # the complexity of this method from 514 lines to under 20 lines.
1049
1050
 
1050
- return # Early return to skip old implementation
1051
+ # Continue with old implementation since handlers are not available
1052
+ # return # Early return to skip old implementation
1051
1053
 
1052
1054
  @self.sio.event
1053
1055
  async def connect(sid, environ, *args):
@@ -10,12 +10,15 @@ Part of TSK-0046: Service Layer Architecture Reorganization
10
10
  Services:
11
11
  - LoggingService: Centralized logging with structured output
12
12
  - HealthMonitor: System health monitoring and alerting
13
+ - MemoryGuardian: Memory monitoring and process restart management
13
14
  """
14
15
 
15
16
  from .logging import LoggingService
16
- from .monitoring import HealthMonitor
17
+ from .monitoring import AdvancedHealthMonitor as HealthMonitor
18
+ from .memory_guardian import MemoryGuardian
17
19
 
18
20
  __all__ = [
19
21
  'LoggingService',
20
22
  'HealthMonitor',
23
+ 'MemoryGuardian',
21
24
  ]
@@ -13,11 +13,11 @@ import logging
13
13
  import json
14
14
  from datetime import datetime
15
15
 
16
- from claude_mpm.services.core import SyncBaseService, ILoggingService
17
- from claude_mpm.utils.logger import get_logger
16
+ from claude_mpm.services.core import SyncBaseService, IStructuredLogger
17
+ from claude_mpm.core.logger import get_logger
18
18
 
19
19
 
20
- class LoggingService(SyncBaseService, ILoggingService):
20
+ class LoggingService(SyncBaseService, IStructuredLogger):
21
21
  """
22
22
  Centralized logging service for the Claude MPM framework.
23
23