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,236 @@
|
|
|
1
|
+
"""MilestoneArchive command implementation using base class."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.crud import BaseArchive, EntityType
|
|
8
|
+
from roadmap.adapters.persistence.parser import MilestoneParser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MilestoneArchive(BaseArchive):
|
|
12
|
+
"""Archive milestone command implementation."""
|
|
13
|
+
|
|
14
|
+
entity_type = EntityType.MILESTONE
|
|
15
|
+
|
|
16
|
+
def get_entities_to_archive(
|
|
17
|
+
self, entity_id: str | None = None, all_closed: bool = False, **kwargs
|
|
18
|
+
) -> list[Any]:
|
|
19
|
+
"""Get milestones to archive based on criteria.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
entity_id: Specific milestone name to archive
|
|
23
|
+
all_closed: Archive all closed milestones
|
|
24
|
+
**kwargs: Additional arguments
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
List of milestones to archive
|
|
28
|
+
"""
|
|
29
|
+
all_milestones = self.core.milestones.list()
|
|
30
|
+
|
|
31
|
+
if entity_id:
|
|
32
|
+
milestone = self.core.milestones.get(entity_id)
|
|
33
|
+
return [milestone] if milestone else []
|
|
34
|
+
elif all_closed:
|
|
35
|
+
return [m for m in all_milestones if m.status.value == "closed"]
|
|
36
|
+
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
def validate_entity_before_archive(
|
|
40
|
+
self, entity: Any, **kwargs
|
|
41
|
+
) -> tuple[bool, str | None]:
|
|
42
|
+
"""Validate milestone before archiving.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
entity: The milestone to validate
|
|
46
|
+
**kwargs: Additional arguments (e.g., force flag)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple of (is_valid, error_message)
|
|
50
|
+
"""
|
|
51
|
+
# Allow archiving if force flag is set
|
|
52
|
+
if kwargs.get("force"):
|
|
53
|
+
return True, None
|
|
54
|
+
|
|
55
|
+
if entity.status.value != "closed":
|
|
56
|
+
return False, f"not closed (status: {entity.status.value})"
|
|
57
|
+
return True, None
|
|
58
|
+
|
|
59
|
+
def find_entity_files(self, entities: list[Any]) -> list[Path]:
|
|
60
|
+
"""Find milestone files for entities.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
entities: List of milestones
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of file paths
|
|
67
|
+
"""
|
|
68
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
69
|
+
milestones_dir = roadmap_dir / "milestones"
|
|
70
|
+
|
|
71
|
+
files = []
|
|
72
|
+
for entity in entities:
|
|
73
|
+
# Search for milestone file by name
|
|
74
|
+
for md_file in milestones_dir.glob("*.md"):
|
|
75
|
+
try:
|
|
76
|
+
milestone = MilestoneParser.parse_milestone_file(md_file)
|
|
77
|
+
if milestone.name == entity.name:
|
|
78
|
+
files.append(md_file)
|
|
79
|
+
break
|
|
80
|
+
except Exception as e:
|
|
81
|
+
from roadmap.common.logging import get_logger
|
|
82
|
+
|
|
83
|
+
logger = get_logger(__name__)
|
|
84
|
+
logger.debug(
|
|
85
|
+
"milestone_parse_failed",
|
|
86
|
+
file=str(md_file),
|
|
87
|
+
error=str(e),
|
|
88
|
+
action="find_milestone_files",
|
|
89
|
+
)
|
|
90
|
+
continue
|
|
91
|
+
return files
|
|
92
|
+
|
|
93
|
+
def execute(self, entity_id: str | None = None, **kwargs) -> bool:
|
|
94
|
+
"""Execute archive operation.
|
|
95
|
+
|
|
96
|
+
Archives milestones by moving files and setting the archived flag.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
entity_id: Optional milestone name
|
|
100
|
+
**kwargs: Filter options (all_closed, etc.)
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
True if successful
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
entities = self.get_entities_to_archive(entity_id, **kwargs)
|
|
107
|
+
|
|
108
|
+
if not entities:
|
|
109
|
+
self.console.print(
|
|
110
|
+
f"No {self.entity_type.value}s to archive",
|
|
111
|
+
style="yellow",
|
|
112
|
+
)
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
# Validate entities
|
|
116
|
+
invalid_entities = []
|
|
117
|
+
for entity in entities:
|
|
118
|
+
is_valid, error_msg = self.validate_entity_before_archive(
|
|
119
|
+
entity, **kwargs
|
|
120
|
+
)
|
|
121
|
+
if not is_valid:
|
|
122
|
+
invalid_entities.append((entity, error_msg))
|
|
123
|
+
self.console.print(
|
|
124
|
+
f"⚠️ Cannot archive {entity.name}: {error_msg}",
|
|
125
|
+
style="yellow",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if invalid_entities:
|
|
129
|
+
invalid_entity_names = {entity.name for entity, _ in invalid_entities}
|
|
130
|
+
entities = [e for e in entities if e.name not in invalid_entity_names]
|
|
131
|
+
if not entities:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
# Find files
|
|
135
|
+
files_to_archive = self.find_entity_files(entities)
|
|
136
|
+
|
|
137
|
+
if not files_to_archive:
|
|
138
|
+
self.console.print(
|
|
139
|
+
f"Could not find files for {len(entities)} {self.entity_type.value}(s)",
|
|
140
|
+
style="yellow",
|
|
141
|
+
)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Archive with folder structure preservation
|
|
145
|
+
from roadmap.adapters.cli.crud.crud_helpers import (
|
|
146
|
+
display_archive_success,
|
|
147
|
+
get_archive_dir,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
archive_dir = get_archive_dir(self.entity_type)
|
|
151
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
archived_files = []
|
|
154
|
+
failed_count = 0
|
|
155
|
+
|
|
156
|
+
for file_path in files_to_archive:
|
|
157
|
+
try:
|
|
158
|
+
archive_file = archive_dir / file_path.name
|
|
159
|
+
if archive_file.exists():
|
|
160
|
+
failed_count += 1
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Ensure parent directory exists and use rename (mv) for atomicity
|
|
164
|
+
archive_file.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
file_path.rename(archive_file)
|
|
166
|
+
archived_files.append(archive_file)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
self.console.print(
|
|
170
|
+
f"⚠️ Failed to archive {file_path.name}: {str(e)}",
|
|
171
|
+
style="yellow",
|
|
172
|
+
)
|
|
173
|
+
failed_count += 1
|
|
174
|
+
|
|
175
|
+
# Update state (set archived flag and move associated issues)
|
|
176
|
+
self.post_archive_hook(archived_files, entities, **kwargs)
|
|
177
|
+
|
|
178
|
+
# Display results
|
|
179
|
+
display_archive_success(
|
|
180
|
+
self.entity_type,
|
|
181
|
+
len(archived_files),
|
|
182
|
+
failed_count,
|
|
183
|
+
console=self.console,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.console.print(
|
|
190
|
+
f"❌ Archive operation failed: {str(e)}",
|
|
191
|
+
style="red",
|
|
192
|
+
)
|
|
193
|
+
import click
|
|
194
|
+
|
|
195
|
+
raise click.ClickException(str(e)) from e
|
|
196
|
+
|
|
197
|
+
def post_archive_hook(
|
|
198
|
+
self, archived_files: list[Path], entities: list[Any], **kwargs
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Handle post-archive state updates.
|
|
201
|
+
|
|
202
|
+
Moves associated issues folder and sets archived flag.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
archived_files: Files that were archived
|
|
206
|
+
entities: Entities that were archived
|
|
207
|
+
**kwargs: Additional arguments
|
|
208
|
+
"""
|
|
209
|
+
# Move associated issues folder to archive
|
|
210
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
211
|
+
|
|
212
|
+
for entity in entities:
|
|
213
|
+
issues_dir = roadmap_dir / "issues" / entity.name
|
|
214
|
+
if issues_dir.exists():
|
|
215
|
+
archive_issues_dir = roadmap_dir / "archive" / "issues" / entity.name
|
|
216
|
+
archive_issues_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
shutil.move(str(issues_dir), str(archive_issues_dir))
|
|
220
|
+
except Exception as e:
|
|
221
|
+
self.console.print(
|
|
222
|
+
f"⚠️ Warning: Failed to move issues for milestone {entity.name}: {e}",
|
|
223
|
+
style="yellow",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Set archived flag
|
|
227
|
+
try:
|
|
228
|
+
self.core.milestones.update_milestone(
|
|
229
|
+
name=entity.name,
|
|
230
|
+
archived=True,
|
|
231
|
+
)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.console.print(
|
|
234
|
+
f"⚠️ Warning: Failed to mark milestone {entity.name} as archived: {e}",
|
|
235
|
+
style="yellow",
|
|
236
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Assign issue to milestone command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
from roadmap.common.formatters.text.operations import (
|
|
9
|
+
format_operation_failure,
|
|
10
|
+
format_operation_success,
|
|
11
|
+
)
|
|
12
|
+
from roadmap.common.logging import (
|
|
13
|
+
log_command,
|
|
14
|
+
track_database_operation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
console = get_console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command("assign")
|
|
21
|
+
@click.argument("issue_id")
|
|
22
|
+
@click.argument("milestone_name")
|
|
23
|
+
@click.pass_context
|
|
24
|
+
@require_initialized
|
|
25
|
+
@log_command("milestone_assign", entity_type="milestone", track_duration=True)
|
|
26
|
+
def assign_milestone(ctx: click.Context, issue_id: str, milestone_name: str):
|
|
27
|
+
"""Assign an issue to a milestone."""
|
|
28
|
+
core = ctx.obj["core"]
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
with track_database_operation("update", "milestone", warn_threshold_ms=2000):
|
|
32
|
+
success = core.issues.assign_to_milestone(issue_id, milestone_name)
|
|
33
|
+
|
|
34
|
+
if success:
|
|
35
|
+
extra_details = {"Milestone": milestone_name}
|
|
36
|
+
lines = format_operation_success(
|
|
37
|
+
"✅", "Assigned", "", issue_id, None, extra_details
|
|
38
|
+
)
|
|
39
|
+
for line in lines:
|
|
40
|
+
console.print(line, style="green")
|
|
41
|
+
else:
|
|
42
|
+
lines = format_operation_failure(
|
|
43
|
+
"Assign", issue_id, "Issue or milestone not found"
|
|
44
|
+
)
|
|
45
|
+
for line in lines:
|
|
46
|
+
console.print(line, style="bold red")
|
|
47
|
+
raise click.Abort()
|
|
48
|
+
except click.Abort:
|
|
49
|
+
raise
|
|
50
|
+
except Exception as e:
|
|
51
|
+
handle_cli_error(
|
|
52
|
+
error=e,
|
|
53
|
+
operation="milestone_assign",
|
|
54
|
+
entity_type="milestone",
|
|
55
|
+
entity_id=milestone_name,
|
|
56
|
+
context={"issue_id": issue_id, "milestone_name": milestone_name},
|
|
57
|
+
fatal=True,
|
|
58
|
+
)
|
|
59
|
+
lines = format_operation_failure("Assign", issue_id, str(e))
|
|
60
|
+
for line in lines:
|
|
61
|
+
console.print(line, style="bold red")
|
|
62
|
+
raise click.Abort() from e
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Close milestone command.
|
|
2
|
+
|
|
3
|
+
SEMANTICS:
|
|
4
|
+
- Close: Changes status to 'closed', keeps files in place (no file movement), archived=false
|
|
5
|
+
- Archive: Moves files to archive directory AND sets archived=true flag (signals sync to delete remotely)
|
|
6
|
+
|
|
7
|
+
This separation allows for flexible status management without workspace reorganization.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
13
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
14
|
+
from roadmap.common.console import get_console
|
|
15
|
+
from roadmap.common.formatters.text.operations import (
|
|
16
|
+
format_operation_failure,
|
|
17
|
+
format_operation_success,
|
|
18
|
+
)
|
|
19
|
+
from roadmap.common.logging import (
|
|
20
|
+
log_command,
|
|
21
|
+
track_database_operation,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
console = get_console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _display_open_issues(milestone_name: str, open_issues: list) -> None:
|
|
28
|
+
"""Display open issues in milestone and provide guidance.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
milestone_name: Name of milestone
|
|
32
|
+
open_issues: List of open issues
|
|
33
|
+
"""
|
|
34
|
+
console.print(
|
|
35
|
+
f"\n⚠️ Cannot close milestone: {len(open_issues)} open issue(s) remain",
|
|
36
|
+
style="bold yellow",
|
|
37
|
+
)
|
|
38
|
+
console.print(f"\n📋 Open issues in '{milestone_name}':", style="cyan")
|
|
39
|
+
|
|
40
|
+
for issue in open_issues[:10]: # Show first 10
|
|
41
|
+
console.print(
|
|
42
|
+
f" • {issue.id[:8]} - {issue.title} [{issue.status.value}]",
|
|
43
|
+
style="dim",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if len(open_issues) > 10:
|
|
47
|
+
console.print(f" ... and {len(open_issues) - 10} more", style="dim")
|
|
48
|
+
|
|
49
|
+
console.print(
|
|
50
|
+
"\n💡 Options to proceed:",
|
|
51
|
+
style="cyan",
|
|
52
|
+
)
|
|
53
|
+
console.print(
|
|
54
|
+
" 1. Close the open issues: roadmap issue close <issue_id>",
|
|
55
|
+
style="dim",
|
|
56
|
+
)
|
|
57
|
+
console.print(
|
|
58
|
+
" 2. Migrate to backlog: roadmap issue update <issue_id> --milestone ''",
|
|
59
|
+
style="dim",
|
|
60
|
+
)
|
|
61
|
+
console.print(
|
|
62
|
+
" 3. Move to a different milestone: roadmap issue update <issue_id> --milestone 'New Milestone'",
|
|
63
|
+
style="dim",
|
|
64
|
+
)
|
|
65
|
+
console.print(
|
|
66
|
+
f" 4. Use --force to close anyway (not recommended): roadmap milestone close '{milestone_name}' --force",
|
|
67
|
+
style="dim",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _confirm_milestone_close(
|
|
72
|
+
milestone_name: str, total_issues: int, force: bool
|
|
73
|
+
) -> bool:
|
|
74
|
+
"""Confirm milestone closure with user.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
milestone_name: Name of milestone
|
|
78
|
+
total_issues: Total number of issues
|
|
79
|
+
force: Skip confirmation if True
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if user confirmed or force is True
|
|
83
|
+
"""
|
|
84
|
+
if force:
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
return click.confirm(
|
|
88
|
+
f"Close milestone '{milestone_name}'? All {total_issues} issue(s) are completed."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _close_milestone_in_db(core, milestone_name: str) -> bool:
|
|
93
|
+
"""Close milestone in database.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
core: RoadmapCore instance
|
|
97
|
+
milestone_name: Name of milestone
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if successful
|
|
101
|
+
"""
|
|
102
|
+
from roadmap.core.domain import MilestoneStatus
|
|
103
|
+
|
|
104
|
+
with track_database_operation("update", "milestone"):
|
|
105
|
+
return core.milestones.update(milestone_name, status=MilestoneStatus.CLOSED)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@click.command("close")
|
|
109
|
+
@click.argument("milestone_name")
|
|
110
|
+
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
|
|
111
|
+
@click.pass_context
|
|
112
|
+
@require_initialized
|
|
113
|
+
@log_command("milestone_close", entity_type="milestone", track_duration=True)
|
|
114
|
+
def close_milestone(ctx: click.Context, milestone_name: str, force: bool):
|
|
115
|
+
"""Close a milestone.
|
|
116
|
+
|
|
117
|
+
Validates that all issues in the milestone are closed before allowing closure.
|
|
118
|
+
If there are open issues, provides guidance on migration options.
|
|
119
|
+
"""
|
|
120
|
+
core = ctx.obj["core"]
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# Check if milestone exists
|
|
124
|
+
milestone = core.milestones.get(milestone_name)
|
|
125
|
+
if not milestone:
|
|
126
|
+
lines = format_operation_failure(
|
|
127
|
+
"Close", milestone_name, "Milestone not found"
|
|
128
|
+
)
|
|
129
|
+
for line in lines:
|
|
130
|
+
console.print(line, style="bold red")
|
|
131
|
+
raise click.Abort()
|
|
132
|
+
|
|
133
|
+
# Get all issues in this milestone
|
|
134
|
+
all_issues = core.issues.list(milestone=milestone_name)
|
|
135
|
+
|
|
136
|
+
# Filter for open issues (not closed)
|
|
137
|
+
open_issues = [issue for issue in all_issues if issue.status.value != "closed"]
|
|
138
|
+
|
|
139
|
+
# If there are open issues, provide guidance and return
|
|
140
|
+
if open_issues:
|
|
141
|
+
_display_open_issues(milestone_name, open_issues)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
# All issues are closed, confirm and proceed
|
|
145
|
+
if not _confirm_milestone_close(milestone_name, len(all_issues), force):
|
|
146
|
+
console.print("❌ Milestone close cancelled.", style="yellow")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Close milestone in database
|
|
150
|
+
success = _close_milestone_in_db(core, milestone_name)
|
|
151
|
+
|
|
152
|
+
if success:
|
|
153
|
+
extra_details = {"Completed issues": str(len(all_issues))}
|
|
154
|
+
lines = format_operation_success(
|
|
155
|
+
"✅", "Closed", milestone_name, "", None, extra_details
|
|
156
|
+
)
|
|
157
|
+
for line in lines:
|
|
158
|
+
console.print(line, style="green")
|
|
159
|
+
else:
|
|
160
|
+
lines = format_operation_failure(
|
|
161
|
+
"Close", milestone_name, "Failed to update status"
|
|
162
|
+
)
|
|
163
|
+
for line in lines:
|
|
164
|
+
console.print(line, style="bold red")
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
handle_cli_error(
|
|
168
|
+
error=e,
|
|
169
|
+
operation="close_milestone",
|
|
170
|
+
entity_type="milestone",
|
|
171
|
+
entity_id=milestone_name,
|
|
172
|
+
context={"force": force},
|
|
173
|
+
fatal=True,
|
|
174
|
+
)
|
|
175
|
+
lines = format_operation_failure("Close", milestone_name, str(e))
|
|
176
|
+
for line in lines:
|
|
177
|
+
console.print(line, style="bold red")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Create milestone command."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
8
|
+
from roadmap.adapters.cli.crud import BaseCreate, EntityType
|
|
9
|
+
from roadmap.adapters.cli.crud.entity_builders import MilestoneBuilder
|
|
10
|
+
from roadmap.common.logging import (
|
|
11
|
+
log_command,
|
|
12
|
+
verbose_output,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _validate_milestone_name(name: str) -> tuple[bool, str | None]:
|
|
17
|
+
"""Validate milestone name against naming conventions.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: The milestone name to validate
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Tuple of (is_valid, error_message)
|
|
24
|
+
"""
|
|
25
|
+
# Check for invalid characters
|
|
26
|
+
if not re.match(r"^[a-zA-Z0-9_\-\.]+$", name):
|
|
27
|
+
return (
|
|
28
|
+
False,
|
|
29
|
+
"Milestone name contains invalid characters. Use only: letters, numbers, hyphens, underscores, dots",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Check for version patterns with dots (should use hyphens instead)
|
|
33
|
+
if re.match(r"^v\d+\.\d+", name): # e.g., v1.0., v1.0.2, v2.0
|
|
34
|
+
suggested = name.replace(".", "-")
|
|
35
|
+
return (
|
|
36
|
+
False,
|
|
37
|
+
f"Version milestones should use hyphens, not dots.\n"
|
|
38
|
+
f" Current: {name}\n"
|
|
39
|
+
f" Suggested: {suggested}",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Valid milestone name
|
|
43
|
+
return True, None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MilestoneCreate(BaseCreate):
|
|
47
|
+
"""Create milestone command implementation."""
|
|
48
|
+
|
|
49
|
+
entity_type = EntityType.MILESTONE
|
|
50
|
+
|
|
51
|
+
def build_entity_dict(self, title: str, **kwargs) -> dict:
|
|
52
|
+
"""Build entity dictionary for milestone creation."""
|
|
53
|
+
create_dict = MilestoneBuilder.build_create_dict(
|
|
54
|
+
name=title,
|
|
55
|
+
description=kwargs.get("description"),
|
|
56
|
+
due_date=kwargs.get("due_date"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Add project_id if provided or auto-detected
|
|
60
|
+
if kwargs.get("project_id"):
|
|
61
|
+
create_dict["project_id"] = kwargs.get("project_id")
|
|
62
|
+
|
|
63
|
+
return create_dict
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@click.command("create")
|
|
67
|
+
@click.option("--title", "-t", required=True, help="Milestone title")
|
|
68
|
+
@click.option("--description", "-d", default="", help="Milestone description")
|
|
69
|
+
@click.option("--due-date", help="Due date for milestone (YYYY-MM-DD format)")
|
|
70
|
+
@click.option("--project", "-p", default=None, help="Project ID to assign milestone to")
|
|
71
|
+
@click.pass_context
|
|
72
|
+
@require_initialized
|
|
73
|
+
@verbose_output
|
|
74
|
+
@log_command("milestone_create", entity_type="milestone", track_duration=True)
|
|
75
|
+
def create_milestone(
|
|
76
|
+
ctx: click.Context,
|
|
77
|
+
title: str,
|
|
78
|
+
description: str,
|
|
79
|
+
due_date: str,
|
|
80
|
+
project: str | None,
|
|
81
|
+
):
|
|
82
|
+
"""Create a new milestone."""
|
|
83
|
+
# Validate milestone name against naming conventions
|
|
84
|
+
is_valid, error_msg = _validate_milestone_name(title)
|
|
85
|
+
if not is_valid:
|
|
86
|
+
if error_msg is None:
|
|
87
|
+
error_msg = "Invalid milestone name"
|
|
88
|
+
raise click.ClickException(error_msg)
|
|
89
|
+
|
|
90
|
+
core = ctx.obj["core"]
|
|
91
|
+
creator = MilestoneCreate(core)
|
|
92
|
+
|
|
93
|
+
# Auto-detect project if not provided
|
|
94
|
+
project_id = project
|
|
95
|
+
if not project_id:
|
|
96
|
+
from roadmap.common.configuration import ConfigManager
|
|
97
|
+
|
|
98
|
+
# Try to get default project from config
|
|
99
|
+
config_manager = ConfigManager(core.config_file)
|
|
100
|
+
config = config_manager.load()
|
|
101
|
+
|
|
102
|
+
if config.behavior.default_project_id:
|
|
103
|
+
project_id = config.behavior.default_project_id
|
|
104
|
+
click.echo(
|
|
105
|
+
f"ℹ️ Assigning milestone to default project: {project_id}", err=False
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
# Check how many projects exist
|
|
109
|
+
all_projects = list(core.projects.list())
|
|
110
|
+
if all_projects:
|
|
111
|
+
# Projects exist but no default - need clarification
|
|
112
|
+
if len(all_projects) > 1:
|
|
113
|
+
raise click.ClickException(
|
|
114
|
+
f"Multiple projects found ({len(all_projects)}). Please specify project with --project flag.\n"
|
|
115
|
+
f"Available projects: {', '.join(p.id for p in all_projects)}"
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
# Auto-assign to the only project
|
|
119
|
+
project_id = all_projects[0].id
|
|
120
|
+
click.echo(
|
|
121
|
+
f"ℹ️ Assigning milestone to project: {all_projects[0].name} ({project_id})",
|
|
122
|
+
err=False,
|
|
123
|
+
)
|
|
124
|
+
# If no projects exist, that's OK - milestone can be created without a project
|
|
125
|
+
|
|
126
|
+
creator.execute(
|
|
127
|
+
title=title,
|
|
128
|
+
description=description,
|
|
129
|
+
due_date=due_date,
|
|
130
|
+
project_id=project_id,
|
|
131
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Delete milestone command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseDelete, EntityType
|
|
7
|
+
from roadmap.common.logging import log_command
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MilestoneDelete(BaseDelete):
|
|
11
|
+
"""Delete milestone command implementation."""
|
|
12
|
+
|
|
13
|
+
entity_type = EntityType.MILESTONE
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command("delete")
|
|
17
|
+
@click.argument("milestone_id")
|
|
18
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
@require_initialized
|
|
21
|
+
@log_command("milestone_delete", entity_type="milestone", track_duration=True)
|
|
22
|
+
def delete_milestone(ctx: click.Context, milestone_id: str, yes: bool):
|
|
23
|
+
"""Delete a milestone."""
|
|
24
|
+
core = ctx.obj["core"]
|
|
25
|
+
deleter = MilestoneDelete(core)
|
|
26
|
+
|
|
27
|
+
deleter.execute(entity_id=milestone_id, force=yes)
|