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,374 @@
|
|
|
1
|
+
"""Sync status command - View sync history and statistics for GitHub-linked issues."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
from roadmap.adapters.cli.cli_command_helpers import ensure_entity_exists
|
|
11
|
+
from roadmap.common.console import get_console
|
|
12
|
+
from roadmap.common.datetime_parser import UnifiedDateTimeParser
|
|
13
|
+
from roadmap.core.services.sync.sync_metadata_service import SyncMetadataService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_console():
|
|
17
|
+
"""Get a fresh console instance for test compatibility."""
|
|
18
|
+
return get_console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
console = get_console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _format_timestamp(iso_str: str) -> str:
|
|
25
|
+
"""Format ISO timestamp to human-readable format."""
|
|
26
|
+
try:
|
|
27
|
+
dt = UnifiedDateTimeParser.parse_iso_datetime(iso_str)
|
|
28
|
+
if dt:
|
|
29
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
30
|
+
return iso_str
|
|
31
|
+
except Exception as e:
|
|
32
|
+
from roadmap.common.logging import get_logger
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
logger.debug(
|
|
36
|
+
"iso_datetime_format_failed",
|
|
37
|
+
iso_str=iso_str,
|
|
38
|
+
error=str(e),
|
|
39
|
+
action="format_iso_timestamp",
|
|
40
|
+
)
|
|
41
|
+
return iso_str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _build_sync_status_header(issue, metadata):
|
|
45
|
+
"""Build header text with issue title and sync status."""
|
|
46
|
+
header = Text()
|
|
47
|
+
header.append(f"#{issue.id}", style="bold cyan")
|
|
48
|
+
header.append(" • ", style="dim")
|
|
49
|
+
header.append(issue.title, style="bold white")
|
|
50
|
+
header.append("\n")
|
|
51
|
+
|
|
52
|
+
# Add sync status
|
|
53
|
+
status_color_map = {
|
|
54
|
+
"success": "green",
|
|
55
|
+
"conflict": "yellow",
|
|
56
|
+
"error": "red",
|
|
57
|
+
"never": "dim",
|
|
58
|
+
}
|
|
59
|
+
status_color = status_color_map.get(metadata.last_sync_status, "white")
|
|
60
|
+
header.append(
|
|
61
|
+
f"[{metadata.last_sync_status.upper()}]", style=f"bold {status_color}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if metadata.last_sync_time:
|
|
65
|
+
header.append(" • ", style="dim")
|
|
66
|
+
header.append(
|
|
67
|
+
f"Last synced: {_format_timestamp(metadata.last_sync_time)}",
|
|
68
|
+
style="dim",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return header
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _build_sync_metadata_table(issue, metadata):
|
|
75
|
+
"""Build metadata table showing sync statistics."""
|
|
76
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
77
|
+
|
|
78
|
+
if metadata.github_issue_id:
|
|
79
|
+
table.add_row("GitHub Issue", f"#{metadata.github_issue_id}")
|
|
80
|
+
|
|
81
|
+
table.add_row("Total Syncs", str(metadata.sync_count))
|
|
82
|
+
table.add_row("Successful", str(metadata.successful_syncs))
|
|
83
|
+
|
|
84
|
+
if metadata.sync_count > 0:
|
|
85
|
+
success_rate = metadata.get_success_rate()
|
|
86
|
+
rate_color = (
|
|
87
|
+
"green"
|
|
88
|
+
if success_rate == 100
|
|
89
|
+
else "yellow"
|
|
90
|
+
if success_rate >= 75
|
|
91
|
+
else "red"
|
|
92
|
+
)
|
|
93
|
+
table.add_row(
|
|
94
|
+
"Success Rate", f"[{rate_color}]{success_rate:.1f}%[/{rate_color}]"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
conflict_count = sum(
|
|
98
|
+
1 for record in metadata.sync_history if record.conflict_resolution
|
|
99
|
+
)
|
|
100
|
+
if conflict_count > 0:
|
|
101
|
+
table.add_row("Conflicts Resolved", str(conflict_count))
|
|
102
|
+
|
|
103
|
+
return table
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _build_sync_history_table(records):
|
|
107
|
+
"""Build table showing recent sync history."""
|
|
108
|
+
table = Table(
|
|
109
|
+
title="Sync History (Most Recent First)",
|
|
110
|
+
show_header=True,
|
|
111
|
+
header_style="bold cyan",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
table.add_column("Time", style="dim", width=20)
|
|
115
|
+
table.add_column("Status", width=12)
|
|
116
|
+
table.add_column("Details", width=30)
|
|
117
|
+
|
|
118
|
+
for record in records:
|
|
119
|
+
time_str = _format_timestamp(record.sync_timestamp)
|
|
120
|
+
|
|
121
|
+
# Status
|
|
122
|
+
if record.success:
|
|
123
|
+
status_text = "[green]✓ SUCCESS[/green]"
|
|
124
|
+
elif record.conflict_resolution:
|
|
125
|
+
status_text = "[yellow]⚠ CONFLICT[/yellow]"
|
|
126
|
+
else:
|
|
127
|
+
status_text = "[red]✗ ERROR[/red]"
|
|
128
|
+
|
|
129
|
+
# Details
|
|
130
|
+
details_parts = []
|
|
131
|
+
if record.local_changes:
|
|
132
|
+
details_parts.append(f"Local: {len(record.local_changes)} changes")
|
|
133
|
+
if record.github_changes:
|
|
134
|
+
details_parts.append(f"GitHub: {len(record.github_changes)} changes")
|
|
135
|
+
if record.conflict_resolution:
|
|
136
|
+
details_parts.append(f"Resolved: {record.conflict_resolution}")
|
|
137
|
+
if record.error_message:
|
|
138
|
+
# Truncate long error messages
|
|
139
|
+
error = record.error_message
|
|
140
|
+
if len(error) > 40:
|
|
141
|
+
error = error[:37] + "..."
|
|
142
|
+
details_parts.append(f"Error: {error}")
|
|
143
|
+
|
|
144
|
+
details_text = " • ".join(details_parts) if details_parts else "—"
|
|
145
|
+
|
|
146
|
+
table.add_row(time_str, status_text, details_text)
|
|
147
|
+
|
|
148
|
+
return table
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _build_aggregate_stats_table(stats):
|
|
152
|
+
"""Build table showing aggregate statistics across issues."""
|
|
153
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
154
|
+
|
|
155
|
+
table.add_row("Total Issues", str(stats["total_issues"]))
|
|
156
|
+
table.add_row("Total Sync Attempts", str(stats["total_sync_attempts"]))
|
|
157
|
+
table.add_row("Successful Syncs", str(stats["successful_syncs"]))
|
|
158
|
+
|
|
159
|
+
if stats["total_sync_attempts"] > 0:
|
|
160
|
+
rate_color = (
|
|
161
|
+
"green"
|
|
162
|
+
if stats["success_rate"] == 100
|
|
163
|
+
else "yellow"
|
|
164
|
+
if stats["success_rate"] >= 75
|
|
165
|
+
else "red"
|
|
166
|
+
)
|
|
167
|
+
table.add_row(
|
|
168
|
+
"Overall Success Rate",
|
|
169
|
+
f"[{rate_color}]{stats['success_rate']:.1f}%[/{rate_color}]",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if stats["total_conflicts"] > 0:
|
|
173
|
+
conflict_color = (
|
|
174
|
+
"green"
|
|
175
|
+
if stats["conflict_rate"] < 5
|
|
176
|
+
else "yellow"
|
|
177
|
+
if stats["conflict_rate"] < 20
|
|
178
|
+
else "red"
|
|
179
|
+
)
|
|
180
|
+
table.add_row(
|
|
181
|
+
"Conflict Rate",
|
|
182
|
+
f"[{conflict_color}]{stats['conflict_rate']:.1f}%[/{conflict_color}]",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
table.add_row("Never Synced", str(stats["never_synced"]))
|
|
186
|
+
|
|
187
|
+
return table
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _handle_sync_statistics(
|
|
191
|
+
core, console_inst, metadata_service: "SyncMetadataService"
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Display aggregate sync statistics across all GitHub-linked issues."""
|
|
194
|
+
all_issues = core.issues.all()
|
|
195
|
+
github_issues = [i for i in all_issues if getattr(i, "github_issue", None)]
|
|
196
|
+
if not github_issues:
|
|
197
|
+
console_inst.print("[yellow]⚠️ No GitHub-linked issues found[/yellow]")
|
|
198
|
+
return
|
|
199
|
+
stats = metadata_service.get_statistics(github_issues)
|
|
200
|
+
panel = Panel(
|
|
201
|
+
_build_aggregate_stats_table(stats),
|
|
202
|
+
title="[bold cyan]GitHub Sync Statistics[/bold cyan]",
|
|
203
|
+
expand=False,
|
|
204
|
+
)
|
|
205
|
+
console_inst.print(panel)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _handle_all_issues_status(
|
|
209
|
+
core, console_inst, metadata_service: "SyncMetadataService"
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Display a brief sync-status overview for every GitHub-linked issue."""
|
|
212
|
+
all_issues = core.issues.all()
|
|
213
|
+
github_issues = [i for i in all_issues if getattr(i, "github_issue", None)]
|
|
214
|
+
if not github_issues:
|
|
215
|
+
console_inst.print("[yellow]⚠️ No GitHub-linked issues found[/yellow]")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
table = Table(
|
|
219
|
+
title=f"GitHub Sync Status ({len(github_issues)} issues)",
|
|
220
|
+
show_header=True,
|
|
221
|
+
header_style="bold cyan",
|
|
222
|
+
)
|
|
223
|
+
table.add_column("Issue ID", style="cyan")
|
|
224
|
+
table.add_column("GitHub", width=10)
|
|
225
|
+
table.add_column("Title", width=35)
|
|
226
|
+
table.add_column("Status", width=12)
|
|
227
|
+
table.add_column("Last Sync", width=20)
|
|
228
|
+
table.add_column("Success %", width=12)
|
|
229
|
+
|
|
230
|
+
status_color_map = {
|
|
231
|
+
"success": "green",
|
|
232
|
+
"conflict": "yellow",
|
|
233
|
+
"error": "red",
|
|
234
|
+
"never": "dim",
|
|
235
|
+
}
|
|
236
|
+
for issue in github_issues:
|
|
237
|
+
metadata = metadata_service.get_metadata(issue)
|
|
238
|
+
status_color = status_color_map.get(metadata.last_sync_status, "white")
|
|
239
|
+
last_sync = (
|
|
240
|
+
_format_timestamp(metadata.last_sync_time)
|
|
241
|
+
if metadata.last_sync_time
|
|
242
|
+
else "Never"
|
|
243
|
+
)
|
|
244
|
+
success_rate = (
|
|
245
|
+
f"{metadata.get_success_rate():.0f}%" if metadata.sync_count > 0 else "—"
|
|
246
|
+
)
|
|
247
|
+
table.add_row(
|
|
248
|
+
issue.id,
|
|
249
|
+
f"#{metadata.github_issue_id}",
|
|
250
|
+
issue.title[:32],
|
|
251
|
+
f"[{status_color}]{metadata.last_sync_status}[/{status_color}]",
|
|
252
|
+
last_sync,
|
|
253
|
+
success_rate,
|
|
254
|
+
)
|
|
255
|
+
console_inst.print(table)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _handle_single_issue_status(
|
|
259
|
+
issue_id: str,
|
|
260
|
+
show_history: bool,
|
|
261
|
+
core,
|
|
262
|
+
console_inst,
|
|
263
|
+
metadata_service: "SyncMetadataService",
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Display full sync status detail for a single GitHub-linked issue."""
|
|
266
|
+
if not issue_id:
|
|
267
|
+
console_inst.print(
|
|
268
|
+
"[red]❌ Must provide ISSUE_ID or use --all or --statistics[/red]"
|
|
269
|
+
)
|
|
270
|
+
sys.exit(1)
|
|
271
|
+
|
|
272
|
+
issue = ensure_entity_exists(core.issues, issue_id, "Issue")
|
|
273
|
+
if not issue:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
if not getattr(issue, "github_issue", None):
|
|
277
|
+
console_inst.print(
|
|
278
|
+
f"[yellow]⚠️ Issue #{issue_id} is not linked to GitHub[/yellow]"
|
|
279
|
+
)
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
metadata = metadata_service.get_metadata(issue)
|
|
283
|
+
|
|
284
|
+
console_inst.print()
|
|
285
|
+
console_inst.print(Panel(_build_sync_status_header(issue, metadata), expand=False))
|
|
286
|
+
console_inst.print()
|
|
287
|
+
console_inst.print(
|
|
288
|
+
Panel(
|
|
289
|
+
_build_sync_metadata_table(issue, metadata),
|
|
290
|
+
title="[bold cyan]Sync Statistics[/bold cyan]",
|
|
291
|
+
expand=False,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
console_inst.print()
|
|
295
|
+
|
|
296
|
+
if metadata.sync_history:
|
|
297
|
+
history_records = list(reversed(metadata.sync_history))
|
|
298
|
+
if not show_history:
|
|
299
|
+
history_records = history_records[:5]
|
|
300
|
+
console_inst.print(_build_sync_history_table(history_records))
|
|
301
|
+
if not show_history and len(metadata.sync_history) > 5:
|
|
302
|
+
console_inst.print(
|
|
303
|
+
f"\n[dim]... showing last 5 of {len(metadata.sync_history)} syncs. "
|
|
304
|
+
f"Use --history to see all[/dim]"
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
console_inst.print(
|
|
308
|
+
Panel(
|
|
309
|
+
"[dim]No sync history recorded yet[/dim]",
|
|
310
|
+
title="[bold cyan]Sync History[/bold cyan]",
|
|
311
|
+
expand=False,
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
console_inst.print()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@click.command(name="sync-status")
|
|
319
|
+
@click.argument("issue_id", default="", required=False)
|
|
320
|
+
@click.option("--history", "show_history", is_flag=True, help="Show full sync history")
|
|
321
|
+
@click.option(
|
|
322
|
+
"--statistics",
|
|
323
|
+
"show_statistics",
|
|
324
|
+
is_flag=True,
|
|
325
|
+
help="Show aggregate statistics across all issues",
|
|
326
|
+
)
|
|
327
|
+
@click.option(
|
|
328
|
+
"--all",
|
|
329
|
+
"show_all_issues",
|
|
330
|
+
is_flag=True,
|
|
331
|
+
help="Show sync status for all GitHub-linked issues",
|
|
332
|
+
)
|
|
333
|
+
@click.pass_context
|
|
334
|
+
def sync_status(
|
|
335
|
+
ctx: click.Context,
|
|
336
|
+
issue_id: str,
|
|
337
|
+
show_history: bool,
|
|
338
|
+
show_statistics: bool,
|
|
339
|
+
show_all_issues: bool,
|
|
340
|
+
) -> None:
|
|
341
|
+
"""Show sync status and history for GitHub-linked issues.
|
|
342
|
+
|
|
343
|
+
Examples:
|
|
344
|
+
# Show status for a single issue
|
|
345
|
+
roadmap issue sync-status ISSUE_ID
|
|
346
|
+
|
|
347
|
+
# Show full sync history
|
|
348
|
+
roadmap issue sync-status ISSUE_ID --history
|
|
349
|
+
|
|
350
|
+
# Show aggregate statistics
|
|
351
|
+
roadmap issue sync-status --statistics
|
|
352
|
+
|
|
353
|
+
# Show status for all linked issues
|
|
354
|
+
roadmap issue sync-status --all
|
|
355
|
+
|
|
356
|
+
The command displays:
|
|
357
|
+
- Last sync time and status
|
|
358
|
+
- Total sync attempts and success rate
|
|
359
|
+
- Recent sync history with change details
|
|
360
|
+
- Conflict resolution information
|
|
361
|
+
"""
|
|
362
|
+
core = ctx.obj
|
|
363
|
+
console_inst = get_console()
|
|
364
|
+
metadata_service = SyncMetadataService(core)
|
|
365
|
+
|
|
366
|
+
if show_statistics:
|
|
367
|
+
_handle_sync_statistics(core, console_inst, metadata_service)
|
|
368
|
+
return
|
|
369
|
+
if show_all_issues:
|
|
370
|
+
_handle_all_issues_status(core, console_inst, metadata_service)
|
|
371
|
+
return
|
|
372
|
+
_handle_single_issue_status(
|
|
373
|
+
issue_id, show_history, core, console_inst, metadata_service
|
|
374
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Unblock issue command - thin wrapper around update.
|
|
2
|
+
|
|
3
|
+
This command is syntactic sugar for: roadmap issue update <ID> --status in-progress
|
|
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("unblock")
|
|
16
|
+
@click.argument("issue_id")
|
|
17
|
+
@click.option("--reason", "-r", help="Reason for unblocking")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@handle_cli_errors(command_name="issue unblock")
|
|
20
|
+
@require_initialized
|
|
21
|
+
def unblock_issue(ctx: click.Context, issue_id: str, reason: str):
|
|
22
|
+
"""Unblock an issue by setting status to in-progress.
|
|
23
|
+
|
|
24
|
+
Syntactic sugar for: roadmap issue update <ID> --status in-progress
|
|
25
|
+
"""
|
|
26
|
+
core = ctx.obj["core"]
|
|
27
|
+
|
|
28
|
+
def check_is_blocked(issue):
|
|
29
|
+
"""Verify issue is currently blocked before unblocking."""
|
|
30
|
+
status_val = getattr(issue.status, "value", str(issue.status))
|
|
31
|
+
if status_val != "blocked":
|
|
32
|
+
return False, f"Issue is not blocked (current: {status_val})"
|
|
33
|
+
return True, None
|
|
34
|
+
|
|
35
|
+
config = StatusChangeConfig(
|
|
36
|
+
status=Status.IN_PROGRESS,
|
|
37
|
+
emoji="✅",
|
|
38
|
+
title_verb="Unblocked",
|
|
39
|
+
title_style="bold green",
|
|
40
|
+
status_display="🔄 In Progress",
|
|
41
|
+
pre_check=check_is_blocked,
|
|
42
|
+
)
|
|
43
|
+
apply_status_change(core, issue_id, config, reason)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Unlink GitHub issue 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("unlink-github")
|
|
21
|
+
@click.argument("issue_id")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
@log_command("issue_unlink", entity_type="issue", track_duration=True)
|
|
24
|
+
@require_initialized
|
|
25
|
+
def unlink_github_issue(ctx: click.Context, issue_id: str) -> None:
|
|
26
|
+
"""Remove GitHub link from a local issue.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
ctx: Click context with core object
|
|
30
|
+
issue_id: ID of the local issue to unlink
|
|
31
|
+
"""
|
|
32
|
+
core = ctx.obj["core"]
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# Get the issue
|
|
36
|
+
issue = core.issues.get(issue_id)
|
|
37
|
+
if not issue:
|
|
38
|
+
lines = format_operation_failure(
|
|
39
|
+
action="unlink",
|
|
40
|
+
entity_id=issue_id,
|
|
41
|
+
error="Issue not found",
|
|
42
|
+
)
|
|
43
|
+
for line in lines:
|
|
44
|
+
console.print(line, style="bold red")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Check if issue is linked
|
|
48
|
+
if issue.github_issue is None:
|
|
49
|
+
console.print(
|
|
50
|
+
f"⚠️ Issue '{issue_id}' is not linked to GitHub",
|
|
51
|
+
style="yellow",
|
|
52
|
+
)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# Store the GitHub ID for display
|
|
56
|
+
github_id = issue.github_issue
|
|
57
|
+
|
|
58
|
+
# Remove the link
|
|
59
|
+
with track_database_operation(
|
|
60
|
+
"update", "issue", entity_id=issue_id, warn_threshold_ms=2000
|
|
61
|
+
):
|
|
62
|
+
updated_issue = core.issues.update(issue_id, github_issue=None)
|
|
63
|
+
|
|
64
|
+
if updated_issue:
|
|
65
|
+
extra_details = {
|
|
66
|
+
"GitHub Issue": f"#{github_id}",
|
|
67
|
+
"Local Issue": issue_id,
|
|
68
|
+
}
|
|
69
|
+
lines = format_operation_success(
|
|
70
|
+
emoji="✅",
|
|
71
|
+
action="Unlinked",
|
|
72
|
+
entity_title=updated_issue.title,
|
|
73
|
+
entity_id=issue_id,
|
|
74
|
+
extra_details=extra_details,
|
|
75
|
+
)
|
|
76
|
+
for line in lines:
|
|
77
|
+
console.print(
|
|
78
|
+
line, style="bold green" if "Unlinked" in line else "cyan"
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
lines = format_operation_failure(
|
|
82
|
+
action="unlink",
|
|
83
|
+
entity_id=issue_id,
|
|
84
|
+
error="Failed to update issue",
|
|
85
|
+
)
|
|
86
|
+
for line in lines:
|
|
87
|
+
console.print(line, style="bold red")
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
handle_cli_error(
|
|
91
|
+
error=e,
|
|
92
|
+
operation="unlink_github_issue",
|
|
93
|
+
entity_type="issue",
|
|
94
|
+
entity_id=issue_id,
|
|
95
|
+
context={},
|
|
96
|
+
fatal=True,
|
|
97
|
+
)
|
|
98
|
+
lines = format_operation_failure(
|
|
99
|
+
action="unlink",
|
|
100
|
+
entity_id=issue_id,
|
|
101
|
+
error=str(e),
|
|
102
|
+
)
|
|
103
|
+
for line in lines:
|
|
104
|
+
console.print(line, style="bold red")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Update issue command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseUpdate, EntityType
|
|
7
|
+
from roadmap.adapters.cli.crud.entity_builders import IssueBuilder
|
|
8
|
+
from roadmap.adapters.cli.presentation.crud_presenter import UpdatePresenter
|
|
9
|
+
from roadmap.common.logging import (
|
|
10
|
+
log_command,
|
|
11
|
+
)
|
|
12
|
+
from roadmap.common.models import IssueUpdateParams
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IssueUpdate(BaseUpdate):
|
|
16
|
+
"""Update issue command implementation."""
|
|
17
|
+
|
|
18
|
+
entity_type = EntityType.ISSUE
|
|
19
|
+
|
|
20
|
+
def __init__(self, core):
|
|
21
|
+
"""Initialize IssueUpdate."""
|
|
22
|
+
super().__init__(core)
|
|
23
|
+
self._current_update_dict = {}
|
|
24
|
+
self._current_reason = None
|
|
25
|
+
|
|
26
|
+
def build_update_dict(self, entity_id: str, **kwargs) -> dict:
|
|
27
|
+
"""Build update dictionary for issue."""
|
|
28
|
+
return IssueBuilder.build_update_dict(
|
|
29
|
+
title=kwargs.get("title"),
|
|
30
|
+
priority=kwargs.get("priority"),
|
|
31
|
+
status=kwargs.get("status"),
|
|
32
|
+
assignee=kwargs.get("assignee"),
|
|
33
|
+
milestone=kwargs.get("milestone"),
|
|
34
|
+
description=kwargs.get("description"),
|
|
35
|
+
estimate=kwargs.get("estimate"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def execute(self, entity_id: str, **kwargs):
|
|
39
|
+
"""Execute update and display results using proper formatter.
|
|
40
|
+
|
|
41
|
+
Overrides parent to store update_dict and reason for display.
|
|
42
|
+
"""
|
|
43
|
+
# Store for use in _display_success
|
|
44
|
+
self._current_update_dict = self.build_update_dict(
|
|
45
|
+
entity_id=entity_id, **kwargs
|
|
46
|
+
)
|
|
47
|
+
self._current_reason = kwargs.get("reason")
|
|
48
|
+
|
|
49
|
+
# Call parent execute which will call _display_success
|
|
50
|
+
return super().execute(entity_id=entity_id, **kwargs)
|
|
51
|
+
|
|
52
|
+
def _display_success(self, entity) -> None:
|
|
53
|
+
"""Display success message using presenter."""
|
|
54
|
+
presenter = UpdatePresenter()
|
|
55
|
+
presenter.render(entity, self._current_update_dict)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@click.command("update")
|
|
59
|
+
@click.argument("issue_id")
|
|
60
|
+
@click.option("--title", help="Update issue title")
|
|
61
|
+
@click.option(
|
|
62
|
+
"--priority",
|
|
63
|
+
"-p",
|
|
64
|
+
type=click.Choice(["critical", "high", "medium", "low"]),
|
|
65
|
+
help="Update priority",
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"--status",
|
|
69
|
+
"-s",
|
|
70
|
+
type=click.Choice(["todo", "in-progress", "blocked", "review", "closed"]),
|
|
71
|
+
help="Update status",
|
|
72
|
+
)
|
|
73
|
+
@click.option("--assignee", "-a", help="Update assignee")
|
|
74
|
+
@click.option("--milestone", "-m", help="Update milestone")
|
|
75
|
+
@click.option("--description", "-d", help="Update description")
|
|
76
|
+
@click.option("--estimate", "-e", type=float, help="Update estimated time (in hours)")
|
|
77
|
+
@click.option("--reason", "-r", help="Reason for the update")
|
|
78
|
+
@click.pass_context
|
|
79
|
+
@log_command("issue_update", entity_type="issue", track_duration=True)
|
|
80
|
+
@require_initialized
|
|
81
|
+
def update_issue(
|
|
82
|
+
ctx: click.Context,
|
|
83
|
+
issue_id: str,
|
|
84
|
+
title: str,
|
|
85
|
+
priority: str,
|
|
86
|
+
status: str,
|
|
87
|
+
assignee: str,
|
|
88
|
+
milestone: str,
|
|
89
|
+
description: str,
|
|
90
|
+
estimate: float,
|
|
91
|
+
reason: str,
|
|
92
|
+
):
|
|
93
|
+
"""Update an existing issue."""
|
|
94
|
+
core = ctx.obj["core"]
|
|
95
|
+
updater = IssueUpdate(core)
|
|
96
|
+
|
|
97
|
+
# Create structured parameter object
|
|
98
|
+
params = IssueUpdateParams(
|
|
99
|
+
issue_id=issue_id,
|
|
100
|
+
title=title,
|
|
101
|
+
priority=priority,
|
|
102
|
+
status=status,
|
|
103
|
+
assignee=assignee,
|
|
104
|
+
milestone=milestone,
|
|
105
|
+
content=description,
|
|
106
|
+
estimate=estimate,
|
|
107
|
+
reason=reason,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
updater.execute(
|
|
111
|
+
entity_id=params.issue_id,
|
|
112
|
+
title=params.title,
|
|
113
|
+
priority=params.priority,
|
|
114
|
+
status=params.status,
|
|
115
|
+
assignee=params.assignee,
|
|
116
|
+
milestone=params.milestone,
|
|
117
|
+
content=params.content,
|
|
118
|
+
estimate=params.estimate,
|
|
119
|
+
reason=params.reason,
|
|
120
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""View issue command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import (
|
|
6
|
+
ensure_entity_exists,
|
|
7
|
+
require_initialized,
|
|
8
|
+
)
|
|
9
|
+
from roadmap.adapters.cli.mappers import IssueMapper
|
|
10
|
+
from roadmap.adapters.cli.presentation.issue_presenter import IssuePresenter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command("view")
|
|
14
|
+
@click.argument("issue_id")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
@require_initialized
|
|
17
|
+
def view_issue(ctx: click.Context, issue_id: str):
|
|
18
|
+
"""Display detailed information about a specific issue.
|
|
19
|
+
|
|
20
|
+
Shows complete issue details including metadata, timeline, description,
|
|
21
|
+
and acceptance criteria in a formatted view.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
roadmap issue view abc123def
|
|
25
|
+
"""
|
|
26
|
+
core = ctx.obj["core"]
|
|
27
|
+
|
|
28
|
+
# Retrieve the domain issue from the repository
|
|
29
|
+
issue = ensure_entity_exists(core, "issue", issue_id)
|
|
30
|
+
|
|
31
|
+
# Convert domain issue to DTO for presentation
|
|
32
|
+
issue_dto = IssueMapper.domain_to_dto(issue)
|
|
33
|
+
|
|
34
|
+
# Use presenter to render the issue
|
|
35
|
+
presenter = IssuePresenter()
|
|
36
|
+
presenter.render(issue_dto)
|