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,137 @@
|
|
|
1
|
+
"""Archive issue command - move completed issues to archive."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.issues.archive_class import IssueArchive
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
from roadmap.common.logging import (
|
|
9
|
+
log_command,
|
|
10
|
+
verbose_output,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click.argument("issue_id", required=False)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--all-closed",
|
|
18
|
+
is_flag=True,
|
|
19
|
+
help="Archive all closed issues",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--orphaned",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help="Archive issues with no milestone assigned",
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--list",
|
|
28
|
+
"list_archived",
|
|
29
|
+
is_flag=True,
|
|
30
|
+
help="List archived issues",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--dry-run",
|
|
34
|
+
is_flag=True,
|
|
35
|
+
help="Preview what would be archived without actually doing it",
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--force",
|
|
39
|
+
is_flag=True,
|
|
40
|
+
help="Skip confirmation prompt",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--verbose",
|
|
44
|
+
"-v",
|
|
45
|
+
is_flag=True,
|
|
46
|
+
help="Show detailed debug information",
|
|
47
|
+
)
|
|
48
|
+
@click.pass_context
|
|
49
|
+
@verbose_output
|
|
50
|
+
@log_command("issue_archive", entity_type="issue", track_duration=True)
|
|
51
|
+
@require_initialized
|
|
52
|
+
def archive_issue(
|
|
53
|
+
ctx: click.Context,
|
|
54
|
+
issue_id: str | None,
|
|
55
|
+
all_closed: bool,
|
|
56
|
+
orphaned: bool,
|
|
57
|
+
list_archived: bool,
|
|
58
|
+
dry_run: bool,
|
|
59
|
+
force: bool,
|
|
60
|
+
verbose: bool, # noqa: F841
|
|
61
|
+
):
|
|
62
|
+
"""Archive an issue by moving it to .roadmap/archive/issues/.
|
|
63
|
+
|
|
64
|
+
This is a non-destructive operation that preserves issue data while
|
|
65
|
+
cleaning up the active workspace. Archived issues can be restored
|
|
66
|
+
if needed.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
roadmap issue archive 8a00a17e
|
|
70
|
+
roadmap issue archive --all-closed
|
|
71
|
+
roadmap issue archive --orphaned --dry-run
|
|
72
|
+
roadmap issue archive --list
|
|
73
|
+
"""
|
|
74
|
+
core = ctx.obj["core"]
|
|
75
|
+
console = get_console()
|
|
76
|
+
|
|
77
|
+
archive = IssueArchive(core, console)
|
|
78
|
+
|
|
79
|
+
if list_archived:
|
|
80
|
+
# List archived issues
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
|
|
83
|
+
from roadmap.adapters.persistence.parser import IssueParser
|
|
84
|
+
|
|
85
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
86
|
+
archive_dir = roadmap_dir / "archive" / "issues"
|
|
87
|
+
|
|
88
|
+
if not archive_dir.exists():
|
|
89
|
+
console.print("📋 No archived issues.", style="yellow")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
archived_files = list(archive_dir.rglob("*.md"))
|
|
93
|
+
if not archived_files:
|
|
94
|
+
console.print("📋 No archived issues.", style="yellow")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
console.print("\n📦 Archived Issues:\n", style="bold blue")
|
|
98
|
+
for file_path in sorted(archived_files):
|
|
99
|
+
try:
|
|
100
|
+
issue = IssueParser.parse_issue_file(file_path)
|
|
101
|
+
milestone = issue.milestone or "No milestone"
|
|
102
|
+
console.print(
|
|
103
|
+
f" • {issue.id[:8]} - {issue.title} [{milestone}] ({issue.status.value})",
|
|
104
|
+
style="cyan",
|
|
105
|
+
)
|
|
106
|
+
except Exception:
|
|
107
|
+
console.print(
|
|
108
|
+
f" • {file_path.name} (error reading file)", style="yellow"
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Validate arguments
|
|
113
|
+
if not issue_id and not all_closed and not orphaned:
|
|
114
|
+
console.print(
|
|
115
|
+
"❌ Error: Specify an issue ID, --all-closed, or --orphaned",
|
|
116
|
+
style="bold red",
|
|
117
|
+
)
|
|
118
|
+
ctx.exit(1)
|
|
119
|
+
|
|
120
|
+
if sum([bool(issue_id), all_closed, orphaned]) > 1:
|
|
121
|
+
console.print(
|
|
122
|
+
"❌ Error: Specify only one of: issue ID, --all-closed, or --orphaned",
|
|
123
|
+
style="bold red",
|
|
124
|
+
)
|
|
125
|
+
ctx.exit(1)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
archive.execute(
|
|
129
|
+
entity_id=issue_id,
|
|
130
|
+
all_closed=all_closed,
|
|
131
|
+
orphaned=orphaned,
|
|
132
|
+
dry_run=dry_run,
|
|
133
|
+
force=force,
|
|
134
|
+
)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
console.print(f"❌ Archive operation failed: {str(e)}", style="bold red")
|
|
137
|
+
ctx.exit(1)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""IssueArchive command implementation using base class."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseArchive, EntityType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IssueArchive(BaseArchive):
|
|
10
|
+
"""Archive issue command implementation."""
|
|
11
|
+
|
|
12
|
+
entity_type = EntityType.ISSUE
|
|
13
|
+
|
|
14
|
+
def get_entities_to_archive(
|
|
15
|
+
self,
|
|
16
|
+
entity_id: str | None = None,
|
|
17
|
+
all_closed: bool = False,
|
|
18
|
+
orphaned: bool = False,
|
|
19
|
+
**kwargs,
|
|
20
|
+
) -> list[Any]:
|
|
21
|
+
"""Get issues to archive based on criteria.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
entity_id: Specific issue ID to archive
|
|
25
|
+
all_closed: Archive all closed issues
|
|
26
|
+
orphaned: Archive issues with no milestone
|
|
27
|
+
**kwargs: Additional arguments
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of issues to archive
|
|
31
|
+
"""
|
|
32
|
+
all_issues = self.core.issues.list()
|
|
33
|
+
|
|
34
|
+
if entity_id:
|
|
35
|
+
issue = self.core.issues.get(entity_id)
|
|
36
|
+
return [issue] if issue else []
|
|
37
|
+
elif all_closed:
|
|
38
|
+
return [i for i in all_issues if i.status.value == "closed"]
|
|
39
|
+
elif orphaned:
|
|
40
|
+
return [i for i in all_issues if not i.milestone]
|
|
41
|
+
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
def validate_entity_before_archive(
|
|
45
|
+
self, entity: Any, **kwargs
|
|
46
|
+
) -> tuple[bool, str | None]:
|
|
47
|
+
"""Validate issue before archiving.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
entity: The issue to validate
|
|
51
|
+
**kwargs: Additional arguments (e.g., force flag)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Tuple of (is_valid, error_message)
|
|
55
|
+
"""
|
|
56
|
+
# Allow archiving if force flag is set
|
|
57
|
+
if kwargs.get("force"):
|
|
58
|
+
return True, None
|
|
59
|
+
|
|
60
|
+
if entity.status.value != "closed":
|
|
61
|
+
return False, f"not closed (status: {entity.status.value})"
|
|
62
|
+
return True, None
|
|
63
|
+
|
|
64
|
+
def find_entity_files(self, entities: list[Any]) -> list[Path]:
|
|
65
|
+
"""Find issue files for entities.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
entities: List of issues
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of file paths
|
|
72
|
+
"""
|
|
73
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
74
|
+
issues_dir = roadmap_dir / "issues"
|
|
75
|
+
|
|
76
|
+
files = []
|
|
77
|
+
for entity in entities:
|
|
78
|
+
# Search recursively for issue file by ID prefix
|
|
79
|
+
matching = list(issues_dir.rglob(f"{str(entity.id)[:8]}*.md"))
|
|
80
|
+
if matching:
|
|
81
|
+
files.append(matching[0])
|
|
82
|
+
return files
|
|
83
|
+
|
|
84
|
+
def _determine_archive_path(self, archive_dir: Path, issue_file: Path) -> Path:
|
|
85
|
+
"""Determine archive path preserving folder structure.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
archive_dir: The archive root directory
|
|
89
|
+
issue_file: The issue file path
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The destination path for archiving
|
|
93
|
+
"""
|
|
94
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
95
|
+
issues_dir = roadmap_dir / "issues"
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
rel_path = issue_file.relative_to(issues_dir)
|
|
99
|
+
except ValueError as e:
|
|
100
|
+
from roadmap.common.logging import get_logger
|
|
101
|
+
|
|
102
|
+
logger = get_logger(__name__)
|
|
103
|
+
logger.debug(
|
|
104
|
+
"issue_relative_path_failed",
|
|
105
|
+
file=str(issue_file),
|
|
106
|
+
error=str(e),
|
|
107
|
+
action="calculate_archive_path",
|
|
108
|
+
)
|
|
109
|
+
return archive_dir / issue_file.name
|
|
110
|
+
|
|
111
|
+
# If file is directly in issues_dir (no parent folder), put in archive root
|
|
112
|
+
if len(rel_path.parts) == 1:
|
|
113
|
+
return archive_dir / issue_file.name
|
|
114
|
+
|
|
115
|
+
# Preserve the parent folder structure
|
|
116
|
+
dest_dir = archive_dir / rel_path.parent
|
|
117
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
return dest_dir / issue_file.name
|
|
119
|
+
|
|
120
|
+
def execute(self, entity_id: str | None = None, **kwargs) -> bool:
|
|
121
|
+
"""Execute archive operation.
|
|
122
|
+
|
|
123
|
+
Archives issues by moving files and setting the archived flag.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
entity_id: Optional issue ID
|
|
127
|
+
**kwargs: Filter options (all_closed, orphaned, etc.)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if successful
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
entities = self.get_entities_to_archive(entity_id, **kwargs)
|
|
134
|
+
|
|
135
|
+
if not entities:
|
|
136
|
+
self.console.print(
|
|
137
|
+
f"No {self.entity_type.value}s to archive",
|
|
138
|
+
style="yellow",
|
|
139
|
+
)
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
# Validate entities
|
|
143
|
+
invalid_entities = []
|
|
144
|
+
for entity in entities:
|
|
145
|
+
is_valid, error_msg = self.validate_entity_before_archive(
|
|
146
|
+
entity, **kwargs
|
|
147
|
+
)
|
|
148
|
+
if not is_valid:
|
|
149
|
+
invalid_entities.append((entity, error_msg))
|
|
150
|
+
self.console.print(
|
|
151
|
+
f"⚠️ Cannot archive {entity.id}: {error_msg}",
|
|
152
|
+
style="yellow",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if invalid_entities:
|
|
156
|
+
invalid_entity_ids = {e.id for e, _ in invalid_entities}
|
|
157
|
+
entities = [e for e in entities if e.id not in invalid_entity_ids]
|
|
158
|
+
if not entities:
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
# Find files
|
|
162
|
+
files_to_archive = self.find_entity_files(entities)
|
|
163
|
+
|
|
164
|
+
if not files_to_archive:
|
|
165
|
+
self.console.print(
|
|
166
|
+
f"Could not find files for {len(entities)} {self.entity_type.value}(s)",
|
|
167
|
+
style="yellow",
|
|
168
|
+
)
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
# Archive with folder structure preservation
|
|
172
|
+
from roadmap.adapters.cli.crud.crud_helpers import (
|
|
173
|
+
display_archive_success,
|
|
174
|
+
get_archive_dir,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
archive_dir = get_archive_dir(self.entity_type)
|
|
178
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
|
|
180
|
+
archived_files = []
|
|
181
|
+
failed_count = 0
|
|
182
|
+
|
|
183
|
+
for file_path in files_to_archive:
|
|
184
|
+
try:
|
|
185
|
+
archive_file = self._determine_archive_path(archive_dir, file_path)
|
|
186
|
+
if archive_file.exists():
|
|
187
|
+
failed_count += 1
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
# Ensure parent directory exists and use rename (mv) for atomicity
|
|
191
|
+
archive_file.parent.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
file_path.rename(archive_file)
|
|
193
|
+
archived_files.append(archive_file)
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
self.console.print(
|
|
197
|
+
f"⚠️ Failed to archive {file_path.name}: {str(e)}",
|
|
198
|
+
style="yellow",
|
|
199
|
+
)
|
|
200
|
+
failed_count += 1
|
|
201
|
+
|
|
202
|
+
# Update state (set archived flag and update cache)
|
|
203
|
+
self.post_archive_hook(archived_files, entities, **kwargs)
|
|
204
|
+
|
|
205
|
+
# Display results
|
|
206
|
+
display_archive_success(
|
|
207
|
+
self.entity_type,
|
|
208
|
+
len(archived_files),
|
|
209
|
+
failed_count,
|
|
210
|
+
console=self.console,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
self.console.print(
|
|
217
|
+
f"❌ Archive operation failed: {str(e)}",
|
|
218
|
+
style="red",
|
|
219
|
+
)
|
|
220
|
+
import click
|
|
221
|
+
|
|
222
|
+
raise click.ClickException(str(e)) from e
|
|
223
|
+
|
|
224
|
+
def post_archive_hook(
|
|
225
|
+
self, archived_files: list[Path], entities: list[Any], **kwargs
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Handle post-archive state updates.
|
|
228
|
+
|
|
229
|
+
Sets the archived flag on entities after moving files.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
archived_files: Files that were archived
|
|
233
|
+
entities: Entities that were archived
|
|
234
|
+
**kwargs: Additional arguments
|
|
235
|
+
"""
|
|
236
|
+
# Set archived flag on entities
|
|
237
|
+
from roadmap.core.models import IssueUpdateServiceParams
|
|
238
|
+
|
|
239
|
+
for entity in entities:
|
|
240
|
+
try:
|
|
241
|
+
params = IssueUpdateServiceParams(
|
|
242
|
+
issue_id=entity.id,
|
|
243
|
+
archived=True,
|
|
244
|
+
)
|
|
245
|
+
self.core.issues.update_issue(params)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.console.print(
|
|
248
|
+
f"⚠️ Warning: Failed to mark issue {entity.id} as archived: {e}",
|
|
249
|
+
style="yellow",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Clear the list cache after archiving to ensure archived issues don't appear in list output
|
|
253
|
+
self.core.issues._ops.issue_service._list_issues_cache.clear()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Block issue command - thin wrapper around update.
|
|
2
|
+
|
|
3
|
+
This command is syntactic sugar for: roadmap issue update <ID> --status blocked
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
9
|
+
from roadmap.common.cli_errors import handle_cli_errors
|
|
10
|
+
from roadmap.core.domain import Status
|
|
11
|
+
|
|
12
|
+
from .issue_status_helpers import StatusChangeConfig, apply_status_change
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command("block")
|
|
16
|
+
@click.argument("issue_id")
|
|
17
|
+
@click.option("--reason", "-r", help="Reason for blocking")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@handle_cli_errors(command_name="issue block")
|
|
20
|
+
@require_initialized
|
|
21
|
+
def block_issue(
|
|
22
|
+
ctx: click.Context,
|
|
23
|
+
issue_id: str,
|
|
24
|
+
reason: str,
|
|
25
|
+
):
|
|
26
|
+
"""Mark an issue as blocked (sets status to blocked).
|
|
27
|
+
|
|
28
|
+
Syntactic sugar for: roadmap issue update <ID> --status blocked
|
|
29
|
+
"""
|
|
30
|
+
core = ctx.obj["core"]
|
|
31
|
+
|
|
32
|
+
config = StatusChangeConfig(
|
|
33
|
+
status=Status.BLOCKED,
|
|
34
|
+
emoji="🚫",
|
|
35
|
+
title_verb="Blocked",
|
|
36
|
+
title_style="bold red",
|
|
37
|
+
status_display="🚫 Blocked",
|
|
38
|
+
)
|
|
39
|
+
apply_status_change(core, issue_id, config, reason)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Close issue command - thin wrapper around update.
|
|
2
|
+
|
|
3
|
+
This command is syntactic sugar for: roadmap issue update <ID> --status closed
|
|
4
|
+
|
|
5
|
+
SEMANTICS:
|
|
6
|
+
- Close: Changes status to 'closed', keeps files in place (no file movement), archived=false
|
|
7
|
+
- Archive: Moves files to archive directory AND sets archived=true flag (signals sync to delete remotely)
|
|
8
|
+
|
|
9
|
+
This separation allows for flexible status management without workspace reorganization.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
from structlog import get_logger
|
|
16
|
+
|
|
17
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
18
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
19
|
+
from roadmap.common.console import get_console
|
|
20
|
+
from roadmap.common.formatters.text.operations import (
|
|
21
|
+
format_operation_failure,
|
|
22
|
+
format_operation_success,
|
|
23
|
+
)
|
|
24
|
+
from roadmap.common.logging import (
|
|
25
|
+
log_command,
|
|
26
|
+
track_database_operation,
|
|
27
|
+
)
|
|
28
|
+
from roadmap.core.domain import Status
|
|
29
|
+
|
|
30
|
+
console = get_console()
|
|
31
|
+
logger = get_logger()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_completion_date(date_str: str) -> datetime | None:
|
|
35
|
+
"""Parse completion date from string.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
date_str: Date string to parse (YYYY-MM-DD HH:MM or YYYY-MM-DD)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Parsed datetime or None if parsing fails
|
|
42
|
+
"""
|
|
43
|
+
if not date_str:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
return datetime.strptime(date_str, "%Y-%m-%d %H:%M")
|
|
48
|
+
except ValueError as e:
|
|
49
|
+
from roadmap.common.logging import get_logger
|
|
50
|
+
|
|
51
|
+
logger = get_logger(__name__)
|
|
52
|
+
logger.debug(
|
|
53
|
+
"datetime_parse_failed",
|
|
54
|
+
date_str=date_str,
|
|
55
|
+
error=str(e),
|
|
56
|
+
action="parse_close_date",
|
|
57
|
+
)
|
|
58
|
+
try:
|
|
59
|
+
return datetime.strptime(date_str, "%Y-%m-%d")
|
|
60
|
+
except ValueError as e:
|
|
61
|
+
logger.debug(
|
|
62
|
+
"completion_date_parse_failed", date_str=date_str, error=str(e)
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve_end_date(
|
|
68
|
+
record_time: bool, date: str
|
|
69
|
+
) -> tuple[datetime | None, str | None]:
|
|
70
|
+
"""Resolve completion datetime and validation error message, if any."""
|
|
71
|
+
if not record_time:
|
|
72
|
+
return None, None
|
|
73
|
+
|
|
74
|
+
if date:
|
|
75
|
+
end_date = _parse_completion_date(date)
|
|
76
|
+
if end_date is None:
|
|
77
|
+
return None, "❌ Invalid date format. Use YYYY-MM-DD or YYYY-MM-DD HH:MM"
|
|
78
|
+
return end_date, None
|
|
79
|
+
|
|
80
|
+
return datetime.now(UTC), None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_close_update_kwargs(end_date: datetime | None) -> dict:
|
|
84
|
+
"""Build keyword arguments used for closing an issue."""
|
|
85
|
+
update_kwargs = {
|
|
86
|
+
"status": Status.CLOSED,
|
|
87
|
+
"progress_percentage": 100.0,
|
|
88
|
+
}
|
|
89
|
+
if end_date:
|
|
90
|
+
update_kwargs["actual_end_date"] = end_date
|
|
91
|
+
return update_kwargs
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _build_close_extra_details(
|
|
95
|
+
updated_issue, reason: str, end_date: datetime | None
|
|
96
|
+
) -> dict[str, str]:
|
|
97
|
+
"""Build success details for close operation output."""
|
|
98
|
+
extra_details: dict[str, str] = {
|
|
99
|
+
"Status": "Closed",
|
|
100
|
+
"Progress": "100%",
|
|
101
|
+
}
|
|
102
|
+
if reason:
|
|
103
|
+
extra_details["Reason"] = reason
|
|
104
|
+
if not end_date:
|
|
105
|
+
return extra_details
|
|
106
|
+
|
|
107
|
+
extra_details["Completed"] = end_date.strftime("%Y-%m-%d %H:%M")
|
|
108
|
+
start_date = updated_issue.actual_start_date
|
|
109
|
+
if not start_date:
|
|
110
|
+
return extra_details
|
|
111
|
+
|
|
112
|
+
duration = end_date - start_date
|
|
113
|
+
hours = duration.total_seconds() / 3600
|
|
114
|
+
extra_details["Duration"] = f"{hours:.1f} hours"
|
|
115
|
+
|
|
116
|
+
if not updated_issue.estimated_hours:
|
|
117
|
+
return extra_details
|
|
118
|
+
|
|
119
|
+
diff = hours - updated_issue.estimated_hours
|
|
120
|
+
if abs(diff) <= 0.5:
|
|
121
|
+
extra_details["Variance"] = "On target"
|
|
122
|
+
elif diff > 0:
|
|
123
|
+
extra_details["Variance"] = f"Over by {diff:.1f} hours"
|
|
124
|
+
else:
|
|
125
|
+
extra_details["Variance"] = f"Under by {abs(diff):.1f} hours"
|
|
126
|
+
|
|
127
|
+
return extra_details
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@click.command("close")
|
|
131
|
+
@click.argument("issue_id")
|
|
132
|
+
@click.option("--reason", "-r", help="Reason for closing the issue")
|
|
133
|
+
@click.option(
|
|
134
|
+
"--record-time",
|
|
135
|
+
"-t",
|
|
136
|
+
is_flag=True,
|
|
137
|
+
help="Record actual completion time",
|
|
138
|
+
)
|
|
139
|
+
@click.option(
|
|
140
|
+
"--date",
|
|
141
|
+
help="Completion date (YYYY-MM-DD HH:MM, defaults to now)",
|
|
142
|
+
)
|
|
143
|
+
@click.pass_context
|
|
144
|
+
@log_command("issue_close", entity_type="issue", track_duration=True)
|
|
145
|
+
@require_initialized
|
|
146
|
+
def close_issue(
|
|
147
|
+
ctx: click.Context,
|
|
148
|
+
issue_id: str,
|
|
149
|
+
reason: str,
|
|
150
|
+
record_time: bool,
|
|
151
|
+
date: str,
|
|
152
|
+
):
|
|
153
|
+
"""Close an issue (sets status to closed and progress to 100%).
|
|
154
|
+
|
|
155
|
+
Syntactic sugar for: roadmap issue update <ID> --status closed
|
|
156
|
+
"""
|
|
157
|
+
core = ctx.obj["core"]
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
end_date, date_error = _resolve_end_date(record_time, date)
|
|
161
|
+
if date_error:
|
|
162
|
+
console.print(date_error, style="bold red")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
update_kwargs = _build_close_update_kwargs(end_date)
|
|
166
|
+
|
|
167
|
+
# Use wrapper for status change display
|
|
168
|
+
from roadmap.adapters.cli.cli_command_helpers import ensure_entity_exists
|
|
169
|
+
|
|
170
|
+
# Verify issue exists before updating
|
|
171
|
+
ensure_entity_exists(core, "issue", issue_id)
|
|
172
|
+
|
|
173
|
+
# Update the issue
|
|
174
|
+
with track_database_operation(
|
|
175
|
+
"update", "issue", entity_id=issue_id, warn_threshold_ms=2000
|
|
176
|
+
):
|
|
177
|
+
updated_issue = core.issues.update(issue_id, **update_kwargs)
|
|
178
|
+
|
|
179
|
+
if updated_issue:
|
|
180
|
+
extra_details = _build_close_extra_details(updated_issue, reason, end_date)
|
|
181
|
+
|
|
182
|
+
# Use formatter
|
|
183
|
+
lines = format_operation_success(
|
|
184
|
+
emoji="✅",
|
|
185
|
+
action="Closed",
|
|
186
|
+
entity_title=updated_issue.title,
|
|
187
|
+
entity_id=issue_id,
|
|
188
|
+
extra_details=extra_details,
|
|
189
|
+
)
|
|
190
|
+
for line in lines:
|
|
191
|
+
console.print(line, style="bold green" if "Closed" in line else "cyan")
|
|
192
|
+
else:
|
|
193
|
+
lines = format_operation_failure(
|
|
194
|
+
action="close",
|
|
195
|
+
entity_id=issue_id,
|
|
196
|
+
error="Failed to update status",
|
|
197
|
+
)
|
|
198
|
+
for line in lines:
|
|
199
|
+
console.print(line, style="bold red")
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
handle_cli_error(
|
|
203
|
+
error=e,
|
|
204
|
+
operation="close_issue",
|
|
205
|
+
entity_type="issue",
|
|
206
|
+
entity_id=issue_id,
|
|
207
|
+
context={"reason": reason},
|
|
208
|
+
fatal=True,
|
|
209
|
+
)
|
|
210
|
+
lines = format_operation_failure(
|
|
211
|
+
action="close",
|
|
212
|
+
entity_id=issue_id,
|
|
213
|
+
error=str(e),
|
|
214
|
+
)
|
|
215
|
+
for line in lines:
|
|
216
|
+
console.print(line, style="bold red")
|