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,258 @@
|
|
|
1
|
+
"""CLI presenter for Issue DTOs."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from rich.markdown import Markdown
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
from roadmap.adapters.cli.dtos import IssueDTO
|
|
11
|
+
from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
|
|
12
|
+
from roadmap.adapters.cli.presentation.table_builders import create_metadata_table
|
|
13
|
+
from roadmap.adapters.cli.styling import PRIORITY_COLORS, STATUS_COLORS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IssuePresenter(BasePresenter):
|
|
17
|
+
"""Presenter for formatting and displaying Issue DTOs."""
|
|
18
|
+
|
|
19
|
+
def render(self, issue_dto: IssueDTO) -> None:
|
|
20
|
+
"""Render an issue DTO in detailed view.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
issue_dto: The IssueDTO to render
|
|
24
|
+
"""
|
|
25
|
+
# Display header
|
|
26
|
+
header = self._build_issue_header(issue_dto)
|
|
27
|
+
self._get_console().print(Panel(header, border_style="cyan"))
|
|
28
|
+
|
|
29
|
+
# Display metadata
|
|
30
|
+
metadata = self._build_metadata_table(issue_dto)
|
|
31
|
+
self._get_console().print(
|
|
32
|
+
Panel(metadata, title="📋 Metadata", border_style="blue")
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Display timeline
|
|
36
|
+
timeline = self._build_timeline_table(issue_dto)
|
|
37
|
+
self._get_console().print(
|
|
38
|
+
Panel(timeline, title="⏱️ Timeline", border_style="yellow")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Display dependencies if any
|
|
42
|
+
deps = self._build_dependencies_table(issue_dto)
|
|
43
|
+
if deps:
|
|
44
|
+
self._get_console().print(
|
|
45
|
+
Panel(deps, title="🔗 Dependencies", border_style="magenta")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Display description if available
|
|
49
|
+
if issue_dto.content:
|
|
50
|
+
description, acceptance = self._extract_description_and_criteria(
|
|
51
|
+
issue_dto.content
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if description:
|
|
55
|
+
md = Markdown(description)
|
|
56
|
+
self._get_console().print(
|
|
57
|
+
Panel(md, title="📝 Description", border_style="white")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if acceptance:
|
|
61
|
+
md = Markdown(acceptance)
|
|
62
|
+
self._get_console().print(
|
|
63
|
+
Panel(md, title="✅ Acceptance Criteria", border_style="green")
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
self._get_console().print(
|
|
67
|
+
Panel(
|
|
68
|
+
"[dim]No description available[/dim]",
|
|
69
|
+
title="📝 Description",
|
|
70
|
+
border_style="white",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Display comments if any
|
|
75
|
+
if issue_dto.comments:
|
|
76
|
+
comments_text = self._build_comments_text(issue_dto.comments)
|
|
77
|
+
self._get_console().print(
|
|
78
|
+
Panel(
|
|
79
|
+
comments_text,
|
|
80
|
+
title=f"💬 Comments ({len(issue_dto.comments)})",
|
|
81
|
+
border_style="cyan",
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _build_issue_header(self, issue_dto: IssueDTO) -> Text:
|
|
86
|
+
"""Build header text with issue title and metadata.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
issue_dto: The IssueDTO to format
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Rich Text object with formatted header
|
|
93
|
+
"""
|
|
94
|
+
status_color = STATUS_COLORS.get(issue_dto.status, "white")
|
|
95
|
+
priority_color = PRIORITY_COLORS.get(issue_dto.priority, "white")
|
|
96
|
+
|
|
97
|
+
header = Text()
|
|
98
|
+
header.append(f"#{issue_dto.id}", style="bold cyan")
|
|
99
|
+
header.append(" • ", style="dim")
|
|
100
|
+
header.append(issue_dto.title, style="bold white")
|
|
101
|
+
header.append("\n")
|
|
102
|
+
header.append(f"[{issue_dto.status.upper()}]", style=f"bold {status_color}")
|
|
103
|
+
header.append(" • ", style="dim")
|
|
104
|
+
header.append(issue_dto.priority.upper(), style=priority_color)
|
|
105
|
+
header.append(" • ", style="dim")
|
|
106
|
+
header.append(issue_dto.issue_type.title(), style="cyan")
|
|
107
|
+
|
|
108
|
+
return header
|
|
109
|
+
|
|
110
|
+
def _build_metadata_table(self, issue_dto: IssueDTO):
|
|
111
|
+
"""Build metadata table for issue DTO.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
issue_dto: The IssueDTO to format
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Rich Table with metadata rows
|
|
118
|
+
"""
|
|
119
|
+
metadata = create_metadata_table()
|
|
120
|
+
|
|
121
|
+
metadata.add_row("Assignee", issue_dto.assignee or "Unassigned")
|
|
122
|
+
metadata.add_row("Milestone", issue_dto.milestone or "None")
|
|
123
|
+
if issue_dto.created:
|
|
124
|
+
metadata.add_row("Created", issue_dto.created.strftime("%Y-%m-%d %H:%M"))
|
|
125
|
+
if issue_dto.updated:
|
|
126
|
+
metadata.add_row("Updated", issue_dto.updated.strftime("%Y-%m-%d %H:%M"))
|
|
127
|
+
|
|
128
|
+
if issue_dto.labels:
|
|
129
|
+
metadata.add_row("Labels", ", ".join(issue_dto.labels))
|
|
130
|
+
|
|
131
|
+
if issue_dto.github_issue:
|
|
132
|
+
github_id = (
|
|
133
|
+
f"#{issue_dto.github_issue}"
|
|
134
|
+
if isinstance(issue_dto.github_issue, int)
|
|
135
|
+
else str(issue_dto.github_issue)
|
|
136
|
+
)
|
|
137
|
+
metadata.add_row("GitHub Issue", github_id)
|
|
138
|
+
|
|
139
|
+
return metadata
|
|
140
|
+
|
|
141
|
+
def _build_timeline_table(self, issue_dto: IssueDTO):
|
|
142
|
+
"""Build timeline table for issue DTO.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
issue_dto: The IssueDTO to format
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Rich Table with timeline rows
|
|
149
|
+
"""
|
|
150
|
+
timeline = create_metadata_table()
|
|
151
|
+
|
|
152
|
+
# Display estimated hours
|
|
153
|
+
if issue_dto.estimated_hours:
|
|
154
|
+
timeline.add_row("Estimated", f"{issue_dto.estimated_hours:.1f}h")
|
|
155
|
+
else:
|
|
156
|
+
timeline.add_row("Estimated", "Not estimated")
|
|
157
|
+
|
|
158
|
+
# Display progress
|
|
159
|
+
if issue_dto.progress_percentage is not None:
|
|
160
|
+
timeline.add_row("Progress", f"{issue_dto.progress_percentage}%")
|
|
161
|
+
else:
|
|
162
|
+
timeline.add_row("Progress", "0%")
|
|
163
|
+
|
|
164
|
+
# Display end date if present
|
|
165
|
+
if isinstance(issue_dto.actual_end_date, datetime):
|
|
166
|
+
timeline.add_row(
|
|
167
|
+
"Completed", issue_dto.actual_end_date.strftime("%Y-%m-%d %H:%M")
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Display due date if present
|
|
171
|
+
if isinstance(issue_dto.due_date, datetime):
|
|
172
|
+
timeline.add_row("Due Date", issue_dto.due_date.strftime("%Y-%m-%d"))
|
|
173
|
+
|
|
174
|
+
return timeline
|
|
175
|
+
|
|
176
|
+
def _build_dependencies_table(self, issue_dto: IssueDTO):
|
|
177
|
+
"""Build dependencies table if exists.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
issue_dto: The IssueDTO to format
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Rich Table with dependencies or None if no dependencies
|
|
184
|
+
"""
|
|
185
|
+
# For now, DTOs don't store dependencies - would need to extend model
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
def _extract_description_and_criteria(self, content: str) -> tuple[str, str]:
|
|
189
|
+
"""Parse issue content to extract description and acceptance criteria.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
content: The issue content/description text
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Tuple of (description, acceptance_criteria) strings
|
|
196
|
+
"""
|
|
197
|
+
content_lines = content.split("\n")
|
|
198
|
+
description_lines = []
|
|
199
|
+
acceptance_lines = []
|
|
200
|
+
in_acceptance = False
|
|
201
|
+
|
|
202
|
+
for line in content_lines:
|
|
203
|
+
if (
|
|
204
|
+
"## Acceptance Criteria" in line
|
|
205
|
+
or "## acceptance criteria" in line.lower()
|
|
206
|
+
):
|
|
207
|
+
in_acceptance = True
|
|
208
|
+
continue
|
|
209
|
+
elif line.startswith("## ") and in_acceptance:
|
|
210
|
+
in_acceptance = False
|
|
211
|
+
|
|
212
|
+
if in_acceptance:
|
|
213
|
+
acceptance_lines.append(line)
|
|
214
|
+
elif not line.startswith("#"):
|
|
215
|
+
description_lines.append(line)
|
|
216
|
+
|
|
217
|
+
description = "\n".join(description_lines).strip()
|
|
218
|
+
acceptance = "\n".join(acceptance_lines).strip()
|
|
219
|
+
|
|
220
|
+
return description, acceptance
|
|
221
|
+
|
|
222
|
+
def _build_comments_text(self, comments: list) -> Table:
|
|
223
|
+
"""Build formatted text for displaying comments.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
comments: List of CommentDTO objects
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Rich Table object with formatted comments
|
|
230
|
+
"""
|
|
231
|
+
# Create a table for comments
|
|
232
|
+
table = Table(show_header=False, box=None, padding=(1, 0))
|
|
233
|
+
table.add_column("Comments", style="white")
|
|
234
|
+
|
|
235
|
+
for comment in comments:
|
|
236
|
+
comment_text = Text()
|
|
237
|
+
# Author and timestamp
|
|
238
|
+
author_line = Text()
|
|
239
|
+
author_line.append(f"@{comment.author}", style="bold magenta")
|
|
240
|
+
author_line.append(
|
|
241
|
+
f" · {comment.created_at.strftime('%Y-%m-%d %H:%M')}", style="dim"
|
|
242
|
+
)
|
|
243
|
+
if comment.in_reply_to:
|
|
244
|
+
author_line.append(
|
|
245
|
+
f" (in reply to #{comment.in_reply_to})", style="dim italic"
|
|
246
|
+
)
|
|
247
|
+
comment_text.append(author_line)
|
|
248
|
+
comment_text.append("\n")
|
|
249
|
+
|
|
250
|
+
# Comment body (using markdown if possible)
|
|
251
|
+
comment_text.append(comment.body, style="white")
|
|
252
|
+
|
|
253
|
+
table.add_row(comment_text)
|
|
254
|
+
# Add separator between comments
|
|
255
|
+
if comment != comments[-1]:
|
|
256
|
+
table.add_row(Text("─" * 60, style="dim"))
|
|
257
|
+
|
|
258
|
+
return table
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Presentation layer for milestone list command.
|
|
2
|
+
|
|
3
|
+
Handles all display logic related to:
|
|
4
|
+
- Rendering milestone tables
|
|
5
|
+
- Formatting milestone data
|
|
6
|
+
- Styling milestone information
|
|
7
|
+
- Empty state handling
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from roadmap.common.console import get_console
|
|
13
|
+
from roadmap.common.logging import get_logger
|
|
14
|
+
|
|
15
|
+
console = get_console()
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MilestoneTablePresenter:
|
|
20
|
+
"""Presenter for rendering milestone data in table format."""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def display_milestone_table(
|
|
24
|
+
milestones: list,
|
|
25
|
+
progress: dict,
|
|
26
|
+
estimates: dict,
|
|
27
|
+
get_due_date_status_fn,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Display milestones in a formatted Rich table.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
milestones: List of milestone objects
|
|
33
|
+
progress: Dictionary mapping milestone names to progress dicts
|
|
34
|
+
estimates: Dictionary mapping milestone names to estimate strings
|
|
35
|
+
get_due_date_status_fn: Function to get due date string and styling
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
39
|
+
table.add_column("Name", style="cyan")
|
|
40
|
+
table.add_column("Description", style="white")
|
|
41
|
+
table.add_column("Due Date", style="yellow", width=12)
|
|
42
|
+
table.add_column("Status", style="green", width=10)
|
|
43
|
+
table.add_column("Progress", style="blue", width=12)
|
|
44
|
+
table.add_column("Estimate", style="green", width=10)
|
|
45
|
+
|
|
46
|
+
for ms in milestones:
|
|
47
|
+
# Get progress
|
|
48
|
+
ms_progress = progress.get(ms.name, {})
|
|
49
|
+
progress_text = (
|
|
50
|
+
f"{ms_progress.get('completed', 0)}/{ms_progress.get('total', 0)}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Get estimate
|
|
54
|
+
estimate_text = estimates.get(ms.name, "-")
|
|
55
|
+
|
|
56
|
+
# Format due date with styling
|
|
57
|
+
due_date_text, style = get_due_date_status_fn(ms)
|
|
58
|
+
if style:
|
|
59
|
+
due_date_text = f"[{style}]{due_date_text}[/{style}]"
|
|
60
|
+
|
|
61
|
+
# Add row to table
|
|
62
|
+
table.add_row(
|
|
63
|
+
ms.name,
|
|
64
|
+
ms.content or "-",
|
|
65
|
+
due_date_text,
|
|
66
|
+
ms.status.value,
|
|
67
|
+
progress_text,
|
|
68
|
+
estimate_text,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
console.print(table)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(
|
|
74
|
+
"failed_to_display_milestone_table",
|
|
75
|
+
error=str(e),
|
|
76
|
+
severity="operational",
|
|
77
|
+
)
|
|
78
|
+
console.print("❌ Failed to display milestone table", style="bold red")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class MilestoneListPresenter:
|
|
82
|
+
"""Presenter for milestone list display."""
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def show_empty_state() -> None:
|
|
86
|
+
"""Display empty state when no milestones found."""
|
|
87
|
+
console.print("📋 No milestones found.", style="yellow")
|
|
88
|
+
console.print(
|
|
89
|
+
"Create one with: roadmap milestone create 'Milestone name'",
|
|
90
|
+
style="dim",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def show_no_upcoming_milestones() -> None:
|
|
95
|
+
"""Display message when no upcoming milestones with due dates found."""
|
|
96
|
+
console.print("📋 No upcoming milestones with due dates found.", style="yellow")
|
|
97
|
+
console.print(
|
|
98
|
+
"Create one with: roadmap milestone create 'Milestone name' --due-date YYYY-MM-DD",
|
|
99
|
+
style="dim",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def show_milestones_list(
|
|
104
|
+
milestones_data: dict,
|
|
105
|
+
get_due_date_status_fn,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Display list of milestones.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
milestones_data: Dictionary with milestones, progress, estimates, etc.
|
|
111
|
+
get_due_date_status_fn: Function to get due date string and styling
|
|
112
|
+
"""
|
|
113
|
+
if not milestones_data["has_data"]:
|
|
114
|
+
MilestoneListPresenter.show_empty_state()
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Display table
|
|
118
|
+
MilestoneTablePresenter.display_milestone_table(
|
|
119
|
+
milestones_data["milestones"],
|
|
120
|
+
milestones_data["progress"],
|
|
121
|
+
milestones_data["estimates"],
|
|
122
|
+
get_due_date_status_fn,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def show_error(error_msg: str) -> None:
|
|
127
|
+
"""Display error message.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
error_msg: Error message to display
|
|
131
|
+
"""
|
|
132
|
+
console.print(
|
|
133
|
+
f"❌ Failed to list milestones: {error_msg}",
|
|
134
|
+
style="bold red",
|
|
135
|
+
)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""CLI presenter for Milestone DTOs."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from rich.markdown import Markdown
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from roadmap.adapters.cli.dtos import MilestoneDTO
|
|
10
|
+
from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
|
|
11
|
+
from roadmap.adapters.cli.presentation.table_builders import (
|
|
12
|
+
create_list_table,
|
|
13
|
+
create_metadata_table,
|
|
14
|
+
)
|
|
15
|
+
from roadmap.adapters.cli.styling import PRIORITY_COLORS, STATUS_COLORS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MilestonePresenter(BasePresenter):
|
|
19
|
+
"""Presenter for formatting and displaying Milestone DTOs."""
|
|
20
|
+
|
|
21
|
+
def render(
|
|
22
|
+
self,
|
|
23
|
+
milestone_dto: MilestoneDTO,
|
|
24
|
+
issues: list | None = None,
|
|
25
|
+
progress_data: dict | None = None,
|
|
26
|
+
description_content: str | None = None,
|
|
27
|
+
comments_text: str | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Render a milestone DTO in detailed view.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
milestone_dto: The MilestoneDTO to render
|
|
33
|
+
issues: Optional list of issues to display
|
|
34
|
+
progress_data: Optional progress information dict with 'completed' and 'total' keys
|
|
35
|
+
description_content: Optional content with description and goals
|
|
36
|
+
comments_text: Optional formatted comments text
|
|
37
|
+
"""
|
|
38
|
+
# Display header
|
|
39
|
+
header = self._build_milestone_header(milestone_dto)
|
|
40
|
+
self._get_console().print(Panel(header, border_style="cyan"))
|
|
41
|
+
|
|
42
|
+
# Display progress if provided
|
|
43
|
+
if progress_data:
|
|
44
|
+
self._display_progress_panel(progress_data)
|
|
45
|
+
|
|
46
|
+
# Display metadata
|
|
47
|
+
metadata = self._build_metadata_table(milestone_dto)
|
|
48
|
+
self._get_console().print(
|
|
49
|
+
Panel(metadata, title="📈 Statistics", border_style="blue")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Display issues if provided
|
|
53
|
+
if issues is not None:
|
|
54
|
+
self._display_issues_panel(milestone_dto.name, issues)
|
|
55
|
+
|
|
56
|
+
# Display description and goals if provided
|
|
57
|
+
if description_content:
|
|
58
|
+
self._display_description_and_goals(description_content)
|
|
59
|
+
|
|
60
|
+
# Display comments if provided
|
|
61
|
+
if comments_text:
|
|
62
|
+
self._get_console().print(
|
|
63
|
+
Panel(
|
|
64
|
+
comments_text,
|
|
65
|
+
title="💬 Comments",
|
|
66
|
+
border_style="cyan",
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Display metadata footer
|
|
71
|
+
metadata_footer = (
|
|
72
|
+
f"Created: {milestone_dto.created} • Updated: {milestone_dto.updated}"
|
|
73
|
+
)
|
|
74
|
+
self._get_console().print(f"\n[dim]{metadata_footer}[/dim]")
|
|
75
|
+
|
|
76
|
+
def _build_milestone_header(self, milestone_dto: MilestoneDTO) -> Text:
|
|
77
|
+
"""Build header text with milestone name and status.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
milestone_dto: The MilestoneDTO to format
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Rich Text object with formatted header
|
|
84
|
+
"""
|
|
85
|
+
status_color = "green" if milestone_dto.status == "closed" else "yellow"
|
|
86
|
+
|
|
87
|
+
header = Text()
|
|
88
|
+
header.append(milestone_dto.name, style="bold cyan")
|
|
89
|
+
header.append("\n")
|
|
90
|
+
header.append(f"[{milestone_dto.status.upper()}]", style=f"bold {status_color}")
|
|
91
|
+
|
|
92
|
+
# Add due date if present
|
|
93
|
+
if milestone_dto.due_date:
|
|
94
|
+
due_date_str = (
|
|
95
|
+
milestone_dto.due_date.strftime("%Y-%m-%d")
|
|
96
|
+
if hasattr(milestone_dto.due_date, "strftime")
|
|
97
|
+
else milestone_dto.due_date
|
|
98
|
+
)
|
|
99
|
+
now = datetime.now(UTC)
|
|
100
|
+
|
|
101
|
+
due_date_obj = (
|
|
102
|
+
milestone_dto.due_date
|
|
103
|
+
if hasattr(milestone_dto.due_date, "replace")
|
|
104
|
+
else datetime.fromisoformat(str(milestone_dto.due_date))
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Normalize due date to an aware datetime in UTC so comparisons
|
|
108
|
+
# between now (UTC-aware) and the due date are consistent.
|
|
109
|
+
if isinstance(due_date_obj, datetime):
|
|
110
|
+
if due_date_obj.tzinfo:
|
|
111
|
+
due_date_aware = due_date_obj.astimezone(UTC)
|
|
112
|
+
else:
|
|
113
|
+
due_date_aware = due_date_obj.replace(tzinfo=UTC)
|
|
114
|
+
else:
|
|
115
|
+
due_date_aware = datetime.fromisoformat(str(due_date_obj)).replace(
|
|
116
|
+
tzinfo=UTC
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
is_overdue = due_date_aware < now and milestone_dto.status == "open"
|
|
120
|
+
|
|
121
|
+
header.append(" • ", style="dim")
|
|
122
|
+
if is_overdue:
|
|
123
|
+
days_overdue = (now - due_date_aware).days
|
|
124
|
+
header.append(f"⚠️ OVERDUE by {days_overdue} days", style="bold red")
|
|
125
|
+
header.append(f" (Due: {due_date_str})", style="red")
|
|
126
|
+
else:
|
|
127
|
+
header.append(f"Due: {due_date_str}", style="white")
|
|
128
|
+
|
|
129
|
+
return header
|
|
130
|
+
|
|
131
|
+
def _build_metadata_table(self, milestone_dto: MilestoneDTO):
|
|
132
|
+
"""Build metadata table for milestone DTO.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
milestone_dto: The MilestoneDTO to format
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Rich Table with metadata rows
|
|
139
|
+
"""
|
|
140
|
+
metadata = create_metadata_table()
|
|
141
|
+
|
|
142
|
+
metadata.add_row("Status", milestone_dto.status.upper())
|
|
143
|
+
|
|
144
|
+
if isinstance(milestone_dto.due_date, datetime):
|
|
145
|
+
metadata.add_row("Due Date", milestone_dto.due_date.strftime("%Y-%m-%d"))
|
|
146
|
+
else:
|
|
147
|
+
metadata.add_row("Due Date", "Not set")
|
|
148
|
+
|
|
149
|
+
if milestone_dto.headline:
|
|
150
|
+
metadata.add_row("Headline", milestone_dto.headline)
|
|
151
|
+
|
|
152
|
+
metadata.add_row("Progress", f"{milestone_dto.progress_percentage}%")
|
|
153
|
+
metadata.add_row(
|
|
154
|
+
"Issues",
|
|
155
|
+
f"{milestone_dto.completed_count}/{milestone_dto.issue_count}",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if isinstance(milestone_dto.created, datetime):
|
|
159
|
+
metadata.add_row(
|
|
160
|
+
"Created", milestone_dto.created.strftime("%Y-%m-%d %H:%M")
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if isinstance(milestone_dto.updated, datetime):
|
|
164
|
+
metadata.add_row(
|
|
165
|
+
"Updated", milestone_dto.updated.strftime("%Y-%m-%d %H:%M")
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return metadata
|
|
169
|
+
|
|
170
|
+
def _display_progress_panel(self, progress_data: dict) -> None:
|
|
171
|
+
"""Display milestone progress as a panel.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
progress_data: Dict with 'completed' and 'total' keys
|
|
175
|
+
"""
|
|
176
|
+
completed = progress_data.get("completed", 0)
|
|
177
|
+
total = progress_data.get("total", 0)
|
|
178
|
+
percentage = (completed / total * 100) if total > 0 else 0
|
|
179
|
+
|
|
180
|
+
progress_table = create_metadata_table()
|
|
181
|
+
progress_table.add_row("Issues Complete", f"{completed}/{total}")
|
|
182
|
+
progress_table.add_row("Percentage", f"{percentage:.1f}%")
|
|
183
|
+
|
|
184
|
+
self._get_console().print(
|
|
185
|
+
Panel(
|
|
186
|
+
progress_table,
|
|
187
|
+
title="📊 Progress",
|
|
188
|
+
border_style="green" if percentage > 50 else "yellow",
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def _display_issues_panel(self, milestone_name: str, issues: list) -> None:
|
|
193
|
+
"""Display issues in a table panel.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
milestone_name: Milestone name used in follow-up guidance
|
|
197
|
+
issues: List of issue objects with status, priority, etc.
|
|
198
|
+
"""
|
|
199
|
+
if not issues:
|
|
200
|
+
self._get_console().print(
|
|
201
|
+
Panel(
|
|
202
|
+
"[dim]No issues assigned to this milestone[/dim]",
|
|
203
|
+
title="📋 Issues",
|
|
204
|
+
border_style="magenta",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
columns = [
|
|
210
|
+
("ID", "cyan", 9),
|
|
211
|
+
("Title", "white", 20),
|
|
212
|
+
("Status", None, 11),
|
|
213
|
+
("Priority", None, 9),
|
|
214
|
+
("Assignee", None, 12),
|
|
215
|
+
("Progress", None, 10),
|
|
216
|
+
("Estimate", None, 10),
|
|
217
|
+
]
|
|
218
|
+
issues_table = create_list_table(columns)
|
|
219
|
+
|
|
220
|
+
for issue in issues[:10]:
|
|
221
|
+
status_color = STATUS_COLORS.get(issue.status.value, "white")
|
|
222
|
+
priority_color = PRIORITY_COLORS.get(issue.priority.value, "white")
|
|
223
|
+
assignee_display = (
|
|
224
|
+
issue.assignee if issue.assignee else "[dim]Unassigned[/dim]"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
issues_table.add_row(
|
|
228
|
+
issue.id,
|
|
229
|
+
issue.title[:50] + "..." if len(issue.title) > 50 else issue.title,
|
|
230
|
+
f"[{status_color}]{issue.status.value}[/{status_color}]",
|
|
231
|
+
f"[{priority_color}]{issue.priority.value}[/{priority_color}]",
|
|
232
|
+
assignee_display,
|
|
233
|
+
issue.progress_display,
|
|
234
|
+
issue.estimated_time_display,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
title = (
|
|
238
|
+
f"📋 Issues (First 10 of {len(issues)})"
|
|
239
|
+
if len(issues) > 10
|
|
240
|
+
else "📋 Issues"
|
|
241
|
+
)
|
|
242
|
+
self._get_console().print(
|
|
243
|
+
Panel(issues_table, title=title, border_style="magenta")
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if len(issues) > 10:
|
|
247
|
+
self._get_console().print(
|
|
248
|
+
"[dim]Showing first 10 issues in milestone view. "
|
|
249
|
+
f"Use 'roadmap issue list -m {milestone_name}' to see all issues.[/dim]"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _display_description_and_goals(self, content: str) -> None:
|
|
253
|
+
"""Display description and goals sections from content.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
content: Raw content with markdown sections
|
|
257
|
+
"""
|
|
258
|
+
description, goals = self._extract_description_and_goals(content)
|
|
259
|
+
|
|
260
|
+
if description:
|
|
261
|
+
md = Markdown(description)
|
|
262
|
+
self._get_console().print(
|
|
263
|
+
Panel(md, title="📝 Description", border_style="white")
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if goals:
|
|
267
|
+
md = Markdown(goals)
|
|
268
|
+
self._get_console().print(Panel(md, title="🎯 Goals", border_style="green"))
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _extract_description_and_goals(content: str) -> tuple[str, str]:
|
|
272
|
+
"""Parse content to extract description and goals sections.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
content: Raw content string with markdown sections
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Tuple of (description, goals) strings
|
|
279
|
+
"""
|
|
280
|
+
content_lines = content.split("\n")
|
|
281
|
+
description_lines = []
|
|
282
|
+
goals_lines = []
|
|
283
|
+
in_goals = False
|
|
284
|
+
|
|
285
|
+
for line in content_lines:
|
|
286
|
+
if "## Goals" in line or "## goals" in line.lower():
|
|
287
|
+
in_goals = True
|
|
288
|
+
continue
|
|
289
|
+
elif line.startswith("## ") and in_goals:
|
|
290
|
+
in_goals = False
|
|
291
|
+
|
|
292
|
+
if in_goals:
|
|
293
|
+
goals_lines.append(line)
|
|
294
|
+
elif not line.startswith("#"):
|
|
295
|
+
description_lines.append(line)
|
|
296
|
+
|
|
297
|
+
return "\n".join(description_lines).strip(), "\n".join(goals_lines).strip()
|