claude-mpm 3.7.4__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 (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -78
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/schema/agent_schema.json +1 -1
  7. claude_mpm/agents/templates/code_analyzer.json +26 -11
  8. claude_mpm/agents/templates/data_engineer.json +4 -7
  9. claude_mpm/agents/templates/documentation.json +2 -2
  10. claude_mpm/agents/templates/engineer.json +2 -2
  11. claude_mpm/agents/templates/ops.json +3 -8
  12. claude_mpm/agents/templates/qa.json +2 -3
  13. claude_mpm/agents/templates/research.json +2 -3
  14. claude_mpm/agents/templates/security.json +3 -6
  15. claude_mpm/agents/templates/ticketing.json +4 -9
  16. claude_mpm/agents/templates/version_control.json +3 -3
  17. claude_mpm/agents/templates/web_qa.json +4 -4
  18. claude_mpm/agents/templates/web_ui.json +4 -4
  19. claude_mpm/cli/__init__.py +2 -2
  20. claude_mpm/cli/commands/__init__.py +2 -1
  21. claude_mpm/cli/commands/agents.py +118 -1
  22. claude_mpm/cli/commands/tickets.py +596 -19
  23. claude_mpm/cli/parser.py +228 -5
  24. claude_mpm/config/__init__.py +30 -39
  25. claude_mpm/config/socketio_config.py +8 -5
  26. claude_mpm/constants.py +13 -0
  27. claude_mpm/core/__init__.py +8 -18
  28. claude_mpm/core/cache.py +596 -0
  29. claude_mpm/core/claude_runner.py +166 -622
  30. claude_mpm/core/config.py +5 -1
  31. claude_mpm/core/constants.py +339 -0
  32. claude_mpm/core/container.py +461 -22
  33. claude_mpm/core/exceptions.py +392 -0
  34. claude_mpm/core/framework_loader.py +208 -93
  35. claude_mpm/core/interactive_session.py +432 -0
  36. claude_mpm/core/interfaces.py +424 -0
  37. claude_mpm/core/lazy.py +467 -0
  38. claude_mpm/core/logging_config.py +444 -0
  39. claude_mpm/core/oneshot_session.py +465 -0
  40. claude_mpm/core/optimized_agent_loader.py +485 -0
  41. claude_mpm/core/optimized_startup.py +490 -0
  42. claude_mpm/core/service_registry.py +52 -26
  43. claude_mpm/core/socketio_pool.py +162 -5
  44. claude_mpm/core/types.py +292 -0
  45. claude_mpm/core/typing_utils.py +477 -0
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  47. claude_mpm/dashboard/templates/index.html +5 -5
  48. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  49. claude_mpm/init.py +2 -1
  50. claude_mpm/services/__init__.py +78 -14
  51. claude_mpm/services/agent/__init__.py +24 -0
  52. claude_mpm/services/agent/deployment.py +2548 -0
  53. claude_mpm/services/agent/management.py +598 -0
  54. claude_mpm/services/agent/registry.py +813 -0
  55. claude_mpm/services/agents/deployment/agent_deployment.py +592 -269
  56. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  57. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  58. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  59. claude_mpm/services/async_session_logger.py +8 -3
  60. claude_mpm/services/communication/__init__.py +21 -0
  61. claude_mpm/services/communication/socketio.py +1933 -0
  62. claude_mpm/services/communication/websocket.py +479 -0
  63. claude_mpm/services/core/__init__.py +123 -0
  64. claude_mpm/services/core/base.py +247 -0
  65. claude_mpm/services/core/interfaces.py +951 -0
  66. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  67. claude_mpm/services/framework_claude_md_generator.py +3 -2
  68. claude_mpm/services/health_monitor.py +4 -3
  69. claude_mpm/services/hook_service.py +64 -4
  70. claude_mpm/services/infrastructure/__init__.py +21 -0
  71. claude_mpm/services/infrastructure/logging.py +202 -0
  72. claude_mpm/services/infrastructure/monitoring.py +893 -0
  73. claude_mpm/services/memory/indexed_memory.py +648 -0
  74. claude_mpm/services/project/__init__.py +21 -0
  75. claude_mpm/services/project/analyzer.py +864 -0
  76. claude_mpm/services/project/registry.py +608 -0
  77. claude_mpm/services/project_analyzer.py +95 -2
  78. claude_mpm/services/recovery_manager.py +15 -9
  79. claude_mpm/services/socketio/__init__.py +25 -0
  80. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  81. claude_mpm/services/socketio/handlers/base.py +121 -0
  82. claude_mpm/services/socketio/handlers/connection.py +198 -0
  83. claude_mpm/services/socketio/handlers/file.py +213 -0
  84. claude_mpm/services/socketio/handlers/git.py +723 -0
  85. claude_mpm/services/socketio/handlers/memory.py +27 -0
  86. claude_mpm/services/socketio/handlers/project.py +25 -0
  87. claude_mpm/services/socketio/handlers/registry.py +145 -0
  88. claude_mpm/services/socketio_client_manager.py +12 -7
  89. claude_mpm/services/socketio_server.py +156 -30
  90. claude_mpm/services/ticket_manager.py +377 -51
  91. claude_mpm/utils/agent_dependency_loader.py +66 -15
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/utils/robust_installer.py +587 -0
  94. claude_mpm/validation/agent_validator.py +27 -14
  95. claude_mpm/validation/frontmatter_validator.py +231 -0
  96. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
  97. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
  98. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  99. claude_mpm/agents/agent-template.yaml +0 -83
  100. claude_mpm/cli/README.md +0 -108
  101. claude_mpm/cli_module/refactoring_guide.md +0 -253
  102. claude_mpm/config/async_logging_config.yaml +0 -145
  103. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  104. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  105. claude_mpm/dashboard/README.md +0 -121
  106. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  107. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  108. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  109. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  110. claude_mpm/hooks/README.md +0 -96
  111. claude_mpm/schemas/agent_schema.json +0 -435
  112. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  113. claude_mpm/services/version_control/VERSION +0 -1
  114. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  115. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  116. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  117. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,27 @@
1
+ """Memory-related event handlers for Socket.IO.
2
+
3
+ WHY: This module handles agent memory events. Currently the memory
4
+ events are broadcast methods rather than Socket.IO events, but this
5
+ provides a place for future memory management features.
6
+ """
7
+
8
+ from .base import BaseEventHandler
9
+
10
+
11
+ class MemoryEventHandler(BaseEventHandler):
12
+ """Handles memory-related Socket.IO events.
13
+
14
+ WHY: Agent memory management events will be handled here as the system
15
+ grows. This provides a clean separation for memory-specific functionality.
16
+ """
17
+
18
+ def register_events(self):
19
+ """Register memory-related event handlers.
20
+
21
+ Currently memory events are handled through broadcast methods
22
+ rather than Socket.IO event handlers, but this provides a place
23
+ for future interactive memory management features.
24
+ """
25
+ # Future memory management events will be registered here
26
+ # For example: query_memory, clear_memory, export_memory, etc.
27
+ pass
@@ -0,0 +1,25 @@
1
+ """Project-related event handlers for Socket.IO.
2
+
3
+ WHY: This module handles project-specific events that don't fit into
4
+ other categories. Currently empty but provides a place for future
5
+ project management features.
6
+ """
7
+
8
+ from .base import BaseEventHandler
9
+
10
+
11
+ class ProjectEventHandler(BaseEventHandler):
12
+ """Handles project-related Socket.IO events.
13
+
14
+ WHY: Project management events will be handled here as the system
15
+ grows. This provides a clean separation for project-specific functionality.
16
+ """
17
+
18
+ def register_events(self):
19
+ """Register project-related event handlers.
20
+
21
+ Currently no project-specific events are defined, but this
22
+ handler is ready for future expansion.
23
+ """
24
+ # Future project events will be registered here
25
+ pass
@@ -0,0 +1,145 @@
1
+ """Event handler registry for Socket.IO server.
2
+
3
+ WHY: This registry manages the registration of all event handlers,
4
+ providing a clean interface for the SocketIOServer to register all
5
+ events without knowing the details of each handler.
6
+ """
7
+
8
+ from typing import List, Type, Optional, TYPE_CHECKING
9
+ from logging import Logger
10
+ from ....core.logger import get_logger
11
+
12
+ from .base import BaseEventHandler
13
+
14
+ if TYPE_CHECKING:
15
+ from ..server import SocketIOServer
16
+ from .connection import ConnectionEventHandler
17
+ from .project import ProjectEventHandler
18
+ from .memory import MemoryEventHandler
19
+ from .file import FileEventHandler
20
+ from .git import GitEventHandler
21
+
22
+
23
+ class EventHandlerRegistry:
24
+ """Manages registration of Socket.IO event handlers.
25
+
26
+ WHY: The registry pattern allows us to easily add, remove, or modify
27
+ event handlers without changing the SocketIOServer implementation.
28
+ It provides a single point of configuration for all event handlers.
29
+ """
30
+
31
+ # Default handler classes in registration order
32
+ DEFAULT_HANDLERS: List[Type[BaseEventHandler]] = [
33
+ ConnectionEventHandler, # Connection management first
34
+ GitEventHandler, # Git operations
35
+ FileEventHandler, # File operations
36
+ ProjectEventHandler, # Project management (future)
37
+ MemoryEventHandler, # Memory management (future)
38
+ ]
39
+
40
+ def __init__(self, server: 'SocketIOServer') -> None:
41
+ """Initialize the registry.
42
+
43
+ Args:
44
+ server: The SocketIOServer instance
45
+ """
46
+ self.server: 'SocketIOServer' = server
47
+ self.logger: Logger = get_logger("EventHandlerRegistry")
48
+ self.handlers: List[BaseEventHandler] = []
49
+ self._initialized: bool = False
50
+
51
+ def initialize(self, handler_classes: Optional[List[Type[BaseEventHandler]]] = None) -> None:
52
+ """Initialize all event handlers.
53
+
54
+ WHY: This creates instances of all handler classes and prepares
55
+ them for event registration. Using a list of classes allows
56
+ customization of which handlers to use.
57
+
58
+ Args:
59
+ handler_classes: Optional list of handler classes to use.
60
+ Defaults to DEFAULT_HANDLERS if not provided.
61
+ """
62
+ if self._initialized:
63
+ self.logger.warning("Registry already initialized, skipping re-initialization")
64
+ return
65
+
66
+ handler_classes = handler_classes or self.DEFAULT_HANDLERS
67
+
68
+ for handler_class in handler_classes:
69
+ try:
70
+ handler = handler_class(self.server)
71
+ self.handlers.append(handler)
72
+ self.logger.info(f"Initialized handler: {handler_class.__name__}")
73
+ except Exception as e:
74
+ self.logger.error(f"Failed to initialize {handler_class.__name__}: {e}")
75
+ import traceback
76
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
77
+
78
+ self._initialized = True
79
+ self.logger.info(f"Registry initialized with {len(self.handlers)} handlers")
80
+
81
+ def register_all_events(self) -> None:
82
+ """Register all events from all handlers.
83
+
84
+ WHY: This is the main method called by SocketIOServer to register
85
+ all events. It delegates to each handler's register_events method,
86
+ keeping the server code clean and simple.
87
+ """
88
+ if not self._initialized:
89
+ self.logger.error("Registry not initialized. Call initialize() first.")
90
+ raise RuntimeError("EventHandlerRegistry not initialized")
91
+
92
+ registered_count = 0
93
+ for handler in self.handlers:
94
+ try:
95
+ handler.register_events()
96
+ registered_count += 1
97
+ self.logger.info(f"Registered events for {handler.__class__.__name__}")
98
+ except NotImplementedError:
99
+ # Handler has no events to register (like ProjectEventHandler)
100
+ self.logger.debug(f"No events to register for {handler.__class__.__name__}")
101
+ except Exception as e:
102
+ self.logger.error(f"Failed to register events for {handler.__class__.__name__}: {e}")
103
+ import traceback
104
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
105
+
106
+ self.logger.info(f"Successfully registered events from {registered_count} handlers")
107
+
108
+ def add_handler(self, handler_class: Type[BaseEventHandler]):
109
+ """Add a custom handler to the registry.
110
+
111
+ WHY: Allows dynamic addition of custom handlers for specific
112
+ deployments or testing without modifying the default handlers.
113
+
114
+ Args:
115
+ handler_class: The handler class to add
116
+ """
117
+ if not self._initialized:
118
+ self.logger.error("Registry not initialized. Call initialize() first.")
119
+ raise RuntimeError("EventHandlerRegistry not initialized")
120
+
121
+ try:
122
+ handler = handler_class(self.server)
123
+ self.handlers.append(handler)
124
+ handler.register_events()
125
+ self.logger.info(f"Added and registered handler: {handler_class.__name__}")
126
+ except Exception as e:
127
+ self.logger.error(f"Failed to add handler {handler_class.__name__}: {e}")
128
+ raise
129
+
130
+ def get_handler(self, handler_class: Type[BaseEventHandler]) -> BaseEventHandler:
131
+ """Get a specific handler instance by class.
132
+
133
+ WHY: Useful for testing or when specific handler functionality
134
+ needs to be accessed directly.
135
+
136
+ Args:
137
+ handler_class: The handler class to find
138
+
139
+ Returns:
140
+ The handler instance or None if not found
141
+ """
142
+ for handler in self.handlers:
143
+ if isinstance(handler, handler_class):
144
+ return handler
145
+ return None
@@ -23,6 +23,11 @@ import time
23
23
  from datetime import datetime
24
24
  from typing import Dict, Any, Optional, List, Tuple
25
25
  import importlib.metadata
26
+ from claude_mpm.core.constants import (
27
+ NetworkConfig,
28
+ TimeoutConfig,
29
+ PerformanceConfig
30
+ )
26
31
 
27
32
  try:
28
33
  import requests
@@ -129,7 +134,7 @@ class SocketIOClientManager:
129
134
  try:
130
135
  response = requests.get(
131
136
  f"http://{host}:{port}/version",
132
- timeout=2.0
137
+ timeout=TimeoutConfig.QUICK_TIMEOUT
133
138
  )
134
139
  if response.status_code == 200:
135
140
  data = response.json()
@@ -177,9 +182,9 @@ class SocketIOClientManager:
177
182
  # Version scoring (basic semantic versioning)
178
183
  try:
179
184
  version_parts = server.server_version.split('.')
180
- score += int(version_parts[0]) * 1000 # Major version
181
- score += int(version_parts[1]) * 100 # Minor version
182
- score += int(version_parts[2]) * 10 # Patch version
185
+ score += int(version_parts[0]) * PerformanceConfig.VERSION_SCORE_MAJOR
186
+ score += int(version_parts[1]) * PerformanceConfig.VERSION_SCORE_MINOR
187
+ score += int(version_parts[2]) * PerformanceConfig.VERSION_SCORE_PATCH
183
188
  except (ValueError, IndexError):
184
189
  pass
185
190
 
@@ -211,7 +216,7 @@ class SocketIOClientManager:
211
216
  compat_response = requests.post(
212
217
  f"{server_info.url}/compatibility",
213
218
  json={"client_version": self.client_version},
214
- timeout=5.0
219
+ timeout=TimeoutConfig.FILE_OPERATION_TIMEOUT
215
220
  )
216
221
 
217
222
  if compat_response.status_code == 200:
@@ -224,7 +229,7 @@ class SocketIOClientManager:
224
229
  self.client = socketio.AsyncClient(
225
230
  reconnection=True,
226
231
  reconnection_attempts=0, # Infinite
227
- reconnection_delay=1,
232
+ reconnection_delay=NetworkConfig.RECONNECTION_DELAY,
228
233
  reconnection_delay_max=5,
229
234
  randomization_factor=0.5,
230
235
  logger=False,
@@ -328,7 +333,7 @@ class SocketIOClientManager:
328
333
  self.running = False
329
334
 
330
335
  if self.connection_thread:
331
- self.connection_thread.join(timeout=5)
336
+ self.connection_thread.join(timeout=TimeoutConfig.THREAD_JOIN_TIMEOUT)
332
337
 
333
338
  if self.client and self.connected:
334
339
  try:
@@ -31,8 +31,17 @@ except ImportError:
31
31
  web = None
32
32
  # Don't print warnings at module level
33
33
 
34
- from ..core.logger import get_logger
34
+ from ..core.logging_config import get_logger, log_operation, log_performance_context
35
35
  from ..deployment_paths import get_project_root, get_scripts_dir
36
+ from .socketio.handlers import EventHandlerRegistry, FileEventHandler, GitEventHandler
37
+ from ..core.constants import (
38
+ SystemLimits,
39
+ NetworkConfig,
40
+ TimeoutConfig,
41
+ PerformanceConfig
42
+ )
43
+ from ..core.interfaces import SocketIOServiceInterface
44
+ from ..exceptions import MPMConnectionError
36
45
 
37
46
 
38
47
  class SocketIOClientProxy:
@@ -47,19 +56,19 @@ class SocketIOClientProxy:
47
56
  def __init__(self, host: str = "localhost", port: int = 8765):
48
57
  self.host = host
49
58
  self.port = port
50
- self.logger = get_logger("socketio_client_proxy")
59
+ self.logger = get_logger(__name__ + ".SocketIOClientProxy")
51
60
  self.running = True # Always "running" for compatibility
52
61
  self._sio_client = None
53
62
  self._client_thread = None
54
63
  self._client_loop = None
55
64
 
56
- def start(self):
65
+ def start_sync(self):
57
66
  """Start the Socket.IO client connection to the persistent server."""
58
67
  self.logger.debug(f"SocketIOClientProxy: Connecting to server on {self.host}:{self.port}")
59
68
  if SOCKETIO_AVAILABLE:
60
69
  self._start_client()
61
70
 
62
- def stop(self):
71
+ def stop_sync(self):
63
72
  """Stop the Socket.IO client connection."""
64
73
  self.logger.debug(f"SocketIOClientProxy: Disconnecting from server")
65
74
  if self._sio_client:
@@ -161,7 +170,7 @@ class SocketIOClientProxy:
161
170
  self.logger.debug(f"SocketIOClientProxy: Todo updated ({len(todos)} todos)")
162
171
 
163
172
 
164
- class SocketIOServer:
173
+ class SocketIOServer(SocketIOServiceInterface):
165
174
  """Socket.IO server for broadcasting Claude MPM events.
166
175
 
167
176
  WHY: Socket.IO provides better connection reliability than raw WebSockets,
@@ -172,9 +181,9 @@ class SocketIOServer:
172
181
  def __init__(self, host: str = "localhost", port: int = 8765):
173
182
  self.host = host
174
183
  self.port = port
175
- self.logger = get_logger("socketio_server")
184
+ self.logger = get_logger(__name__)
176
185
  self.clients: Set[str] = set() # Store session IDs instead of connection objects
177
- self.event_history: deque = deque(maxlen=1000) # Keep last 1000 events
186
+ self.event_history: deque = deque(maxlen=SystemLimits.MAX_EVENT_HISTORY)
178
187
  self.sio = None
179
188
  self.app = None
180
189
  self.runner = None
@@ -199,8 +208,8 @@ class SocketIOServer:
199
208
  except:
200
209
  self.logger.info("Socket.IO server using python-socketio (version unavailable)")
201
210
 
202
- def start(self):
203
- """Start the Socket.IO server in a background thread."""
211
+ def start_sync(self):
212
+ """Start the Socket.IO server in a background thread (synchronous version)."""
204
213
  if not SOCKETIO_AVAILABLE:
205
214
  self.logger.debug("Socket.IO server skipped - required packages not installed")
206
215
  return
@@ -223,13 +232,13 @@ class SocketIOServer:
223
232
  else:
224
233
  self.logger.error(f"❌ Socket.IO server thread failed to start!")
225
234
 
226
- def stop(self):
227
- """Stop the Socket.IO server."""
235
+ def stop_sync(self):
236
+ """Stop the Socket.IO server (synchronous version)."""
228
237
  self.running = False
229
238
  if self.loop:
230
239
  asyncio.run_coroutine_threadsafe(self._shutdown(), self.loop)
231
240
  if self.thread:
232
- self.thread.join(timeout=5)
241
+ self.thread.join(timeout=TimeoutConfig.THREAD_JOIN_TIMEOUT)
233
242
  self.logger.info("Socket.IO server stopped")
234
243
 
235
244
  def _run_server(self):
@@ -258,8 +267,8 @@ class SocketIOServer:
258
267
  # Create Socket.IO server with improved configuration
259
268
  self.sio = socketio.AsyncServer(
260
269
  cors_allowed_origins="*",
261
- ping_timeout=120,
262
- ping_interval=30,
270
+ ping_timeout=NetworkConfig.PING_TIMEOUT,
271
+ ping_interval=NetworkConfig.PING_INTERVAL,
263
272
  max_http_buffer_size=1000000,
264
273
  allow_upgrades=True,
265
274
  transports=['websocket', 'polling'],
@@ -310,7 +319,16 @@ class SocketIOServer:
310
319
  await self.runner.setup()
311
320
 
312
321
  self.site = web.TCPSite(self.runner, self.host, self.port)
313
- await self.site.start()
322
+ try:
323
+ await self.site.start()
324
+ except OSError as e:
325
+ if "Address already in use" in str(e) or "address already in use" in str(e).lower():
326
+ raise MPMConnectionError(
327
+ f"Port {self.port} is already in use",
328
+ context={"host": self.host, "port": self.port, "error": str(e)}
329
+ ) from e
330
+ else:
331
+ raise
314
332
 
315
333
  self.logger.info(f"🎉 Socket.IO server SUCCESSFULLY listening on http://{self.host}:{self.port}")
316
334
 
@@ -319,7 +337,7 @@ class SocketIOServer:
319
337
  while self.running:
320
338
  await asyncio.sleep(0.1)
321
339
  loop_count += 1
322
- if loop_count % 100 == 0: # Log every 10 seconds
340
+ if loop_count % PerformanceConfig.LOG_EVERY_N_ITERATIONS == 0:
323
341
  self.logger.debug(f"🔄 Socket.IO server heartbeat - {len(self.clients)} clients connected")
324
342
 
325
343
  except Exception as e:
@@ -463,7 +481,7 @@ class SocketIOServer:
463
481
  async def _handle_cors_preflight(self, request):
464
482
  """Handle CORS preflight requests."""
465
483
  return web.Response(
466
- status=200,
484
+ status=NetworkConfig.HTTP_OK,
467
485
  headers={
468
486
  'Access-Control-Allow-Origin': '*',
469
487
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
@@ -502,8 +520,12 @@ class SocketIOServer:
502
520
 
503
521
  self.logger.debug(f"Git diff requested for file: {file_path}, timestamp: {timestamp}")
504
522
 
505
- # Generate git diff using the _generate_git_diff helper
506
- diff_result = await self._generate_git_diff(file_path, timestamp, working_dir)
523
+ # Generate git diff using the git handler's method
524
+ if hasattr(self, 'git_handler') and self.git_handler:
525
+ diff_result = await self.git_handler.generate_git_diff(file_path, timestamp, working_dir)
526
+ else:
527
+ # Fallback to old method if handler not available
528
+ diff_result = await self._generate_git_diff(file_path, timestamp, working_dir)
507
529
 
508
530
  self.logger.info(f"Git diff result: success={diff_result.get('success', False)}, method={diff_result.get('method', 'unknown')}")
509
531
 
@@ -520,7 +542,7 @@ class SocketIOServer:
520
542
  return web.json_response({
521
543
  "success": False,
522
544
  "error": f"Failed to generate git diff: {str(e)}"
523
- }, status=500, headers={
545
+ }, status=NetworkConfig.HTTP_INTERNAL_ERROR, headers={
524
546
  'Access-Control-Allow-Origin': '*',
525
547
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
526
548
  'Access-Control-Allow-Headers': 'Content-Type, Accept'
@@ -538,7 +560,7 @@ class SocketIOServer:
538
560
  # Extract query parameters
539
561
  file_path = request.query.get('file_path')
540
562
  working_dir = request.query.get('working_dir', os.getcwd())
541
- max_size = int(request.query.get('max_size', 1024 * 1024)) # 1MB default
563
+ max_size = int(request.query.get('max_size', SystemLimits.MAX_FILE_SIZE))
542
564
 
543
565
  self.logger.info(f"File content API request: file_path={file_path}, working_dir={working_dir}")
544
566
 
@@ -553,8 +575,12 @@ class SocketIOServer:
553
575
  'Access-Control-Allow-Headers': 'Content-Type, Accept'
554
576
  })
555
577
 
556
- # Use the same file reading logic as the Socket.IO handler
557
- result = await self._read_file_safely(file_path, working_dir, max_size)
578
+ # Use the file handler's safe reading logic
579
+ if hasattr(self, 'file_handler') and self.file_handler:
580
+ result = await self.file_handler._read_file_safely(file_path, working_dir, max_size)
581
+ else:
582
+ # Fallback to old method if handler not available
583
+ result = await self._read_file_safely(file_path, working_dir, max_size)
558
584
 
559
585
  status_code = 200 if result.get('success') else 400
560
586
  return web.json_response(result, status=status_code, headers={
@@ -570,13 +596,13 @@ class SocketIOServer:
570
596
  return web.json_response({
571
597
  "success": False,
572
598
  "error": f"Failed to read file: {str(e)}"
573
- }, status=500, headers={
599
+ }, status=NetworkConfig.HTTP_INTERNAL_ERROR, headers={
574
600
  'Access-Control-Allow-Origin': '*',
575
601
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
576
602
  'Access-Control-Allow-Headers': 'Content-Type, Accept'
577
603
  })
578
604
 
579
- async def _read_file_safely(self, file_path: str, working_dir: str = None, max_size: int = 1024 * 1024):
605
+ async def _read_file_safely(self, file_path: str, working_dir: str = None, max_size: int = SystemLimits.MAX_FILE_SIZE):
580
606
  """Safely read file content with security checks.
581
607
 
582
608
  This method contains the core file reading logic that can be used by both
@@ -998,7 +1024,30 @@ class SocketIOServer:
998
1024
 
999
1025
 
1000
1026
  def _register_events(self):
1001
- """Register Socket.IO event handlers."""
1027
+ """Register Socket.IO event handlers.
1028
+
1029
+ WHY: This method now uses the EventHandlerRegistry to manage all event
1030
+ handlers in a modular way. Each handler focuses on a specific domain,
1031
+ reducing complexity and improving maintainability.
1032
+ """
1033
+ # Initialize the event handler registry
1034
+ self.event_registry = EventHandlerRegistry(self)
1035
+ self.event_registry.initialize()
1036
+
1037
+ # Register all events from all handlers
1038
+ self.event_registry.register_all_events()
1039
+
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)
1043
+
1044
+ self.logger.info("All Socket.IO events registered via handler system")
1045
+
1046
+ # Note: The actual event registration is now handled by individual
1047
+ # handler classes in socketio/handlers/. This dramatically reduces
1048
+ # the complexity of this method from 514 lines to under 20 lines.
1049
+
1050
+ return # Early return to skip old implementation
1002
1051
 
1003
1052
  @self.sio.event
1004
1053
  async def connect(sid, environ, *args):
@@ -1240,7 +1289,7 @@ class SocketIOServer:
1240
1289
  try:
1241
1290
  file_path = data.get('file_path')
1242
1291
  working_dir = data.get('working_dir', os.getcwd())
1243
- max_size = data.get('max_size', 1024 * 1024) # 1MB default limit
1292
+ max_size = data.get('max_size', SystemLimits.MAX_FILE_SIZE)
1244
1293
 
1245
1294
  if not file_path:
1246
1295
  await self.sio.emit('file_content_response', {
@@ -1626,7 +1675,7 @@ class SocketIOServer:
1626
1675
  )
1627
1676
  # Wait for completion with timeout to detect issues
1628
1677
  try:
1629
- future.result(timeout=2.0) # 2 second timeout
1678
+ future.result(timeout=TimeoutConfig.QUICK_TIMEOUT)
1630
1679
  self.logger.debug(f"📨 Successfully broadcasted {event_type} to {len(self.clients)} clients")
1631
1680
  except asyncio.TimeoutError:
1632
1681
  self.logger.warning(f"⏰ Broadcast timeout for event {event_type} - continuing anyway")
@@ -1758,6 +1807,83 @@ class SocketIOServer:
1758
1807
  "context_size": context_size,
1759
1808
  "timestamp": datetime.utcnow().isoformat() + "Z"
1760
1809
  })
1810
+
1811
+ # ================================================================================
1812
+ # Interface Adapter Methods
1813
+ # ================================================================================
1814
+ # These methods adapt the existing implementation to comply with SocketIOServiceInterface
1815
+
1816
+ async def start(self, host: str = "localhost", port: int = 8765) -> None:
1817
+ """Start the WebSocket server (async adapter).
1818
+
1819
+ WHY: The interface expects async methods, but our implementation uses
1820
+ synchronous start with background threads. This adapter provides compatibility.
1821
+
1822
+ Args:
1823
+ host: Host to bind to
1824
+ port: Port to listen on
1825
+ """
1826
+ self.host = host
1827
+ self.port = port
1828
+ # Call the synchronous start method
1829
+ self.start_sync()
1830
+
1831
+ async def stop(self) -> None:
1832
+ """Stop the WebSocket server (async adapter).
1833
+
1834
+ WHY: The interface expects async methods. This adapter wraps the
1835
+ synchronous stop method for interface compliance.
1836
+ """
1837
+ # Call the synchronous stop method
1838
+ self.stop_sync()
1839
+
1840
+ async def emit(self, event: str, data: Any, room: Optional[str] = None) -> None:
1841
+ """Emit an event to connected clients.
1842
+
1843
+ WHY: Provides interface compliance by wrapping broadcast_event with
1844
+ async signature and room support.
1845
+
1846
+ Args:
1847
+ event: Event name
1848
+ data: Event data
1849
+ room: Optional room to target (not supported in current implementation)
1850
+ """
1851
+ if room:
1852
+ self.logger.warning(f"Room-based emit not supported, broadcasting to all: {event}")
1853
+
1854
+ # Use existing broadcast_event method
1855
+ self.broadcast_event(event, data)
1856
+
1857
+ async def broadcast(self, event: str, data: Any) -> None:
1858
+ """Broadcast event to all connected clients.
1859
+
1860
+ WHY: Provides interface compliance with async signature.
1861
+
1862
+ Args:
1863
+ event: Event name
1864
+ data: Event data
1865
+ """
1866
+ self.broadcast_event(event, data)
1867
+
1868
+ def get_connection_count(self) -> int:
1869
+ """Get number of connected clients.
1870
+
1871
+ WHY: Provides interface compliance for monitoring connections.
1872
+
1873
+ Returns:
1874
+ Number of active connections
1875
+ """
1876
+ return len(self.clients)
1877
+
1878
+ def is_running(self) -> bool:
1879
+ """Check if server is running.
1880
+
1881
+ WHY: Provides interface compliance for status checking.
1882
+
1883
+ Returns:
1884
+ True if server is active
1885
+ """
1886
+ return self.running
1761
1887
 
1762
1888
 
1763
1889
  # Global instance for easy access
@@ -1795,7 +1921,7 @@ def get_socketio_server() -> SocketIOServer:
1795
1921
  def start_socketio_server():
1796
1922
  """Start the global Socket.IO server."""
1797
1923
  server = get_socketio_server()
1798
- server.start()
1924
+ server.start_sync()
1799
1925
  return server
1800
1926
 
1801
1927
 
@@ -1803,5 +1929,5 @@ def stop_socketio_server():
1803
1929
  """Stop the global Socket.IO server."""
1804
1930
  global _socketio_server
1805
1931
  if _socketio_server:
1806
- _socketio_server.stop()
1932
+ _socketio_server.stop_sync()
1807
1933
  _socketio_server = None