claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__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 +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
    
        claude_mpm/core/cache.py
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            #!/usr/bin/env python3
         | 
| 2 4 | 
             
            """File system caching with LRU eviction for performance optimization.
         | 
| 3 5 |  | 
| @@ -14,24 +16,23 @@ WHY file system caching: | |
| 14 16 | 
             
            import asyncio
         | 
| 15 17 | 
             
            import hashlib
         | 
| 16 18 | 
             
            import json
         | 
| 17 | 
            -
            import os
         | 
| 18 19 | 
             
            import pickle
         | 
| 19 20 | 
             
            import threading
         | 
| 20 21 | 
             
            import time
         | 
| 21 22 | 
             
            from collections import OrderedDict
         | 
| 22 23 | 
             
            from dataclasses import dataclass, field
         | 
| 23 24 | 
             
            from datetime import datetime, timedelta
         | 
| 24 | 
            -
            from pathlib import Path
         | 
| 25 25 | 
             
            from typing import Any, Callable, Dict, Optional, Tuple, TypeVar, Union
         | 
| 26 26 |  | 
| 27 27 | 
             
            from ..core.logger import get_logger
         | 
| 28 28 |  | 
| 29 | 
            -
            T = TypeVar( | 
| 29 | 
            +
            T = TypeVar("T")
         | 
| 30 30 |  | 
| 31 31 |  | 
| 32 32 | 
             
            @dataclass
         | 
| 33 33 | 
             
            class CacheEntry:
         | 
| 34 34 | 
             
                """Single cache entry with metadata."""
         | 
| 35 | 
            +
             | 
| 35 36 | 
             
                key: str
         | 
| 36 37 | 
             
                value: Any
         | 
| 37 38 | 
             
                size: int
         | 
| @@ -39,14 +40,14 @@ class CacheEntry: | |
| 39 40 | 
             
                last_accessed: datetime = field(default_factory=datetime.now)
         | 
| 40 41 | 
             
                access_count: int = 0
         | 
| 41 42 | 
             
                ttl: Optional[float] = None
         | 
| 42 | 
            -
             | 
| 43 | 
            +
             | 
| 43 44 | 
             
                def is_expired(self) -> bool:
         | 
| 44 45 | 
             
                    """Check if entry has expired based on TTL."""
         | 
| 45 46 | 
             
                    if self.ttl is None:
         | 
| 46 47 | 
             
                        return False
         | 
| 47 48 | 
             
                    age = (datetime.now() - self.created_at).total_seconds()
         | 
| 48 49 | 
             
                    return age > self.ttl
         | 
| 49 | 
            -
             | 
| 50 | 
            +
             | 
| 50 51 | 
             
                def touch(self):
         | 
| 51 52 | 
             
                    """Update last access time and increment counter."""
         | 
| 52 53 | 
             
                    self.last_accessed = datetime.now()
         | 
| @@ -56,12 +57,13 @@ class CacheEntry: | |
| 56 57 | 
             
            @dataclass
         | 
| 57 58 | 
             
            class CacheStats:
         | 
| 58 59 | 
             
                """Cache performance statistics."""
         | 
| 60 | 
            +
             | 
| 59 61 | 
             
                hits: int = 0
         | 
| 60 62 | 
             
                misses: int = 0
         | 
| 61 63 | 
             
                evictions: int = 0
         | 
| 62 64 | 
             
                total_size: int = 0
         | 
| 63 65 | 
             
                entry_count: int = 0
         | 
| 64 | 
            -
             | 
| 66 | 
            +
             | 
| 65 67 | 
             
                @property
         | 
| 66 68 | 
             
                def hit_rate(self) -> float:
         | 
| 67 69 | 
             
                    """Calculate cache hit rate."""
         | 
| @@ -71,19 +73,19 @@ class CacheStats: | |
| 71 73 |  | 
| 72 74 | 
             
            class FileSystemCache:
         | 
| 73 75 | 
             
                """LRU cache for file system operations.
         | 
| 74 | 
            -
             | 
| 76 | 
            +
             | 
| 75 77 | 
             
                WHY this design:
         | 
| 76 78 | 
             
                - OrderedDict provides O(1) LRU operations
         | 
| 77 79 | 
             
                - Thread-safe with fine-grained locking
         | 
| 78 80 | 
             
                - Memory-aware with size limits
         | 
| 79 81 | 
             
                - TTL support for dynamic content
         | 
| 80 | 
            -
             | 
| 82 | 
            +
             | 
| 81 83 | 
             
                Example:
         | 
| 82 84 | 
             
                    cache = FileSystemCache(max_size_mb=100, default_ttl=300)
         | 
| 83 | 
            -
             | 
| 85 | 
            +
             | 
| 84 86 | 
             
                    # Cache file content
         | 
| 85 87 | 
             
                    content = cache.get_file('/path/to/file.json')
         | 
| 86 | 
            -
             | 
| 88 | 
            +
             | 
| 87 89 | 
             
                    # Cache expensive computation
         | 
| 88 90 | 
             
                    result = cache.get_or_compute(
         | 
| 89 91 | 
             
                        'expensive_key',
         | 
| @@ -91,16 +93,16 @@ class FileSystemCache: | |
| 91 93 | 
             
                        ttl=60
         | 
| 92 94 | 
             
                    )
         | 
| 93 95 | 
             
                """
         | 
| 94 | 
            -
             | 
| 96 | 
            +
             | 
| 95 97 | 
             
                def __init__(
         | 
| 96 98 | 
             
                    self,
         | 
| 97 99 | 
             
                    max_size_mb: float = 100,
         | 
| 98 100 | 
             
                    max_entries: int = 10000,
         | 
| 99 101 | 
             
                    default_ttl: Optional[float] = None,
         | 
| 100 | 
            -
                    persist_path: Optional[Path] = None
         | 
| 102 | 
            +
                    persist_path: Optional[Path] = None,
         | 
| 101 103 | 
             
                ):
         | 
| 102 104 | 
             
                    """Initialize file system cache.
         | 
| 103 | 
            -
             | 
| 105 | 
            +
             | 
| 104 106 | 
             
                    Args:
         | 
| 105 107 | 
             
                        max_size_mb: Maximum cache size in megabytes
         | 
| 106 108 | 
             
                        max_entries: Maximum number of cache entries
         | 
| @@ -111,16 +113,16 @@ class FileSystemCache: | |
| 111 113 | 
             
                    self.max_entries = max_entries
         | 
| 112 114 | 
             
                    self.default_ttl = default_ttl
         | 
| 113 115 | 
             
                    self.persist_path = persist_path
         | 
| 114 | 
            -
             | 
| 116 | 
            +
             | 
| 115 117 | 
             
                    self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
         | 
| 116 118 | 
             
                    self._lock = threading.RLock()
         | 
| 117 119 | 
             
                    self._stats = CacheStats()
         | 
| 118 120 | 
             
                    self._logger = get_logger("fs_cache")
         | 
| 119 | 
            -
             | 
| 121 | 
            +
             | 
| 120 122 | 
             
                    # Load persisted cache if available
         | 
| 121 123 | 
             
                    if persist_path and persist_path.exists():
         | 
| 122 124 | 
             
                        self._load_cache()
         | 
| 123 | 
            -
             | 
| 125 | 
            +
             | 
| 124 126 | 
             
                def _estimate_size(self, value: Any) -> int:
         | 
| 125 127 | 
             
                    """Estimate memory size of a value in bytes."""
         | 
| 126 128 | 
             
                    if isinstance(value, (str, bytes)):
         | 
| @@ -137,13 +139,13 @@ class FileSystemCache: | |
| 137 139 | 
             
                            return len(pickle.dumps(value))
         | 
| 138 140 | 
             
                        except:
         | 
| 139 141 | 
             
                            return 100  # Default small size
         | 
| 140 | 
            -
             | 
| 142 | 
            +
             | 
| 141 143 | 
             
                def _evict_lru(self):
         | 
| 142 144 | 
             
                    """Evict least recently used entries to make space."""
         | 
| 143 145 | 
             
                    with self._lock:
         | 
| 144 146 | 
             
                        while self._cache and (
         | 
| 145 | 
            -
                            self._stats.total_size > self.max_size | 
| 146 | 
            -
                            self._stats.entry_count > self.max_entries
         | 
| 147 | 
            +
                            self._stats.total_size > self.max_size
         | 
| 148 | 
            +
                            or self._stats.entry_count > self.max_entries
         | 
| 147 149 | 
             
                        ):
         | 
| 148 150 | 
             
                            # Remove oldest entry (first in OrderedDict)
         | 
| 149 151 | 
             
                            key, entry = self._cache.popitem(last=False)
         | 
| @@ -151,37 +153,36 @@ class FileSystemCache: | |
| 151 153 | 
             
                            self._stats.entry_count -= 1
         | 
| 152 154 | 
             
                            self._stats.evictions += 1
         | 
| 153 155 | 
             
                            self._logger.debug(f"Evicted cache entry: {key}")
         | 
| 154 | 
            -
             | 
| 156 | 
            +
             | 
| 155 157 | 
             
                def _evict_expired(self):
         | 
| 156 158 | 
             
                    """Remove expired entries from cache."""
         | 
| 157 159 | 
             
                    with self._lock:
         | 
| 158 160 | 
             
                        expired_keys = [
         | 
| 159 | 
            -
                            key for key, entry in self._cache.items()
         | 
| 160 | 
            -
                            if entry.is_expired()
         | 
| 161 | 
            +
                            key for key, entry in self._cache.items() if entry.is_expired()
         | 
| 161 162 | 
             
                        ]
         | 
| 162 | 
            -
             | 
| 163 | 
            +
             | 
| 163 164 | 
             
                        for key in expired_keys:
         | 
| 164 165 | 
             
                            entry = self._cache.pop(key)
         | 
| 165 166 | 
             
                            self._stats.total_size -= entry.size
         | 
| 166 167 | 
             
                            self._stats.entry_count -= 1
         | 
| 167 168 | 
             
                            self._logger.debug(f"Expired cache entry: {key}")
         | 
| 168 | 
            -
             | 
| 169 | 
            +
             | 
| 169 170 | 
             
                def get(self, key: str) -> Optional[Any]:
         | 
| 170 171 | 
             
                    """Get value from cache.
         | 
| 171 | 
            -
             | 
| 172 | 
            +
             | 
| 172 173 | 
             
                    Args:
         | 
| 173 174 | 
             
                        key: Cache key
         | 
| 174 | 
            -
             | 
| 175 | 
            +
             | 
| 175 176 | 
             
                    Returns:
         | 
| 176 177 | 
             
                        Cached value or None if not found/expired
         | 
| 177 178 | 
             
                    """
         | 
| 178 179 | 
             
                    with self._lock:
         | 
| 179 180 | 
             
                        entry = self._cache.get(key)
         | 
| 180 | 
            -
             | 
| 181 | 
            +
             | 
| 181 182 | 
             
                        if entry is None:
         | 
| 182 183 | 
             
                            self._stats.misses += 1
         | 
| 183 184 | 
             
                            return None
         | 
| 184 | 
            -
             | 
| 185 | 
            +
             | 
| 185 186 | 
             
                        # Check expiration
         | 
| 186 187 | 
             
                        if entry.is_expired():
         | 
| 187 188 | 
             
                            self._cache.pop(key)
         | 
| @@ -189,70 +190,59 @@ class FileSystemCache: | |
| 189 190 | 
             
                            self._stats.entry_count -= 1
         | 
| 190 191 | 
             
                            self._stats.misses += 1
         | 
| 191 192 | 
             
                            return None
         | 
| 192 | 
            -
             | 
| 193 | 
            +
             | 
| 193 194 | 
             
                        # Update LRU order
         | 
| 194 195 | 
             
                        self._cache.move_to_end(key)
         | 
| 195 196 | 
             
                        entry.touch()
         | 
| 196 | 
            -
             | 
| 197 | 
            +
             | 
| 197 198 | 
             
                        self._stats.hits += 1
         | 
| 198 199 | 
             
                        return entry.value
         | 
| 199 | 
            -
             | 
| 200 | 
            -
                def put(
         | 
| 201 | 
            -
                    self,
         | 
| 202 | 
            -
                    key: str,
         | 
| 203 | 
            -
                    value: Any,
         | 
| 204 | 
            -
                    ttl: Optional[float] = None
         | 
| 205 | 
            -
                ) -> None:
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                def put(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
         | 
| 206 202 | 
             
                    """Store value in cache.
         | 
| 207 | 
            -
             | 
| 203 | 
            +
             | 
| 208 204 | 
             
                    Args:
         | 
| 209 205 | 
             
                        key: Cache key
         | 
| 210 206 | 
             
                        value: Value to cache
         | 
| 211 207 | 
             
                        ttl: Time-to-live in seconds (overrides default)
         | 
| 212 208 | 
             
                    """
         | 
| 213 209 | 
             
                    size = self._estimate_size(value)
         | 
| 214 | 
            -
             | 
| 210 | 
            +
             | 
| 215 211 | 
             
                    # Don't cache if single item exceeds max size
         | 
| 216 212 | 
             
                    if size > self.max_size:
         | 
| 217 213 | 
             
                        self._logger.warning(f"Value too large to cache: {key} ({size} bytes)")
         | 
| 218 214 | 
             
                        return
         | 
| 219 | 
            -
             | 
| 215 | 
            +
             | 
| 220 216 | 
             
                    with self._lock:
         | 
| 221 217 | 
             
                        # Remove existing entry if present
         | 
| 222 218 | 
             
                        if key in self._cache:
         | 
| 223 219 | 
             
                            old_entry = self._cache.pop(key)
         | 
| 224 220 | 
             
                            self._stats.total_size -= old_entry.size
         | 
| 225 221 | 
             
                            self._stats.entry_count -= 1
         | 
| 226 | 
            -
             | 
| 222 | 
            +
             | 
| 227 223 | 
             
                        # Create new entry
         | 
| 228 224 | 
             
                        entry = CacheEntry(
         | 
| 229 | 
            -
                            key=key,
         | 
| 230 | 
            -
                            value=value,
         | 
| 231 | 
            -
                            size=size,
         | 
| 232 | 
            -
                            ttl=ttl or self.default_ttl
         | 
| 225 | 
            +
                            key=key, value=value, size=size, ttl=ttl or self.default_ttl
         | 
| 233 226 | 
             
                        )
         | 
| 234 | 
            -
             | 
| 227 | 
            +
             | 
| 235 228 | 
             
                        # Add to cache
         | 
| 236 229 | 
             
                        self._cache[key] = entry
         | 
| 237 230 | 
             
                        self._stats.total_size += size
         | 
| 238 231 | 
             
                        self._stats.entry_count += 1
         | 
| 239 | 
            -
             | 
| 232 | 
            +
             | 
| 240 233 | 
             
                        # Evict if necessary
         | 
| 241 234 | 
             
                        self._evict_lru()
         | 
| 242 | 
            -
             | 
| 235 | 
            +
             | 
| 243 236 | 
             
                def get_or_compute(
         | 
| 244 | 
            -
                    self,
         | 
| 245 | 
            -
                    key: str,
         | 
| 246 | 
            -
                    compute_fn: Callable[[], T],
         | 
| 247 | 
            -
                    ttl: Optional[float] = None
         | 
| 237 | 
            +
                    self, key: str, compute_fn: Callable[[], T], ttl: Optional[float] = None
         | 
| 248 238 | 
             
                ) -> T:
         | 
| 249 239 | 
             
                    """Get from cache or compute if missing.
         | 
| 250 | 
            -
             | 
| 240 | 
            +
             | 
| 251 241 | 
             
                    Args:
         | 
| 252 242 | 
             
                        key: Cache key
         | 
| 253 243 | 
             
                        compute_fn: Function to compute value if not cached
         | 
| 254 244 | 
             
                        ttl: Time-to-live in seconds
         | 
| 255 | 
            -
             | 
| 245 | 
            +
             | 
| 256 246 | 
             
                    Returns:
         | 
| 257 247 | 
             
                        Cached or computed value
         | 
| 258 248 | 
             
                    """
         | 
| @@ -260,46 +250,46 @@ class FileSystemCache: | |
| 260 250 | 
             
                    value = self.get(key)
         | 
| 261 251 | 
             
                    if value is not None:
         | 
| 262 252 | 
             
                        return value
         | 
| 263 | 
            -
             | 
| 253 | 
            +
             | 
| 264 254 | 
             
                    # Compute value
         | 
| 265 255 | 
             
                    value = compute_fn()
         | 
| 266 | 
            -
             | 
| 256 | 
            +
             | 
| 267 257 | 
             
                    # Cache result
         | 
| 268 258 | 
             
                    self.put(key, value, ttl)
         | 
| 269 | 
            -
             | 
| 259 | 
            +
             | 
| 270 260 | 
             
                    return value
         | 
| 271 | 
            -
             | 
| 261 | 
            +
             | 
| 272 262 | 
             
                def get_file(
         | 
| 273 263 | 
             
                    self,
         | 
| 274 264 | 
             
                    file_path: Union[str, Path],
         | 
| 275 | 
            -
                    mode: str =  | 
| 276 | 
            -
                    encoding: str =  | 
| 277 | 
            -
                    ttl: Optional[float] = None
         | 
| 265 | 
            +
                    mode: str = "r",
         | 
| 266 | 
            +
                    encoding: str = "utf-8",
         | 
| 267 | 
            +
                    ttl: Optional[float] = None,
         | 
| 278 268 | 
             
                ) -> Optional[Any]:
         | 
| 279 269 | 
             
                    """Get file content from cache or read from disk.
         | 
| 280 | 
            -
             | 
| 270 | 
            +
             | 
| 281 271 | 
             
                    Args:
         | 
| 282 272 | 
             
                        file_path: Path to file
         | 
| 283 273 | 
             
                        mode: File open mode ('r' for text, 'rb' for binary)
         | 
| 284 274 | 
             
                        encoding: Text encoding (for text mode)
         | 
| 285 275 | 
             
                        ttl: Time-to-live in seconds
         | 
| 286 | 
            -
             | 
| 276 | 
            +
             | 
| 287 277 | 
             
                    Returns:
         | 
| 288 278 | 
             
                        File content or None if file doesn't exist
         | 
| 289 279 | 
             
                    """
         | 
| 290 280 | 
             
                    file_path = Path(file_path)
         | 
| 291 | 
            -
             | 
| 281 | 
            +
             | 
| 292 282 | 
             
                    # Generate cache key based on file path and modification time
         | 
| 293 283 | 
             
                    if file_path.exists():
         | 
| 294 284 | 
             
                        mtime = file_path.stat().st_mtime
         | 
| 295 285 | 
             
                        cache_key = f"file:{file_path}:{mtime}:{mode}"
         | 
| 296 286 | 
             
                    else:
         | 
| 297 287 | 
             
                        return None
         | 
| 298 | 
            -
             | 
| 288 | 
            +
             | 
| 299 289 | 
             
                    def read_file():
         | 
| 300 290 | 
             
                        """Read file from disk."""
         | 
| 301 291 | 
             
                        try:
         | 
| 302 | 
            -
                            if  | 
| 292 | 
            +
                            if "b" in mode:
         | 
| 303 293 | 
             
                                with open(file_path, mode) as f:
         | 
| 304 294 | 
             
                                    return f.read()
         | 
| 305 295 | 
             
                            else:
         | 
| @@ -308,39 +298,37 @@ class FileSystemCache: | |
| 308 298 | 
             
                        except Exception as e:
         | 
| 309 299 | 
             
                            self._logger.error(f"Failed to read file {file_path}: {e}")
         | 
| 310 300 | 
             
                            return None
         | 
| 311 | 
            -
             | 
| 301 | 
            +
             | 
| 312 302 | 
             
                    return self.get_or_compute(cache_key, read_file, ttl)
         | 
| 313 | 
            -
             | 
| 303 | 
            +
             | 
| 314 304 | 
             
                def get_json(
         | 
| 315 | 
            -
                    self,
         | 
| 316 | 
            -
                    file_path: Union[str, Path],
         | 
| 317 | 
            -
                    ttl: Optional[float] = None
         | 
| 305 | 
            +
                    self, file_path: Union[str, Path], ttl: Optional[float] = None
         | 
| 318 306 | 
             
                ) -> Optional[Dict[str, Any]]:
         | 
| 319 307 | 
             
                    """Get JSON file content from cache or parse from disk.
         | 
| 320 | 
            -
             | 
| 308 | 
            +
             | 
| 321 309 | 
             
                    Args:
         | 
| 322 310 | 
             
                        file_path: Path to JSON file
         | 
| 323 311 | 
             
                        ttl: Time-to-live in seconds
         | 
| 324 | 
            -
             | 
| 312 | 
            +
             | 
| 325 313 | 
             
                    Returns:
         | 
| 326 314 | 
             
                        Parsed JSON or None if file doesn't exist/invalid
         | 
| 327 315 | 
             
                    """
         | 
| 328 | 
            -
                    content = self.get_file(file_path, mode= | 
| 316 | 
            +
                    content = self.get_file(file_path, mode="r", ttl=ttl)
         | 
| 329 317 | 
             
                    if content is None:
         | 
| 330 318 | 
             
                        return None
         | 
| 331 | 
            -
             | 
| 319 | 
            +
             | 
| 332 320 | 
             
                    try:
         | 
| 333 321 | 
             
                        return json.loads(content)
         | 
| 334 322 | 
             
                    except json.JSONDecodeError as e:
         | 
| 335 323 | 
             
                        self._logger.error(f"Invalid JSON in {file_path}: {e}")
         | 
| 336 324 | 
             
                        return None
         | 
| 337 | 
            -
             | 
| 325 | 
            +
             | 
| 338 326 | 
             
                def invalidate(self, key: str) -> bool:
         | 
| 339 327 | 
             
                    """Remove entry from cache.
         | 
| 340 | 
            -
             | 
| 328 | 
            +
             | 
| 341 329 | 
             
                    Args:
         | 
| 342 330 | 
             
                        key: Cache key to invalidate
         | 
| 343 | 
            -
             | 
| 331 | 
            +
             | 
| 344 332 | 
             
                    Returns:
         | 
| 345 333 | 
             
                        True if entry was removed, False if not found
         | 
| 346 334 | 
             
                    """
         | 
| @@ -351,44 +339,43 @@ class FileSystemCache: | |
| 351 339 | 
             
                            self._stats.entry_count -= 1
         | 
| 352 340 | 
             
                            return True
         | 
| 353 341 | 
             
                        return False
         | 
| 354 | 
            -
             | 
| 342 | 
            +
             | 
| 355 343 | 
             
                def invalidate_pattern(self, pattern: str) -> int:
         | 
| 356 344 | 
             
                    """Invalidate all keys matching pattern.
         | 
| 357 | 
            -
             | 
| 345 | 
            +
             | 
| 358 346 | 
             
                    Args:
         | 
| 359 347 | 
             
                        pattern: Pattern to match (supports * wildcard)
         | 
| 360 | 
            -
             | 
| 348 | 
            +
             | 
| 361 349 | 
             
                    Returns:
         | 
| 362 350 | 
             
                        Number of entries invalidated
         | 
| 363 351 | 
             
                    """
         | 
| 364 352 | 
             
                    import fnmatch
         | 
| 365 | 
            -
             | 
| 353 | 
            +
             | 
| 366 354 | 
             
                    with self._lock:
         | 
| 367 355 | 
             
                        matching_keys = [
         | 
| 368 | 
            -
                            key for key in self._cache.keys()
         | 
| 369 | 
            -
                            if fnmatch.fnmatch(key, pattern)
         | 
| 356 | 
            +
                            key for key in self._cache.keys() if fnmatch.fnmatch(key, pattern)
         | 
| 370 357 | 
             
                        ]
         | 
| 371 | 
            -
             | 
| 358 | 
            +
             | 
| 372 359 | 
             
                        count = 0
         | 
| 373 360 | 
             
                        for key in matching_keys:
         | 
| 374 361 | 
             
                            if self.invalidate(key):
         | 
| 375 362 | 
             
                                count += 1
         | 
| 376 | 
            -
             | 
| 363 | 
            +
             | 
| 377 364 | 
             
                        return count
         | 
| 378 | 
            -
             | 
| 365 | 
            +
             | 
| 379 366 | 
             
                def clear(self):
         | 
| 380 367 | 
             
                    """Clear all cache entries."""
         | 
| 381 368 | 
             
                    with self._lock:
         | 
| 382 369 | 
             
                        self._cache.clear()
         | 
| 383 370 | 
             
                        self._stats = CacheStats()
         | 
| 384 371 | 
             
                        self._logger.info("Cache cleared")
         | 
| 385 | 
            -
             | 
| 372 | 
            +
             | 
| 386 373 | 
             
                def get_stats(self) -> Dict[str, Any]:
         | 
| 387 374 | 
             
                    """Get cache statistics."""
         | 
| 388 375 | 
             
                    with self._lock:
         | 
| 389 376 | 
             
                        # Clean up expired entries first
         | 
| 390 377 | 
             
                        self._evict_expired()
         | 
| 391 | 
            -
             | 
| 378 | 
            +
             | 
| 392 379 | 
             
                        return {
         | 
| 393 380 | 
             
                            "hits": self._stats.hits,
         | 
| 394 381 | 
             
                            "misses": self._stats.misses,
         | 
| @@ -396,38 +383,38 @@ class FileSystemCache: | |
| 396 383 | 
             
                            "evictions": self._stats.evictions,
         | 
| 397 384 | 
             
                            "entry_count": self._stats.entry_count,
         | 
| 398 385 | 
             
                            "total_size_mb": self._stats.total_size / (1024 * 1024),
         | 
| 399 | 
            -
                            "max_size_mb": self.max_size / (1024 * 1024)
         | 
| 386 | 
            +
                            "max_size_mb": self.max_size / (1024 * 1024),
         | 
| 400 387 | 
             
                        }
         | 
| 401 | 
            -
             | 
| 388 | 
            +
             | 
| 402 389 | 
             
                def _save_cache(self):
         | 
| 403 390 | 
             
                    """Persist cache to disk."""
         | 
| 404 391 | 
             
                    if not self.persist_path:
         | 
| 405 392 | 
             
                        return
         | 
| 406 | 
            -
             | 
| 393 | 
            +
             | 
| 407 394 | 
             
                    try:
         | 
| 408 395 | 
             
                        self.persist_path.parent.mkdir(parents=True, exist_ok=True)
         | 
| 409 | 
            -
                        with open(self.persist_path,  | 
| 396 | 
            +
                        with open(self.persist_path, "wb") as f:
         | 
| 410 397 | 
             
                            pickle.dump(self._cache, f)
         | 
| 411 398 | 
             
                        self._logger.debug(f"Cache persisted to {self.persist_path}")
         | 
| 412 399 | 
             
                    except Exception as e:
         | 
| 413 400 | 
             
                        self._logger.error(f"Failed to persist cache: {e}")
         | 
| 414 | 
            -
             | 
| 401 | 
            +
             | 
| 415 402 | 
             
                def _load_cache(self):
         | 
| 416 403 | 
             
                    """Load cache from disk."""
         | 
| 417 404 | 
             
                    if not self.persist_path or not self.persist_path.exists():
         | 
| 418 405 | 
             
                        return
         | 
| 419 | 
            -
             | 
| 406 | 
            +
             | 
| 420 407 | 
             
                    try:
         | 
| 421 | 
            -
                        with open(self.persist_path,  | 
| 408 | 
            +
                        with open(self.persist_path, "rb") as f:
         | 
| 422 409 | 
             
                            loaded_cache = pickle.load(f)
         | 
| 423 | 
            -
             | 
| 410 | 
            +
             | 
| 424 411 | 
             
                        # Rebuild cache with validation
         | 
| 425 412 | 
             
                        for key, entry in loaded_cache.items():
         | 
| 426 413 | 
             
                            if not entry.is_expired():
         | 
| 427 414 | 
             
                                self._cache[key] = entry
         | 
| 428 415 | 
             
                                self._stats.total_size += entry.size
         | 
| 429 416 | 
             
                                self._stats.entry_count += 1
         | 
| 430 | 
            -
             | 
| 417 | 
            +
             | 
| 431 418 | 
             
                        self._logger.info(f"Loaded {len(self._cache)} entries from cache")
         | 
| 432 419 | 
             
                    except Exception as e:
         | 
| 433 420 | 
             
                        self._logger.error(f"Failed to load cache: {e}")
         | 
| @@ -435,82 +422,67 @@ class FileSystemCache: | |
| 435 422 |  | 
| 436 423 | 
             
            class AsyncFileSystemCache:
         | 
| 437 424 | 
             
                """Async version of FileSystemCache for async applications.
         | 
| 438 | 
            -
             | 
| 425 | 
            +
             | 
| 439 426 | 
             
                Provides non-blocking cache operations for async contexts.
         | 
| 440 427 | 
             
                """
         | 
| 441 | 
            -
             | 
| 428 | 
            +
             | 
| 442 429 | 
             
                def __init__(
         | 
| 443 430 | 
             
                    self,
         | 
| 444 431 | 
             
                    max_size_mb: float = 100,
         | 
| 445 432 | 
             
                    max_entries: int = 10000,
         | 
| 446 | 
            -
                    default_ttl: Optional[float] = None
         | 
| 433 | 
            +
                    default_ttl: Optional[float] = None,
         | 
| 447 434 | 
             
                ):
         | 
| 448 435 | 
             
                    self.sync_cache = FileSystemCache(
         | 
| 449 | 
            -
                        max_size_mb=max_size_mb,
         | 
| 450 | 
            -
                        max_entries=max_entries,
         | 
| 451 | 
            -
                        default_ttl=default_ttl
         | 
| 436 | 
            +
                        max_size_mb=max_size_mb, max_entries=max_entries, default_ttl=default_ttl
         | 
| 452 437 | 
             
                    )
         | 
| 453 438 | 
             
                    self._lock = asyncio.Lock()
         | 
| 454 439 | 
             
                    self._logger = get_logger("async_fs_cache")
         | 
| 455 | 
            -
             | 
| 440 | 
            +
             | 
| 456 441 | 
             
                async def get(self, key: str) -> Optional[Any]:
         | 
| 457 442 | 
             
                    """Get value from cache asynchronously."""
         | 
| 458 443 | 
             
                    async with self._lock:
         | 
| 459 444 | 
             
                        # Run in executor to avoid blocking
         | 
| 460 445 | 
             
                        loop = asyncio.get_event_loop()
         | 
| 461 446 | 
             
                        return await loop.run_in_executor(None, self.sync_cache.get, key)
         | 
| 462 | 
            -
             | 
| 463 | 
            -
                async def put(
         | 
| 464 | 
            -
                    self,
         | 
| 465 | 
            -
                    key: str,
         | 
| 466 | 
            -
                    value: Any,
         | 
| 467 | 
            -
                    ttl: Optional[float] = None
         | 
| 468 | 
            -
                ) -> None:
         | 
| 447 | 
            +
             | 
| 448 | 
            +
                async def put(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
         | 
| 469 449 | 
             
                    """Store value in cache asynchronously."""
         | 
| 470 450 | 
             
                    async with self._lock:
         | 
| 471 451 | 
             
                        loop = asyncio.get_event_loop()
         | 
| 472 452 | 
             
                        await loop.run_in_executor(None, self.sync_cache.put, key, value, ttl)
         | 
| 473 | 
            -
             | 
| 453 | 
            +
             | 
| 474 454 | 
             
                async def get_or_compute(
         | 
| 475 | 
            -
                    self,
         | 
| 476 | 
            -
                    key: str,
         | 
| 477 | 
            -
                    compute_fn: Callable[[], T],
         | 
| 478 | 
            -
                    ttl: Optional[float] = None
         | 
| 455 | 
            +
                    self, key: str, compute_fn: Callable[[], T], ttl: Optional[float] = None
         | 
| 479 456 | 
             
                ) -> T:
         | 
| 480 457 | 
             
                    """Get from cache or compute asynchronously."""
         | 
| 481 458 | 
             
                    # Try cache first
         | 
| 482 459 | 
             
                    value = await self.get(key)
         | 
| 483 460 | 
             
                    if value is not None:
         | 
| 484 461 | 
             
                        return value
         | 
| 485 | 
            -
             | 
| 462 | 
            +
             | 
| 486 463 | 
             
                    # Compute value (handle both sync and async functions)
         | 
| 487 464 | 
             
                    if asyncio.iscoroutinefunction(compute_fn):
         | 
| 488 465 | 
             
                        value = await compute_fn()
         | 
| 489 466 | 
             
                    else:
         | 
| 490 467 | 
             
                        loop = asyncio.get_event_loop()
         | 
| 491 468 | 
             
                        value = await loop.run_in_executor(None, compute_fn)
         | 
| 492 | 
            -
             | 
| 469 | 
            +
             | 
| 493 470 | 
             
                    # Cache result
         | 
| 494 471 | 
             
                    await self.put(key, value, ttl)
         | 
| 495 | 
            -
             | 
| 472 | 
            +
             | 
| 496 473 | 
             
                    return value
         | 
| 497 | 
            -
             | 
| 474 | 
            +
             | 
| 498 475 | 
             
                async def get_file(
         | 
| 499 476 | 
             
                    self,
         | 
| 500 477 | 
             
                    file_path: Union[str, Path],
         | 
| 501 | 
            -
                    mode: str =  | 
| 502 | 
            -
                    encoding: str =  | 
| 503 | 
            -
                    ttl: Optional[float] = None
         | 
| 478 | 
            +
                    mode: str = "r",
         | 
| 479 | 
            +
                    encoding: str = "utf-8",
         | 
| 480 | 
            +
                    ttl: Optional[float] = None,
         | 
| 504 481 | 
             
                ) -> Optional[Any]:
         | 
| 505 482 | 
             
                    """Get file content asynchronously."""
         | 
| 506 483 | 
             
                    loop = asyncio.get_event_loop()
         | 
| 507 484 | 
             
                    return await loop.run_in_executor(
         | 
| 508 | 
            -
                        None,
         | 
| 509 | 
            -
                        self.sync_cache.get_file,
         | 
| 510 | 
            -
                        file_path,
         | 
| 511 | 
            -
                        mode,
         | 
| 512 | 
            -
                        encoding,
         | 
| 513 | 
            -
                        ttl
         | 
| 485 | 
            +
                        None, self.sync_cache.get_file, file_path, mode, encoding, ttl
         | 
| 514 486 | 
             
                    )
         | 
| 515 487 |  | 
| 516 488 |  | 
| @@ -520,15 +492,14 @@ _async_cache: Optional[AsyncFileSystemCache] = None | |
| 520 492 |  | 
| 521 493 |  | 
| 522 494 | 
             
            def get_file_cache(
         | 
| 523 | 
            -
                max_size_mb: float = 100,
         | 
| 524 | 
            -
                default_ttl: Optional[float] = 300
         | 
| 495 | 
            +
                max_size_mb: float = 100, default_ttl: Optional[float] = 300
         | 
| 525 496 | 
             
            ) -> FileSystemCache:
         | 
| 526 497 | 
             
                """Get or create global file cache instance.
         | 
| 527 | 
            -
             | 
| 498 | 
            +
             | 
| 528 499 | 
             
                Args:
         | 
| 529 500 | 
             
                    max_size_mb: Maximum cache size in MB
         | 
| 530 501 | 
             
                    default_ttl: Default TTL in seconds
         | 
| 531 | 
            -
             | 
| 502 | 
            +
             | 
| 532 503 | 
             
                Returns:
         | 
| 533 504 | 
             
                    Global FileSystemCache instance
         | 
| 534 505 | 
             
                """
         | 
| @@ -537,60 +508,55 @@ def get_file_cache( | |
| 537 508 | 
             
                    # Use project cache directory
         | 
| 538 509 | 
             
                    cache_dir = Path.home() / ".claude-mpm" / "cache"
         | 
| 539 510 | 
             
                    cache_dir.mkdir(parents=True, exist_ok=True)
         | 
| 540 | 
            -
             | 
| 511 | 
            +
             | 
| 541 512 | 
             
                    _file_cache = FileSystemCache(
         | 
| 542 513 | 
             
                        max_size_mb=max_size_mb,
         | 
| 543 514 | 
             
                        default_ttl=default_ttl,
         | 
| 544 | 
            -
                        persist_path=cache_dir / "fs_cache.pkl"
         | 
| 515 | 
            +
                        persist_path=cache_dir / "fs_cache.pkl",
         | 
| 545 516 | 
             
                    )
         | 
| 546 517 | 
             
                return _file_cache
         | 
| 547 518 |  | 
| 548 519 |  | 
| 549 520 | 
             
            def get_async_cache(
         | 
| 550 | 
            -
                max_size_mb: float = 100,
         | 
| 551 | 
            -
                default_ttl: Optional[float] = 300
         | 
| 521 | 
            +
                max_size_mb: float = 100, default_ttl: Optional[float] = 300
         | 
| 552 522 | 
             
            ) -> AsyncFileSystemCache:
         | 
| 553 523 | 
             
                """Get or create global async cache instance."""
         | 
| 554 524 | 
             
                global _async_cache
         | 
| 555 525 | 
             
                if _async_cache is None:
         | 
| 556 526 | 
             
                    _async_cache = AsyncFileSystemCache(
         | 
| 557 | 
            -
                        max_size_mb=max_size_mb,
         | 
| 558 | 
            -
                        default_ttl=default_ttl
         | 
| 527 | 
            +
                        max_size_mb=max_size_mb, default_ttl=default_ttl
         | 
| 559 528 | 
             
                    )
         | 
| 560 529 | 
             
                return _async_cache
         | 
| 561 530 |  | 
| 562 531 |  | 
| 563 | 
            -
            def cache_decorator(
         | 
| 564 | 
            -
                ttl: Optional[float] = None,
         | 
| 565 | 
            -
                key_prefix: str = ""
         | 
| 566 | 
            -
            ):
         | 
| 532 | 
            +
            def cache_decorator(ttl: Optional[float] = None, key_prefix: str = ""):
         | 
| 567 533 | 
             
                """Decorator for caching function results.
         | 
| 568 | 
            -
             | 
| 534 | 
            +
             | 
| 569 535 | 
             
                Args:
         | 
| 570 536 | 
             
                    ttl: Time-to-live in seconds
         | 
| 571 537 | 
             
                    key_prefix: Optional prefix for cache keys
         | 
| 572 | 
            -
             | 
| 538 | 
            +
             | 
| 573 539 | 
             
                Example:
         | 
| 574 540 | 
             
                    @cache_decorator(ttl=60)
         | 
| 575 541 | 
             
                    def expensive_function(param):
         | 
| 576 542 | 
             
                        return compute_result(param)
         | 
| 577 543 | 
             
                """
         | 
| 544 | 
            +
             | 
| 578 545 | 
             
                def decorator(func: Callable) -> Callable:
         | 
| 579 546 | 
             
                    cache = get_file_cache()
         | 
| 580 | 
            -
             | 
| 547 | 
            +
             | 
| 581 548 | 
             
                    def wrapper(*args, **kwargs):
         | 
| 582 549 | 
             
                        # Generate cache key from function name and arguments
         | 
| 583 550 | 
             
                        key_parts = [key_prefix, func.__name__]
         | 
| 584 551 | 
             
                        key_parts.extend(str(arg) for arg in args)
         | 
| 585 552 | 
             
                        key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
         | 
| 586 553 | 
             
                        cache_key = ":".join(key_parts)
         | 
| 587 | 
            -
             | 
| 554 | 
            +
             | 
| 588 555 | 
             
                        # Use cache
         | 
| 589 556 | 
             
                        return cache.get_or_compute(
         | 
| 590 | 
            -
                            cache_key,
         | 
| 591 | 
            -
                            lambda: func(*args, **kwargs),
         | 
| 592 | 
            -
                            ttl=ttl
         | 
| 557 | 
            +
                            cache_key, lambda: func(*args, **kwargs), ttl=ttl
         | 
| 593 558 | 
             
                        )
         | 
| 594 | 
            -
             | 
| 559 | 
            +
             | 
| 595 560 | 
             
                    return wrapper
         | 
| 596 | 
            -
             | 
| 561 | 
            +
             | 
| 562 | 
            +
                return decorator
         |