claude-mpm 4.5.11__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (183) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/frontmatter_validator.py +4 -4
  3. claude_mpm/cli/commands/agent_manager.py +3 -3
  4. claude_mpm/cli/commands/agents.py +6 -6
  5. claude_mpm/cli/commands/aggregate.py +4 -4
  6. claude_mpm/cli/commands/analyze.py +2 -2
  7. claude_mpm/cli/commands/analyze_code.py +1 -1
  8. claude_mpm/cli/commands/cleanup.py +3 -3
  9. claude_mpm/cli/commands/config.py +2 -2
  10. claude_mpm/cli/commands/configure.py +14 -14
  11. claude_mpm/cli/commands/dashboard.py +1 -1
  12. claude_mpm/cli/commands/debug.py +3 -3
  13. claude_mpm/cli/commands/doctor.py +1 -1
  14. claude_mpm/cli/commands/mcp.py +7 -7
  15. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  16. claude_mpm/cli/commands/mcp_config.py +2 -2
  17. claude_mpm/cli/commands/mcp_external_commands.py +2 -2
  18. claude_mpm/cli/commands/mcp_install_commands.py +3 -3
  19. claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
  20. claude_mpm/cli/commands/mcp_setup_external.py +3 -3
  21. claude_mpm/cli/commands/monitor.py +1 -1
  22. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  23. claude_mpm/cli/interactive/agent_wizard.py +1 -1
  24. claude_mpm/cli/parsers/search_parser.py +1 -1
  25. claude_mpm/cli/shared/argument_patterns.py +2 -2
  26. claude_mpm/cli/shared/base_command.py +1 -1
  27. claude_mpm/cli/startup_logging.py +4 -4
  28. claude_mpm/config/experimental_features.py +4 -4
  29. claude_mpm/config/socketio_config.py +2 -2
  30. claude_mpm/core/agent_session_manager.py +2 -2
  31. claude_mpm/core/api_validator.py +3 -3
  32. claude_mpm/core/base_service.py +10 -1
  33. claude_mpm/core/cache.py +2 -2
  34. claude_mpm/core/config.py +4 -4
  35. claude_mpm/core/config_aliases.py +4 -4
  36. claude_mpm/core/config_constants.py +1 -1
  37. claude_mpm/core/error_handler.py +1 -1
  38. claude_mpm/core/file_utils.py +5 -5
  39. claude_mpm/core/framework/formatters/capability_generator.py +5 -5
  40. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  41. claude_mpm/core/framework/processors/metadata_processor.py +1 -1
  42. claude_mpm/core/framework/processors/template_processor.py +3 -3
  43. claude_mpm/core/framework_loader.py +2 -2
  44. claude_mpm/core/log_manager.py +4 -4
  45. claude_mpm/core/logger.py +2 -2
  46. claude_mpm/core/optimized_startup.py +1 -1
  47. claude_mpm/core/output_style_manager.py +1 -1
  48. claude_mpm/core/service_registry.py +2 -2
  49. claude_mpm/core/session_manager.py +3 -3
  50. claude_mpm/core/shared/config_loader.py +1 -1
  51. claude_mpm/core/socketio_pool.py +2 -2
  52. claude_mpm/core/unified_agent_registry.py +2 -2
  53. claude_mpm/core/unified_config.py +6 -6
  54. claude_mpm/core/unified_paths.py +2 -2
  55. claude_mpm/dashboard/api/simple_directory.py +1 -1
  56. claude_mpm/generators/agent_profile_generator.py +1 -1
  57. claude_mpm/hooks/claude_hooks/event_handlers.py +2 -2
  58. claude_mpm/hooks/claude_hooks/installer.py +9 -9
  59. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +7 -2
  60. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  61. claude_mpm/hooks/memory_integration_hook.py +1 -1
  62. claude_mpm/hooks/validation_hooks.py +1 -1
  63. claude_mpm/init.py +4 -4
  64. claude_mpm/models/agent_session.py +1 -1
  65. claude_mpm/scripts/socketio_daemon.py +5 -5
  66. claude_mpm/services/__init__.py +2 -2
  67. claude_mpm/services/agent_capabilities_service.py +1 -1
  68. claude_mpm/services/agents/agent_builder.py +4 -4
  69. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -1
  70. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  71. claude_mpm/services/agents/deployment/agent_record_service.py +3 -3
  72. claude_mpm/services/agents/deployment/deployment_wrapper.py +1 -1
  73. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +2 -2
  74. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  75. claude_mpm/services/agents/local_template_manager.py +5 -5
  76. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  77. claude_mpm/services/agents/registry/modification_tracker.py +19 -11
  78. claude_mpm/services/async_session_logger.py +1 -1
  79. claude_mpm/services/claude_session_logger.py +1 -1
  80. claude_mpm/services/cli/agent_listing_service.py +3 -3
  81. claude_mpm/services/cli/agent_validation_service.py +1 -1
  82. claude_mpm/services/cli/session_manager.py +2 -2
  83. claude_mpm/services/core/path_resolver.py +1 -1
  84. claude_mpm/services/diagnostics/checks/agent_check.py +1 -1
  85. claude_mpm/services/diagnostics/checks/claude_code_check.py +2 -2
  86. claude_mpm/services/diagnostics/checks/common_issues_check.py +3 -3
  87. claude_mpm/services/diagnostics/checks/configuration_check.py +2 -2
  88. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  89. claude_mpm/services/diagnostics/checks/mcp_check.py +1 -1
  90. claude_mpm/services/diagnostics/checks/mcp_services_check.py +9 -9
  91. claude_mpm/services/diagnostics/checks/monitor_check.py +1 -1
  92. claude_mpm/services/diagnostics/doctor_reporter.py +1 -1
  93. claude_mpm/services/event_aggregator.py +1 -1
  94. claude_mpm/services/event_bus/event_bus.py +7 -2
  95. claude_mpm/services/events/consumers/dead_letter.py +2 -2
  96. claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
  97. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
  98. claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
  99. claude_mpm/services/hook_installer_service.py +7 -7
  100. claude_mpm/services/infrastructure/context_preservation.py +7 -7
  101. claude_mpm/services/infrastructure/daemon_manager.py +5 -5
  102. claude_mpm/services/mcp_config_manager.py +10 -10
  103. claude_mpm/services/mcp_gateway/auto_configure.py +5 -5
  104. claude_mpm/services/mcp_gateway/config/config_loader.py +2 -2
  105. claude_mpm/services/mcp_gateway/config/configuration.py +3 -3
  106. claude_mpm/services/mcp_gateway/core/process_pool.py +3 -3
  107. claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
  108. claude_mpm/services/mcp_gateway/core/startup_verification.py +1 -1
  109. claude_mpm/services/mcp_gateway/main.py +1 -1
  110. claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
  111. claude_mpm/services/mcp_gateway/registry/tool_registry.py +2 -1
  112. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  113. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -1
  114. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +1 -1
  115. claude_mpm/services/mcp_gateway/tools/hello_world.py +1 -1
  116. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +5 -5
  117. claude_mpm/services/mcp_gateway/utils/update_preferences.py +2 -2
  118. claude_mpm/services/mcp_service_verifier.py +1 -1
  119. claude_mpm/services/memory/builder.py +1 -1
  120. claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
  121. claude_mpm/services/memory/indexed_memory.py +3 -3
  122. claude_mpm/services/monitor/daemon.py +1 -1
  123. claude_mpm/services/monitor/daemon_manager.py +9 -9
  124. claude_mpm/services/monitor/handlers/file.py +1 -1
  125. claude_mpm/services/monitor/handlers/hooks.py +3 -3
  126. claude_mpm/services/monitor/management/lifecycle.py +7 -7
  127. claude_mpm/services/monitor/server.py +2 -2
  128. claude_mpm/services/orphan_detection.py +13 -16
  129. claude_mpm/services/port_manager.py +2 -2
  130. claude_mpm/services/project/analyzer.py +3 -3
  131. claude_mpm/services/project/archive_manager.py +13 -13
  132. claude_mpm/services/project/dependency_analyzer.py +4 -4
  133. claude_mpm/services/project/documentation_manager.py +4 -4
  134. claude_mpm/services/project/enhanced_analyzer.py +8 -8
  135. claude_mpm/services/project/registry.py +4 -4
  136. claude_mpm/services/project_port_allocator.py +7 -11
  137. claude_mpm/services/session_management_service.py +1 -1
  138. claude_mpm/services/socketio/event_normalizer.py +1 -1
  139. claude_mpm/services/socketio/handlers/code_analysis.py +14 -12
  140. claude_mpm/services/socketio/handlers/file.py +1 -1
  141. claude_mpm/services/socketio/migration_utils.py +1 -1
  142. claude_mpm/services/socketio/server/core.py +1 -1
  143. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +1 -1
  144. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -4
  145. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
  146. claude_mpm/services/unified/config_strategies/config_schema.py +4 -4
  147. claude_mpm/services/unified/config_strategies/context_strategy.py +6 -6
  148. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +10 -10
  149. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -5
  150. claude_mpm/services/unified/config_strategies/unified_config_service.py +8 -8
  151. claude_mpm/services/unified/config_strategies/validation_strategy.py +15 -15
  152. claude_mpm/services/unified/deployment_strategies/base.py +4 -4
  153. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +15 -15
  154. claude_mpm/services/unified/deployment_strategies/local.py +9 -9
  155. claude_mpm/services/unified/deployment_strategies/utils.py +9 -9
  156. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -7
  157. claude_mpm/services/unified/unified_config.py +5 -5
  158. claude_mpm/services/unified/unified_deployment.py +2 -2
  159. claude_mpm/services/utility_service.py +1 -1
  160. claude_mpm/services/version_control/conflict_resolution.py +2 -2
  161. claude_mpm/services/version_control/git_operations.py +3 -3
  162. claude_mpm/services/version_control/semantic_versioning.py +13 -13
  163. claude_mpm/services/version_control/version_parser.py +1 -1
  164. claude_mpm/storage/state_storage.py +12 -13
  165. claude_mpm/tools/code_tree_analyzer.py +5 -5
  166. claude_mpm/tools/code_tree_builder.py +4 -4
  167. claude_mpm/tools/socketio_debug.py +1 -1
  168. claude_mpm/utils/agent_dependency_loader.py +4 -4
  169. claude_mpm/utils/common.py +2 -2
  170. claude_mpm/utils/config_manager.py +3 -3
  171. claude_mpm/utils/dependency_cache.py +2 -2
  172. claude_mpm/utils/dependency_strategies.py +6 -6
  173. claude_mpm/utils/file_utils.py +11 -11
  174. claude_mpm/utils/log_cleanup.py +1 -1
  175. claude_mpm/utils/path_operations.py +1 -1
  176. claude_mpm/validation/agent_validator.py +2 -2
  177. claude_mpm/validation/frontmatter_validator.py +1 -1
  178. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/METADATA +1 -1
  179. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/RECORD +183 -183
  180. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/WHEEL +0 -0
  181. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/entry_points.txt +0 -0
  182. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/licenses/LICENSE +0 -0
  183. {claude_mpm-4.5.11.dist-info → claude_mpm-4.5.12.dist-info}/top_level.txt +0 -0
@@ -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()
@@ -287,7 +287,7 @@ class AgentBuilderService:
287
287
 
288
288
  for template_file in self.templates_dir.glob("*.json"):
289
289
  try:
290
- with open(template_file) as f:
290
+ with template_file.open() as f:
291
291
  config = json.load(f)
292
292
 
293
293
  # Use filename stem as ID if not specified in config
@@ -377,12 +377,12 @@ class AgentBuilderService:
377
377
  raise AgentDeploymentError(f"Template '{template_id}' not found")
378
378
 
379
379
  try:
380
- with open(template_file) as f:
380
+ with template_file.open() as f:
381
381
  config = json.load(f)
382
382
  self._template_cache[template_id] = config
383
383
  return config.copy()
384
384
  except Exception as e:
385
- raise AgentDeploymentError(f"Failed to load template '{template_id}': {e}")
385
+ raise AgentDeploymentError(f"Failed to load template '{template_id}': {e}") from e
386
386
 
387
387
  def _load_instructions(self, agent_id: str) -> str:
388
388
  """Load agent instructions.
@@ -406,7 +406,7 @@ class AgentBuilderService:
406
406
  for instructions_file in possible_files:
407
407
  if instructions_file.exists():
408
408
  try:
409
- with open(instructions_file) as f:
409
+ with instructions_file.open() as f:
410
410
  return f.read()
411
411
  except Exception as e:
412
412
  self.logger.warning(
@@ -272,7 +272,7 @@ class AgentLifecycleManager(BaseService):
272
272
  # First convert to JSON string with custom encoder, then save
273
273
  json_str = json.dumps(data, indent=2, default=str)
274
274
  records_file.parent.mkdir(parents=True, exist_ok=True)
275
- with open(records_file, "w", encoding="utf-8") as f:
275
+ with records_file.open("w", encoding="utf-8") as f:
276
276
  f.write(json_str)
277
277
 
278
278
  self.logger.debug(f"Saved {len(self.agent_records)} agent records")
@@ -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: # noqa: PLR0911
230
+ def _extract_agent_type(self, agent_name: str) -> str:
231
231
  """
232
232
  Extract agent type from agent name for categorization.
233
233
 
@@ -80,7 +80,7 @@ class AgentRecordService(BaseService):
80
80
  json_str = json.dumps(data, indent=2, default=str)
81
81
  self.records_file.parent.mkdir(parents=True, exist_ok=True)
82
82
 
83
- with open(self.records_file, "w", encoding="utf-8") as f:
83
+ with self.records_file.open("w", encoding="utf-8") as f:
84
84
  f.write(json_str)
85
85
 
86
86
  self.logger.debug(f"Saved {len(records)} agent records")
@@ -149,7 +149,7 @@ class AgentRecordService(BaseService):
149
149
  json_str = json.dumps(data, indent=2, default=str)
150
150
  self.history_file.parent.mkdir(parents=True, exist_ok=True)
151
151
 
152
- with open(self.history_file, "w", encoding="utf-8") as f:
152
+ with self.history_file.open("w", encoding="utf-8") as f:
153
153
  f.write(json_str)
154
154
 
155
155
  self.logger.debug(f"Saved {len(history)} operation history entries")
@@ -255,7 +255,7 @@ class AgentRecordService(BaseService):
255
255
 
256
256
  # Write to output path
257
257
  json_str = json.dumps(data, indent=2, default=str)
258
- with open(output_path, "w", encoding="utf-8") as f:
258
+ with output_path.open("w", encoding="utf-8") as f:
259
259
  f.write(json_str)
260
260
 
261
261
  elif format == "csv":
@@ -91,7 +91,7 @@ class DeploymentServiceWrapper:
91
91
 
92
92
  # Read agent content if file exists
93
93
  if agent_path.exists():
94
- with open(agent_path) as f:
94
+ with agent_path.open() as f:
95
95
  content = f.read()
96
96
 
97
97
  # Parse metadata from content
@@ -48,10 +48,10 @@ class TargetDirectorySetupStep(BaseDeploymentStep):
48
48
  try:
49
49
  test_file.write_text("test")
50
50
  test_file.unlink()
51
- except Exception:
51
+ except Exception as e:
52
52
  raise PermissionError(
53
53
  f"Target directory is not writable: {context.actual_target_dir}"
54
- )
54
+ ) from e
55
55
 
56
56
  self.logger.info(f"Target directory set up: {context.actual_target_dir}")
57
57
 
@@ -457,7 +457,7 @@ class AgentProfileLoader(BaseService):
457
457
 
458
458
  if prompt_file.exists():
459
459
  try:
460
- with open(prompt_file) as f:
460
+ with prompt_file.open() as f:
461
461
  data = json.load(f)
462
462
  for prompt_data in data:
463
463
  prompt = ImprovedPrompt(
@@ -500,7 +500,7 @@ class AgentProfileLoader(BaseService):
500
500
 
501
501
  # Save to file
502
502
  prompt_file = self.improved_prompts_path / f"{agent_name}_prompts.json"
503
- with open(prompt_file, "w") as f:
503
+ with prompt_file.open("w") as f:
504
504
  json.dump(
505
505
  [
506
506
  {
@@ -195,7 +195,7 @@ class LocalAgentTemplateManager:
195
195
  """
196
196
  for template_file in directory.glob("*.json"):
197
197
  try:
198
- with open(template_file) as f:
198
+ with template_file.open() as f:
199
199
  data = json.load(f)
200
200
 
201
201
  # Create LocalAgentTemplate
@@ -307,7 +307,7 @@ class LocalAgentTemplateManager:
307
307
 
308
308
  # Save to JSON file
309
309
  template_file = target_dir / f"{template.agent_id}.json"
310
- with open(template_file, "w") as f:
310
+ with template_file.open("w") as f:
311
311
  json.dump(template.to_json(), f, indent=2)
312
312
 
313
313
  # Invalidate cache
@@ -628,7 +628,7 @@ class LocalAgentTemplateManager:
628
628
 
629
629
  # Save current version
630
630
  old_version_file = versions_dir / f"{template.agent_version}.json"
631
- with open(old_version_file, "w") as f:
631
+ with old_version_file.open("w") as f:
632
632
  json.dump(template.to_json(), f, indent=2)
633
633
 
634
634
  # Update template version
@@ -691,7 +691,7 @@ class LocalAgentTemplateManager:
691
691
  count = 0
692
692
  for agent_id, template in templates.items():
693
693
  output_file = output_dir / f"{agent_id}.json"
694
- with open(output_file, "w") as f:
694
+ with output_file.open("w") as f:
695
695
  json.dump(template.to_json(), f, indent=2)
696
696
  count += 1
697
697
 
@@ -715,7 +715,7 @@ class LocalAgentTemplateManager:
715
715
  count = 0
716
716
  for template_file in input_dir.glob("*.json"):
717
717
  try:
718
- with open(template_file) as f:
718
+ with template_file.open() as f:
719
719
  data = json.load(f)
720
720
 
721
721
  template = LocalAgentTemplate.from_json(data)
@@ -166,7 +166,7 @@ class DeployedAgentDiscovery(ConfigServiceBase):
166
166
  try:
167
167
  path = Path(agent_path)
168
168
  if path.exists() and path.suffix == ".json":
169
- with open(path) as f:
169
+ with path.open() as f:
170
170
  return json.load(f)
171
171
  except Exception as e:
172
172
  logger.warning(f"Failed to load full agent data from {agent_path}: {e}")
@@ -156,12 +156,19 @@ class AgentFileSystemHandler(FileSystemEventHandler):
156
156
  def __init__(self, tracker: "AgentModificationTracker"):
157
157
  self.tracker = tracker
158
158
 
159
+ def _create_tracked_task(self, coro):
160
+ """Create a task with automatic tracking and cleanup."""
161
+ task = asyncio.create_task(coro)
162
+ self.tracker._file_event_tasks.add(task)
163
+ task.add_done_callback(self.tracker._file_event_tasks.discard)
164
+ return task
165
+
159
166
  def on_created(self, event: FileSystemEvent) -> None:
160
167
  """Handle file creation events."""
161
168
  if not event.is_directory and event.src_path.endswith(
162
169
  (".md", ".json", ".yaml")
163
170
  ):
164
- asyncio.create_task(
171
+ self._create_tracked_task(
165
172
  self.tracker._handle_file_modification(
166
173
  event.src_path, ModificationType.CREATE
167
174
  )
@@ -172,7 +179,7 @@ class AgentFileSystemHandler(FileSystemEventHandler):
172
179
  if not event.is_directory and event.src_path.endswith(
173
180
  (".md", ".json", ".yaml")
174
181
  ):
175
- asyncio.create_task(
182
+ self._create_tracked_task(
176
183
  self.tracker._handle_file_modification(
177
184
  event.src_path, ModificationType.MODIFY
178
185
  )
@@ -183,7 +190,7 @@ class AgentFileSystemHandler(FileSystemEventHandler):
183
190
  if not event.is_directory and event.src_path.endswith(
184
191
  (".md", ".json", ".yaml")
185
192
  ):
186
- asyncio.create_task(
193
+ self._create_tracked_task(
187
194
  self.tracker._handle_file_modification(
188
195
  event.src_path, ModificationType.DELETE
189
196
  )
@@ -194,7 +201,7 @@ class AgentFileSystemHandler(FileSystemEventHandler):
194
201
  if not event.is_directory and event.src_path.endswith(
195
202
  (".md", ".json", ".yaml")
196
203
  ):
197
- asyncio.create_task(
204
+ self._create_tracked_task(
198
205
  self.tracker._handle_file_move(event.src_path, event.dest_path)
199
206
  )
200
207
 
@@ -248,6 +255,7 @@ class AgentModificationTracker(BaseService):
248
255
  # Background tasks
249
256
  self._persistence_task: Optional[asyncio.Task] = None
250
257
  self._cleanup_task: Optional[asyncio.Task] = None
258
+ self._file_event_tasks: Set[asyncio.Task] = set() # Track file event tasks
251
259
 
252
260
  # Callbacks
253
261
  self.modification_callbacks: List[Callable[[AgentModification], None]] = []
@@ -473,7 +481,7 @@ class AgentModificationTracker(BaseService):
473
481
  metadata["file_size_after"] = path.stat().st_size
474
482
 
475
483
  # File hash
476
- with open(path, "rb") as f:
484
+ with path.open("rb") as f:
477
485
  metadata["file_hash_after"] = hashlib.sha256(f.read()).hexdigest()
478
486
 
479
487
  # File type
@@ -512,7 +520,7 @@ class AgentModificationTracker(BaseService):
512
520
  }
513
521
 
514
522
  metadata_path = backup_dir / "metadata.json"
515
- with open(metadata_path, "w") as f:
523
+ with metadata_path.open("w") as f:
516
524
  json.dump(metadata, f, indent=2)
517
525
 
518
526
  return str(backup_path)
@@ -661,7 +669,7 @@ class AgentModificationTracker(BaseService):
661
669
  # Load active modifications
662
670
  active_path = self.persistence_root / "active_modifications.json"
663
671
  if active_path.exists():
664
- with open(active_path) as f:
672
+ with active_path.open() as f:
665
673
  data = json.load(f)
666
674
  self.active_modifications = {
667
675
  k: AgentModification.from_dict(v) for k, v in data.items()
@@ -669,7 +677,7 @@ class AgentModificationTracker(BaseService):
669
677
 
670
678
  # Load modification history
671
679
  for history_file in self.history_root.glob("*.json"):
672
- with open(history_file) as f:
680
+ with history_file.open() as f:
673
681
  data = json.load(f)
674
682
  agent_name = data["agent_name"]
675
683
  history = ModificationHistory(agent_name=agent_name)
@@ -699,7 +707,7 @@ class AgentModificationTracker(BaseService):
699
707
  active_data = {k: v.to_dict() for k, v in self.active_modifications.items()}
700
708
  active_path = self.persistence_root / "active_modifications.json"
701
709
 
702
- with open(active_path, "w") as f:
710
+ with active_path.open("w") as f:
703
711
  json.dump(active_data, f, indent=2)
704
712
 
705
713
  # Save modification history
@@ -714,7 +722,7 @@ class AgentModificationTracker(BaseService):
714
722
  }
715
723
 
716
724
  history_path = self.history_root / f"{agent_name}_history.json"
717
- with open(history_path, "w") as f:
725
+ with history_path.open("w") as f:
718
726
  json.dump(history_data, f, indent=2)
719
727
 
720
728
  self.logger.debug("Saved modification history to disk")
@@ -767,7 +775,7 @@ class AgentModificationTracker(BaseService):
767
775
  if backup_dir.is_dir():
768
776
  metadata_path = backup_dir / "metadata.json"
769
777
  if metadata_path.exists():
770
- with open(metadata_path) as f:
778
+ with metadata_path.open() as f:
771
779
  metadata = json.load(f)
772
780
  if metadata.get("backup_time", 0) < cutoff_time:
773
781
  shutil.rmtree(backup_dir)
@@ -336,7 +336,7 @@ class AsyncSessionLogger:
336
336
  with gzip.open(file_path, "wt", encoding="utf-8") as f:
337
337
  json.dump(data, f, indent=2, ensure_ascii=False)
338
338
  else:
339
- with open(file_path, "w", encoding="utf-8") as f:
339
+ with file_path.open("w", encoding="utf-8") as f:
340
340
  json.dump(data, f, indent=2, ensure_ascii=False)
341
341
 
342
342
  logger.debug(f"Wrote log entry to {file_path}")
@@ -224,7 +224,7 @@ class ClaudeSessionLogger:
224
224
 
225
225
  # Save response
226
226
  try:
227
- with open(file_path, "w", encoding="utf-8") as f:
227
+ with file_path.open("w", encoding="utf-8") as f:
228
228
  json.dump(response_data, f, indent=2, ensure_ascii=False)
229
229
 
230
230
  logger.debug(f"Logged response to {filename} for session {self.session_id}")
@@ -169,8 +169,8 @@ class AgentListingService(IAgentListingService):
169
169
 
170
170
  base_service = AgentDeploymentService()
171
171
  self._deployment_service = DeploymentServiceWrapper(base_service)
172
- except ImportError:
173
- raise ImportError("Agent deployment service not available")
172
+ except ImportError as e:
173
+ raise ImportError("Agent deployment service not available") from e
174
174
  return self._deployment_service
175
175
 
176
176
  @property
@@ -364,7 +364,7 @@ class AgentListingService(IAgentListingService):
364
364
  if not agent_path.exists():
365
365
  return None
366
366
 
367
- with open(agent_path) as f:
367
+ with agent_path.open() as f:
368
368
  content = f.read()
369
369
 
370
370
  details = {
@@ -72,7 +72,7 @@ class AgentValidationService(IAgentValidationService):
72
72
  self._registry = adapter.registry
73
73
  except Exception as e:
74
74
  self.logger.error(f"Failed to initialize agent registry: {e}")
75
- raise RuntimeError(f"Could not initialize agent registry: {e}")
75
+ raise RuntimeError(f"Could not initialize agent registry: {e}") from e
76
76
  return self._registry
77
77
 
78
78
  def validate_agent(self, agent_name: str) -> Dict[str, Any]:
@@ -449,7 +449,7 @@ class SessionManager(ISessionManager):
449
449
  sessions_dict = {
450
450
  sid: session.to_dict() for sid, session in self._sessions_cache.items()
451
451
  }
452
- with open(session_file, "w") as f:
452
+ with session_file.open("w") as f:
453
453
  json.dump(sessions_dict, f, indent=2)
454
454
  except Exception as e:
455
455
  self.logger.error(f"Failed to save sessions: {e}")
@@ -464,7 +464,7 @@ class SessionManager(ISessionManager):
464
464
  return
465
465
 
466
466
  try:
467
- with open(session_file) as f:
467
+ with session_file.open() as f:
468
468
  sessions_dict = json.load(f)
469
469
 
470
470
  self._sessions_cache = {
@@ -166,7 +166,7 @@ class PathResolver(IPathResolver):
166
166
  self.logger.debug(f"No project root found from {start_path}")
167
167
  return None
168
168
 
169
- def detect_framework_path(self) -> Optional[Path]: # noqa: PLR0911
169
+ def detect_framework_path(self) -> Optional[Path]:
170
170
  """
171
171
  Auto-detect claude-mpm framework using unified path management.
172
172
 
@@ -289,7 +289,7 @@ class AgentCheck(BaseDiagnosticCheck):
289
289
 
290
290
  # Basic validation
291
291
  try:
292
- with open(agent_file) as f:
292
+ with agent_file.open() as f:
293
293
  content = f.read()
294
294
 
295
295
  # Check for required sections
@@ -187,7 +187,7 @@ class ClaudeCodeCheck(BaseDiagnosticCheck):
187
187
 
188
188
  # Check if it's up to date
189
189
  try:
190
- with open(style_path) as f:
190
+ with style_path.open() as f:
191
191
  content = f.read()
192
192
  if "Claude MPM Output Style" in content:
193
193
  return DiagnosticResult(
@@ -232,7 +232,7 @@ class ClaudeCodeCheck(BaseDiagnosticCheck):
232
232
  )
233
233
 
234
234
  try:
235
- with open(config_path) as f:
235
+ with config_path.open() as f:
236
236
  config = json.load(f)
237
237
 
238
238
  mcp_servers = config.get("mcpServers", {})