claude-mpm 4.5.8__py3-none-any.whl → 4.5.12__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 (226) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +20 -5
  3. claude_mpm/agents/agent_loader.py +19 -2
  4. claude_mpm/agents/base_agent_loader.py +5 -5
  5. claude_mpm/agents/frontmatter_validator.py +4 -4
  6. claude_mpm/agents/templates/agent-manager.json +3 -3
  7. claude_mpm/agents/templates/agentic-coder-optimizer.json +3 -3
  8. claude_mpm/agents/templates/api_qa.json +1 -1
  9. claude_mpm/agents/templates/clerk-ops.json +3 -3
  10. claude_mpm/agents/templates/code_analyzer.json +3 -3
  11. claude_mpm/agents/templates/dart_engineer.json +294 -0
  12. claude_mpm/agents/templates/data_engineer.json +3 -3
  13. claude_mpm/agents/templates/documentation.json +2 -2
  14. claude_mpm/agents/templates/engineer.json +2 -2
  15. claude_mpm/agents/templates/gcp_ops_agent.json +2 -2
  16. claude_mpm/agents/templates/imagemagick.json +1 -1
  17. claude_mpm/agents/templates/local_ops_agent.json +319 -41
  18. claude_mpm/agents/templates/memory_manager.json +2 -2
  19. claude_mpm/agents/templates/nextjs_engineer.json +2 -2
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/php-engineer.json +1 -1
  22. claude_mpm/agents/templates/project_organizer.json +1 -1
  23. claude_mpm/agents/templates/prompt-engineer.json +6 -4
  24. claude_mpm/agents/templates/python_engineer.json +2 -2
  25. claude_mpm/agents/templates/qa.json +1 -1
  26. claude_mpm/agents/templates/react_engineer.json +3 -3
  27. claude_mpm/agents/templates/refactoring_engineer.json +3 -3
  28. claude_mpm/agents/templates/research.json +2 -2
  29. claude_mpm/agents/templates/security.json +2 -2
  30. claude_mpm/agents/templates/ticketing.json +2 -2
  31. claude_mpm/agents/templates/typescript_engineer.json +2 -2
  32. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  33. claude_mpm/agents/templates/version_control.json +2 -2
  34. claude_mpm/agents/templates/web_qa.json +6 -6
  35. claude_mpm/agents/templates/web_ui.json +3 -3
  36. claude_mpm/cli/__init__.py +49 -19
  37. claude_mpm/cli/commands/agent_manager.py +3 -3
  38. claude_mpm/cli/commands/agents.py +6 -6
  39. claude_mpm/cli/commands/aggregate.py +4 -4
  40. claude_mpm/cli/commands/analyze.py +2 -2
  41. claude_mpm/cli/commands/analyze_code.py +1 -1
  42. claude_mpm/cli/commands/cleanup.py +3 -3
  43. claude_mpm/cli/commands/config.py +2 -2
  44. claude_mpm/cli/commands/configure.py +605 -21
  45. claude_mpm/cli/commands/dashboard.py +1 -1
  46. claude_mpm/cli/commands/debug.py +3 -3
  47. claude_mpm/cli/commands/doctor.py +1 -1
  48. claude_mpm/cli/commands/mcp.py +7 -7
  49. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  50. claude_mpm/cli/commands/mcp_config.py +2 -2
  51. claude_mpm/cli/commands/mcp_external_commands.py +2 -2
  52. claude_mpm/cli/commands/mcp_install_commands.py +3 -3
  53. claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
  54. claude_mpm/cli/commands/mcp_setup_external.py +3 -3
  55. claude_mpm/cli/commands/monitor.py +1 -1
  56. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  57. claude_mpm/cli/interactive/agent_wizard.py +1 -1
  58. claude_mpm/cli/parsers/configure_parser.py +5 -0
  59. claude_mpm/cli/parsers/search_parser.py +1 -1
  60. claude_mpm/cli/shared/argument_patterns.py +2 -2
  61. claude_mpm/cli/shared/base_command.py +1 -1
  62. claude_mpm/cli/startup_logging.py +4 -4
  63. claude_mpm/config/experimental_features.py +4 -4
  64. claude_mpm/config/socketio_config.py +2 -2
  65. claude_mpm/core/__init__.py +53 -17
  66. claude_mpm/core/agent_session_manager.py +2 -2
  67. claude_mpm/core/api_validator.py +3 -3
  68. claude_mpm/core/base_service.py +10 -1
  69. claude_mpm/core/cache.py +2 -2
  70. claude_mpm/core/config.py +5 -5
  71. claude_mpm/core/config_aliases.py +4 -4
  72. claude_mpm/core/config_constants.py +1 -1
  73. claude_mpm/core/error_handler.py +1 -1
  74. claude_mpm/core/file_utils.py +5 -5
  75. claude_mpm/core/framework/formatters/capability_generator.py +5 -5
  76. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  77. claude_mpm/core/framework/processors/metadata_processor.py +1 -1
  78. claude_mpm/core/framework/processors/template_processor.py +3 -3
  79. claude_mpm/core/framework_loader.py +2 -2
  80. claude_mpm/core/log_manager.py +11 -4
  81. claude_mpm/core/logger.py +2 -2
  82. claude_mpm/core/optimized_startup.py +1 -1
  83. claude_mpm/core/output_style_manager.py +1 -1
  84. claude_mpm/core/service_registry.py +2 -2
  85. claude_mpm/core/session_manager.py +3 -3
  86. claude_mpm/core/shared/config_loader.py +1 -1
  87. claude_mpm/core/socketio_pool.py +2 -2
  88. claude_mpm/core/unified_agent_registry.py +2 -2
  89. claude_mpm/core/unified_config.py +6 -6
  90. claude_mpm/core/unified_paths.py +2 -2
  91. claude_mpm/dashboard/api/simple_directory.py +1 -1
  92. claude_mpm/generators/agent_profile_generator.py +1 -1
  93. claude_mpm/hooks/claude_hooks/event_handlers.py +2 -2
  94. claude_mpm/hooks/claude_hooks/installer.py +9 -9
  95. claude_mpm/hooks/claude_hooks/response_tracking.py +16 -11
  96. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +16 -13
  97. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  98. claude_mpm/hooks/memory_integration_hook.py +1 -1
  99. claude_mpm/hooks/validation_hooks.py +1 -1
  100. claude_mpm/init.py +4 -4
  101. claude_mpm/models/agent_session.py +1 -1
  102. claude_mpm/scripts/socketio_daemon.py +5 -5
  103. claude_mpm/services/__init__.py +145 -161
  104. claude_mpm/services/agent_capabilities_service.py +1 -1
  105. claude_mpm/services/agents/agent_builder.py +4 -4
  106. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -1
  107. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  108. claude_mpm/services/agents/deployment/agent_record_service.py +3 -3
  109. claude_mpm/services/agents/deployment/deployment_config_loader.py +21 -0
  110. claude_mpm/services/agents/deployment/deployment_wrapper.py +1 -1
  111. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +2 -2
  112. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  113. claude_mpm/services/agents/loading/base_agent_manager.py +12 -2
  114. claude_mpm/services/agents/local_template_manager.py +5 -5
  115. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  116. claude_mpm/services/agents/registry/modification_tracker.py +19 -11
  117. claude_mpm/services/async_session_logger.py +3 -3
  118. claude_mpm/services/claude_session_logger.py +4 -4
  119. claude_mpm/services/cli/agent_listing_service.py +3 -3
  120. claude_mpm/services/cli/agent_validation_service.py +1 -1
  121. claude_mpm/services/cli/session_manager.py +2 -2
  122. claude_mpm/services/core/path_resolver.py +1 -1
  123. claude_mpm/services/diagnostics/checks/agent_check.py +1 -1
  124. claude_mpm/services/diagnostics/checks/claude_code_check.py +2 -2
  125. claude_mpm/services/diagnostics/checks/common_issues_check.py +3 -3
  126. claude_mpm/services/diagnostics/checks/configuration_check.py +2 -2
  127. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  128. claude_mpm/services/diagnostics/checks/mcp_check.py +1 -1
  129. claude_mpm/services/diagnostics/checks/mcp_services_check.py +9 -9
  130. claude_mpm/services/diagnostics/checks/monitor_check.py +1 -1
  131. claude_mpm/services/diagnostics/doctor_reporter.py +1 -1
  132. claude_mpm/services/event_aggregator.py +1 -1
  133. claude_mpm/services/event_bus/event_bus.py +7 -2
  134. claude_mpm/services/events/consumers/dead_letter.py +2 -2
  135. claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
  136. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
  137. claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
  138. claude_mpm/services/hook_installer_service.py +7 -7
  139. claude_mpm/services/infrastructure/context_preservation.py +7 -7
  140. claude_mpm/services/infrastructure/daemon_manager.py +5 -5
  141. claude_mpm/services/mcp_config_manager.py +169 -48
  142. claude_mpm/services/mcp_gateway/__init__.py +98 -94
  143. claude_mpm/services/mcp_gateway/auto_configure.py +5 -5
  144. claude_mpm/services/mcp_gateway/config/config_loader.py +2 -2
  145. claude_mpm/services/mcp_gateway/config/configuration.py +3 -3
  146. claude_mpm/services/mcp_gateway/core/process_pool.py +3 -3
  147. claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
  148. claude_mpm/services/mcp_gateway/core/startup_verification.py +1 -1
  149. claude_mpm/services/mcp_gateway/main.py +1 -1
  150. claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
  151. claude_mpm/services/mcp_gateway/registry/tool_registry.py +2 -1
  152. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  153. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -1
  154. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +1 -1
  155. claude_mpm/services/mcp_gateway/tools/hello_world.py +1 -1
  156. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +5 -5
  157. claude_mpm/services/mcp_gateway/utils/update_preferences.py +2 -2
  158. claude_mpm/services/mcp_service_verifier.py +1 -1
  159. claude_mpm/services/memory/builder.py +1 -1
  160. claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
  161. claude_mpm/services/memory/indexed_memory.py +3 -3
  162. claude_mpm/services/monitor/daemon.py +1 -1
  163. claude_mpm/services/monitor/daemon_manager.py +9 -9
  164. claude_mpm/services/monitor/event_emitter.py +1 -1
  165. claude_mpm/services/monitor/handlers/file.py +1 -1
  166. claude_mpm/services/monitor/handlers/hooks.py +3 -3
  167. claude_mpm/services/monitor/management/lifecycle.py +7 -7
  168. claude_mpm/services/monitor/server.py +2 -2
  169. claude_mpm/services/orphan_detection.py +788 -0
  170. claude_mpm/services/port_manager.py +2 -2
  171. claude_mpm/services/project/analyzer.py +3 -3
  172. claude_mpm/services/project/archive_manager.py +13 -13
  173. claude_mpm/services/project/dependency_analyzer.py +4 -4
  174. claude_mpm/services/project/documentation_manager.py +4 -4
  175. claude_mpm/services/project/enhanced_analyzer.py +8 -8
  176. claude_mpm/services/project/registry.py +4 -4
  177. claude_mpm/services/project_port_allocator.py +597 -0
  178. claude_mpm/services/response_tracker.py +1 -1
  179. claude_mpm/services/session_management_service.py +1 -1
  180. claude_mpm/services/session_manager.py +6 -4
  181. claude_mpm/services/socketio/event_normalizer.py +1 -1
  182. claude_mpm/services/socketio/handlers/code_analysis.py +14 -12
  183. claude_mpm/services/socketio/handlers/file.py +1 -1
  184. claude_mpm/services/socketio/migration_utils.py +1 -1
  185. claude_mpm/services/socketio/server/core.py +1 -1
  186. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +1 -1
  187. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -4
  188. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
  189. claude_mpm/services/unified/config_strategies/config_schema.py +4 -4
  190. claude_mpm/services/unified/config_strategies/context_strategy.py +6 -6
  191. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +10 -10
  192. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -5
  193. claude_mpm/services/unified/config_strategies/unified_config_service.py +8 -8
  194. claude_mpm/services/unified/config_strategies/validation_strategy.py +15 -15
  195. claude_mpm/services/unified/deployment_strategies/base.py +4 -4
  196. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +15 -15
  197. claude_mpm/services/unified/deployment_strategies/local.py +9 -9
  198. claude_mpm/services/unified/deployment_strategies/utils.py +9 -9
  199. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -7
  200. claude_mpm/services/unified/unified_config.py +5 -5
  201. claude_mpm/services/unified/unified_deployment.py +2 -2
  202. claude_mpm/services/utility_service.py +1 -1
  203. claude_mpm/services/version_control/conflict_resolution.py +2 -2
  204. claude_mpm/services/version_control/git_operations.py +3 -3
  205. claude_mpm/services/version_control/semantic_versioning.py +13 -13
  206. claude_mpm/services/version_control/version_parser.py +1 -1
  207. claude_mpm/storage/state_storage.py +12 -13
  208. claude_mpm/tools/code_tree_analyzer.py +5 -5
  209. claude_mpm/tools/code_tree_builder.py +4 -4
  210. claude_mpm/tools/socketio_debug.py +1 -1
  211. claude_mpm/utils/agent_dependency_loader.py +4 -4
  212. claude_mpm/utils/common.py +2 -2
  213. claude_mpm/utils/config_manager.py +3 -3
  214. claude_mpm/utils/dependency_cache.py +2 -2
  215. claude_mpm/utils/dependency_strategies.py +6 -6
  216. claude_mpm/utils/file_utils.py +11 -11
  217. claude_mpm/utils/log_cleanup.py +1 -1
  218. claude_mpm/utils/path_operations.py +1 -1
  219. claude_mpm/validation/agent_validator.py +2 -2
  220. claude_mpm/validation/frontmatter_validator.py +1 -1
  221. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/METADATA +1 -1
  222. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/RECORD +226 -223
  223. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/WHEEL +0 -0
  224. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/entry_points.txt +0 -0
  225. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/licenses/LICENSE +0 -0
  226. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/top_level.txt +0 -0
@@ -80,8 +80,8 @@ def safe_path_join(*parts: Union[str, Path]) -> Path:
80
80
  # Ensure the resolved path is under the base path
81
81
  try:
82
82
  resolved.relative_to(base)
83
- except ValueError:
84
- raise ValueError(f"Path traversal detected: {path}")
83
+ except ValueError as e:
84
+ raise ValueError(f"Path traversal detected: {path}") from e
85
85
 
86
86
  return resolved
87
87
 
@@ -142,7 +142,7 @@ def safe_read(
142
142
  """Safely read a file with error handling.
143
143
 
144
144
  Replaces the common pattern:
145
- with open(file, 'r') as f:
145
+ with file.open('r') as f:
146
146
  content = f.read()
147
147
 
148
148
  Args:
@@ -641,7 +641,7 @@ def file_lock(filepath: Union[str, Path], timeout: float = 5.0):
641
641
  if e.errno != errno.EAGAIN:
642
642
  raise
643
643
  if time.time() - start_time > timeout:
644
- raise TimeoutError(f"Could not acquire lock for {filepath}")
644
+ raise TimeoutError(f"Could not acquire lock for {filepath}") from e
645
645
  time.sleep(0.1)
646
646
 
647
647
  yield lock_handle
@@ -724,7 +724,7 @@ def get_file_hash(
724
724
 
725
725
  try:
726
726
  hasher = hashlib.new(algorithm)
727
- with open(filepath, "rb") as f:
727
+ with filepath.open("rb") as f:
728
728
  for chunk in iter(lambda: f.read(8192), b""):
729
729
  hasher.update(chunk)
730
730
  return hasher.hexdigest()
@@ -154,7 +154,7 @@ class CapabilityGenerator:
154
154
  Dictionary with agent metadata or None
155
155
  """
156
156
  try:
157
- with open(agent_file) as f:
157
+ with agent_file.open() as f:
158
158
  content = f.read()
159
159
 
160
160
  # Default values
@@ -244,7 +244,7 @@ class CapabilityGenerator:
244
244
  template_file = templates_dir / f"{agent_name}.json"
245
245
 
246
246
  if template_file.exists():
247
- with open(template_file) as f:
247
+ with template_file.open() as f:
248
248
  template_data = json.load(f)
249
249
  return template_data.get("routing")
250
250
 
@@ -262,7 +262,7 @@ class CapabilityGenerator:
262
262
  if alt_name != agent_name:
263
263
  alt_file = templates_dir / f"{alt_name}.json"
264
264
  if alt_file.exists():
265
- with open(alt_file) as f:
265
+ with alt_file.open() as f:
266
266
  template_data = json.load(f)
267
267
  return template_data.get("routing")
268
268
 
@@ -310,7 +310,7 @@ class CapabilityGenerator:
310
310
  template_file = templates_dir / f"{agent_name}.json"
311
311
 
312
312
  if template_file.exists():
313
- with open(template_file) as f:
313
+ with template_file.open() as f:
314
314
  template_data = json.load(f)
315
315
  return template_data.get("memory_routing")
316
316
 
@@ -330,7 +330,7 @@ class CapabilityGenerator:
330
330
  if alt_name != agent_name:
331
331
  alt_file = templates_dir / f"{alt_name}.json"
332
332
  if alt_file.exists():
333
- with open(alt_file) as f:
333
+ with alt_file.open() as f:
334
334
  template_data = json.load(f)
335
335
  return template_data.get("memory_routing")
336
336
 
@@ -134,7 +134,7 @@ class AgentLoader:
134
134
 
135
135
  for json_file in template_dir.glob("*.json"):
136
136
  try:
137
- with open(json_file) as f:
137
+ with json_file.open() as f:
138
138
  template_data = json.load(f)
139
139
 
140
140
  # Extract agent metadata
@@ -55,7 +55,7 @@ class MetadataProcessor:
55
55
  Dictionary with agent metadata or None
56
56
  """
57
57
  try:
58
- with open(agent_file) as f:
58
+ with agent_file.open() as f:
59
59
  content = f.read()
60
60
 
61
61
  # Default values
@@ -89,7 +89,7 @@ class TemplateProcessor:
89
89
  # Try exact match first
90
90
  template_file = templates_dir / f"{agent_name}.json"
91
91
  if template_file.exists():
92
- with open(template_file) as f:
92
+ with template_file.open() as f:
93
93
  return json.load(f)
94
94
 
95
95
  # Try alternative naming variations
@@ -97,7 +97,7 @@ class TemplateProcessor:
97
97
  for alt_name in alternative_names:
98
98
  alt_file = templates_dir / f"{alt_name}.json"
99
99
  if alt_file.exists():
100
- with open(alt_file) as f:
100
+ with alt_file.open() as f:
101
101
  return json.load(f)
102
102
 
103
103
  return None
@@ -218,7 +218,7 @@ class TemplateProcessor:
218
218
 
219
219
  for json_file in template_dir.glob("*.json"):
220
220
  try:
221
- with open(json_file) as f:
221
+ with json_file.open() as f:
222
222
  template_data = json.load(f)
223
223
 
224
224
  agent_metadata = self.extract_metadata(template_data)
@@ -504,9 +504,9 @@ class FrameworkLoader:
504
504
  metadata["instructions_length"] = len(instructions)
505
505
 
506
506
  if loop.is_running():
507
- asyncio.create_task(
507
+ _task = asyncio.create_task( # noqa: RUF006
508
508
  log_manager.log_prompt("system_prompt", instructions, metadata)
509
- )
509
+ ) # Fire-and-forget logging
510
510
  else:
511
511
  loop.run_until_complete(
512
512
  log_manager.log_prompt("system_prompt", instructions, metadata)
@@ -122,6 +122,13 @@ class LogManager:
122
122
  if run_cleanup_on_startup is None:
123
123
  return # Cleanup utility not available
124
124
 
125
+ # Check environment variable to skip cleanup (for configure command)
126
+ import os
127
+
128
+ if os.environ.get("CLAUDE_MPM_SKIP_CLEANUP", "0") == "1":
129
+ logger.debug("Startup cleanup skipped (CLAUDE_MPM_SKIP_CLEANUP=1)")
130
+ return
131
+
125
132
  try:
126
133
  # Get cleanup configuration
127
134
  cleanup_config = self.config.get("log_cleanup", {})
@@ -535,12 +542,12 @@ class LogManager:
535
542
 
536
543
  if extension == ".json":
537
544
  # JSON files also get structured metadata for consistency
538
- with open(file_path, "w", encoding="utf-8") as f:
545
+ with file_path.open("w", encoding="utf-8") as f:
539
546
  json.dump(data, f, indent=2, ensure_ascii=False)
540
547
  # For markdown or text files
541
548
  elif isinstance(data, dict):
542
549
  # Write as formatted markdown with metadata
543
- with open(file_path, "w", encoding="utf-8") as f:
550
+ with file_path.open("w", encoding="utf-8") as f:
544
551
  f.write("---\n")
545
552
  f.write(f"timestamp: {data.get('timestamp', 'unknown')}\n")
546
553
  f.write(f"type: {data.get('type', 'unknown')}\n")
@@ -552,7 +559,7 @@ class LogManager:
552
559
  f.write(data.get("content", ""))
553
560
  else:
554
561
  # Write content directly
555
- with open(file_path, "w", encoding="utf-8") as f:
562
+ with file_path.open("w", encoding="utf-8") as f:
556
563
  f.write(str(data))
557
564
  except Exception as e:
558
565
  logger.error(f"Failed to write {file_path}: {e}")
@@ -581,7 +588,7 @@ class LogManager:
581
588
 
582
589
  def write_task():
583
590
  try:
584
- with open(log_file, "a", encoding="utf-8") as f:
591
+ with log_file.open("a", encoding="utf-8") as f:
585
592
  f.write(log_entry)
586
593
  except Exception as e:
587
594
  logger.error(f"Failed to write log: {e}")
claude_mpm/core/logger.py CHANGED
@@ -526,7 +526,7 @@ class ProjectLogger:
526
526
  self.dirs["logs_system"]
527
527
  / f"{datetime.now(timezone.utc).strftime('%Y%m%d')}.jsonl"
528
528
  )
529
- with open(log_file, "a") as f:
529
+ with log_file.open("a") as f:
530
530
  f.write(json.dumps(log_entry) + "\n")
531
531
 
532
532
  def log_agent_invocation(
@@ -580,7 +580,7 @@ class ProjectLogger:
580
580
  daily_log = (
581
581
  agent_log_dir / f"{datetime.now(timezone.utc).strftime('%Y%m%d')}.jsonl"
582
582
  )
583
- with open(daily_log, "a") as f:
583
+ with daily_log.open("a") as f:
584
584
  f.write(json.dumps(log_entry) + "\n")
585
585
 
586
586
  def get_session_summary(self) -> Dict[str, Any]:
@@ -227,7 +227,7 @@ class OptimizedStartup:
227
227
  """Parse configuration file."""
228
228
  import yaml
229
229
 
230
- with open(path) as f:
230
+ with path.open() as f:
231
231
  return yaml.safe_load(f) or {}
232
232
 
233
233
  def _store_config(self, config: Dict[str, Any]):
@@ -42,7 +42,7 @@ class OutputStyleManager:
42
42
  Path(__file__).parent.parent / "agents" / "OUTPUT_STYLE.md"
43
43
  )
44
44
 
45
- def _detect_claude_version(self) -> Optional[str]: # noqa: PLR0911
45
+ def _detect_claude_version(self) -> Optional[str]:
46
46
  """
47
47
  Detect Claude Code version by running 'claude --version'.
48
48
  Uses global cache to avoid duplicate detection and logging.
@@ -160,10 +160,10 @@ class ServiceRegistry:
160
160
  try:
161
161
  # Use the enhanced container's named resolution
162
162
  return self.container.get(BaseService, name=service_type)
163
- except Exception:
163
+ except Exception as e:
164
164
  # Fall back to looking up class and resolving
165
165
  if service_type not in self._services:
166
- raise KeyError(f"Service '{service_type}' not registered")
166
+ raise KeyError(f"Service '{service_type}' not registered") from e
167
167
  service_class = self._services[service_type]
168
168
  return self.container.get(service_class)
169
169
  else:
@@ -187,7 +187,7 @@ class SessionManager:
187
187
  """Save sessions to disk."""
188
188
  session_file = self.session_dir / "active_sessions.json"
189
189
  try:
190
- with open(session_file, "w") as f:
190
+ with session_file.open("w") as f:
191
191
  json.dump(self.active_sessions, f, indent=2)
192
192
  except Exception as e:
193
193
  logger.error(f"Failed to save sessions: {e}")
@@ -197,7 +197,7 @@ class SessionManager:
197
197
  session_file = self.session_dir / "active_sessions.json"
198
198
  if session_file.exists():
199
199
  try:
200
- with open(session_file) as f:
200
+ with session_file.open() as f:
201
201
  self.active_sessions = json.load(f)
202
202
 
203
203
  # Clean up old sessions on load (archive by default)
@@ -286,7 +286,7 @@ class SessionManager:
286
286
  backup_path = archive_dir / backup_name
287
287
 
288
288
  # Compress and backup current file
289
- with open(claude_json_path, "rb") as f_in:
289
+ with claude_json_path.open("rb") as f_in:
290
290
  with gzip.open(backup_path, "wb") as f_out:
291
291
  shutil.copyfileobj(f_in, f_out)
292
292
 
@@ -228,7 +228,7 @@ class ConfigLoader:
228
228
  try:
229
229
  import yaml
230
230
 
231
- with open(config_file) as f:
231
+ with config_file.open() as f:
232
232
  if config_file.suffix.lower() in (".yaml", ".yml"):
233
233
  return yaml.safe_load(f) or {}
234
234
  # Try JSON as fallback
@@ -594,9 +594,9 @@ class SocketIOConnectionPool:
594
594
  timeout=2.0,
595
595
  )
596
596
 
597
- except asyncio.TimeoutError:
597
+ except asyncio.TimeoutError as e:
598
598
  self.logger.debug("Socket.IO connection timeout")
599
- raise TimeoutError("Socket.IO connection timeout")
599
+ raise TimeoutError("Socket.IO connection timeout") from e
600
600
  except Exception as e:
601
601
  self.logger.debug(f"Client connection failed: {e}")
602
602
  raise
@@ -728,7 +728,7 @@ class UnifiedAgentRegistry:
728
728
  },
729
729
  }
730
730
 
731
- with open(output_path, "w") as f:
731
+ with output_path.open("w") as f:
732
732
  json.dump(export_data, f, indent=2)
733
733
 
734
734
  logger.info(f"Exported {len(self.registry)} agents to {output_path}")
@@ -737,7 +737,7 @@ class UnifiedAgentRegistry:
737
737
  """Import registry from JSON file."""
738
738
  input_path = Path(input_path)
739
739
 
740
- with open(input_path) as f:
740
+ with input_path.open() as f:
741
741
  data = json.load(f)
742
742
 
743
743
  # Clear current registry
@@ -436,7 +436,7 @@ class ConfigurationService:
436
436
  if config_path.exists():
437
437
  import yaml
438
438
 
439
- with open(config_path) as f:
439
+ with config_path.open() as f:
440
440
  file_config = yaml.safe_load(f) or {}
441
441
  config_data.update(file_config)
442
442
  break
@@ -448,7 +448,7 @@ class ConfigurationService:
448
448
  raise ConfigurationError(
449
449
  f"Failed to load configuration: {e}",
450
450
  context={"error_type": type(e).__name__},
451
- )
451
+ ) from e
452
452
 
453
453
  @property
454
454
  def config(self) -> UnifiedConfig:
@@ -515,7 +515,7 @@ class ConfigurationService:
515
515
  return True
516
516
  raise ConfigurationError("Invalid SocketIO port range")
517
517
  except Exception as e:
518
- raise ConfigurationError(f"Configuration validation failed: {e}")
518
+ raise ConfigurationError(f"Configuration validation failed: {e}") from e
519
519
 
520
520
  def export_to_file(self, file_path: Union[str, Path], format: str = "yaml") -> None:
521
521
  """
@@ -531,12 +531,12 @@ class ConfigurationService:
531
531
  if format.lower() == "yaml":
532
532
  import yaml
533
533
 
534
- with open(file_path, "w") as f:
534
+ with file_path.open("w") as f:
535
535
  yaml.dump(self._config.dict(), f, default_flow_style=False)
536
536
  elif format.lower() == "json":
537
537
  import json
538
538
 
539
- with open(file_path, "w") as f:
539
+ with file_path.open("w") as f:
540
540
  json.dump(self._config.dict(), f, indent=2)
541
541
  else:
542
542
  raise ConfigurationError(f"Unsupported export format: {format}")
@@ -545,4 +545,4 @@ class ConfigurationService:
545
545
  raise ConfigurationError(
546
546
  f"Failed to export configuration: {e}",
547
547
  context={"file_path": str(file_path), "format": format},
548
- )
548
+ ) from e
@@ -167,7 +167,7 @@ class PathContext:
167
167
 
168
168
  @staticmethod
169
169
  @lru_cache(maxsize=1)
170
- def detect_deployment_context() -> DeploymentContext: # noqa: PLR0911
170
+ def detect_deployment_context() -> DeploymentContext:
171
171
  """Detect the current deployment context.
172
172
 
173
173
  Priority order:
@@ -396,7 +396,7 @@ class UnifiedPathManager:
396
396
  return current
397
397
  current = current.parent
398
398
 
399
- raise FileNotFoundError("Could not determine framework root")
399
+ raise FileNotFoundError("Could not determine framework root") from None
400
400
 
401
401
  @property
402
402
  @lru_cache(maxsize=1)
@@ -129,7 +129,7 @@ def has_code_files(directory_path, max_depth=5, current_depth=0):
129
129
  return False
130
130
 
131
131
 
132
- def should_show_item(item_name, item_path, is_directory): # noqa: PLR0911
132
+ def should_show_item(item_name, item_path, is_directory):
133
133
  """Determine if an item should be shown based on filtering rules"""
134
134
  # Always hide system files
135
135
  if item_name in {".DS_Store", "Thumbs.db", "desktop.ini"}:
@@ -29,7 +29,7 @@ class AgentProfileGenerator:
29
29
  if not self.template_path.exists():
30
30
  raise FileNotFoundError(f"Template not found: {self.template_path}")
31
31
 
32
- with open(self.template_path) as f:
32
+ with self.template_path.open() as f:
33
33
  return yaml.safe_load(f)
34
34
 
35
35
  def generate_profile(self, config: Dict[str, Any]) -> str:
@@ -284,11 +284,11 @@ class EventHandlers:
284
284
  # Log the agent prompt asynchronously
285
285
  try:
286
286
  loop = asyncio.get_running_loop()
287
- asyncio.create_task(
287
+ _task = asyncio.create_task( # noqa: RUF006
288
288
  log_manager.log_prompt(
289
289
  f"agent_{agent_type}", prompt_content, metadata
290
290
  )
291
- )
291
+ ) # Fire-and-forget logging (ephemeral hook process)
292
292
  except RuntimeError:
293
293
  # No running loop, create one
294
294
  loop = asyncio.new_event_loop()
@@ -428,7 +428,7 @@ main "$@"
428
428
  return
429
429
 
430
430
  try:
431
- with open(self.old_settings_file) as f:
431
+ with self.old_settings_file.open() as f:
432
432
  old_settings = json.load(f)
433
433
 
434
434
  # Remove hooks section if present
@@ -437,7 +437,7 @@ main "$@"
437
437
  self.logger.info(f"Removing hooks from {self.old_settings_file}")
438
438
 
439
439
  # Write back the cleaned settings
440
- with open(self.old_settings_file, "w") as f:
440
+ with self.old_settings_file.open("w") as f:
441
441
  json.dump(old_settings, f, indent=2)
442
442
 
443
443
  self.logger.info(f"Cleaned up hooks from {self.old_settings_file}")
@@ -450,7 +450,7 @@ main "$@"
450
450
 
451
451
  # Load existing settings.json or create new
452
452
  if self.settings_file.exists():
453
- with open(self.settings_file) as f:
453
+ with self.settings_file.open() as f:
454
454
  settings = json.load(f)
455
455
  self.logger.info(f"Found existing Claude settings at {self.settings_file}")
456
456
  else:
@@ -490,7 +490,7 @@ main "$@"
490
490
  ]
491
491
 
492
492
  # Write settings to settings.json
493
- with open(self.settings_file, "w") as f:
493
+ with self.settings_file.open("w") as f:
494
494
  json.dump(settings, f, indent=2)
495
495
 
496
496
  self.logger.info(f"Updated Claude settings at {self.settings_file}")
@@ -564,7 +564,7 @@ main "$@"
564
564
  issues.append(f"Claude settings file not found at {self.settings_file}")
565
565
  else:
566
566
  try:
567
- with open(self.settings_file) as f:
567
+ with self.settings_file.open() as f:
568
568
  settings = json.load(f)
569
569
 
570
570
  if "hooks" not in settings:
@@ -615,7 +615,7 @@ main "$@"
615
615
  # Remove from Claude settings (both old and new locations)
616
616
  for settings_path in [self.settings_file, self.old_settings_file]:
617
617
  if settings_path.exists():
618
- with open(settings_path) as f:
618
+ with settings_path.open() as f:
619
619
  settings = json.load(f)
620
620
 
621
621
  if "hooks" in settings:
@@ -655,7 +655,7 @@ main "$@"
655
655
  del settings["hooks"]
656
656
 
657
657
  # Write back settings
658
- with open(settings_path, "w") as f:
658
+ with settings_path.open("w") as f:
659
659
  json.dump(settings, f, indent=2)
660
660
 
661
661
  self.logger.info(f"Removed hooks from {settings_path}")
@@ -709,7 +709,7 @@ main "$@"
709
709
 
710
710
  if self.settings_file.exists():
711
711
  try:
712
- with open(self.settings_file) as f:
712
+ with self.settings_file.open() as f:
713
713
  settings = json.load(f)
714
714
  if "hooks" in settings:
715
715
  status["configured_events"] = list(settings["hooks"].keys())
@@ -720,7 +720,7 @@ main "$@"
720
720
  # Also check old settings file
721
721
  if self.old_settings_file.exists():
722
722
  try:
723
- with open(self.old_settings_file) as f:
723
+ with self.old_settings_file.open() as f:
724
724
  old_settings = json.load(f)
725
725
  if "hooks" in old_settings:
726
726
  status["old_file_has_hooks"] = True
@@ -10,28 +10,27 @@ import os
10
10
  import re
11
11
  import sys
12
12
  from datetime import datetime, timezone
13
- from typing import Optional
13
+ from typing import Any, Optional
14
14
 
15
15
  # Debug mode
16
16
  DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
17
17
 
18
18
  # Response tracking integration
19
- RESPONSE_TRACKING_AVAILABLE = False
20
- try:
21
- from claude_mpm.services.response_tracker import ResponseTracker
22
-
23
- RESPONSE_TRACKING_AVAILABLE = True
24
- except Exception as e:
25
- if DEBUG:
26
- print(f"Response tracking not available: {e}", file=sys.stderr)
27
- RESPONSE_TRACKING_AVAILABLE = False
19
+ # NOTE: ResponseTracker import moved to _initialize_response_tracking() for lazy loading
20
+ # This prevents unnecessary import of base_agent_loader and other heavy dependencies
21
+ # when hooks don't need response tracking
22
+ RESPONSE_TRACKING_AVAILABLE = (
23
+ True # Assume available, will check on actual initialization
24
+ )
28
25
 
29
26
 
30
27
  class ResponseTrackingManager:
31
28
  """Manager for response tracking functionality."""
32
29
 
33
30
  def __init__(self):
34
- self.response_tracker: Optional[ResponseTracker] = None
31
+ self.response_tracker: Optional[Any] = (
32
+ None # Type hint changed to Any for lazy import
33
+ )
35
34
  self.response_tracking_enabled = False
36
35
  self.track_all_interactions = (
37
36
  False # Track all Claude interactions, not just delegations
@@ -49,8 +48,14 @@ class ResponseTrackingManager:
49
48
 
50
49
  DESIGN DECISION: Check configuration to allow enabling/disabling
51
50
  response tracking without code changes.
51
+
52
+ NOTE: ResponseTracker is imported lazily here to avoid loading
53
+ base_agent_loader and other heavy dependencies unless actually needed.
52
54
  """
53
55
  try:
56
+ # Lazy import of ResponseTracker to avoid unnecessary dependency loading
57
+ from claude_mpm.services.response_tracker import ResponseTracker
58
+
54
59
  # Create configuration with optional config file using ConfigLoader
55
60
  config_file = os.environ.get("CLAUDE_PM_CONFIG_FILE")
56
61
  from claude_mpm.core.shared.config_loader import ConfigLoader, ConfigPattern
@@ -26,14 +26,8 @@ except ImportError:
26
26
  REQUESTS_AVAILABLE = False
27
27
  requests = None
28
28
 
29
- # Import high-performance event emitter
30
- try:
31
- from claude_mpm.services.monitor.event_emitter import get_event_emitter
32
-
33
- EVENT_EMITTER_AVAILABLE = True
34
- except ImportError:
35
- EVENT_EMITTER_AVAILABLE = False
36
- get_event_emitter = None
29
+ # Import high-performance event emitter - lazy loaded in _async_emit()
30
+ # to reduce hook handler initialization time by ~85% (792ms -> minimal)
37
31
 
38
32
  # Import EventNormalizer for consistent event formatting
39
33
  try:
@@ -82,6 +76,9 @@ class ConnectionManagerService:
82
76
  # For backward compatibility with tests
83
77
  self.connection_pool = None # No longer used
84
78
 
79
+ # Track async emit tasks to prevent garbage collection
80
+ self._emit_tasks: set = set()
81
+
85
82
  if DEBUG:
86
83
  print(
87
84
  f"✅ HTTP connection manager initialized - endpoint: {self.http_endpoint}",
@@ -137,9 +134,6 @@ class ConnectionManagerService:
137
134
 
138
135
  def _try_async_emit(self, namespace: str, event: str, data: dict) -> bool:
139
136
  """Try to emit event using high-performance async emitter."""
140
- if not EVENT_EMITTER_AVAILABLE:
141
- return False
142
-
143
137
  try:
144
138
  # Run async emission in the current event loop or create one
145
139
  loop = None
@@ -150,8 +144,10 @@ class ConnectionManagerService:
150
144
  pass
151
145
 
152
146
  if loop:
153
- # We're in an async context, create a task
154
- loop.create_task(self._async_emit(namespace, event, data))
147
+ # We're in an async context, create a task with tracking
148
+ task = loop.create_task(self._async_emit(namespace, event, data))
149
+ self._emit_tasks.add(task)
150
+ task.add_done_callback(self._emit_tasks.discard)
155
151
  # Don't wait for completion to maintain low latency
156
152
  if DEBUG:
157
153
  print(f"✅ Async emit scheduled: {event}", file=sys.stderr)
@@ -170,8 +166,15 @@ class ConnectionManagerService:
170
166
  async def _async_emit(self, namespace: str, event: str, data: dict) -> bool:
171
167
  """Async helper for event emission."""
172
168
  try:
169
+ # Lazy load event emitter to reduce initialization overhead
170
+ from claude_mpm.services.monitor.event_emitter import get_event_emitter
171
+
173
172
  emitter = await get_event_emitter()
174
173
  return await emitter.emit_event(namespace, "claude_event", data)
174
+ except ImportError:
175
+ if DEBUG:
176
+ print("⚠️ Event emitter not available", file=sys.stderr)
177
+ return False
175
178
  except Exception as e:
176
179
  if DEBUG:
177
180
  print(f"⚠️ Async emitter error: {e}", file=sys.stderr)
@@ -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: # noqa: PLR0911
141
+ def classify_tool_operation(tool_name: str, tool_input: dict) -> str:
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: # noqa: P
155
155
  return "other"
156
156
 
157
157
 
158
- def assess_security_risk(tool_name: str, tool_input: dict) -> str: # noqa: PLR0911
158
+ def assess_security_risk(tool_name: str, tool_input: dict) -> str:
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: # noqa: PLR0911
237
+ def execute(self, context: HookContext) -> HookResult:
238
238
  """Extract and store learnings from delegation result.
239
239
 
240
240
  WHY: Capturing learnings immediately after task completion ensures we
@@ -118,7 +118,7 @@ async def validate_agent_dependencies(profile_path: Path) -> ValidationResult:
118
118
  result = ValidationResult(is_valid=True)
119
119
 
120
120
  try:
121
- with open(profile_path) as f:
121
+ with profile_path.open() as f:
122
122
  profile_data = yaml.safe_load(f)
123
123
 
124
124
  # Check for circular dependencies