claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +2 -2
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +330 -86
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +363 -220
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +124 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/built/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/built/dashboard.js +2 -0
- claude_mpm/dashboard/static/built/socket-client.js +2 -0
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +93 -72
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +133 -53
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +575 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +166 -64
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +185 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/METADATA +90 -22
- claude_mpm-4.0.4.dist-info/RECORD +417 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
| @@ -10,43 +10,43 @@ to provide a complete ticket management system within the claude-mpm CLI. The co | |
| 10 10 | 
             
            mirror the scripts/ticket.py interface for consistency.
         | 
| 11 11 | 
             
            """
         | 
| 12 12 |  | 
| 13 | 
            -
            import  | 
| 13 | 
            +
            import json
         | 
| 14 14 | 
             
            import subprocess
         | 
| 15 | 
            -
             | 
| 16 | 
            -
            from  | 
| 15 | 
            +
            import sys
         | 
| 16 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 17 17 |  | 
| 18 | 
            -
            from ...core.logger import get_logger
         | 
| 19 18 | 
             
            from ...constants import TicketCommands
         | 
| 19 | 
            +
            from ...core.logger import get_logger
         | 
| 20 20 |  | 
| 21 21 |  | 
| 22 22 | 
             
            def manage_tickets(args):
         | 
| 23 23 | 
             
                """
         | 
| 24 24 | 
             
                Main ticket command dispatcher.
         | 
| 25 | 
            -
             | 
| 25 | 
            +
             | 
| 26 26 | 
             
                WHY: This function routes ticket subcommands to their appropriate handlers,
         | 
| 27 27 | 
             
                providing a single entry point for all ticket-related operations.
         | 
| 28 | 
            -
             | 
| 28 | 
            +
             | 
| 29 29 | 
             
                DESIGN DECISION: We use a subcommand pattern similar to git, allowing for
         | 
| 30 30 | 
             
                intuitive command structure like 'claude-mpm tickets create "title"'.
         | 
| 31 | 
            -
             | 
| 31 | 
            +
             | 
| 32 32 | 
             
                Args:
         | 
| 33 33 | 
             
                    args: Parsed command line arguments with 'tickets_command' attribute
         | 
| 34 | 
            -
             | 
| 34 | 
            +
             | 
| 35 35 | 
             
                Returns:
         | 
| 36 36 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 37 37 | 
             
                """
         | 
| 38 38 | 
             
                logger = get_logger("cli.tickets")
         | 
| 39 | 
            -
             | 
| 39 | 
            +
             | 
| 40 40 | 
             
                # Handle case where no subcommand is provided - default to list
         | 
| 41 | 
            -
                if not hasattr(args,  | 
| 41 | 
            +
                if not hasattr(args, "tickets_command") or not args.tickets_command:
         | 
| 42 42 | 
             
                    # Default to list command for backward compatibility
         | 
| 43 43 | 
             
                    args.tickets_command = TicketCommands.LIST.value
         | 
| 44 44 | 
             
                    # Set default limit if not present
         | 
| 45 | 
            -
                    if not hasattr(args,  | 
| 45 | 
            +
                    if not hasattr(args, "limit"):
         | 
| 46 46 | 
             
                        args.limit = 10
         | 
| 47 | 
            -
                    if not hasattr(args,  | 
| 47 | 
            +
                    if not hasattr(args, "verbose"):
         | 
| 48 48 | 
             
                        args.verbose = False
         | 
| 49 | 
            -
             | 
| 49 | 
            +
             | 
| 50 50 | 
             
                # Map subcommands to handler functions
         | 
| 51 51 | 
             
                handlers = {
         | 
| 52 52 | 
             
                    TicketCommands.CREATE.value: create_ticket,
         | 
| @@ -59,7 +59,7 @@ def manage_tickets(args): | |
| 59 59 | 
             
                    TicketCommands.COMMENT.value: add_comment,
         | 
| 60 60 | 
             
                    TicketCommands.WORKFLOW.value: update_workflow,
         | 
| 61 61 | 
             
                }
         | 
| 62 | 
            -
             | 
| 62 | 
            +
             | 
| 63 63 | 
             
                # Execute the appropriate handler
         | 
| 64 64 | 
             
                handler = handlers.get(args.tickets_command)
         | 
| 65 65 | 
             
                if handler:
         | 
| @@ -70,8 +70,9 @@ def manage_tickets(args): | |
| 70 70 | 
             
                        return 1
         | 
| 71 71 | 
             
                    except Exception as e:
         | 
| 72 72 | 
             
                        logger.error(f"Error executing {args.tickets_command}: {e}")
         | 
| 73 | 
            -
                        if hasattr(args,  | 
| 73 | 
            +
                        if hasattr(args, "debug") and args.debug:
         | 
| 74 74 | 
             
                            import traceback
         | 
| 75 | 
            +
             | 
| 75 76 | 
             
                            traceback.print_exc()
         | 
| 76 77 | 
             
                        return 1
         | 
| 77 78 | 
             
                else:
         | 
| @@ -82,34 +83,34 @@ def manage_tickets(args): | |
| 82 83 | 
             
            def create_ticket(args):
         | 
| 83 84 | 
             
                """
         | 
| 84 85 | 
             
                Create a new ticket.
         | 
| 85 | 
            -
             | 
| 86 | 
            +
             | 
| 86 87 | 
             
                WHY: Users need to create tickets to track work items, bugs, and features.
         | 
| 87 88 | 
             
                This command provides a streamlined interface for ticket creation.
         | 
| 88 | 
            -
             | 
| 89 | 
            +
             | 
| 89 90 | 
             
                DESIGN DECISION: We parse description from remaining args to allow natural
         | 
| 90 91 | 
             
                command line usage like: tickets create "title" -d This is a description
         | 
| 91 | 
            -
             | 
| 92 | 
            +
             | 
| 92 93 | 
             
                Args:
         | 
| 93 94 | 
             
                    args: Arguments with title, type, priority, description, tags, etc.
         | 
| 94 | 
            -
             | 
| 95 | 
            +
             | 
| 95 96 | 
             
                Returns:
         | 
| 96 97 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 97 98 | 
             
                """
         | 
| 98 99 | 
             
                logger = get_logger("cli.tickets")
         | 
| 99 | 
            -
             | 
| 100 | 
            +
             | 
| 100 101 | 
             
                try:
         | 
| 101 102 | 
             
                    from ...services.ticket_manager import TicketManager
         | 
| 102 103 | 
             
                except ImportError:
         | 
| 103 104 | 
             
                    from claude_mpm.services.ticket_manager import TicketManager
         | 
| 104 | 
            -
             | 
| 105 | 
            +
             | 
| 105 106 | 
             
                ticket_manager = TicketManager()
         | 
| 106 | 
            -
             | 
| 107 | 
            +
             | 
| 107 108 | 
             
                # Parse description from remaining args or use default
         | 
| 108 109 | 
             
                description = " ".join(args.description) if args.description else ""
         | 
| 109 | 
            -
             | 
| 110 | 
            +
             | 
| 110 111 | 
             
                # Parse tags
         | 
| 111 112 | 
             
                tags = args.tags.split(",") if args.tags else []
         | 
| 112 | 
            -
             | 
| 113 | 
            +
             | 
| 113 114 | 
             
                # Create ticket with all provided parameters
         | 
| 114 115 | 
             
                ticket_id = ticket_manager.create_ticket(
         | 
| 115 116 | 
             
                    title=args.title,
         | 
| @@ -118,10 +119,10 @@ def create_ticket(args): | |
| 118 119 | 
             
                    priority=args.priority,
         | 
| 119 120 | 
             
                    tags=tags,
         | 
| 120 121 | 
             
                    source="claude-mpm-cli",
         | 
| 121 | 
            -
                    parent_epic=getattr(args,  | 
| 122 | 
            -
                    parent_issue=getattr(args,  | 
| 122 | 
            +
                    parent_epic=getattr(args, "parent_epic", None),
         | 
| 123 | 
            +
                    parent_issue=getattr(args, "parent_issue", None),
         | 
| 123 124 | 
             
                )
         | 
| 124 | 
            -
             | 
| 125 | 
            +
             | 
| 125 126 | 
             
                if ticket_id:
         | 
| 126 127 | 
             
                    print(f"✅ Created ticket: {ticket_id}")
         | 
| 127 128 | 
             
                    if args.verbose:
         | 
| @@ -129,9 +130,9 @@ def create_ticket(args): | |
| 129 130 | 
             
                        print(f"   Priority: {args.priority}")
         | 
| 130 131 | 
             
                        if tags:
         | 
| 131 132 | 
             
                            print(f"   Tags: {', '.join(tags)}")
         | 
| 132 | 
            -
                        if getattr(args,  | 
| 133 | 
            +
                        if getattr(args, "parent_epic", None):
         | 
| 133 134 | 
             
                            print(f"   Parent Epic: {args.parent_epic}")
         | 
| 134 | 
            -
                        if getattr(args,  | 
| 135 | 
            +
                        if getattr(args, "parent_issue", None):
         | 
| 135 136 | 
             
                            print(f"   Parent Issue: {args.parent_issue}")
         | 
| 136 137 | 
             
                    return 0
         | 
| 137 138 | 
             
                else:
         | 
| @@ -142,82 +143,158 @@ def create_ticket(args): | |
| 142 143 | 
             
            def list_tickets(args):
         | 
| 143 144 | 
             
                """
         | 
| 144 145 | 
             
                List recent tickets with optional filtering.
         | 
| 145 | 
            -
             | 
| 146 | 
            +
             | 
| 146 147 | 
             
                WHY: Users need to review tickets created during Claude sessions. This command
         | 
| 147 148 | 
             
                provides a quick way to see recent tickets with their status and metadata.
         | 
| 148 | 
            -
             | 
| 149 | 
            +
             | 
| 149 150 | 
             
                DESIGN DECISION: We show tickets in a compact format with emoji status indicators
         | 
| 150 151 | 
             
                for better visual scanning. Filters allow focusing on specific ticket types/statuses.
         | 
| 151 | 
            -
             | 
| 152 | 
            +
             | 
| 152 153 | 
             
                Args:
         | 
| 153 154 | 
             
                    args: Arguments with limit, type filter, status filter, verbose flag
         | 
| 154 | 
            -
             | 
| 155 | 
            +
             | 
| 155 156 | 
             
                Returns:
         | 
| 156 157 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 157 158 | 
             
                """
         | 
| 158 159 | 
             
                logger = get_logger("cli.tickets")
         | 
| 159 | 
            -
             | 
| 160 | 
            +
             | 
| 160 161 | 
             
                try:
         | 
| 162 | 
            +
                    # Get pagination parameters
         | 
| 163 | 
            +
                    page = getattr(args, "page", 1)
         | 
| 164 | 
            +
                    page_size = getattr(args, "page_size", 20)
         | 
| 165 | 
            +
                    limit = getattr(args, "limit", page_size)  # Use page_size as default limit
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    # Validate pagination parameters
         | 
| 168 | 
            +
                    if page < 1:
         | 
| 169 | 
            +
                        print("❌ Page number must be 1 or greater")
         | 
| 170 | 
            +
                        return 1
         | 
| 171 | 
            +
                    if page_size < 1:
         | 
| 172 | 
            +
                        print("❌ Page size must be 1 or greater")
         | 
| 173 | 
            +
                        return 1
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    # Try to use ai-trackdown CLI directly for better pagination support
         | 
| 176 | 
            +
                    tickets = []
         | 
| 161 177 | 
             
                    try:
         | 
| 162 | 
            -
                         | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
                         | 
| 178 | 
            -
             | 
| 179 | 
            -
                             | 
| 180 | 
            -
             | 
| 181 | 
            -
                        
         | 
| 182 | 
            -
                         | 
| 183 | 
            -
             | 
| 184 | 
            -
                         | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 178 | 
            +
                        # Build aitrackdown command with pagination
         | 
| 179 | 
            +
                        cmd = ["aitrackdown", "status", "tasks"]
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                        # Calculate offset for pagination
         | 
| 182 | 
            +
                        offset = (page - 1) * page_size
         | 
| 183 | 
            +
                        total_needed = offset + page_size
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                        # Request more tickets than needed to handle filtering
         | 
| 186 | 
            +
                        cmd.extend(["--limit", str(total_needed * 2)])
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                        # Add filters
         | 
| 189 | 
            +
                        type_filter = getattr(args, "type", None) or "all"
         | 
| 190 | 
            +
                        if type_filter != "all" and type_filter is not None:
         | 
| 191 | 
            +
                            cmd.extend(["--type", type_filter])
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                        status_filter = getattr(args, "status", None) or "all"
         | 
| 194 | 
            +
                        if status_filter != "all" and status_filter is not None:
         | 
| 195 | 
            +
                            cmd.extend(["--status", status_filter])
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                        # Execute command
         | 
| 198 | 
            +
                        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                        # Parse JSON output
         | 
| 201 | 
            +
                        if result.stdout.strip():
         | 
| 202 | 
            +
                            try:
         | 
| 203 | 
            +
                                all_tickets = json.loads(result.stdout)
         | 
| 204 | 
            +
                                if isinstance(all_tickets, list):
         | 
| 205 | 
            +
                                    # Apply pagination
         | 
| 206 | 
            +
                                    start_idx = offset
         | 
| 207 | 
            +
                                    end_idx = start_idx + page_size
         | 
| 208 | 
            +
                                    tickets = all_tickets[start_idx:end_idx]
         | 
| 209 | 
            +
                                else:
         | 
| 210 | 
            +
                                    tickets = []
         | 
| 211 | 
            +
                            except json.JSONDecodeError:
         | 
| 212 | 
            +
                                logger.warning(
         | 
| 213 | 
            +
                                    "Failed to parse aitrackdown JSON output, falling back to stub"
         | 
| 214 | 
            +
                                )
         | 
| 215 | 
            +
                                tickets = []
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                    except (subprocess.CalledProcessError, FileNotFoundError) as e:
         | 
| 218 | 
            +
                        logger.warning(f"aitrackdown command failed: {e}, falling back to stub")
         | 
| 219 | 
            +
                        # Fallback to stub implementation
         | 
| 220 | 
            +
                        try:
         | 
| 221 | 
            +
                            from ...services.ticket_manager import TicketManager
         | 
| 222 | 
            +
                        except ImportError:
         | 
| 223 | 
            +
                            from claude_mpm.services.ticket_manager import TicketManager
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                        ticket_manager = TicketManager()
         | 
| 226 | 
            +
                        all_tickets = ticket_manager.list_recent_tickets(limit=limit * 2)
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                        # Apply filters and pagination manually for stub
         | 
| 229 | 
            +
                        filtered_tickets = []
         | 
| 230 | 
            +
                        for ticket in all_tickets:
         | 
| 231 | 
            +
                            # Type filter
         | 
| 232 | 
            +
                            if type_filter != "all":
         | 
| 233 | 
            +
                                ticket_type = ticket.get("metadata", {}).get(
         | 
| 234 | 
            +
                                    "ticket_type", "unknown"
         | 
| 235 | 
            +
                                )
         | 
| 236 | 
            +
                                if ticket_type != type_filter:
         | 
| 237 | 
            +
                                    continue
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                            # Status filter
         | 
| 240 | 
            +
                            if status_filter != "all":
         | 
| 241 | 
            +
                                if ticket.get("status") != status_filter:
         | 
| 242 | 
            +
                                    continue
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                            filtered_tickets.append(ticket)
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                        # Apply pagination
         | 
| 247 | 
            +
                        start_idx = (page - 1) * page_size
         | 
| 248 | 
            +
                        end_idx = start_idx + page_size
         | 
| 249 | 
            +
                        tickets = filtered_tickets[start_idx:end_idx]
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                    if not tickets:
         | 
| 193 252 | 
             
                        print("No tickets found matching criteria")
         | 
| 253 | 
            +
                        if page > 1:
         | 
| 254 | 
            +
                            print(f"Try a lower page number (current: {page})")
         | 
| 194 255 | 
             
                        return 0
         | 
| 195 | 
            -
             | 
| 196 | 
            -
                     | 
| 256 | 
            +
             | 
| 257 | 
            +
                    # Display pagination info
         | 
| 258 | 
            +
                    total_shown = len(tickets)
         | 
| 259 | 
            +
                    print(f"Tickets (page {page}, showing {total_shown} tickets):")
         | 
| 197 260 | 
             
                    print("-" * 80)
         | 
| 198 | 
            -
             | 
| 199 | 
            -
                    for ticket in  | 
| 261 | 
            +
             | 
| 262 | 
            +
                    for ticket in tickets:
         | 
| 200 263 | 
             
                        # Use emoji to indicate status visually
         | 
| 201 264 | 
             
                        status_emoji = {
         | 
| 202 265 | 
             
                            "open": "🔵",
         | 
| 203 266 | 
             
                            "in_progress": "🟡",
         | 
| 204 267 | 
             
                            "done": "🟢",
         | 
| 205 268 | 
             
                            "closed": "⚫",
         | 
| 206 | 
            -
                            "blocked": "🔴"
         | 
| 207 | 
            -
                        }.get(ticket.get( | 
| 208 | 
            -
             | 
| 269 | 
            +
                            "blocked": "🔴",
         | 
| 270 | 
            +
                        }.get(ticket.get("status", "unknown"), "⚪")
         | 
| 271 | 
            +
             | 
| 209 272 | 
             
                        print(f"{status_emoji} [{ticket['id']}] {ticket['title']}")
         | 
| 210 | 
            -
             | 
| 211 | 
            -
                        if getattr(args,  | 
| 212 | 
            -
                            ticket_type = ticket.get( | 
| 213 | 
            -
                            print( | 
| 214 | 
            -
             | 
| 273 | 
            +
             | 
| 274 | 
            +
                        if getattr(args, "verbose", False):
         | 
| 275 | 
            +
                            ticket_type = ticket.get("metadata", {}).get("ticket_type", "task")
         | 
| 276 | 
            +
                            print(
         | 
| 277 | 
            +
                                f"   Type: {ticket_type} | Status: {ticket['status']} | Priority: {ticket['priority']}"
         | 
| 278 | 
            +
                            )
         | 
| 279 | 
            +
                            if ticket.get("tags"):
         | 
| 215 280 | 
             
                                print(f"   Tags: {', '.join(ticket['tags'])}")
         | 
| 216 281 | 
             
                            print(f"   Created: {ticket['created_at']}")
         | 
| 217 282 | 
             
                            print()
         | 
| 218 | 
            -
             | 
| 283 | 
            +
             | 
| 284 | 
            +
                    # Show pagination navigation hints
         | 
| 285 | 
            +
                    if total_shown == page_size:
         | 
| 286 | 
            +
                        print("-" * 80)
         | 
| 287 | 
            +
                        print(f"📄 Page {page} | Showing {total_shown} tickets")
         | 
| 288 | 
            +
                        print(
         | 
| 289 | 
            +
                            f"💡 Next page: claude-mpm tickets list --page {page + 1} --page-size {page_size}"
         | 
| 290 | 
            +
                        )
         | 
| 291 | 
            +
                        if page > 1:
         | 
| 292 | 
            +
                            print(
         | 
| 293 | 
            +
                                f"💡 Previous page: claude-mpm tickets list --page {page - 1} --page-size {page_size}"
         | 
| 294 | 
            +
                            )
         | 
| 295 | 
            +
             | 
| 219 296 | 
             
                    return 0
         | 
| 220 | 
            -
             | 
| 297 | 
            +
             | 
| 221 298 | 
             
                except ImportError:
         | 
| 222 299 | 
             
                    logger.error("ai-trackdown-pytools not installed")
         | 
| 223 300 | 
             
                    print("Error: ai-trackdown-pytools not installed")
         | 
| @@ -232,126 +309,141 @@ def list_tickets(args): | |
| 232 309 | 
             
            def view_ticket(args):
         | 
| 233 310 | 
             
                """
         | 
| 234 311 | 
             
                View a specific ticket in detail.
         | 
| 235 | 
            -
             | 
| 312 | 
            +
             | 
| 236 313 | 
             
                WHY: Users need to see full ticket details including description, metadata,
         | 
| 237 314 | 
             
                and all associated information for understanding context and status.
         | 
| 238 | 
            -
             | 
| 315 | 
            +
             | 
| 239 316 | 
             
                Args:
         | 
| 240 317 | 
             
                    args: Arguments with ticket id and verbose flag
         | 
| 241 | 
            -
             | 
| 318 | 
            +
             | 
| 242 319 | 
             
                Returns:
         | 
| 243 320 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 244 321 | 
             
                """
         | 
| 245 322 | 
             
                logger = get_logger("cli.tickets")
         | 
| 246 | 
            -
             | 
| 323 | 
            +
             | 
| 247 324 | 
             
                try:
         | 
| 248 325 | 
             
                    from ...services.ticket_manager import TicketManager
         | 
| 249 326 | 
             
                except ImportError:
         | 
| 250 327 | 
             
                    from claude_mpm.services.ticket_manager import TicketManager
         | 
| 251 | 
            -
             | 
| 328 | 
            +
             | 
| 252 329 | 
             
                ticket_manager = TicketManager()
         | 
| 253 | 
            -
                 | 
| 254 | 
            -
                
         | 
| 330 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 331 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 332 | 
            +
                if not ticket_id:
         | 
| 333 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 334 | 
            +
                    return 1
         | 
| 335 | 
            +
                ticket = ticket_manager.get_ticket(ticket_id)
         | 
| 336 | 
            +
             | 
| 255 337 | 
             
                if not ticket:
         | 
| 256 | 
            -
                    print(f"❌ Ticket { | 
| 338 | 
            +
                    print(f"❌ Ticket {ticket_id} not found")
         | 
| 257 339 | 
             
                    return 1
         | 
| 258 | 
            -
             | 
| 340 | 
            +
             | 
| 259 341 | 
             
                print(f"Ticket: {ticket['id']}")
         | 
| 260 342 | 
             
                print("=" * 80)
         | 
| 261 343 | 
             
                print(f"Title: {ticket['title']}")
         | 
| 262 344 | 
             
                print(f"Type: {ticket.get('metadata', {}).get('ticket_type', 'unknown')}")
         | 
| 263 345 | 
             
                print(f"Status: {ticket['status']}")
         | 
| 264 346 | 
             
                print(f"Priority: {ticket['priority']}")
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                if ticket.get( | 
| 347 | 
            +
             | 
| 348 | 
            +
                if ticket.get("tags"):
         | 
| 267 349 | 
             
                    print(f"Tags: {', '.join(ticket['tags'])}")
         | 
| 268 | 
            -
             | 
| 269 | 
            -
                if ticket.get( | 
| 350 | 
            +
             | 
| 351 | 
            +
                if ticket.get("assignees"):
         | 
| 270 352 | 
             
                    print(f"Assignees: {', '.join(ticket['assignees'])}")
         | 
| 271 | 
            -
             | 
| 353 | 
            +
             | 
| 272 354 | 
             
                # Show parent references if they exist
         | 
| 273 | 
            -
                metadata = ticket.get( | 
| 274 | 
            -
                if metadata.get( | 
| 355 | 
            +
                metadata = ticket.get("metadata", {})
         | 
| 356 | 
            +
                if metadata.get("parent_epic"):
         | 
| 275 357 | 
             
                    print(f"Parent Epic: {metadata['parent_epic']}")
         | 
| 276 | 
            -
                if metadata.get( | 
| 358 | 
            +
                if metadata.get("parent_issue"):
         | 
| 277 359 | 
             
                    print(f"Parent Issue: {metadata['parent_issue']}")
         | 
| 278 | 
            -
             | 
| 360 | 
            +
             | 
| 279 361 | 
             
                print(f"\nDescription:")
         | 
| 280 362 | 
             
                print("-" * 40)
         | 
| 281 | 
            -
                print(ticket.get( | 
| 282 | 
            -
             | 
| 363 | 
            +
                print(ticket.get("description", "No description"))
         | 
| 364 | 
            +
             | 
| 283 365 | 
             
                print(f"\nCreated: {ticket['created_at']}")
         | 
| 284 366 | 
             
                print(f"Updated: {ticket['updated_at']}")
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                if args.verbose and ticket.get( | 
| 367 | 
            +
             | 
| 368 | 
            +
                if args.verbose and ticket.get("metadata"):
         | 
| 287 369 | 
             
                    print(f"\nMetadata:")
         | 
| 288 370 | 
             
                    print("-" * 40)
         | 
| 289 | 
            -
                    for key, value in ticket[ | 
| 290 | 
            -
                        if key not in [ | 
| 371 | 
            +
                    for key, value in ticket["metadata"].items():
         | 
| 372 | 
            +
                        if key not in [
         | 
| 373 | 
            +
                            "parent_epic",
         | 
| 374 | 
            +
                            "parent_issue",
         | 
| 375 | 
            +
                            "ticket_type",
         | 
| 376 | 
            +
                        ]:  # Already shown above
         | 
| 291 377 | 
             
                            print(f"  {key}: {value}")
         | 
| 292 | 
            -
             | 
| 378 | 
            +
             | 
| 293 379 | 
             
                return 0
         | 
| 294 380 |  | 
| 295 381 |  | 
| 296 382 | 
             
            def update_ticket(args):
         | 
| 297 383 | 
             
                """
         | 
| 298 384 | 
             
                Update a ticket's properties.
         | 
| 299 | 
            -
             | 
| 385 | 
            +
             | 
| 300 386 | 
             
                WHY: Tickets need to be updated as work progresses, priorities change,
         | 
| 301 387 | 
             
                or additional information becomes available.
         | 
| 302 | 
            -
             | 
| 388 | 
            +
             | 
| 303 389 | 
             
                DESIGN DECISION: For complex updates, we delegate to aitrackdown CLI
         | 
| 304 390 | 
             
                for operations not directly supported by our TicketManager interface.
         | 
| 305 | 
            -
             | 
| 391 | 
            +
             | 
| 306 392 | 
             
                Args:
         | 
| 307 393 | 
             
                    args: Arguments with ticket id and update fields
         | 
| 308 | 
            -
             | 
| 394 | 
            +
             | 
| 309 395 | 
             
                Returns:
         | 
| 310 396 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 311 397 | 
             
                """
         | 
| 312 398 | 
             
                logger = get_logger("cli.tickets")
         | 
| 313 | 
            -
             | 
| 399 | 
            +
             | 
| 314 400 | 
             
                try:
         | 
| 315 401 | 
             
                    from ...services.ticket_manager import TicketManager
         | 
| 316 402 | 
             
                except ImportError:
         | 
| 317 403 | 
             
                    from claude_mpm.services.ticket_manager import TicketManager
         | 
| 318 | 
            -
             | 
| 404 | 
            +
             | 
| 319 405 | 
             
                ticket_manager = TicketManager()
         | 
| 320 | 
            -
             | 
| 406 | 
            +
             | 
| 407 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 408 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 409 | 
            +
                if not ticket_id:
         | 
| 410 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 411 | 
            +
                    return 1
         | 
| 412 | 
            +
             | 
| 321 413 | 
             
                # Build update dictionary
         | 
| 322 414 | 
             
                updates = {}
         | 
| 323 | 
            -
             | 
| 415 | 
            +
             | 
| 324 416 | 
             
                if args.status:
         | 
| 325 | 
            -
                    updates[ | 
| 326 | 
            -
             | 
| 417 | 
            +
                    updates["status"] = args.status
         | 
| 418 | 
            +
             | 
| 327 419 | 
             
                if args.priority:
         | 
| 328 | 
            -
                    updates[ | 
| 329 | 
            -
             | 
| 420 | 
            +
                    updates["priority"] = args.priority
         | 
| 421 | 
            +
             | 
| 330 422 | 
             
                if args.description:
         | 
| 331 | 
            -
                    updates[ | 
| 332 | 
            -
             | 
| 423 | 
            +
                    updates["description"] = " ".join(args.description)
         | 
| 424 | 
            +
             | 
| 333 425 | 
             
                if args.tags:
         | 
| 334 | 
            -
                    updates[ | 
| 335 | 
            -
             | 
| 426 | 
            +
                    updates["tags"] = args.tags.split(",")
         | 
| 427 | 
            +
             | 
| 336 428 | 
             
                if args.assign:
         | 
| 337 | 
            -
                    updates[ | 
| 338 | 
            -
             | 
| 429 | 
            +
                    updates["assignees"] = [args.assign]
         | 
| 430 | 
            +
             | 
| 339 431 | 
             
                if not updates:
         | 
| 340 432 | 
             
                    print("❌ No updates specified")
         | 
| 341 433 | 
             
                    return 1
         | 
| 342 | 
            -
             | 
| 434 | 
            +
             | 
| 343 435 | 
             
                # Try to update using TicketManager
         | 
| 344 | 
            -
                success = ticket_manager.update_task( | 
| 345 | 
            -
             | 
| 436 | 
            +
                success = ticket_manager.update_task(ticket_id, **updates)
         | 
| 437 | 
            +
             | 
| 346 438 | 
             
                if success:
         | 
| 347 | 
            -
                    print(f"✅ Updated ticket: { | 
| 439 | 
            +
                    print(f"✅ Updated ticket: {ticket_id}")
         | 
| 348 440 | 
             
                    return 0
         | 
| 349 441 | 
             
                else:
         | 
| 350 442 | 
             
                    # Fallback to aitrackdown CLI for status transitions
         | 
| 351 443 | 
             
                    if args.status:
         | 
| 352 444 | 
             
                        logger.info("Attempting update via aitrackdown CLI")
         | 
| 353 | 
            -
                        cmd = ["aitrackdown", "transition",  | 
| 354 | 
            -
             | 
| 445 | 
            +
                        cmd = ["aitrackdown", "transition", ticket_id, args.status]
         | 
| 446 | 
            +
             | 
| 355 447 | 
             
                        # Add comment with other updates
         | 
| 356 448 | 
             
                        comment_parts = []
         | 
| 357 449 | 
             
                        if args.priority:
         | 
| @@ -360,180 +452,219 @@ def update_ticket(args): | |
| 360 452 | 
             
                            comment_parts.append(f"Assigned to: {args.assign}")
         | 
| 361 453 | 
             
                        if args.tags:
         | 
| 362 454 | 
             
                            comment_parts.append(f"Tags: {args.tags}")
         | 
| 363 | 
            -
             | 
| 455 | 
            +
             | 
| 364 456 | 
             
                        if comment_parts:
         | 
| 365 457 | 
             
                            comment = " | ".join(comment_parts)
         | 
| 366 458 | 
             
                            cmd.extend(["--comment", comment])
         | 
| 367 | 
            -
             | 
| 459 | 
            +
             | 
| 368 460 | 
             
                        try:
         | 
| 369 461 | 
             
                            subprocess.run(cmd, check=True, capture_output=True, text=True)
         | 
| 370 | 
            -
                            print(f"✅ Updated ticket: { | 
| 462 | 
            +
                            print(f"✅ Updated ticket: {ticket_id}")
         | 
| 371 463 | 
             
                            return 0
         | 
| 372 464 | 
             
                        except subprocess.CalledProcessError as e:
         | 
| 373 465 | 
             
                            logger.error(f"Failed to update via CLI: {e}")
         | 
| 374 | 
            -
                            print(f"❌ Failed to update ticket: { | 
| 466 | 
            +
                            print(f"❌ Failed to update ticket: {ticket_id}")
         | 
| 375 467 | 
             
                            return 1
         | 
| 376 468 | 
             
                    else:
         | 
| 377 | 
            -
                        print(f"❌ Failed to update ticket: { | 
| 469 | 
            +
                        print(f"❌ Failed to update ticket: {ticket_id}")
         | 
| 378 470 | 
             
                        return 1
         | 
| 379 471 |  | 
| 380 472 |  | 
| 381 473 | 
             
            def close_ticket(args):
         | 
| 382 474 | 
             
                """
         | 
| 383 475 | 
             
                Close a ticket.
         | 
| 384 | 
            -
             | 
| 476 | 
            +
             | 
| 385 477 | 
             
                WHY: Tickets need to be closed when work is completed or no longer relevant.
         | 
| 386 | 
            -
             | 
| 478 | 
            +
             | 
| 387 479 | 
             
                Args:
         | 
| 388 480 | 
             
                    args: Arguments with ticket id and optional resolution
         | 
| 389 | 
            -
             | 
| 481 | 
            +
             | 
| 390 482 | 
             
                Returns:
         | 
| 391 483 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 392 484 | 
             
                """
         | 
| 393 485 | 
             
                logger = get_logger("cli.tickets")
         | 
| 394 | 
            -
             | 
| 486 | 
            +
             | 
| 395 487 | 
             
                try:
         | 
| 396 488 | 
             
                    from ...services.ticket_manager import TicketManager
         | 
| 397 489 | 
             
                except ImportError:
         | 
| 398 490 | 
             
                    from claude_mpm.services.ticket_manager import TicketManager
         | 
| 399 | 
            -
             | 
| 491 | 
            +
             | 
| 400 492 | 
             
                ticket_manager = TicketManager()
         | 
| 401 | 
            -
             | 
| 493 | 
            +
             | 
| 494 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 495 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 496 | 
            +
                if not ticket_id:
         | 
| 497 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 498 | 
            +
                    return 1
         | 
| 499 | 
            +
             | 
| 402 500 | 
             
                # Try to close using TicketManager
         | 
| 403 | 
            -
                resolution = getattr(args,  | 
| 404 | 
            -
                success = ticket_manager.close_task( | 
| 405 | 
            -
             | 
| 501 | 
            +
                resolution = getattr(args, "resolution", getattr(args, "comment", None))
         | 
| 502 | 
            +
                success = ticket_manager.close_task(ticket_id, resolution=resolution)
         | 
| 503 | 
            +
             | 
| 406 504 | 
             
                if success:
         | 
| 407 | 
            -
                    print(f"✅ Closed ticket: { | 
| 505 | 
            +
                    print(f"✅ Closed ticket: {ticket_id}")
         | 
| 408 506 | 
             
                    return 0
         | 
| 409 507 | 
             
                else:
         | 
| 410 508 | 
             
                    # Fallback to aitrackdown CLI
         | 
| 411 509 | 
             
                    logger.info("Attempting close via aitrackdown CLI")
         | 
| 412 | 
            -
                    cmd = ["aitrackdown", "close",  | 
| 413 | 
            -
             | 
| 510 | 
            +
                    cmd = ["aitrackdown", "close", ticket_id]
         | 
| 511 | 
            +
             | 
| 414 512 | 
             
                    if resolution:
         | 
| 415 513 | 
             
                        cmd.extend(["--comment", resolution])
         | 
| 416 | 
            -
             | 
| 514 | 
            +
             | 
| 417 515 | 
             
                    try:
         | 
| 418 516 | 
             
                        subprocess.run(cmd, check=True, capture_output=True, text=True)
         | 
| 419 | 
            -
                        print(f"✅ Closed ticket: { | 
| 517 | 
            +
                        print(f"✅ Closed ticket: {ticket_id}")
         | 
| 420 518 | 
             
                        return 0
         | 
| 421 519 | 
             
                    except subprocess.CalledProcessError:
         | 
| 422 | 
            -
                        print(f"❌ Failed to close ticket: { | 
| 520 | 
            +
                        print(f"❌ Failed to close ticket: {ticket_id}")
         | 
| 423 521 | 
             
                        return 1
         | 
| 424 522 |  | 
| 425 523 |  | 
| 426 524 | 
             
            def delete_ticket(args):
         | 
| 427 525 | 
             
                """
         | 
| 428 526 | 
             
                Delete a ticket.
         | 
| 429 | 
            -
             | 
| 527 | 
            +
             | 
| 430 528 | 
             
                WHY: Sometimes tickets are created in error or are no longer needed
         | 
| 431 529 | 
             
                and should be removed from the system.
         | 
| 432 | 
            -
             | 
| 530 | 
            +
             | 
| 433 531 | 
             
                DESIGN DECISION: We delegate to aitrackdown CLI as deletion is a
         | 
| 434 532 | 
             
                destructive operation that should use the official tool.
         | 
| 435 | 
            -
             | 
| 533 | 
            +
             | 
| 436 534 | 
             
                Args:
         | 
| 437 535 | 
             
                    args: Arguments with ticket id and force flag
         | 
| 438 | 
            -
             | 
| 536 | 
            +
             | 
| 439 537 | 
             
                Returns:
         | 
| 440 538 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 441 539 | 
             
                """
         | 
| 442 540 | 
             
                logger = get_logger("cli.tickets")
         | 
| 443 | 
            -
             | 
| 541 | 
            +
             | 
| 542 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 543 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 544 | 
            +
                if not ticket_id:
         | 
| 545 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 546 | 
            +
                    return 1
         | 
| 547 | 
            +
             | 
| 444 548 | 
             
                # Confirm deletion unless forced
         | 
| 445 549 | 
             
                if not args.force:
         | 
| 446 | 
            -
                     | 
| 447 | 
            -
             | 
| 550 | 
            +
                    sys.stdout.flush()  # Ensure prompt is displayed before input
         | 
| 551 | 
            +
             | 
| 552 | 
            +
                    # Check if we're in a TTY environment for proper input handling
         | 
| 553 | 
            +
                    if not sys.stdin.isatty():
         | 
| 554 | 
            +
                        # In non-TTY environment (like pipes), use readline
         | 
| 555 | 
            +
                        print(
         | 
| 556 | 
            +
                            f"Are you sure you want to delete ticket {ticket_id}? (y/N): ",
         | 
| 557 | 
            +
                            end="",
         | 
| 558 | 
            +
                            flush=True,
         | 
| 559 | 
            +
                        )
         | 
| 560 | 
            +
                        try:
         | 
| 561 | 
            +
                            response = sys.stdin.readline().strip().lower()
         | 
| 562 | 
            +
                            # Handle various line endings and control characters
         | 
| 563 | 
            +
                            response = response.replace("\r", "").replace("\n", "").strip()
         | 
| 564 | 
            +
                        except (EOFError, KeyboardInterrupt):
         | 
| 565 | 
            +
                            response = "n"
         | 
| 566 | 
            +
                    else:
         | 
| 567 | 
            +
                        # In TTY environment, use normal input()
         | 
| 568 | 
            +
                        try:
         | 
| 569 | 
            +
                            response = (
         | 
| 570 | 
            +
                                input(f"Are you sure you want to delete ticket {ticket_id}? (y/N): ")
         | 
| 571 | 
            +
                                .strip()
         | 
| 572 | 
            +
                                .lower()
         | 
| 573 | 
            +
                            )
         | 
| 574 | 
            +
                        except (EOFError, KeyboardInterrupt):
         | 
| 575 | 
            +
                            response = "n"
         | 
| 576 | 
            +
             | 
| 577 | 
            +
                    if response != "y":
         | 
| 448 578 | 
             
                        print("Deletion cancelled")
         | 
| 449 579 | 
             
                        return 0
         | 
| 450 | 
            -
             | 
| 580 | 
            +
             | 
| 451 581 | 
             
                # Use aitrackdown CLI for deletion
         | 
| 452 | 
            -
                cmd = ["aitrackdown", "delete",  | 
| 582 | 
            +
                cmd = ["aitrackdown", "delete", ticket_id]
         | 
| 453 583 | 
             
                if args.force:
         | 
| 454 584 | 
             
                    cmd.append("--force")
         | 
| 455 | 
            -
             | 
| 585 | 
            +
             | 
| 456 586 | 
             
                try:
         | 
| 457 587 | 
             
                    subprocess.run(cmd, check=True, capture_output=True, text=True)
         | 
| 458 | 
            -
                    print(f"✅ Deleted ticket: { | 
| 588 | 
            +
                    print(f"✅ Deleted ticket: {ticket_id}")
         | 
| 459 589 | 
             
                    return 0
         | 
| 460 590 | 
             
                except subprocess.CalledProcessError:
         | 
| 461 | 
            -
                    print(f"❌ Failed to delete ticket: { | 
| 591 | 
            +
                    print(f"❌ Failed to delete ticket: {ticket_id}")
         | 
| 462 592 | 
             
                    return 1
         | 
| 463 593 |  | 
| 464 594 |  | 
| 465 595 | 
             
            def search_tickets(args):
         | 
| 466 596 | 
             
                """
         | 
| 467 597 | 
             
                Search tickets by query string.
         | 
| 468 | 
            -
             | 
| 598 | 
            +
             | 
| 469 599 | 
             
                WHY: Users need to find specific tickets based on content, tags, or other criteria.
         | 
| 470 | 
            -
             | 
| 600 | 
            +
             | 
| 471 601 | 
             
                DESIGN DECISION: We perform simple text matching on ticket data. For more advanced
         | 
| 472 602 | 
             
                search, users should use the aitrackdown CLI directly.
         | 
| 473 | 
            -
             | 
| 603 | 
            +
             | 
| 474 604 | 
             
                Args:
         | 
| 475 605 | 
             
                    args: Arguments with search query and filters
         | 
| 476 | 
            -
             | 
| 606 | 
            +
             | 
| 477 607 | 
             
                Returns:
         | 
| 478 608 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 479 609 | 
             
                """
         | 
| 480 610 | 
             
                logger = get_logger("cli.tickets")
         | 
| 481 | 
            -
             | 
| 611 | 
            +
             | 
| 482 612 | 
             
                try:
         | 
| 483 613 | 
             
                    from ...services.ticket_manager import TicketManager
         | 
| 484 614 | 
             
                except ImportError:
         | 
| 485 615 | 
             
                    from claude_mpm.services.ticket_manager import TicketManager
         | 
| 486 | 
            -
             | 
| 616 | 
            +
             | 
| 487 617 | 
             
                ticket_manager = TicketManager()
         | 
| 488 | 
            -
             | 
| 618 | 
            +
             | 
| 489 619 | 
             
                # Get all available tickets for searching
         | 
| 490 620 | 
             
                all_tickets = ticket_manager.list_recent_tickets(limit=100)
         | 
| 491 | 
            -
             | 
| 621 | 
            +
             | 
| 492 622 | 
             
                # Search tickets
         | 
| 493 623 | 
             
                query = args.query.lower()
         | 
| 494 624 | 
             
                matched_tickets = []
         | 
| 495 | 
            -
             | 
| 625 | 
            +
             | 
| 496 626 | 
             
                for ticket in all_tickets:
         | 
| 497 627 | 
             
                    # Check if query matches title, description, or tags
         | 
| 498 | 
            -
                    if ( | 
| 499 | 
            -
                        query in ticket.get( | 
| 500 | 
            -
                         | 
| 501 | 
            -
                        
         | 
| 628 | 
            +
                    if (
         | 
| 629 | 
            +
                        query in ticket.get("title", "").lower()
         | 
| 630 | 
            +
                        or query in ticket.get("description", "").lower()
         | 
| 631 | 
            +
                        or any(query in tag.lower() for tag in ticket.get("tags", []))
         | 
| 632 | 
            +
                    ):
         | 
| 502 633 | 
             
                        # Apply type filter
         | 
| 503 | 
            -
                        if args.type !=  | 
| 504 | 
            -
                            ticket_type = ticket.get( | 
| 634 | 
            +
                        if args.type != "all":
         | 
| 635 | 
            +
                            ticket_type = ticket.get("metadata", {}).get("ticket_type", "unknown")
         | 
| 505 636 | 
             
                            if ticket_type != args.type:
         | 
| 506 637 | 
             
                                continue
         | 
| 507 | 
            -
             | 
| 638 | 
            +
             | 
| 508 639 | 
             
                        # Apply status filter
         | 
| 509 | 
            -
                        if args.status !=  | 
| 510 | 
            -
                            if ticket.get( | 
| 640 | 
            +
                        if args.status != "all":
         | 
| 641 | 
            +
                            if ticket.get("status") != args.status:
         | 
| 511 642 | 
             
                                continue
         | 
| 512 | 
            -
             | 
| 643 | 
            +
             | 
| 513 644 | 
             
                        matched_tickets.append(ticket)
         | 
| 514 645 | 
             
                        if len(matched_tickets) >= args.limit:
         | 
| 515 646 | 
             
                            break
         | 
| 516 | 
            -
             | 
| 647 | 
            +
             | 
| 517 648 | 
             
                if not matched_tickets:
         | 
| 518 649 | 
             
                    print(f"No tickets found matching '{args.query}'")
         | 
| 519 650 | 
             
                    return 0
         | 
| 520 | 
            -
             | 
| 651 | 
            +
             | 
| 521 652 | 
             
                print(f"Search results for '{args.query}' (showing {len(matched_tickets)}):")
         | 
| 522 653 | 
             
                print("-" * 80)
         | 
| 523 | 
            -
             | 
| 654 | 
            +
             | 
| 524 655 | 
             
                for ticket in matched_tickets:
         | 
| 525 656 | 
             
                    status_emoji = {
         | 
| 526 657 | 
             
                        "open": "🔵",
         | 
| 527 658 | 
             
                        "in_progress": "🟡",
         | 
| 528 659 | 
             
                        "done": "🟢",
         | 
| 529 660 | 
             
                        "closed": "⚫",
         | 
| 530 | 
            -
                        "blocked": "🔴"
         | 
| 531 | 
            -
                    }.get(ticket.get( | 
| 532 | 
            -
             | 
| 661 | 
            +
                        "blocked": "🔴",
         | 
| 662 | 
            +
                    }.get(ticket.get("status", "unknown"), "⚪")
         | 
| 663 | 
            +
             | 
| 533 664 | 
             
                    print(f"{status_emoji} [{ticket['id']}] {ticket['title']}")
         | 
| 534 | 
            -
             | 
| 665 | 
            +
             | 
| 535 666 | 
             
                    # Show snippet of description if it contains the query
         | 
| 536 | 
            -
                    desc = ticket.get( | 
| 667 | 
            +
                    desc = ticket.get("description", "")
         | 
| 537 668 | 
             
                    if query in desc.lower():
         | 
| 538 669 | 
             
                        # Find and show context around the match
         | 
| 539 670 | 
             
                        idx = desc.lower().index(query)
         | 
| @@ -545,83 +676,95 @@ def search_tickets(args): | |
| 545 676 | 
             
                        if end < len(desc):
         | 
| 546 677 | 
             
                            snippet = snippet + "..."
         | 
| 547 678 | 
             
                        print(f"   {snippet}")
         | 
| 548 | 
            -
             | 
| 679 | 
            +
             | 
| 549 680 | 
             
                return 0
         | 
| 550 681 |  | 
| 551 682 |  | 
| 552 683 | 
             
            def add_comment(args):
         | 
| 553 684 | 
             
                """
         | 
| 554 685 | 
             
                Add a comment to a ticket.
         | 
| 555 | 
            -
             | 
| 686 | 
            +
             | 
| 556 687 | 
             
                WHY: Comments allow tracking progress, decisions, and additional context
         | 
| 557 688 | 
             
                on tickets over time.
         | 
| 558 | 
            -
             | 
| 689 | 
            +
             | 
| 559 690 | 
             
                DESIGN DECISION: We delegate to aitrackdown CLI as it has proper comment
         | 
| 560 691 | 
             
                tracking infrastructure.
         | 
| 561 | 
            -
             | 
| 692 | 
            +
             | 
| 562 693 | 
             
                Args:
         | 
| 563 694 | 
             
                    args: Arguments with ticket id and comment text
         | 
| 564 | 
            -
             | 
| 695 | 
            +
             | 
| 565 696 | 
             
                Returns:
         | 
| 566 697 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 567 698 | 
             
                """
         | 
| 568 699 | 
             
                logger = get_logger("cli.tickets")
         | 
| 569 | 
            -
             | 
| 700 | 
            +
             | 
| 701 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 702 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 703 | 
            +
                if not ticket_id:
         | 
| 704 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 705 | 
            +
                    return 1
         | 
| 706 | 
            +
             | 
| 570 707 | 
             
                # Join comment parts into single string
         | 
| 571 | 
            -
                comment = " ".join(args.comment)
         | 
| 572 | 
            -
             | 
| 708 | 
            +
                comment = " ".join(args.comment) if isinstance(args.comment, list) else args.comment
         | 
| 709 | 
            +
             | 
| 573 710 | 
             
                # Use aitrackdown CLI for comments
         | 
| 574 | 
            -
                cmd = ["aitrackdown", "comment",  | 
| 575 | 
            -
             | 
| 711 | 
            +
                cmd = ["aitrackdown", "comment", ticket_id, comment]
         | 
| 712 | 
            +
             | 
| 576 713 | 
             
                try:
         | 
| 577 714 | 
             
                    subprocess.run(cmd, check=True, capture_output=True, text=True)
         | 
| 578 | 
            -
                    print(f"✅ Added comment to ticket: { | 
| 715 | 
            +
                    print(f"✅ Added comment to ticket: {ticket_id}")
         | 
| 579 716 | 
             
                    return 0
         | 
| 580 717 | 
             
                except subprocess.CalledProcessError:
         | 
| 581 | 
            -
                    print(f"❌ Failed to add comment to ticket: { | 
| 718 | 
            +
                    print(f"❌ Failed to add comment to ticket: {ticket_id}")
         | 
| 582 719 | 
             
                    return 1
         | 
| 583 720 |  | 
| 584 721 |  | 
| 585 722 | 
             
            def update_workflow(args):
         | 
| 586 723 | 
             
                """
         | 
| 587 724 | 
             
                Update ticket workflow state.
         | 
| 588 | 
            -
             | 
| 725 | 
            +
             | 
| 589 726 | 
             
                WHY: Workflow states track the progress of tickets through defined stages
         | 
| 590 727 | 
             
                like todo, in_progress, ready, tested, done.
         | 
| 591 | 
            -
             | 
| 728 | 
            +
             | 
| 592 729 | 
             
                DESIGN DECISION: We use aitrackdown's transition command for workflow updates
         | 
| 593 730 | 
             
                as it maintains proper state machine transitions.
         | 
| 594 | 
            -
             | 
| 731 | 
            +
             | 
| 595 732 | 
             
                Args:
         | 
| 596 733 | 
             
                    args: Arguments with ticket id, new state, and optional comment
         | 
| 597 | 
            -
             | 
| 734 | 
            +
             | 
| 598 735 | 
             
                Returns:
         | 
| 599 736 | 
             
                    Exit code (0 for success, non-zero for errors)
         | 
| 600 737 | 
             
                """
         | 
| 601 738 | 
             
                logger = get_logger("cli.tickets")
         | 
| 602 | 
            -
             | 
| 739 | 
            +
             | 
| 740 | 
            +
                # Handle both 'id' and 'ticket_id' attributes for compatibility
         | 
| 741 | 
            +
                ticket_id = getattr(args, 'ticket_id', getattr(args, 'id', None))
         | 
| 742 | 
            +
                if not ticket_id:
         | 
| 743 | 
            +
                    print("❌ No ticket ID provided")
         | 
| 744 | 
            +
                    return 1
         | 
| 745 | 
            +
             | 
| 603 746 | 
             
                # Map workflow states to status if needed
         | 
| 604 747 | 
             
                state_mapping = {
         | 
| 605 | 
            -
                     | 
| 606 | 
            -
                     | 
| 607 | 
            -
                     | 
| 608 | 
            -
                     | 
| 609 | 
            -
                     | 
| 610 | 
            -
                     | 
| 748 | 
            +
                    "todo": "open",
         | 
| 749 | 
            +
                    "in_progress": "in_progress",
         | 
| 750 | 
            +
                    "ready": "ready",
         | 
| 751 | 
            +
                    "tested": "tested",
         | 
| 752 | 
            +
                    "done": "done",
         | 
| 753 | 
            +
                    "blocked": "blocked",
         | 
| 611 754 | 
             
                }
         | 
| 612 | 
            -
             | 
| 755 | 
            +
             | 
| 613 756 | 
             
                # Use aitrackdown transition command
         | 
| 614 | 
            -
                cmd = ["aitrackdown", "transition",  | 
| 615 | 
            -
             | 
| 616 | 
            -
                if getattr(args,  | 
| 757 | 
            +
                cmd = ["aitrackdown", "transition", ticket_id, args.state]
         | 
| 758 | 
            +
             | 
| 759 | 
            +
                if getattr(args, "comment", None):
         | 
| 617 760 | 
             
                    cmd.extend(["--comment", args.comment])
         | 
| 618 | 
            -
             | 
| 761 | 
            +
             | 
| 619 762 | 
             
                try:
         | 
| 620 763 | 
             
                    subprocess.run(cmd, check=True, capture_output=True, text=True)
         | 
| 621 | 
            -
                    print(f"✅ Updated workflow state for { | 
| 764 | 
            +
                    print(f"✅ Updated workflow state for {ticket_id} to: {args.state}")
         | 
| 622 765 | 
             
                    return 0
         | 
| 623 766 | 
             
                except subprocess.CalledProcessError:
         | 
| 624 | 
            -
                    print(f"❌ Failed to update workflow state for ticket: { | 
| 767 | 
            +
                    print(f"❌ Failed to update workflow state for ticket: {ticket_id}")
         | 
| 625 768 | 
             
                    return 1
         | 
| 626 769 |  | 
| 627 770 |  | 
| @@ -629,12 +772,12 @@ def update_workflow(args): | |
| 629 772 | 
             
            def list_tickets_legacy(args):
         | 
| 630 773 | 
             
                """
         | 
| 631 774 | 
             
                Legacy list_tickets function for backward compatibility.
         | 
| 632 | 
            -
             | 
| 775 | 
            +
             | 
| 633 776 | 
             
                WHY: The old CLI interface expected a simple list_tickets function.
         | 
| 634 777 | 
             
                This wrapper maintains that interface while using the new implementation.
         | 
| 635 | 
            -
             | 
| 778 | 
            +
             | 
| 636 779 | 
             
                Args:
         | 
| 637 780 | 
             
                    args: Parsed command line arguments with 'limit' attribute
         | 
| 638 781 | 
             
                """
         | 
| 639 782 | 
             
                # Call the new list_tickets function
         | 
| 640 | 
            -
                return list_tickets(args)
         | 
| 783 | 
            +
                return list_tickets(args)
         |