claude-mpm 4.3.11__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 (207) 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/agents/templates/research.json +20 -8
  5. claude_mpm/agents/templates/web_qa.json +25 -10
  6. claude_mpm/cli/__init__.py +1 -0
  7. claude_mpm/cli/commands/agent_manager.py +3 -3
  8. claude_mpm/cli/commands/agents.py +2 -2
  9. claude_mpm/cli/commands/aggregate.py +1 -1
  10. claude_mpm/cli/commands/config.py +2 -2
  11. claude_mpm/cli/commands/configure.py +5 -5
  12. claude_mpm/cli/commands/configure_tui.py +7 -7
  13. claude_mpm/cli/commands/dashboard.py +1 -1
  14. claude_mpm/cli/commands/debug.py +5 -5
  15. claude_mpm/cli/commands/mcp.py +1 -1
  16. claude_mpm/cli/commands/mcp_command_router.py +12 -1
  17. claude_mpm/cli/commands/mcp_config.py +154 -0
  18. claude_mpm/cli/commands/mcp_external_commands.py +249 -0
  19. claude_mpm/cli/commands/mcp_install_commands.py +93 -24
  20. claude_mpm/cli/commands/mcp_setup_external.py +870 -0
  21. claude_mpm/cli/commands/monitor.py +2 -2
  22. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  23. claude_mpm/cli/commands/run.py +114 -0
  24. claude_mpm/cli/commands/search.py +292 -0
  25. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  26. claude_mpm/cli/parsers/base_parser.py +13 -0
  27. claude_mpm/cli/parsers/mcp_parser.py +15 -0
  28. claude_mpm/cli/parsers/run_parser.py +5 -0
  29. claude_mpm/cli/parsers/search_parser.py +245 -0
  30. claude_mpm/cli/startup_logging.py +3 -5
  31. claude_mpm/cli/utils.py +1 -1
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/agent_registry.py +12 -8
  34. claude_mpm/core/agent_session_manager.py +8 -8
  35. claude_mpm/core/api_validator.py +4 -4
  36. claude_mpm/core/base_service.py +10 -10
  37. claude_mpm/core/cache.py +5 -5
  38. claude_mpm/core/config_constants.py +1 -1
  39. claude_mpm/core/container.py +1 -1
  40. claude_mpm/core/error_handler.py +2 -2
  41. claude_mpm/core/file_utils.py +1 -1
  42. claude_mpm/core/framework_loader.py +3 -3
  43. claude_mpm/core/hook_manager.py +8 -6
  44. claude_mpm/core/instruction_reinforcement_hook.py +2 -2
  45. claude_mpm/core/interactive_session.py +1 -1
  46. claude_mpm/core/lazy.py +3 -3
  47. claude_mpm/core/log_manager.py +16 -12
  48. claude_mpm/core/logger.py +16 -11
  49. claude_mpm/core/logging_config.py +4 -2
  50. claude_mpm/core/oneshot_session.py +1 -1
  51. claude_mpm/core/optimized_agent_loader.py +6 -6
  52. claude_mpm/core/output_style_manager.py +1 -1
  53. claude_mpm/core/pm_hook_interceptor.py +3 -3
  54. claude_mpm/core/service_registry.py +1 -1
  55. claude_mpm/core/session_manager.py +11 -9
  56. claude_mpm/core/socketio_pool.py +13 -13
  57. claude_mpm/core/types.py +2 -2
  58. claude_mpm/core/unified_agent_registry.py +9 -2
  59. claude_mpm/core/unified_paths.py +1 -1
  60. claude_mpm/dashboard/analysis_runner.py +4 -4
  61. claude_mpm/dashboard/api/simple_directory.py +1 -1
  62. claude_mpm/generators/agent_profile_generator.py +4 -2
  63. claude_mpm/hooks/base_hook.py +2 -2
  64. claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
  65. claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
  66. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
  67. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
  68. claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
  69. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
  70. claude_mpm/hooks/claude_hooks/installer.py +3 -3
  71. claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
  72. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
  73. claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
  74. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
  75. claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
  76. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
  77. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  78. claude_mpm/hooks/memory_integration_hook.py +1 -1
  79. claude_mpm/hooks/tool_call_interceptor.py +2 -2
  80. claude_mpm/models/agent_session.py +5 -5
  81. claude_mpm/services/__init__.py +1 -1
  82. claude_mpm/services/agent_capabilities_service.py +1 -1
  83. claude_mpm/services/agents/agent_builder.py +3 -3
  84. claude_mpm/services/agents/deployment/agent_deployment.py +29 -13
  85. claude_mpm/services/agents/deployment/agent_discovery_service.py +22 -6
  86. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
  87. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
  88. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  89. claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
  90. claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
  91. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
  92. claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
  93. claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
  94. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
  95. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
  96. claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
  97. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  98. claude_mpm/services/agents/local_template_manager.py +6 -6
  99. claude_mpm/services/agents/management/agent_management_service.py +3 -3
  100. claude_mpm/services/agents/memory/content_manager.py +3 -3
  101. claude_mpm/services/agents/memory/memory_format_service.py +2 -2
  102. claude_mpm/services/agents/memory/template_generator.py +3 -3
  103. claude_mpm/services/agents/registry/__init__.py +1 -1
  104. claude_mpm/services/agents/registry/modification_tracker.py +2 -2
  105. claude_mpm/services/async_session_logger.py +3 -3
  106. claude_mpm/services/claude_session_logger.py +4 -4
  107. claude_mpm/services/cli/agent_cleanup_service.py +5 -0
  108. claude_mpm/services/cli/agent_listing_service.py +1 -1
  109. claude_mpm/services/cli/agent_validation_service.py +1 -0
  110. claude_mpm/services/cli/memory_crud_service.py +11 -6
  111. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  112. claude_mpm/services/cli/session_manager.py +15 -11
  113. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  114. claude_mpm/services/core/memory_manager.py +81 -23
  115. claude_mpm/services/core/path_resolver.py +2 -2
  116. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  117. claude_mpm/services/event_aggregator.py +4 -2
  118. claude_mpm/services/event_bus/direct_relay.py +5 -3
  119. claude_mpm/services/event_bus/event_bus.py +3 -3
  120. claude_mpm/services/event_bus/relay.py +6 -4
  121. claude_mpm/services/events/consumers/dead_letter.py +5 -3
  122. claude_mpm/services/events/core.py +3 -3
  123. claude_mpm/services/events/producers/hook.py +6 -6
  124. claude_mpm/services/events/producers/system.py +8 -8
  125. claude_mpm/services/exceptions.py +5 -5
  126. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
  127. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
  128. claude_mpm/services/hook_installer_service.py +1 -1
  129. claude_mpm/services/infrastructure/context_preservation.py +6 -4
  130. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  131. claude_mpm/services/infrastructure/logging.py +2 -2
  132. claude_mpm/services/mcp_config_manager.py +439 -0
  133. claude_mpm/services/mcp_gateway/__init__.py +1 -1
  134. claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
  135. claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
  136. claude_mpm/services/mcp_gateway/config/configuration.py +18 -1
  137. claude_mpm/services/mcp_gateway/core/base.py +2 -2
  138. claude_mpm/services/mcp_gateway/main.py +52 -0
  139. claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
  140. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
  141. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  142. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
  143. claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
  144. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
  145. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +443 -0
  146. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
  147. claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
  148. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
  149. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
  150. claude_mpm/services/memory/builder.py +7 -5
  151. claude_mpm/services/memory/indexed_memory.py +4 -4
  152. claude_mpm/services/memory/optimizer.py +6 -6
  153. claude_mpm/services/memory/router.py +3 -3
  154. claude_mpm/services/monitor/daemon.py +1 -1
  155. claude_mpm/services/monitor/daemon_manager.py +6 -6
  156. claude_mpm/services/monitor/event_emitter.py +2 -2
  157. claude_mpm/services/monitor/handlers/file.py +1 -1
  158. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  159. claude_mpm/services/monitor/server.py +4 -4
  160. claude_mpm/services/monitor_build_service.py +2 -2
  161. claude_mpm/services/port_manager.py +2 -2
  162. claude_mpm/services/response_tracker.py +2 -2
  163. claude_mpm/services/session_management_service.py +3 -2
  164. claude_mpm/services/socketio/client_proxy.py +2 -2
  165. claude_mpm/services/socketio/dashboard_server.py +4 -3
  166. claude_mpm/services/socketio/event_normalizer.py +12 -8
  167. claude_mpm/services/socketio/handlers/base.py +2 -2
  168. claude_mpm/services/socketio/handlers/connection.py +10 -10
  169. claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
  170. claude_mpm/services/socketio/handlers/file.py +1 -1
  171. claude_mpm/services/socketio/handlers/git.py +1 -1
  172. claude_mpm/services/socketio/handlers/hook.py +16 -15
  173. claude_mpm/services/socketio/migration_utils.py +1 -1
  174. claude_mpm/services/socketio/monitor_client.py +5 -5
  175. claude_mpm/services/socketio/server/broadcaster.py +9 -7
  176. claude_mpm/services/socketio/server/connection_manager.py +2 -2
  177. claude_mpm/services/socketio/server/core.py +7 -5
  178. claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
  179. claude_mpm/services/socketio/server/main.py +13 -13
  180. claude_mpm/services/socketio_client_manager.py +4 -4
  181. claude_mpm/services/system_instructions_service.py +2 -2
  182. claude_mpm/services/ticket_services/validation_service.py +1 -1
  183. claude_mpm/services/utility_service.py +5 -2
  184. claude_mpm/services/version_control/branch_strategy.py +2 -2
  185. claude_mpm/services/version_control/git_operations.py +22 -20
  186. claude_mpm/services/version_control/semantic_versioning.py +3 -3
  187. claude_mpm/services/version_control/version_parser.py +7 -5
  188. claude_mpm/services/visualization/mermaid_generator.py +1 -1
  189. claude_mpm/storage/state_storage.py +1 -1
  190. claude_mpm/tools/code_tree_analyzer.py +19 -18
  191. claude_mpm/tools/code_tree_builder.py +2 -2
  192. claude_mpm/tools/code_tree_events.py +10 -8
  193. claude_mpm/tools/socketio_debug.py +3 -3
  194. claude_mpm/utils/agent_dependency_loader.py +2 -2
  195. claude_mpm/utils/dependency_strategies.py +8 -3
  196. claude_mpm/utils/environment_context.py +2 -2
  197. claude_mpm/utils/error_handler.py +2 -2
  198. claude_mpm/utils/file_utils.py +1 -1
  199. claude_mpm/utils/imports.py +1 -1
  200. claude_mpm/utils/log_cleanup.py +21 -7
  201. claude_mpm/validation/agent_validator.py +2 -2
  202. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +4 -1
  203. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +207 -200
  204. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
  205. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
  206. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
  207. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/top_level.txt +0 -0
@@ -30,7 +30,7 @@ import shutil
30
30
  import time
31
31
  import uuid
32
32
  from dataclasses import asdict, dataclass, field
33
- from datetime import datetime
33
+ from datetime import datetime, timezone
34
34
  from enum import Enum
35
35
  from typing import Any, Callable, Dict, List, Optional, Set, Tuple
36
36
 
@@ -90,7 +90,7 @@ class AgentModification:
90
90
  @property
91
91
  def modification_datetime(self) -> datetime:
92
92
  """Get modification timestamp as datetime."""
93
- return datetime.fromtimestamp(self.timestamp)
93
+ return datetime.fromtimestamp(self.timestamp, tz=timezone.utc)
94
94
 
95
95
  @property
96
96
  def age_seconds(self) -> float:
@@ -22,7 +22,7 @@ import os
22
22
  import sys
23
23
  import time
24
24
  from dataclasses import asdict, dataclass
25
- from datetime import datetime
25
+ from datetime import datetime, timezone
26
26
  from enum import Enum
27
27
  from queue import Full, Queue
28
28
  from threading import Lock, Thread
@@ -184,7 +184,7 @@ class AsyncSessionLogger:
184
184
  return session_id
185
185
 
186
186
  # Generate timestamp-based session ID
187
- session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
187
+ session_id = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
188
188
  logger.info(f"Generated session ID: {session_id}")
189
189
  return session_id
190
190
 
@@ -406,7 +406,7 @@ class AsyncSessionLogger:
406
406
  agent_name = metadata["agent"].replace(" ", "_").lower()
407
407
 
408
408
  # Create timestamp with microsecond precision
409
- now = datetime.now()
409
+ now = datetime.now(timezone.utc)
410
410
  timestamp = now.isoformat()
411
411
  microseconds = now.microsecond
412
412
 
@@ -13,7 +13,7 @@ Configuration via .claude-mpm/configuration.yaml.
13
13
  import json
14
14
  import logging
15
15
  import os
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from typing import Any, Dict, Optional
18
18
 
19
19
  # Import configuration manager
@@ -133,7 +133,7 @@ class ClaudeSessionLogger:
133
133
  # Generate a default based on timestamp if nothing found
134
134
  if not session_id:
135
135
  # Use a timestamp-based session ID as fallback
136
- session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
136
+ session_id = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
137
137
  logger.info(f"No Claude session ID found, using generated: {session_id}")
138
138
  else:
139
139
  logger.info(f"Using Claude session ID: {session_id}")
@@ -156,7 +156,7 @@ class ClaudeSessionLogger:
156
156
  agent_name = agent_name.replace(" ", "_").lower()
157
157
 
158
158
  # Generate timestamp with microseconds for uniqueness
159
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
159
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S_%f")
160
160
 
161
161
  # Create filename: session_id-agent-timestamp.json
162
162
  return f"{self.session_id}-{agent_name}-{timestamp}.json"
@@ -216,7 +216,7 @@ class ClaudeSessionLogger:
216
216
 
217
217
  # Prepare response data with standardized field names
218
218
  response_data = {
219
- "timestamp": datetime.now().isoformat(),
219
+ "timestamp": datetime.now(timezone.utc).isoformat(),
220
220
  "session_id": self.session_id,
221
221
  "request": request_summary, # Standardized field name
222
222
  "response": response_content, # Already correct
@@ -138,6 +138,11 @@ class AgentCleanupService(IAgentCleanupService):
138
138
  if not isinstance(result, dict):
139
139
  result = {"success": bool(result)}
140
140
 
141
+ # Add success flag based on whether there were errors
142
+ if "success" not in result:
143
+ # Consider it successful if no errors occurred
144
+ result["success"] = not bool(result.get("errors"))
145
+
141
146
  # Add cleaned_count for backward compatibility
142
147
  if "cleaned_count" not in result:
143
148
  removed_count = len(result.get("removed", []))
@@ -336,7 +336,7 @@ class AgentListingService(IAgentListingService):
336
336
  self.logger.error(f"Error listing agents by tier: {e}", exc_info=True)
337
337
  return AgentTierInfo(project=[], user=[], system=[])
338
338
 
339
- def get_agent_details(self, agent_name: str) -> Optional[Dict[str, Any]]:
339
+ def get_agent_details(self, agent_name: str) -> Optional[Dict[str, Any]]: # noqa: PLR0911
340
340
  """Get detailed information for a specific agent."""
341
341
  cache_key = f"agent_details_{agent_name}"
342
342
  cached = self._get_from_cache(cache_key)
@@ -1,3 +1,4 @@
1
+ import re
1
2
  """
2
3
  Agent Validation Service
3
4
  ========================
@@ -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
  )