claude-mpm 3.9.9__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 +155 -0
- 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 +90 -49
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +21 -18
- 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 +143 -762
- 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 -1150
- 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 +217 -0
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +36 -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 +571 -0
- 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 +40 -23
- 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 +14 -21
- 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 +97 -93
- claude_mpm/services/mcp_gateway/main.py +307 -127
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
- claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
- 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 +110 -121
- 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 +20 -534
- 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 +9 -0
- claude_mpm/storage/state_storage.py +552 -0
- 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.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/core/config_paths.py +0 -150
- 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/memory_guardian.py +0 -770
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- 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.9.dist-info/RECORD +0 -293
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -17,44 +17,46 @@ to provide complete project lifecycle tracking. | |
| 17 17 | 
             
            """
         | 
| 18 18 |  | 
| 19 19 | 
             
            import os
         | 
| 20 | 
            -
            import sys
         | 
| 21 | 
            -
            import uuid
         | 
| 22 | 
            -
            import subprocess
         | 
| 23 20 | 
             
            import platform
         | 
| 24 21 | 
             
            import shutil
         | 
| 25 | 
            -
             | 
| 22 | 
            +
            import subprocess
         | 
| 23 | 
            +
            import sys
         | 
| 24 | 
            +
            import uuid
         | 
| 25 | 
            +
            from datetime import datetime, timedelta, timezone
         | 
| 26 26 | 
             
            from pathlib import Path
         | 
| 27 | 
            -
            from typing import Dict,  | 
| 27 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 28 | 
            +
             | 
| 28 29 | 
             
            import yaml
         | 
| 29 30 |  | 
| 30 31 | 
             
            from claude_mpm.core.logger import get_logger
         | 
| 31 | 
            -
            from claude_mpm. | 
| 32 | 
            +
            from claude_mpm.core.unified_paths import get_project_root
         | 
| 32 33 |  | 
| 33 34 |  | 
| 34 35 | 
             
            class ProjectRegistryError(Exception):
         | 
| 35 36 | 
             
                """Base exception for project registry operations."""
         | 
| 37 | 
            +
             | 
| 36 38 | 
             
                pass
         | 
| 37 39 |  | 
| 38 40 |  | 
| 39 41 | 
             
            class ProjectRegistry:
         | 
| 40 42 | 
             
                """
         | 
| 41 43 | 
             
                Manages the project registry for claude-mpm installations.
         | 
| 42 | 
            -
             | 
| 44 | 
            +
             | 
| 43 45 | 
             
                WHY: The project registry provides persistent project tracking across sessions,
         | 
| 44 46 | 
             
                enabling project identification, metadata collection, and usage analytics.
         | 
| 45 47 | 
             
                This is crucial for multi-project environments where users switch between
         | 
| 46 48 | 
             
                different codebases.
         | 
| 47 | 
            -
             | 
| 49 | 
            +
             | 
| 48 50 | 
             
                DESIGN DECISION: Registry files are stored in ~/.claude-mpm/registry/
         | 
| 49 51 | 
             
                with UUID-based filenames to ensure uniqueness and avoid conflicts. The registry
         | 
| 50 52 | 
             
                uses YAML for human readability and ease of manual inspection/editing.
         | 
| 51 53 | 
             
                Registry is stored in the user's home directory for persistence across projects.
         | 
| 52 54 | 
             
                """
         | 
| 53 | 
            -
             | 
| 55 | 
            +
             | 
| 54 56 | 
             
                def __init__(self):
         | 
| 55 57 | 
             
                    """
         | 
| 56 58 | 
             
                    Initialize the project registry.
         | 
| 57 | 
            -
             | 
| 59 | 
            +
             | 
| 58 60 | 
             
                    WHY: Sets up the registry directory and logger. The registry directory
         | 
| 59 61 | 
             
                    is created in the user's home directory to keep registry data user-specific
         | 
| 60 62 | 
             
                    and persistent across different projects and sessions.
         | 
| @@ -65,189 +67,189 @@ class ProjectRegistry: | |
| 65 67 | 
             
                    user_home = Path.home()
         | 
| 66 68 | 
             
                    self.registry_dir = user_home / ".claude-mpm" / "registry"
         | 
| 67 69 | 
             
                    self.current_project_path = Path.cwd().resolve()
         | 
| 68 | 
            -
             | 
| 70 | 
            +
             | 
| 69 71 | 
             
                    # Ensure registry directory exists
         | 
| 70 72 | 
             
                    try:
         | 
| 71 73 | 
             
                        self.registry_dir.mkdir(parents=True, exist_ok=True)
         | 
| 72 74 | 
             
                    except Exception as e:
         | 
| 73 75 | 
             
                        self.logger.error(f"Failed to create registry directory: {e}")
         | 
| 74 76 | 
             
                        raise ProjectRegistryError(f"Cannot create registry directory: {e}")
         | 
| 75 | 
            -
             | 
| 77 | 
            +
             | 
| 76 78 | 
             
                def get_or_create_project_entry(self) -> Dict[str, Any]:
         | 
| 77 79 | 
             
                    """
         | 
| 78 80 | 
             
                    Get existing project registry entry or create a new one.
         | 
| 79 | 
            -
             | 
| 81 | 
            +
             | 
| 80 82 | 
             
                    WHY: This is the main entry point for project registration. It handles
         | 
| 81 83 | 
             
                    both new project registration and existing project updates, ensuring
         | 
| 82 84 | 
             
                    that every claude-mpm session is properly tracked.
         | 
| 83 | 
            -
             | 
| 85 | 
            +
             | 
| 84 86 | 
             
                    DESIGN DECISION: Matching is done by normalized absolute path to handle
         | 
| 85 87 | 
             
                    symbolic links and different path representations consistently.
         | 
| 86 | 
            -
             | 
| 88 | 
            +
             | 
| 87 89 | 
             
                    Returns:
         | 
| 88 90 | 
             
                        Dictionary containing the project registry data
         | 
| 89 | 
            -
             | 
| 91 | 
            +
             | 
| 90 92 | 
             
                    Raises:
         | 
| 91 93 | 
             
                        ProjectRegistryError: If registry operations fail
         | 
| 92 94 | 
             
                    """
         | 
| 93 95 | 
             
                    try:
         | 
| 94 96 | 
             
                        # Look for existing registry entry
         | 
| 95 97 | 
             
                        existing_entry = self._find_existing_entry()
         | 
| 96 | 
            -
             | 
| 98 | 
            +
             | 
| 97 99 | 
             
                        if existing_entry:
         | 
| 98 | 
            -
                            self.logger.debug( | 
| 100 | 
            +
                            self.logger.debug(
         | 
| 101 | 
            +
                                f"Found existing project entry: {existing_entry['project_id']}"
         | 
| 102 | 
            +
                            )
         | 
| 99 103 | 
             
                            # Update existing entry with current session info
         | 
| 100 104 | 
             
                            return self._update_existing_entry(existing_entry)
         | 
| 101 105 | 
             
                        else:
         | 
| 102 106 | 
             
                            self.logger.debug("Creating new project registry entry")
         | 
| 103 107 | 
             
                            # Create new entry
         | 
| 104 108 | 
             
                            return self._create_new_entry()
         | 
| 105 | 
            -
             | 
| 109 | 
            +
             | 
| 106 110 | 
             
                    except Exception as e:
         | 
| 107 111 | 
             
                        self.logger.error(f"Failed to get or create project entry: {e}")
         | 
| 108 112 | 
             
                        raise ProjectRegistryError(f"Registry operation failed: {e}")
         | 
| 109 | 
            -
             | 
| 113 | 
            +
             | 
| 110 114 | 
             
                def _find_existing_entry(self) -> Optional[Dict[str, Any]]:
         | 
| 111 115 | 
             
                    """
         | 
| 112 116 | 
             
                    Search for existing registry entry matching current project path.
         | 
| 113 | 
            -
             | 
| 117 | 
            +
             | 
| 114 118 | 
             
                    WHY: We need to match projects by their absolute path to avoid creating
         | 
| 115 119 | 
             
                    duplicate entries when the same project is accessed from different
         | 
| 116 120 | 
             
                    working directories or via different path representations.
         | 
| 117 | 
            -
             | 
| 121 | 
            +
             | 
| 118 122 | 
             
                    Returns:
         | 
| 119 123 | 
             
                        Existing registry data if found, None otherwise
         | 
| 120 124 | 
             
                    """
         | 
| 121 125 | 
             
                    try:
         | 
| 122 126 | 
             
                        # Normalize current path for consistent matching
         | 
| 123 127 | 
             
                        current_path_str = str(self.current_project_path)
         | 
| 124 | 
            -
             | 
| 128 | 
            +
             | 
| 125 129 | 
             
                        # Search all registry files
         | 
| 126 130 | 
             
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 127 131 | 
             
                            try:
         | 
| 128 | 
            -
                                with open(registry_file,  | 
| 132 | 
            +
                                with open(registry_file, "r", encoding="utf-8") as f:
         | 
| 129 133 | 
             
                                    data = yaml.safe_load(f) or {}
         | 
| 130 | 
            -
             | 
| 134 | 
            +
             | 
| 131 135 | 
             
                                # Check if project_path matches
         | 
| 132 | 
            -
                                if data.get( | 
| 133 | 
            -
                                    data[ | 
| 136 | 
            +
                                if data.get("project_path") == current_path_str:
         | 
| 137 | 
            +
                                    data["_registry_file"] = registry_file  # Add file reference
         | 
| 134 138 | 
             
                                    return data
         | 
| 135 | 
            -
             | 
| 139 | 
            +
             | 
| 136 140 | 
             
                            except Exception as e:
         | 
| 137 | 
            -
                                self.logger.warning( | 
| 141 | 
            +
                                self.logger.warning(
         | 
| 142 | 
            +
                                    f"Failed to read registry file {registry_file}: {e}"
         | 
| 143 | 
            +
                                )
         | 
| 138 144 | 
             
                                continue
         | 
| 139 | 
            -
             | 
| 145 | 
            +
             | 
| 140 146 | 
             
                        return None
         | 
| 141 | 
            -
             | 
| 147 | 
            +
             | 
| 142 148 | 
             
                    except Exception as e:
         | 
| 143 149 | 
             
                        self.logger.error(f"Error searching for existing entry: {e}")
         | 
| 144 150 | 
             
                        return None
         | 
| 145 | 
            -
             | 
| 151 | 
            +
             | 
| 146 152 | 
             
                def _create_new_entry(self) -> Dict[str, Any]:
         | 
| 147 153 | 
             
                    """
         | 
| 148 154 | 
             
                    Create a new project registry entry.
         | 
| 149 | 
            -
             | 
| 155 | 
            +
             | 
| 150 156 | 
             
                    WHY: New projects need to be registered with comprehensive metadata
         | 
| 151 157 | 
             
                    including project information, environment details, and initial runtime
         | 
| 152 158 | 
             
                    data. This creates a complete snapshot of the project at first access.
         | 
| 153 | 
            -
             | 
| 159 | 
            +
             | 
| 154 160 | 
             
                    Returns:
         | 
| 155 161 | 
             
                        Newly created registry data
         | 
| 156 162 | 
             
                    """
         | 
| 157 163 | 
             
                    project_id = str(uuid.uuid4())
         | 
| 158 164 | 
             
                    registry_file = self.registry_dir / f"{project_id}.yaml"
         | 
| 159 | 
            -
             | 
| 165 | 
            +
             | 
| 160 166 | 
             
                    # Build comprehensive project data
         | 
| 161 167 | 
             
                    project_data = {
         | 
| 162 | 
            -
                         | 
| 163 | 
            -
                         | 
| 164 | 
            -
                         | 
| 165 | 
            -
                         | 
| 166 | 
            -
                         | 
| 167 | 
            -
                         | 
| 168 | 
            -
                         | 
| 169 | 
            -
                         | 
| 170 | 
            -
                         | 
| 168 | 
            +
                        "project_id": project_id,
         | 
| 169 | 
            +
                        "project_path": str(self.current_project_path),
         | 
| 170 | 
            +
                        "project_name": self.current_project_path.name,
         | 
| 171 | 
            +
                        "metadata": self._build_metadata(is_new=True),
         | 
| 172 | 
            +
                        "runtime": self._build_runtime_info(),
         | 
| 173 | 
            +
                        "environment": self._build_environment_info(),
         | 
| 174 | 
            +
                        "git": self._build_git_info(),
         | 
| 175 | 
            +
                        "session": self._build_session_info(),
         | 
| 176 | 
            +
                        "project_info": self._build_project_info(),
         | 
| 171 177 | 
             
                    }
         | 
| 172 | 
            -
             | 
| 178 | 
            +
             | 
| 173 179 | 
             
                    # Save to registry file
         | 
| 174 180 | 
             
                    self._save_registry_data(registry_file, project_data)
         | 
| 175 | 
            -
                    project_data[ | 
| 176 | 
            -
             | 
| 181 | 
            +
                    project_data["_registry_file"] = registry_file
         | 
| 182 | 
            +
             | 
| 177 183 | 
             
                    self.logger.info(f"Created new project registry entry: {project_id}")
         | 
| 178 184 | 
             
                    return project_data
         | 
| 179 | 
            -
             | 
| 185 | 
            +
             | 
| 180 186 | 
             
                def _update_existing_entry(self, existing_data: Dict[str, Any]) -> Dict[str, Any]:
         | 
| 181 187 | 
             
                    """
         | 
| 182 188 | 
             
                    Update existing project registry entry with current session information.
         | 
| 183 | 
            -
             | 
| 189 | 
            +
             | 
| 184 190 | 
             
                    WHY: Existing projects need their metadata updated to reflect current
         | 
| 185 191 | 
             
                    access patterns, runtime information, and any changes in project state.
         | 
| 186 192 | 
             
                    This maintains accurate usage tracking and project state history.
         | 
| 187 | 
            -
             | 
| 193 | 
            +
             | 
| 188 194 | 
             
                    Args:
         | 
| 189 195 | 
             
                        existing_data: The existing registry data to update
         | 
| 190 | 
            -
             | 
| 196 | 
            +
             | 
| 191 197 | 
             
                    Returns:
         | 
| 192 198 | 
             
                        Updated registry data
         | 
| 193 199 | 
             
                    """
         | 
| 194 | 
            -
                    registry_file = existing_data.get( | 
| 200 | 
            +
                    registry_file = existing_data.get("_registry_file")
         | 
| 195 201 | 
             
                    if not registry_file:
         | 
| 196 | 
            -
                        raise ProjectRegistryError( | 
| 197 | 
            -
             | 
| 202 | 
            +
                        raise ProjectRegistryError(
         | 
| 203 | 
            +
                            "Registry file reference missing from existing data"
         | 
| 204 | 
            +
                        )
         | 
| 205 | 
            +
             | 
| 198 206 | 
             
                    # Update timestamps and counters
         | 
| 199 | 
            -
                    metadata = existing_data.get( | 
| 200 | 
            -
                    access_count = metadata.get( | 
| 207 | 
            +
                    metadata = existing_data.get("metadata", {})
         | 
| 208 | 
            +
                    access_count = metadata.get("access_count", 0) + 1
         | 
| 201 209 | 
             
                    now = datetime.now(timezone.utc).isoformat()
         | 
| 202 | 
            -
             | 
| 203 | 
            -
                    existing_data[ | 
| 204 | 
            -
                         | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                    })
         | 
| 208 | 
            -
                    
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                    existing_data["metadata"].update(
         | 
| 212 | 
            +
                        {"updated_at": now, "last_accessed": now, "access_count": access_count}
         | 
| 213 | 
            +
                    )
         | 
| 214 | 
            +
             | 
| 209 215 | 
             
                    # Update runtime information
         | 
| 210 | 
            -
                    existing_data[ | 
| 211 | 
            -
             | 
| 216 | 
            +
                    existing_data["runtime"] = self._build_runtime_info()
         | 
| 217 | 
            +
             | 
| 212 218 | 
             
                    # Update session information
         | 
| 213 | 
            -
                    existing_data[ | 
| 214 | 
            -
             | 
| 219 | 
            +
                    existing_data["session"] = self._build_session_info()
         | 
| 220 | 
            +
             | 
| 215 221 | 
             
                    # Update git information (may have changed)
         | 
| 216 | 
            -
                    existing_data[ | 
| 217 | 
            -
             | 
| 222 | 
            +
                    existing_data["git"] = self._build_git_info()
         | 
| 223 | 
            +
             | 
| 218 224 | 
             
                    # Update project info (may have changed)
         | 
| 219 | 
            -
                    existing_data[ | 
| 220 | 
            -
             | 
| 225 | 
            +
                    existing_data["project_info"] = self._build_project_info()
         | 
| 226 | 
            +
             | 
| 221 227 | 
             
                    # Save updated data
         | 
| 222 228 | 
             
                    self._save_registry_data(registry_file, existing_data)
         | 
| 223 | 
            -
             | 
| 229 | 
            +
             | 
| 224 230 | 
             
                    self.logger.debug(f"Updated project registry entry (access #{access_count})")
         | 
| 225 231 | 
             
                    return existing_data
         | 
| 226 | 
            -
             | 
| 232 | 
            +
             | 
| 227 233 | 
             
                def _build_metadata(self, is_new: bool = False) -> Dict[str, Any]:
         | 
| 228 234 | 
             
                    """
         | 
| 229 235 | 
             
                    Build metadata section for registry entry.
         | 
| 230 | 
            -
             | 
| 236 | 
            +
             | 
| 231 237 | 
             
                    WHY: Metadata tracks creation, modification, and access patterns for
         | 
| 232 238 | 
             
                    analytics and project lifecycle management.
         | 
| 233 239 | 
             
                    """
         | 
| 234 240 | 
             
                    now = datetime.now(timezone.utc).isoformat()
         | 
| 235 | 
            -
             | 
| 236 | 
            -
                    metadata = {
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                        'last_accessed': now,
         | 
| 239 | 
            -
                        'access_count': 1
         | 
| 240 | 
            -
                    }
         | 
| 241 | 
            -
                    
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                    metadata = {"updated_at": now, "last_accessed": now, "access_count": 1}
         | 
| 243 | 
            +
             | 
| 242 244 | 
             
                    if is_new:
         | 
| 243 | 
            -
                        metadata[ | 
| 244 | 
            -
             | 
| 245 | 
            +
                        metadata["created_at"] = now
         | 
| 246 | 
            +
             | 
| 245 247 | 
             
                    return metadata
         | 
| 246 | 
            -
             | 
| 248 | 
            +
             | 
| 247 249 | 
             
                def _build_runtime_info(self) -> Dict[str, Any]:
         | 
| 248 250 | 
             
                    """
         | 
| 249 251 | 
             
                    Build runtime information section.
         | 
| 250 | 
            -
             | 
| 252 | 
            +
             | 
| 251 253 | 
             
                    WHY: Runtime information helps track session lifecycle, process management,
         | 
| 252 254 | 
             
                    and system state. This is valuable for debugging session issues and
         | 
| 253 255 | 
             
                    understanding usage patterns.
         | 
| @@ -257,128 +259,128 @@ class ProjectRegistry: | |
| 257 259 | 
             
                        from claude_mpm import __version__ as claude_mpm_version
         | 
| 258 260 | 
             
                    except ImportError:
         | 
| 259 261 | 
             
                        claude_mpm_version = "unknown"
         | 
| 260 | 
            -
             | 
| 262 | 
            +
             | 
| 261 263 | 
             
                    return {
         | 
| 262 | 
            -
                         | 
| 263 | 
            -
                         | 
| 264 | 
            -
                         | 
| 265 | 
            -
                         | 
| 266 | 
            -
                         | 
| 267 | 
            -
                         | 
| 264 | 
            +
                        "startup_time": datetime.now(timezone.utc).isoformat(),
         | 
| 265 | 
            +
                        "pid": os.getpid(),
         | 
| 266 | 
            +
                        "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
         | 
| 267 | 
            +
                        "claude_mpm_version": claude_mpm_version,
         | 
| 268 | 
            +
                        "command_line": " ".join(sys.argv),
         | 
| 269 | 
            +
                        "launch_method": "subprocess",  # Default, could be detected based on parent process
         | 
| 268 270 | 
             
                    }
         | 
| 269 | 
            -
             | 
| 271 | 
            +
             | 
| 270 272 | 
             
                def _build_environment_info(self) -> Dict[str, Any]:
         | 
| 271 273 | 
             
                    """
         | 
| 272 274 | 
             
                    Build environment information section.
         | 
| 273 | 
            -
             | 
| 275 | 
            +
             | 
| 274 276 | 
             
                    WHY: Environment information helps with debugging, platform-specific
         | 
| 275 277 | 
             
                    behavior analysis, and provides context for project usage patterns
         | 
| 276 278 | 
             
                    across different systems and user setups.
         | 
| 277 279 | 
             
                    """
         | 
| 278 280 | 
             
                    return {
         | 
| 279 | 
            -
                         | 
| 280 | 
            -
                         | 
| 281 | 
            -
                         | 
| 282 | 
            -
                         | 
| 283 | 
            -
                         | 
| 284 | 
            -
                         | 
| 285 | 
            -
                         | 
| 281 | 
            +
                        "user": os.getenv("USER") or os.getenv("USERNAME", "unknown"),
         | 
| 282 | 
            +
                        "hostname": platform.node(),
         | 
| 283 | 
            +
                        "os": platform.system(),
         | 
| 284 | 
            +
                        "os_version": platform.release(),
         | 
| 285 | 
            +
                        "shell": os.getenv("SHELL", "unknown"),
         | 
| 286 | 
            +
                        "terminal": os.getenv("TERM", "unknown"),
         | 
| 287 | 
            +
                        "cwd": str(Path.cwd()),
         | 
| 286 288 | 
             
                    }
         | 
| 287 | 
            -
             | 
| 289 | 
            +
             | 
| 288 290 | 
             
                def _build_git_info(self) -> Dict[str, Any]:
         | 
| 289 291 | 
             
                    """
         | 
| 290 292 | 
             
                    Build git repository information.
         | 
| 291 | 
            -
             | 
| 293 | 
            +
             | 
| 292 294 | 
             
                    WHY: Git information is crucial for project identification and tracking
         | 
| 293 295 | 
             
                    changes across different branches and commits. This helps understand
         | 
| 294 296 | 
             
                    project state and enables better project management features.
         | 
| 295 297 | 
             
                    """
         | 
| 296 298 | 
             
                    git_info = {
         | 
| 297 | 
            -
                         | 
| 298 | 
            -
                         | 
| 299 | 
            -
                         | 
| 300 | 
            -
                         | 
| 301 | 
            -
                         | 
| 299 | 
            +
                        "is_repo": False,
         | 
| 300 | 
            +
                        "branch": None,
         | 
| 301 | 
            +
                        "remote_url": None,
         | 
| 302 | 
            +
                        "last_commit": None,
         | 
| 303 | 
            +
                        "has_uncommitted": False,
         | 
| 302 304 | 
             
                    }
         | 
| 303 | 
            -
             | 
| 305 | 
            +
             | 
| 304 306 | 
             
                    try:
         | 
| 305 307 | 
             
                        # Check if we're in a git repository
         | 
| 306 308 | 
             
                        result = subprocess.run(
         | 
| 307 | 
            -
                            [ | 
| 309 | 
            +
                            ["git", "rev-parse", "--git-dir"],
         | 
| 308 310 | 
             
                            cwd=self.current_project_path,
         | 
| 309 311 | 
             
                            capture_output=True,
         | 
| 310 312 | 
             
                            text=True,
         | 
| 311 | 
            -
                            timeout=5
         | 
| 313 | 
            +
                            timeout=5,
         | 
| 312 314 | 
             
                        )
         | 
| 313 | 
            -
             | 
| 315 | 
            +
             | 
| 314 316 | 
             
                        if result.returncode == 0:
         | 
| 315 | 
            -
                            git_info[ | 
| 316 | 
            -
             | 
| 317 | 
            +
                            git_info["is_repo"] = True
         | 
| 318 | 
            +
             | 
| 317 319 | 
             
                            # Get current branch
         | 
| 318 320 | 
             
                            try:
         | 
| 319 321 | 
             
                                result = subprocess.run(
         | 
| 320 | 
            -
                                    [ | 
| 322 | 
            +
                                    ["git", "branch", "--show-current"],
         | 
| 321 323 | 
             
                                    cwd=self.current_project_path,
         | 
| 322 324 | 
             
                                    capture_output=True,
         | 
| 323 325 | 
             
                                    text=True,
         | 
| 324 | 
            -
                                    timeout=5
         | 
| 326 | 
            +
                                    timeout=5,
         | 
| 325 327 | 
             
                                )
         | 
| 326 328 | 
             
                                if result.returncode == 0:
         | 
| 327 | 
            -
                                    git_info[ | 
| 329 | 
            +
                                    git_info["branch"] = result.stdout.strip()
         | 
| 328 330 | 
             
                            except Exception:
         | 
| 329 331 | 
             
                                pass
         | 
| 330 | 
            -
             | 
| 332 | 
            +
             | 
| 331 333 | 
             
                            # Get remote URL
         | 
| 332 334 | 
             
                            try:
         | 
| 333 335 | 
             
                                result = subprocess.run(
         | 
| 334 | 
            -
                                    [ | 
| 336 | 
            +
                                    ["git", "remote", "get-url", "origin"],
         | 
| 335 337 | 
             
                                    cwd=self.current_project_path,
         | 
| 336 338 | 
             
                                    capture_output=True,
         | 
| 337 339 | 
             
                                    text=True,
         | 
| 338 | 
            -
                                    timeout=5
         | 
| 340 | 
            +
                                    timeout=5,
         | 
| 339 341 | 
             
                                )
         | 
| 340 342 | 
             
                                if result.returncode == 0:
         | 
| 341 | 
            -
                                    git_info[ | 
| 343 | 
            +
                                    git_info["remote_url"] = result.stdout.strip()
         | 
| 342 344 | 
             
                            except Exception:
         | 
| 343 345 | 
             
                                pass
         | 
| 344 | 
            -
             | 
| 346 | 
            +
             | 
| 345 347 | 
             
                            # Get last commit
         | 
| 346 348 | 
             
                            try:
         | 
| 347 349 | 
             
                                result = subprocess.run(
         | 
| 348 | 
            -
                                    [ | 
| 350 | 
            +
                                    ["git", "rev-parse", "HEAD"],
         | 
| 349 351 | 
             
                                    cwd=self.current_project_path,
         | 
| 350 352 | 
             
                                    capture_output=True,
         | 
| 351 353 | 
             
                                    text=True,
         | 
| 352 | 
            -
                                    timeout=5
         | 
| 354 | 
            +
                                    timeout=5,
         | 
| 353 355 | 
             
                                )
         | 
| 354 356 | 
             
                                if result.returncode == 0:
         | 
| 355 | 
            -
                                    git_info[ | 
| 357 | 
            +
                                    git_info["last_commit"] = result.stdout.strip()
         | 
| 356 358 | 
             
                            except Exception:
         | 
| 357 359 | 
             
                                pass
         | 
| 358 | 
            -
             | 
| 360 | 
            +
             | 
| 359 361 | 
             
                            # Check for uncommitted changes
         | 
| 360 362 | 
             
                            try:
         | 
| 361 363 | 
             
                                result = subprocess.run(
         | 
| 362 | 
            -
                                    [ | 
| 364 | 
            +
                                    ["git", "status", "--porcelain"],
         | 
| 363 365 | 
             
                                    cwd=self.current_project_path,
         | 
| 364 366 | 
             
                                    capture_output=True,
         | 
| 365 367 | 
             
                                    text=True,
         | 
| 366 | 
            -
                                    timeout=5
         | 
| 368 | 
            +
                                    timeout=5,
         | 
| 367 369 | 
             
                                )
         | 
| 368 370 | 
             
                                if result.returncode == 0:
         | 
| 369 | 
            -
                                    git_info[ | 
| 371 | 
            +
                                    git_info["has_uncommitted"] = bool(result.stdout.strip())
         | 
| 370 372 | 
             
                            except Exception:
         | 
| 371 373 | 
             
                                pass
         | 
| 372 | 
            -
             | 
| 374 | 
            +
             | 
| 373 375 | 
             
                    except Exception as e:
         | 
| 374 376 | 
             
                        self.logger.debug(f"Failed to get git info: {e}")
         | 
| 375 | 
            -
             | 
| 377 | 
            +
             | 
| 376 378 | 
             
                    return git_info
         | 
| 377 | 
            -
             | 
| 379 | 
            +
             | 
| 378 380 | 
             
                def _build_session_info(self) -> Dict[str, Any]:
         | 
| 379 381 | 
             
                    """
         | 
| 380 382 | 
             
                    Build session information.
         | 
| 381 | 
            -
             | 
| 383 | 
            +
             | 
| 382 384 | 
             
                    WHY: Session information tracks the current claude-mpm session state,
         | 
| 383 385 | 
             
                    including active components and configuration. This helps with session
         | 
| 384 386 | 
             
                    management and debugging.
         | 
| @@ -386,223 +388,243 @@ class ProjectRegistry: | |
| 386 388 | 
             
                    # These would be populated by the actual session manager
         | 
| 387 389 | 
             
                    # For now, we provide placeholders that can be updated by the caller
         | 
| 388 390 | 
             
                    return {
         | 
| 389 | 
            -
                         | 
| 390 | 
            -
                         | 
| 391 | 
            -
                         | 
| 392 | 
            -
                         | 
| 393 | 
            -
                         | 
| 391 | 
            +
                        "session_id": None,  # Could be set by session manager
         | 
| 392 | 
            +
                        "ticket_count": 0,  # Could be updated by ticket manager
         | 
| 393 | 
            +
                        "agent_count": 0,  # Could be updated by agent manager
         | 
| 394 | 
            +
                        "hooks_enabled": False,  # Could be detected from configuration
         | 
| 395 | 
            +
                        "monitor_enabled": False,  # Could be detected from process state
         | 
| 394 396 | 
             
                    }
         | 
| 395 | 
            -
             | 
| 397 | 
            +
             | 
| 396 398 | 
             
                def _build_project_info(self) -> Dict[str, Any]:
         | 
| 397 399 | 
             
                    """
         | 
| 398 400 | 
             
                    Build project information section.
         | 
| 399 | 
            -
             | 
| 401 | 
            +
             | 
| 400 402 | 
             
                    WHY: Project information helps identify the type of project and its
         | 
| 401 403 | 
             
                    characteristics, enabling better tool selection and project-specific
         | 
| 402 404 | 
             
                    optimizations.
         | 
| 403 405 | 
             
                    """
         | 
| 404 406 | 
             
                    project_info = {
         | 
| 405 | 
            -
                         | 
| 406 | 
            -
                         | 
| 407 | 
            -
                         | 
| 408 | 
            -
                         | 
| 409 | 
            -
                         | 
| 407 | 
            +
                        "has_claude_config": False,
         | 
| 408 | 
            +
                        "has_claude_md": False,
         | 
| 409 | 
            +
                        "has_pyproject": False,
         | 
| 410 | 
            +
                        "has_package_json": False,
         | 
| 411 | 
            +
                        "project_type": "unknown",
         | 
| 410 412 | 
             
                    }
         | 
| 411 | 
            -
             | 
| 413 | 
            +
             | 
| 412 414 | 
             
                    # Check for various project files
         | 
| 413 | 
            -
                    claude_files = [ | 
| 415 | 
            +
                    claude_files = [".claude", "claude.toml", ".claude.toml"]
         | 
| 414 416 | 
             
                    for claude_file in claude_files:
         | 
| 415 417 | 
             
                        if (self.current_project_path / claude_file).exists():
         | 
| 416 | 
            -
                            project_info[ | 
| 418 | 
            +
                            project_info["has_claude_config"] = True
         | 
| 417 419 | 
             
                            break
         | 
| 418 | 
            -
             | 
| 419 | 
            -
                    claude_md_files = [ | 
| 420 | 
            +
             | 
| 421 | 
            +
                    claude_md_files = ["CLAUDE.md", "claude.md", ".claude.md"]
         | 
| 420 422 | 
             
                    for claude_md in claude_md_files:
         | 
| 421 423 | 
             
                        if (self.current_project_path / claude_md).exists():
         | 
| 422 | 
            -
                            project_info[ | 
| 424 | 
            +
                            project_info["has_claude_md"] = True
         | 
| 423 425 | 
             
                            break
         | 
| 424 | 
            -
             | 
| 425 | 
            -
                    if (self.current_project_path /  | 
| 426 | 
            -
                        project_info[ | 
| 427 | 
            -
             | 
| 428 | 
            -
                    if (self.current_project_path /  | 
| 429 | 
            -
                        project_info[ | 
| 430 | 
            -
             | 
| 426 | 
            +
             | 
| 427 | 
            +
                    if (self.current_project_path / "pyproject.toml").exists():
         | 
| 428 | 
            +
                        project_info["has_pyproject"] = True
         | 
| 429 | 
            +
             | 
| 430 | 
            +
                    if (self.current_project_path / "package.json").exists():
         | 
| 431 | 
            +
                        project_info["has_package_json"] = True
         | 
| 432 | 
            +
             | 
| 431 433 | 
             
                    # Determine project type
         | 
| 432 | 
            -
                    if  | 
| 433 | 
            -
                        project_info[ | 
| 434 | 
            -
             | 
| 435 | 
            -
             | 
| 436 | 
            -
             | 
| 437 | 
            -
             | 
| 438 | 
            -
             | 
| 439 | 
            -
             | 
| 440 | 
            -
             | 
| 441 | 
            -
             | 
| 442 | 
            -
             | 
| 443 | 
            -
             | 
| 444 | 
            -
             | 
| 445 | 
            -
             | 
| 434 | 
            +
                    if (
         | 
| 435 | 
            +
                        project_info["has_pyproject"]
         | 
| 436 | 
            +
                        or (self.current_project_path / "setup.py").exists()
         | 
| 437 | 
            +
                    ):
         | 
| 438 | 
            +
                        project_info["project_type"] = "python"
         | 
| 439 | 
            +
                    elif project_info["has_package_json"]:
         | 
| 440 | 
            +
                        project_info["project_type"] = "javascript"
         | 
| 441 | 
            +
                    elif (self.current_project_path / "Cargo.toml").exists():
         | 
| 442 | 
            +
                        project_info["project_type"] = "rust"
         | 
| 443 | 
            +
                    elif (self.current_project_path / "go.mod").exists():
         | 
| 444 | 
            +
                        project_info["project_type"] = "go"
         | 
| 445 | 
            +
                    elif (self.current_project_path / "pom.xml").exists():
         | 
| 446 | 
            +
                        project_info["project_type"] = "java"
         | 
| 447 | 
            +
                    elif any(
         | 
| 448 | 
            +
                        (self.current_project_path / ext).exists()
         | 
| 449 | 
            +
                        for ext in ["*.c", "*.cpp", "*.h", "*.hpp"]
         | 
| 450 | 
            +
                    ):
         | 
| 451 | 
            +
                        project_info["project_type"] = "c/cpp"
         | 
| 452 | 
            +
                    elif project_info["has_claude_config"] or project_info["has_claude_md"]:
         | 
| 453 | 
            +
                        project_info["project_type"] = "claude"
         | 
| 446 454 | 
             
                    else:
         | 
| 447 455 | 
             
                        # Try to detect by file extensions
         | 
| 448 | 
            -
                        common_files = list(self.current_project_path.iterdir())[ | 
| 456 | 
            +
                        common_files = list(self.current_project_path.iterdir())[
         | 
| 457 | 
            +
                            :20
         | 
| 458 | 
            +
                        ]  # Check first 20 files
         | 
| 449 459 | 
             
                        extensions = {f.suffix.lower() for f in common_files if f.is_file()}
         | 
| 450 | 
            -
             | 
| 451 | 
            -
                        if  | 
| 452 | 
            -
                            project_info[ | 
| 453 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 454 | 
            -
                            project_info[ | 
| 455 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 456 | 
            -
                            project_info[ | 
| 457 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 458 | 
            -
                            project_info[ | 
| 459 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 460 | 
            -
                            project_info[ | 
| 461 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 462 | 
            -
                            project_info[ | 
| 463 | 
            -
                        elif any(ext in extensions for ext in [ | 
| 464 | 
            -
                            project_info[ | 
| 465 | 
            -
             | 
| 460 | 
            +
             | 
| 461 | 
            +
                        if ".py" in extensions:
         | 
| 462 | 
            +
                            project_info["project_type"] = "python"
         | 
| 463 | 
            +
                        elif any(ext in extensions for ext in [".js", ".ts", ".jsx", ".tsx"]):
         | 
| 464 | 
            +
                            project_info["project_type"] = "javascript"
         | 
| 465 | 
            +
                        elif any(ext in extensions for ext in [".rs"]):
         | 
| 466 | 
            +
                            project_info["project_type"] = "rust"
         | 
| 467 | 
            +
                        elif any(ext in extensions for ext in [".go"]):
         | 
| 468 | 
            +
                            project_info["project_type"] = "go"
         | 
| 469 | 
            +
                        elif any(ext in extensions for ext in [".java", ".scala", ".kt"]):
         | 
| 470 | 
            +
                            project_info["project_type"] = "jvm"
         | 
| 471 | 
            +
                        elif any(ext in extensions for ext in [".c", ".cpp", ".cc", ".h", ".hpp"]):
         | 
| 472 | 
            +
                            project_info["project_type"] = "c/cpp"
         | 
| 473 | 
            +
                        elif any(ext in extensions for ext in [".md", ".txt", ".rst"]):
         | 
| 474 | 
            +
                            project_info["project_type"] = "documentation"
         | 
| 475 | 
            +
             | 
| 466 476 | 
             
                    return project_info
         | 
| 467 | 
            -
             | 
| 477 | 
            +
             | 
| 468 478 | 
             
                def _save_registry_data(self, registry_file: Path, data: Dict[str, Any]) -> None:
         | 
| 469 479 | 
             
                    """
         | 
| 470 480 | 
             
                    Save registry data to YAML file.
         | 
| 471 | 
            -
             | 
| 481 | 
            +
             | 
| 472 482 | 
             
                    WHY: Centralized saving logic ensures consistent formatting and error
         | 
| 473 483 | 
             
                    handling across all registry operations. YAML format provides human
         | 
| 474 484 | 
             
                    readability for debugging and manual inspection.
         | 
| 475 | 
            -
             | 
| 485 | 
            +
             | 
| 476 486 | 
             
                    Args:
         | 
| 477 487 | 
             
                        registry_file: Path to the registry file
         | 
| 478 488 | 
             
                        data: Registry data to save
         | 
| 479 489 | 
             
                    """
         | 
| 480 490 | 
             
                    try:
         | 
| 481 491 | 
             
                        # Remove internal fields before saving
         | 
| 482 | 
            -
                        save_data = {k: v for k, v in data.items() if not k.startswith( | 
| 483 | 
            -
             | 
| 484 | 
            -
                        with open(registry_file,  | 
| 485 | 
            -
                            yaml.dump( | 
| 486 | 
            -
             | 
| 492 | 
            +
                        save_data = {k: v for k, v in data.items() if not k.startswith("_")}
         | 
| 493 | 
            +
             | 
| 494 | 
            +
                        with open(registry_file, "w", encoding="utf-8") as f:
         | 
| 495 | 
            +
                            yaml.dump(
         | 
| 496 | 
            +
                                save_data, f, default_flow_style=False, sort_keys=False, indent=2
         | 
| 497 | 
            +
                            )
         | 
| 498 | 
            +
             | 
| 487 499 | 
             
                        self.logger.debug(f"Saved registry data to {registry_file}")
         | 
| 488 | 
            -
             | 
| 500 | 
            +
             | 
| 489 501 | 
             
                    except Exception as e:
         | 
| 490 502 | 
             
                        self.logger.error(f"Failed to save registry data: {e}")
         | 
| 491 503 | 
             
                        raise ProjectRegistryError(f"Failed to save registry: {e}")
         | 
| 492 | 
            -
             | 
| 504 | 
            +
             | 
| 493 505 | 
             
                def list_projects(self) -> List[Dict[str, Any]]:
         | 
| 494 506 | 
             
                    """
         | 
| 495 507 | 
             
                    List all registered projects.
         | 
| 496 | 
            -
             | 
| 508 | 
            +
             | 
| 497 509 | 
             
                    WHY: Provides visibility into all projects managed by claude-mpm,
         | 
| 498 510 | 
             
                    useful for project management and analytics.
         | 
| 499 | 
            -
             | 
| 511 | 
            +
             | 
| 500 512 | 
             
                    Returns:
         | 
| 501 513 | 
             
                        List of project registry data dictionaries
         | 
| 502 514 | 
             
                    """
         | 
| 503 515 | 
             
                    projects = []
         | 
| 504 | 
            -
             | 
| 516 | 
            +
             | 
| 505 517 | 
             
                    try:
         | 
| 506 518 | 
             
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 507 519 | 
             
                            try:
         | 
| 508 | 
            -
                                with open(registry_file,  | 
| 520 | 
            +
                                with open(registry_file, "r", encoding="utf-8") as f:
         | 
| 509 521 | 
             
                                    data = yaml.safe_load(f) or {}
         | 
| 510 522 | 
             
                                projects.append(data)
         | 
| 511 523 | 
             
                            except Exception as e:
         | 
| 512 | 
            -
                                self.logger.warning( | 
| 524 | 
            +
                                self.logger.warning(
         | 
| 525 | 
            +
                                    f"Failed to read registry file {registry_file}: {e}"
         | 
| 526 | 
            +
                                )
         | 
| 513 527 | 
             
                                continue
         | 
| 514 | 
            -
             | 
| 528 | 
            +
             | 
| 515 529 | 
             
                    except Exception as e:
         | 
| 516 530 | 
             
                        self.logger.error(f"Failed to list projects: {e}")
         | 
| 517 | 
            -
             | 
| 531 | 
            +
             | 
| 518 532 | 
             
                    return projects
         | 
| 519 | 
            -
             | 
| 533 | 
            +
             | 
| 520 534 | 
             
                def cleanup_old_entries(self, max_age_days: int = 90) -> int:
         | 
| 521 535 | 
             
                    """
         | 
| 522 536 | 
             
                    Clean up old registry entries that haven't been accessed recently.
         | 
| 523 | 
            -
             | 
| 537 | 
            +
             | 
| 524 538 | 
             
                    WHY: Prevents registry directory from growing indefinitely with old
         | 
| 525 539 | 
             
                    project entries. Keeps the registry focused on active projects.
         | 
| 526 | 
            -
             | 
| 540 | 
            +
             | 
| 527 541 | 
             
                    Args:
         | 
| 528 542 | 
             
                        max_age_days: Maximum age in days for keeping entries
         | 
| 529 | 
            -
             | 
| 543 | 
            +
             | 
| 530 544 | 
             
                    Returns:
         | 
| 531 545 | 
             
                        Number of entries cleaned up
         | 
| 532 546 | 
             
                    """
         | 
| 533 547 | 
             
                    if max_age_days <= 0:
         | 
| 534 548 | 
             
                        return 0
         | 
| 535 | 
            -
             | 
| 549 | 
            +
             | 
| 536 550 | 
             
                    cleaned_count = 0
         | 
| 537 551 | 
             
                    cutoff_date = datetime.now(timezone.utc) - timedelta(days=max_age_days)
         | 
| 538 | 
            -
             | 
| 552 | 
            +
             | 
| 539 553 | 
             
                    try:
         | 
| 540 554 | 
             
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 541 555 | 
             
                            try:
         | 
| 542 | 
            -
                                with open(registry_file,  | 
| 556 | 
            +
                                with open(registry_file, "r", encoding="utf-8") as f:
         | 
| 543 557 | 
             
                                    data = yaml.safe_load(f) or {}
         | 
| 544 | 
            -
             | 
| 558 | 
            +
             | 
| 545 559 | 
             
                                # Check last accessed time
         | 
| 546 | 
            -
                                last_accessed_str = data.get( | 
| 560 | 
            +
                                last_accessed_str = data.get("metadata", {}).get("last_accessed")
         | 
| 547 561 | 
             
                                if last_accessed_str:
         | 
| 548 | 
            -
                                    last_accessed = datetime.fromisoformat( | 
| 562 | 
            +
                                    last_accessed = datetime.fromisoformat(
         | 
| 563 | 
            +
                                        last_accessed_str.replace("Z", "+00:00")
         | 
| 564 | 
            +
                                    )
         | 
| 549 565 | 
             
                                    if last_accessed < cutoff_date:
         | 
| 550 566 | 
             
                                        registry_file.unlink()
         | 
| 551 567 | 
             
                                        cleaned_count += 1
         | 
| 552 | 
            -
                                        self.logger.debug( | 
| 553 | 
            -
             | 
| 568 | 
            +
                                        self.logger.debug(
         | 
| 569 | 
            +
                                            f"Cleaned up old registry entry: {registry_file}"
         | 
| 570 | 
            +
                                        )
         | 
| 571 | 
            +
             | 
| 554 572 | 
             
                            except Exception as e:
         | 
| 555 | 
            -
                                self.logger.warning( | 
| 573 | 
            +
                                self.logger.warning(
         | 
| 574 | 
            +
                                    f"Failed to process registry file {registry_file}: {e}"
         | 
| 575 | 
            +
                                )
         | 
| 556 576 | 
             
                                continue
         | 
| 557 | 
            -
             | 
| 577 | 
            +
             | 
| 558 578 | 
             
                    except Exception as e:
         | 
| 559 579 | 
             
                        self.logger.error(f"Failed to cleanup old entries: {e}")
         | 
| 560 | 
            -
             | 
| 580 | 
            +
             | 
| 561 581 | 
             
                    if cleaned_count > 0:
         | 
| 562 582 | 
             
                        self.logger.info(f"Cleaned up {cleaned_count} old registry entries")
         | 
| 563 | 
            -
             | 
| 583 | 
            +
             | 
| 564 584 | 
             
                    return cleaned_count
         | 
| 565 | 
            -
             | 
| 585 | 
            +
             | 
| 566 586 | 
             
                def update_session_info(self, session_updates: Dict[str, Any]) -> bool:
         | 
| 567 587 | 
             
                    """
         | 
| 568 588 | 
             
                    Update session information for the current project.
         | 
| 569 | 
            -
             | 
| 589 | 
            +
             | 
| 570 590 | 
             
                    WHY: Allows other components (session manager, ticket manager, etc.)
         | 
| 571 591 | 
             
                    to update the registry with current session state information.
         | 
| 572 | 
            -
             | 
| 592 | 
            +
             | 
| 573 593 | 
             
                    Args:
         | 
| 574 594 | 
             
                        session_updates: Dictionary of session updates to apply
         | 
| 575 | 
            -
             | 
| 595 | 
            +
             | 
| 576 596 | 
             
                    Returns:
         | 
| 577 597 | 
             
                        True if update was successful, False otherwise
         | 
| 578 598 | 
             
                    """
         | 
| 579 599 | 
             
                    try:
         | 
| 580 600 | 
             
                        existing_entry = self._find_existing_entry()
         | 
| 581 601 | 
             
                        if not existing_entry:
         | 
| 582 | 
            -
                            self.logger.warning( | 
| 602 | 
            +
                            self.logger.warning(
         | 
| 603 | 
            +
                                "No existing registry entry found for session update"
         | 
| 604 | 
            +
                            )
         | 
| 583 605 | 
             
                            return False
         | 
| 584 | 
            -
             | 
| 585 | 
            -
                        registry_file = existing_entry.get( | 
| 606 | 
            +
             | 
| 607 | 
            +
                        registry_file = existing_entry.get("_registry_file")
         | 
| 586 608 | 
             
                        if not registry_file:
         | 
| 587 609 | 
             
                            self.logger.error("Registry file reference missing")
         | 
| 588 610 | 
             
                            return False
         | 
| 589 | 
            -
             | 
| 611 | 
            +
             | 
| 590 612 | 
             
                        # Update session information
         | 
| 591 | 
            -
                        if  | 
| 592 | 
            -
                            existing_entry[ | 
| 593 | 
            -
             | 
| 594 | 
            -
                        existing_entry[ | 
| 595 | 
            -
             | 
| 613 | 
            +
                        if "session" not in existing_entry:
         | 
| 614 | 
            +
                            existing_entry["session"] = {}
         | 
| 615 | 
            +
             | 
| 616 | 
            +
                        existing_entry["session"].update(session_updates)
         | 
| 617 | 
            +
             | 
| 596 618 | 
             
                        # Update metadata timestamp
         | 
| 597 619 | 
             
                        now = datetime.now(timezone.utc).isoformat()
         | 
| 598 | 
            -
                        existing_entry[ | 
| 599 | 
            -
             | 
| 620 | 
            +
                        existing_entry["metadata"]["updated_at"] = now
         | 
| 621 | 
            +
             | 
| 600 622 | 
             
                        # Save updated data
         | 
| 601 623 | 
             
                        self._save_registry_data(registry_file, existing_entry)
         | 
| 602 | 
            -
             | 
| 624 | 
            +
             | 
| 603 625 | 
             
                        self.logger.debug(f"Updated session info: {session_updates}")
         | 
| 604 626 | 
             
                        return True
         | 
| 605 | 
            -
             | 
| 627 | 
            +
             | 
| 606 628 | 
             
                    except Exception as e:
         | 
| 607 629 | 
             
                        self.logger.error(f"Failed to update session info: {e}")
         | 
| 608 | 
            -
                        return False
         | 
| 630 | 
            +
                        return False
         |