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