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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +20 -5
- claude_mpm/agents/agent_loader.py +19 -2
- claude_mpm/agents/base_agent_loader.py +5 -5
- claude_mpm/agents/frontmatter_validator.py +4 -4
- claude_mpm/agents/templates/agent-manager.json +3 -3
- claude_mpm/agents/templates/agentic-coder-optimizer.json +3 -3
- claude_mpm/agents/templates/api_qa.json +1 -1
- claude_mpm/agents/templates/clerk-ops.json +3 -3
- claude_mpm/agents/templates/code_analyzer.json +3 -3
- claude_mpm/agents/templates/dart_engineer.json +294 -0
- claude_mpm/agents/templates/data_engineer.json +3 -3
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/gcp_ops_agent.json +2 -2
- claude_mpm/agents/templates/imagemagick.json +1 -1
- claude_mpm/agents/templates/local_ops_agent.json +319 -41
- claude_mpm/agents/templates/memory_manager.json +2 -2
- claude_mpm/agents/templates/nextjs_engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/php-engineer.json +1 -1
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/prompt-engineer.json +6 -4
- claude_mpm/agents/templates/python_engineer.json +2 -2
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/react_engineer.json +3 -3
- claude_mpm/agents/templates/refactoring_engineer.json +3 -3
- claude_mpm/agents/templates/research.json +2 -2
- claude_mpm/agents/templates/security.json +2 -2
- claude_mpm/agents/templates/ticketing.json +2 -2
- claude_mpm/agents/templates/typescript_engineer.json +2 -2
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +2 -2
- claude_mpm/agents/templates/web_qa.json +6 -6
- claude_mpm/agents/templates/web_ui.json +3 -3
- claude_mpm/cli/__init__.py +49 -19
- claude_mpm/cli/commands/agent_manager.py +3 -3
- claude_mpm/cli/commands/agents.py +6 -6
- claude_mpm/cli/commands/aggregate.py +4 -4
- claude_mpm/cli/commands/analyze.py +2 -2
- claude_mpm/cli/commands/analyze_code.py +1 -1
- claude_mpm/cli/commands/cleanup.py +3 -3
- claude_mpm/cli/commands/config.py +2 -2
- claude_mpm/cli/commands/configure.py +605 -21
- claude_mpm/cli/commands/dashboard.py +1 -1
- claude_mpm/cli/commands/debug.py +3 -3
- claude_mpm/cli/commands/doctor.py +1 -1
- claude_mpm/cli/commands/mcp.py +7 -7
- claude_mpm/cli/commands/mcp_command_router.py +1 -1
- claude_mpm/cli/commands/mcp_config.py +2 -2
- claude_mpm/cli/commands/mcp_external_commands.py +2 -2
- claude_mpm/cli/commands/mcp_install_commands.py +3 -3
- claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
- claude_mpm/cli/commands/mcp_setup_external.py +3 -3
- claude_mpm/cli/commands/monitor.py +1 -1
- claude_mpm/cli/commands/mpm_init_handler.py +1 -1
- claude_mpm/cli/interactive/agent_wizard.py +1 -1
- claude_mpm/cli/parsers/configure_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +1 -1
- claude_mpm/cli/shared/argument_patterns.py +2 -2
- claude_mpm/cli/shared/base_command.py +1 -1
- claude_mpm/cli/startup_logging.py +4 -4
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/socketio_config.py +2 -2
- claude_mpm/core/__init__.py +53 -17
- claude_mpm/core/agent_session_manager.py +2 -2
- claude_mpm/core/api_validator.py +3 -3
- claude_mpm/core/base_service.py +10 -1
- claude_mpm/core/cache.py +2 -2
- claude_mpm/core/config.py +5 -5
- claude_mpm/core/config_aliases.py +4 -4
- claude_mpm/core/config_constants.py +1 -1
- claude_mpm/core/error_handler.py +1 -1
- claude_mpm/core/file_utils.py +5 -5
- claude_mpm/core/framework/formatters/capability_generator.py +5 -5
- claude_mpm/core/framework/loaders/agent_loader.py +1 -1
- claude_mpm/core/framework/processors/metadata_processor.py +1 -1
- claude_mpm/core/framework/processors/template_processor.py +3 -3
- claude_mpm/core/framework_loader.py +2 -2
- claude_mpm/core/log_manager.py +11 -4
- claude_mpm/core/logger.py +2 -2
- claude_mpm/core/optimized_startup.py +1 -1
- claude_mpm/core/output_style_manager.py +1 -1
- claude_mpm/core/service_registry.py +2 -2
- claude_mpm/core/session_manager.py +3 -3
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +2 -2
- claude_mpm/core/unified_agent_registry.py +2 -2
- claude_mpm/core/unified_config.py +6 -6
- claude_mpm/core/unified_paths.py +2 -2
- claude_mpm/dashboard/api/simple_directory.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +1 -1
- claude_mpm/hooks/claude_hooks/event_handlers.py +2 -2
- claude_mpm/hooks/claude_hooks/installer.py +9 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +16 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +16 -13
- claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/init.py +4 -4
- claude_mpm/models/agent_session.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +5 -5
- claude_mpm/services/__init__.py +145 -161
- claude_mpm/services/agent_capabilities_service.py +1 -1
- claude_mpm/services/agents/agent_builder.py +4 -4
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +3 -3
- claude_mpm/services/agents/deployment/deployment_config_loader.py +21 -0
- claude_mpm/services/agents/deployment/deployment_wrapper.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
- claude_mpm/services/agents/loading/base_agent_manager.py +12 -2
- claude_mpm/services/agents/local_template_manager.py +5 -5
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/agents/registry/modification_tracker.py +19 -11
- claude_mpm/services/async_session_logger.py +3 -3
- claude_mpm/services/claude_session_logger.py +4 -4
- claude_mpm/services/cli/agent_listing_service.py +3 -3
- claude_mpm/services/cli/agent_validation_service.py +1 -1
- claude_mpm/services/cli/session_manager.py +2 -2
- claude_mpm/services/core/path_resolver.py +1 -1
- claude_mpm/services/diagnostics/checks/agent_check.py +1 -1
- claude_mpm/services/diagnostics/checks/claude_code_check.py +2 -2
- claude_mpm/services/diagnostics/checks/common_issues_check.py +3 -3
- claude_mpm/services/diagnostics/checks/configuration_check.py +2 -2
- claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
- claude_mpm/services/diagnostics/checks/mcp_check.py +1 -1
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +9 -9
- claude_mpm/services/diagnostics/checks/monitor_check.py +1 -1
- claude_mpm/services/diagnostics/doctor_reporter.py +1 -1
- claude_mpm/services/event_aggregator.py +1 -1
- claude_mpm/services/event_bus/event_bus.py +7 -2
- claude_mpm/services/events/consumers/dead_letter.py +2 -2
- claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
- claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
- claude_mpm/services/hook_installer_service.py +7 -7
- claude_mpm/services/infrastructure/context_preservation.py +7 -7
- claude_mpm/services/infrastructure/daemon_manager.py +5 -5
- claude_mpm/services/mcp_config_manager.py +169 -48
- claude_mpm/services/mcp_gateway/__init__.py +98 -94
- claude_mpm/services/mcp_gateway/auto_configure.py +5 -5
- claude_mpm/services/mcp_gateway/config/config_loader.py +2 -2
- claude_mpm/services/mcp_gateway/config/configuration.py +3 -3
- claude_mpm/services/mcp_gateway/core/process_pool.py +3 -3
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
- claude_mpm/services/mcp_gateway/core/startup_verification.py +1 -1
- claude_mpm/services/mcp_gateway/main.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +2 -1
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -1
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +1 -1
- claude_mpm/services/mcp_gateway/tools/hello_world.py +1 -1
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +5 -5
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +2 -2
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/builder.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
- claude_mpm/services/memory/indexed_memory.py +3 -3
- claude_mpm/services/monitor/daemon.py +1 -1
- claude_mpm/services/monitor/daemon_manager.py +9 -9
- claude_mpm/services/monitor/event_emitter.py +1 -1
- claude_mpm/services/monitor/handlers/file.py +1 -1
- claude_mpm/services/monitor/handlers/hooks.py +3 -3
- claude_mpm/services/monitor/management/lifecycle.py +7 -7
- claude_mpm/services/monitor/server.py +2 -2
- claude_mpm/services/orphan_detection.py +788 -0
- claude_mpm/services/port_manager.py +2 -2
- claude_mpm/services/project/analyzer.py +3 -3
- claude_mpm/services/project/archive_manager.py +13 -13
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/documentation_manager.py +4 -4
- claude_mpm/services/project/enhanced_analyzer.py +8 -8
- claude_mpm/services/project/registry.py +4 -4
- claude_mpm/services/project_port_allocator.py +597 -0
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/session_management_service.py +1 -1
- claude_mpm/services/session_manager.py +6 -4
- claude_mpm/services/socketio/event_normalizer.py +1 -1
- claude_mpm/services/socketio/handlers/code_analysis.py +14 -12
- claude_mpm/services/socketio/handlers/file.py +1 -1
- claude_mpm/services/socketio/migration_utils.py +1 -1
- claude_mpm/services/socketio/server/core.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -4
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
- claude_mpm/services/unified/config_strategies/config_schema.py +4 -4
- claude_mpm/services/unified/config_strategies/context_strategy.py +6 -6
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +10 -10
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -5
- claude_mpm/services/unified/config_strategies/unified_config_service.py +8 -8
- claude_mpm/services/unified/config_strategies/validation_strategy.py +15 -15
- claude_mpm/services/unified/deployment_strategies/base.py +4 -4
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +15 -15
- claude_mpm/services/unified/deployment_strategies/local.py +9 -9
- claude_mpm/services/unified/deployment_strategies/utils.py +9 -9
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -7
- claude_mpm/services/unified/unified_config.py +5 -5
- claude_mpm/services/unified/unified_deployment.py +2 -2
- claude_mpm/services/utility_service.py +1 -1
- claude_mpm/services/version_control/conflict_resolution.py +2 -2
- claude_mpm/services/version_control/git_operations.py +3 -3
- claude_mpm/services/version_control/semantic_versioning.py +13 -13
- claude_mpm/services/version_control/version_parser.py +1 -1
- claude_mpm/storage/state_storage.py +12 -13
- claude_mpm/tools/code_tree_analyzer.py +5 -5
- claude_mpm/tools/code_tree_builder.py +4 -4
- claude_mpm/tools/socketio_debug.py +1 -1
- claude_mpm/utils/agent_dependency_loader.py +4 -4
- claude_mpm/utils/common.py +2 -2
- claude_mpm/utils/config_manager.py +3 -3
- claude_mpm/utils/dependency_cache.py +2 -2
- claude_mpm/utils/dependency_strategies.py +6 -6
- claude_mpm/utils/file_utils.py +11 -11
- claude_mpm/utils/log_cleanup.py +1 -1
- claude_mpm/utils/path_operations.py +1 -1
- claude_mpm/validation/agent_validator.py +2 -2
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/METADATA +1 -1
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/RECORD +226 -223
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/top_level.txt +0 -0
claude_mpm/core/file_utils.py
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
333
|
+
with alt_file.open() as f:
|
334
334
|
template_data = json.load(f)
|
335
335
|
return template_data.get("memory_routing")
|
336
336
|
|
@@ -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(
|
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(
|
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(
|
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)
|
claude_mpm/core/log_manager.py
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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]:
|
@@ -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]:
|
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(
|
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(
|
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(
|
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
|
|
claude_mpm/core/socketio_pool.py
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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
|
claude_mpm/core/unified_paths.py
CHANGED
@@ -167,7 +167,7 @@ class PathContext:
|
|
167
167
|
|
168
168
|
@staticmethod
|
169
169
|
@lru_cache(maxsize=1)
|
170
|
-
def detect_deployment_context() -> DeploymentContext:
|
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):
|
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
|
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
|
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
|
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
|
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
|
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
|
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(
|
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(
|
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
|
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
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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[
|
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
|
-
|
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:
|
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:
|
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:
|
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(
|
121
|
+
with profile_path.open() as f:
|
122
122
|
profile_data = yaml.safe_load(f)
|
123
123
|
|
124
124
|
# Check for circular dependencies
|