roadmap-cli 0.1.1__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.
- roadmap/__init__.py +27 -0
- roadmap/adapters/__init__.py +1 -0
- roadmap/adapters/base_paginated_adapter.py +117 -0
- roadmap/adapters/cli/__init__.py +249 -0
- roadmap/adapters/cli/analysis/__init__.py +5 -0
- roadmap/adapters/cli/analysis/commands.py +370 -0
- roadmap/adapters/cli/analysis/presenter.py +125 -0
- roadmap/adapters/cli/archive_operations.py +86 -0
- roadmap/adapters/cli/cli_command_helpers.py +109 -0
- roadmap/adapters/cli/cli_confirmations.py +74 -0
- roadmap/adapters/cli/cli_error_handlers.py +146 -0
- roadmap/adapters/cli/cli_validators.py +39 -0
- roadmap/adapters/cli/commands/sync_status.py +308 -0
- roadmap/adapters/cli/comment/__init__.py +5 -0
- roadmap/adapters/cli/comment/commands.py +106 -0
- roadmap/adapters/cli/config/__init__.py +5 -0
- roadmap/adapters/cli/config/commands.py +198 -0
- roadmap/adapters/cli/console_exports.py +10 -0
- roadmap/adapters/cli/core.py +26 -0
- roadmap/adapters/cli/crud/__init__.py +47 -0
- roadmap/adapters/cli/crud/base_archive.py +208 -0
- roadmap/adapters/cli/crud/base_create.py +154 -0
- roadmap/adapters/cli/crud/base_delete.py +155 -0
- roadmap/adapters/cli/crud/base_restore.py +283 -0
- roadmap/adapters/cli/crud/base_update.py +193 -0
- roadmap/adapters/cli/crud/crud_helpers.py +206 -0
- roadmap/adapters/cli/crud/crud_utils.py +152 -0
- roadmap/adapters/cli/crud/entity_builders.py +374 -0
- roadmap/adapters/cli/data/__init__.py +8 -0
- roadmap/adapters/cli/data/commands.py +149 -0
- roadmap/adapters/cli/decorators.py +208 -0
- roadmap/adapters/cli/dtos/__init__.py +155 -0
- roadmap/adapters/cli/exception_handler.py +105 -0
- roadmap/adapters/cli/git/__init__.py +8 -0
- roadmap/adapters/cli/git/commands.py +475 -0
- roadmap/adapters/cli/git/handlers/__init__.py +1 -0
- roadmap/adapters/cli/git/handlers/git_authentication_handler.py +124 -0
- roadmap/adapters/cli/git/handlers/git_branch_handler.py +238 -0
- roadmap/adapters/cli/git/handlers/git_connectivity_handler.py +122 -0
- roadmap/adapters/cli/git/handlers/git_hooks_handler.py +105 -0
- roadmap/adapters/cli/git/hooks_config.py +327 -0
- roadmap/adapters/cli/git/status_display.py +158 -0
- roadmap/adapters/cli/health/__init__.py +8 -0
- roadmap/adapters/cli/health/db_integrity.py +204 -0
- roadmap/adapters/cli/health/enhancer.py +227 -0
- roadmap/adapters/cli/health/fixer.py +295 -0
- roadmap/adapters/cli/health/fixers/__init__.py +10 -0
- roadmap/adapters/cli/health/fixers/corrupted_comments_fixer.py +231 -0
- roadmap/adapters/cli/health/fixers/data_integrity_fixer.py +137 -0
- roadmap/adapters/cli/health/fixers/duplicate_issues_fixer.py +138 -0
- roadmap/adapters/cli/health/fixers/folder_structure_fixer.py +114 -0
- roadmap/adapters/cli/health/fixers/label_normalization_fixer.py +162 -0
- roadmap/adapters/cli/health/fixers/milestone_name_normalization_fixer.py +181 -0
- roadmap/adapters/cli/health/fixers/milestone_naming_compliance_fixer.py +244 -0
- roadmap/adapters/cli/health/fixers/milestone_parse_error_fixer.py +162 -0
- roadmap/adapters/cli/health/fixers/milestone_validation_fixer.py +211 -0
- roadmap/adapters/cli/health/fixers/old_backups_fixer.py +135 -0
- roadmap/adapters/cli/health/fixers/orphaned_issues_fixer.py +252 -0
- roadmap/adapters/cli/health/formatter.py +250 -0
- roadmap/adapters/cli/health/formatters.py +427 -0
- roadmap/adapters/cli/health/scan.py +293 -0
- roadmap/adapters/cli/init/__init__.py +5 -0
- roadmap/adapters/cli/init/commands.py +587 -0
- roadmap/adapters/cli/init.py +9 -0
- roadmap/adapters/cli/issues/__init__.py +70 -0
- roadmap/adapters/cli/issues/archive.py +137 -0
- roadmap/adapters/cli/issues/archive_class.py +253 -0
- roadmap/adapters/cli/issues/block.py +39 -0
- roadmap/adapters/cli/issues/close.py +216 -0
- roadmap/adapters/cli/issues/comment.py +178 -0
- roadmap/adapters/cli/issues/create.py +162 -0
- roadmap/adapters/cli/issues/delete.py +31 -0
- roadmap/adapters/cli/issues/deps.py +161 -0
- roadmap/adapters/cli/issues/issue_status_helpers.py +111 -0
- roadmap/adapters/cli/issues/link.py +308 -0
- roadmap/adapters/cli/issues/list.py +402 -0
- roadmap/adapters/cli/issues/lookup.py +96 -0
- roadmap/adapters/cli/issues/progress.py +102 -0
- roadmap/adapters/cli/issues/restore.py +96 -0
- roadmap/adapters/cli/issues/restore_class.py +57 -0
- roadmap/adapters/cli/issues/start.py +165 -0
- roadmap/adapters/cli/issues/sync_status.py +374 -0
- roadmap/adapters/cli/issues/unblock.py +43 -0
- roadmap/adapters/cli/issues/unlink.py +104 -0
- roadmap/adapters/cli/issues/update.py +120 -0
- roadmap/adapters/cli/issues/view.py +36 -0
- roadmap/adapters/cli/layout.py +220 -0
- roadmap/adapters/cli/mappers.py +200 -0
- roadmap/adapters/cli/milestones/__init__.py +52 -0
- roadmap/adapters/cli/milestones/archive.py +125 -0
- roadmap/adapters/cli/milestones/archive_class.py +236 -0
- roadmap/adapters/cli/milestones/assign.py +62 -0
- roadmap/adapters/cli/milestones/close.py +177 -0
- roadmap/adapters/cli/milestones/create.py +131 -0
- roadmap/adapters/cli/milestones/delete.py +27 -0
- roadmap/adapters/cli/milestones/kanban.py +123 -0
- roadmap/adapters/cli/milestones/list.py +87 -0
- roadmap/adapters/cli/milestones/recalculate.py +79 -0
- roadmap/adapters/cli/milestones/restore.py +87 -0
- roadmap/adapters/cli/milestones/restore_class.py +99 -0
- roadmap/adapters/cli/milestones/update.py +61 -0
- roadmap/adapters/cli/milestones/view.py +142 -0
- roadmap/adapters/cli/output_manager.py +319 -0
- roadmap/adapters/cli/presentation/base_presenter.py +121 -0
- roadmap/adapters/cli/presentation/cleanup_presenter.py +235 -0
- roadmap/adapters/cli/presentation/core_initialization_presenter.py +277 -0
- roadmap/adapters/cli/presentation/crud_presenter.py +134 -0
- roadmap/adapters/cli/presentation/daily_summary_presenter.py +266 -0
- roadmap/adapters/cli/presentation/issue_presenter.py +258 -0
- roadmap/adapters/cli/presentation/milestone_list_presenter.py +135 -0
- roadmap/adapters/cli/presentation/milestone_presenter.py +297 -0
- roadmap/adapters/cli/presentation/project_presenter.py +233 -0
- roadmap/adapters/cli/presentation/project_status_presenter.py +186 -0
- roadmap/adapters/cli/presentation/table_builders.py +95 -0
- roadmap/adapters/cli/projects/__init__.py +42 -0
- roadmap/adapters/cli/projects/archive.py +127 -0
- roadmap/adapters/cli/projects/archive_class.py +96 -0
- roadmap/adapters/cli/projects/close.py +94 -0
- roadmap/adapters/cli/projects/create.py +50 -0
- roadmap/adapters/cli/projects/delete.py +27 -0
- roadmap/adapters/cli/projects/list.py +227 -0
- roadmap/adapters/cli/projects/restore.py +87 -0
- roadmap/adapters/cli/projects/restore_class.py +56 -0
- roadmap/adapters/cli/projects/update.py +57 -0
- roadmap/adapters/cli/projects/view.py +157 -0
- roadmap/adapters/cli/services/__init__.py +1 -0
- roadmap/adapters/cli/services/daily_summary_service.py +313 -0
- roadmap/adapters/cli/services/export_manager.py +268 -0
- roadmap/adapters/cli/services/milestone_list_service.py +266 -0
- roadmap/adapters/cli/services/project_initialization_service.py +29 -0
- roadmap/adapters/cli/services/project_status_service.py +462 -0
- roadmap/adapters/cli/services/sync_service.py +62 -0
- roadmap/adapters/cli/status.py +408 -0
- roadmap/adapters/cli/styling.py +21 -0
- roadmap/adapters/cli/sync.py +1117 -0
- roadmap/adapters/cli/sync_context.py +432 -0
- roadmap/adapters/cli/sync_handlers/__init__.py +77 -0
- roadmap/adapters/cli/sync_handlers/apply_ops.py +522 -0
- roadmap/adapters/cli/sync_handlers/baseline_ops.py +366 -0
- roadmap/adapters/cli/sync_handlers/conflict_ops.py +161 -0
- roadmap/adapters/cli/sync_handlers/dry_run_display.py +237 -0
- roadmap/adapters/cli/sync_handlers/interactive_resolver.py +396 -0
- roadmap/adapters/cli/sync_handlers/progress_tracker.py +128 -0
- roadmap/adapters/cli/sync_metrics_command.py +190 -0
- roadmap/adapters/cli/sync_presenter.py +58 -0
- roadmap/adapters/cli/sync_validation.py +295 -0
- roadmap/adapters/cli/today.py +52 -0
- roadmap/adapters/cli/utils/click_options.py +38 -0
- roadmap/adapters/git/__init__.py +1 -0
- roadmap/adapters/git/git.py +208 -0
- roadmap/adapters/git/git_branch_manager.py +400 -0
- roadmap/adapters/git/git_command_executor.py +56 -0
- roadmap/adapters/git/git_commit_analyzer.py +334 -0
- roadmap/adapters/git/git_hooks.py +22 -0
- roadmap/adapters/git/git_hooks_manager.py +455 -0
- roadmap/adapters/git/git_repository_info.py +65 -0
- roadmap/adapters/git/hook_installer.py +106 -0
- roadmap/adapters/git/hook_registry.py +97 -0
- roadmap/adapters/git/hook_script_generator.py +58 -0
- roadmap/adapters/git/sync_monitor.py +409 -0
- roadmap/adapters/git/workflow_automation.py +286 -0
- roadmap/adapters/github/__init__.py +1 -0
- roadmap/adapters/github/github.py +360 -0
- roadmap/adapters/github/handlers/__init__.py +17 -0
- roadmap/adapters/github/handlers/base.py +337 -0
- roadmap/adapters/github/handlers/collaborators.py +165 -0
- roadmap/adapters/github/handlers/comments.py +118 -0
- roadmap/adapters/github/handlers/issues.py +150 -0
- roadmap/adapters/github/handlers/labels.py +164 -0
- roadmap/adapters/github/handlers/milestones.py +104 -0
- roadmap/adapters/persistence/__init__.py +35 -0
- roadmap/adapters/persistence/conflict_resolver.py +190 -0
- roadmap/adapters/persistence/database_manager.py +492 -0
- roadmap/adapters/persistence/entity_sync_coordinators.py +672 -0
- roadmap/adapters/persistence/file_locking.py +348 -0
- roadmap/adapters/persistence/file_parser.py +92 -0
- roadmap/adapters/persistence/file_synchronizer.py +107 -0
- roadmap/adapters/persistence/focused_managers.py +140 -0
- roadmap/adapters/persistence/git_history.py +330 -0
- roadmap/adapters/persistence/parser/__init__.py +20 -0
- roadmap/adapters/persistence/parser/frontmatter.py +152 -0
- roadmap/adapters/persistence/parser/issue.py +245 -0
- roadmap/adapters/persistence/parser/milestone.py +146 -0
- roadmap/adapters/persistence/parser/project.py +106 -0
- roadmap/adapters/persistence/persistence.py +288 -0
- roadmap/adapters/persistence/repositories/__init__.py +15 -0
- roadmap/adapters/persistence/repositories/issue_repository.py +223 -0
- roadmap/adapters/persistence/repositories/milestone_repository.py +140 -0
- roadmap/adapters/persistence/repositories/project_repository.py +168 -0
- roadmap/adapters/persistence/repositories/remote_link_repository.py +355 -0
- roadmap/adapters/persistence/repositories/sync_state_repository.py +66 -0
- roadmap/adapters/persistence/storage/__init__.py +36 -0
- roadmap/adapters/persistence/storage/conflicts.py +101 -0
- roadmap/adapters/persistence/storage/connection_manager.py +79 -0
- roadmap/adapters/persistence/storage/issue_storage.py +89 -0
- roadmap/adapters/persistence/storage/milestone_storage.py +80 -0
- roadmap/adapters/persistence/storage/project_storage.py +102 -0
- roadmap/adapters/persistence/storage/queries.py +227 -0
- roadmap/adapters/persistence/storage/state_manager.py +652 -0
- roadmap/adapters/persistence/storage/sync_state_storage.py +207 -0
- roadmap/adapters/persistence/sync_metrics_repository.py +342 -0
- roadmap/adapters/persistence/sync_orchestrator.py +293 -0
- roadmap/adapters/persistence/sync_state_tracker.py +86 -0
- roadmap/adapters/persistence/yaml_repositories.py +711 -0
- roadmap/adapters/sync/__init__.py +21 -0
- roadmap/adapters/sync/backend_factory.py +108 -0
- roadmap/adapters/sync/backends/__init__.py +15 -0
- roadmap/adapters/sync/backends/converters.py +209 -0
- roadmap/adapters/sync/backends/github_backend_helpers.py +380 -0
- roadmap/adapters/sync/backends/github_client.py +99 -0
- roadmap/adapters/sync/backends/github_sync_backend.py +792 -0
- roadmap/adapters/sync/backends/github_sync_ops.py +908 -0
- roadmap/adapters/sync/backends/services/__init__.py +1 -0
- roadmap/adapters/sync/backends/services/github_authentication_service.py +105 -0
- roadmap/adapters/sync/backends/services/github_issue_delete_service.py +423 -0
- roadmap/adapters/sync/backends/services/github_issue_dependency_service.py +63 -0
- roadmap/adapters/sync/backends/services/github_issue_fetch_service.py +158 -0
- roadmap/adapters/sync/backends/services/github_label_sync_service.py +126 -0
- roadmap/adapters/sync/backends/services/github_local_issue_sync_service.py +229 -0
- roadmap/adapters/sync/backends/services/github_milestone_fetch_service.py +204 -0
- roadmap/adapters/sync/backends/vanilla_git_sync_backend.py +198 -0
- roadmap/adapters/sync/services/__init__.py +11 -0
- roadmap/adapters/sync/services/baseline_state_handler.py +83 -0
- roadmap/adapters/sync/services/conflict_converter.py +85 -0
- roadmap/adapters/sync/services/issue_persistence_service.py +263 -0
- roadmap/adapters/sync/services/issue_state_service.py +243 -0
- roadmap/adapters/sync/services/local_change_filter.py +129 -0
- roadmap/adapters/sync/services/pull_result_processor.py +71 -0
- roadmap/adapters/sync/services/remote_issue_creation_service.py +112 -0
- roadmap/adapters/sync/services/sync_analysis_service.py +250 -0
- roadmap/adapters/sync/services/sync_authentication_service.py +57 -0
- roadmap/adapters/sync/services/sync_data_fetch_service.py +212 -0
- roadmap/adapters/sync/services/sync_linking_service.py +329 -0
- roadmap/adapters/sync/services/sync_report_service.py +62 -0
- roadmap/adapters/sync/services/sync_state_update_service.py +51 -0
- roadmap/adapters/sync/sync_cache_orchestrator.py +454 -0
- roadmap/adapters/sync/sync_merge_engine.py +416 -0
- roadmap/adapters/sync/sync_merge_orchestrator.py +1313 -0
- roadmap/adapters/sync/sync_retrieval_orchestrator.py +810 -0
- roadmap/application/services/__init__.py +1 -0
- roadmap/application/services/deduplicate_service.py +517 -0
- roadmap/application/use_cases/__init__.py +1 -0
- roadmap/common/__init__.py +308 -0
- roadmap/common/cache.py +209 -0
- roadmap/common/cli_errors.py +147 -0
- roadmap/common/configuration/__init__.py +33 -0
- roadmap/common/configuration/config_loader.py +264 -0
- roadmap/common/configuration/config_manager.py +164 -0
- roadmap/common/configuration/config_schema.py +145 -0
- roadmap/common/configuration/github/__init__.py +9 -0
- roadmap/common/configuration/github/config_manager.py +154 -0
- roadmap/common/configuration/github/token_resolver.py +84 -0
- roadmap/common/console.py +184 -0
- roadmap/common/constants.py +134 -0
- roadmap/common/datetime_parser.py +319 -0
- roadmap/common/error_formatter.py +84 -0
- roadmap/common/errors/__init__.py +143 -0
- roadmap/common/errors/error_base.py +73 -0
- roadmap/common/errors/error_file.py +82 -0
- roadmap/common/errors/error_git.py +67 -0
- roadmap/common/errors/error_handler.py +166 -0
- roadmap/common/errors/error_network.py +90 -0
- roadmap/common/errors/error_security.py +78 -0
- roadmap/common/errors/error_standards.py +561 -0
- roadmap/common/errors/error_validation.py +111 -0
- roadmap/common/errors/exceptions.py +432 -0
- roadmap/common/formatters/__init__.py +29 -0
- roadmap/common/formatters/base_table_formatter.py +96 -0
- roadmap/common/formatters/export/__init__.py +7 -0
- roadmap/common/formatters/export/issue_exporter.py +97 -0
- roadmap/common/formatters/helpers.py +28 -0
- roadmap/common/formatters/kanban/__init__.py +9 -0
- roadmap/common/formatters/kanban/layout.py +48 -0
- roadmap/common/formatters/kanban/organizer.py +81 -0
- roadmap/common/formatters/output/__init__.py +15 -0
- roadmap/common/formatters/tables/__init__.py +11 -0
- roadmap/common/formatters/tables/column_factory.py +312 -0
- roadmap/common/formatters/tables/issue_table.py +276 -0
- roadmap/common/formatters/tables/milestone_table.py +247 -0
- roadmap/common/formatters/tables/project_table.py +195 -0
- roadmap/common/formatters/text/__init__.py +57 -0
- roadmap/common/formatters/text/basic.py +217 -0
- roadmap/common/formatters/text/display.py +42 -0
- roadmap/common/formatters/text/duration.py +36 -0
- roadmap/common/formatters/text/operations.py +325 -0
- roadmap/common/formatters/text/status_badges.py +57 -0
- roadmap/common/initialization/github/__init__.py +11 -0
- roadmap/common/initialization/github/setup_service.py +238 -0
- roadmap/common/initialization/github/setup_validator.py +78 -0
- roadmap/common/logging/__init__.py +113 -0
- roadmap/common/logging/audit_logging.py +5 -0
- roadmap/common/logging/correlation.py +45 -0
- roadmap/common/logging/decorators.py +267 -0
- roadmap/common/logging/error_logging.py +217 -0
- roadmap/common/logging/formatters.py +124 -0
- roadmap/common/logging/loggers.py +68 -0
- roadmap/common/logging/performance_tracking.py +267 -0
- roadmap/common/logging/utils.py +335 -0
- roadmap/common/models/__init__.py +44 -0
- roadmap/common/models/cli_models.py +118 -0
- roadmap/common/models/config_models.py +199 -0
- roadmap/common/models/output_models.py +381 -0
- roadmap/common/observability/__init__.py +28 -0
- roadmap/common/observability/instrumentation.py +103 -0
- roadmap/common/observability/observability.py +76 -0
- roadmap/common/observability/otel_init.py +109 -0
- roadmap/common/output_formatter.py +393 -0
- roadmap/common/progress.py +379 -0
- roadmap/common/result.py +396 -0
- roadmap/common/security/__init__.py +48 -0
- roadmap/common/security/exceptions.py +13 -0
- roadmap/common/security/export_cleanup.py +79 -0
- roadmap/common/security/file_operations.py +104 -0
- roadmap/common/security/filename_sanitization.py +54 -0
- roadmap/common/security/logging.py +27 -0
- roadmap/common/security/path_validation.py +137 -0
- roadmap/common/security/temp_files.py +41 -0
- roadmap/common/services/__init__.py +65 -0
- roadmap/common/services/decorators.py +183 -0
- roadmap/common/services/logging_utils.py +208 -0
- roadmap/common/services/metrics.py +161 -0
- roadmap/common/services/performance.py +219 -0
- roadmap/common/services/profiling.py +288 -0
- roadmap/common/services/retry.py +288 -0
- roadmap/common/status_style_manager.py +114 -0
- roadmap/common/union_find.py +88 -0
- roadmap/common/update_constants.py +13 -0
- roadmap/common/utils/__init__.py +65 -0
- roadmap/common/utils/cli_helpers.py +381 -0
- roadmap/common/utils/file_utils.py +440 -0
- roadmap/common/utils/path_utils.py +38 -0
- roadmap/common/utils/status_utils.py +130 -0
- roadmap/common/utils/timezone_utils.py +480 -0
- roadmap/common/validation/__init__.py +37 -0
- roadmap/common/validation/field_validator.py +104 -0
- roadmap/common/validation/result.py +37 -0
- roadmap/common/validation/roadmap_validator.py +310 -0
- roadmap/common/validation/schema_validator.py +36 -0
- roadmap/common/validation/validators.py +50 -0
- roadmap/core/__init__.py +1 -0
- roadmap/core/domain/__init__.py +30 -0
- roadmap/core/domain/comment.py +24 -0
- roadmap/core/domain/health.py +15 -0
- roadmap/core/domain/issue.py +274 -0
- roadmap/core/domain/milestone.py +173 -0
- roadmap/core/domain/ports/__init__.py +1 -0
- roadmap/core/domain/ports/baseline_repository.py +24 -0
- roadmap/core/domain/ports/issue_repository.py +34 -0
- roadmap/core/domain/ports/remote_backend_port.py +31 -0
- roadmap/core/domain/project.py +130 -0
- roadmap/core/interfaces/__init__.py +109 -0
- roadmap/core/interfaces/assignee_validator.py +40 -0
- roadmap/core/interfaces/backend_factory.py +175 -0
- roadmap/core/interfaces/github.py +102 -0
- roadmap/core/interfaces/parsers.py +140 -0
- roadmap/core/interfaces/persistence.py +45 -0
- roadmap/core/interfaces/repositories.py +174 -0
- roadmap/core/interfaces/state_managers.py +111 -0
- roadmap/core/interfaces/state_storage.py +64 -0
- roadmap/core/interfaces/sync_backend.py +275 -0
- roadmap/core/interfaces/sync_services.py +119 -0
- roadmap/core/interfaces/sync_validators.py +180 -0
- roadmap/core/models/__init__.py +31 -0
- roadmap/core/models/sync_models.py +162 -0
- roadmap/core/models.py +115 -0
- roadmap/core/observability/__init__.py +17 -0
- roadmap/core/observability/sync_metrics.py +699 -0
- roadmap/core/repositories.py +200 -0
- roadmap/core/services/__init__.py +313 -0
- roadmap/core/services/baseline/__init__.py +1 -0
- roadmap/core/services/baseline/baseline_builder_progress.py +274 -0
- roadmap/core/services/baseline/baseline_retriever.py +63 -0
- roadmap/core/services/baseline/baseline_selector.py +248 -0
- roadmap/core/services/baseline/baseline_state_retriever.py +284 -0
- roadmap/core/services/baseline/optimized_baseline_builder.py +425 -0
- roadmap/core/services/comment/__init__.py +1 -0
- roadmap/core/services/comment/comment_service.py +277 -0
- roadmap/core/services/git/__init__.py +1 -0
- roadmap/core/services/git/git_hook_auto_sync_service.py +403 -0
- roadmap/core/services/github/__init__.py +1 -0
- roadmap/core/services/github/github_change_detector.py +162 -0
- roadmap/core/services/github/github_config_validator.py +148 -0
- roadmap/core/services/github/github_conflict_detector.py +179 -0
- roadmap/core/services/github/github_entity_classifier.py +51 -0
- roadmap/core/services/github/github_integration_service.py +439 -0
- roadmap/core/services/github/github_issue_client.py +346 -0
- roadmap/core/services/health/__init__.py +1 -0
- roadmap/core/services/health/backup_cleanup_service.py +202 -0
- roadmap/core/services/health/data_integrity_validator_service.py +90 -0
- roadmap/core/services/health/entity_health_scanner.py +460 -0
- roadmap/core/services/health/file_repair_service.py +193 -0
- roadmap/core/services/health/health_check_service.py +166 -0
- roadmap/core/services/health/health_models.py +80 -0
- roadmap/core/services/health/infrastructure_validator_service.py +328 -0
- roadmap/core/services/health/issue_health_scanner.py +263 -0
- roadmap/core/services/helpers/__init__.py +18 -0
- roadmap/core/services/helpers/status_change_helpers.py +88 -0
- roadmap/core/services/initialization/__init__.py +19 -0
- roadmap/core/services/initialization/utils.py +104 -0
- roadmap/core/services/initialization/validator.py +78 -0
- roadmap/core/services/initialization/workflow.py +182 -0
- roadmap/core/services/initialization_service.py +111 -0
- roadmap/core/services/issue/__init__.py +1 -0
- roadmap/core/services/issue/assignee_validation_service.py +312 -0
- roadmap/core/services/issue/issue_creation_service.py +309 -0
- roadmap/core/services/issue/issue_filter_service.py +302 -0
- roadmap/core/services/issue/issue_matching_service.py +127 -0
- roadmap/core/services/issue/issue_service.py +765 -0
- roadmap/core/services/issue/issue_update_service.py +144 -0
- roadmap/core/services/issue/start_issue_service.py +174 -0
- roadmap/core/services/issue_helpers/__init__.py +18 -0
- roadmap/core/services/issue_helpers/issue_filters.py +296 -0
- roadmap/core/services/milestone_service.py +438 -0
- roadmap/core/services/project/__init__.py +1 -0
- roadmap/core/services/project/project_service.py +434 -0
- roadmap/core/services/project/project_status_service.py +125 -0
- roadmap/core/services/project_init/__init__.py +22 -0
- roadmap/core/services/project_init/context_detection.py +146 -0
- roadmap/core/services/project_init/creation.py +76 -0
- roadmap/core/services/project_init/detection.py +51 -0
- roadmap/core/services/project_init/template.py +228 -0
- roadmap/core/services/status_change_service.py +88 -0
- roadmap/core/services/sync/__init__.py +1 -0
- roadmap/core/services/sync/batch_processor.py +290 -0
- roadmap/core/services/sync/conflict_resolver.py +154 -0
- roadmap/core/services/sync/dependency_resolver.py +367 -0
- roadmap/core/services/sync/duplicate_detector.py +771 -0
- roadmap/core/services/sync/duplicate_resolver.py +315 -0
- roadmap/core/services/sync/error_classification.py +470 -0
- roadmap/core/services/sync/performance.py +263 -0
- roadmap/core/services/sync/sync_change_computer.py +211 -0
- roadmap/core/services/sync/sync_checkpoint.py +348 -0
- roadmap/core/services/sync/sync_conflict_detector.py +147 -0
- roadmap/core/services/sync/sync_conflict_resolver.py +448 -0
- roadmap/core/services/sync/sync_errors.py +383 -0
- roadmap/core/services/sync/sync_key_normalizer.py +222 -0
- roadmap/core/services/sync/sync_metadata_service.py +361 -0
- roadmap/core/services/sync/sync_plan.py +171 -0
- roadmap/core/services/sync/sync_plan_executor.py +447 -0
- roadmap/core/services/sync/sync_report.py +296 -0
- roadmap/core/services/sync/sync_state.py +173 -0
- roadmap/core/services/sync/sync_state_comparator.py +690 -0
- roadmap/core/services/sync/sync_state_manager.py +431 -0
- roadmap/core/services/sync/sync_state_normalizer.py +98 -0
- roadmap/core/services/sync/sync_three_way.py +78 -0
- roadmap/core/services/sync/three_way_merger.py +178 -0
- roadmap/core/services/utils/__init__.py +1 -0
- roadmap/core/services/utils/configuration_service.py +113 -0
- roadmap/core/services/utils/critical_path_calculator.py +418 -0
- roadmap/core/services/utils/dependency_analyzer.py +360 -0
- roadmap/core/services/utils/field_conflict_detector.py +126 -0
- roadmap/core/services/utils/remote_fetcher.py +202 -0
- roadmap/core/services/utils/remote_state_normalizer.py +110 -0
- roadmap/core/services/utils/retry_policy.py +51 -0
- roadmap/core/services/validator_base.py +130 -0
- roadmap/core/services/validators/__init__.py +28 -0
- roadmap/core/services/validators/_utils.py +20 -0
- roadmap/core/services/validators/archivable_issues_validator.py +71 -0
- roadmap/core/services/validators/archivable_milestones_validator.py +69 -0
- roadmap/core/services/validators/backup_validator.py +89 -0
- roadmap/core/services/validators/data_integrity_validator.py +84 -0
- roadmap/core/services/validators/duplicate_issues_validator.py +75 -0
- roadmap/core/services/validators/duplicate_milestones_validator.py +141 -0
- roadmap/core/services/validators/folder_structure_validator.py +165 -0
- roadmap/core/services/validators/health_status_utils.py +28 -0
- roadmap/core/services/validators/milestone_naming_validator.py +146 -0
- roadmap/core/services/validators/missing_headlines_validator.py +97 -0
- roadmap/core/services/validators/orphaned_issues_validator.py +142 -0
- roadmap/core/services/validators/orphaned_milestones_validator.py +85 -0
- roadmap/core/utils/git_remote_parser.py +83 -0
- roadmap/core/utils/github_urls.py +66 -0
- roadmap/domain/validation.py +30 -0
- roadmap/infrastructure/__init__.py +55 -0
- roadmap/infrastructure/coordination/__init__.py +8 -0
- roadmap/infrastructure/coordination/coordinator_params.py +82 -0
- roadmap/infrastructure/coordination/core.py +446 -0
- roadmap/infrastructure/coordination/git_coordinator.py +160 -0
- roadmap/infrastructure/coordination/initialization.py +348 -0
- roadmap/infrastructure/coordination/issue_coordinator.py +154 -0
- roadmap/infrastructure/coordination/issue_operations.py +456 -0
- roadmap/infrastructure/coordination/milestone_coordinator.py +135 -0
- roadmap/infrastructure/coordination/milestone_operations.py +201 -0
- roadmap/infrastructure/coordination/project_coordinator.py +113 -0
- roadmap/infrastructure/coordination/project_operations.py +144 -0
- roadmap/infrastructure/coordination/team_coordinator.py +107 -0
- roadmap/infrastructure/coordination/user_operations.py +150 -0
- roadmap/infrastructure/coordination/validation_coordinator.py +249 -0
- roadmap/infrastructure/coordination_gateway.py +152 -0
- roadmap/infrastructure/git/__init__.py +6 -0
- roadmap/infrastructure/git/git_integration_ops.py +234 -0
- roadmap/infrastructure/git_gateway.py +35 -0
- roadmap/infrastructure/github_gateway.py +52 -0
- roadmap/infrastructure/maintenance/__init__.py +5 -0
- roadmap/infrastructure/maintenance/cleanup.py +483 -0
- roadmap/infrastructure/observability/__init__.py +7 -0
- roadmap/infrastructure/observability/health.py +196 -0
- roadmap/infrastructure/observability/health_formatter.py +73 -0
- roadmap/infrastructure/observability/specialized_health_checkers.py +188 -0
- roadmap/infrastructure/persistence_gateway.py +164 -0
- roadmap/infrastructure/security/__init__.py +1 -0
- roadmap/infrastructure/security/credentials.py +489 -0
- roadmap/infrastructure/sync_gateway.py +94 -0
- roadmap/infrastructure/validation/__init__.py +7 -0
- roadmap/infrastructure/validation/file_enumeration.py +219 -0
- roadmap/infrastructure/validation/github_assignee_validator.py +47 -0
- roadmap/infrastructure/validation/github_validator.py +101 -0
- roadmap/infrastructure/validation/milestone_consistency_validator.py +96 -0
- roadmap/infrastructure/validation/vanilla_assignee_validator.py +37 -0
- roadmap/infrastructure/validation_gateway.py +96 -0
- roadmap/presentation/formatters/sync_metrics_formatter.py +355 -0
- roadmap/settings.py +345 -0
- roadmap/templates/github_workflows/ci-cd-roadmap.yml +241 -0
- roadmap/templates/github_workflows/issue-lifecycle.yml +236 -0
- roadmap/templates/github_workflows/roadmap-integration.yml +137 -0
- roadmap/templates/github_workflows/roadmap-starter.yml +59 -0
- roadmap/version.py +381 -0
- roadmap_cli-0.1.1.dist-info/METADATA +448 -0
- roadmap_cli-0.1.1.dist-info/RECORD +521 -0
- roadmap_cli-0.1.1.dist-info/WHEEL +4 -0
- roadmap_cli-0.1.1.dist-info/entry_points.txt +2 -0
- roadmap_cli-0.1.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Link GitHub issue command - link internal issue to GitHub issue."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
8
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
9
|
+
from roadmap.common.console import get_console
|
|
10
|
+
from roadmap.common.formatters.text.operations import (
|
|
11
|
+
format_operation_failure,
|
|
12
|
+
format_operation_success,
|
|
13
|
+
)
|
|
14
|
+
from roadmap.common.logging import (
|
|
15
|
+
log_command,
|
|
16
|
+
)
|
|
17
|
+
from roadmap.core.services.github.github_issue_client import GitHubIssueClient
|
|
18
|
+
|
|
19
|
+
console = get_console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command("link")
|
|
23
|
+
@click.argument("issue_id")
|
|
24
|
+
@click.option(
|
|
25
|
+
"--github-id",
|
|
26
|
+
type=int,
|
|
27
|
+
required=True,
|
|
28
|
+
help="GitHub issue number to link (must be positive integer)",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--owner",
|
|
32
|
+
type=str,
|
|
33
|
+
help="GitHub repository owner (required if not in config)",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--repo",
|
|
37
|
+
type=str,
|
|
38
|
+
help="GitHub repository name (required if not in config)",
|
|
39
|
+
)
|
|
40
|
+
@click.pass_context
|
|
41
|
+
@log_command("issue_link", entity_type="issue", track_duration=True)
|
|
42
|
+
@require_initialized
|
|
43
|
+
def link_github_issue(
|
|
44
|
+
ctx: click.Context,
|
|
45
|
+
issue_id: str,
|
|
46
|
+
github_id: int,
|
|
47
|
+
owner: str | None,
|
|
48
|
+
repo: str | None,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Link an internal issue to a GitHub issue.
|
|
51
|
+
|
|
52
|
+
Links the internal issue identified by ISSUE_ID to a GitHub issue
|
|
53
|
+
identified by the GitHub issue number provided via --github-id.
|
|
54
|
+
|
|
55
|
+
Once linked, you can use other commands like sync-github to fetch
|
|
56
|
+
updates from GitHub.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
roadmap issue link abc123 --github-id 456
|
|
60
|
+
roadmap issue link abc123 --github-id 456 --owner owner-name --repo repo-name
|
|
61
|
+
"""
|
|
62
|
+
core = ctx.obj["core"]
|
|
63
|
+
|
|
64
|
+
# Validate internal issue
|
|
65
|
+
issue = _validate_internal_issue(ctx, issue_id)
|
|
66
|
+
|
|
67
|
+
# Validate GitHub ID is positive
|
|
68
|
+
_validate_github_id(ctx, github_id)
|
|
69
|
+
|
|
70
|
+
# Check if already linked
|
|
71
|
+
if issue.github_issue is not None:
|
|
72
|
+
if issue.github_issue == github_id:
|
|
73
|
+
_display_already_linked(issue, issue_id)
|
|
74
|
+
return
|
|
75
|
+
else:
|
|
76
|
+
_display_already_linked_different(ctx, issue_id, issue.github_issue)
|
|
77
|
+
|
|
78
|
+
# Get owner/repo from config or command-line
|
|
79
|
+
config_owner, config_repo = _resolve_github_config(ctx, core, owner, repo)
|
|
80
|
+
|
|
81
|
+
# Validate GitHub issue exists
|
|
82
|
+
_validate_github_issue_exists(ctx, config_owner, config_repo, github_id)
|
|
83
|
+
|
|
84
|
+
# Perform the link
|
|
85
|
+
_perform_link(ctx, core, issue, issue_id, github_id, config_owner, config_repo)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _validate_internal_issue(ctx: click.Context, issue_id: str) -> Any:
|
|
89
|
+
"""Validate that an internal issue exists.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ctx: Click context
|
|
93
|
+
issue_id: Issue ID to validate
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Issue object
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
SystemExit: If issue not found
|
|
100
|
+
"""
|
|
101
|
+
core = ctx.obj["core"]
|
|
102
|
+
try:
|
|
103
|
+
issue = core.issues.get_by_id(issue_id)
|
|
104
|
+
if not issue:
|
|
105
|
+
lines = format_operation_failure("link issue", issue_id, "Issue not found")
|
|
106
|
+
for line in lines:
|
|
107
|
+
console.print(line)
|
|
108
|
+
ctx.exit(1)
|
|
109
|
+
return issue
|
|
110
|
+
except Exception as e:
|
|
111
|
+
handle_cli_error(
|
|
112
|
+
error=e,
|
|
113
|
+
operation="retrieve issue",
|
|
114
|
+
entity_type="issue",
|
|
115
|
+
entity_id=issue_id,
|
|
116
|
+
fatal=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _validate_github_id(ctx: click.Context, github_id: int) -> None:
|
|
121
|
+
"""Validate GitHub ID is positive.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
ctx: Click context
|
|
125
|
+
github_id: GitHub issue ID to validate
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
SystemExit: If ID is invalid
|
|
129
|
+
"""
|
|
130
|
+
if github_id <= 0:
|
|
131
|
+
lines = format_operation_failure(
|
|
132
|
+
"link issue", "", "Invalid GitHub issue number"
|
|
133
|
+
)
|
|
134
|
+
for line in lines:
|
|
135
|
+
console.print(line)
|
|
136
|
+
ctx.exit(1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _display_already_linked(issue: Any, issue_id: str) -> None:
|
|
140
|
+
"""Display message when issue is already linked to same GitHub ID.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
issue: Issue object
|
|
144
|
+
issue_id: Issue ID
|
|
145
|
+
"""
|
|
146
|
+
lines = format_operation_success("✅", "Linked", issue.title, issue_id)
|
|
147
|
+
for line in lines:
|
|
148
|
+
console.print(line)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _display_already_linked_different(
|
|
152
|
+
ctx: click.Context, issue_id: str, existing_github_id: int
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Display error when issue is already linked to different GitHub ID.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ctx: Click context
|
|
158
|
+
issue_id: Issue ID
|
|
159
|
+
existing_github_id: Existing GitHub issue ID
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
SystemExit: Always exits
|
|
163
|
+
"""
|
|
164
|
+
lines = format_operation_failure(
|
|
165
|
+
"link issue",
|
|
166
|
+
issue_id,
|
|
167
|
+
f"Already linked to GitHub issue #{existing_github_id}",
|
|
168
|
+
)
|
|
169
|
+
for line in lines:
|
|
170
|
+
console.print(line)
|
|
171
|
+
ctx.exit(1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_github_config(
|
|
175
|
+
ctx: click.Context,
|
|
176
|
+
core: Any,
|
|
177
|
+
owner: str | None,
|
|
178
|
+
repo: str | None,
|
|
179
|
+
) -> tuple[str, str]:
|
|
180
|
+
"""Resolve GitHub owner and repo from config or arguments.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
ctx: Click context
|
|
184
|
+
core: RoadmapCore instance
|
|
185
|
+
owner: Optional owner override
|
|
186
|
+
repo: Optional repo override
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Tuple of (owner, repo)
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
SystemExit: If config cannot be resolved
|
|
193
|
+
"""
|
|
194
|
+
config_owner = owner
|
|
195
|
+
config_repo = repo
|
|
196
|
+
|
|
197
|
+
if not config_owner or not config_repo:
|
|
198
|
+
try:
|
|
199
|
+
from pathlib import Path
|
|
200
|
+
|
|
201
|
+
from roadmap.core.services.github.github_integration_service import (
|
|
202
|
+
GitHubIntegrationService,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
config_path = Path(core.root_path) / "config.yaml"
|
|
206
|
+
gh_service = GitHubIntegrationService(Path(core.root_path), config_path)
|
|
207
|
+
_, config_owner, config_repo = gh_service.get_github_config()
|
|
208
|
+
|
|
209
|
+
if not config_owner or not config_repo:
|
|
210
|
+
lines = format_operation_failure(
|
|
211
|
+
"configure GitHub", None, "GitHub repository not configured"
|
|
212
|
+
)
|
|
213
|
+
for line in lines:
|
|
214
|
+
console.print(line)
|
|
215
|
+
ctx.exit(1)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
lines = format_operation_failure("get GitHub configuration", None, str(e))
|
|
218
|
+
for line in lines:
|
|
219
|
+
console.print(line)
|
|
220
|
+
ctx.exit(1)
|
|
221
|
+
|
|
222
|
+
# Type narrowing: at this point these must be non-None
|
|
223
|
+
assert config_owner is not None
|
|
224
|
+
assert config_repo is not None
|
|
225
|
+
|
|
226
|
+
return config_owner, config_repo
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _validate_github_issue_exists(
|
|
230
|
+
ctx: click.Context, owner: str, repo: str, github_id: int
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Validate that a GitHub issue exists.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
ctx: Click context
|
|
236
|
+
owner: GitHub repository owner
|
|
237
|
+
repo: GitHub repository name
|
|
238
|
+
github_id: GitHub issue ID
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
SystemExit: If GitHub issue doesn't exist
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
gh_client = GitHubIssueClient()
|
|
245
|
+
exists = gh_client.issue_exists(owner, repo, github_id)
|
|
246
|
+
if not exists:
|
|
247
|
+
lines = format_operation_failure(
|
|
248
|
+
"verify GitHub issue",
|
|
249
|
+
str(github_id),
|
|
250
|
+
f"GitHub issue #{github_id} not found",
|
|
251
|
+
)
|
|
252
|
+
for line in lines:
|
|
253
|
+
console.print(line)
|
|
254
|
+
ctx.exit(1)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
lines = format_operation_failure("verify GitHub issue", str(github_id), str(e))
|
|
257
|
+
for line in lines:
|
|
258
|
+
console.print(line)
|
|
259
|
+
ctx.exit(1)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _perform_link(
|
|
263
|
+
ctx: click.Context,
|
|
264
|
+
core: Any,
|
|
265
|
+
issue: Any,
|
|
266
|
+
issue_id: str,
|
|
267
|
+
github_id: int,
|
|
268
|
+
owner: str,
|
|
269
|
+
repo: str,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Perform the actual link operation.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
ctx: Click context
|
|
275
|
+
core: RoadmapCore instance
|
|
276
|
+
issue: Issue object
|
|
277
|
+
issue_id: Issue ID
|
|
278
|
+
github_id: GitHub issue ID
|
|
279
|
+
owner: GitHub repository owner
|
|
280
|
+
repo: GitHub repository name
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
SystemExit: If link fails
|
|
284
|
+
"""
|
|
285
|
+
try:
|
|
286
|
+
issue.github_issue = github_id
|
|
287
|
+
core.issues.update(issue)
|
|
288
|
+
|
|
289
|
+
# Success message with link details
|
|
290
|
+
lines = format_operation_success("✅", "Linked", issue.title, issue_id)
|
|
291
|
+
for line in lines:
|
|
292
|
+
console.print(line)
|
|
293
|
+
console.print(
|
|
294
|
+
f" Repository: {owner}/{repo}",
|
|
295
|
+
style="cyan",
|
|
296
|
+
)
|
|
297
|
+
console.print(
|
|
298
|
+
f" You can now use 'roadmap issue sync-github {issue_id}' to sync changes",
|
|
299
|
+
style="dim",
|
|
300
|
+
)
|
|
301
|
+
except Exception as e:
|
|
302
|
+
handle_cli_error(
|
|
303
|
+
error=e,
|
|
304
|
+
operation="link issue",
|
|
305
|
+
entity_type="issue",
|
|
306
|
+
entity_id=issue_id,
|
|
307
|
+
fatal=True,
|
|
308
|
+
)
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""List issues command."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
8
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
9
|
+
from roadmap.adapters.cli.decorators import with_output_support
|
|
10
|
+
from roadmap.adapters.cli.dtos import IssueDTO
|
|
11
|
+
from roadmap.adapters.cli.mappers import IssueMapper
|
|
12
|
+
from roadmap.adapters.cli.services.export_manager import ExportManager
|
|
13
|
+
from roadmap.common.console import get_console
|
|
14
|
+
from roadmap.common.errors import ErrorHandler, ValidationError
|
|
15
|
+
from roadmap.common.formatters import IssueTableFormatter
|
|
16
|
+
from roadmap.common.logging import verbose_output
|
|
17
|
+
from roadmap.common.models import ColumnType, IssueListParams
|
|
18
|
+
from roadmap.core.services.issue_helpers import (
|
|
19
|
+
IssueFilterValidator,
|
|
20
|
+
IssueQueryService,
|
|
21
|
+
WorkloadCalculator,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_console():
|
|
26
|
+
"""Get console instance at runtime to respect Click's test environment."""
|
|
27
|
+
return get_console()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _validate_and_get_issues(
|
|
31
|
+
core,
|
|
32
|
+
backlog: bool,
|
|
33
|
+
assignee: str | None,
|
|
34
|
+
my_issues: bool,
|
|
35
|
+
unassigned: bool,
|
|
36
|
+
next_milestone: bool,
|
|
37
|
+
milestone: str | None,
|
|
38
|
+
overdue: bool,
|
|
39
|
+
) -> tuple[list[IssueDTO] | None, str | None]:
|
|
40
|
+
"""Validate filter combinations and get filtered issues as DTOs.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
core: RoadmapCore instance
|
|
44
|
+
backlog: Show backlog issues
|
|
45
|
+
assignee: Filter by assignee
|
|
46
|
+
my_issues: Show current user's issues
|
|
47
|
+
unassigned: Show unassigned issues
|
|
48
|
+
next_milestone: Show next milestone issues
|
|
49
|
+
milestone: Filter by milestone name
|
|
50
|
+
overdue: Show overdue issues
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Tuple of (issues DTOs list or None, filter description or None)
|
|
54
|
+
"""
|
|
55
|
+
is_valid, error_msg = IssueFilterValidator.validate_filters(
|
|
56
|
+
assignee, my_issues, backlog, unassigned, next_milestone, milestone
|
|
57
|
+
)
|
|
58
|
+
if not is_valid:
|
|
59
|
+
_get_console().print(f"❌ {error_msg}", style="bold red")
|
|
60
|
+
return None, None
|
|
61
|
+
|
|
62
|
+
query_service = IssueQueryService(core)
|
|
63
|
+
issues, filter_description = query_service.get_filtered_issues(
|
|
64
|
+
milestone=milestone,
|
|
65
|
+
backlog=backlog,
|
|
66
|
+
overdue=overdue,
|
|
67
|
+
unassigned=unassigned,
|
|
68
|
+
next_milestone=next_milestone,
|
|
69
|
+
assignee=assignee,
|
|
70
|
+
my_issues=my_issues,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Convert domain Issues to DTOs for CLI presentation
|
|
74
|
+
issue_dtos = (
|
|
75
|
+
[IssueMapper.domain_to_dto(issue) for issue in issues] if issues else []
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return issue_dtos, filter_description
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _handle_no_upcoming_milestones() -> None:
|
|
82
|
+
"""Handle case where no upcoming milestones are found."""
|
|
83
|
+
_get_console().print(
|
|
84
|
+
"📋 No upcoming milestones with due dates found.", style="yellow"
|
|
85
|
+
)
|
|
86
|
+
_get_console().print(
|
|
87
|
+
"Create one with: roadmap milestone create 'Milestone name' --due-date YYYY-MM-DD",
|
|
88
|
+
style="dim",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _apply_additional_filters(
|
|
93
|
+
core,
|
|
94
|
+
issues: list[IssueDTO],
|
|
95
|
+
filter_description: str | None,
|
|
96
|
+
open_flag: bool,
|
|
97
|
+
blocked: bool,
|
|
98
|
+
status: str | None,
|
|
99
|
+
priority: str | None,
|
|
100
|
+
issue_type: str | None,
|
|
101
|
+
) -> tuple[list[IssueDTO], str]:
|
|
102
|
+
"""Apply additional filters to issue DTOs.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
core: RoadmapCore instance
|
|
106
|
+
issues: List of issue DTOs to filter
|
|
107
|
+
filter_description: Current filter description
|
|
108
|
+
open_flag: Show only open issues
|
|
109
|
+
blocked: Show only blocked issues
|
|
110
|
+
status: Filter by status
|
|
111
|
+
priority: Filter by priority
|
|
112
|
+
issue_type: Filter by issue type
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Tuple of (filtered issue DTOs, updated filter description)
|
|
116
|
+
"""
|
|
117
|
+
# Convert DTOs back to domain for filtering via service
|
|
118
|
+
from roadmap.adapters.cli.mappers import IssueMapper
|
|
119
|
+
|
|
120
|
+
domain_issues = [IssueMapper.dto_to_domain(dto) for dto in issues]
|
|
121
|
+
|
|
122
|
+
query_service = IssueQueryService(core)
|
|
123
|
+
filtered_domain_issues, filter_description = query_service.apply_additional_filters(
|
|
124
|
+
domain_issues,
|
|
125
|
+
filter_description or "",
|
|
126
|
+
open_only=open_flag,
|
|
127
|
+
blocked_only=blocked,
|
|
128
|
+
status=status,
|
|
129
|
+
priority=priority,
|
|
130
|
+
issue_type=issue_type,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Convert filtered domain issues back to DTOs
|
|
134
|
+
filtered_dtos = [
|
|
135
|
+
IssueMapper.domain_to_dto(issue) for issue in filtered_domain_issues
|
|
136
|
+
]
|
|
137
|
+
return filtered_dtos, filter_description
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _normalize_filter_type(params: IssueListParams) -> None:
|
|
141
|
+
"""Normalize positional filter aliases into canonical flags."""
|
|
142
|
+
if params.filter_type and params.filter_type.lower() == "backlog":
|
|
143
|
+
params.backlog = True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _display_no_issues(filter_description: str) -> None:
|
|
147
|
+
"""Display standard no-issues message."""
|
|
148
|
+
_get_console().print(f"📋 No {filter_description} issues found.", style="yellow")
|
|
149
|
+
_get_console().print(
|
|
150
|
+
"Create one with: roadmap issue create 'Issue title'", style="dim"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _print_issue_list_header(issue_count: int, filter_description: str) -> None:
|
|
155
|
+
"""Print issue list summary header."""
|
|
156
|
+
header_text = (
|
|
157
|
+
f"📋 {issue_count} {filter_description} issue{'s' if issue_count != 1 else ''}"
|
|
158
|
+
)
|
|
159
|
+
_get_console().print(header_text, style="bold cyan")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _maybe_export_issues(
|
|
163
|
+
issues: list[IssueDTO], issue_count: int, export: bool
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Export issue DTOs when requested."""
|
|
166
|
+
if not export:
|
|
167
|
+
return
|
|
168
|
+
export_manager = ExportManager()
|
|
169
|
+
export_data = [asdict(dto) for dto in issues]
|
|
170
|
+
_content, export_path = export_manager.export_data(export_data, "issues")
|
|
171
|
+
_get_console().print(
|
|
172
|
+
f"✅ Exported {issue_count} issues to {export_path}", style="green"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _maybe_display_workload_summary(
|
|
177
|
+
params: IssueListParams, domain_issues: list
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Display workload summary for assignee-focused views."""
|
|
180
|
+
if not ((params.assignee or params.my_issues) and domain_issues):
|
|
181
|
+
return
|
|
182
|
+
assignee_name = params.assignee if params.assignee else "you"
|
|
183
|
+
workload = WorkloadCalculator.calculate_workload(domain_issues)
|
|
184
|
+
IssueTableFormatter.display_workload_summary(
|
|
185
|
+
assignee_name,
|
|
186
|
+
workload["total_hours"],
|
|
187
|
+
workload["status_breakdown"],
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _build_list_error_context(params: IssueListParams) -> dict:
|
|
192
|
+
"""Build error context for list_issues failure handling."""
|
|
193
|
+
return {
|
|
194
|
+
"backlog": params.backlog,
|
|
195
|
+
"assignee": params.assignee,
|
|
196
|
+
"my_issues": params.my_issues,
|
|
197
|
+
"filter": params.status,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@click.command("list")
|
|
202
|
+
@click.argument(
|
|
203
|
+
"filter_type",
|
|
204
|
+
required=False,
|
|
205
|
+
default=None,
|
|
206
|
+
type=click.Choice(["backlog"], case_sensitive=False),
|
|
207
|
+
)
|
|
208
|
+
@click.option("--milestone", "-m", help="Filter by milestone")
|
|
209
|
+
@click.option("--backlog", is_flag=True, help="Show only backlog issues (no milestone)")
|
|
210
|
+
@click.option(
|
|
211
|
+
"--unassigned",
|
|
212
|
+
is_flag=True,
|
|
213
|
+
help="Show only unassigned issues (alias for --backlog)",
|
|
214
|
+
)
|
|
215
|
+
@click.option("--open", is_flag=True, help="Show only open issues (not closed)")
|
|
216
|
+
@click.option("--blocked", is_flag=True, help="Show only blocked issues")
|
|
217
|
+
@click.option(
|
|
218
|
+
"--next-milestone", is_flag=True, help="Show issues for the next upcoming milestone"
|
|
219
|
+
)
|
|
220
|
+
@click.option("--assignee", "-a", help="Filter by assignee")
|
|
221
|
+
@click.option("--my-issues", is_flag=True, help="Show only issues assigned to me")
|
|
222
|
+
@click.option(
|
|
223
|
+
"--status",
|
|
224
|
+
"-s",
|
|
225
|
+
type=click.Choice(["todo", "in-progress", "blocked", "review", "closed"]),
|
|
226
|
+
help="Filter by workflow status (todo|in-progress|blocked|review|closed). Use --open for non-closed issues",
|
|
227
|
+
)
|
|
228
|
+
@click.option(
|
|
229
|
+
"--priority",
|
|
230
|
+
"-p",
|
|
231
|
+
type=click.Choice(["critical", "high", "medium", "low"]),
|
|
232
|
+
help="Filter by priority",
|
|
233
|
+
)
|
|
234
|
+
@click.option(
|
|
235
|
+
"--issue-type",
|
|
236
|
+
"-t",
|
|
237
|
+
type=click.Choice(["feature", "bug", "other"]),
|
|
238
|
+
help="Filter by issue type",
|
|
239
|
+
)
|
|
240
|
+
@click.option(
|
|
241
|
+
"--overdue", is_flag=True, help="Show only overdue issues (past due date)"
|
|
242
|
+
)
|
|
243
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show verbose output")
|
|
244
|
+
@click.option(
|
|
245
|
+
"--show-github-ids",
|
|
246
|
+
is_flag=True,
|
|
247
|
+
help="Display GitHub issue IDs for linked issues",
|
|
248
|
+
)
|
|
249
|
+
@click.option(
|
|
250
|
+
"--export",
|
|
251
|
+
"-e",
|
|
252
|
+
is_flag=True,
|
|
253
|
+
help="Export results to file (uses config defaults for format and location)",
|
|
254
|
+
)
|
|
255
|
+
@click.pass_context
|
|
256
|
+
@with_output_support(
|
|
257
|
+
available_columns=[
|
|
258
|
+
"id",
|
|
259
|
+
"title",
|
|
260
|
+
"priority",
|
|
261
|
+
"status",
|
|
262
|
+
"progress",
|
|
263
|
+
"assignee",
|
|
264
|
+
"estimate",
|
|
265
|
+
"milestone",
|
|
266
|
+
],
|
|
267
|
+
column_types={
|
|
268
|
+
"id": ColumnType.STRING,
|
|
269
|
+
"title": ColumnType.STRING,
|
|
270
|
+
"priority": ColumnType.ENUM,
|
|
271
|
+
"status": ColumnType.ENUM,
|
|
272
|
+
"progress": ColumnType.STRING,
|
|
273
|
+
"assignee": ColumnType.STRING,
|
|
274
|
+
"estimate": ColumnType.STRING,
|
|
275
|
+
"milestone": ColumnType.STRING,
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
@verbose_output
|
|
279
|
+
@require_initialized
|
|
280
|
+
def list_issues( # noqa: F841 - verbose is used by decorator
|
|
281
|
+
ctx: click.Context,
|
|
282
|
+
filter_type: str,
|
|
283
|
+
milestone: str,
|
|
284
|
+
backlog: bool,
|
|
285
|
+
unassigned: bool,
|
|
286
|
+
open: bool,
|
|
287
|
+
blocked: bool,
|
|
288
|
+
next_milestone: bool,
|
|
289
|
+
assignee: str,
|
|
290
|
+
my_issues: bool,
|
|
291
|
+
status: str,
|
|
292
|
+
priority: str,
|
|
293
|
+
issue_type: str,
|
|
294
|
+
overdue: bool,
|
|
295
|
+
verbose: bool, # noqa: F841 - used by @verbose_output decorator
|
|
296
|
+
show_github_ids: bool,
|
|
297
|
+
export: bool,
|
|
298
|
+
):
|
|
299
|
+
"""List all issues with various filtering options.
|
|
300
|
+
|
|
301
|
+
Optional positional argument 'backlog' shows only backlog issues (no milestone).
|
|
302
|
+
Equivalent to: roadmap issue list --backlog
|
|
303
|
+
|
|
304
|
+
Supports output formatting with --format, --columns, --sort-by, --filter flags.
|
|
305
|
+
"""
|
|
306
|
+
core = ctx.obj["core"]
|
|
307
|
+
|
|
308
|
+
# Create structured parameter object
|
|
309
|
+
params = IssueListParams(
|
|
310
|
+
filter_type=filter_type,
|
|
311
|
+
milestone=milestone,
|
|
312
|
+
backlog=backlog,
|
|
313
|
+
unassigned=unassigned,
|
|
314
|
+
open=open,
|
|
315
|
+
blocked=blocked,
|
|
316
|
+
next_milestone=next_milestone,
|
|
317
|
+
assignee=assignee,
|
|
318
|
+
my_issues=my_issues,
|
|
319
|
+
status=status,
|
|
320
|
+
priority=priority,
|
|
321
|
+
issue_type=issue_type,
|
|
322
|
+
overdue=overdue,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
_normalize_filter_type(params)
|
|
327
|
+
|
|
328
|
+
# Validate and get issues
|
|
329
|
+
issues, filter_description = _validate_and_get_issues(
|
|
330
|
+
core,
|
|
331
|
+
params.backlog,
|
|
332
|
+
params.assignee,
|
|
333
|
+
params.my_issues,
|
|
334
|
+
params.unassigned,
|
|
335
|
+
params.next_milestone,
|
|
336
|
+
params.milestone,
|
|
337
|
+
params.overdue,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if issues is None:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
# Handle next milestone not found
|
|
344
|
+
if next_milestone and not issues and not filter_description:
|
|
345
|
+
_handle_no_upcoming_milestones()
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
# Apply additional filters
|
|
349
|
+
issues, filter_description = _apply_additional_filters(
|
|
350
|
+
core,
|
|
351
|
+
issues,
|
|
352
|
+
filter_description,
|
|
353
|
+
params.open,
|
|
354
|
+
params.blocked,
|
|
355
|
+
params.status,
|
|
356
|
+
params.priority,
|
|
357
|
+
params.issue_type,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Handle no issues found
|
|
361
|
+
if not issues:
|
|
362
|
+
_display_no_issues(filter_description)
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
issue_count = len(issues)
|
|
366
|
+
_print_issue_list_header(issue_count, filter_description)
|
|
367
|
+
|
|
368
|
+
# Convert DTOs back to domain objects for table formatter
|
|
369
|
+
# (formatters still work with domain models)
|
|
370
|
+
domain_issues = [IssueMapper.dto_to_domain(dto) for dto in issues]
|
|
371
|
+
|
|
372
|
+
_maybe_export_issues(issues, issue_count, export)
|
|
373
|
+
|
|
374
|
+
# Convert to TableData for structured output
|
|
375
|
+
table_data = IssueTableFormatter.issues_to_table_data(
|
|
376
|
+
domain_issues,
|
|
377
|
+
title="Issues",
|
|
378
|
+
description=filter_description,
|
|
379
|
+
show_github_ids=show_github_ids,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
_maybe_display_workload_summary(params, domain_issues)
|
|
383
|
+
|
|
384
|
+
# Return TableData for decorator to handle formatting
|
|
385
|
+
return table_data
|
|
386
|
+
|
|
387
|
+
except Exception as e:
|
|
388
|
+
handle_cli_error(
|
|
389
|
+
error=e,
|
|
390
|
+
operation="list_issues",
|
|
391
|
+
entity_type="issue",
|
|
392
|
+
entity_id="all",
|
|
393
|
+
context=_build_list_error_context(params),
|
|
394
|
+
fatal=True,
|
|
395
|
+
)
|
|
396
|
+
error_handler = ErrorHandler()
|
|
397
|
+
error_handler.handle_error(
|
|
398
|
+
ValidationError(
|
|
399
|
+
"Failed to list issues", context={"command": "list"}, cause=e
|
|
400
|
+
),
|
|
401
|
+
exit_on_critical=False,
|
|
402
|
+
)
|