claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__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 +2 -2
- 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 +330 -86
- 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 +363 -220
- 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 +124 -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/built/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/built/dashboard.js +2 -0
- claude_mpm/dashboard/static/built/socket-client.js +2 -0
- 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 +93 -72
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
- 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 +133 -53
- 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 +575 -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 +166 -64
- 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 +185 -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.4.dist-info}/METADATA +90 -22
- claude_mpm-4.0.4.dist-info/RECORD +417 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.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.4.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
| @@ -6,73 +6,84 @@ Inspired by awesome-claude-code's comprehensive error handling approach. | |
| 6 6 |  | 
| 7 7 | 
             
            import logging
         | 
| 8 8 | 
             
            import sys
         | 
| 9 | 
            -
            from typing import Optional, Type, Callable, Any, Dict, List
         | 
| 10 | 
            -
            from functools import wraps
         | 
| 11 9 | 
             
            import traceback
         | 
| 12 10 | 
             
            from datetime import datetime
         | 
| 11 | 
            +
            from functools import wraps
         | 
| 12 | 
            +
            from typing import Any, Callable, Dict, List, Optional, Type
         | 
| 13 13 |  | 
| 14 14 | 
             
            logger = logging.getLogger(__name__)
         | 
| 15 15 |  | 
| 16 16 |  | 
| 17 17 | 
             
            class MPMError(Exception):
         | 
| 18 18 | 
             
                """Base exception for claude-mpm errors."""
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                def __init__( | 
| 21 | 
            -
             | 
| 19 | 
            +
             | 
| 20 | 
            +
                def __init__(
         | 
| 21 | 
            +
                    self,
         | 
| 22 | 
            +
                    message: str,
         | 
| 23 | 
            +
                    details: Optional[Dict[str, Any]] = None,
         | 
| 24 | 
            +
                    suggestions: Optional[List[str]] = None,
         | 
| 25 | 
            +
                ):
         | 
| 22 26 | 
             
                    """Initialize MPM error with details and suggestions."""
         | 
| 23 27 | 
             
                    super().__init__(message)
         | 
| 24 28 | 
             
                    self.details = details or {}
         | 
| 25 29 | 
             
                    self.suggestions = suggestions or []
         | 
| 26 30 | 
             
                    self.timestamp = datetime.now()
         | 
| 27 | 
            -
             | 
| 31 | 
            +
             | 
| 28 32 | 
             
                def get_user_friendly_message(self) -> str:
         | 
| 29 33 | 
             
                    """Get a user-friendly error message."""
         | 
| 30 34 | 
             
                    lines = [f"❌ Error: {str(self)}"]
         | 
| 31 | 
            -
             | 
| 35 | 
            +
             | 
| 32 36 | 
             
                    if self.details:
         | 
| 33 37 | 
             
                        lines.append("\nDetails:")
         | 
| 34 38 | 
             
                        for key, value in self.details.items():
         | 
| 35 39 | 
             
                            lines.append(f"  {key}: {value}")
         | 
| 36 | 
            -
             | 
| 40 | 
            +
             | 
| 37 41 | 
             
                    if self.suggestions:
         | 
| 38 42 | 
             
                        lines.append("\n💡 Suggestions:")
         | 
| 39 43 | 
             
                        for suggestion in self.suggestions:
         | 
| 40 44 | 
             
                            lines.append(f"  - {suggestion}")
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                    return  | 
| 45 | 
            +
             | 
| 46 | 
            +
                    return "\n".join(lines)
         | 
| 43 47 |  | 
| 44 48 |  | 
| 45 49 | 
             
            class AgentLoadError(MPMError):
         | 
| 46 50 | 
             
                """Raised when agent loading fails."""
         | 
| 51 | 
            +
             | 
| 47 52 | 
             
                pass
         | 
| 48 53 |  | 
| 49 54 |  | 
| 50 55 | 
             
            class ValidationError(MPMError):
         | 
| 51 56 | 
             
                """Raised when validation fails."""
         | 
| 57 | 
            +
             | 
| 52 58 | 
             
                pass
         | 
| 53 59 |  | 
| 54 60 |  | 
| 55 61 | 
             
            class ExecutionError(MPMError):
         | 
| 56 62 | 
             
                """Raised when agent execution fails."""
         | 
| 63 | 
            +
             | 
| 57 64 | 
             
                pass
         | 
| 58 65 |  | 
| 59 66 |  | 
| 60 67 | 
             
            class ConfigurationError(MPMError):
         | 
| 61 68 | 
             
                """Raised when configuration is invalid."""
         | 
| 69 | 
            +
             | 
| 62 70 | 
             
                pass
         | 
| 63 71 |  | 
| 64 72 |  | 
| 65 | 
            -
            def handle_errors( | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 73 | 
            +
            def handle_errors(
         | 
| 74 | 
            +
                error_type: Type[Exception] = Exception,
         | 
| 75 | 
            +
                fallback_value: Any = None,
         | 
| 76 | 
            +
                log_level: int = logging.ERROR,
         | 
| 77 | 
            +
            ) -> Callable:
         | 
| 68 78 | 
             
                """
         | 
| 69 79 | 
             
                Decorator for handling errors with detailed logging and user feedback.
         | 
| 70 | 
            -
             | 
| 80 | 
            +
             | 
| 71 81 | 
             
                Args:
         | 
| 72 82 | 
             
                    error_type: Type of exception to catch
         | 
| 73 83 | 
             
                    fallback_value: Value to return on error
         | 
| 74 84 | 
             
                    log_level: Logging level for errors
         | 
| 75 85 | 
             
                """
         | 
| 86 | 
            +
             | 
| 76 87 | 
             
                def decorator(func: Callable) -> Callable:
         | 
| 77 88 | 
             
                    @wraps(func)
         | 
| 78 89 | 
             
                    def wrapper(*args, **kwargs):
         | 
| @@ -81,76 +92,82 @@ def handle_errors(error_type: Type[Exception] = Exception, | |
| 81 92 | 
             
                        except error_type as e:
         | 
| 82 93 | 
             
                            # Log the error with full traceback
         | 
| 83 94 | 
             
                            logger.log(log_level, f"Error in {func.__name__}: {e}", exc_info=True)
         | 
| 84 | 
            -
             | 
| 95 | 
            +
             | 
| 85 96 | 
             
                            # Provide user-friendly feedback
         | 
| 86 97 | 
             
                            if isinstance(e, MPMError):
         | 
| 87 98 | 
             
                                print(e.get_user_friendly_message(), file=sys.stderr)
         | 
| 88 99 | 
             
                            else:
         | 
| 89 100 | 
             
                                print(f"❌ Error: {e}", file=sys.stderr)
         | 
| 90 | 
            -
             | 
| 101 | 
            +
             | 
| 91 102 | 
             
                            return fallback_value
         | 
| 92 103 | 
             
                        except Exception as e:
         | 
| 93 104 | 
             
                            # Catch unexpected errors
         | 
| 94 | 
            -
                            logger.critical( | 
| 105 | 
            +
                            logger.critical(
         | 
| 106 | 
            +
                                f"Unexpected error in {func.__name__}: {e}", exc_info=True
         | 
| 107 | 
            +
                            )
         | 
| 95 108 | 
             
                            print(f"❌ Unexpected error: {e}", file=sys.stderr)
         | 
| 96 109 | 
             
                            print("💡 This might be a bug. Please report it.", file=sys.stderr)
         | 
| 97 110 | 
             
                            return fallback_value
         | 
| 98 | 
            -
             | 
| 111 | 
            +
             | 
| 99 112 | 
             
                    return wrapper
         | 
| 113 | 
            +
             | 
| 100 114 | 
             
                return decorator
         | 
| 101 115 |  | 
| 102 116 |  | 
| 103 117 | 
             
            class ErrorContext:
         | 
| 104 118 | 
             
                """Context manager for enhanced error reporting."""
         | 
| 105 | 
            -
             | 
| 119 | 
            +
             | 
| 106 120 | 
             
                def __init__(self, operation: str, details: Optional[Dict[str, Any]] = None):
         | 
| 107 121 | 
             
                    """Initialize error context."""
         | 
| 108 122 | 
             
                    self.operation = operation
         | 
| 109 123 | 
             
                    self.details = details or {}
         | 
| 110 | 
            -
             | 
| 124 | 
            +
             | 
| 111 125 | 
             
                def __enter__(self):
         | 
| 112 126 | 
             
                    """Enter the context."""
         | 
| 113 127 | 
             
                    logger.debug(f"Starting operation: {self.operation}")
         | 
| 114 128 | 
             
                    return self
         | 
| 115 | 
            -
             | 
| 129 | 
            +
             | 
| 116 130 | 
             
                def __exit__(self, exc_type, exc_val, exc_tb):
         | 
| 117 131 | 
             
                    """Exit the context, handling any errors."""
         | 
| 118 132 | 
             
                    if exc_type is None:
         | 
| 119 133 | 
             
                        logger.debug(f"Completed operation: {self.operation}")
         | 
| 120 134 | 
             
                        return
         | 
| 121 | 
            -
             | 
| 135 | 
            +
             | 
| 122 136 | 
             
                    # Log the error with context
         | 
| 123 137 | 
             
                    logger.error(
         | 
| 124 138 | 
             
                        f"Error during {self.operation}: {exc_val}",
         | 
| 125 | 
            -
                        extra={ | 
| 126 | 
            -
                        exc_info=True
         | 
| 139 | 
            +
                        extra={"operation": self.operation, "details": self.details},
         | 
| 140 | 
            +
                        exc_info=True,
         | 
| 127 141 | 
             
                    )
         | 
| 128 | 
            -
             | 
| 142 | 
            +
             | 
| 129 143 | 
             
                    # Don't suppress the exception
         | 
| 130 144 | 
             
                    return False
         | 
| 131 145 |  | 
| 132 146 |  | 
| 133 | 
            -
            def retry_on_error( | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 147 | 
            +
            def retry_on_error(
         | 
| 148 | 
            +
                max_attempts: int = 3,
         | 
| 149 | 
            +
                delay: float = 1.0,
         | 
| 150 | 
            +
                backoff_factor: float = 2.0,
         | 
| 151 | 
            +
                exceptions: tuple = (Exception,),
         | 
| 152 | 
            +
            ) -> Callable:
         | 
| 137 153 | 
             
                """
         | 
| 138 154 | 
             
                Decorator for retrying operations on error.
         | 
| 139 | 
            -
             | 
| 155 | 
            +
             | 
| 140 156 | 
             
                Args:
         | 
| 141 157 | 
             
                    max_attempts: Maximum number of attempts
         | 
| 142 158 | 
             
                    delay: Initial delay between attempts
         | 
| 143 159 | 
             
                    backoff_factor: Multiplier for delay after each failure
         | 
| 144 160 | 
             
                    exceptions: Tuple of exceptions to retry on
         | 
| 145 161 | 
             
                """
         | 
| 162 | 
            +
             | 
| 146 163 | 
             
                def decorator(func: Callable) -> Callable:
         | 
| 147 164 | 
             
                    @wraps(func)
         | 
| 148 165 | 
             
                    async def async_wrapper(*args, **kwargs):
         | 
| 149 166 | 
             
                        import asyncio
         | 
| 150 | 
            -
             | 
| 167 | 
            +
             | 
| 151 168 | 
             
                        last_exception = None
         | 
| 152 169 | 
             
                        current_delay = delay
         | 
| 153 | 
            -
             | 
| 170 | 
            +
             | 
| 154 171 | 
             
                        for attempt in range(max_attempts):
         | 
| 155 172 | 
             
                            try:
         | 
| 156 173 | 
             
                                return await func(*args, **kwargs)
         | 
| @@ -166,16 +183,16 @@ def retry_on_error(max_attempts: int = 3, | |
| 166 183 | 
             
                                    logger.error(
         | 
| 167 184 | 
             
                                        f"All {max_attempts} attempts failed for {func.__name__}"
         | 
| 168 185 | 
             
                                    )
         | 
| 169 | 
            -
             | 
| 186 | 
            +
             | 
| 170 187 | 
             
                        raise last_exception
         | 
| 171 | 
            -
             | 
| 188 | 
            +
             | 
| 172 189 | 
             
                    @wraps(func)
         | 
| 173 190 | 
             
                    def sync_wrapper(*args, **kwargs):
         | 
| 174 191 | 
             
                        import time
         | 
| 175 | 
            -
             | 
| 192 | 
            +
             | 
| 176 193 | 
             
                        last_exception = None
         | 
| 177 194 | 
             
                        current_delay = delay
         | 
| 178 | 
            -
             | 
| 195 | 
            +
             | 
| 179 196 | 
             
                        for attempt in range(max_attempts):
         | 
| 180 197 | 
             
                            try:
         | 
| 181 198 | 
             
                                return func(*args, **kwargs)
         | 
| @@ -191,16 +208,17 @@ def retry_on_error(max_attempts: int = 3, | |
| 191 208 | 
             
                                    logger.error(
         | 
| 192 209 | 
             
                                        f"All {max_attempts} attempts failed for {func.__name__}"
         | 
| 193 210 | 
             
                                    )
         | 
| 194 | 
            -
             | 
| 211 | 
            +
             | 
| 195 212 | 
             
                        raise last_exception
         | 
| 196 | 
            -
             | 
| 213 | 
            +
             | 
| 197 214 | 
             
                    # Return appropriate wrapper based on function type
         | 
| 198 215 | 
             
                    import asyncio
         | 
| 216 | 
            +
             | 
| 199 217 | 
             
                    if asyncio.iscoroutinefunction(func):
         | 
| 200 218 | 
             
                        return async_wrapper
         | 
| 201 219 | 
             
                    else:
         | 
| 202 220 | 
             
                        return sync_wrapper
         | 
| 203 | 
            -
             | 
| 221 | 
            +
             | 
| 204 222 | 
             
                return decorator
         | 
| 205 223 |  | 
| 206 224 |  | 
| @@ -209,18 +227,20 @@ def format_exception_chain(exc: Exception) -> str: | |
| 209 227 | 
             
                lines = []
         | 
| 210 228 | 
             
                current = exc
         | 
| 211 229 | 
             
                level = 0
         | 
| 212 | 
            -
             | 
| 230 | 
            +
             | 
| 213 231 | 
             
                while current is not None:
         | 
| 214 232 | 
             
                    indent = "  " * level
         | 
| 215 | 
            -
                    lines.append( | 
| 216 | 
            -
             | 
| 217 | 
            -
                     | 
| 233 | 
            +
                    lines.append(
         | 
| 234 | 
            +
                        f"{indent}{'└─' if level > 0 else ''}{type(current).__name__}: {current}"
         | 
| 235 | 
            +
                    )
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                    if hasattr(current, "__cause__"):
         | 
| 218 238 | 
             
                        current = current.__cause__
         | 
| 219 239 | 
             
                        level += 1
         | 
| 220 240 | 
             
                    else:
         | 
| 221 241 | 
             
                        break
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                return  | 
| 242 | 
            +
             | 
| 243 | 
            +
                return "\n".join(lines)
         | 
| 224 244 |  | 
| 225 245 |  | 
| 226 246 | 
             
            # Setup patterns from awesome-claude-code
         | 
| @@ -228,20 +248,20 @@ def suggest_setup_fix(error: Exception) -> List[str]: | |
| 228 248 | 
             
                """Suggest fixes for common setup errors."""
         | 
| 229 249 | 
             
                suggestions = []
         | 
| 230 250 | 
             
                error_msg = str(error).lower()
         | 
| 231 | 
            -
             | 
| 232 | 
            -
                if  | 
| 251 | 
            +
             | 
| 252 | 
            +
                if "git" in error_msg and "not found" in error_msg:
         | 
| 233 253 | 
             
                    suggestions.append("Install git from https://git-scm.com/downloads")
         | 
| 234 | 
            -
             | 
| 235 | 
            -
                if  | 
| 254 | 
            +
             | 
| 255 | 
            +
                if "python" in error_msg and "module" in error_msg:
         | 
| 236 256 | 
             
                    suggestions.append("Ensure you're in a virtual environment")
         | 
| 237 257 | 
             
                    suggestions.append("Run: pip install -e .")
         | 
| 238 | 
            -
             | 
| 239 | 
            -
                if  | 
| 258 | 
            +
             | 
| 259 | 
            +
                if "permission" in error_msg:
         | 
| 240 260 | 
             
                    suggestions.append("Check file permissions")
         | 
| 241 261 | 
             
                    suggestions.append("You may need to run with appropriate privileges")
         | 
| 242 | 
            -
             | 
| 243 | 
            -
                if  | 
| 262 | 
            +
             | 
| 263 | 
            +
                if "config" in error_msg or "configuration" in error_msg:
         | 
| 244 264 | 
             
                    suggestions.append("Check your configuration files")
         | 
| 245 265 | 
             
                    suggestions.append("Run: mpm validate-config")
         | 
| 246 | 
            -
             | 
| 247 | 
            -
                return suggestions
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                return suggestions
         | 
    
        claude_mpm/utils/file_utils.py
    CHANGED
    
    | @@ -13,22 +13,19 @@ import tempfile | |
| 13 13 | 
             
            from pathlib import Path
         | 
| 14 14 | 
             
            from typing import Any, Dict, Optional, Union
         | 
| 15 15 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
            class FileOperationError(Exception):
         | 
| 18 | 
            -
                """Exception raised for file operation errors."""
         | 
| 19 | 
            -
                pass
         | 
| 16 | 
            +
            from ..core.exceptions import FileOperationError
         | 
| 20 17 |  | 
| 21 18 |  | 
| 22 19 | 
             
            def ensure_directory(path: Union[str, Path]) -> Path:
         | 
| 23 20 | 
             
                """
         | 
| 24 21 | 
             
                Ensure a directory exists, creating it if necessary.
         | 
| 25 | 
            -
             | 
| 22 | 
            +
             | 
| 26 23 | 
             
                Args:
         | 
| 27 24 | 
             
                    path: Directory path to ensure exists
         | 
| 28 | 
            -
             | 
| 25 | 
            +
             | 
| 29 26 | 
             
                Returns:
         | 
| 30 27 | 
             
                    Path object for the directory
         | 
| 31 | 
            -
             | 
| 28 | 
            +
             | 
| 32 29 | 
             
                Raises:
         | 
| 33 30 | 
             
                    FileOperationError: If directory cannot be created
         | 
| 34 31 | 
             
                """
         | 
| @@ -36,46 +33,71 @@ def ensure_directory(path: Union[str, Path]) -> Path: | |
| 36 33 | 
             
                try:
         | 
| 37 34 | 
             
                    path.mkdir(parents=True, exist_ok=True)
         | 
| 38 35 | 
             
                    return path
         | 
| 39 | 
            -
                except  | 
| 40 | 
            -
                    raise FileOperationError( | 
| 36 | 
            +
                except (OSError, PermissionError) as e:
         | 
| 37 | 
            +
                    raise FileOperationError(
         | 
| 38 | 
            +
                        f"Failed to create directory: {e}",
         | 
| 39 | 
            +
                        context={
         | 
| 40 | 
            +
                            "path": str(path),
         | 
| 41 | 
            +
                            "operation": "mkdir",
         | 
| 42 | 
            +
                            "error_type": type(e).__name__,
         | 
| 43 | 
            +
                        },
         | 
| 44 | 
            +
                    )
         | 
| 41 45 |  | 
| 42 46 |  | 
| 43 | 
            -
            def safe_read_file(path: Union[str, Path], encoding: str =  | 
| 47 | 
            +
            def safe_read_file(path: Union[str, Path], encoding: str = "utf-8") -> str:
         | 
| 44 48 | 
             
                """
         | 
| 45 49 | 
             
                Safely read a file with error handling.
         | 
| 46 | 
            -
             | 
| 50 | 
            +
             | 
| 47 51 | 
             
                Args:
         | 
| 48 52 | 
             
                    path: File path to read
         | 
| 49 53 | 
             
                    encoding: Text encoding to use
         | 
| 50 | 
            -
             | 
| 54 | 
            +
             | 
| 51 55 | 
             
                Returns:
         | 
| 52 56 | 
             
                    File contents as string
         | 
| 53 | 
            -
             | 
| 57 | 
            +
             | 
| 54 58 | 
             
                Raises:
         | 
| 55 59 | 
             
                    FileOperationError: If file cannot be read
         | 
| 56 60 | 
             
                """
         | 
| 57 61 | 
             
                path = Path(path)
         | 
| 58 62 | 
             
                try:
         | 
| 59 63 | 
             
                    return path.read_text(encoding=encoding)
         | 
| 60 | 
            -
                except  | 
| 61 | 
            -
                    raise FileOperationError( | 
| 64 | 
            +
                except FileNotFoundError as e:
         | 
| 65 | 
            +
                    raise FileOperationError(
         | 
| 66 | 
            +
                        f"File not found: {path}",
         | 
| 67 | 
            +
                        context={
         | 
| 68 | 
            +
                            "path": str(path),
         | 
| 69 | 
            +
                            "operation": "read",
         | 
| 70 | 
            +
                            "encoding": encoding,
         | 
| 71 | 
            +
                            "error_type": "FileNotFoundError",
         | 
| 72 | 
            +
                        },
         | 
| 73 | 
            +
                    )
         | 
| 74 | 
            +
                except (OSError, PermissionError, UnicodeDecodeError) as e:
         | 
| 75 | 
            +
                    raise FileOperationError(
         | 
| 76 | 
            +
                        f"Failed to read file: {e}",
         | 
| 77 | 
            +
                        context={
         | 
| 78 | 
            +
                            "path": str(path),
         | 
| 79 | 
            +
                            "operation": "read",
         | 
| 80 | 
            +
                            "encoding": encoding,
         | 
| 81 | 
            +
                            "error_type": type(e).__name__,
         | 
| 82 | 
            +
                        },
         | 
| 83 | 
            +
                    )
         | 
| 62 84 |  | 
| 63 85 |  | 
| 64 86 | 
             
            def safe_write_file(
         | 
| 65 | 
            -
                path: Union[str, Path], | 
| 66 | 
            -
                content: str, | 
| 67 | 
            -
                encoding: str =  | 
| 68 | 
            -
                create_dirs: bool = True
         | 
| 87 | 
            +
                path: Union[str, Path],
         | 
| 88 | 
            +
                content: str,
         | 
| 89 | 
            +
                encoding: str = "utf-8",
         | 
| 90 | 
            +
                create_dirs: bool = True,
         | 
| 69 91 | 
             
            ) -> None:
         | 
| 70 92 | 
             
                """
         | 
| 71 93 | 
             
                Safely write content to a file with directory creation.
         | 
| 72 | 
            -
             | 
| 94 | 
            +
             | 
| 73 95 | 
             
                Args:
         | 
| 74 96 | 
             
                    path: File path to write
         | 
| 75 97 | 
             
                    content: Content to write
         | 
| 76 98 | 
             
                    encoding: Text encoding to use
         | 
| 77 99 | 
             
                    create_dirs: Whether to create parent directories
         | 
| 78 | 
            -
             | 
| 100 | 
            +
             | 
| 79 101 | 
             
                Raises:
         | 
| 80 102 | 
             
                    FileOperationError: If file cannot be written
         | 
| 81 103 | 
             
                """
         | 
| @@ -89,22 +111,22 @@ def safe_write_file( | |
| 89 111 |  | 
| 90 112 |  | 
| 91 113 | 
             
            def atomic_write(
         | 
| 92 | 
            -
                path: Union[str, Path], | 
| 93 | 
            -
                content: str, | 
| 94 | 
            -
                encoding: str =  | 
| 95 | 
            -
                create_dirs: bool = True
         | 
| 114 | 
            +
                path: Union[str, Path],
         | 
| 115 | 
            +
                content: str,
         | 
| 116 | 
            +
                encoding: str = "utf-8",
         | 
| 117 | 
            +
                create_dirs: bool = True,
         | 
| 96 118 | 
             
            ) -> None:
         | 
| 97 119 | 
             
                """
         | 
| 98 120 | 
             
                Atomically write content to a file using a temporary file.
         | 
| 99 | 
            -
             | 
| 121 | 
            +
             | 
| 100 122 | 
             
                This prevents corruption if the write operation is interrupted.
         | 
| 101 | 
            -
             | 
| 123 | 
            +
             | 
| 102 124 | 
             
                Args:
         | 
| 103 125 | 
             
                    path: File path to write
         | 
| 104 126 | 
             
                    content: Content to write
         | 
| 105 127 | 
             
                    encoding: Text encoding to use
         | 
| 106 128 | 
             
                    create_dirs: Whether to create parent directories
         | 
| 107 | 
            -
             | 
| 129 | 
            +
             | 
| 108 130 | 
             
                Raises:
         | 
| 109 131 | 
             
                    FileOperationError: If file cannot be written atomically
         | 
| 110 132 | 
             
                """
         | 
| @@ -112,25 +134,21 @@ def atomic_write( | |
| 112 134 | 
             
                try:
         | 
| 113 135 | 
             
                    if create_dirs:
         | 
| 114 136 | 
             
                        ensure_directory(path.parent)
         | 
| 115 | 
            -
             | 
| 137 | 
            +
             | 
| 116 138 | 
             
                    # Write to temporary file first
         | 
| 117 139 | 
             
                    with tempfile.NamedTemporaryFile(
         | 
| 118 | 
            -
                        mode= | 
| 119 | 
            -
                        encoding=encoding,
         | 
| 120 | 
            -
                        dir=path.parent,
         | 
| 121 | 
            -
                        delete=False,
         | 
| 122 | 
            -
                        suffix='.tmp'
         | 
| 140 | 
            +
                        mode="w", encoding=encoding, dir=path.parent, delete=False, suffix=".tmp"
         | 
| 123 141 | 
             
                    ) as temp_file:
         | 
| 124 142 | 
             
                        temp_file.write(content)
         | 
| 125 143 | 
             
                        temp_path = temp_file.name
         | 
| 126 | 
            -
             | 
| 144 | 
            +
             | 
| 127 145 | 
             
                    # Atomically move temporary file to target
         | 
| 128 146 | 
             
                    shutil.move(temp_path, path)
         | 
| 129 | 
            -
             | 
| 147 | 
            +
             | 
| 130 148 | 
             
                except Exception as e:
         | 
| 131 149 | 
             
                    # Clean up temporary file if it exists
         | 
| 132 150 | 
             
                    try:
         | 
| 133 | 
            -
                        if  | 
| 151 | 
            +
                        if "temp_path" in locals():
         | 
| 134 152 | 
             
                            os.unlink(temp_path)
         | 
| 135 153 | 
             
                    except:
         | 
| 136 154 | 
             
                        pass
         | 
| @@ -140,10 +158,10 @@ def atomic_write( | |
| 140 158 | 
             
            def get_file_info(path: Union[str, Path]) -> Optional[Dict[str, Any]]:
         | 
| 141 159 | 
             
                """
         | 
| 142 160 | 
             
                Get file metadata safely.
         | 
| 143 | 
            -
             | 
| 161 | 
            +
             | 
| 144 162 | 
             
                Args:
         | 
| 145 163 | 
             
                    path: File path to examine
         | 
| 146 | 
            -
             | 
| 164 | 
            +
             | 
| 147 165 | 
             
                Returns:
         | 
| 148 166 | 
             
                    Dictionary with file information or None if file doesn't exist
         | 
| 149 167 | 
             
                """
         | 
| @@ -151,7 +169,7 @@ def get_file_info(path: Union[str, Path]) -> Optional[Dict[str, Any]]: | |
| 151 169 | 
             
                try:
         | 
| 152 170 | 
             
                    if not path.exists():
         | 
| 153 171 | 
             
                        return None
         | 
| 154 | 
            -
             | 
| 172 | 
            +
             | 
| 155 173 | 
             
                    stat = path.stat()
         | 
| 156 174 | 
             
                    return {
         | 
| 157 175 | 
             
                        "path": str(path),
         | 
| @@ -160,40 +178,38 @@ def get_file_info(path: Union[str, Path]) -> Optional[Dict[str, Any]]: | |
| 160 178 | 
             
                        "created": stat.st_ctime,
         | 
| 161 179 | 
             
                        "is_file": path.is_file(),
         | 
| 162 180 | 
             
                        "is_dir": path.is_dir(),
         | 
| 163 | 
            -
                        "permissions": oct(stat.st_mode)[-3:]
         | 
| 181 | 
            +
                        "permissions": oct(stat.st_mode)[-3:],
         | 
| 164 182 | 
             
                    }
         | 
| 165 183 | 
             
                except Exception:
         | 
| 166 184 | 
             
                    return None
         | 
| 167 185 |  | 
| 168 186 |  | 
| 169 187 | 
             
            def safe_copy_file(
         | 
| 170 | 
            -
                src: Union[str, Path], 
         | 
| 171 | 
            -
                dst: Union[str, Path],
         | 
| 172 | 
            -
                create_dirs: bool = True
         | 
| 188 | 
            +
                src: Union[str, Path], dst: Union[str, Path], create_dirs: bool = True
         | 
| 173 189 | 
             
            ) -> None:
         | 
| 174 190 | 
             
                """
         | 
| 175 191 | 
             
                Safely copy a file with error handling.
         | 
| 176 | 
            -
             | 
| 192 | 
            +
             | 
| 177 193 | 
             
                Args:
         | 
| 178 194 | 
             
                    src: Source file path
         | 
| 179 195 | 
             
                    dst: Destination file path
         | 
| 180 196 | 
             
                    create_dirs: Whether to create destination directories
         | 
| 181 | 
            -
             | 
| 197 | 
            +
             | 
| 182 198 | 
             
                Raises:
         | 
| 183 199 | 
             
                    FileOperationError: If file cannot be copied
         | 
| 184 200 | 
             
                """
         | 
| 185 201 | 
             
                src = Path(src)
         | 
| 186 202 | 
             
                dst = Path(dst)
         | 
| 187 | 
            -
             | 
| 203 | 
            +
             | 
| 188 204 | 
             
                try:
         | 
| 189 205 | 
             
                    if not src.exists():
         | 
| 190 206 | 
             
                        raise FileOperationError(f"Source file does not exist: {src}")
         | 
| 191 | 
            -
             | 
| 207 | 
            +
             | 
| 192 208 | 
             
                    if create_dirs:
         | 
| 193 209 | 
             
                        ensure_directory(dst.parent)
         | 
| 194 | 
            -
             | 
| 210 | 
            +
             | 
| 195 211 | 
             
                    shutil.copy2(src, dst)
         | 
| 196 | 
            -
             | 
| 212 | 
            +
             | 
| 197 213 | 
             
                except Exception as e:
         | 
| 198 214 | 
             
                    raise FileOperationError(f"Failed to copy {src} to {dst}: {e}")
         | 
| 199 215 |  | 
| @@ -201,13 +217,13 @@ def safe_copy_file( | |
| 201 217 | 
             
            def safe_remove_file(path: Union[str, Path]) -> bool:
         | 
| 202 218 | 
             
                """
         | 
| 203 219 | 
             
                Safely remove a file.
         | 
| 204 | 
            -
             | 
| 220 | 
            +
             | 
| 205 221 | 
             
                Args:
         | 
| 206 222 | 
             
                    path: File path to remove
         | 
| 207 | 
            -
             | 
| 223 | 
            +
             | 
| 208 224 | 
             
                Returns:
         | 
| 209 225 | 
             
                    True if file was removed, False if it didn't exist
         | 
| 210 | 
            -
             | 
| 226 | 
            +
             | 
| 211 227 | 
             
                Raises:
         | 
| 212 228 | 
             
                    FileOperationError: If file cannot be removed
         | 
| 213 229 | 
             
                """
         | 
| @@ -224,13 +240,13 @@ def safe_remove_file(path: Union[str, Path]) -> bool: | |
| 224 240 | 
             
            def read_json_file(path: Union[str, Path]) -> Any:
         | 
| 225 241 | 
             
                """
         | 
| 226 242 | 
             
                Read and parse a JSON file safely.
         | 
| 227 | 
            -
             | 
| 243 | 
            +
             | 
| 228 244 | 
             
                Args:
         | 
| 229 245 | 
             
                    path: JSON file path
         | 
| 230 | 
            -
             | 
| 246 | 
            +
             | 
| 231 247 | 
             
                Returns:
         | 
| 232 248 | 
             
                    Parsed JSON data
         | 
| 233 | 
            -
             | 
| 249 | 
            +
             | 
| 234 250 | 
             
                Raises:
         | 
| 235 251 | 
             
                    FileOperationError: If file cannot be read or parsed
         | 
| 236 252 | 
             
                """
         | 
| @@ -242,20 +258,17 @@ def read_json_file(path: Union[str, Path]) -> Any: | |
| 242 258 |  | 
| 243 259 |  | 
| 244 260 | 
             
            def write_json_file(
         | 
| 245 | 
            -
                path: Union[str, Path], 
         | 
| 246 | 
            -
                data: Any, 
         | 
| 247 | 
            -
                indent: int = 2,
         | 
| 248 | 
            -
                atomic: bool = True
         | 
| 261 | 
            +
                path: Union[str, Path], data: Any, indent: int = 2, atomic: bool = True
         | 
| 249 262 | 
             
            ) -> None:
         | 
| 250 263 | 
             
                """
         | 
| 251 264 | 
             
                Write data to a JSON file safely.
         | 
| 252 | 
            -
             | 
| 265 | 
            +
             | 
| 253 266 | 
             
                Args:
         | 
| 254 267 | 
             
                    path: JSON file path
         | 
| 255 268 | 
             
                    data: Data to serialize to JSON
         | 
| 256 269 | 
             
                    indent: JSON indentation level
         | 
| 257 270 | 
             
                    atomic: Whether to use atomic write
         | 
| 258 | 
            -
             | 
| 271 | 
            +
             | 
| 259 272 | 
             
                Raises:
         | 
| 260 273 | 
             
                    FileOperationError: If file cannot be written
         | 
| 261 274 | 
             
                """
         | 
| @@ -269,23 +282,23 @@ def write_json_file( | |
| 269 282 | 
             
                    raise FileOperationError(f"Failed to write JSON file {path}: {e}")
         | 
| 270 283 |  | 
| 271 284 |  | 
| 272 | 
            -
            def backup_file(path: Union[str, Path], backup_suffix: str =  | 
| 285 | 
            +
            def backup_file(path: Union[str, Path], backup_suffix: str = ".backup") -> Path:
         | 
| 273 286 | 
             
                """
         | 
| 274 287 | 
             
                Create a backup copy of a file.
         | 
| 275 | 
            -
             | 
| 288 | 
            +
             | 
| 276 289 | 
             
                Args:
         | 
| 277 290 | 
             
                    path: File path to backup
         | 
| 278 291 | 
             
                    backup_suffix: Suffix to add to backup filename
         | 
| 279 | 
            -
             | 
| 292 | 
            +
             | 
| 280 293 | 
             
                Returns:
         | 
| 281 294 | 
             
                    Path to the backup file
         | 
| 282 | 
            -
             | 
| 295 | 
            +
             | 
| 283 296 | 
             
                Raises:
         | 
| 284 297 | 
             
                    FileOperationError: If backup cannot be created
         | 
| 285 298 | 
             
                """
         | 
| 286 299 | 
             
                path = Path(path)
         | 
| 287 300 | 
             
                backup_path = path.with_suffix(path.suffix + backup_suffix)
         | 
| 288 | 
            -
             | 
| 301 | 
            +
             | 
| 289 302 | 
             
                try:
         | 
| 290 303 | 
             
                    safe_copy_file(path, backup_path)
         | 
| 291 304 | 
             
                    return backup_path
         | 
| @@ -1,39 +1,40 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            """Framework source directory detection utilities.
         | 
| 2 4 |  | 
| 3 5 | 
             
            WHY: This module provides utilities to detect if we're in the framework source directory
         | 
| 4 6 | 
             
            to prevent accidental overwrites of the template files during deployment.
         | 
| 5 7 | 
             
            """
         | 
| 6 8 |  | 
| 7 | 
            -
            from  | 
| 8 | 
            -
            from typing import Tuple, List
         | 
| 9 | 
            +
            from typing import List, Tuple
         | 
| 9 10 |  | 
| 10 11 |  | 
| 11 12 | 
             
            def is_framework_source_directory(path: Path) -> Tuple[bool, List[str]]:
         | 
| 12 13 | 
             
                """
         | 
| 13 14 | 
             
                Check if the given path is the framework source directory.
         | 
| 14 | 
            -
             | 
| 15 | 
            +
             | 
| 15 16 | 
             
                WHY: We need to prevent deployment to the framework source directory itself
         | 
| 16 17 | 
             
                to avoid overwriting template files.
         | 
| 17 | 
            -
             | 
| 18 | 
            +
             | 
| 18 19 | 
             
                Args:
         | 
| 19 20 | 
             
                    path: Path to check
         | 
| 20 | 
            -
             | 
| 21 | 
            +
             | 
| 21 22 | 
             
                Returns:
         | 
| 22 23 | 
             
                    Tuple of (is_framework_source, list of detected markers)
         | 
| 23 24 | 
             
                """
         | 
| 24 25 | 
             
                markers = []
         | 
| 25 | 
            -
             | 
| 26 | 
            +
             | 
| 26 27 | 
             
                # Check for framework source markers
         | 
| 27 28 | 
             
                if (path / "src" / "claude_mpm").exists():
         | 
| 28 29 | 
             
                    markers.append("src/claude_mpm")
         | 
| 29 | 
            -
             | 
| 30 | 
            +
             | 
| 30 31 | 
             
                if (path / "pyproject.toml").exists():
         | 
| 31 32 | 
             
                    markers.append("pyproject.toml")
         | 
| 32 | 
            -
             | 
| 33 | 
            +
             | 
| 33 34 | 
             
                if (path / "src" / "claude_mpm" / "agents" / "INSTRUCTIONS.md").exists():
         | 
| 34 35 | 
             
                    markers.append("framework INSTRUCTIONS.md template")
         | 
| 35 | 
            -
             | 
| 36 | 
            +
             | 
| 36 37 | 
             
                # If we have multiple markers, it's likely the framework source
         | 
| 37 38 | 
             
                is_framework = len(markers) >= 2
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                return is_framework, markers
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                return is_framework, markers
         |