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,319 @@
|
|
|
1
|
+
"""Unified output management for CLI commands.
|
|
2
|
+
|
|
3
|
+
Centralizes all CLI output handling across:
|
|
4
|
+
- Multiple formats (table/json/csv/markdown)
|
|
5
|
+
- Terminal vs file output
|
|
6
|
+
- Test-friendly rendering (ANSI stripping)
|
|
7
|
+
- Format auto-detection
|
|
8
|
+
|
|
9
|
+
This replaces scattered console.print() and click.secho() calls with
|
|
10
|
+
a unified interface that supports exporting to files.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
from roadmap.adapters.cli.layout import SmartTableLayout
|
|
21
|
+
from roadmap.common.models import TableData
|
|
22
|
+
from roadmap.common.output_formatter import OutputFormatter
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OutputManager:
|
|
26
|
+
"""Unified output management for CLI commands.
|
|
27
|
+
|
|
28
|
+
Handles rendering of TableData in multiple formats:
|
|
29
|
+
- table: Pretty Rich table (default, terminal-friendly)
|
|
30
|
+
- plain: ASCII table (POSIX-compliant, no ANSI codes)
|
|
31
|
+
- json: Machine-readable JSON (API/data export)
|
|
32
|
+
- csv: RFC 4180 CSV (Excel/Sheets import)
|
|
33
|
+
- markdown: Markdown table (documentation)
|
|
34
|
+
- html: HTML table with styling (email/web)
|
|
35
|
+
|
|
36
|
+
Can output to terminal or file with automatic format detection.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
# Terminal output
|
|
40
|
+
manager = OutputManager(format='table')
|
|
41
|
+
manager.render_table(table_data)
|
|
42
|
+
|
|
43
|
+
# File output
|
|
44
|
+
manager = OutputManager(
|
|
45
|
+
format='csv',
|
|
46
|
+
output_file=Path('/tmp/export.csv')
|
|
47
|
+
)
|
|
48
|
+
manager.render_table(table_data)
|
|
49
|
+
|
|
50
|
+
# In Click commands
|
|
51
|
+
@click.option('--format', type=click.Choice(['table', 'json', 'csv', 'html']))
|
|
52
|
+
@click.option('--output', type=click.Path())
|
|
53
|
+
def list_items(format, output):
|
|
54
|
+
manager = OutputManager(
|
|
55
|
+
format=format or 'table',
|
|
56
|
+
output_file=Path(output) if output else None
|
|
57
|
+
)
|
|
58
|
+
table_data = IssueTableFormatter.issues_to_table_data(items)
|
|
59
|
+
manager.render_table(table_data)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
VALID_FORMATS = {"table", "plain", "json", "csv", "markdown", "html"}
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
format: str = "table",
|
|
67
|
+
output_file: Path | None = None,
|
|
68
|
+
force_plain: bool = False,
|
|
69
|
+
):
|
|
70
|
+
"""Initialize OutputManager.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
format: Output format (table/plain/json/csv/markdown/html)
|
|
74
|
+
output_file: Optional file path to save output
|
|
75
|
+
force_plain: Force plain text output (for testing)
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If format is not valid
|
|
79
|
+
"""
|
|
80
|
+
if format not in self.VALID_FORMATS:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Invalid format '{format}'. Must be one of: {', '.join(self.VALID_FORMATS)}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.format = format
|
|
86
|
+
self.output_file = output_file
|
|
87
|
+
self.force_plain = force_plain
|
|
88
|
+
|
|
89
|
+
# Determine color support
|
|
90
|
+
should_use_colors = self._should_use_colors()
|
|
91
|
+
|
|
92
|
+
# Initialize console for Rich output
|
|
93
|
+
# Use test-safe settings that work with Click's test runner
|
|
94
|
+
is_testing = self._is_testing_environment()
|
|
95
|
+
self.console = Console(
|
|
96
|
+
file=sys.stdout,
|
|
97
|
+
force_terminal=should_use_colors and not is_testing,
|
|
98
|
+
no_color=not should_use_colors or is_testing or force_plain,
|
|
99
|
+
width=80 if is_testing else 120,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Initialize smart layout system for responsive table rendering
|
|
103
|
+
self.layout = SmartTableLayout()
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _is_testing_environment() -> bool:
|
|
107
|
+
"""Detect if running in a test environment."""
|
|
108
|
+
return any(
|
|
109
|
+
[
|
|
110
|
+
"PYTEST_CURRENT_TEST" in os.environ,
|
|
111
|
+
"pytest" in sys.modules,
|
|
112
|
+
"_pytest" in [m.split(".")[0] for m in sys.modules.keys()],
|
|
113
|
+
os.environ.get("NO_COLOR") in ("1", "true"),
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _should_use_colors() -> bool:
|
|
119
|
+
"""Detect if terminal supports colors.
|
|
120
|
+
|
|
121
|
+
Checks:
|
|
122
|
+
1. NO_COLOR env var (standard no-color support)
|
|
123
|
+
2. FORCE_COLOR env var (force colors even in non-TTY)
|
|
124
|
+
3. Terminal type (dumb, emacs, etc. don't support colors)
|
|
125
|
+
4. isatty() - check if stdout is a terminal
|
|
126
|
+
5. CI environment detection
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if colors should be used, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
# Check NO_COLOR first (highest priority to disable)
|
|
132
|
+
if os.environ.get("NO_COLOR", "").lower() in ("1", "true", "yes"):
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Check FORCE_COLOR (explicit enable)
|
|
136
|
+
if os.environ.get("FORCE_COLOR", "").lower() in ("1", "true", "yes"):
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
# Check terminal type
|
|
140
|
+
term = os.environ.get("TERM", "").lower()
|
|
141
|
+
if term in ("dumb", "emacs", "milk"):
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Check if stdout is a TTY
|
|
145
|
+
try:
|
|
146
|
+
if not sys.stdout.isatty():
|
|
147
|
+
return False
|
|
148
|
+
except (AttributeError, Exception):
|
|
149
|
+
# If isatty() fails, assume no colors
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
# Check for CI environments (usually don't support interactive colors)
|
|
153
|
+
ci_env_vars = [
|
|
154
|
+
"CI",
|
|
155
|
+
"CONTINUOUS_INTEGRATION",
|
|
156
|
+
"GITHUB_ACTIONS",
|
|
157
|
+
"GITLAB_CI",
|
|
158
|
+
"TRAVIS",
|
|
159
|
+
"CIRCLECI",
|
|
160
|
+
"JENKINS_URL",
|
|
161
|
+
"BUILDKITE",
|
|
162
|
+
"DRONE",
|
|
163
|
+
]
|
|
164
|
+
if any(var in os.environ for var in ci_env_vars):
|
|
165
|
+
# CI is set, but some CI systems do support colors
|
|
166
|
+
# Check for specific ones that support colors
|
|
167
|
+
if os.environ.get("GITHUB_ACTIONS") == "true":
|
|
168
|
+
return True # GitHub Actions supports colors
|
|
169
|
+
if os.environ.get("GITLAB_CI") == "true":
|
|
170
|
+
return True # GitLab CI supports colors
|
|
171
|
+
# Others might not, so be conservative
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
# Default: use colors (most modern terminals support them)
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
def render_table(self, table_data: TableData) -> None:
|
|
178
|
+
"""Render a table in the configured format.
|
|
179
|
+
|
|
180
|
+
Handles both terminal output and file saves.
|
|
181
|
+
Automatically detects test environment and strips ANSI codes.
|
|
182
|
+
For 'table' format, uses smart responsive layout system.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
table_data: TableData object to render
|
|
186
|
+
"""
|
|
187
|
+
formatter = OutputFormatter(table_data)
|
|
188
|
+
|
|
189
|
+
if self.format == "table":
|
|
190
|
+
# Use smart responsive layout for 'table' format
|
|
191
|
+
renderable = self.layout.render(table_data)
|
|
192
|
+
self.console.print(renderable)
|
|
193
|
+
|
|
194
|
+
elif self.format == "plain":
|
|
195
|
+
# Plain ASCII for POSIX compatibility
|
|
196
|
+
content = formatter.to_plain_text()
|
|
197
|
+
self._output_content(content)
|
|
198
|
+
|
|
199
|
+
elif self.format == "json":
|
|
200
|
+
# JSON for data export
|
|
201
|
+
content = formatter.to_json()
|
|
202
|
+
self._output_content(content)
|
|
203
|
+
|
|
204
|
+
elif self.format == "csv":
|
|
205
|
+
# CSV for spreadsheet import
|
|
206
|
+
content = formatter.to_csv()
|
|
207
|
+
self._output_content(content)
|
|
208
|
+
|
|
209
|
+
elif self.format == "markdown":
|
|
210
|
+
# Markdown for documentation
|
|
211
|
+
content = formatter.to_markdown()
|
|
212
|
+
self._output_content(content)
|
|
213
|
+
|
|
214
|
+
elif self.format == "html":
|
|
215
|
+
# HTML for email/web
|
|
216
|
+
content = formatter.to_html()
|
|
217
|
+
self._output_content(content)
|
|
218
|
+
|
|
219
|
+
def render_rich(self, renderable) -> None:
|
|
220
|
+
"""Render a Rich-compatible object.
|
|
221
|
+
|
|
222
|
+
Useful for rendering non-table objects like Panels, etc.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
renderable: Any Rich-compatible renderable object
|
|
226
|
+
"""
|
|
227
|
+
self.console.print(renderable)
|
|
228
|
+
|
|
229
|
+
def _output_content(self, content: str) -> None:
|
|
230
|
+
"""Output content to file or stdout.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
content: String content to output
|
|
234
|
+
"""
|
|
235
|
+
if self.output_file:
|
|
236
|
+
self.output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
self.output_file.write_text(content)
|
|
238
|
+
click.secho(f"â
Saved to {self.output_file}", fg="green")
|
|
239
|
+
else:
|
|
240
|
+
click.echo(content)
|
|
241
|
+
|
|
242
|
+
def print_message(
|
|
243
|
+
self, message: str, style: str | None = None, fg: str | None = None
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Print a simple message (not a table).
|
|
246
|
+
|
|
247
|
+
Useful for status messages, warnings, etc.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
message: Message to print
|
|
251
|
+
style: Rich style (e.g., 'bold red')
|
|
252
|
+
fg: Click color (e.g., 'green')
|
|
253
|
+
"""
|
|
254
|
+
if self.format in {"json", "csv", "markdown"}:
|
|
255
|
+
# Skip non-data messages in data export formats
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if self.format == "table" or self.format == "plain":
|
|
259
|
+
if fg:
|
|
260
|
+
click.secho(message, fg=fg)
|
|
261
|
+
elif style:
|
|
262
|
+
self.console.print(message, style=style)
|
|
263
|
+
else:
|
|
264
|
+
click.echo(message)
|
|
265
|
+
|
|
266
|
+
def print_styled(
|
|
267
|
+
self,
|
|
268
|
+
text: str,
|
|
269
|
+
color: str | None = None,
|
|
270
|
+
bold: bool = False,
|
|
271
|
+
italic: bool = False,
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Print styled text (terminal output only).
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
text: Text to print
|
|
277
|
+
color: Color name (red, green, yellow, etc.)
|
|
278
|
+
bold: Make bold
|
|
279
|
+
italic: Make italic
|
|
280
|
+
"""
|
|
281
|
+
if self.format in {"json", "csv", "markdown"}:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
if color or bold or italic:
|
|
285
|
+
click.secho(text, fg=color, bold=bold)
|
|
286
|
+
else:
|
|
287
|
+
click.echo(text)
|
|
288
|
+
|
|
289
|
+
def print_section_header(self, title: str) -> None:
|
|
290
|
+
"""Print a section header.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
title: Section title
|
|
294
|
+
"""
|
|
295
|
+
if self.format in {"json", "csv", "markdown"}:
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
click.secho(f"\n{title}", fg="cyan", bold=True)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# Convenience function for quick output
|
|
302
|
+
def create_output_manager(
|
|
303
|
+
format: str | None = None, output_file: str | None = None
|
|
304
|
+
) -> OutputManager:
|
|
305
|
+
"""Create an OutputManager with optional CLI arguments.
|
|
306
|
+
|
|
307
|
+
Useful for Click commands with --format and --output options.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
format: Output format or None (uses 'table' default)
|
|
311
|
+
output_file: Path to output file or None (uses stdout)
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Configured OutputManager instance
|
|
315
|
+
"""
|
|
316
|
+
return OutputManager(
|
|
317
|
+
format=format or "table",
|
|
318
|
+
output_file=Path(output_file) if output_file else None,
|
|
319
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Base presenter class for all CLI presenters.
|
|
2
|
+
|
|
3
|
+
This module provides an abstract base class that defines the interface for all
|
|
4
|
+
presenters in the CLI layer. All presenters should inherit from BasePresenter
|
|
5
|
+
to ensure consistent method signatures and behavior.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from roadmap.common.console import get_console
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BasePresenter(ABC):
|
|
17
|
+
"""Abstract base class for all CLI presenters.
|
|
18
|
+
|
|
19
|
+
Defines the standard interface that all presenters must implement.
|
|
20
|
+
Presenters are responsible for formatting and rendering data to the console.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
class MyPresenter(BasePresenter):
|
|
24
|
+
def render(self, data: dict) -> None:
|
|
25
|
+
console = get_console()
|
|
26
|
+
console.print(data["title"])
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def render(self, data: Any) -> None:
|
|
31
|
+
"""Render data to console.
|
|
32
|
+
|
|
33
|
+
This is the main entry point for presentation. Implementers should
|
|
34
|
+
format the provided data and output it using the console.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data: The data to render. Type depends on specific presenter.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If data is invalid or incomplete
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
45
|
+
# Common Utility Methods (Optional for subclasses to use)
|
|
46
|
+
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def _get_console() -> Console:
|
|
50
|
+
"""Get the standard console instance.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The global console instance for output
|
|
54
|
+
"""
|
|
55
|
+
return get_console()
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _render_header(title: str, style: str = "bold cyan") -> None:
|
|
59
|
+
"""Render a section header.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
title: Header text
|
|
63
|
+
style: Rich style string (default: bold cyan)
|
|
64
|
+
"""
|
|
65
|
+
console = get_console()
|
|
66
|
+
console.print(f"\n[{style}]{title}[/{style}]")
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _render_section(title: str, content: str, style: str = "dim") -> None:
|
|
70
|
+
"""Render a named section with indented content.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
title: Section title
|
|
74
|
+
content: Section content
|
|
75
|
+
style: Rich style for title (default: dim)
|
|
76
|
+
"""
|
|
77
|
+
console = get_console()
|
|
78
|
+
console.print(f"[{style}]{title}:[/{style}] {content}")
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _render_footer(message: str | None = None) -> None:
|
|
82
|
+
"""Render footer content.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
message: Optional footer message
|
|
86
|
+
"""
|
|
87
|
+
console = get_console()
|
|
88
|
+
if message:
|
|
89
|
+
console.print(f"\n[dim]{message}[/dim]")
|
|
90
|
+
else:
|
|
91
|
+
console.print()
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _render_warning(message: str) -> None:
|
|
95
|
+
"""Render a warning message.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
message: Warning text
|
|
99
|
+
"""
|
|
100
|
+
console = get_console()
|
|
101
|
+
console.print(f"[yellow]â ď¸ {message}[/yellow]")
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _render_error(message: str) -> None:
|
|
105
|
+
"""Render an error message.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
message: Error text
|
|
109
|
+
"""
|
|
110
|
+
console = get_console()
|
|
111
|
+
console.print(f"[red]â {message}[/red]")
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _render_success(message: str) -> None:
|
|
115
|
+
"""Render a success message.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
message: Success text
|
|
119
|
+
"""
|
|
120
|
+
console = get_console()
|
|
121
|
+
console.print(f"[green]â
{message}[/green]")
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Presenter for cleanup command output."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from roadmap.core.services.health.backup_cleanup_service import (
|
|
9
|
+
BackupCleanupResult as BackupResult,
|
|
10
|
+
)
|
|
11
|
+
from roadmap.core.services.health.file_repair_service import FileRepairResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CleanupPresenter:
|
|
15
|
+
"""Handles all console output for cleanup operations."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize presenter."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def present_no_roadmap(self) -> None:
|
|
22
|
+
"""Show message that roadmap is not initialized."""
|
|
23
|
+
click.secho(
|
|
24
|
+
"â Roadmap not initialized. Run 'roadmap init' first.",
|
|
25
|
+
fg="red",
|
|
26
|
+
bold=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def present_no_issues_dir(self) -> None:
|
|
30
|
+
"""Show message that issues directory not found."""
|
|
31
|
+
click.secho("đ No issues directory found.", fg="yellow")
|
|
32
|
+
|
|
33
|
+
def present_folder_check_clean(self) -> None:
|
|
34
|
+
"""Show message that all issues are in correct folders."""
|
|
35
|
+
click.secho(
|
|
36
|
+
"â
All issues appear to be in correct folders.",
|
|
37
|
+
fg="green",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def present_folder_issues(self, issues: dict[str, Any]) -> None:
|
|
41
|
+
"""Show folder structure issues found."""
|
|
42
|
+
if "misplaced" in issues and issues["misplaced"]:
|
|
43
|
+
click.secho(
|
|
44
|
+
f"\nâ ď¸ Found {len(issues['misplaced'])} issue(s) in wrong folders:\n",
|
|
45
|
+
fg="yellow",
|
|
46
|
+
bold=True,
|
|
47
|
+
)
|
|
48
|
+
for item in issues["misplaced"]:
|
|
49
|
+
click.secho(f" ⢠{item['issue_id']}: {item['title']}", fg="cyan")
|
|
50
|
+
click.echo(f" Current: {item['current_location']}")
|
|
51
|
+
click.echo(f" Expected: {item['expected_location']}")
|
|
52
|
+
click.echo()
|
|
53
|
+
|
|
54
|
+
if "orphaned" in issues and issues["orphaned"]:
|
|
55
|
+
click.secho(
|
|
56
|
+
f"\nâ ď¸ Found {len(issues['orphaned'])} orphaned issue(s):\n",
|
|
57
|
+
fg="yellow",
|
|
58
|
+
bold=True,
|
|
59
|
+
)
|
|
60
|
+
for item in issues["orphaned"]:
|
|
61
|
+
click.secho(f" ⢠{item['issue_id']}: {item['title']}", fg="cyan")
|
|
62
|
+
click.echo(f" Location: {item['location']}")
|
|
63
|
+
click.echo()
|
|
64
|
+
|
|
65
|
+
def present_duplicates_check_clean(self) -> None:
|
|
66
|
+
"""Show message that no duplicates found."""
|
|
67
|
+
click.secho(
|
|
68
|
+
"â
No duplicate issues found.",
|
|
69
|
+
fg="green",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def present_duplicate_issues(
|
|
73
|
+
self, duplicates: dict[str, list[Path]], roadmap_dir: Path
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Show duplicate issues found."""
|
|
76
|
+
click.secho(
|
|
77
|
+
f"\nâ ď¸ Found {len(duplicates)} issue(s) with multiple copies:\n",
|
|
78
|
+
fg="yellow",
|
|
79
|
+
bold=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for issue_id, files in sorted(duplicates.items()):
|
|
83
|
+
click.secho(f" Issue ID: {issue_id}", fg="cyan")
|
|
84
|
+
for file_path in sorted(files):
|
|
85
|
+
click.echo(f" ⢠{file_path.relative_to(roadmap_dir)}")
|
|
86
|
+
click.echo()
|
|
87
|
+
|
|
88
|
+
def present_malformed_check_clean(self) -> None:
|
|
89
|
+
"""Show message that no malformed files found."""
|
|
90
|
+
click.secho(
|
|
91
|
+
"â
No malformed YAML files found.",
|
|
92
|
+
fg="green",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def present_malformed_files(self, malformed_files: list[str]) -> None:
|
|
96
|
+
"""Show malformed files found."""
|
|
97
|
+
click.secho(
|
|
98
|
+
f"\nâ ď¸ Found {len(malformed_files)} malformed file(s):\n",
|
|
99
|
+
fg="yellow",
|
|
100
|
+
bold=True,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for file_rel in malformed_files:
|
|
104
|
+
click.secho(f" ⢠{file_rel}", fg="cyan")
|
|
105
|
+
click.echo()
|
|
106
|
+
|
|
107
|
+
def present_backup_cleanup_dry_run(
|
|
108
|
+
self, files_to_delete: list[dict[str, Any]]
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Show what backups would be deleted in dry-run mode."""
|
|
111
|
+
total_size = sum(f["size"] for f in files_to_delete)
|
|
112
|
+
size_mb = total_size / (1024 * 1024)
|
|
113
|
+
|
|
114
|
+
click.secho(
|
|
115
|
+
f"\nđ [DRY RUN] Would delete {len(files_to_delete)} backup file(s) ({size_mb:.2f} MB):\n",
|
|
116
|
+
fg="blue",
|
|
117
|
+
bold=True,
|
|
118
|
+
)
|
|
119
|
+
for backup in files_to_delete:
|
|
120
|
+
click.secho(
|
|
121
|
+
f" ⢠{backup['path'].name} ({backup['size']} bytes)",
|
|
122
|
+
fg="cyan",
|
|
123
|
+
)
|
|
124
|
+
click.secho(
|
|
125
|
+
f"\nWould free approximately {size_mb:.2f} MB of disk space.",
|
|
126
|
+
fg="green",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def present_backup_cleanup_result(self, result: BackupResult) -> None:
|
|
130
|
+
"""Show backup cleanup results."""
|
|
131
|
+
click.secho(
|
|
132
|
+
f"â
Cleaned up {result.deleted_count} backup file(s), freed ~{result.freed_mb:.2f} MB",
|
|
133
|
+
fg="green",
|
|
134
|
+
bold=True,
|
|
135
|
+
)
|
|
136
|
+
if result.failed_count > 0:
|
|
137
|
+
click.secho(
|
|
138
|
+
f"â ď¸ {result.failed_count} file(s) failed to delete", fg="yellow"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def present_malformed_repair_result(self, result: FileRepairResult) -> None:
|
|
142
|
+
"""Show malformed file repair results."""
|
|
143
|
+
if result.fixed_files:
|
|
144
|
+
click.secho(
|
|
145
|
+
f"â
Fixed {len(result.fixed_files)} file(s)",
|
|
146
|
+
fg="green",
|
|
147
|
+
)
|
|
148
|
+
for file_rel in result.fixed_files:
|
|
149
|
+
click.echo(f" ⢠{file_rel}")
|
|
150
|
+
|
|
151
|
+
if result.errors:
|
|
152
|
+
click.secho(
|
|
153
|
+
f"â ď¸ Could not fix {len(result.errors)} file(s)",
|
|
154
|
+
fg="yellow",
|
|
155
|
+
)
|
|
156
|
+
for file_rel in result.errors:
|
|
157
|
+
click.echo(f" ⢠{file_rel}")
|
|
158
|
+
|
|
159
|
+
def present_folder_moves(self, moves_to_make: list[dict[str, Any]]) -> None:
|
|
160
|
+
"""Show folder moves that will be made."""
|
|
161
|
+
click.secho(
|
|
162
|
+
f"\nđ§ Resolving {len(moves_to_make)} issue(s) with folder structure problems:\n",
|
|
163
|
+
fg="blue",
|
|
164
|
+
bold=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
for move in moves_to_make:
|
|
168
|
+
click.secho(
|
|
169
|
+
f" {move['issue_id']}: {move['from']} â {move['to']}",
|
|
170
|
+
fg="cyan",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def present_folder_moves_result(self, moved_count: int, failed_count: int) -> None:
|
|
174
|
+
"""Show folder move results."""
|
|
175
|
+
click.secho(
|
|
176
|
+
f"â
Moved {moved_count} issue file(s) to correct folders",
|
|
177
|
+
fg="green",
|
|
178
|
+
bold=True,
|
|
179
|
+
)
|
|
180
|
+
if failed_count > 0:
|
|
181
|
+
click.secho(f"â ď¸ {failed_count} file(s) failed to move", fg="yellow")
|
|
182
|
+
|
|
183
|
+
def present_duplicate_resolution(
|
|
184
|
+
self, duplicates: dict[str, list[Path]], roadmap_dir: Path
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Show duplicate issue resolution."""
|
|
187
|
+
click.secho(
|
|
188
|
+
f"\n�� Resolving {len(duplicates)} duplicate issue(s) (keeping latest):\n",
|
|
189
|
+
fg="blue",
|
|
190
|
+
bold=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
for issue_id, files in sorted(duplicates.items()):
|
|
194
|
+
click.secho(f" Issue ID: {issue_id}", fg="cyan")
|
|
195
|
+
|
|
196
|
+
sorted_files = sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
|
|
197
|
+
keeper = sorted_files[0]
|
|
198
|
+
to_remove = sorted_files[1:]
|
|
199
|
+
|
|
200
|
+
click.secho(f" Keeping: {keeper.relative_to(roadmap_dir)}", fg="green")
|
|
201
|
+
for f in to_remove:
|
|
202
|
+
click.secho(f" Removing: {f.relative_to(roadmap_dir)}", fg="red")
|
|
203
|
+
|
|
204
|
+
def present_duplicate_resolution_result(
|
|
205
|
+
self, deleted_count: int, failed_count: int
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Show duplicate resolution results."""
|
|
208
|
+
click.secho(
|
|
209
|
+
f"â
Deleted {deleted_count} duplicate file(s)",
|
|
210
|
+
fg="green",
|
|
211
|
+
bold=True,
|
|
212
|
+
)
|
|
213
|
+
if failed_count > 0:
|
|
214
|
+
click.secho(f"â ď¸ {failed_count} file(s) failed to delete", fg="yellow")
|
|
215
|
+
|
|
216
|
+
def present_cleanup_error(self, error: str) -> None:
|
|
217
|
+
"""Show cleanup error."""
|
|
218
|
+
click.secho(f"â Cleanup failed: {error}", fg="red", bold=True)
|
|
219
|
+
|
|
220
|
+
def present_dry_run_skipped(self) -> None:
|
|
221
|
+
"""Show message about dry-run skipping certain operations."""
|
|
222
|
+
click.echo(
|
|
223
|
+
"\n[DRY RUN] Folder and duplicate resolution skipped in preview mode"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def present_no_backups(self) -> None:
|
|
227
|
+
"""Show message that no backups found."""
|
|
228
|
+
click.secho("đ No backup files found.", fg="yellow")
|
|
229
|
+
|
|
230
|
+
def present_no_cleanup_needed(self) -> None:
|
|
231
|
+
"""Show message that no cleanup is needed."""
|
|
232
|
+
click.secho(
|
|
233
|
+
"â
No backup files need to be cleaned up.",
|
|
234
|
+
fg="green",
|
|
235
|
+
)
|