claude-mpm 4.7.4__py3-none-any.whl ā 4.18.2__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/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.4.dist-info ā claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Optimized Claude Code hook handler with EventBus architecture.
|
|
3
|
-
|
|
4
|
-
This handler uses the EventBus for decoupled event emission instead of
|
|
5
|
-
direct Socket.IO connections. This provides better separation of concerns
|
|
6
|
-
and improved testability.
|
|
7
|
-
|
|
8
|
-
WHY EventBus approach:
|
|
9
|
-
- Decouples hook processing from Socket.IO implementation
|
|
10
|
-
- Enables multiple event consumers without code changes
|
|
11
|
-
- Simplifies testing by removing Socket.IO dependencies
|
|
12
|
-
- Provides centralized event routing and filtering
|
|
13
|
-
- Maintains backward compatibility with existing hooks
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import json
|
|
17
|
-
import os
|
|
18
|
-
import select
|
|
19
|
-
import signal
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
22
|
-
import threading
|
|
23
|
-
import time
|
|
24
|
-
from collections import deque
|
|
25
|
-
from datetime import datetime, timezone
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
from typing import Optional
|
|
28
|
-
|
|
29
|
-
# Add parent path for imports
|
|
30
|
-
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
31
|
-
|
|
32
|
-
# Import EventBus
|
|
33
|
-
try:
|
|
34
|
-
from claude_mpm.services.event_bus import EventBus
|
|
35
|
-
|
|
36
|
-
EVENTBUS_AVAILABLE = True
|
|
37
|
-
except ImportError:
|
|
38
|
-
EVENTBUS_AVAILABLE = False
|
|
39
|
-
EventBus = None
|
|
40
|
-
|
|
41
|
-
# Import EventNormalizer for consistent event formatting
|
|
42
|
-
try:
|
|
43
|
-
from claude_mpm.services.socketio.event_normalizer import EventNormalizer
|
|
44
|
-
except ImportError:
|
|
45
|
-
# Create a simple fallback EventNormalizer if import fails
|
|
46
|
-
class EventNormalizer:
|
|
47
|
-
def normalize(self, event_data, source="hook"):
|
|
48
|
-
"""Simple fallback normalizer that returns event as-is."""
|
|
49
|
-
return type(
|
|
50
|
-
"NormalizedEvent",
|
|
51
|
-
(),
|
|
52
|
-
{
|
|
53
|
-
"to_dict": lambda: {
|
|
54
|
-
"event": "claude_event",
|
|
55
|
-
"type": event_data.get("type", "unknown"),
|
|
56
|
-
"subtype": event_data.get("subtype", "generic"),
|
|
57
|
-
"timestamp": event_data.get(
|
|
58
|
-
"timestamp", datetime.now(timezone.utc).isoformat()
|
|
59
|
-
),
|
|
60
|
-
"data": event_data.get("data", event_data),
|
|
61
|
-
"source": source,
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Import constants for configuration
|
|
68
|
-
try:
|
|
69
|
-
from claude_mpm.core.constants import TimeoutConfig
|
|
70
|
-
except ImportError:
|
|
71
|
-
# Fallback values if constants module not available
|
|
72
|
-
class TimeoutConfig:
|
|
73
|
-
QUICK_TIMEOUT = 2.0
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# Import other handler modules
|
|
77
|
-
try:
|
|
78
|
-
from .event_handlers import EventHandlers
|
|
79
|
-
from .memory_integration import MemoryHookManager
|
|
80
|
-
from .response_tracking import ResponseTrackingManager
|
|
81
|
-
except ImportError:
|
|
82
|
-
# Fallback for direct execution
|
|
83
|
-
from event_handlers import EventHandlers
|
|
84
|
-
from memory_integration import MemoryHookManager
|
|
85
|
-
from response_tracking import ResponseTrackingManager
|
|
86
|
-
|
|
87
|
-
# Debug mode is enabled by default for better visibility into hook processing
|
|
88
|
-
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
|
|
89
|
-
|
|
90
|
-
# Global singleton handler instance
|
|
91
|
-
_global_handler = None
|
|
92
|
-
_handler_lock = threading.Lock()
|
|
93
|
-
|
|
94
|
-
# Track recent events to detect duplicates
|
|
95
|
-
_recent_events = deque(maxlen=10)
|
|
96
|
-
_events_lock = threading.Lock()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class HookHandler:
|
|
100
|
-
"""Main hook handler class using EventBus for event emission.
|
|
101
|
-
|
|
102
|
-
WHY EventBus integration:
|
|
103
|
-
- Replaces direct Socket.IO connections with EventBus publishing
|
|
104
|
-
- Events are published once and consumed by multiple listeners
|
|
105
|
-
- Failures in one consumer don't affect others
|
|
106
|
-
- Simplified testing without Socket.IO dependencies
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
# Tracking dictionaries with size limits
|
|
110
|
-
MAX_DELEGATION_TRACKING = 100
|
|
111
|
-
MAX_PROMPT_TRACKING = 50
|
|
112
|
-
MAX_CACHE_AGE_SECONDS = 1800 # 30 minutes
|
|
113
|
-
|
|
114
|
-
def __init__(self):
|
|
115
|
-
"""Initialize the hook handler with EventBus."""
|
|
116
|
-
# Initialize EventBus if available
|
|
117
|
-
self.event_bus = EventBus.get_instance() if EVENTBUS_AVAILABLE else None
|
|
118
|
-
self.event_normalizer = EventNormalizer()
|
|
119
|
-
|
|
120
|
-
# Initialize tracking managers
|
|
121
|
-
self.memory_manager = MemoryHookManager()
|
|
122
|
-
self.response_tracker = ResponseTrackingManager()
|
|
123
|
-
self.event_handlers = EventHandlers(self)
|
|
124
|
-
|
|
125
|
-
# Delegation tracking
|
|
126
|
-
self.active_delegations = {}
|
|
127
|
-
self.delegation_requests = {}
|
|
128
|
-
self.delegation_history = deque(maxlen=20)
|
|
129
|
-
|
|
130
|
-
# Prompt tracking
|
|
131
|
-
self.pending_prompts = {}
|
|
132
|
-
|
|
133
|
-
# Git branch caching
|
|
134
|
-
self._git_branch_cache = {}
|
|
135
|
-
self._git_branch_cache_time = {}
|
|
136
|
-
|
|
137
|
-
# Session tracking
|
|
138
|
-
self.current_session_id = None
|
|
139
|
-
|
|
140
|
-
# Cleanup old entries periodically
|
|
141
|
-
self._last_cleanup = time.time()
|
|
142
|
-
|
|
143
|
-
if self.event_bus:
|
|
144
|
-
logger_msg = "HookHandler initialized with EventBus"
|
|
145
|
-
else:
|
|
146
|
-
logger_msg = "HookHandler initialized (EventBus not available)"
|
|
147
|
-
|
|
148
|
-
if DEBUG:
|
|
149
|
-
print(f"š {logger_msg}", file=sys.stderr)
|
|
150
|
-
|
|
151
|
-
def _emit_event(self, event_type: str, data: dict):
|
|
152
|
-
"""Emit an event through the EventBus.
|
|
153
|
-
|
|
154
|
-
WHY this approach:
|
|
155
|
-
- Single point of event emission
|
|
156
|
-
- Consistent event normalization
|
|
157
|
-
- Graceful fallback if EventBus unavailable
|
|
158
|
-
- Easy to add metrics and monitoring
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
event_type: The event type (e.g., 'pre_tool', 'subagent_stop')
|
|
162
|
-
data: The event data
|
|
163
|
-
"""
|
|
164
|
-
if not self.event_bus:
|
|
165
|
-
if DEBUG:
|
|
166
|
-
print(
|
|
167
|
-
f"EventBus not available, cannot emit: hook.{event_type}",
|
|
168
|
-
file=sys.stderr,
|
|
169
|
-
)
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
try:
|
|
173
|
-
# Create event data for normalization
|
|
174
|
-
raw_event = {
|
|
175
|
-
"type": "hook",
|
|
176
|
-
"subtype": event_type,
|
|
177
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
178
|
-
"data": data,
|
|
179
|
-
"source": "claude_hooks",
|
|
180
|
-
"session_id": data.get("sessionId", self.current_session_id),
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
# Normalize the event
|
|
184
|
-
normalized_event = self.event_normalizer.normalize(raw_event, source="hook")
|
|
185
|
-
event_data = normalized_event.to_dict()
|
|
186
|
-
|
|
187
|
-
# Publish to EventBus
|
|
188
|
-
success = self.event_bus.publish(f"hook.{event_type}", event_data)
|
|
189
|
-
|
|
190
|
-
if DEBUG:
|
|
191
|
-
if success:
|
|
192
|
-
print(
|
|
193
|
-
f"ā
Published to EventBus: hook.{event_type}", file=sys.stderr
|
|
194
|
-
)
|
|
195
|
-
else:
|
|
196
|
-
print(
|
|
197
|
-
f"ā ļø EventBus rejected event: hook.{event_type}", file=sys.stderr
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
# Log important events
|
|
201
|
-
if DEBUG and event_type in ["subagent_stop", "pre_tool"]:
|
|
202
|
-
if event_type == "subagent_stop":
|
|
203
|
-
agent_type = data.get("agent_type", "unknown")
|
|
204
|
-
print(
|
|
205
|
-
f"š¤ Published SubagentStop for agent '{agent_type}'",
|
|
206
|
-
file=sys.stderr,
|
|
207
|
-
)
|
|
208
|
-
elif event_type == "pre_tool" and data.get("tool_name") == "Task":
|
|
209
|
-
delegation = data.get("delegation_details", {})
|
|
210
|
-
agent_type = delegation.get("agent_type", "unknown")
|
|
211
|
-
print(
|
|
212
|
-
f"š¤ Published Task delegation to agent '{agent_type}'",
|
|
213
|
-
file=sys.stderr,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
except Exception as e:
|
|
217
|
-
if DEBUG:
|
|
218
|
-
print(
|
|
219
|
-
f"ā Failed to publish event hook.{event_type}: {e}",
|
|
220
|
-
file=sys.stderr,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
def _get_git_branch(self, working_dir: Optional[str] = None) -> str:
|
|
224
|
-
"""Get git branch for the given directory with caching."""
|
|
225
|
-
# Use current working directory if not specified
|
|
226
|
-
if not working_dir:
|
|
227
|
-
working_dir = Path.cwd()
|
|
228
|
-
|
|
229
|
-
# Check cache first (cache for 30 seconds)
|
|
230
|
-
current_time = time.time()
|
|
231
|
-
cache_key = working_dir
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
cache_key in self._git_branch_cache
|
|
235
|
-
and cache_key in self._git_branch_cache_time
|
|
236
|
-
and current_time - self._git_branch_cache_time[cache_key] < 30
|
|
237
|
-
):
|
|
238
|
-
return self._git_branch_cache[cache_key]
|
|
239
|
-
|
|
240
|
-
# Try to get git branch
|
|
241
|
-
try:
|
|
242
|
-
# Change to the working directory temporarily
|
|
243
|
-
original_cwd = Path.cwd()
|
|
244
|
-
os.chdir(working_dir)
|
|
245
|
-
|
|
246
|
-
# Run git command to get current branch
|
|
247
|
-
result = subprocess.run(
|
|
248
|
-
["git", "branch", "--show-current"],
|
|
249
|
-
capture_output=True,
|
|
250
|
-
text=True,
|
|
251
|
-
timeout=TimeoutConfig.QUICK_TIMEOUT,
|
|
252
|
-
check=False,
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
# Restore original directory
|
|
256
|
-
os.chdir(original_cwd)
|
|
257
|
-
|
|
258
|
-
if result.returncode == 0 and result.stdout.strip():
|
|
259
|
-
branch = result.stdout.strip()
|
|
260
|
-
# Cache the result
|
|
261
|
-
self._git_branch_cache[cache_key] = branch
|
|
262
|
-
self._git_branch_cache_time[cache_key] = current_time
|
|
263
|
-
return branch
|
|
264
|
-
return "unknown"
|
|
265
|
-
|
|
266
|
-
except Exception:
|
|
267
|
-
return "unknown"
|
|
268
|
-
|
|
269
|
-
def _cleanup_old_entries(self):
|
|
270
|
-
"""Clean up old entries to prevent memory growth."""
|
|
271
|
-
time.time() - self.MAX_CACHE_AGE_SECONDS
|
|
272
|
-
|
|
273
|
-
# Clean up delegation tracking dictionaries
|
|
274
|
-
for storage in [self.active_delegations, self.delegation_requests]:
|
|
275
|
-
if len(storage) > self.MAX_DELEGATION_TRACKING:
|
|
276
|
-
# Keep only the most recent entries
|
|
277
|
-
sorted_keys = sorted(storage.keys())
|
|
278
|
-
excess = len(storage) - self.MAX_DELEGATION_TRACKING
|
|
279
|
-
for key in sorted_keys[:excess]:
|
|
280
|
-
del storage[key]
|
|
281
|
-
|
|
282
|
-
# Clean up pending prompts
|
|
283
|
-
if len(self.pending_prompts) > self.MAX_PROMPT_TRACKING:
|
|
284
|
-
sorted_keys = sorted(self.pending_prompts.keys())
|
|
285
|
-
excess = len(self.pending_prompts) - self.MAX_PROMPT_TRACKING
|
|
286
|
-
for key in sorted_keys[:excess]:
|
|
287
|
-
del self.pending_prompts[key]
|
|
288
|
-
|
|
289
|
-
# Clean up git branch cache
|
|
290
|
-
expired_keys = [
|
|
291
|
-
key
|
|
292
|
-
for key, cache_time in self._git_branch_cache_time.items()
|
|
293
|
-
if time.time() - cache_time > self.MAX_CACHE_AGE_SECONDS
|
|
294
|
-
]
|
|
295
|
-
for key in expired_keys:
|
|
296
|
-
self._git_branch_cache.pop(key, None)
|
|
297
|
-
self._git_branch_cache_time.pop(key, None)
|
|
298
|
-
|
|
299
|
-
def handle_event(self, event: dict):
|
|
300
|
-
"""Process an event from Claude Code.
|
|
301
|
-
|
|
302
|
-
Args:
|
|
303
|
-
event: The event dictionary from Claude
|
|
304
|
-
"""
|
|
305
|
-
# Periodic cleanup
|
|
306
|
-
current_time = time.time()
|
|
307
|
-
if current_time - self._last_cleanup > 300: # Every 5 minutes
|
|
308
|
-
self._cleanup_old_entries()
|
|
309
|
-
self._last_cleanup = current_time
|
|
310
|
-
|
|
311
|
-
# Extract event details
|
|
312
|
-
event_type = event.get("type", "")
|
|
313
|
-
event_name = event.get("name", "")
|
|
314
|
-
|
|
315
|
-
# Update session ID if present
|
|
316
|
-
if "sessionId" in event:
|
|
317
|
-
self.current_session_id = event["sessionId"]
|
|
318
|
-
|
|
319
|
-
# Detect duplicate events
|
|
320
|
-
event_signature = (
|
|
321
|
-
f"{event_type}:{event_name}:{json.dumps(event.get('data', ''))[:100]}"
|
|
322
|
-
)
|
|
323
|
-
with _events_lock:
|
|
324
|
-
if event_signature in _recent_events:
|
|
325
|
-
if DEBUG:
|
|
326
|
-
print(f"Skipping duplicate event: {event_type}", file=sys.stderr)
|
|
327
|
-
return
|
|
328
|
-
_recent_events.append(event_signature)
|
|
329
|
-
|
|
330
|
-
# Route to appropriate handler
|
|
331
|
-
if event_type == "Start":
|
|
332
|
-
self.event_handlers.handle_start(event)
|
|
333
|
-
elif event_type == "Stop":
|
|
334
|
-
self.event_handlers.handle_stop(event)
|
|
335
|
-
elif event_type == "UserPrompt":
|
|
336
|
-
self.event_handlers.handle_user_prompt(event)
|
|
337
|
-
elif event_type == "AssistantResponse":
|
|
338
|
-
self.event_handlers.handle_assistant_response(event)
|
|
339
|
-
elif event_type == "SubagentStart":
|
|
340
|
-
self.event_handlers.handle_subagent_start(event)
|
|
341
|
-
elif event_type == "SubagentStop":
|
|
342
|
-
self.event_handlers.handle_subagent_stop(event)
|
|
343
|
-
elif event_type == "PreToolExecution" and event_name == "Task":
|
|
344
|
-
self.event_handlers.handle_task_delegation(event)
|
|
345
|
-
elif event_type == "PreToolExecution":
|
|
346
|
-
self.event_handlers.handle_pre_tool(event)
|
|
347
|
-
elif event_type == "PostToolExecution":
|
|
348
|
-
self.event_handlers.handle_post_tool(event)
|
|
349
|
-
elif event_type == "PromptCachingBetaStats":
|
|
350
|
-
# Ignore caching stats events
|
|
351
|
-
pass
|
|
352
|
-
# Log unhandled events in debug mode
|
|
353
|
-
elif DEBUG:
|
|
354
|
-
print(f"Unhandled event type: {event_type}", file=sys.stderr)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
def get_handler() -> HookHandler:
|
|
358
|
-
"""Get or create the global hook handler instance.
|
|
359
|
-
|
|
360
|
-
Returns:
|
|
361
|
-
HookHandler: The singleton handler instance
|
|
362
|
-
"""
|
|
363
|
-
global _global_handler
|
|
364
|
-
if _global_handler is None:
|
|
365
|
-
with _handler_lock:
|
|
366
|
-
if _global_handler is None:
|
|
367
|
-
_global_handler = HookHandler()
|
|
368
|
-
return _global_handler
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
def main():
|
|
372
|
-
"""Main entry point for the hook handler."""
|
|
373
|
-
if DEBUG:
|
|
374
|
-
print("šÆ EventBus Hook Handler starting...", file=sys.stderr)
|
|
375
|
-
|
|
376
|
-
handler = get_handler()
|
|
377
|
-
|
|
378
|
-
# Set up signal handling for clean shutdown
|
|
379
|
-
def signal_handler(signum, frame):
|
|
380
|
-
if DEBUG:
|
|
381
|
-
print("\nš Hook handler shutting down...", file=sys.stderr)
|
|
382
|
-
sys.exit(0)
|
|
383
|
-
|
|
384
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
385
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
|
386
|
-
|
|
387
|
-
# Process events from stdin
|
|
388
|
-
try:
|
|
389
|
-
while True:
|
|
390
|
-
# Check if data is available with timeout
|
|
391
|
-
readable, _, _ = select.select([sys.stdin], [], [], 0.1)
|
|
392
|
-
if readable:
|
|
393
|
-
line = sys.stdin.readline()
|
|
394
|
-
if not line:
|
|
395
|
-
break
|
|
396
|
-
|
|
397
|
-
try:
|
|
398
|
-
event = json.loads(line.strip())
|
|
399
|
-
handler.handle_event(event)
|
|
400
|
-
|
|
401
|
-
# Acknowledge event
|
|
402
|
-
print(json.dumps({"status": "ok"}))
|
|
403
|
-
sys.stdout.flush()
|
|
404
|
-
|
|
405
|
-
except json.JSONDecodeError as e:
|
|
406
|
-
if DEBUG:
|
|
407
|
-
print(f"Invalid JSON: {e}", file=sys.stderr)
|
|
408
|
-
print(json.dumps({"status": "error", "message": str(e)}))
|
|
409
|
-
sys.stdout.flush()
|
|
410
|
-
except Exception as e:
|
|
411
|
-
if DEBUG:
|
|
412
|
-
print(f"Error processing event: {e}", file=sys.stderr)
|
|
413
|
-
print(json.dumps({"status": "error", "message": str(e)}))
|
|
414
|
-
sys.stdout.flush()
|
|
415
|
-
|
|
416
|
-
except KeyboardInterrupt:
|
|
417
|
-
if DEBUG:
|
|
418
|
-
print("\nš Hook handler interrupted", file=sys.stderr)
|
|
419
|
-
finally:
|
|
420
|
-
if DEBUG:
|
|
421
|
-
print("Hook handler exiting", file=sys.stderr)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if __name__ == "__main__":
|
|
425
|
-
main()
|