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
@@ -11,7 +11,7 @@ import json
11
11
  import os
12
12
  import re
13
13
  import sys
14
- from datetime import datetime
14
+ from datetime import datetime, timezone
15
15
  from typing import Optional, Tuple
16
16
 
17
17
  # Debug mode is enabled by default for better visibility into hook processing
@@ -272,7 +272,7 @@ class SubagentResponseProcessor:
272
272
  "duration_ms": event.get("duration_ms"),
273
273
  "working_directory": working_dir,
274
274
  "git_branch": git_branch,
275
- "timestamp": datetime.now().isoformat(),
275
+ "timestamp": datetime.now(timezone.utc).isoformat(),
276
276
  "event_type": "subagent_stop",
277
277
  "reason": reason,
278
278
  "original_request_timestamp": request_info.get("timestamp"),
@@ -347,7 +347,7 @@ class SubagentResponseProcessor:
347
347
  "session_id": session_id,
348
348
  "working_directory": working_dir,
349
349
  "git_branch": git_branch,
350
- "timestamp": datetime.now().isoformat(),
350
+ "timestamp": datetime.now(timezone.utc).isoformat(),
351
351
  "is_successful_completion": reason in ["completed", "finished", "done"],
352
352
  "is_error_termination": reason in ["error", "timeout", "failed", "blocked"],
353
353
  "is_delegation_related": agent_type
@@ -138,7 +138,7 @@ def summarize_todos(todos: list) -> dict:
138
138
  }
139
139
 
140
140
 
141
- def classify_tool_operation(tool_name: str, tool_input: dict) -> str:
141
+ def classify_tool_operation(tool_name: str, tool_input: dict) -> str: # noqa: PLR0911
142
142
  """Classify the type of operation being performed."""
143
143
  if tool_name in ["Read", "LS", "Glob", "Grep", "NotebookRead"]:
144
144
  return "read"
@@ -155,7 +155,7 @@ def classify_tool_operation(tool_name: str, tool_input: dict) -> str:
155
155
  return "other"
156
156
 
157
157
 
158
- def assess_security_risk(tool_name: str, tool_input: dict) -> str:
158
+ def assess_security_risk(tool_name: str, tool_input: dict) -> str: # noqa: PLR0911
159
159
  """Assess the security risk level of the tool operation."""
160
160
  if tool_name == "Bash":
161
161
  command = tool_input.get("command", "").lower()
@@ -234,7 +234,7 @@ class MemoryPostDelegationHook(PostDelegationHook):
234
234
  "context": "context", # Current Technical Context
235
235
  }
236
236
 
237
- def execute(self, context: HookContext) -> HookResult:
237
+ def execute(self, context: HookContext) -> HookResult: # noqa: PLR0911
238
238
  """Extract and store learnings from delegation result.
239
239
 
240
240
  WHY: Capturing learnings immediately after task completion ensures we
@@ -2,7 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  from collections import defaultdict
5
- from datetime import datetime
5
+ from datetime import datetime, timezone
6
6
  from typing import Any, Dict, List, Optional
7
7
 
8
8
  from claude_mpm.core.logger import get_logger
@@ -105,7 +105,7 @@ class ToolCallInterceptor:
105
105
  "parameters": parameters.copy(), # Copy to avoid modifying original
106
106
  },
107
107
  metadata=metadata or {},
108
- timestamp=datetime.now(),
108
+ timestamp=datetime.now(timezone.utc),
109
109
  )
110
110
 
111
111
  # Run hooks
@@ -13,7 +13,7 @@ chronological order for session replay and analysis.
13
13
 
14
14
  import json
15
15
  from dataclasses import asdict, dataclass, field
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from enum import Enum
18
18
  from typing import Any, Dict, List, Optional, Set
19
19
 
@@ -204,7 +204,7 @@ class AgentSession:
204
204
  and metric updates.
205
205
  """
206
206
  if timestamp is None:
207
- timestamp = datetime.utcnow().isoformat() + "Z"
207
+ timestamp = datetime.now(timezone.utc).isoformat() + "Z"
208
208
 
209
209
  # Categorize the event
210
210
  category = self._categorize_event(event_type, data)
@@ -232,7 +232,7 @@ class AgentSession:
232
232
 
233
233
  return event
234
234
 
235
- def _categorize_event(self, event_type: str, data: Dict[str, Any]) -> EventCategory:
235
+ def _categorize_event(self, event_type: str, data: Dict[str, Any]) -> EventCategory: # noqa: PLR0911
236
236
  """Categorize an event based on its type and data.
237
237
 
238
238
  WHY: Categories help with filtering and analysis of related events.
@@ -338,7 +338,7 @@ class AgentSession:
338
338
  event.timestamp.replace("Z", "+00:00")
339
339
  )
340
340
  tool_op.duration_ms = int((end - start).total_seconds() * 1000)
341
- except:
341
+ except Exception:
342
342
  pass
343
343
 
344
344
  event.correlation_id = corr_id
@@ -393,7 +393,7 @@ class AgentSession:
393
393
  self.metrics.session_duration_ms = int(
394
394
  (end - start).total_seconds() * 1000
395
395
  )
396
- except:
396
+ except Exception:
397
397
  pass
398
398
 
399
399
  # Finalize any pending delegations
@@ -13,7 +13,7 @@ New structure:
13
13
 
14
14
 
15
15
  # Use lazy imports to prevent circular dependency issues
16
- def __getattr__(name):
16
+ def __getattr__(name): # noqa: PLR0911
17
17
  """Lazy import to prevent circular dependencies."""
18
18
  if name == "TicketManager":
19
19
  from .ticket_manager import TicketManager
@@ -215,7 +215,7 @@ class AgentCapabilitiesService(BaseService, AgentCapabilitiesInterface):
215
215
  self.logger.debug(f"Could not parse agent {agent_file}: {e}")
216
216
  continue
217
217
 
218
- def _categorize_agent(self, agent_id: str, content: str) -> str:
218
+ def _categorize_agent(self, agent_id: str, content: str) -> str: # noqa: PLR0911
219
219
  """Categorize an agent based on its ID and content."""
220
220
  agent_id_lower = agent_id.lower()
221
221
  content_lower = content.lower()
@@ -10,7 +10,7 @@ This service provides comprehensive agent lifecycle management including:
10
10
 
11
11
  import json
12
12
  import re
13
- from datetime import datetime
13
+ from datetime import datetime, timezone
14
14
  from pathlib import Path
15
15
  from typing import Any, Dict, List, Optional, Tuple
16
16
 
@@ -108,7 +108,7 @@ class AgentBuilderService:
108
108
  agent_metadata = {
109
109
  "description": description,
110
110
  "version": "1.0.0",
111
- "created": datetime.now().isoformat(),
111
+ "created": datetime.now(timezone.utc).isoformat(),
112
112
  "author": "Agent Manager",
113
113
  "category": "custom",
114
114
  }
@@ -179,7 +179,7 @@ class AgentBuilderService:
179
179
  {
180
180
  "base_agent": base_agent_id,
181
181
  "variant": True,
182
- "variant_created": datetime.now().isoformat(),
182
+ "variant_created": datetime.now(timezone.utc).isoformat(),
183
183
  }
184
184
  )
185
185
 
@@ -776,16 +776,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
776
776
  agent_sources=agent_sources,
777
777
  )
778
778
 
779
- # Log version upgrades and source changes
780
- if comparison_results.get("version_upgrades"):
781
- self.logger.info(
782
- f"Version upgrades available for {len(comparison_results['version_upgrades'])} agents"
783
- )
784
- if comparison_results.get("source_changes"):
785
- self.logger.info(
786
- f"Source changes for {len(comparison_results['source_changes'])} agents"
787
- )
788
-
789
779
  # Filter agents based on comparison results (unless force_rebuild is set)
790
780
  if not force_rebuild:
791
781
  # Only deploy agents that need updates or are new
@@ -804,11 +794,37 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
804
794
  for name, path in agents_to_deploy.items()
805
795
  if name in agents_needing_update
806
796
  }
797
+
798
+ # Only log upgrade messages if we're actually going to deploy them
799
+ if filtered_agents and comparison_results.get("version_upgrades"):
800
+ # Filter upgrades to only those actually being deployed
801
+ deployed_upgrades = [
802
+ upgrade
803
+ for upgrade in comparison_results["version_upgrades"]
804
+ if upgrade["name"] in filtered_agents
805
+ ]
806
+
807
+ if deployed_upgrades:
808
+ self.logger.info(
809
+ f"Deploying {len(deployed_upgrades)} agent upgrade(s):"
810
+ )
811
+ for upgrade in deployed_upgrades:
812
+ self.logger.info(
813
+ f" Upgrading: {upgrade['name']} "
814
+ f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
815
+ f"(from {upgrade['source']})"
816
+ )
817
+
807
818
  agents_to_deploy = filtered_agents
808
819
 
809
- self.logger.info(
810
- f"Filtered to {len(agents_to_deploy)} agents needing deployment"
811
- )
820
+ if agents_to_deploy:
821
+ self.logger.info(
822
+ f"Deploying {len(agents_to_deploy)} agents that need updates"
823
+ )
824
+ else:
825
+ self.logger.debug(
826
+ f"All {len(comparison_results.get('up_to_date', []))} agents are up to date"
827
+ )
812
828
 
813
829
  # Convert to list of Path objects
814
830
  template_files = list(agents_to_deploy.values())
@@ -216,25 +216,41 @@ class AgentDiscoveryService:
216
216
  metadata = template_data.get("metadata", {})
217
217
  capabilities = template_data.get("capabilities", {})
218
218
 
219
+ # Handle capabilities as either dict or list
220
+ if isinstance(capabilities, list):
221
+ # If capabilities is a list (like in php-engineer.json), treat it as capabilities list
222
+ tools_list = template_data.get(
223
+ "tools", []
224
+ ) # Look for tools at root level
225
+ model_value = template_data.get("model", "sonnet")
226
+ else:
227
+ # If capabilities is a dict, extract tools and model from it
228
+ tools_list = capabilities.get("tools", [])
229
+ model_value = capabilities.get("model", "sonnet")
230
+
219
231
  agent_info = {
220
232
  "name": metadata.get("name", template_file.stem),
221
- "description": metadata.get("description", "No description available"),
233
+ "description": metadata.get(
234
+ "description",
235
+ template_data.get("description", "No description available"),
236
+ ),
222
237
  "type": template_data.get(
223
- "agent_type", metadata.get("category", "agent")
238
+ "agent_type",
239
+ metadata.get("category", template_data.get("category", "agent")),
224
240
  ), # Extract agent type
225
241
  "version": template_data.get(
226
242
  "agent_version",
227
243
  template_data.get("version", metadata.get("version", "1.0.0")),
228
244
  ),
229
- "tools": capabilities.get("tools", []),
245
+ "tools": tools_list,
230
246
  "specializations": metadata.get(
231
- "tags", []
232
- ), # Use tags as specializations
247
+ "tags", template_data.get("tags", [])
248
+ ), # Use tags as specializations, fallback to root-level tags
233
249
  "file": template_file.name,
234
250
  "path": str(template_file),
235
251
  "file_path": str(template_file), # Keep for backward compatibility
236
252
  "size": template_file.stat().st_size,
237
- "model": capabilities.get("model", "sonnet"),
253
+ "model": model_value,
238
254
  "author": metadata.get("author", "unknown"),
239
255
  }
240
256
 
@@ -183,9 +183,11 @@ class AgentFileSystemManager:
183
183
  try:
184
184
  # Generate backup directory name if not provided
185
185
  if not backup_dir:
186
- import datetime
186
+ from datetime import datetime, timezone
187
187
 
188
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
188
+ timestamp = datetime.now(timezone.utc).strftime(
189
+ "%Y%m%d_%H%M%S"
190
+ )
189
191
  backup_dir = agents_dir.parent / f"agents_backup_{timestamp}"
190
192
 
191
193
  # Create backup
@@ -324,7 +326,7 @@ class AgentFileSystemManager:
324
326
 
325
327
  def _convert_yaml_to_markdown(self, yaml_content: str, agent_name: str) -> str:
326
328
  """Convert YAML agent content to Markdown format with frontmatter."""
327
- from datetime import datetime
329
+ from datetime import datetime, timezone
328
330
 
329
331
  # Extract YAML fields (simplified parsing)
330
332
  name = self._extract_yaml_field(yaml_content, "name") or agent_name
@@ -356,8 +358,8 @@ name: {name}
356
358
  description: "{description}"
357
359
  version: "{version}"
358
360
  author: "claude-mpm@anthropic.com"
359
- created: "{datetime.now().isoformat()}Z"
360
- updated: "{datetime.now().isoformat()}Z"
361
+ created: "{datetime.now(timezone.utc).isoformat()}Z"
362
+ updated: "{datetime.now(timezone.utc).isoformat()}Z"
361
363
  tags: ["{agent_name}", "mpm-framework"]
362
364
  tools: {tools_list}
363
365
  model: "sonnet"
@@ -32,6 +32,7 @@ Created for ISS-0118: Agent Registry and Hierarchical Discovery System
32
32
 
33
33
  import asyncio
34
34
  import time
35
+ from datetime import datetime, timezone
35
36
  from pathlib import Path
36
37
  from typing import Any, Dict, List, Optional
37
38
 
@@ -52,6 +53,7 @@ from claude_mpm.services.agents.registry.modification_tracker import (
52
53
  ModificationType,
53
54
  )
54
55
  from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
56
+ from claude_mpm.core.unified_paths import get_path_manager
55
57
  from claude_mpm.utils.path_operations import path_ops
56
58
 
57
59
  # Import extracted services
@@ -758,7 +760,7 @@ class AgentLifecycleManager(BaseService):
758
760
  backup_dir = get_path_manager().get_tracking_dir() / "backups"
759
761
  path_ops.ensure_dir(backup_dir)
760
762
 
761
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
763
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
762
764
  backup_filename = f"{agent_name}_deleted_{timestamp}{source_path.suffix}"
763
765
  backup_path = backup_dir / backup_filename
764
766
 
@@ -227,7 +227,7 @@ class AgentMetricsCollector:
227
227
  return "validation_error"
228
228
  return "other_error"
229
229
 
230
- def _extract_agent_type(self, agent_name: str) -> str:
230
+ def _extract_agent_type(self, agent_name: str) -> str: # noqa: PLR0911
231
231
  """
232
232
  Extract agent type from agent name for categorization.
233
233
 
@@ -479,9 +479,9 @@ class AgentOperationService(BaseService):
479
479
  backup_dir = get_path_manager().get_tracking_dir() / "backups"
480
480
  path_ops.ensure_dir(backup_dir)
481
481
 
482
- from datetime import datetime
482
+ from datetime import datetime, timezone
483
483
 
484
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
484
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
485
485
  backup_filename = f"{agent_name}_deleted_{timestamp}{source_path.suffix}"
486
486
  backup_path = backup_dir / backup_filename
487
487
 
@@ -59,9 +59,9 @@ class AgentLifecycleRecord:
59
59
  @property
60
60
  def last_modified_datetime(self):
61
61
  """Get last modified as datetime."""
62
- from datetime import datetime
62
+ from datetime import datetime, timezone
63
63
 
64
- return datetime.fromtimestamp(self.last_modified)
64
+ return datetime.fromtimestamp(self.last_modified, tz=timezone.utc)
65
65
 
66
66
 
67
67
  @dataclass
@@ -230,7 +230,7 @@ class AgentTemplateBuilder:
230
230
  )
231
231
 
232
232
  # Convert tools list to comma-separated string (without spaces for compatibility)
233
- tools_str = ",".join(tools)
233
+ ",".join(tools)
234
234
 
235
235
  # Map model names to Claude Code format (as required)
236
236
  model_map = {
@@ -30,5 +30,5 @@ class AgentVersionManager:
30
30
  for part in parts:
31
31
  int(part)
32
32
  return True
33
- except:
33
+ except Exception:
34
34
  return False
@@ -1,5 +1,6 @@
1
1
  """Temporary wrapper to provide backward compatibility for CLI commands."""
2
2
 
3
+ import contextlib
3
4
  from pathlib import Path
4
5
  from typing import Any, Dict
5
6
 
@@ -101,10 +102,8 @@ class DeploymentServiceWrapper:
101
102
  # Extract frontmatter
102
103
  parts = content.split("---", 2)
103
104
  if len(parts) >= 2:
104
- try:
105
+ with contextlib.suppress(yaml.YAMLError):
105
106
  metadata = yaml.safe_load(parts[1])
106
- except yaml.YAMLError:
107
- pass
108
107
 
109
108
  return {
110
109
  "name": agent_name,
@@ -742,18 +742,20 @@ class MultiSourceAgentDeploymentService:
742
742
 
743
743
  self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
744
744
 
745
+ # Don't log upgrades here - let the caller decide when to log
746
+ # This prevents repeated upgrade messages on every startup
745
747
  if comparison_results["version_upgrades"]:
746
748
  for upgrade in comparison_results["version_upgrades"]:
747
- self.logger.info(
748
- f" Upgrade: {upgrade['name']} "
749
+ self.logger.debug(
750
+ f" Upgrade available: {upgrade['name']} "
749
751
  f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
750
752
  f"(from {upgrade['source']})"
751
753
  )
752
754
 
753
755
  if comparison_results["source_changes"]:
754
756
  for change in comparison_results["source_changes"]:
755
- self.logger.info(
756
- f" Source change: {change['name']} "
757
+ self.logger.debug(
758
+ f" Source change available: {change['name']} "
757
759
  f"from {change['from_source']} to {change['to_source']}"
758
760
  )
759
761
 
@@ -229,7 +229,7 @@ class AgentProcessingStep(BaseDeploymentStep):
229
229
  cwd = Path.cwd()
230
230
  if str(cwd) in template_str:
231
231
  return "project"
232
- except:
232
+ except Exception:
233
233
  pass
234
234
 
235
235
  # Check if it's a user agent
@@ -24,7 +24,7 @@ import json
24
24
  import logging
25
25
  import os
26
26
  from dataclasses import dataclass, field
27
- from datetime import datetime
27
+ from datetime import datetime, timezone
28
28
  from enum import Enum
29
29
  from pathlib import Path
30
30
  from typing import Any, Dict, List, Optional, Tuple
@@ -227,7 +227,9 @@ class AgentProfileLoader(BaseService):
227
227
  # Check cache first
228
228
  if use_cache and agent_name in self.profile_cache:
229
229
  profile = self.profile_cache[agent_name]
230
- if (datetime.now() - profile.loaded_at).seconds < self.cache_ttl:
230
+ if (
231
+ datetime.now(timezone.utc) - profile.loaded_at
232
+ ).seconds < self.cache_ttl:
231
233
  self.load_metrics[f"{agent_name}_cache_hit"] = (
232
234
  asyncio.get_event_loop().time() - start_time
233
235
  )
@@ -322,7 +324,7 @@ class AgentProfileLoader(BaseService):
322
324
  try:
323
325
  data = yaml.safe_load(content)
324
326
  instructions = data.get("instructions", "")
325
- except:
327
+ except Exception:
326
328
  data = json.loads(content)
327
329
  instructions = data.get("instructions", "")
328
330
 
@@ -9,7 +9,7 @@ Enforces template structure and provides section-specific update methods.
9
9
 
10
10
  import logging
11
11
  from dataclasses import dataclass, field
12
- from datetime import datetime
12
+ from datetime import datetime, timezone
13
13
  from enum import Enum
14
14
  from pathlib import Path
15
15
  from typing import Any, Dict, List, Optional
@@ -358,7 +358,7 @@ class BaseAgentManager(ConfigServiceBase):
358
358
 
359
359
  def _create_backup(self) -> Path:
360
360
  """Create a timestamped backup of base_agent.md."""
361
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
361
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
362
362
  backup_path = self.base_agent_path.parent / f"base_agent_{timestamp}.backup"
363
363
 
364
364
  if self.base_agent_path.exists():
@@ -15,7 +15,7 @@ Key Features:
15
15
 
16
16
  import json
17
17
  from dataclasses import dataclass
18
- from datetime import datetime
18
+ from datetime import datetime, timezone
19
19
  from pathlib import Path
20
20
  from typing import Any, Dict, List, Optional, Tuple, Union
21
21
 
@@ -57,9 +57,9 @@ class LocalAgentTemplate:
57
57
  if self.configuration is None:
58
58
  self.configuration = {}
59
59
  if self.created_at is None:
60
- self.created_at = datetime.now().isoformat()
60
+ self.created_at = datetime.now(timezone.utc).isoformat()
61
61
  if self.updated_at is None:
62
- self.updated_at = datetime.now().isoformat()
62
+ self.updated_at = datetime.now(timezone.utc).isoformat()
63
63
 
64
64
  # Ensure metadata has required fields
65
65
  if "name" not in self.metadata:
@@ -303,7 +303,7 @@ class LocalAgentTemplateManager:
303
303
  target_dir.mkdir(parents=True, exist_ok=True)
304
304
 
305
305
  # Update timestamp
306
- template.updated_at = datetime.now().isoformat()
306
+ template.updated_at = datetime.now(timezone.utc).isoformat()
307
307
 
308
308
  # Save to JSON file
309
309
  template_file = target_dir / f"{template.agent_id}.json"
@@ -455,7 +455,7 @@ class LocalAgentTemplateManager:
455
455
 
456
456
  try:
457
457
  # Create backup directory
458
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
458
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
459
459
  backup_dir = (
460
460
  self.working_directory
461
461
  / ".claude-mpm"
@@ -633,7 +633,7 @@ class LocalAgentTemplateManager:
633
633
 
634
634
  # Update template version
635
635
  template.agent_version = new_version
636
- template.updated_at = datetime.now().isoformat()
636
+ template.updated_at = datetime.now(timezone.utc).isoformat()
637
637
 
638
638
  # Save updated template
639
639
  self.save_local_template(template)
@@ -13,7 +13,7 @@ Uses python-frontmatter and mistune for markdown parsing as recommended.
13
13
 
14
14
  import logging
15
15
  import re
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from typing import Any, Dict, List, Optional
18
18
 
19
19
  import frontmatter
@@ -156,7 +156,7 @@ class AgentManager:
156
156
  # Increment version if requested
157
157
  if increment_version:
158
158
  agent_def.metadata.increment_serial_version()
159
- agent_def.metadata.last_updated = datetime.now()
159
+ agent_def.metadata.last_updated = datetime.now(timezone.utc)
160
160
 
161
161
  # Write back
162
162
  agent_path = self._find_agent_file(name)
@@ -234,7 +234,7 @@ class AgentManager:
234
234
  # Increment version
235
235
  if increment_version:
236
236
  agent_def.metadata.increment_serial_version()
237
- agent_def.metadata.last_updated = datetime.now()
237
+ agent_def.metadata.last_updated = datetime.now(timezone.utc)
238
238
 
239
239
  # Write back
240
240
  return self.update_agent(name, {}, increment_version=False)
@@ -14,7 +14,7 @@ This module provides:
14
14
 
15
15
  import logging
16
16
  import re
17
- from datetime import datetime
17
+ from datetime import datetime, timezone
18
18
  from difflib import SequenceMatcher
19
19
  from typing import Any, Dict, List, Optional, Tuple
20
20
 
@@ -203,7 +203,7 @@ class MemoryContentManager:
203
203
  Returns:
204
204
  str: Content with updated timestamp
205
205
  """
206
- timestamp = datetime.now().isoformat() + "Z"
206
+ timestamp = datetime.now(timezone.utc).isoformat() + "Z"
207
207
  # Handle both old and new timestamp formats
208
208
  content = re.sub(
209
209
  r"<!-- Last Updated: .+? -->",
@@ -260,7 +260,7 @@ class MemoryContentManager:
260
260
 
261
261
  if not has_timestamp:
262
262
  new_lines.append(
263
- f"<!-- Last Updated: {datetime.now().isoformat()}Z -->"
263
+ f"<!-- Last Updated: {datetime.now(timezone.utc).isoformat()}Z -->"
264
264
  )
265
265
  new_lines.append("")
266
266
  else:
@@ -3,7 +3,7 @@
3
3
 
4
4
  import logging
5
5
  import re
6
- from datetime import datetime
6
+ from datetime import datetime, timezone
7
7
  from typing import Dict, List
8
8
 
9
9
 
@@ -26,7 +26,7 @@ class MemoryFormatService:
26
26
  """
27
27
  # Build header
28
28
  header = f"# {agent_id.title()} Agent Memory\n\n"
29
- header += f"Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
29
+ header += f"Last Updated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}\n\n"
30
30
  header += "## Learnings\n\n"
31
31
 
32
32
  # Build item list
@@ -13,7 +13,7 @@ This module provides:
13
13
  """
14
14
 
15
15
  import logging
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from pathlib import Path
18
18
  from typing import Any, Dict
19
19
 
@@ -58,7 +58,7 @@ class MemoryTemplateGenerator:
58
58
  """
59
59
  # Convert agent_id to proper name, handling cases like "test_agent" -> "Test"
60
60
  agent_id.replace("_agent", "").replace("_", " ").title()
61
- datetime.now().strftime("%Y-%m-%d %H:%M:%S")
61
+ datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
62
62
 
63
63
  # Create a simple template that agents will populate through learning
64
64
  return self._create_basic_memory_template(agent_id, limits)
@@ -75,7 +75,7 @@ class MemoryTemplateGenerator:
75
75
  Returns:
76
76
  str: Basic memory template
77
77
  """
78
- timestamp = datetime.now().isoformat() + "Z"
78
+ timestamp = datetime.now(timezone.utc).isoformat() + "Z"
79
79
 
80
80
  return f"""# Agent Memory: {agent_id}
81
81
  <!-- Last Updated: {timestamp} -->
@@ -4,8 +4,8 @@ from claude_mpm.core.unified_agent_registry import (
4
4
  AgentMetadata,
5
5
  AgentTier,
6
6
  AgentType,
7
- UnifiedAgentRegistry as AgentRegistry,
8
7
  )
8
+ from claude_mpm.core.unified_agent_registry import UnifiedAgentRegistry as AgentRegistry
9
9
 
10
10
  from .deployed_agent_discovery import DeployedAgentDiscovery
11
11
  from .modification_tracker import (