claude-mpm 3.7.8__py3-none-any.whl → 3.8.1__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 (93) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -96
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/templates/code_analyzer.json +2 -2
  7. claude_mpm/agents/templates/data_engineer.json +1 -1
  8. claude_mpm/agents/templates/documentation.json +1 -1
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/ops.json +1 -1
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/agents/templates/security.json +1 -1
  14. claude_mpm/agents/templates/ticketing.json +2 -7
  15. claude_mpm/agents/templates/version_control.json +1 -1
  16. claude_mpm/agents/templates/web_qa.json +2 -2
  17. claude_mpm/agents/templates/web_ui.json +2 -2
  18. claude_mpm/cli/__init__.py +2 -2
  19. claude_mpm/cli/commands/__init__.py +2 -1
  20. claude_mpm/cli/commands/tickets.py +596 -19
  21. claude_mpm/cli/parser.py +217 -5
  22. claude_mpm/config/__init__.py +30 -39
  23. claude_mpm/config/socketio_config.py +8 -5
  24. claude_mpm/constants.py +13 -0
  25. claude_mpm/core/__init__.py +8 -18
  26. claude_mpm/core/cache.py +596 -0
  27. claude_mpm/core/claude_runner.py +166 -622
  28. claude_mpm/core/config.py +5 -1
  29. claude_mpm/core/constants.py +339 -0
  30. claude_mpm/core/container.py +461 -22
  31. claude_mpm/core/exceptions.py +392 -0
  32. claude_mpm/core/framework_loader.py +208 -94
  33. claude_mpm/core/interactive_session.py +432 -0
  34. claude_mpm/core/interfaces.py +424 -0
  35. claude_mpm/core/lazy.py +467 -0
  36. claude_mpm/core/logging_config.py +444 -0
  37. claude_mpm/core/oneshot_session.py +465 -0
  38. claude_mpm/core/optimized_agent_loader.py +485 -0
  39. claude_mpm/core/optimized_startup.py +490 -0
  40. claude_mpm/core/service_registry.py +52 -26
  41. claude_mpm/core/socketio_pool.py +162 -5
  42. claude_mpm/core/types.py +292 -0
  43. claude_mpm/core/typing_utils.py +477 -0
  44. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  45. claude_mpm/init.py +2 -1
  46. claude_mpm/services/__init__.py +78 -14
  47. claude_mpm/services/agent/__init__.py +24 -0
  48. claude_mpm/services/agent/deployment.py +2548 -0
  49. claude_mpm/services/agent/management.py +598 -0
  50. claude_mpm/services/agent/registry.py +813 -0
  51. claude_mpm/services/agents/deployment/agent_deployment.py +587 -268
  52. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  53. claude_mpm/services/async_session_logger.py +8 -3
  54. claude_mpm/services/communication/__init__.py +21 -0
  55. claude_mpm/services/communication/socketio.py +1933 -0
  56. claude_mpm/services/communication/websocket.py +479 -0
  57. claude_mpm/services/core/__init__.py +123 -0
  58. claude_mpm/services/core/base.py +247 -0
  59. claude_mpm/services/core/interfaces.py +951 -0
  60. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  61. claude_mpm/services/framework_claude_md_generator.py +3 -2
  62. claude_mpm/services/health_monitor.py +4 -3
  63. claude_mpm/services/hook_service.py +64 -4
  64. claude_mpm/services/infrastructure/__init__.py +21 -0
  65. claude_mpm/services/infrastructure/logging.py +202 -0
  66. claude_mpm/services/infrastructure/monitoring.py +893 -0
  67. claude_mpm/services/memory/indexed_memory.py +648 -0
  68. claude_mpm/services/project/__init__.py +21 -0
  69. claude_mpm/services/project/analyzer.py +864 -0
  70. claude_mpm/services/project/registry.py +608 -0
  71. claude_mpm/services/project_analyzer.py +95 -2
  72. claude_mpm/services/recovery_manager.py +15 -9
  73. claude_mpm/services/socketio/__init__.py +25 -0
  74. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  75. claude_mpm/services/socketio/handlers/base.py +121 -0
  76. claude_mpm/services/socketio/handlers/connection.py +198 -0
  77. claude_mpm/services/socketio/handlers/file.py +213 -0
  78. claude_mpm/services/socketio/handlers/git.py +723 -0
  79. claude_mpm/services/socketio/handlers/memory.py +27 -0
  80. claude_mpm/services/socketio/handlers/project.py +25 -0
  81. claude_mpm/services/socketio/handlers/registry.py +145 -0
  82. claude_mpm/services/socketio_client_manager.py +12 -7
  83. claude_mpm/services/socketio_server.py +156 -30
  84. claude_mpm/services/ticket_manager.py +170 -7
  85. claude_mpm/utils/error_handler.py +1 -1
  86. claude_mpm/validation/agent_validator.py +27 -14
  87. claude_mpm/validation/frontmatter_validator.py +231 -0
  88. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +58 -21
  89. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +93 -53
  90. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  91. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  92. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  93. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,213 @@
1
+ """File operation event handlers for Socket.IO.
2
+
3
+ WHY: This module handles file-related events including reading file content
4
+ safely with security checks. Separating file operations improves security
5
+ auditing and makes it easier to add file-related features.
6
+ """
7
+
8
+ import os
9
+ from typing import Optional, Dict, Any
10
+ from pathlib import Path
11
+
12
+ from .base import BaseEventHandler
13
+ from ....deployment_paths import get_project_root
14
+ from ....core.typing_utils import SocketId, EventData, PathLike
15
+
16
+
17
+ class FileEventHandler(BaseEventHandler):
18
+ """Handles file operation Socket.IO events.
19
+
20
+ WHY: File operations require careful security considerations and
21
+ consistent error handling. Having a dedicated handler ensures
22
+ all file operations follow the same security patterns.
23
+ """
24
+
25
+ def register_events(self) -> None:
26
+ """Register file operation event handlers."""
27
+
28
+ @self.sio.event
29
+ async def read_file(sid, data):
30
+ """Read file contents safely.
31
+
32
+ WHY: The dashboard needs to display file contents when users
33
+ click on files, but we must ensure secure file access with
34
+ proper validation and size limits.
35
+ """
36
+ try:
37
+ file_path = data.get('file_path')
38
+ working_dir = data.get('working_dir', os.getcwd())
39
+ max_size = data.get('max_size', 1024 * 1024) # 1MB default limit
40
+
41
+ if not file_path:
42
+ await self.emit_to_client(sid, 'file_content_response', {
43
+ 'success': False,
44
+ 'error': 'file_path is required',
45
+ 'file_path': file_path
46
+ })
47
+ return
48
+
49
+ # Use the shared file reading logic
50
+ result = await self._read_file_safely(file_path, working_dir, max_size)
51
+
52
+ # Send the result back to the client
53
+ await self.emit_to_client(sid, 'file_content_response', result)
54
+
55
+ except Exception as e:
56
+ self.log_error("read_file", e, data)
57
+ await self.emit_to_client(sid, 'file_content_response', {
58
+ 'success': False,
59
+ 'error': str(e),
60
+ 'file_path': data.get('file_path', 'unknown')
61
+ })
62
+
63
+ async def _read_file_safely(self, file_path: str, working_dir: Optional[str] = None, max_size: int = 1024 * 1024) -> EventData:
64
+ """Safely read file content with security checks.
65
+
66
+ WHY: File reading must be secure to prevent directory traversal attacks
67
+ and resource exhaustion. This method centralizes all security checks
68
+ and provides consistent error handling.
69
+
70
+ Args:
71
+ file_path: Path to the file to read
72
+ working_dir: Working directory (defaults to current directory)
73
+ max_size: Maximum file size in bytes
74
+
75
+ Returns:
76
+ dict: Response with success status, content, and metadata
77
+ """
78
+ try:
79
+ if working_dir is None:
80
+ working_dir = os.getcwd()
81
+
82
+ # Resolve absolute path based on working directory
83
+ if not os.path.isabs(file_path):
84
+ full_path = os.path.join(working_dir, file_path)
85
+ else:
86
+ full_path = file_path
87
+
88
+ # Security check: ensure file is within working directory or project
89
+ try:
90
+ real_path = os.path.realpath(full_path)
91
+ real_working_dir = os.path.realpath(working_dir)
92
+
93
+ # Allow access to files within working directory or the project root
94
+ project_root = os.path.realpath(get_project_root())
95
+ allowed_paths = [real_working_dir, project_root]
96
+
97
+ is_allowed = any(real_path.startswith(allowed_path) for allowed_path in allowed_paths)
98
+
99
+ if not is_allowed:
100
+ return {
101
+ 'success': False,
102
+ 'error': 'Access denied: file is outside allowed directories',
103
+ 'file_path': file_path
104
+ }
105
+
106
+ except Exception as path_error:
107
+ self.logger.error(f"Path validation error: {path_error}")
108
+ return {
109
+ 'success': False,
110
+ 'error': 'Invalid file path',
111
+ 'file_path': file_path
112
+ }
113
+
114
+ # Check if file exists
115
+ if not os.path.exists(real_path):
116
+ return {
117
+ 'success': False,
118
+ 'error': 'File does not exist',
119
+ 'file_path': file_path
120
+ }
121
+
122
+ # Check if it's a file (not directory)
123
+ if not os.path.isfile(real_path):
124
+ return {
125
+ 'success': False,
126
+ 'error': 'Path is not a file',
127
+ 'file_path': file_path
128
+ }
129
+
130
+ # Check file size
131
+ file_size = os.path.getsize(real_path)
132
+ if file_size > max_size:
133
+ return {
134
+ 'success': False,
135
+ 'error': f'File too large ({file_size} bytes). Maximum allowed: {max_size} bytes',
136
+ 'file_path': file_path,
137
+ 'file_size': file_size
138
+ }
139
+
140
+ # Read file content
141
+ try:
142
+ with open(real_path, 'r', encoding='utf-8') as f:
143
+ content = f.read()
144
+
145
+ # Get file extension for syntax highlighting hint
146
+ _, ext = os.path.splitext(real_path)
147
+
148
+ return {
149
+ 'success': True,
150
+ 'file_path': file_path,
151
+ 'content': content,
152
+ 'file_size': file_size,
153
+ 'extension': ext.lower(),
154
+ 'encoding': 'utf-8'
155
+ }
156
+
157
+ except UnicodeDecodeError:
158
+ # Try reading as binary if UTF-8 fails
159
+ return self._read_binary_file(real_path, file_path, file_size)
160
+
161
+ except Exception as e:
162
+ self.logger.error(f"Error in _read_file_safely: {e}")
163
+ return {
164
+ 'success': False,
165
+ 'error': str(e),
166
+ 'file_path': file_path
167
+ }
168
+
169
+ def _read_binary_file(self, real_path: str, file_path: str, file_size: int) -> Dict[str, Any]:
170
+ """Handle binary or non-UTF8 files.
171
+
172
+ WHY: Not all files are UTF-8 encoded. We need to handle other
173
+ encodings gracefully and detect binary files that shouldn't
174
+ be displayed as text.
175
+ """
176
+ try:
177
+ with open(real_path, 'rb') as f:
178
+ binary_content = f.read()
179
+
180
+ # Check if it's a text file by looking for common text patterns
181
+ try:
182
+ text_content = binary_content.decode('latin-1')
183
+ if '\x00' in text_content:
184
+ # Binary file
185
+ return {
186
+ 'success': False,
187
+ 'error': 'File appears to be binary and cannot be displayed as text',
188
+ 'file_path': file_path,
189
+ 'file_size': file_size
190
+ }
191
+ else:
192
+ # Text file with different encoding
193
+ _, ext = os.path.splitext(real_path)
194
+ return {
195
+ 'success': True,
196
+ 'file_path': file_path,
197
+ 'content': text_content,
198
+ 'file_size': file_size,
199
+ 'extension': ext.lower(),
200
+ 'encoding': 'latin-1'
201
+ }
202
+ except Exception:
203
+ return {
204
+ 'success': False,
205
+ 'error': 'File encoding not supported',
206
+ 'file_path': file_path
207
+ }
208
+ except Exception as read_error:
209
+ return {
210
+ 'success': False,
211
+ 'error': f'Failed to read file: {str(read_error)}',
212
+ 'file_path': file_path
213
+ }