claude-mpm 4.5.11__py3-none-any.whl → 4.5.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 (190) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +47 -0
  3. claude_mpm/agents/BASE_QA.md +60 -0
  4. claude_mpm/agents/frontmatter_validator.py +4 -4
  5. claude_mpm/agents/templates/nextjs_engineer.json +2 -2
  6. claude_mpm/agents/templates/qa.json +13 -3
  7. claude_mpm/agents/templates/react_engineer.json +2 -2
  8. claude_mpm/agents/templates/typescript_engineer.json +2 -2
  9. claude_mpm/agents/templates/web_qa.json +14 -3
  10. claude_mpm/cli/commands/agent_manager.py +3 -3
  11. claude_mpm/cli/commands/agents.py +6 -6
  12. claude_mpm/cli/commands/aggregate.py +4 -4
  13. claude_mpm/cli/commands/analyze.py +2 -2
  14. claude_mpm/cli/commands/analyze_code.py +1 -1
  15. claude_mpm/cli/commands/cleanup.py +3 -3
  16. claude_mpm/cli/commands/config.py +2 -2
  17. claude_mpm/cli/commands/configure.py +14 -14
  18. claude_mpm/cli/commands/dashboard.py +1 -1
  19. claude_mpm/cli/commands/debug.py +3 -3
  20. claude_mpm/cli/commands/doctor.py +1 -1
  21. claude_mpm/cli/commands/mcp.py +7 -7
  22. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  23. claude_mpm/cli/commands/mcp_config.py +2 -2
  24. claude_mpm/cli/commands/mcp_external_commands.py +2 -2
  25. claude_mpm/cli/commands/mcp_install_commands.py +3 -3
  26. claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
  27. claude_mpm/cli/commands/mcp_setup_external.py +3 -3
  28. claude_mpm/cli/commands/monitor.py +1 -1
  29. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  30. claude_mpm/cli/interactive/agent_wizard.py +1 -1
  31. claude_mpm/cli/parsers/search_parser.py +1 -1
  32. claude_mpm/cli/shared/argument_patterns.py +2 -2
  33. claude_mpm/cli/shared/base_command.py +1 -1
  34. claude_mpm/cli/startup_logging.py +6 -4
  35. claude_mpm/config/experimental_features.py +4 -4
  36. claude_mpm/config/socketio_config.py +2 -2
  37. claude_mpm/core/agent_session_manager.py +2 -2
  38. claude_mpm/core/api_validator.py +3 -3
  39. claude_mpm/core/base_service.py +10 -1
  40. claude_mpm/core/cache.py +2 -2
  41. claude_mpm/core/config.py +4 -4
  42. claude_mpm/core/config_aliases.py +4 -4
  43. claude_mpm/core/config_constants.py +1 -1
  44. claude_mpm/core/error_handler.py +1 -1
  45. claude_mpm/core/file_utils.py +5 -5
  46. claude_mpm/core/framework/formatters/capability_generator.py +5 -5
  47. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  48. claude_mpm/core/framework/processors/metadata_processor.py +1 -1
  49. claude_mpm/core/framework/processors/template_processor.py +3 -3
  50. claude_mpm/core/framework_loader.py +2 -2
  51. claude_mpm/core/log_manager.py +4 -4
  52. claude_mpm/core/logger.py +2 -2
  53. claude_mpm/core/optimized_startup.py +1 -1
  54. claude_mpm/core/output_style_manager.py +1 -1
  55. claude_mpm/core/service_registry.py +2 -2
  56. claude_mpm/core/session_manager.py +3 -3
  57. claude_mpm/core/shared/config_loader.py +1 -1
  58. claude_mpm/core/socketio_pool.py +2 -2
  59. claude_mpm/core/unified_agent_registry.py +2 -2
  60. claude_mpm/core/unified_config.py +6 -6
  61. claude_mpm/core/unified_paths.py +2 -2
  62. claude_mpm/dashboard/api/simple_directory.py +1 -1
  63. claude_mpm/generators/agent_profile_generator.py +1 -1
  64. claude_mpm/hooks/claude_hooks/event_handlers.py +2 -2
  65. claude_mpm/hooks/claude_hooks/installer.py +9 -9
  66. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +7 -2
  67. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  68. claude_mpm/hooks/memory_integration_hook.py +1 -1
  69. claude_mpm/hooks/validation_hooks.py +1 -1
  70. claude_mpm/init.py +4 -4
  71. claude_mpm/models/agent_session.py +1 -1
  72. claude_mpm/scripts/socketio_daemon.py +5 -5
  73. claude_mpm/services/__init__.py +2 -2
  74. claude_mpm/services/agent_capabilities_service.py +1 -1
  75. claude_mpm/services/agents/agent_builder.py +6 -4
  76. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -1
  77. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  78. claude_mpm/services/agents/deployment/agent_record_service.py +3 -3
  79. claude_mpm/services/agents/deployment/deployment_wrapper.py +1 -1
  80. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +2 -2
  81. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  82. claude_mpm/services/agents/local_template_manager.py +5 -5
  83. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  84. claude_mpm/services/agents/registry/modification_tracker.py +19 -11
  85. claude_mpm/services/async_session_logger.py +1 -1
  86. claude_mpm/services/claude_session_logger.py +1 -1
  87. claude_mpm/services/cli/agent_listing_service.py +3 -3
  88. claude_mpm/services/cli/agent_validation_service.py +1 -1
  89. claude_mpm/services/cli/session_manager.py +2 -2
  90. claude_mpm/services/core/path_resolver.py +1 -1
  91. claude_mpm/services/diagnostics/checks/agent_check.py +1 -1
  92. claude_mpm/services/diagnostics/checks/claude_code_check.py +2 -2
  93. claude_mpm/services/diagnostics/checks/common_issues_check.py +3 -3
  94. claude_mpm/services/diagnostics/checks/configuration_check.py +2 -2
  95. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  96. claude_mpm/services/diagnostics/checks/mcp_check.py +1 -1
  97. claude_mpm/services/diagnostics/checks/mcp_services_check.py +9 -9
  98. claude_mpm/services/diagnostics/checks/monitor_check.py +1 -1
  99. claude_mpm/services/diagnostics/doctor_reporter.py +1 -1
  100. claude_mpm/services/event_aggregator.py +1 -1
  101. claude_mpm/services/event_bus/event_bus.py +9 -2
  102. claude_mpm/services/events/consumers/dead_letter.py +2 -2
  103. claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
  105. claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
  106. claude_mpm/services/hook_installer_service.py +7 -7
  107. claude_mpm/services/infrastructure/context_preservation.py +7 -7
  108. claude_mpm/services/infrastructure/daemon_manager.py +5 -5
  109. claude_mpm/services/mcp_config_manager.py +10 -10
  110. claude_mpm/services/mcp_gateway/auto_configure.py +5 -5
  111. claude_mpm/services/mcp_gateway/config/config_loader.py +2 -2
  112. claude_mpm/services/mcp_gateway/config/configuration.py +5 -3
  113. claude_mpm/services/mcp_gateway/core/process_pool.py +3 -3
  114. claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
  115. claude_mpm/services/mcp_gateway/core/startup_verification.py +1 -1
  116. claude_mpm/services/mcp_gateway/main.py +1 -1
  117. claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
  118. claude_mpm/services/mcp_gateway/registry/tool_registry.py +2 -1
  119. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  120. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -1
  121. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +1 -1
  122. claude_mpm/services/mcp_gateway/tools/hello_world.py +1 -1
  123. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +5 -5
  124. claude_mpm/services/mcp_gateway/utils/update_preferences.py +2 -2
  125. claude_mpm/services/mcp_service_verifier.py +1 -1
  126. claude_mpm/services/memory/builder.py +1 -1
  127. claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
  128. claude_mpm/services/memory/indexed_memory.py +3 -3
  129. claude_mpm/services/monitor/daemon.py +1 -1
  130. claude_mpm/services/monitor/daemon_manager.py +9 -9
  131. claude_mpm/services/monitor/handlers/file.py +1 -1
  132. claude_mpm/services/monitor/handlers/hooks.py +3 -3
  133. claude_mpm/services/monitor/management/lifecycle.py +7 -7
  134. claude_mpm/services/monitor/server.py +2 -2
  135. claude_mpm/services/orphan_detection.py +11 -16
  136. claude_mpm/services/port_manager.py +2 -2
  137. claude_mpm/services/project/analyzer.py +3 -3
  138. claude_mpm/services/project/archive_manager.py +17 -13
  139. claude_mpm/services/project/dependency_analyzer.py +4 -4
  140. claude_mpm/services/project/documentation_manager.py +4 -4
  141. claude_mpm/services/project/enhanced_analyzer.py +19 -8
  142. claude_mpm/services/project/registry.py +4 -4
  143. claude_mpm/services/project_port_allocator.py +7 -12
  144. claude_mpm/services/session_management_service.py +1 -1
  145. claude_mpm/services/socketio/event_normalizer.py +1 -1
  146. claude_mpm/services/socketio/handlers/code_analysis.py +14 -12
  147. claude_mpm/services/socketio/handlers/file.py +1 -1
  148. claude_mpm/services/socketio/migration_utils.py +1 -1
  149. claude_mpm/services/socketio/server/core.py +1 -1
  150. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +1 -1
  151. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -4
  152. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
  153. claude_mpm/services/unified/config_strategies/config_schema.py +4 -4
  154. claude_mpm/services/unified/config_strategies/context_strategy.py +8 -6
  155. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +10 -10
  156. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -5
  157. claude_mpm/services/unified/config_strategies/unified_config_service.py +8 -8
  158. claude_mpm/services/unified/config_strategies/validation_strategy.py +15 -15
  159. claude_mpm/services/unified/deployment_strategies/base.py +4 -4
  160. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +15 -15
  161. claude_mpm/services/unified/deployment_strategies/local.py +11 -11
  162. claude_mpm/services/unified/deployment_strategies/utils.py +11 -9
  163. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -9
  164. claude_mpm/services/unified/unified_config.py +5 -5
  165. claude_mpm/services/unified/unified_deployment.py +2 -2
  166. claude_mpm/services/utility_service.py +1 -1
  167. claude_mpm/services/version_control/conflict_resolution.py +2 -2
  168. claude_mpm/services/version_control/git_operations.py +3 -3
  169. claude_mpm/services/version_control/semantic_versioning.py +13 -13
  170. claude_mpm/services/version_control/version_parser.py +1 -1
  171. claude_mpm/storage/state_storage.py +12 -13
  172. claude_mpm/tools/code_tree_analyzer.py +5 -5
  173. claude_mpm/tools/code_tree_builder.py +4 -4
  174. claude_mpm/tools/socketio_debug.py +1 -1
  175. claude_mpm/utils/agent_dependency_loader.py +4 -4
  176. claude_mpm/utils/common.py +2 -2
  177. claude_mpm/utils/config_manager.py +3 -3
  178. claude_mpm/utils/dependency_cache.py +2 -2
  179. claude_mpm/utils/dependency_strategies.py +6 -6
  180. claude_mpm/utils/file_utils.py +11 -11
  181. claude_mpm/utils/log_cleanup.py +1 -1
  182. claude_mpm/utils/path_operations.py +1 -1
  183. claude_mpm/validation/agent_validator.py +2 -2
  184. claude_mpm/validation/frontmatter_validator.py +1 -1
  185. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.dist-info}/METADATA +1 -1
  186. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.dist-info}/RECORD +190 -190
  187. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.dist-info}/WHEEL +0 -0
  188. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.dist-info}/entry_points.txt +0 -0
  189. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.dist-info}/licenses/LICENSE +0 -0
  190. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.13.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)
@@ -542,12 +542,12 @@ class LogManager:
542
542
 
543
543
  if extension == ".json":
544
544
  # JSON files also get structured metadata for consistency
545
- with open(file_path, "w", encoding="utf-8") as f:
545
+ with file_path.open("w", encoding="utf-8") as f:
546
546
  json.dump(data, f, indent=2, ensure_ascii=False)
547
547
  # For markdown or text files
548
548
  elif isinstance(data, dict):
549
549
  # Write as formatted markdown with metadata
550
- with open(file_path, "w", encoding="utf-8") as f:
550
+ with file_path.open("w", encoding="utf-8") as f:
551
551
  f.write("---\n")
552
552
  f.write(f"timestamp: {data.get('timestamp', 'unknown')}\n")
553
553
  f.write(f"type: {data.get('type', 'unknown')}\n")
@@ -559,7 +559,7 @@ class LogManager:
559
559
  f.write(data.get("content", ""))
560
560
  else:
561
561
  # Write content directly
562
- with open(file_path, "w", encoding="utf-8") as f:
562
+ with file_path.open("w", encoding="utf-8") as f:
563
563
  f.write(str(data))
564
564
  except Exception as e:
565
565
  logger.error(f"Failed to write {file_path}: {e}")
@@ -588,7 +588,7 @@ class LogManager:
588
588
 
589
589
  def write_task():
590
590
  try:
591
- with open(log_file, "a", encoding="utf-8") as f:
591
+ with log_file.open("a", encoding="utf-8") as f:
592
592
  f.write(log_entry)
593
593
  except Exception as e:
594
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
@@ -76,6 +76,9 @@ class ConnectionManagerService:
76
76
  # For backward compatibility with tests
77
77
  self.connection_pool = None # No longer used
78
78
 
79
+ # Track async emit tasks to prevent garbage collection
80
+ self._emit_tasks: set = set()
81
+
79
82
  if DEBUG:
80
83
  print(
81
84
  f"✅ HTTP connection manager initialized - endpoint: {self.http_endpoint}",
@@ -141,8 +144,10 @@ class ConnectionManagerService:
141
144
  pass
142
145
 
143
146
  if loop:
144
- # We're in an async context, create a task
145
- 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)
146
151
  # Don't wait for completion to maintain low latency
147
152
  if DEBUG:
148
153
  print(f"✅ Async emit scheduled: {event}", 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
claude_mpm/init.py CHANGED
@@ -270,11 +270,11 @@ class ProjectInitializer:
270
270
  """
271
271
  try:
272
272
  # Read existing JSON configuration
273
- with open(old_file) as f:
273
+ with old_file.open() as f:
274
274
  config = json.load(f)
275
275
 
276
276
  # Write as YAML
277
- with open(new_file, "w") as f:
277
+ with new_file.open("w") as f:
278
278
  yaml.dump(config, f, default_flow_style=False, sort_keys=False)
279
279
 
280
280
  self.logger.info(
@@ -310,7 +310,7 @@ class ProjectInitializer:
310
310
  },
311
311
  }
312
312
 
313
- with open(config_file, "w") as f:
313
+ with config_file.open("w") as f:
314
314
  yaml.dump(default_config, f, default_flow_style=False, sort_keys=False)
315
315
 
316
316
  def _create_project_config(self, config_file: Path):
@@ -322,7 +322,7 @@ class ProjectInitializer:
322
322
  "tickets": {"auto_create": True, "prefix": "TSK"},
323
323
  }
324
324
 
325
- with open(config_file, "w") as f:
325
+ with config_file.open("w") as f:
326
326
  json.dump(project_config, f, indent=2)
327
327
 
328
328
  def _copy_agent_templates(self):
@@ -448,7 +448,7 @@ class AgentSession:
448
448
  filepath = directory / filename
449
449
 
450
450
  # Save to file
451
- with open(filepath, "w", encoding="utf-8") as f:
451
+ with filepath.open("w", encoding="utf-8") as f:
452
452
  json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
453
453
 
454
454
  return str(filepath)
@@ -37,7 +37,7 @@ def is_running(pid_file: Path) -> bool:
37
37
  return False
38
38
 
39
39
  try:
40
- with open(pid_file) as f:
40
+ with pid_file.open() as f:
41
41
  pid = int(f.read().strip())
42
42
 
43
43
  # Check if process exists
@@ -85,7 +85,7 @@ def start_server(port: int = DEFAULT_PORT, daemon: bool = True) -> bool:
85
85
  logger.info(f"Socket.IO daemon started on port {actual_port}")
86
86
  # Save the port for clients to discover
87
87
  port_file = pid_file.parent / "socketio-port"
88
- with open(port_file, "w") as f:
88
+ with port_file.open("w") as f:
89
89
  f.write(str(actual_port))
90
90
  else:
91
91
  logger.error("Failed to start Socket.IO daemon")
@@ -102,7 +102,7 @@ def stop_server() -> bool:
102
102
  return False
103
103
 
104
104
  try:
105
- with open(pid_file) as f:
105
+ with pid_file.open() as f:
106
106
  pid = int(f.read().strip())
107
107
 
108
108
  # Send SIGTERM for graceful shutdown
@@ -155,12 +155,12 @@ def status_server() -> bool:
155
155
 
156
156
  if is_running(pid_file):
157
157
  try:
158
- with open(pid_file) as f:
158
+ with pid_file.open() as f:
159
159
  pid = int(f.read().strip())
160
160
 
161
161
  port = DEFAULT_PORT
162
162
  if port_file.exists():
163
- with open(port_file) as f:
163
+ with port_file.open() as f:
164
164
  port = int(f.read().strip())
165
165
 
166
166
  print(f"Socket.IO daemon is running (PID: {pid}, Port: {port})")
@@ -144,8 +144,8 @@ def __getattr__(name):
144
144
  try:
145
145
  module = import_module("claude_mpm.services.recovery_manager")
146
146
  return module.RecoveryManager
147
- except ImportError:
148
- raise AttributeError(f"Recovery management not available: {name}")
147
+ except ImportError as e:
148
+ raise AttributeError(f"Recovery management not available: {name}") from e
149
149
 
150
150
  # Handle MCP interfaces (names starting with "IMCP")
151
151
  if name.startswith("IMCP"):
@@ -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: # noqa: PLR0911
218
+ def _categorize_agent(self, agent_id: str, content: str) -> str:
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()