claude-mpm 4.3.12__py3-none-any.whl → 4.3.13__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 (199) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +390 -28
  3. claude_mpm/agents/templates/data_engineer.json +39 -14
  4. claude_mpm/cli/commands/agent_manager.py +3 -3
  5. claude_mpm/cli/commands/agents.py +2 -2
  6. claude_mpm/cli/commands/aggregate.py +1 -1
  7. claude_mpm/cli/commands/config.py +2 -2
  8. claude_mpm/cli/commands/configure.py +5 -5
  9. claude_mpm/cli/commands/configure_tui.py +7 -7
  10. claude_mpm/cli/commands/dashboard.py +1 -1
  11. claude_mpm/cli/commands/debug.py +5 -5
  12. claude_mpm/cli/commands/mcp.py +1 -1
  13. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  14. claude_mpm/cli/commands/mcp_config.py +7 -10
  15. claude_mpm/cli/commands/mcp_external_commands.py +40 -32
  16. claude_mpm/cli/commands/mcp_install_commands.py +38 -10
  17. claude_mpm/cli/commands/mcp_setup_external.py +143 -102
  18. claude_mpm/cli/commands/monitor.py +2 -2
  19. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  20. claude_mpm/cli/commands/run.py +46 -2
  21. claude_mpm/cli/commands/search.py +41 -34
  22. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  23. claude_mpm/cli/parsers/mcp_parser.py +1 -3
  24. claude_mpm/cli/parsers/search_parser.py +10 -4
  25. claude_mpm/cli/startup_logging.py +3 -5
  26. claude_mpm/cli/utils.py +1 -1
  27. claude_mpm/core/agent_registry.py +12 -8
  28. claude_mpm/core/agent_session_manager.py +8 -8
  29. claude_mpm/core/api_validator.py +4 -4
  30. claude_mpm/core/base_service.py +10 -10
  31. claude_mpm/core/cache.py +5 -5
  32. claude_mpm/core/config_constants.py +1 -1
  33. claude_mpm/core/container.py +1 -1
  34. claude_mpm/core/error_handler.py +2 -2
  35. claude_mpm/core/file_utils.py +1 -1
  36. claude_mpm/core/framework_loader.py +3 -3
  37. claude_mpm/core/hook_manager.py +8 -6
  38. claude_mpm/core/instruction_reinforcement_hook.py +2 -2
  39. claude_mpm/core/interactive_session.py +1 -1
  40. claude_mpm/core/lazy.py +3 -3
  41. claude_mpm/core/log_manager.py +16 -12
  42. claude_mpm/core/logger.py +16 -11
  43. claude_mpm/core/logging_config.py +4 -2
  44. claude_mpm/core/oneshot_session.py +1 -1
  45. claude_mpm/core/optimized_agent_loader.py +6 -6
  46. claude_mpm/core/output_style_manager.py +1 -1
  47. claude_mpm/core/pm_hook_interceptor.py +3 -3
  48. claude_mpm/core/service_registry.py +1 -1
  49. claude_mpm/core/session_manager.py +11 -9
  50. claude_mpm/core/socketio_pool.py +13 -13
  51. claude_mpm/core/types.py +2 -2
  52. claude_mpm/core/unified_agent_registry.py +2 -2
  53. claude_mpm/core/unified_paths.py +1 -1
  54. claude_mpm/dashboard/analysis_runner.py +4 -4
  55. claude_mpm/dashboard/api/simple_directory.py +1 -1
  56. claude_mpm/generators/agent_profile_generator.py +4 -2
  57. claude_mpm/hooks/base_hook.py +2 -2
  58. claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
  59. claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
  60. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
  61. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
  62. claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
  63. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
  64. claude_mpm/hooks/claude_hooks/installer.py +3 -3
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
  66. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
  67. claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
  68. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
  69. claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
  70. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
  71. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  72. claude_mpm/hooks/memory_integration_hook.py +1 -1
  73. claude_mpm/hooks/tool_call_interceptor.py +2 -2
  74. claude_mpm/models/agent_session.py +5 -5
  75. claude_mpm/services/__init__.py +1 -1
  76. claude_mpm/services/agent_capabilities_service.py +1 -1
  77. claude_mpm/services/agents/agent_builder.py +3 -3
  78. claude_mpm/services/agents/deployment/agent_deployment.py +2 -1
  79. claude_mpm/services/agents/deployment/agent_discovery_service.py +9 -3
  80. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
  81. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
  82. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  83. claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
  84. claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
  85. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
  86. claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
  87. claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
  88. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
  89. claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
  90. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  91. claude_mpm/services/agents/local_template_manager.py +6 -6
  92. claude_mpm/services/agents/management/agent_management_service.py +3 -3
  93. claude_mpm/services/agents/memory/content_manager.py +3 -3
  94. claude_mpm/services/agents/memory/memory_format_service.py +2 -2
  95. claude_mpm/services/agents/memory/template_generator.py +3 -3
  96. claude_mpm/services/agents/registry/__init__.py +1 -1
  97. claude_mpm/services/agents/registry/modification_tracker.py +2 -2
  98. claude_mpm/services/async_session_logger.py +3 -3
  99. claude_mpm/services/claude_session_logger.py +4 -4
  100. claude_mpm/services/cli/agent_listing_service.py +1 -1
  101. claude_mpm/services/cli/agent_validation_service.py +1 -0
  102. claude_mpm/services/cli/memory_crud_service.py +11 -6
  103. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  104. claude_mpm/services/cli/session_manager.py +15 -11
  105. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  106. claude_mpm/services/core/memory_manager.py +81 -23
  107. claude_mpm/services/core/path_resolver.py +2 -2
  108. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  109. claude_mpm/services/event_aggregator.py +4 -2
  110. claude_mpm/services/event_bus/direct_relay.py +5 -3
  111. claude_mpm/services/event_bus/event_bus.py +3 -3
  112. claude_mpm/services/event_bus/relay.py +6 -4
  113. claude_mpm/services/events/consumers/dead_letter.py +5 -3
  114. claude_mpm/services/events/core.py +3 -3
  115. claude_mpm/services/events/producers/hook.py +6 -6
  116. claude_mpm/services/events/producers/system.py +8 -8
  117. claude_mpm/services/exceptions.py +5 -5
  118. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
  119. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
  120. claude_mpm/services/hook_installer_service.py +1 -1
  121. claude_mpm/services/infrastructure/context_preservation.py +6 -4
  122. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  123. claude_mpm/services/infrastructure/logging.py +2 -2
  124. claude_mpm/services/mcp_config_manager.py +175 -30
  125. claude_mpm/services/mcp_gateway/__init__.py +1 -1
  126. claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
  127. claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
  128. claude_mpm/services/mcp_gateway/config/configuration.py +1 -1
  129. claude_mpm/services/mcp_gateway/core/base.py +2 -2
  130. claude_mpm/services/mcp_gateway/main.py +21 -7
  131. claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
  132. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
  133. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  134. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
  135. claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
  136. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
  137. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +190 -137
  138. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
  139. claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
  140. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
  141. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
  142. claude_mpm/services/memory/builder.py +7 -5
  143. claude_mpm/services/memory/indexed_memory.py +4 -4
  144. claude_mpm/services/memory/optimizer.py +6 -6
  145. claude_mpm/services/memory/router.py +3 -3
  146. claude_mpm/services/monitor/daemon.py +1 -1
  147. claude_mpm/services/monitor/daemon_manager.py +6 -6
  148. claude_mpm/services/monitor/event_emitter.py +2 -2
  149. claude_mpm/services/monitor/handlers/file.py +1 -1
  150. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  151. claude_mpm/services/monitor/server.py +4 -4
  152. claude_mpm/services/monitor_build_service.py +2 -2
  153. claude_mpm/services/port_manager.py +2 -2
  154. claude_mpm/services/response_tracker.py +2 -2
  155. claude_mpm/services/session_management_service.py +3 -2
  156. claude_mpm/services/socketio/client_proxy.py +2 -2
  157. claude_mpm/services/socketio/dashboard_server.py +4 -3
  158. claude_mpm/services/socketio/event_normalizer.py +12 -8
  159. claude_mpm/services/socketio/handlers/base.py +2 -2
  160. claude_mpm/services/socketio/handlers/connection.py +10 -10
  161. claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
  162. claude_mpm/services/socketio/handlers/file.py +1 -1
  163. claude_mpm/services/socketio/handlers/git.py +1 -1
  164. claude_mpm/services/socketio/handlers/hook.py +16 -15
  165. claude_mpm/services/socketio/migration_utils.py +1 -1
  166. claude_mpm/services/socketio/monitor_client.py +5 -5
  167. claude_mpm/services/socketio/server/broadcaster.py +9 -7
  168. claude_mpm/services/socketio/server/connection_manager.py +2 -2
  169. claude_mpm/services/socketio/server/core.py +7 -5
  170. claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
  171. claude_mpm/services/socketio/server/main.py +13 -13
  172. claude_mpm/services/socketio_client_manager.py +4 -4
  173. claude_mpm/services/system_instructions_service.py +2 -2
  174. claude_mpm/services/ticket_services/validation_service.py +1 -1
  175. claude_mpm/services/utility_service.py +5 -2
  176. claude_mpm/services/version_control/branch_strategy.py +2 -2
  177. claude_mpm/services/version_control/git_operations.py +22 -20
  178. claude_mpm/services/version_control/semantic_versioning.py +3 -3
  179. claude_mpm/services/version_control/version_parser.py +7 -5
  180. claude_mpm/services/visualization/mermaid_generator.py +1 -1
  181. claude_mpm/storage/state_storage.py +1 -1
  182. claude_mpm/tools/code_tree_analyzer.py +19 -18
  183. claude_mpm/tools/code_tree_builder.py +2 -2
  184. claude_mpm/tools/code_tree_events.py +10 -8
  185. claude_mpm/tools/socketio_debug.py +3 -3
  186. claude_mpm/utils/agent_dependency_loader.py +2 -2
  187. claude_mpm/utils/dependency_strategies.py +8 -3
  188. claude_mpm/utils/environment_context.py +2 -2
  189. claude_mpm/utils/error_handler.py +2 -2
  190. claude_mpm/utils/file_utils.py +1 -1
  191. claude_mpm/utils/imports.py +1 -1
  192. claude_mpm/utils/log_cleanup.py +21 -7
  193. claude_mpm/validation/agent_validator.py +2 -2
  194. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +1 -1
  195. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +199 -199
  196. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
  197. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
  198. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
  199. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/top_level.txt +0 -0
@@ -19,7 +19,7 @@ import contextlib
19
19
  import os
20
20
  import threading
21
21
  import time
22
- from datetime import datetime
22
+ from datetime import datetime, timezone
23
23
  from pathlib import Path
24
24
  from typing import Dict, Optional
25
25
 
@@ -365,7 +365,7 @@ class UnifiedMonitorServer:
365
365
  return web.Response(text=f"Error: {e!s}", status=500)
366
366
 
367
367
  # File content endpoint for file viewer
368
- async def api_file_handler(request):
368
+ async def api_file_handler(request): # noqa: PLR0911
369
369
  """Handle file content requests."""
370
370
  import json
371
371
  import os
@@ -481,7 +481,7 @@ class UnifiedMonitorServer:
481
481
  config = {
482
482
  "workingDirectory": os.getcwd(),
483
483
  "gitBranch": "Unknown",
484
- "serverTime": datetime.utcnow().isoformat() + "Z",
484
+ "serverTime": datetime.now(timezone.utc).isoformat() + "Z",
485
485
  "service": "unified-monitor",
486
486
  }
487
487
 
@@ -661,7 +661,7 @@ class UnifiedMonitorServer:
661
661
 
662
662
  # Create heartbeat data
663
663
  heartbeat_data = {
664
- "timestamp": datetime.utcnow().isoformat() + "Z",
664
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
665
665
  "type": "heartbeat",
666
666
  "server_uptime": uptime_seconds,
667
667
  "server_uptime_formatted": uptime_str,
@@ -156,9 +156,9 @@ class MonitorBuildService(BaseService):
156
156
  info: Build information dictionary
157
157
  """
158
158
  # Add timestamp
159
- from datetime import datetime
159
+ from datetime import datetime, timezone
160
160
 
161
- info["last_updated"] = datetime.utcnow().isoformat()
161
+ info["last_updated"] = datetime.now(timezone.utc).isoformat()
162
162
 
163
163
  # Write atomically using temp file and rename
164
164
  temp_fd, temp_path = tempfile.mkstemp(
@@ -243,7 +243,7 @@ class PortManager:
243
243
 
244
244
  return any(pattern in cmdline_lower for pattern in daemon_patterns)
245
245
 
246
- def kill_process_on_port(self, port: int, force: bool = False) -> bool:
246
+ def kill_process_on_port(self, port: int, force: bool = False) -> bool: # noqa: PLR0911
247
247
  """Kill a process using a specific port if it's safe to do so.
248
248
 
249
249
  WHY: Automatically reclaim ports from our debug scripts while preserving
@@ -354,7 +354,7 @@ class PortManager:
354
354
 
355
355
  def find_available_port(
356
356
  self, preferred_port: Optional[int] = None, reclaim: bool = True
357
- ) -> Optional[int]:
357
+ ) -> Optional[int]: # noqa: PLR0911
358
358
  """Find an available port, preferring the specified port if given.
359
359
 
360
360
  WHY: Enhanced to intelligently reclaim ports from our debug processes
@@ -19,7 +19,7 @@ DESIGN DECISIONS:
19
19
  """
20
20
 
21
21
  import logging
22
- from datetime import datetime
22
+ from datetime import datetime, timezone
23
23
  from typing import Any, Dict, Optional
24
24
 
25
25
  from claude_mpm.core.config import Config
@@ -144,7 +144,7 @@ class ResponseTracker:
144
144
  {
145
145
  "agent": agent_name,
146
146
  "tracked_by": "ResponseTracker",
147
- "tracking_timestamp": datetime.now().isoformat(),
147
+ "tracking_timestamp": datetime.now(timezone.utc).isoformat(),
148
148
  }
149
149
  )
150
150
 
@@ -13,6 +13,7 @@ Extracted from ClaudeRunner to follow Single Responsibility Principle.
13
13
 
14
14
  import time
15
15
  import uuid
16
+ from datetime import datetime, timezone
16
17
  from typing import Any, Dict, List, Optional
17
18
 
18
19
  from claude_mpm.core.base_service import BaseService
@@ -153,7 +154,7 @@ class SessionManagementService(BaseService, SessionManagementInterface):
153
154
  session_logs_dir.mkdir(parents=True, exist_ok=True)
154
155
 
155
156
  # Generate unique session log filename
156
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
157
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
157
158
  session_id = str(uuid.uuid4())[:8]
158
159
  log_filename = f"session_{timestamp}_{session_id}.jsonl"
159
160
  log_file = session_logs_dir / log_filename
@@ -183,7 +184,7 @@ class SessionManagementService(BaseService, SessionManagementInterface):
183
184
  from datetime import datetime
184
185
 
185
186
  # Add timestamp to event data
186
- event_data["timestamp"] = datetime.now().isoformat()
187
+ event_data["timestamp"] = datetime.now(timezone.utc).isoformat()
187
188
 
188
189
  # Append to log file as JSONL
189
190
  with open(log_file, "a") as f:
@@ -13,7 +13,7 @@ organization and to reduce the complexity of the main server file.
13
13
  import asyncio
14
14
  import threading
15
15
  import time
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from typing import Any, Dict, List, Optional
18
18
 
19
19
  try:
@@ -172,7 +172,7 @@ class SocketIOClientProxy:
172
172
  try:
173
173
  event = {
174
174
  "type": event_type,
175
- "timestamp": datetime.now().isoformat(),
175
+ "timestamp": datetime.now(timezone.utc).isoformat(),
176
176
  "data": data,
177
177
  }
178
178
 
@@ -16,7 +16,7 @@ DESIGN DECISIONS:
16
16
  - Maintains backward compatibility with existing dashboard clients
17
17
  """
18
18
 
19
- from datetime import datetime
19
+ from datetime import datetime, timezone
20
20
  from typing import Any, Dict, List, Optional
21
21
 
22
22
  try:
@@ -125,7 +125,7 @@ class DashboardServer(SocketIOServiceInterface):
125
125
  # Update state
126
126
  self.running = self.dashboard_server.is_running()
127
127
  if self.running:
128
- self.stats["start_time"] = datetime.now().isoformat()
128
+ self.stats["start_time"] = datetime.now(timezone.utc).isoformat()
129
129
  self.stats["monitor_connected"] = monitor_connected
130
130
 
131
131
  self.logger.info(
@@ -224,7 +224,8 @@ class DashboardServer(SocketIOServiceInterface):
224
224
  "monitor_stats": monitor_stats,
225
225
  "uptime": (
226
226
  (
227
- datetime.now() - datetime.fromisoformat(self.stats["start_time"])
227
+ datetime.now(timezone.utc)
228
+ - datetime.fromisoformat(self.stats["start_time"])
228
229
  ).total_seconds()
229
230
  if self.stats["start_time"]
230
231
  else 0
@@ -15,7 +15,7 @@ DESIGN DECISION: Transform all events to a consistent schema:
15
15
 
16
16
  import re
17
17
  from dataclasses import dataclass, field
18
- from datetime import datetime
18
+ from datetime import datetime, timezone
19
19
  from enum import Enum
20
20
  from typing import Any, Dict, Optional, Tuple
21
21
 
@@ -243,7 +243,7 @@ class EventNormalizer:
243
243
  source="system",
244
244
  type="unknown",
245
245
  subtype="error",
246
- timestamp=datetime.now().isoformat(),
246
+ timestamp=datetime.now(timezone.utc).isoformat(),
247
247
  data={"original": str(event_data), "error": str(e)},
248
248
  )
249
249
 
@@ -277,7 +277,9 @@ class EventNormalizer:
277
277
  source=source,
278
278
  type=event_data.get("type", "unknown"),
279
279
  subtype=event_data.get("subtype", "generic"),
280
- timestamp=event_data.get("timestamp", datetime.now().isoformat()),
280
+ timestamp=event_data.get(
281
+ "timestamp", datetime.now(timezone.utc).isoformat()
282
+ ),
281
283
  data=event_data.get("data", {}),
282
284
  )
283
285
 
@@ -340,7 +342,7 @@ class EventNormalizer:
340
342
 
341
343
  return "unknown"
342
344
 
343
- def _map_event_name(self, event_name: str) -> Tuple[str, str]:
345
+ def _map_event_name(self, event_name: str) -> Tuple[str, str]: # noqa: PLR0911
344
346
  """Map event name to (type, subtype) tuple.
345
347
 
346
348
  WHY: Consistent categorization helps clients filter and handle events.
@@ -553,16 +555,18 @@ class EventNormalizer:
553
555
  # Convert other formats
554
556
  try:
555
557
  if isinstance(timestamp, (int, float)):
556
- return datetime.fromtimestamp(timestamp).isoformat()
557
- except:
558
+ return datetime.fromtimestamp(
559
+ timestamp, tz=timezone.utc
560
+ ).isoformat()
561
+ except Exception:
558
562
  pass
559
563
 
560
564
  # Generate new timestamp if not found
561
- return datetime.now().isoformat()
565
+ return datetime.now(timezone.utc).isoformat()
562
566
 
563
567
  def _determine_source(
564
568
  self, event_data: Any, event_type: str, source_override: Optional[str] = None
565
- ) -> str:
569
+ ) -> str: # noqa: PLR0911
566
570
  """Determine the source of an event.
567
571
 
568
572
  WHY: Knowing where events originate helps with debugging,
@@ -5,7 +5,7 @@ logging, error handling, and access to the server instance. All handler
5
5
  classes inherit from this to ensure consistent behavior.
6
6
  """
7
7
 
8
- from datetime import datetime
8
+ from datetime import datetime, timezone
9
9
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
10
10
 
11
11
  from ....core.logger import get_logger
@@ -107,7 +107,7 @@ class BaseEventHandler:
107
107
  """
108
108
  event = {
109
109
  "type": event_type,
110
- "timestamp": datetime.utcnow().isoformat() + "Z",
110
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
111
111
  "data": data,
112
112
  }
113
113
  self.event_history.append(event)
@@ -8,7 +8,7 @@ from other handlers makes connection management more maintainable.
8
8
  import asyncio
9
9
  import functools
10
10
  import time
11
- from datetime import datetime
11
+ from datetime import datetime, timezone
12
12
  from typing import Any, Callable, Dict, List, Optional
13
13
 
14
14
  from .base import BaseEventHandler
@@ -48,7 +48,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
48
48
  f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
49
49
  f"(close to {timeout_seconds}s timeout)"
50
50
  )
51
- except:
51
+ except Exception:
52
52
  print(
53
53
  f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)"
54
54
  )
@@ -65,7 +65,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
65
65
  logger.error(
66
66
  f"❌ Handler {handler_name} timed out after {elapsed:.2f}s"
67
67
  )
68
- except:
68
+ except Exception:
69
69
  print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
70
70
 
71
71
  return None
@@ -80,7 +80,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
80
80
  logger.error(
81
81
  f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}"
82
82
  )
83
- except:
83
+ except Exception:
84
84
  print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
85
85
  raise
86
86
 
@@ -274,7 +274,7 @@ class ConnectionEventHandler(BaseEventHandler):
274
274
  # Force disconnect if still connected
275
275
  try:
276
276
  await self.sio.disconnect(sid)
277
- except:
277
+ except Exception:
278
278
  pass # Already disconnected
279
279
 
280
280
  self.logger.info(f"🔌 Cleaned up stale connection: {sid}")
@@ -326,7 +326,7 @@ class ConnectionEventHandler(BaseEventHandler):
326
326
  status_data = {
327
327
  "type": "connection",
328
328
  "subtype": "status",
329
- "timestamp": datetime.utcnow().isoformat() + "Z",
329
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
330
330
  "source": "server",
331
331
  "session_id": self.server.session_id,
332
332
  "data": {
@@ -348,13 +348,13 @@ class ConnectionEventHandler(BaseEventHandler):
348
348
  {
349
349
  "type": "connection",
350
350
  "subtype": "welcome",
351
- "timestamp": datetime.utcnow().isoformat() + "Z",
351
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
352
352
  "source": "server",
353
353
  "session_id": self.server.session_id,
354
354
  "data": {
355
355
  "message": "Connected to Claude MPM Socket.IO server",
356
356
  "client_id": sid,
357
- "server_time": datetime.utcnow().isoformat() + "Z",
357
+ "server_time": datetime.now(timezone.utc).isoformat() + "Z",
358
358
  "build_info": monitor_build_info,
359
359
  },
360
360
  },
@@ -401,7 +401,7 @@ class ConnectionEventHandler(BaseEventHandler):
401
401
  status_data = {
402
402
  "type": "connection",
403
403
  "subtype": "status",
404
- "timestamp": datetime.utcnow().isoformat() + "Z",
404
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
405
405
  "source": "server",
406
406
  "session_id": self.server.session_id,
407
407
  "data": {
@@ -456,7 +456,7 @@ class ConnectionEventHandler(BaseEventHandler):
456
456
  {
457
457
  "type": "connection",
458
458
  "subtype": "subscribed",
459
- "timestamp": datetime.utcnow().isoformat() + "Z",
459
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
460
460
  "source": "server",
461
461
  "data": {"channels": channels},
462
462
  },
@@ -8,7 +8,7 @@ DESIGN DECISION: Centralized connection event handling ensures consistent
8
8
  state management and provides resilient event delivery across reconnections.
9
9
  """
10
10
 
11
- from datetime import datetime
11
+ from datetime import datetime, timezone
12
12
 
13
13
  from .base import BaseEventHandler
14
14
 
@@ -50,7 +50,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
50
50
  # Store client info
51
51
  self.server.client_info[sid] = {
52
52
  "client_id": conn.client_id,
53
- "connected_at": datetime.now().isoformat(),
53
+ "connected_at": datetime.now(timezone.utc).isoformat(),
54
54
  "user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
55
55
  "remote_addr": environ.get("REMOTE_ADDR", "unknown"),
56
56
  }
@@ -61,7 +61,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
61
61
  {
62
62
  "client_id": conn.client_id,
63
63
  "sid": sid,
64
- "timestamp": datetime.now().isoformat(),
64
+ "timestamp": datetime.now(timezone.utc).isoformat(),
65
65
  "server_version": self.get_server_version(),
66
66
  },
67
67
  room=sid,
@@ -120,7 +120,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
120
120
  self.server.stats["connections_total"] += 1
121
121
 
122
122
  self.server.client_info[sid] = {
123
- "connected_at": datetime.now().isoformat(),
123
+ "connected_at": datetime.now(timezone.utc).isoformat(),
124
124
  "user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
125
125
  "remote_addr": environ.get("REMOTE_ADDR", "unknown"),
126
126
  }
@@ -185,8 +185,8 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
185
185
  await sio.emit(
186
186
  "pong",
187
187
  {
188
- "timestamp": datetime.now().isoformat(),
189
- "server_time": datetime.now().timestamp(),
188
+ "timestamp": datetime.now(timezone.utc).isoformat(),
189
+ "server_time": datetime.now(timezone.utc).timestamp(),
190
190
  },
191
191
  room=sid,
192
192
  )
@@ -210,7 +210,10 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
210
210
  # Optional: Send confirmation
211
211
  await sio.emit(
212
212
  "ack_confirmed",
213
- {"sequence": sequence, "timestamp": datetime.now().isoformat()},
213
+ {
214
+ "sequence": sequence,
215
+ "timestamp": datetime.now(timezone.utc).isoformat(),
216
+ },
214
217
  room=sid,
215
218
  )
216
219
 
@@ -263,7 +266,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
263
266
  """Get connection statistics for debugging."""
264
267
  try:
265
268
  stats = {
266
- "timestamp": datetime.now().isoformat(),
269
+ "timestamp": datetime.now(timezone.utc).isoformat(),
267
270
  "total_connections": len(self.server.connected_clients),
268
271
  "server_stats": self.server.stats,
269
272
  }
@@ -312,7 +315,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
312
315
  if self.server.stats.get("start_time")
313
316
  else None
314
317
  ),
315
- "timestamp": datetime.now().isoformat(),
318
+ "timestamp": datetime.now(timezone.utc).isoformat(),
316
319
  }
317
320
 
318
321
  await self.server.core.sio.emit("server_status", status_data, room=sid)
@@ -326,5 +329,5 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
326
329
  from claude_mpm.services.version_service import VersionService
327
330
 
328
331
  return VersionService().get_version()
329
- except:
332
+ except Exception:
330
333
  return "unknown"
@@ -108,7 +108,7 @@ class FileEventHandler(BaseEventHandler):
108
108
  file_path: str,
109
109
  working_dir: Optional[str] = None,
110
110
  max_size: int = 1024 * 1024,
111
- ) -> EventData:
111
+ ) -> EventData: # noqa: PLR0911
112
112
  """Safely read file content with security checks.
113
113
 
114
114
  WHY: File reading must be secure to prevent directory traversal attacks
@@ -601,7 +601,7 @@ class GitEventHandler(BaseEventHandler):
601
601
  file_path: str,
602
602
  timestamp: Optional[str] = None,
603
603
  working_dir: Optional[str] = None,
604
- ) -> Dict[str, Any]:
604
+ ) -> Dict[str, Any]: # noqa: PLR0911
605
605
  """Generate git diff for a specific file operation.
606
606
 
607
607
  WHY: This method generates a git diff showing the changes made to a file
@@ -4,7 +4,7 @@ WHY: This module handles hook events from Claude to track session information,
4
4
  agent delegations, and other hook-based activity for the system heartbeat.
5
5
  """
6
6
 
7
- from datetime import datetime
7
+ from datetime import datetime, timezone
8
8
  from typing import Any, Dict
9
9
 
10
10
  from .base import BaseEventHandler
@@ -66,7 +66,8 @@ class HookEventHandler(BaseEventHandler):
66
66
  "type": "hook",
67
67
  "event": hook_event,
68
68
  "data": hook_data,
69
- "timestamp": data.get("timestamp") or datetime.now().isoformat(),
69
+ "timestamp": data.get("timestamp")
70
+ or datetime.now(timezone.utc).isoformat(),
70
71
  }
71
72
 
72
73
  # Add the event to history for replay
@@ -105,11 +106,11 @@ class HookEventHandler(BaseEventHandler):
105
106
  if hasattr(self.server, "active_sessions"):
106
107
  self.server.active_sessions[session_id] = {
107
108
  "session_id": session_id,
108
- "start_time": datetime.now().isoformat(),
109
+ "start_time": datetime.now(timezone.utc).isoformat(),
109
110
  "agent": agent_type,
110
111
  "status": "active",
111
112
  "prompt": data.get("prompt", "")[:100], # First 100 chars
112
- "last_activity": datetime.now().isoformat(),
113
+ "last_activity": datetime.now(timezone.utc).isoformat(),
113
114
  }
114
115
 
115
116
  self.logger.debug(
@@ -132,9 +133,9 @@ class HookEventHandler(BaseEventHandler):
132
133
  if session_id in self.server.active_sessions:
133
134
  # Mark as completed rather than removing immediately
134
135
  self.server.active_sessions[session_id]["status"] = "completed"
135
- self.server.active_sessions[session_id][
136
- "last_activity"
137
- ] = datetime.now().isoformat()
136
+ self.server.active_sessions[session_id]["last_activity"] = datetime.now(
137
+ timezone.utc
138
+ ).isoformat()
138
139
 
139
140
  self.logger.debug(
140
141
  f"Marked session completed: session={session_id[:8]}..."
@@ -156,18 +157,18 @@ class HookEventHandler(BaseEventHandler):
156
157
  if session_id not in self.server.active_sessions:
157
158
  self.server.active_sessions[session_id] = {
158
159
  "session_id": session_id,
159
- "start_time": datetime.now().isoformat(),
160
+ "start_time": datetime.now(timezone.utc).isoformat(),
160
161
  "agent": "pm", # Default to PM
161
162
  "status": "active",
162
163
  "prompt": data.get("prompt_text", "")[:100],
163
164
  "working_directory": data.get("working_directory", ""),
164
- "last_activity": datetime.now().isoformat(),
165
+ "last_activity": datetime.now(timezone.utc).isoformat(),
165
166
  }
166
167
  else:
167
168
  # Update last activity
168
- self.server.active_sessions[session_id][
169
- "last_activity"
170
- ] = datetime.now().isoformat()
169
+ self.server.active_sessions[session_id]["last_activity"] = datetime.now(
170
+ timezone.utc
171
+ ).isoformat()
171
172
 
172
173
  async def _handle_pre_tool(self, data: Dict[str, Any]):
173
174
  """Handle pre-tool events.
@@ -190,9 +191,9 @@ class HookEventHandler(BaseEventHandler):
190
191
  if session_id in self.server.active_sessions:
191
192
  self.server.active_sessions[session_id]["agent"] = agent_type
192
193
  self.server.active_sessions[session_id]["status"] = "delegated"
193
- self.server.active_sessions[session_id][
194
- "last_activity"
195
- ] = datetime.now().isoformat()
194
+ self.server.active_sessions[session_id]["last_activity"] = datetime.now(
195
+ timezone.utc
196
+ ).isoformat()
196
197
 
197
198
  self.logger.debug(
198
199
  f"Updated session delegation: session={session_id[:8]}..., agent={agent_type}"
@@ -223,7 +223,7 @@ class EventTypeMapper:
223
223
  }
224
224
 
225
225
  @classmethod
226
- def map_event_type(cls, old_type: str) -> Tuple[str, str]:
226
+ def map_event_type(cls, old_type: str) -> Tuple[str, str]: # noqa: PLR0911
227
227
  """Map an old event type to new type/subtype.
228
228
 
229
229
  WHY: Provides consistent categorization for all events.
@@ -16,7 +16,7 @@ DESIGN DECISIONS:
16
16
  import asyncio
17
17
  import threading
18
18
  import time
19
- from datetime import datetime
19
+ from datetime import datetime, timezone
20
20
  from typing import Any, Callable, Dict
21
21
 
22
22
  try:
@@ -119,7 +119,7 @@ class MonitorClient:
119
119
  # Use the event loop from the client thread
120
120
  if hasattr(self.client, "disconnect"):
121
121
  asyncio.run(self.client.disconnect())
122
- except:
122
+ except Exception:
123
123
  pass
124
124
 
125
125
  self.connected = False
@@ -152,7 +152,7 @@ class MonitorClient:
152
152
  "monitor_url": self.monitor_url,
153
153
  "uptime": (
154
154
  (
155
- datetime.now()
155
+ datetime.now(timezone.utc)
156
156
  - datetime.fromisoformat(self.stats["last_connected"])
157
157
  ).total_seconds()
158
158
  if self.stats["last_connected"] and self.connected
@@ -224,7 +224,7 @@ class MonitorClient:
224
224
  self.connected = True
225
225
  self.connecting = False
226
226
  self.stats["successful_connections"] += 1
227
- self.stats["last_connected"] = datetime.now().isoformat()
227
+ self.stats["last_connected"] = datetime.now(timezone.utc).isoformat()
228
228
  self.reconnect_delay = 1.0 # Reset reconnect delay on successful connection
229
229
 
230
230
  self.logger.info(f"Connected to monitor server at {self.monitor_url}")
@@ -255,7 +255,7 @@ class MonitorClient:
255
255
  """Handle disconnection."""
256
256
  self.logger.info("Disconnected from monitor server")
257
257
  self.connected = False
258
- self.stats["last_disconnected"] = datetime.now().isoformat()
258
+ self.stats["last_disconnected"] = datetime.now(timezone.utc).isoformat()
259
259
 
260
260
  @self.client.event
261
261
  async def connect_error(data):
@@ -13,7 +13,7 @@ import asyncio
13
13
  import time
14
14
  from collections import deque
15
15
  from dataclasses import dataclass
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from typing import Any, Deque, Dict, List, Optional, Set
18
18
 
19
19
  from ..event_normalizer import EventNormalizer
@@ -284,7 +284,7 @@ class SocketIOEventBroadcaster:
284
284
  # Reconstruct the raw event
285
285
  raw_event = {
286
286
  "type": event.event_type,
287
- "timestamp": datetime.now().isoformat(),
287
+ "timestamp": datetime.now(timezone.utc).isoformat(),
288
288
  "data": {**event.data, "retry_attempt": event.attempt_count + 1},
289
289
  }
290
290
 
@@ -323,7 +323,7 @@ class SocketIOEventBroadcaster:
323
323
  # Create raw event for normalization
324
324
  raw_event = {
325
325
  "type": event_type,
326
- "timestamp": datetime.now().isoformat(),
326
+ "timestamp": datetime.now(timezone.utc).isoformat(),
327
327
  "data": data,
328
328
  }
329
329
 
@@ -388,11 +388,11 @@ class SocketIOEventBroadcaster:
388
388
  asyncio.run_coroutine_threadsafe(
389
389
  update_activities(), self.loop
390
390
  )
391
- except:
391
+ except Exception:
392
392
  pass # Non-critical
393
393
 
394
394
  self.logger.debug(f"Broadcasted event: {event_type}")
395
- except:
395
+ except Exception:
396
396
  # Will be added to retry queue below
397
397
  pass
398
398
  else:
@@ -426,13 +426,15 @@ class SocketIOEventBroadcaster:
426
426
  "session_id": session_id,
427
427
  "launch_method": launch_method,
428
428
  "working_dir": working_dir,
429
- "timestamp": datetime.now().isoformat(),
429
+ "timestamp": datetime.now(timezone.utc).isoformat(),
430
430
  },
431
431
  )
432
432
 
433
433
  def session_ended(self):
434
434
  """Notify that a session has ended."""
435
- self.broadcast_event("session_ended", {"timestamp": datetime.now().isoformat()})
435
+ self.broadcast_event(
436
+ "session_ended", {"timestamp": datetime.now(timezone.utc).isoformat()}
437
+ )
436
438
 
437
439
  def claude_status_changed(
438
440
  self, status: str, pid: Optional[int] = None, message: str = ""
@@ -14,7 +14,7 @@ import contextlib
14
14
  import time
15
15
  from collections import deque
16
16
  from dataclasses import dataclass, field
17
- from datetime import datetime
17
+ from datetime import datetime, timezone
18
18
  from enum import Enum
19
19
  from typing import Any, Deque, Dict, List, Optional
20
20
  from uuid import uuid4
@@ -464,7 +464,7 @@ class ConnectionManager:
464
464
  async with self._lock:
465
465
  now = time.time()
466
466
  report = {
467
- "timestamp": datetime.now().isoformat(),
467
+ "timestamp": datetime.now(timezone.utc).isoformat(),
468
468
  "total_connections": len(self.connections),
469
469
  "healthy": 0,
470
470
  "stale": 0,