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
@@ -17,7 +17,7 @@ DESIGN DECISIONS:
17
17
 
18
18
  import os
19
19
  from abc import ABC, abstractmethod
20
- from datetime import datetime
20
+ from datetime import datetime, timezone
21
21
  from pathlib import Path
22
22
  from typing import Any, Dict, List, Optional
23
23
 
@@ -223,7 +223,9 @@ class MemoryCRUDService(IMemoryCRUDService):
223
223
  stat = memory_file.stat()
224
224
  file_stats = {
225
225
  "size_kb": stat.st_size / 1024,
226
- "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
226
+ "modified": datetime.fromtimestamp(
227
+ stat.st_mtime, tz=timezone.utc
228
+ ).isoformat(),
227
229
  "path": str(memory_file),
228
230
  }
229
231
 
@@ -256,7 +258,7 @@ class MemoryCRUDService(IMemoryCRUDService):
256
258
  "file_stats": {
257
259
  "size_kb": stat.st_size / 1024,
258
260
  "modified": datetime.fromtimestamp(
259
- stat.st_mtime
261
+ stat.st_mtime, tz=timezone.utc
260
262
  ).isoformat(),
261
263
  "path": str(memory_file),
262
264
  },
@@ -431,10 +433,10 @@ class MemoryCRUDService(IMemoryCRUDService):
431
433
  {
432
434
  "size_kb": stat.st_size / 1024,
433
435
  "modified": datetime.fromtimestamp(
434
- stat.st_mtime
436
+ stat.st_mtime, tz=timezone.utc
435
437
  ).isoformat(),
436
438
  "created": datetime.fromtimestamp(
437
- stat.st_ctime
439
+ stat.st_ctime, tz=timezone.utc
438
440
  ).isoformat(),
439
441
  }
440
442
  )
@@ -499,7 +501,10 @@ class MemoryCRUDService(IMemoryCRUDService):
499
501
  continue
500
502
 
501
503
  stat = memory_file.stat()
502
- age_days = (datetime.now() - datetime.fromtimestamp(stat.st_mtime)).days
504
+ age_days = (
505
+ datetime.now(timezone.utc)
506
+ - datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
507
+ ).days
503
508
 
504
509
  # Identify files older than 30 days as candidates
505
510
  if age_days > 30:
@@ -200,7 +200,7 @@ class MemoryOutputFormatter(IMemoryOutputFormatter):
200
200
  try:
201
201
  dt = datetime.fromisoformat(last_modified.replace("Z", "+00:00"))
202
202
  last_modified_str = dt.strftime("%Y-%m-%d %H:%M:%S")
203
- except:
203
+ except Exception:
204
204
  last_modified_str = last_modified
205
205
 
206
206
  # Status indicator based on usage
@@ -18,7 +18,7 @@ import json
18
18
  import uuid
19
19
  from abc import ABC, abstractmethod
20
20
  from dataclasses import dataclass, field
21
- from datetime import datetime, timedelta
21
+ from datetime import datetime, timedelta, timezone
22
22
  from pathlib import Path
23
23
  from typing import Any, Dict, List, Optional
24
24
 
@@ -151,8 +151,12 @@ class SessionInfo:
151
151
 
152
152
  id: str
153
153
  context: str = "default"
154
- created_at: str = field(default_factory=lambda: datetime.now().isoformat())
155
- last_used: str = field(default_factory=lambda: datetime.now().isoformat())
154
+ created_at: str = field(
155
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
156
+ )
157
+ last_used: str = field(
158
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
159
+ )
156
160
  use_count: int = 0
157
161
  agents_run: List[Dict[str, Any]] = field(default_factory=list)
158
162
  metadata: Dict[str, Any] = field(default_factory=dict)
@@ -175,8 +179,8 @@ class SessionInfo:
175
179
  return cls(
176
180
  id=data["id"],
177
181
  context=data.get("context", "default"),
178
- created_at=data.get("created_at", datetime.now().isoformat()),
179
- last_used=data.get("last_used", datetime.now().isoformat()),
182
+ created_at=data.get("created_at", datetime.now(timezone.utc).isoformat()),
183
+ last_used=data.get("last_used", datetime.now(timezone.utc).isoformat()),
180
184
  use_count=data.get("use_count", 0),
181
185
  agents_run=data.get("agents_run", []),
182
186
  metadata=data.get("metadata", {}),
@@ -284,7 +288,7 @@ class SessionManager(ISessionManager):
284
288
  # Check session age
285
289
  try:
286
290
  created = datetime.fromisoformat(session.created_at)
287
- age = datetime.now() - created
291
+ age = datetime.now(timezone.utc) - created
288
292
 
289
293
  if age > timedelta(days=7):
290
294
  validation.warnings.append(f"Session is {age.days} days old")
@@ -355,10 +359,10 @@ class SessionManager(ISessionManager):
355
359
  {
356
360
  "agent": agent,
357
361
  "task": task[:100], # Truncate long tasks
358
- "timestamp": datetime.now().isoformat(),
362
+ "timestamp": datetime.now(timezone.utc).isoformat(),
359
363
  }
360
364
  )
361
- session.last_used = datetime.now().isoformat()
365
+ session.last_used = datetime.now(timezone.utc).isoformat()
362
366
  session.use_count += 1
363
367
 
364
368
  self.save_session(session)
@@ -370,7 +374,7 @@ class SessionManager(ISessionManager):
370
374
 
371
375
  WHY: Prevents unbounded growth of session data and improves performance.
372
376
  """
373
- now = datetime.now()
377
+ now = datetime.now(timezone.utc)
374
378
  max_age = timedelta(hours=max_age_hours)
375
379
 
376
380
  expired_ids = []
@@ -418,7 +422,7 @@ class SessionManager(ISessionManager):
418
422
  return True
419
423
 
420
424
  # Create timestamped archive file
421
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
425
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
422
426
  archive_name = f"sessions_archive_{timestamp}.json.gz"
423
427
  archive_path = archive_dir / archive_name
424
428
 
@@ -509,5 +513,5 @@ class ManagedSession:
509
513
  """Exit session context with cleanup."""
510
514
  if self.session:
511
515
  # Update last used time
512
- self.session.last_used = datetime.now().isoformat()
516
+ self.session.last_used = datetime.now(timezone.utc).isoformat()
513
517
  self.manager.save_session(self.session)
@@ -81,7 +81,7 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
81
81
  background: bool = False,
82
82
  open_browser: bool = True,
83
83
  force_restart: bool = False,
84
- ) -> Tuple[bool, bool]:
84
+ ) -> Tuple[bool, bool]: # noqa: PLR0911
85
85
  """
86
86
  Start the dashboard using unified daemon.
87
87
 
@@ -21,7 +21,7 @@ ARCHITECTURE:
21
21
  """
22
22
 
23
23
  import logging
24
- from datetime import datetime
24
+ from datetime import datetime, timezone
25
25
  from pathlib import Path
26
26
  from typing import Any, Dict, List, Optional, Set
27
27
 
@@ -169,7 +169,7 @@ class MemoryManager(IMemoryManager):
169
169
  ]
170
170
 
171
171
  # Add new memory as a bullet point
172
- timestamp = datetime.now().isoformat()
172
+ timestamp = datetime.now(timezone.utc).isoformat()
173
173
  lines.append(f"- [{timestamp}] {key}: {value}")
174
174
 
175
175
  # Write back
@@ -335,19 +335,37 @@ class MemoryManager(IMemoryManager):
335
335
  if pm_memories:
336
336
  aggregated_pm = self._aggregate_memories(pm_memories)
337
337
  result["actual_memories"] = aggregated_pm
338
- memory_size = len(aggregated_pm.encode("utf-8"))
339
- self.logger.info(
340
- f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)"
341
- )
338
+ # Count actual memory items in aggregated content
339
+ memory_items = [
340
+ line.strip()
341
+ for line in aggregated_pm.split("\n")
342
+ if line.strip().startswith("-")
343
+ ]
344
+ if memory_items:
345
+ self.logger.info(
346
+ f"Aggregated PM memory: {len(memory_items)} total items from {len(pm_memories)} source(s)"
347
+ )
348
+ else:
349
+ self.logger.debug(
350
+ f"Aggregated PM memory from {len(pm_memories)} source(s) (no items)"
351
+ )
342
352
 
343
353
  # Store agent memories (already aggregated per agent)
344
354
  if agent_memories_dict:
345
355
  result["agent_memories"] = agent_memories_dict
346
356
  for agent_name, memory_content in agent_memories_dict.items():
347
- memory_size = len(memory_content.encode("utf-8"))
348
- self.logger.debug(
349
- f"Aggregated {agent_name} memory: {memory_size:,} bytes"
350
- )
357
+ # Count actual memory items
358
+ memory_items = [
359
+ line.strip()
360
+ for line in memory_content.split("\n")
361
+ if line.strip().startswith("-")
362
+ ]
363
+ if memory_items:
364
+ self.logger.debug(
365
+ f"Aggregated {agent_name} memory: {len(memory_items)} items"
366
+ )
367
+ else:
368
+ self.logger.debug(f"Aggregated {agent_name} memory: no items")
351
369
 
352
370
  # Log summary
353
371
  if self._stats["loaded_count"] > 0 or self._stats["skipped_count"] > 0:
@@ -410,10 +428,21 @@ class MemoryManager(IMemoryManager):
410
428
  "path": pm_memory_path,
411
429
  }
412
430
  )
413
- memory_size = len(loaded_content.encode("utf-8"))
414
- self.logger.info(
415
- f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)"
416
- )
431
+ # Count actual memory items (lines starting with "-")
432
+ memory_items = [
433
+ line.strip()
434
+ for line in loaded_content.split("\n")
435
+ if line.strip().startswith("-")
436
+ ]
437
+ if memory_items:
438
+ self.logger.info(
439
+ f"Loaded {source} PM memory: {len(memory_items)} items"
440
+ )
441
+ else:
442
+ # Skip logging if no actual memory items
443
+ self.logger.debug(
444
+ f"Skipped {source} PM memory: {pm_memory_path} (no memory items)"
445
+ )
417
446
  self._stats["loaded_count"] += 1
418
447
  except Exception as e:
419
448
  self.logger.error(
@@ -471,20 +500,47 @@ class MemoryManager(IMemoryManager):
471
500
  }
472
501
  )
473
502
 
474
- memory_size = len(loaded_content.encode("utf-8"))
475
- self.logger.info(
476
- f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)"
477
- )
503
+ # Count actual memory items (lines starting with "-")
504
+ memory_items = [
505
+ line.strip()
506
+ for line in loaded_content.split("\n")
507
+ if line.strip().startswith("-")
508
+ ]
509
+ if memory_items:
510
+ self.logger.info(
511
+ f"Loaded {source} memory for {agent_name}: {len(memory_items)} items"
512
+ )
513
+ else:
514
+ # Skip logging if no actual memory items
515
+ self.logger.debug(
516
+ f"Skipped {source} memory for {agent_name}: {memory_file.name} (no memory items)"
517
+ )
478
518
  self._stats["loaded_count"] += 1
479
519
  except Exception as e:
480
520
  self.logger.error(
481
521
  f"Failed to load agent memory from {memory_file}: {e}"
482
522
  )
483
523
  else:
484
- # Log skipped memories
485
- self.logger.info(
486
- f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed)"
487
- )
524
+ # Log skipped memories only if they contain actual items
525
+ try:
526
+ loaded_content = memory_file.read_text(encoding="utf-8")
527
+ memory_items = [
528
+ line.strip()
529
+ for line in loaded_content.split("\n")
530
+ if line.strip().startswith("-")
531
+ ]
532
+ if memory_items:
533
+ self.logger.info(
534
+ f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed, {len(memory_items)} items)"
535
+ )
536
+ else:
537
+ # Don't log if file has no actual memory items
538
+ self.logger.debug(
539
+ f"Skipped {source} memory: {memory_file.name} (agent '{agent_name}' not deployed, no items)"
540
+ )
541
+ except Exception:
542
+ # If we can't read the file, just skip silently
543
+ pass
488
544
 
489
545
  # Detect naming mismatches
490
546
  alt_name = (
@@ -570,7 +626,9 @@ class MemoryManager(IMemoryManager):
570
626
  lines.append("# Agent Memory")
571
627
 
572
628
  # Add latest timestamp
573
- lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
629
+ lines.append(
630
+ f"<!-- Last Updated: {datetime.now(timezone.utc).isoformat()}Z -->"
631
+ )
574
632
  lines.append("")
575
633
 
576
634
  # Add all unique items (sorted for consistency)
@@ -163,7 +163,7 @@ class PathResolver(IPathResolver):
163
163
  self.logger.debug(f"No project root found from {start_path}")
164
164
  return None
165
165
 
166
- def detect_framework_path(self) -> Optional[Path]:
166
+ def detect_framework_path(self) -> Optional[Path]: # noqa: PLR0911
167
167
  """
168
168
  Auto-detect claude-mpm framework using unified path management.
169
169
 
@@ -322,8 +322,8 @@ class PathResolver(IPathResolver):
322
322
  """Detect framework path using unified path management."""
323
323
  try:
324
324
  # Import here to avoid circular dependencies
325
+ from ...core.unified_paths import DeploymentContext as UnifiedContext
325
326
  from ...core.unified_paths import (
326
- DeploymentContext as UnifiedContext,
327
327
  get_path_manager,
328
328
  )
329
329
 
@@ -136,7 +136,7 @@ class InstallationCheck(BaseDiagnosticCheck):
136
136
  details={"error": str(e)},
137
137
  )
138
138
 
139
- def _check_installation_method(self) -> DiagnosticResult:
139
+ def _check_installation_method(self) -> DiagnosticResult: # noqa: PLR0911
140
140
  """Detect how claude-mpm was installed."""
141
141
  methods_found = []
142
142
  details = {}
@@ -18,7 +18,7 @@ import sys
18
18
  import threading
19
19
  import time
20
20
  from collections import defaultdict
21
- from datetime import datetime
21
+ from datetime import datetime, timezone
22
22
  from typing import Any, Dict, List, Optional
23
23
 
24
24
  try:
@@ -267,7 +267,9 @@ class EventAggregator:
267
267
  try:
268
268
  # Extract event metadata
269
269
  event_type = event_data.get("type", "unknown")
270
- timestamp = event_data.get("timestamp", datetime.utcnow().isoformat() + "Z")
270
+ timestamp = event_data.get(
271
+ "timestamp", datetime.now(timezone.utc).isoformat() + "Z"
272
+ )
271
273
  data = event_data.get("data", {})
272
274
 
273
275
  # Update statistics
@@ -15,7 +15,7 @@ DO NOT use "event" or "type" fields - use "hook_event_name" instead!
15
15
  """
16
16
 
17
17
  import logging
18
- from datetime import datetime
18
+ from datetime import datetime, timezone
19
19
  from typing import Any
20
20
 
21
21
  from .event_bus import EventBus
@@ -223,7 +223,9 @@ class DirectSocketIORelay:
223
223
  event_type, broadcast_data
224
224
  )
225
225
  self.stats["events_relayed"] += 1
226
- self.stats["last_relay_time"] = datetime.now().isoformat()
226
+ self.stats["last_relay_time"] = datetime.now(
227
+ timezone.utc
228
+ ).isoformat()
227
229
 
228
230
  # Reset retry counter on successful broadcast
229
231
  if self.connection_retries > 0:
@@ -258,7 +260,7 @@ class DirectSocketIORelay:
258
260
  logger.info(
259
261
  f"[DirectRelay] Retry successful for {event_type}"
260
262
  )
261
- except:
263
+ except Exception:
262
264
  pass # Already counted as failed
263
265
  else:
264
266
  # Enhanced logging when broadcaster is not available
@@ -11,7 +11,7 @@ WHY pyee over alternatives:
11
11
  import asyncio
12
12
  import logging
13
13
  import threading
14
- from datetime import datetime
14
+ from datetime import datetime, timezone
15
15
  from typing import Any, Callable, Dict, List, Optional, Set
16
16
 
17
17
  from pyee.asyncio import AsyncIOEventEmitter
@@ -210,7 +210,7 @@ class EventBus:
210
210
 
211
211
  # Update stats
212
212
  self._stats["events_published"] += 1
213
- self._stats["last_event_time"] = datetime.now().isoformat()
213
+ self._stats["last_event_time"] = datetime.now(timezone.utc).isoformat()
214
214
 
215
215
  if self._debug:
216
216
  logger.debug(f"Published event: {event_type}")
@@ -302,7 +302,7 @@ class EventBus:
302
302
  data: The event data
303
303
  """
304
304
  event_record = {
305
- "timestamp": datetime.now().isoformat(),
305
+ "timestamp": datetime.now(timezone.utc).isoformat(),
306
306
  "type": event_type,
307
307
  "data": data,
308
308
  }
@@ -11,7 +11,7 @@ WHY separate relay component:
11
11
  import logging
12
12
  import os
13
13
  import time
14
- from datetime import datetime
14
+ from datetime import datetime, timezone
15
15
  from typing import Any, Dict, Optional
16
16
 
17
17
  # Socket.IO imports
@@ -151,7 +151,7 @@ class SocketIORelay:
151
151
  # Verify connection is still alive
152
152
  if self.client.connected:
153
153
  return True
154
- except:
154
+ except Exception:
155
155
  pass
156
156
 
157
157
  # Need to create or reconnect
@@ -187,7 +187,9 @@ class SocketIORelay:
187
187
  "subtype": (
188
188
  event_type.split(".", 1)[1] if "." in event_type else "generic"
189
189
  ),
190
- "timestamp": data.get("timestamp", datetime.now().isoformat()),
190
+ "timestamp": data.get(
191
+ "timestamp", datetime.now(timezone.utc).isoformat()
192
+ ),
191
193
  "data": data,
192
194
  "source": "event_bus",
193
195
  },
@@ -195,7 +197,7 @@ class SocketIORelay:
195
197
 
196
198
  # Update statistics
197
199
  self.stats["events_relayed"] += 1
198
- self.stats["last_relay_time"] = datetime.now().isoformat()
200
+ self.stats["last_relay_time"] = datetime.now(timezone.utc).isoformat()
199
201
 
200
202
  if self.debug:
201
203
  logger.debug(f"Relayed event to Socket.IO: {event_type}")
@@ -6,7 +6,7 @@ Handles events that failed processing in other consumers.
6
6
  """
7
7
 
8
8
  import json
9
- from datetime import datetime
9
+ from datetime import datetime, timezone
10
10
  from pathlib import Path
11
11
  from typing import Any, Dict, List, Optional
12
12
 
@@ -284,7 +284,7 @@ class DeadLetterConsumer(IEventConsumer):
284
284
 
285
285
  def _rotate_file(self) -> None:
286
286
  """Rotate to a new output file."""
287
- timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
287
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
288
288
  self._current_file = self.output_dir / f"dead-letter-{timestamp}.jsonl"
289
289
  self._current_file_size = 0
290
290
  self._metrics["files_created"] += 1
@@ -293,7 +293,9 @@ class DeadLetterConsumer(IEventConsumer):
293
293
 
294
294
  async def _cleanup_old_files(self) -> None:
295
295
  """Remove files older than retention period."""
296
- cutoff_time = datetime.now().timestamp() - (self.retention_days * 86400)
296
+ cutoff_time = datetime.now(timezone.utc).timestamp() - (
297
+ self.retention_days * 86400
298
+ )
297
299
 
298
300
  for file_path in self.output_dir.glob("dead-letter-*.jsonl"):
299
301
  try:
@@ -12,7 +12,7 @@ import time
12
12
  import uuid
13
13
  from collections import defaultdict, deque
14
14
  from dataclasses import dataclass, field
15
- from datetime import datetime
15
+ from datetime import datetime, timezone
16
16
  from enum import Enum
17
17
  from typing import Any, Deque, Dict, List, Optional, Set
18
18
 
@@ -225,7 +225,7 @@ class EventBus(IEventBus):
225
225
 
226
226
  # Add metadata
227
227
  if event.metadata:
228
- event.metadata.published_at = datetime.now()
228
+ event.metadata.published_at = datetime.now(timezone.utc)
229
229
 
230
230
  # Queue event
231
231
  self._event_queues[event.priority].append(event)
@@ -353,7 +353,7 @@ class EventBus(IEventBus):
353
353
  # Update metrics
354
354
  if events_processed > 0:
355
355
  self._metrics["events_processed"] += events_processed
356
- self._metrics["last_event_time"] = datetime.now()
356
+ self._metrics["last_event_time"] = datetime.now(timezone.utc)
357
357
  self._metrics["queue_size"] = sum(
358
358
  len(q) for q in self._event_queues.values()
359
359
  )
@@ -7,7 +7,7 @@ This replaces direct Socket.IO emission in the hook handler.
7
7
  """
8
8
 
9
9
  import uuid
10
- from datetime import datetime
10
+ from datetime import datetime, timezone
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
  from claude_mpm.core.logging_config import get_logger
@@ -96,7 +96,7 @@ class HookEventProducer(IEventProducer):
96
96
  id=str(uuid.uuid4()),
97
97
  topic="hook.response",
98
98
  type="AssistantResponse",
99
- timestamp=datetime.now(),
99
+ timestamp=datetime.now(timezone.utc),
100
100
  source=self.source_name,
101
101
  data=response_data,
102
102
  correlation_id=correlation_id,
@@ -128,7 +128,7 @@ class HookEventProducer(IEventProducer):
128
128
  id=str(uuid.uuid4()),
129
129
  topic="hook.tool",
130
130
  type="ToolUse",
131
- timestamp=datetime.now(),
131
+ timestamp=datetime.now(timezone.utc),
132
132
  source=self.source_name,
133
133
  data={
134
134
  "tool": tool_name,
@@ -164,7 +164,7 @@ class HookEventProducer(IEventProducer):
164
164
  id=str(uuid.uuid4()),
165
165
  topic="hook.error",
166
166
  type="Error",
167
- timestamp=datetime.now(),
167
+ timestamp=datetime.now(timezone.utc),
168
168
  source=self.source_name,
169
169
  data={
170
170
  "error_type": error_type,
@@ -200,7 +200,7 @@ class HookEventProducer(IEventProducer):
200
200
  id=str(uuid.uuid4()),
201
201
  topic=f"hook.subagent.{event_type.lower()}",
202
202
  type=f"Subagent{event_type}",
203
- timestamp=datetime.now(),
203
+ timestamp=datetime.now(timezone.utc),
204
204
  source=self.source_name,
205
205
  data={
206
206
  "subagent": subagent_name,
@@ -255,7 +255,7 @@ class HookEventProducer(IEventProducer):
255
255
  id=str(uuid.uuid4()),
256
256
  topic=topic,
257
257
  type=hook_type,
258
- timestamp=datetime.now(),
258
+ timestamp=datetime.now(timezone.utc),
259
259
  source=self.source_name,
260
260
  data=hook_data,
261
261
  correlation_id=correlation_id,
@@ -6,7 +6,7 @@ Publishes system-level events to the event bus.
6
6
  """
7
7
 
8
8
  import uuid
9
- from datetime import datetime
9
+ from datetime import datetime, timezone
10
10
  from typing import Any, Dict, List, Optional
11
11
 
12
12
  from claude_mpm.core.logging_config import get_logger
@@ -100,7 +100,7 @@ class SystemEventProducer(IEventProducer):
100
100
  id=str(uuid.uuid4()),
101
101
  topic="system.lifecycle.startup",
102
102
  type="ServiceStartup",
103
- timestamp=datetime.now(),
103
+ timestamp=datetime.now(timezone.utc),
104
104
  source=self.source_name,
105
105
  data={
106
106
  "service": service_name,
@@ -133,7 +133,7 @@ class SystemEventProducer(IEventProducer):
133
133
  id=str(uuid.uuid4()),
134
134
  topic="system.lifecycle.shutdown",
135
135
  type="ServiceShutdown",
136
- timestamp=datetime.now(),
136
+ timestamp=datetime.now(timezone.utc),
137
137
  source=self.source_name,
138
138
  data={
139
139
  "service": service_name,
@@ -168,7 +168,7 @@ class SystemEventProducer(IEventProducer):
168
168
  id=str(uuid.uuid4()),
169
169
  topic="system.health",
170
170
  type="HealthStatus",
171
- timestamp=datetime.now(),
171
+ timestamp=datetime.now(timezone.utc),
172
172
  source=self.source_name,
173
173
  data={
174
174
  "service": service_name,
@@ -206,7 +206,7 @@ class SystemEventProducer(IEventProducer):
206
206
  id=str(uuid.uuid4()),
207
207
  topic="system.config",
208
208
  type="ConfigChange",
209
- timestamp=datetime.now(),
209
+ timestamp=datetime.now(timezone.utc),
210
210
  source=self.source_name,
211
211
  data={
212
212
  "service": service_name,
@@ -238,7 +238,7 @@ class SystemEventProducer(IEventProducer):
238
238
  id=str(uuid.uuid4()),
239
239
  topic="system.performance",
240
240
  type="PerformanceMetrics",
241
- timestamp=datetime.now(),
241
+ timestamp=datetime.now(timezone.utc),
242
242
  source=self.source_name,
243
243
  data={
244
244
  "service": service_name,
@@ -274,7 +274,7 @@ class SystemEventProducer(IEventProducer):
274
274
  id=str(uuid.uuid4()),
275
275
  topic="system.error",
276
276
  type="SystemError",
277
- timestamp=datetime.now(),
277
+ timestamp=datetime.now(timezone.utc),
278
278
  source=self.source_name,
279
279
  data={
280
280
  "service": service_name,
@@ -311,7 +311,7 @@ class SystemEventProducer(IEventProducer):
311
311
  id=str(uuid.uuid4()),
312
312
  topic="system.warning",
313
313
  type="SystemWarning",
314
- timestamp=datetime.now(),
314
+ timestamp=datetime.now(timezone.utc),
315
315
  source=self.source_name,
316
316
  data={
317
317
  "service": service_name,
@@ -15,7 +15,7 @@ Design Principles:
15
15
 
16
16
  import platform
17
17
  import time
18
- from datetime import datetime
18
+ from datetime import datetime, timezone
19
19
  from typing import Any, Dict, List, Optional
20
20
 
21
21
 
@@ -43,7 +43,7 @@ class SocketIOServerError(Exception):
43
43
  self.message = message
44
44
  self.error_code = error_code or self.__class__.__name__.lower()
45
45
  self.context = context or {}
46
- self.timestamp = datetime.utcnow().isoformat() + "Z"
46
+ self.timestamp = datetime.now(timezone.utc).isoformat() + "Z"
47
47
 
48
48
  def to_dict(self) -> Dict[str, Any]:
49
49
  """Convert error to dictionary format for structured logging/handling."""
@@ -125,9 +125,9 @@ class DaemonConflictError(SocketIOServerError):
125
125
  )
126
126
 
127
127
  if create_time:
128
- start_time = datetime.fromtimestamp(create_time).strftime(
129
- "%Y-%m-%d %H:%M:%S"
130
- )
128
+ start_time = datetime.fromtimestamp(
129
+ create_time, tz=timezone.utc
130
+ ).strftime("%Y-%m-%d %H:%M:%S")
131
131
  uptime = time.time() - create_time
132
132
  lines.append(f" • Started: {start_time} (uptime: {uptime:.0f}s)")
133
133