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,155 @@
|
|
|
1
|
+
"""Base class for delete operations across all entity types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.cli.crud.crud_helpers import EntityType
|
|
9
|
+
from roadmap.adapters.cli.crud.crud_utils import (
|
|
10
|
+
delete_entity_by_type,
|
|
11
|
+
format_entity_not_found_error,
|
|
12
|
+
get_entity_by_type,
|
|
13
|
+
get_entity_title,
|
|
14
|
+
)
|
|
15
|
+
from roadmap.adapters.cli.presentation.crud_presenter import DeletePresenter
|
|
16
|
+
from roadmap.common.console import get_console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseDelete(ABC):
|
|
20
|
+
"""Abstract base class for entity delete commands.
|
|
21
|
+
|
|
22
|
+
Subclasses implement:
|
|
23
|
+
- entity_type: The EntityType this deletes
|
|
24
|
+
|
|
25
|
+
Uses Template Method pattern for extensibility.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
entity_type: EntityType
|
|
29
|
+
|
|
30
|
+
def __init__(self, core: Any, console: Any = None) -> None:
|
|
31
|
+
"""Initialize delete command.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
core: The core application context
|
|
35
|
+
console: Optional console for output (uses default if not provided)
|
|
36
|
+
"""
|
|
37
|
+
self.core = core
|
|
38
|
+
self.console = console or get_console()
|
|
39
|
+
|
|
40
|
+
def post_delete_hook(self, entity_id: str, **kwargs) -> None: # noqa: B027
|
|
41
|
+
"""Handle entity deletion completion with optional custom logic.
|
|
42
|
+
|
|
43
|
+
Override to handle cleanup, notifications, etc.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
entity_id: ID of deleted entity
|
|
47
|
+
**kwargs: Original CLI arguments
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def execute(self, entity_id: str, force: bool = False, **kwargs) -> bool:
|
|
52
|
+
"""Execute the delete operation.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
entity_id: ID of entity to delete
|
|
56
|
+
force: Whether to force deletion without confirmation
|
|
57
|
+
**kwargs: All other CLI arguments
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if deletion succeeded, False otherwise
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Verify entity exists
|
|
64
|
+
entity = self._get_entity(entity_id)
|
|
65
|
+
if entity is None:
|
|
66
|
+
self.console.print(
|
|
67
|
+
format_entity_not_found_error(self.entity_type, entity_id),
|
|
68
|
+
style="red",
|
|
69
|
+
)
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# Request confirmation if not forced
|
|
73
|
+
if not force:
|
|
74
|
+
title = self._get_title(entity)
|
|
75
|
+
if not click.confirm(
|
|
76
|
+
f"Delete {self.entity_type.value} '{title}'? This cannot be undone."
|
|
77
|
+
):
|
|
78
|
+
self.console.print("⚠️ Delete cancelled", style="yellow")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
# Delete via appropriate service
|
|
82
|
+
if not self._delete_entity(entity_id):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# Run post-delete hooks
|
|
86
|
+
self.post_delete_hook(entity_id, **kwargs)
|
|
87
|
+
|
|
88
|
+
# Display success
|
|
89
|
+
self._display_success(entity_id, entity)
|
|
90
|
+
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
self.console.print(
|
|
95
|
+
f"❌ Failed to delete {self.entity_type.value}: {str(e)}",
|
|
96
|
+
style="red",
|
|
97
|
+
)
|
|
98
|
+
raise click.ClickException(str(e)) from e
|
|
99
|
+
|
|
100
|
+
def _get_entity(self, entity_id: str) -> Any | None:
|
|
101
|
+
"""Get entity by ID.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
entity_id: Entity ID to retrieve
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Entity object or None if not found
|
|
108
|
+
"""
|
|
109
|
+
return get_entity_by_type(self.core, self.entity_type, entity_id)
|
|
110
|
+
|
|
111
|
+
def _delete_entity(self, entity_id: str) -> bool:
|
|
112
|
+
"""Delete entity via appropriate service.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
entity_id: Entity ID to delete
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if deletion succeeded
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
delete_entity_by_type(self.core, self.entity_type, entity_id)
|
|
122
|
+
return True
|
|
123
|
+
except Exception as e:
|
|
124
|
+
from roadmap.common.logging import get_logger
|
|
125
|
+
|
|
126
|
+
logger = get_logger(__name__)
|
|
127
|
+
logger.debug(
|
|
128
|
+
"entity_deletion_failed",
|
|
129
|
+
entity_id=entity_id,
|
|
130
|
+
entity_type=self.entity_type,
|
|
131
|
+
error=str(e),
|
|
132
|
+
action="delete_entity",
|
|
133
|
+
)
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def _display_success(self, entity_id: str, entity: Any) -> None:
|
|
137
|
+
"""Display success message.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
entity_id: ID of deleted entity
|
|
141
|
+
entity: The deleted entity
|
|
142
|
+
"""
|
|
143
|
+
presenter = DeletePresenter()
|
|
144
|
+
presenter.render(entity)
|
|
145
|
+
|
|
146
|
+
def _get_title(self, entity: Any) -> str:
|
|
147
|
+
"""Get entity title/name.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
entity: The entity object
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Title or name string
|
|
154
|
+
"""
|
|
155
|
+
return get_entity_title(entity)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Base class for restore operations across all entity types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from roadmap.adapters.cli.crud.crud_helpers import (
|
|
10
|
+
EntityType,
|
|
11
|
+
display_restore_success,
|
|
12
|
+
get_active_dir,
|
|
13
|
+
get_archive_dir,
|
|
14
|
+
)
|
|
15
|
+
from roadmap.common.console import get_console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseRestore(ABC):
|
|
19
|
+
"""Abstract base class for entity restore commands using Template Method pattern.
|
|
20
|
+
|
|
21
|
+
Subclasses must implement:
|
|
22
|
+
- entity_type: The EntityType this restores
|
|
23
|
+
- get_archived_files_to_restore(): Determine which archived files to restore
|
|
24
|
+
- post_restore_hook(): Update database and handle entity-specific logic
|
|
25
|
+
|
|
26
|
+
Subclasses may override:
|
|
27
|
+
- validate_entity_before_restore(): Custom validation before restoring
|
|
28
|
+
- check_conflict(): Custom conflict detection logic
|
|
29
|
+
|
|
30
|
+
This class handles the overall restore workflow:
|
|
31
|
+
1. Find archived files matching criteria
|
|
32
|
+
2. Validate before restoring
|
|
33
|
+
3. Check for conflicts with active entities
|
|
34
|
+
4. Move files from archive to active
|
|
35
|
+
5. Update state via post_restore_hook
|
|
36
|
+
6. Display results
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
entity_type: EntityType
|
|
40
|
+
|
|
41
|
+
def __init__(self, core: Any, console: Any = None) -> None:
|
|
42
|
+
"""Initialize restore command.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
core: The core application context
|
|
46
|
+
console: Optional console for output (uses default if not provided)
|
|
47
|
+
"""
|
|
48
|
+
self.core = core
|
|
49
|
+
self.console = console or get_console()
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def get_archived_files_to_restore(
|
|
53
|
+
self, entity_id: str | None = None, **filters
|
|
54
|
+
) -> list[Path]:
|
|
55
|
+
"""Determine which archived files should be restored.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
entity_id: Optional specific entity ID to restore
|
|
59
|
+
**filters: Entity-specific filter criteria
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of archived file paths to restore
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def post_restore_hook(self, restored_files: list[Path], **kwargs) -> None:
|
|
67
|
+
"""Handle entity-specific state updates after restoring.
|
|
68
|
+
|
|
69
|
+
Override to update database, send notifications, etc.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
restored_files: List of files that were restored
|
|
73
|
+
**kwargs: Original CLI arguments
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def validate_entity_before_restore(
|
|
77
|
+
self, file_path: Path
|
|
78
|
+
) -> tuple[bool, str | None]:
|
|
79
|
+
"""Validate entity before restoring.
|
|
80
|
+
|
|
81
|
+
Allows subclasses to add entity-specific validation rules.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
file_path: The file being considered for restore
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of (is_valid, error_message_or_none)
|
|
88
|
+
"""
|
|
89
|
+
return True, None
|
|
90
|
+
|
|
91
|
+
def check_conflict(self, archived_file: Path) -> tuple[bool, str | None]:
|
|
92
|
+
"""Check if restoring would cause a conflict.
|
|
93
|
+
|
|
94
|
+
Override for custom conflict detection.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
archived_file: The file being restored
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Tuple of (has_conflict, conflict_description)
|
|
101
|
+
"""
|
|
102
|
+
active_dir = get_active_dir(self.entity_type)
|
|
103
|
+
active_file = active_dir / archived_file.name
|
|
104
|
+
if active_file.exists():
|
|
105
|
+
return True, f"already exists in active {self.entity_type.value}s"
|
|
106
|
+
return False, None
|
|
107
|
+
|
|
108
|
+
def _validate_files_for_restore(
|
|
109
|
+
self, files_to_restore: list[Path]
|
|
110
|
+
) -> tuple[list[Path], list[tuple[Path, str | None]]]:
|
|
111
|
+
"""Validate files before restoration.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
files_to_restore: List of files to validate
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Tuple of (valid_files, invalid_files_with_reasons)
|
|
118
|
+
"""
|
|
119
|
+
invalid_files = []
|
|
120
|
+
|
|
121
|
+
for file_path in files_to_restore:
|
|
122
|
+
is_valid, error_msg = self.validate_entity_before_restore(file_path)
|
|
123
|
+
if not is_valid:
|
|
124
|
+
invalid_files.append((file_path, error_msg))
|
|
125
|
+
|
|
126
|
+
valid_files = [
|
|
127
|
+
f for f in files_to_restore if f not in [f[0] for f in invalid_files]
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
return valid_files, invalid_files
|
|
131
|
+
|
|
132
|
+
def _check_files_for_conflicts(
|
|
133
|
+
self, files_to_restore: list[Path]
|
|
134
|
+
) -> tuple[list[Path], list[tuple[Path, str | None]]]:
|
|
135
|
+
"""Check files for conflicts with active entities.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
files_to_restore: List of files to check
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Tuple of (non_conflicting_files, conflicting_files_with_reasons)
|
|
142
|
+
"""
|
|
143
|
+
conflicts = []
|
|
144
|
+
|
|
145
|
+
for file_path in files_to_restore:
|
|
146
|
+
has_conflict, conflict_desc = self.check_conflict(file_path)
|
|
147
|
+
if has_conflict:
|
|
148
|
+
conflicts.append((file_path, conflict_desc))
|
|
149
|
+
|
|
150
|
+
non_conflicting = [
|
|
151
|
+
f for f in files_to_restore if f not in [f[0] for f in conflicts]
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
return non_conflicting, conflicts
|
|
155
|
+
|
|
156
|
+
def _perform_file_restore(
|
|
157
|
+
self, files_to_restore: list[Path]
|
|
158
|
+
) -> tuple[list[Path], int]:
|
|
159
|
+
"""Move files from archive to active directory.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
files_to_restore: List of files to restore
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Tuple of (successfully_restored_files, failed_count)
|
|
166
|
+
"""
|
|
167
|
+
active_dir = get_active_dir(self.entity_type)
|
|
168
|
+
active_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
|
|
170
|
+
restored_files = []
|
|
171
|
+
failed_count = 0
|
|
172
|
+
|
|
173
|
+
for archive_file in files_to_restore:
|
|
174
|
+
try:
|
|
175
|
+
active_file = active_dir / archive_file.name
|
|
176
|
+
active_file.write_text(archive_file.read_text())
|
|
177
|
+
archive_file.unlink()
|
|
178
|
+
restored_files.append(active_file)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
self.console.print(
|
|
181
|
+
f"⚠️ Failed to restore {archive_file.name}: {str(e)}",
|
|
182
|
+
style="yellow",
|
|
183
|
+
)
|
|
184
|
+
failed_count += 1
|
|
185
|
+
|
|
186
|
+
return restored_files, failed_count
|
|
187
|
+
|
|
188
|
+
def _display_validation_errors(
|
|
189
|
+
self, invalid_files: list[tuple[Path, str | None]]
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Display validation errors to user.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
invalid_files: List of files with validation errors
|
|
195
|
+
"""
|
|
196
|
+
for file_path, error_msg in invalid_files:
|
|
197
|
+
self.console.print(
|
|
198
|
+
f"⚠️ Cannot restore {file_path.name}: {error_msg}",
|
|
199
|
+
style="yellow",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _display_conflicts(self, conflicts: list[tuple[Path, str | None]]) -> None:
|
|
203
|
+
"""Display conflict errors to user.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
conflicts: List of conflicting files
|
|
207
|
+
"""
|
|
208
|
+
for file_path, conflict_desc in conflicts:
|
|
209
|
+
self.console.print(
|
|
210
|
+
f"⚠️ Cannot restore {file_path.name}: {conflict_desc}",
|
|
211
|
+
style="yellow",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def execute(self, entity_id: str | None = None, **kwargs) -> bool:
|
|
215
|
+
"""Execute restore operation (Template Method).
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
entity_id: Optional specific entity ID to restore
|
|
219
|
+
**kwargs: Entity-specific filters and options
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
True if restore succeeded
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
# Step 1: Find archived files matching criteria
|
|
226
|
+
archive_dir = get_archive_dir(self.entity_type)
|
|
227
|
+
|
|
228
|
+
if not archive_dir.exists():
|
|
229
|
+
self.console.print(
|
|
230
|
+
f"No archive found for {self.entity_type.value}s",
|
|
231
|
+
style="yellow",
|
|
232
|
+
)
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
files_to_restore = self.get_archived_files_to_restore(entity_id, **kwargs)
|
|
236
|
+
|
|
237
|
+
if not files_to_restore:
|
|
238
|
+
self.console.print(
|
|
239
|
+
f"No archived {self.entity_type.value}s found to restore",
|
|
240
|
+
style="yellow",
|
|
241
|
+
)
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
# Step 2: Validate files
|
|
245
|
+
files_to_restore, invalid_files = self._validate_files_for_restore(
|
|
246
|
+
files_to_restore
|
|
247
|
+
)
|
|
248
|
+
if invalid_files:
|
|
249
|
+
self._display_validation_errors(invalid_files)
|
|
250
|
+
if not files_to_restore:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
# Step 3: Check for conflicts
|
|
254
|
+
files_to_restore, conflicts = self._check_files_for_conflicts(
|
|
255
|
+
files_to_restore
|
|
256
|
+
)
|
|
257
|
+
if conflicts:
|
|
258
|
+
self._display_conflicts(conflicts)
|
|
259
|
+
if not files_to_restore:
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
# Step 4: Restore files
|
|
263
|
+
restored_files, failed_count = self._perform_file_restore(files_to_restore)
|
|
264
|
+
|
|
265
|
+
# Step 5: Update state via hook
|
|
266
|
+
self.post_restore_hook(restored_files, **kwargs)
|
|
267
|
+
|
|
268
|
+
# Step 6: Display results
|
|
269
|
+
display_restore_success(
|
|
270
|
+
self.entity_type,
|
|
271
|
+
len(restored_files),
|
|
272
|
+
failed_count,
|
|
273
|
+
console=self.console,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
self.console.print(
|
|
280
|
+
f"❌ Restore operation failed: {str(e)}",
|
|
281
|
+
style="red",
|
|
282
|
+
)
|
|
283
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Base class for update operations across all entity types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.cli.crud.crud_helpers import EntityType
|
|
9
|
+
from roadmap.adapters.cli.crud.crud_utils import (
|
|
10
|
+
format_entity_not_found_error,
|
|
11
|
+
get_entity_by_type,
|
|
12
|
+
get_entity_id,
|
|
13
|
+
get_entity_title,
|
|
14
|
+
update_entity_by_type,
|
|
15
|
+
)
|
|
16
|
+
from roadmap.adapters.cli.presentation.crud_presenter import UpdatePresenter
|
|
17
|
+
from roadmap.common.console import get_console
|
|
18
|
+
from roadmap.common.errors.exceptions import ValidationError
|
|
19
|
+
from roadmap.common.logging import log_audit_event
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseUpdate(ABC):
|
|
23
|
+
"""Abstract base class for entity update commands.
|
|
24
|
+
|
|
25
|
+
Subclasses implement:
|
|
26
|
+
- entity_type: The EntityType this updates
|
|
27
|
+
- build_update_dict(): Convert CLI args to update dict
|
|
28
|
+
- post_update_hook(): Optional hook after update (default: no-op)
|
|
29
|
+
|
|
30
|
+
Uses Template Method pattern for extensibility.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
entity_type: EntityType
|
|
34
|
+
|
|
35
|
+
def __init__(self, core: Any, console: Any = None) -> None:
|
|
36
|
+
"""Initialize update command.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
core: The core application context
|
|
40
|
+
console: Optional console for output (uses default if not provided)
|
|
41
|
+
"""
|
|
42
|
+
self.core = core
|
|
43
|
+
self.console = console or get_console()
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def build_update_dict(self, entity_id: str, **kwargs) -> dict[str, Any]:
|
|
47
|
+
"""Convert CLI arguments to update dictionary.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
entity_id: ID of entity to update
|
|
51
|
+
**kwargs: All click option values
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dictionary of fields to update
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValidationError: If arguments are invalid
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def post_update_hook( # noqa: B027
|
|
62
|
+
self, entity: Any, update_dict: dict[str, Any], **kwargs
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Handle entity update completion with optional custom logic.
|
|
65
|
+
|
|
66
|
+
Override to handle:
|
|
67
|
+
- Git branch updates
|
|
68
|
+
- Milestone reassignments
|
|
69
|
+
- Custom notifications
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
entity: The updated entity
|
|
73
|
+
update_dict: The update dict that was applied
|
|
74
|
+
**kwargs: Original CLI arguments
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def execute(self, entity_id: str, **kwargs) -> Any | None:
|
|
79
|
+
"""Execute the update operation.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
entity_id: ID of entity to update
|
|
83
|
+
**kwargs: All other CLI arguments
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Updated entity, or None if update failed
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
# Verify entity exists
|
|
90
|
+
entity = self._get_entity(entity_id)
|
|
91
|
+
if entity is None:
|
|
92
|
+
self.console.print(
|
|
93
|
+
format_entity_not_found_error(self.entity_type, entity_id),
|
|
94
|
+
style="red",
|
|
95
|
+
)
|
|
96
|
+
raise click.ClickException(f"{self.entity_type.value} not found")
|
|
97
|
+
|
|
98
|
+
# Build update dict from arguments
|
|
99
|
+
update_dict = self.build_update_dict(entity_id=entity_id, **kwargs)
|
|
100
|
+
|
|
101
|
+
# Update via appropriate service
|
|
102
|
+
updated_entity = self._update_entity(entity_id, update_dict)
|
|
103
|
+
if updated_entity is None:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# Run post-update hooks
|
|
107
|
+
self.post_update_hook(updated_entity, update_dict, **kwargs)
|
|
108
|
+
|
|
109
|
+
# Log audit event for successful update
|
|
110
|
+
entity_id = self._get_id(updated_entity)
|
|
111
|
+
log_audit_event(
|
|
112
|
+
action="update",
|
|
113
|
+
entity_type=self.entity_type.value,
|
|
114
|
+
entity_id=entity_id,
|
|
115
|
+
before={"entity_id": entity_id}, # Original only tracked by entity_id
|
|
116
|
+
after=update_dict,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Display success
|
|
120
|
+
self._display_success(updated_entity)
|
|
121
|
+
|
|
122
|
+
return updated_entity
|
|
123
|
+
|
|
124
|
+
except ValidationError:
|
|
125
|
+
# ValidationError will be handled by centralized CLI exception handler
|
|
126
|
+
# which formats it and directs to stderr
|
|
127
|
+
raise
|
|
128
|
+
except Exception:
|
|
129
|
+
# Other exceptions will also be handled by centralized handler
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
def _get_entity(self, entity_id: str) -> Any | None:
|
|
133
|
+
"""Get entity by ID.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
entity_id: Entity ID to retrieve
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Entity object or None if not found
|
|
140
|
+
"""
|
|
141
|
+
return get_entity_by_type(self.core, self.entity_type, entity_id)
|
|
142
|
+
|
|
143
|
+
def _update_entity(self, entity_id: str, update_dict: dict[str, Any]) -> Any | None:
|
|
144
|
+
"""Update entity via appropriate service.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
entity_id: Entity ID to update
|
|
148
|
+
update_dict: Dictionary of fields to update
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Updated entity or None if failed
|
|
152
|
+
"""
|
|
153
|
+
result = update_entity_by_type(
|
|
154
|
+
self.core, self.entity_type, entity_id, update_dict
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Some coordinator update methods (e.g., milestones) return bool instead
|
|
158
|
+
# of the updated entity. Normalize by fetching the entity after success.
|
|
159
|
+
if isinstance(result, bool):
|
|
160
|
+
return self._get_entity(entity_id) if result else None
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
def _display_success(self, entity: Any) -> None:
|
|
165
|
+
"""Display success message.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
entity: The updated entity
|
|
169
|
+
"""
|
|
170
|
+
presenter = UpdatePresenter()
|
|
171
|
+
presenter.render(entity)
|
|
172
|
+
|
|
173
|
+
def _get_title(self, entity: Any) -> str:
|
|
174
|
+
"""Get entity title/name.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
entity: The entity object
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Title or name string
|
|
181
|
+
"""
|
|
182
|
+
return get_entity_title(entity)
|
|
183
|
+
|
|
184
|
+
def _get_id(self, entity: Any) -> str:
|
|
185
|
+
"""Get entity ID.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
entity: The entity object
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Entity ID string
|
|
192
|
+
"""
|
|
193
|
+
return get_entity_id(entity)
|