claude-mpm 3.9.11__py3-none-any.whl โ 4.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info โ claude_mpm-4.0.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info โ claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info โ claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info โ claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info โ claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            """
         | 
| 2 4 | 
             
            Memory cleanup command implementation for claude-mpm.
         | 
| 3 5 |  | 
| @@ -12,98 +14,89 @@ DESIGN DECISIONS: | |
| 12 14 | 
             
            - Keep recent conversations (30 days by default) in active memory
         | 
| 13 15 | 
             
            """
         | 
| 14 16 |  | 
| 15 | 
            -
            import os
         | 
| 16 | 
            -
            import sys
         | 
| 17 17 | 
             
            import json
         | 
| 18 18 | 
             
            import shutil
         | 
| 19 | 
            -
             | 
| 19 | 
            +
            import sys
         | 
| 20 20 | 
             
            from datetime import datetime, timedelta
         | 
| 21 | 
            -
            from typing import  | 
| 21 | 
            +
            from typing import Any, Dict, List, Tuple
         | 
| 22 22 |  | 
| 23 23 | 
             
            from ...core.logger import get_logger
         | 
| 24 24 |  | 
| 25 25 |  | 
| 26 26 | 
             
            def add_cleanup_parser(subparsers):
         | 
| 27 27 | 
             
                """Add cleanup command parser.
         | 
| 28 | 
            -
             | 
| 28 | 
            +
             | 
| 29 29 | 
             
                WHY: This command addresses the memory leak issue caused by large .claude.json files.
         | 
| 30 30 | 
             
                It provides users with tools to manage conversation history and prevent memory issues.
         | 
| 31 31 | 
             
                """
         | 
| 32 32 | 
             
                parser = subparsers.add_parser(
         | 
| 33 | 
            -
                     | 
| 34 | 
            -
                    aliases=[ | 
| 35 | 
            -
                    help= | 
| 33 | 
            +
                    "cleanup-memory",
         | 
| 34 | 
            +
                    aliases=["cleanup", "clean"],
         | 
| 35 | 
            +
                    help="Clean up Claude conversation history to reduce memory usage",
         | 
| 36 36 | 
             
                )
         | 
| 37 | 
            -
             | 
| 37 | 
            +
             | 
| 38 38 | 
             
                parser.add_argument(
         | 
| 39 | 
            -
                     | 
| 39 | 
            +
                    "--days",
         | 
| 40 40 | 
             
                    type=int,
         | 
| 41 41 | 
             
                    default=30,
         | 
| 42 | 
            -
                    help= | 
| 42 | 
            +
                    help="Keep conversations from the last N days (default: 30)",
         | 
| 43 43 | 
             
                )
         | 
| 44 | 
            -
             | 
| 44 | 
            +
             | 
| 45 45 | 
             
                parser.add_argument(
         | 
| 46 | 
            -
                     | 
| 46 | 
            +
                    "--max-size",
         | 
| 47 47 | 
             
                    type=str,
         | 
| 48 | 
            -
                    default= | 
| 49 | 
            -
                    help= | 
| 48 | 
            +
                    default="500KB",
         | 
| 49 | 
            +
                    help="Maximum size for .claude.json file (e.g., 500KB, 1MB, default: 500KB)",
         | 
| 50 50 | 
             
                )
         | 
| 51 | 
            -
             | 
| 51 | 
            +
             | 
| 52 52 | 
             
                parser.add_argument(
         | 
| 53 | 
            -
                     | 
| 54 | 
            -
                    action= | 
| 53 | 
            +
                    "--archive",
         | 
| 54 | 
            +
                    action="store_true",
         | 
| 55 55 | 
             
                    default=True,
         | 
| 56 | 
            -
                    help= | 
| 56 | 
            +
                    help="Archive old conversations instead of deleting (default: True)",
         | 
| 57 57 | 
             
                )
         | 
| 58 | 
            -
             | 
| 58 | 
            +
             | 
| 59 59 | 
             
                parser.add_argument(
         | 
| 60 | 
            -
                     | 
| 61 | 
            -
                    dest= | 
| 62 | 
            -
                    action= | 
| 63 | 
            -
                    help= | 
| 60 | 
            +
                    "--no-archive",
         | 
| 61 | 
            +
                    dest="archive",
         | 
| 62 | 
            +
                    action="store_false",
         | 
| 63 | 
            +
                    help="Delete old conversations without archiving",
         | 
| 64 64 | 
             
                )
         | 
| 65 | 
            -
             | 
| 65 | 
            +
             | 
| 66 66 | 
             
                parser.add_argument(
         | 
| 67 | 
            -
                     | 
| 68 | 
            -
                    action='store_true',
         | 
| 69 | 
            -
                    help='Skip confirmation prompts'
         | 
| 67 | 
            +
                    "--force", action="store_true", help="Skip confirmation prompts"
         | 
| 70 68 | 
             
                )
         | 
| 71 | 
            -
             | 
| 69 | 
            +
             | 
| 72 70 | 
             
                parser.add_argument(
         | 
| 73 | 
            -
                     | 
| 74 | 
            -
                    action= | 
| 75 | 
            -
                    help= | 
| 71 | 
            +
                    "--dry-run",
         | 
| 72 | 
            +
                    action="store_true",
         | 
| 73 | 
            +
                    help="Show what would be cleaned without making changes",
         | 
| 76 74 | 
             
                )
         | 
| 77 | 
            -
             | 
| 75 | 
            +
             | 
| 78 76 | 
             
                parser.set_defaults(func=cleanup_memory)
         | 
| 79 77 |  | 
| 80 78 |  | 
| 81 79 | 
             
            def parse_size(size_str: str) -> int:
         | 
| 82 80 | 
             
                """Parse human-readable size string to bytes.
         | 
| 83 | 
            -
             | 
| 81 | 
            +
             | 
| 84 82 | 
             
                Args:
         | 
| 85 83 | 
             
                    size_str: Size string like "500KB", "1MB", "2GB"
         | 
| 86 | 
            -
             | 
| 84 | 
            +
             | 
| 87 85 | 
             
                Returns:
         | 
| 88 86 | 
             
                    Size in bytes
         | 
| 89 87 | 
             
                """
         | 
| 90 88 | 
             
                size_str = size_str.upper().strip()
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                multipliers = {
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                    'KB': 1024,
         | 
| 95 | 
            -
                    'MB': 1024 * 1024,
         | 
| 96 | 
            -
                    'GB': 1024 * 1024 * 1024
         | 
| 97 | 
            -
                }
         | 
| 98 | 
            -
                
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                multipliers = {"B": 1, "KB": 1024, "MB": 1024 * 1024, "GB": 1024 * 1024 * 1024}
         | 
| 91 | 
            +
             | 
| 99 92 | 
             
                for suffix, multiplier in multipliers.items():
         | 
| 100 93 | 
             
                    if size_str.endswith(suffix):
         | 
| 101 94 | 
             
                        try:
         | 
| 102 | 
            -
                            number = float(size_str[ | 
| 95 | 
            +
                            number = float(size_str[: -len(suffix)])
         | 
| 103 96 | 
             
                            return int(number * multiplier)
         | 
| 104 97 | 
             
                        except ValueError:
         | 
| 105 98 | 
             
                            pass
         | 
| 106 | 
            -
             | 
| 99 | 
            +
             | 
| 107 100 | 
             
                # Try to parse as raw number (assume bytes)
         | 
| 108 101 | 
             
                try:
         | 
| 109 102 | 
             
                    return int(size_str)
         | 
| @@ -113,14 +106,14 @@ def parse_size(size_str: str) -> int: | |
| 113 106 |  | 
| 114 107 | 
             
            def format_size(size_bytes: int) -> str:
         | 
| 115 108 | 
             
                """Format bytes as human-readable size.
         | 
| 116 | 
            -
             | 
| 109 | 
            +
             | 
| 117 110 | 
             
                Args:
         | 
| 118 111 | 
             
                    size_bytes: Size in bytes
         | 
| 119 | 
            -
             | 
| 112 | 
            +
             | 
| 120 113 | 
             
                Returns:
         | 
| 121 114 | 
             
                    Human-readable size string
         | 
| 122 115 | 
             
                """
         | 
| 123 | 
            -
                for unit in [ | 
| 116 | 
            +
                for unit in ["B", "KB", "MB", "GB"]:
         | 
| 124 117 | 
             
                    if size_bytes < 1024.0:
         | 
| 125 118 | 
             
                        return f"{size_bytes:.1f}{unit}"
         | 
| 126 119 | 
             
                    size_bytes /= 1024.0
         | 
| @@ -129,139 +122,140 @@ def format_size(size_bytes: int) -> str: | |
| 129 122 |  | 
| 130 123 | 
             
            def analyze_claude_json(file_path: Path) -> Tuple[Dict[str, Any], List[str]]:
         | 
| 131 124 | 
             
                """Analyze .claude.json file for cleanup opportunities.
         | 
| 132 | 
            -
             | 
| 125 | 
            +
             | 
| 133 126 | 
             
                WHY: We need to understand the structure of the conversation history
         | 
| 134 127 | 
             
                to identify what can be safely cleaned up.
         | 
| 135 | 
            -
             | 
| 128 | 
            +
             | 
| 136 129 | 
             
                Args:
         | 
| 137 130 | 
             
                    file_path: Path to .claude.json file
         | 
| 138 | 
            -
             | 
| 131 | 
            +
             | 
| 139 132 | 
             
                Returns:
         | 
| 140 133 | 
             
                    Tuple of (stats dict, issues list)
         | 
| 141 134 | 
             
                """
         | 
| 142 135 | 
             
                stats = {
         | 
| 143 | 
            -
                     | 
| 144 | 
            -
                     | 
| 145 | 
            -
                     | 
| 146 | 
            -
                     | 
| 147 | 
            -
                     | 
| 148 | 
            -
                     | 
| 149 | 
            -
                     | 
| 136 | 
            +
                    "file_size": 0,
         | 
| 137 | 
            +
                    "line_count": 0,
         | 
| 138 | 
            +
                    "conversation_count": 0,
         | 
| 139 | 
            +
                    "oldest_conversation": None,
         | 
| 140 | 
            +
                    "newest_conversation": None,
         | 
| 141 | 
            +
                    "large_conversations": [],
         | 
| 142 | 
            +
                    "duplicate_count": 0,
         | 
| 150 143 | 
             
                }
         | 
| 151 | 
            -
             | 
| 144 | 
            +
             | 
| 152 145 | 
             
                issues = []
         | 
| 153 | 
            -
             | 
| 146 | 
            +
             | 
| 154 147 | 
             
                if not file_path.exists():
         | 
| 155 148 | 
             
                    issues.append(f"File not found: {file_path}")
         | 
| 156 149 | 
             
                    return stats, issues
         | 
| 157 | 
            -
             | 
| 150 | 
            +
             | 
| 158 151 | 
             
                # Get file stats
         | 
| 159 152 | 
             
                file_stat = file_path.stat()
         | 
| 160 | 
            -
                stats[ | 
| 161 | 
            -
             | 
| 153 | 
            +
                stats["file_size"] = file_stat.st_size
         | 
| 154 | 
            +
             | 
| 162 155 | 
             
                # Count lines
         | 
| 163 | 
            -
                with open(file_path,  | 
| 164 | 
            -
                    stats[ | 
| 165 | 
            -
             | 
| 156 | 
            +
                with open(file_path, "r") as f:
         | 
| 157 | 
            +
                    stats["line_count"] = sum(1 for _ in f)
         | 
| 158 | 
            +
             | 
| 166 159 | 
             
                # Try to parse JSON structure
         | 
| 167 160 | 
             
                try:
         | 
| 168 | 
            -
                    with open(file_path,  | 
| 161 | 
            +
                    with open(file_path, "r") as f:
         | 
| 169 162 | 
             
                        data = json.load(f)
         | 
| 170 | 
            -
             | 
| 163 | 
            +
             | 
| 171 164 | 
             
                    # Analyze conversation structure
         | 
| 172 165 | 
             
                    # Note: The actual structure may vary, this is a best-effort analysis
         | 
| 173 166 | 
             
                    if isinstance(data, dict):
         | 
| 174 167 | 
             
                        # Look for conversation-like structures
         | 
| 175 168 | 
             
                        for key, value in data.items():
         | 
| 176 | 
            -
                            if isinstance(value, dict) and  | 
| 177 | 
            -
                                stats[ | 
| 178 | 
            -
             | 
| 169 | 
            +
                            if isinstance(value, dict) and "messages" in value:
         | 
| 170 | 
            +
                                stats["conversation_count"] += 1
         | 
| 171 | 
            +
             | 
| 179 172 | 
             
                                # Track conversation sizes
         | 
| 180 173 | 
             
                                conv_size = len(json.dumps(value))
         | 
| 181 174 | 
             
                                if conv_size > 100000:  # >100KB per conversation
         | 
| 182 | 
            -
                                    stats[ | 
| 183 | 
            -
                                         | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 175 | 
            +
                                    stats["large_conversations"].append(
         | 
| 176 | 
            +
                                        {
         | 
| 177 | 
            +
                                            "id": key,
         | 
| 178 | 
            +
                                            "size": conv_size,
         | 
| 179 | 
            +
                                            "message_count": len(value.get("messages", [])),
         | 
| 180 | 
            +
                                        }
         | 
| 181 | 
            +
                                    )
         | 
| 182 | 
            +
             | 
| 188 183 | 
             
                        # Sort large conversations by size
         | 
| 189 | 
            -
                        stats[ | 
| 190 | 
            -
             | 
| 184 | 
            +
                        stats["large_conversations"].sort(key=lambda x: x["size"], reverse=True)
         | 
| 185 | 
            +
             | 
| 191 186 | 
             
                except json.JSONDecodeError as e:
         | 
| 192 187 | 
             
                    issues.append(f"JSON parsing error: {e}")
         | 
| 193 188 | 
             
                except Exception as e:
         | 
| 194 189 | 
             
                    issues.append(f"Error analyzing file: {e}")
         | 
| 195 | 
            -
             | 
| 190 | 
            +
             | 
| 196 191 | 
             
                return stats, issues
         | 
| 197 192 |  | 
| 198 193 |  | 
| 199 194 | 
             
            def create_archive(source_path: Path, archive_dir: Path) -> Path:
         | 
| 200 195 | 
             
                """Create an archive of the current .claude.json file.
         | 
| 201 | 
            -
             | 
| 196 | 
            +
             | 
| 202 197 | 
             
                WHY: We want to preserve conversation history in case users need to
         | 
| 203 198 | 
             
                reference it later, while still cleaning up active memory usage.
         | 
| 204 | 
            -
             | 
| 199 | 
            +
             | 
| 205 200 | 
             
                Args:
         | 
| 206 201 | 
             
                    source_path: Path to source file
         | 
| 207 202 | 
             
                    archive_dir: Directory for archives
         | 
| 208 | 
            -
             | 
| 203 | 
            +
             | 
| 209 204 | 
             
                Returns:
         | 
| 210 205 | 
             
                    Path to created archive
         | 
| 211 206 | 
             
                """
         | 
| 212 207 | 
             
                archive_dir.mkdir(parents=True, exist_ok=True)
         | 
| 213 | 
            -
             | 
| 208 | 
            +
             | 
| 214 209 | 
             
                # Create timestamped archive name
         | 
| 215 210 | 
             
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
         | 
| 216 211 | 
             
                archive_name = f"claude_archive_{timestamp}.json"
         | 
| 217 212 | 
             
                archive_path = archive_dir / archive_name
         | 
| 218 | 
            -
             | 
| 213 | 
            +
             | 
| 219 214 | 
             
                # Copy file to archive
         | 
| 220 215 | 
             
                shutil.copy2(source_path, archive_path)
         | 
| 221 | 
            -
             | 
| 216 | 
            +
             | 
| 222 217 | 
             
                # Optionally compress large archives
         | 
| 223 218 | 
             
                if archive_path.stat().st_size > 10 * 1024 * 1024:  # >10MB
         | 
| 224 219 | 
             
                    import gzip
         | 
| 225 | 
            -
             | 
| 226 | 
            -
                     | 
| 227 | 
            -
             | 
| 220 | 
            +
             | 
| 221 | 
            +
                    compressed_path = archive_path.with_suffix(".json.gz")
         | 
| 222 | 
            +
                    with open(archive_path, "rb") as f_in:
         | 
| 223 | 
            +
                        with gzip.open(compressed_path, "wb") as f_out:
         | 
| 228 224 | 
             
                            shutil.copyfileobj(f_in, f_out)
         | 
| 229 225 | 
             
                    archive_path.unlink()  # Remove uncompressed version
         | 
| 230 226 | 
             
                    return compressed_path
         | 
| 231 | 
            -
             | 
| 227 | 
            +
             | 
| 232 228 | 
             
                return archive_path
         | 
| 233 229 |  | 
| 234 230 |  | 
| 235 | 
            -
            def clean_claude_json( | 
| 231 | 
            +
            def clean_claude_json(
         | 
| 232 | 
            +
                file_path: Path, keep_days: int, dry_run: bool = False
         | 
| 233 | 
            +
            ) -> Tuple[int, int]:
         | 
| 236 234 | 
             
                """Clean up old conversations from .claude.json file.
         | 
| 237 | 
            -
             | 
| 235 | 
            +
             | 
| 238 236 | 
             
                WHY: This function removes old conversation data while preserving recent
         | 
| 239 237 | 
             
                conversations, reducing memory usage when Claude loads the file.
         | 
| 240 | 
            -
             | 
| 238 | 
            +
             | 
| 241 239 | 
             
                Args:
         | 
| 242 240 | 
             
                    file_path: Path to .claude.json file
         | 
| 243 241 | 
             
                    keep_days: Number of days of history to keep
         | 
| 244 242 | 
             
                    dry_run: If True, don't make actual changes
         | 
| 245 | 
            -
             | 
| 243 | 
            +
             | 
| 246 244 | 
             
                Returns:
         | 
| 247 245 | 
             
                    Tuple of (original_size, new_size) in bytes
         | 
| 248 246 | 
             
                """
         | 
| 249 247 | 
             
                if not file_path.exists():
         | 
| 250 248 | 
             
                    return 0, 0
         | 
| 251 | 
            -
             | 
| 249 | 
            +
             | 
| 252 250 | 
             
                original_size = file_path.stat().st_size
         | 
| 253 | 
            -
             | 
| 251 | 
            +
             | 
| 254 252 | 
             
                # For now, return a simple implementation
         | 
| 255 253 | 
             
                # In a real implementation, we would:
         | 
| 256 254 | 
             
                # 1. Parse the JSON structure
         | 
| 257 255 | 
             
                # 2. Filter conversations by date
         | 
| 258 256 | 
             
                # 3. Remove old conversations
         | 
| 259 257 | 
             
                # 4. Write back the cleaned data
         | 
| 260 | 
            -
             | 
| 261 | 
            -
                # Since we don't know the exact structure of .claude.json,
         | 
| 262 | 
            -
                # we'll implement a safer approach: create a new minimal file
         | 
| 263 | 
            -
                # if the current one is too large
         | 
| 264 | 
            -
                
         | 
| 258 | 
            +
             | 
| 265 259 | 
             
                if dry_run:
         | 
| 266 260 | 
             
                    # Estimate new size (roughly 10% of original for very large files)
         | 
| 267 261 | 
             
                    if original_size > 1024 * 1024:  # >1MB
         | 
| @@ -269,7 +263,7 @@ def clean_claude_json(file_path: Path, keep_days: int, dry_run: bool = False) -> | |
| 269 263 | 
             
                    else:
         | 
| 270 264 | 
             
                        estimated_new_size = original_size
         | 
| 271 265 | 
             
                    return original_size, estimated_new_size
         | 
| 272 | 
            -
             | 
| 266 | 
            +
             | 
| 273 267 | 
             
                # For actual cleanup, we would need to understand the file structure better
         | 
| 274 268 | 
             
                # For now, we'll just report the size without making changes
         | 
| 275 269 | 
             
                return original_size, original_size
         | 
| @@ -277,40 +271,40 @@ def clean_claude_json(file_path: Path, keep_days: int, dry_run: bool = False) -> | |
| 277 271 |  | 
| 278 272 | 
             
            def cleanup_memory(args):
         | 
| 279 273 | 
             
                """Clean up Claude conversation history to reduce memory usage.
         | 
| 280 | 
            -
             | 
| 274 | 
            +
             | 
| 281 275 | 
             
                WHY: This command addresses the 2GB memory leak issue when using --resume
         | 
| 282 276 | 
             
                with large .claude.json files. It provides users with tools to manage
         | 
| 283 277 | 
             
                their conversation history and prevent memory issues.
         | 
| 284 | 
            -
             | 
| 278 | 
            +
             | 
| 285 279 | 
             
                Args:
         | 
| 286 280 | 
             
                    args: Parsed command line arguments
         | 
| 287 281 | 
             
                """
         | 
| 288 282 | 
             
                logger = get_logger("cleanup")
         | 
| 289 | 
            -
             | 
| 283 | 
            +
             | 
| 290 284 | 
             
                # File paths
         | 
| 291 285 | 
             
                claude_json = Path.home() / ".claude.json"
         | 
| 292 286 | 
             
                archive_dir = Path.home() / ".claude-mpm" / "archives"
         | 
| 293 | 
            -
             | 
| 287 | 
            +
             | 
| 294 288 | 
             
                print("๐งน Claude Memory Cleanup Tool")
         | 
| 295 289 | 
             
                print("=" * 50)
         | 
| 296 | 
            -
             | 
| 290 | 
            +
             | 
| 297 291 | 
             
                # Check if .claude.json exists
         | 
| 298 292 | 
             
                if not claude_json.exists():
         | 
| 299 293 | 
             
                    print("โ
 No .claude.json file found - nothing to clean up")
         | 
| 300 294 | 
             
                    return
         | 
| 301 | 
            -
             | 
| 295 | 
            +
             | 
| 302 296 | 
             
                # Analyze current state
         | 
| 303 297 | 
             
                print("\n๐ Analyzing current conversation history...")
         | 
| 304 298 | 
             
                stats, issues = analyze_claude_json(claude_json)
         | 
| 305 | 
            -
             | 
| 299 | 
            +
             | 
| 306 300 | 
             
                # Display current status
         | 
| 307 301 | 
             
                print(f"\n๐ File: {claude_json}")
         | 
| 308 302 | 
             
                print(f"๐ Size: {format_size(stats['file_size'])} ({stats['line_count']:,} lines)")
         | 
| 309 | 
            -
             | 
| 303 | 
            +
             | 
| 310 304 | 
             
                # Check if cleanup is needed
         | 
| 311 305 | 
             
                max_size = parse_size(args.max_size)
         | 
| 312 | 
            -
                needs_cleanup = stats[ | 
| 313 | 
            -
             | 
| 306 | 
            +
                needs_cleanup = stats["file_size"] > max_size
         | 
| 307 | 
            +
             | 
| 314 308 | 
             
                if not needs_cleanup:
         | 
| 315 309 | 
             
                    print(f"โ
 File size is within limits ({format_size(max_size)})")
         | 
| 316 310 | 
             
                    if not args.force:
         | 
| @@ -319,13 +313,15 @@ def cleanup_memory(args): | |
| 319 313 | 
             
                else:
         | 
| 320 314 | 
             
                    print(f"โ ๏ธ  File size exceeds recommended limit of {format_size(max_size)}")
         | 
| 321 315 | 
             
                    print(f"   This can cause memory issues when using --resume")
         | 
| 322 | 
            -
             | 
| 316 | 
            +
             | 
| 323 317 | 
             
                # Show large conversations if any
         | 
| 324 | 
            -
                if stats[ | 
| 318 | 
            +
                if stats["large_conversations"]:
         | 
| 325 319 | 
             
                    print(f"\n๐ Found {len(stats['large_conversations'])} large conversations:")
         | 
| 326 | 
            -
                    for conv in stats[ | 
| 327 | 
            -
                        print( | 
| 328 | 
            -
             | 
| 320 | 
            +
                    for conv in stats["large_conversations"][:3]:
         | 
| 321 | 
            +
                        print(
         | 
| 322 | 
            +
                            f"   โข {format_size(conv['size'])} - {conv['message_count']} messages"
         | 
| 323 | 
            +
                        )
         | 
| 324 | 
            +
             | 
| 329 325 | 
             
                # Show cleanup plan
         | 
| 330 326 | 
             
                print(f"\n๐ Cleanup Plan:")
         | 
| 331 327 | 
             
                print(f"   โข Keep conversations from last {args.days} days")
         | 
| @@ -333,17 +329,17 @@ def cleanup_memory(args): | |
| 333 329 | 
             
                    print(f"   โข Archive old conversations to: {archive_dir}")
         | 
| 334 330 | 
             
                else:
         | 
| 335 331 | 
             
                    print(f"   โข Delete old conversations (no archive)")
         | 
| 336 | 
            -
             | 
| 332 | 
            +
             | 
| 337 333 | 
             
                if args.dry_run:
         | 
| 338 334 | 
             
                    print("\n๐ DRY RUN MODE - No changes will be made")
         | 
| 339 | 
            -
             | 
| 335 | 
            +
             | 
| 340 336 | 
             
                # Get confirmation unless forced
         | 
| 341 337 | 
             
                if not args.force and not args.dry_run:
         | 
| 342 338 | 
             
                    print("\nโ ๏ธ  This will modify your conversation history")
         | 
| 343 | 
            -
             | 
| 339 | 
            +
             | 
| 344 340 | 
             
                    # Ensure stdout is flushed before reading input
         | 
| 345 341 | 
             
                    sys.stdout.flush()
         | 
| 346 | 
            -
             | 
| 342 | 
            +
             | 
| 347 343 | 
             
                    # Check if we're in a TTY environment
         | 
| 348 344 | 
             
                    if not sys.stdin.isatty():
         | 
| 349 345 | 
             
                        # In non-TTY environment (like pipes), we need special handling
         | 
| @@ -352,20 +348,20 @@ def cleanup_memory(args): | |
| 352 348 | 
             
                            # Use readline for better compatibility in non-TTY environments
         | 
| 353 349 | 
             
                            response = sys.stdin.readline().strip().lower()
         | 
| 354 350 | 
             
                        except (EOFError, KeyboardInterrupt):
         | 
| 355 | 
            -
                            response =  | 
| 351 | 
            +
                            response = "n"
         | 
| 356 352 | 
             
                    else:
         | 
| 357 353 | 
             
                        # In TTY environment, use normal input()
         | 
| 358 354 | 
             
                        try:
         | 
| 359 355 | 
             
                            response = input("Continue? [y/N]: ").strip().lower()
         | 
| 360 356 | 
             
                        except (EOFError, KeyboardInterrupt):
         | 
| 361 | 
            -
                            response =  | 
| 362 | 
            -
             | 
| 357 | 
            +
                            response = "n"
         | 
| 358 | 
            +
             | 
| 363 359 | 
             
                    # Handle various line endings and control characters
         | 
| 364 | 
            -
                    response = response.replace( | 
| 365 | 
            -
                    if response !=  | 
| 360 | 
            +
                    response = response.replace("\r", "").replace("\n", "").strip()
         | 
| 361 | 
            +
                    if response != "y":
         | 
| 366 362 | 
             
                        print("โ Cleanup cancelled")
         | 
| 367 363 | 
             
                        return
         | 
| 368 | 
            -
             | 
| 364 | 
            +
             | 
| 369 365 | 
             
                # Create backup/archive
         | 
| 370 366 | 
             
                if args.archive and not args.dry_run:
         | 
| 371 367 | 
             
                    print(f"\n๐ฆ Creating archive...")
         | 
| @@ -380,71 +376,75 @@ def cleanup_memory(args): | |
| 380 376 | 
             
                        if not args.force:
         | 
| 381 377 | 
             
                            print("โ Cleanup cancelled for safety")
         | 
| 382 378 | 
             
                            return
         | 
| 383 | 
            -
             | 
| 379 | 
            +
             | 
| 384 380 | 
             
                # Perform cleanup
         | 
| 385 381 | 
             
                print(f"\n๐งน Cleaning up conversation history...")
         | 
| 386 | 
            -
             | 
| 382 | 
            +
             | 
| 387 383 | 
             
                try:
         | 
| 388 384 | 
             
                    original_size, new_size = clean_claude_json(
         | 
| 389 | 
            -
                        claude_json,
         | 
| 390 | 
            -
                        keep_days=args.days,
         | 
| 391 | 
            -
                        dry_run=args.dry_run
         | 
| 385 | 
            +
                        claude_json, keep_days=args.days, dry_run=args.dry_run
         | 
| 392 386 | 
             
                    )
         | 
| 393 | 
            -
             | 
| 387 | 
            +
             | 
| 394 388 | 
             
                    if args.dry_run:
         | 
| 395 | 
            -
                        print( | 
| 389 | 
            +
                        print(
         | 
| 390 | 
            +
                            f"๐ Would reduce size from {format_size(original_size)} to ~{format_size(new_size)}"
         | 
| 391 | 
            +
                        )
         | 
| 396 392 | 
             
                        print(f"๐พ Estimated savings: {format_size(original_size - new_size)}")
         | 
| 397 393 | 
             
                    else:
         | 
| 398 394 | 
             
                        if new_size < original_size:
         | 
| 399 395 | 
             
                            print(f"โ
 Cleanup complete!")
         | 
| 400 | 
            -
                            print( | 
| 396 | 
            +
                            print(
         | 
| 397 | 
            +
                                f"๐ Reduced size from {format_size(original_size)} to {format_size(new_size)}"
         | 
| 398 | 
            +
                            )
         | 
| 401 399 | 
             
                            print(f"๐พ Saved: {format_size(original_size - new_size)}")
         | 
| 402 400 | 
             
                        else:
         | 
| 403 401 | 
             
                            print(f"โน๏ธ  No conversations were old enough to clean up")
         | 
| 404 | 
            -
                            print( | 
| 405 | 
            -
             | 
| 402 | 
            +
                            print(
         | 
| 403 | 
            +
                                f"๐ก Try using --days with a smaller value to clean more aggressively"
         | 
| 404 | 
            +
                            )
         | 
| 405 | 
            +
             | 
| 406 406 | 
             
                except Exception as e:
         | 
| 407 407 | 
             
                    logger.error(f"Cleanup failed: {e}")
         | 
| 408 408 | 
             
                    print(f"โ Cleanup failed: {e}")
         | 
| 409 409 | 
             
                    return
         | 
| 410 | 
            -
             | 
| 410 | 
            +
             | 
| 411 411 | 
             
                # Clean up old archive files
         | 
| 412 412 | 
             
                if args.archive and not args.dry_run:
         | 
| 413 413 | 
             
                    print(f"\n๐๏ธ  Cleaning up old archives...")
         | 
| 414 414 | 
             
                    old_archives = clean_old_archives(archive_dir, keep_days=90)
         | 
| 415 415 | 
             
                    if old_archives:
         | 
| 416 416 | 
             
                        print(f"โ
 Removed {len(old_archives)} old archive files")
         | 
| 417 | 
            -
             | 
| 417 | 
            +
             | 
| 418 418 | 
             
                print("\nโจ Memory cleanup complete!")
         | 
| 419 419 | 
             
                print("๐ก You can now use 'claude-mpm run --resume' without memory issues")
         | 
| 420 420 |  | 
| 421 421 |  | 
| 422 422 | 
             
            def clean_old_archives(archive_dir: Path, keep_days: int = 90) -> List[Path]:
         | 
| 423 423 | 
             
                """Clean up old archive files.
         | 
| 424 | 
            -
             | 
| 424 | 
            +
             | 
| 425 425 | 
             
                WHY: Archive files can accumulate over time. We keep them for a reasonable
         | 
| 426 426 | 
             
                period (90 days by default) then clean them up to save disk space.
         | 
| 427 | 
            -
             | 
| 427 | 
            +
             | 
| 428 428 | 
             
                Args:
         | 
| 429 429 | 
             
                    archive_dir: Directory containing archives
         | 
| 430 430 | 
             
                    keep_days: Number of days to keep archives
         | 
| 431 | 
            -
             | 
| 431 | 
            +
             | 
| 432 432 | 
             
                Returns:
         | 
| 433 433 | 
             
                    List of removed archive paths
         | 
| 434 434 | 
             
                """
         | 
| 435 435 | 
             
                if not archive_dir.exists():
         | 
| 436 436 | 
             
                    return []
         | 
| 437 | 
            -
             | 
| 437 | 
            +
             | 
| 438 438 | 
             
                removed = []
         | 
| 439 439 | 
             
                cutoff_date = datetime.now() - timedelta(days=keep_days)
         | 
| 440 | 
            -
             | 
| 440 | 
            +
             | 
| 441 441 | 
             
                for archive_file in archive_dir.glob("claude_archive_*.json*"):
         | 
| 442 442 | 
             
                    # Check file age
         | 
| 443 443 | 
             
                    file_stat = archive_file.stat()
         | 
| 444 444 | 
             
                    file_time = datetime.fromtimestamp(file_stat.st_mtime)
         | 
| 445 | 
            -
             | 
| 445 | 
            +
             | 
| 446 446 | 
             
                    if file_time < cutoff_date:
         | 
| 447 447 | 
             
                        archive_file.unlink()
         | 
| 448 448 | 
             
                        removed.append(archive_file)
         | 
| 449 | 
            -
             | 
| 450 | 
            -
                return removed
         | 
| 449 | 
            +
             | 
| 450 | 
            +
                return removed
         |