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,128 @@
|
|
|
1
|
+
"""Progress tracking for sync operations."""
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from rich.progress import (
|
|
6
|
+
BarColumn,
|
|
7
|
+
Progress,
|
|
8
|
+
SpinnerColumn,
|
|
9
|
+
TaskProgressColumn,
|
|
10
|
+
TextColumn,
|
|
11
|
+
TimeElapsedColumn,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from roadmap.common.console import get_console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SyncProgressTracker:
|
|
18
|
+
"""Tracks and displays progress during sync operations."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, console=None):
|
|
21
|
+
"""Initialize progress tracker.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
console: Rich console instance (creates new one if None)
|
|
25
|
+
"""
|
|
26
|
+
self.console = console or get_console()
|
|
27
|
+
self.progress = None
|
|
28
|
+
self.current_task = None
|
|
29
|
+
|
|
30
|
+
@contextmanager
|
|
31
|
+
def track_sync(self, total_issues: int):
|
|
32
|
+
"""Context manager for tracking overall sync progress.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
total_issues: Total number of issues to sync
|
|
36
|
+
|
|
37
|
+
Yields:
|
|
38
|
+
Progress tracker instance
|
|
39
|
+
"""
|
|
40
|
+
self.progress = Progress(
|
|
41
|
+
SpinnerColumn(),
|
|
42
|
+
TextColumn("[bold blue]{task.description}"),
|
|
43
|
+
BarColumn(),
|
|
44
|
+
TaskProgressColumn(),
|
|
45
|
+
TextColumn("•"),
|
|
46
|
+
TimeElapsedColumn(),
|
|
47
|
+
console=self.console,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
with self.progress:
|
|
51
|
+
self.current_task = self.progress.add_task(
|
|
52
|
+
"Syncing issues...", total=total_issues
|
|
53
|
+
)
|
|
54
|
+
yield self
|
|
55
|
+
|
|
56
|
+
def update(self, advance: int = 1, description: str | None = None):
|
|
57
|
+
"""Update progress.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
advance: Number of steps to advance
|
|
61
|
+
description: Optional new description
|
|
62
|
+
"""
|
|
63
|
+
if self.progress and self.current_task is not None:
|
|
64
|
+
if description:
|
|
65
|
+
self.progress.update(self.current_task, description=description)
|
|
66
|
+
self.progress.advance(self.current_task, advance)
|
|
67
|
+
|
|
68
|
+
def set_description(self, description: str):
|
|
69
|
+
"""Set current task description.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
description: New description text
|
|
73
|
+
"""
|
|
74
|
+
if self.progress and self.current_task is not None:
|
|
75
|
+
self.progress.update(self.current_task, description=description)
|
|
76
|
+
|
|
77
|
+
@contextmanager
|
|
78
|
+
def track_phase(self, phase_name: str, total: int | None = None):
|
|
79
|
+
"""Track a specific phase of sync operation.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
phase_name: Name of the phase (e.g., "Fetching", "Pushing")
|
|
83
|
+
total: Total items in this phase
|
|
84
|
+
|
|
85
|
+
Yields:
|
|
86
|
+
Task ID for this phase
|
|
87
|
+
"""
|
|
88
|
+
if not self.progress:
|
|
89
|
+
yield None
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
task_id = self.progress.add_task(
|
|
93
|
+
f"[cyan]{phase_name}...",
|
|
94
|
+
total=total if total is not None else None,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
yield task_id
|
|
99
|
+
finally:
|
|
100
|
+
self.progress.update(task_id, completed=True)
|
|
101
|
+
|
|
102
|
+
def update_phase(self, task_id, advance: int = 1, description: str | None = None):
|
|
103
|
+
"""Update a specific phase task.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
task_id: Task ID returned from track_phase
|
|
107
|
+
advance: Number of steps to advance
|
|
108
|
+
description: Optional new description
|
|
109
|
+
"""
|
|
110
|
+
if self.progress and task_id is not None:
|
|
111
|
+
if description:
|
|
112
|
+
self.progress.update(task_id, description=description)
|
|
113
|
+
if advance:
|
|
114
|
+
self.progress.advance(task_id, advance)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_spinner_progress() -> Progress:
|
|
118
|
+
"""Create a simple spinner progress for indeterminate operations.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Progress instance with spinner
|
|
122
|
+
"""
|
|
123
|
+
return Progress(
|
|
124
|
+
SpinnerColumn(),
|
|
125
|
+
TextColumn("[bold blue]{task.description}"),
|
|
126
|
+
TimeElapsedColumn(),
|
|
127
|
+
console=get_console(),
|
|
128
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""CLI command for displaying and querying sync metrics.
|
|
2
|
+
|
|
3
|
+
This module provides commands to view sync operation metrics,
|
|
4
|
+
historical data, and aggregate statistics.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
11
|
+
from roadmap.adapters.persistence.sync_metrics_repository import (
|
|
12
|
+
SyncMetricsRepository,
|
|
13
|
+
)
|
|
14
|
+
from roadmap.common.console import get_console
|
|
15
|
+
from roadmap.presentation.formatters.sync_metrics_formatter import (
|
|
16
|
+
create_metrics_history_table,
|
|
17
|
+
create_metrics_summary_table,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command(name="metrics")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--backend",
|
|
24
|
+
type=str,
|
|
25
|
+
default=None,
|
|
26
|
+
help="Filter metrics by backend type (e.g., github, vanilla_git)",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--days",
|
|
30
|
+
type=int,
|
|
31
|
+
default=7,
|
|
32
|
+
help="Show metrics from the last N days (default: 7)",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--latest",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="Show only the latest sync metrics",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--stats",
|
|
41
|
+
is_flag=True,
|
|
42
|
+
help="Show aggregate statistics for the period",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"--verbose",
|
|
46
|
+
"-v",
|
|
47
|
+
is_flag=True,
|
|
48
|
+
help="Show detailed metrics including performance data",
|
|
49
|
+
)
|
|
50
|
+
@click.pass_context
|
|
51
|
+
@require_initialized
|
|
52
|
+
def sync_metrics(
|
|
53
|
+
ctx: click.Context,
|
|
54
|
+
backend: str | None,
|
|
55
|
+
days: int,
|
|
56
|
+
latest: bool,
|
|
57
|
+
stats: bool,
|
|
58
|
+
verbose: bool,
|
|
59
|
+
) -> None:
|
|
60
|
+
r"""Display sync operation metrics and history.
|
|
61
|
+
|
|
62
|
+
View historical sync metrics, performance data, and statistics
|
|
63
|
+
to monitor sync health and performance over time.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
\b
|
|
67
|
+
# Show metrics from last 7 days
|
|
68
|
+
roadmap sync metrics
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
# Show metrics from last 30 days for GitHub
|
|
72
|
+
roadmap sync metrics --backend github --days 30
|
|
73
|
+
|
|
74
|
+
\b
|
|
75
|
+
# Show only the latest sync
|
|
76
|
+
roadmap sync metrics --latest
|
|
77
|
+
|
|
78
|
+
\b
|
|
79
|
+
# Show aggregate statistics
|
|
80
|
+
roadmap sync metrics --stats
|
|
81
|
+
"""
|
|
82
|
+
console_inst = get_console()
|
|
83
|
+
core = ctx.obj["core"]
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
# Initialize repository with core's database manager
|
|
87
|
+
metrics_repo = SyncMetricsRepository(core.db_manager)
|
|
88
|
+
|
|
89
|
+
# Get metrics
|
|
90
|
+
if latest:
|
|
91
|
+
# Show only the latest sync
|
|
92
|
+
metrics = metrics_repo.get_latest(backend_type=backend)
|
|
93
|
+
if not metrics:
|
|
94
|
+
console_inst.print("[yellow]No sync metrics found[/yellow]")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
console_inst.print()
|
|
98
|
+
console_inst.print(
|
|
99
|
+
Panel(
|
|
100
|
+
create_metrics_summary_table(metrics, verbose),
|
|
101
|
+
title="[bold cyan]Latest Sync Metrics[/bold cyan]",
|
|
102
|
+
border_style="cyan",
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
console_inst.print()
|
|
106
|
+
|
|
107
|
+
elif stats:
|
|
108
|
+
# Show aggregate statistics
|
|
109
|
+
statistics = metrics_repo.get_statistics(backend_type=backend, days=days)
|
|
110
|
+
|
|
111
|
+
if statistics and statistics["total_syncs"] > 0:
|
|
112
|
+
from rich.table import Table
|
|
113
|
+
|
|
114
|
+
stats_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
115
|
+
stats_table.add_column(style="bold")
|
|
116
|
+
stats_table.add_column(justify="right", style="cyan")
|
|
117
|
+
|
|
118
|
+
stats_table.add_row(
|
|
119
|
+
"Total Syncs",
|
|
120
|
+
str(statistics["total_syncs"]),
|
|
121
|
+
)
|
|
122
|
+
stats_table.add_row(
|
|
123
|
+
"Average Duration",
|
|
124
|
+
f"{statistics['avg_duration_seconds']:.1f}s",
|
|
125
|
+
)
|
|
126
|
+
stats_table.add_row(
|
|
127
|
+
"Total Duplicates",
|
|
128
|
+
str(statistics["total_duplicates_detected"]),
|
|
129
|
+
)
|
|
130
|
+
stats_table.add_row(
|
|
131
|
+
"Total Conflicts",
|
|
132
|
+
str(statistics["total_conflicts_detected"]),
|
|
133
|
+
)
|
|
134
|
+
stats_table.add_row(
|
|
135
|
+
"Total Errors",
|
|
136
|
+
str(statistics["total_errors"]),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
console_inst.print()
|
|
140
|
+
console_inst.print(
|
|
141
|
+
Panel(
|
|
142
|
+
stats_table,
|
|
143
|
+
title=f"[bold cyan]Statistics ({days} days)[/bold cyan]",
|
|
144
|
+
border_style="cyan",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
console_inst.print()
|
|
148
|
+
else:
|
|
149
|
+
console_inst.print(
|
|
150
|
+
"[yellow]No sync metrics found for the specified period[/yellow]"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
# Show history
|
|
155
|
+
metrics_list = metrics_repo.list_by_date(backend_type=backend, days=days)
|
|
156
|
+
|
|
157
|
+
if not metrics_list:
|
|
158
|
+
console_inst.print(
|
|
159
|
+
f"[yellow]No sync metrics found in the last {days} days[/yellow]"
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
console_inst.print()
|
|
164
|
+
console_inst.print(
|
|
165
|
+
Panel(
|
|
166
|
+
create_metrics_history_table(metrics_list, limit=10),
|
|
167
|
+
title=f"[bold cyan]Sync History (last {days} days)[/bold cyan]",
|
|
168
|
+
border_style="cyan",
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
console_inst.print()
|
|
172
|
+
|
|
173
|
+
# Show brief summary
|
|
174
|
+
latest_metrics = metrics_list[0] if metrics_list else None
|
|
175
|
+
if latest_metrics and verbose:
|
|
176
|
+
console_inst.print(
|
|
177
|
+
Panel(
|
|
178
|
+
create_metrics_summary_table(latest_metrics, verbose),
|
|
179
|
+
title="[bold cyan]Latest Sync Details[/bold cyan]",
|
|
180
|
+
border_style="cyan",
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
console_inst.print()
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
console_inst.print(
|
|
187
|
+
f"[bold red]❌ Error retrieving metrics: {str(e)}[/bold red]"
|
|
188
|
+
)
|
|
189
|
+
if verbose:
|
|
190
|
+
raise
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Presentation helpers for the `roadmap sync` CLI command.
|
|
2
|
+
|
|
3
|
+
This module contains functions responsible for printing analysis results
|
|
4
|
+
and prompting the user for confirmation. It centralizes UI logic so that
|
|
5
|
+
the sync command can remain focused on orchestration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from roadmap.common.console import get_console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def present_analysis(analysis_report: Any, verbose: bool = False) -> None:
|
|
14
|
+
"""Present a summary (and optional details) of the sync analysis.
|
|
15
|
+
|
|
16
|
+
The function is defensive about which attributes exist on
|
|
17
|
+
`analysis_report` to avoid tight coupling with concrete report classes.
|
|
18
|
+
"""
|
|
19
|
+
console = get_console()
|
|
20
|
+
|
|
21
|
+
console.print("\n[bold cyan]📈 Sync Analysis[/bold cyan]")
|
|
22
|
+
console.print(
|
|
23
|
+
f" ✓ Up-to-date: {getattr(analysis_report, 'issues_up_to_date', 0)}"
|
|
24
|
+
)
|
|
25
|
+
console.print(
|
|
26
|
+
f" 📤 Needs Push: {getattr(analysis_report, 'issues_needs_push', 0)}"
|
|
27
|
+
)
|
|
28
|
+
console.print(
|
|
29
|
+
f" 📥 Needs Pull: {getattr(analysis_report, 'issues_needs_pull', 0)}"
|
|
30
|
+
)
|
|
31
|
+
console.print(
|
|
32
|
+
f" ✓ Potential Conflicts: {getattr(analysis_report, 'conflicts_detected', 0)}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if verbose:
|
|
36
|
+
# If the report exposes a list of changes, show a concise table-like view
|
|
37
|
+
changes = getattr(analysis_report, "changes", None)
|
|
38
|
+
if changes:
|
|
39
|
+
console.print("\n[bold]Detailed changes:[/bold]")
|
|
40
|
+
for change in changes:
|
|
41
|
+
try:
|
|
42
|
+
line = f" • {change.issue_id}: "
|
|
43
|
+
if getattr(change, "has_conflict", False):
|
|
44
|
+
line += "(CONFLICT) "
|
|
45
|
+
title = getattr(change, "title", None)
|
|
46
|
+
if title:
|
|
47
|
+
line += f"{title}"
|
|
48
|
+
console.print(line)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
from roadmap.common.logging import get_logger
|
|
51
|
+
|
|
52
|
+
logger = get_logger(__name__)
|
|
53
|
+
logger.debug(
|
|
54
|
+
"sync_change_presentation_failed",
|
|
55
|
+
error=str(e),
|
|
56
|
+
action="present_change",
|
|
57
|
+
)
|
|
58
|
+
continue
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""Sync validation commands for checking remote link health.
|
|
2
|
+
|
|
3
|
+
This module provides commands to validate and repair remote links in the database,
|
|
4
|
+
ensuring that all YAML remote_ids are properly tracked and accessible.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
12
|
+
from roadmap.common.console import get_console
|
|
13
|
+
from roadmap.common.logging import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command(name="validate-links")
|
|
19
|
+
@click.option(
|
|
20
|
+
"--auto-fix",
|
|
21
|
+
is_flag=True,
|
|
22
|
+
help="Automatically repair broken or missing links",
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--prune-extra",
|
|
26
|
+
is_flag=True,
|
|
27
|
+
help="Remove database links that are not present in YAML",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--dedupe",
|
|
31
|
+
is_flag=True,
|
|
32
|
+
help="Remove duplicate remote_id links (keep YAML-backed link if present)",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--dry-run",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="Preview changes without applying them",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--verbose",
|
|
41
|
+
is_flag=True,
|
|
42
|
+
help="Show detailed validation information for each issue",
|
|
43
|
+
)
|
|
44
|
+
@click.pass_context
|
|
45
|
+
@require_initialized
|
|
46
|
+
def validate_links(
|
|
47
|
+
ctx: click.Context,
|
|
48
|
+
auto_fix: bool,
|
|
49
|
+
prune_extra: bool,
|
|
50
|
+
dedupe: bool,
|
|
51
|
+
dry_run: bool,
|
|
52
|
+
verbose: bool,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Validate remote links in the database against YAML files.
|
|
55
|
+
|
|
56
|
+
This command scans all YAML files to verify that remote_ids are:
|
|
57
|
+
1. Properly loaded in the database
|
|
58
|
+
2. Match between YAML source and database cache
|
|
59
|
+
3. Valid and accessible for sync operations
|
|
60
|
+
|
|
61
|
+
**Examples:**
|
|
62
|
+
# Check link health
|
|
63
|
+
roadmap sync validate-links
|
|
64
|
+
|
|
65
|
+
# Show detailed info about each issue
|
|
66
|
+
roadmap sync validate-links --verbose
|
|
67
|
+
|
|
68
|
+
# Preview what would be fixed
|
|
69
|
+
roadmap sync validate-links --auto-fix --dry-run
|
|
70
|
+
|
|
71
|
+
# Repair broken links
|
|
72
|
+
roadmap sync validate-links --auto-fix
|
|
73
|
+
"""
|
|
74
|
+
core = ctx.obj["core"]
|
|
75
|
+
console = get_console()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
issues_dir = core.issues_dir
|
|
79
|
+
if not issues_dir.exists():
|
|
80
|
+
console.print(
|
|
81
|
+
f"❌ Issues directory not found: {issues_dir}", style="bold red"
|
|
82
|
+
)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
validation_data = core.validation.collect_remote_link_validation_data(
|
|
86
|
+
issues_dir
|
|
87
|
+
)
|
|
88
|
+
issue_files = validation_data["issue_files"]
|
|
89
|
+
if not issue_files:
|
|
90
|
+
console.print("ℹ️ No issue files found", style="yellow")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
validation_report: dict = {
|
|
94
|
+
"total_files": len(issue_files),
|
|
95
|
+
"files_with_remote_ids": 0,
|
|
96
|
+
"database_links": 0,
|
|
97
|
+
"discrepancies": [],
|
|
98
|
+
"missing_in_db": [],
|
|
99
|
+
"extra_in_db": [],
|
|
100
|
+
"duplicate_remote_ids": {},
|
|
101
|
+
}
|
|
102
|
+
yaml_remote_ids = validation_data["yaml_remote_ids"]
|
|
103
|
+
unparseable_files = validation_data["unparseable_files"]
|
|
104
|
+
db_links = validation_data["db_links"]
|
|
105
|
+
validation_report.update(
|
|
106
|
+
core.validation.build_remote_link_report(yaml_remote_ids, db_links)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
_display_validation_summary(validation_report, unparseable_files, console)
|
|
110
|
+
|
|
111
|
+
if auto_fix:
|
|
112
|
+
_apply_auto_fix(
|
|
113
|
+
core,
|
|
114
|
+
yaml_remote_ids,
|
|
115
|
+
validation_report,
|
|
116
|
+
console,
|
|
117
|
+
dry_run,
|
|
118
|
+
verbose,
|
|
119
|
+
prune_extra,
|
|
120
|
+
dedupe,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if validation_report["missing_in_db"] and (not auto_fix or dry_run):
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
console.print(
|
|
127
|
+
"\n✅ All remote links are valid and synchronized", style="bold green"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(
|
|
132
|
+
"validation_failed",
|
|
133
|
+
error=str(e),
|
|
134
|
+
error_type=type(e).__name__,
|
|
135
|
+
severity="system_error",
|
|
136
|
+
)
|
|
137
|
+
console.print(f"❌ Validation failed: {str(e)}", style="bold red")
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _display_validation_summary(
|
|
142
|
+
validation_report: dict, unparseable_files: list, console
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Print the remote-link validation summary to the console."""
|
|
145
|
+
console.print("\n[bold]Remote Links Validation Report[/bold]", style="bold cyan")
|
|
146
|
+
console.print(f"Total issue files: {validation_report['total_files']}")
|
|
147
|
+
console.print(
|
|
148
|
+
f"Files with remote_ids: {validation_report['files_with_remote_ids']}"
|
|
149
|
+
)
|
|
150
|
+
console.print(f"Database links: {validation_report['database_links']}")
|
|
151
|
+
|
|
152
|
+
missing = validation_report["missing_in_db"]
|
|
153
|
+
if missing:
|
|
154
|
+
console.print(f"\n[bold red]❌ Missing in database: {len(missing)}[/bold red]")
|
|
155
|
+
for issue_uuid in missing[:5]:
|
|
156
|
+
console.print(f" - {issue_uuid}")
|
|
157
|
+
if len(missing) > 5:
|
|
158
|
+
console.print(f" ... and {len(missing) - 5} more")
|
|
159
|
+
|
|
160
|
+
if validation_report["extra_in_db"]:
|
|
161
|
+
console.print(
|
|
162
|
+
f"\n[bold yellow]⚠️ In database but not in YAML: {len(validation_report['extra_in_db'])}[/bold yellow]"
|
|
163
|
+
)
|
|
164
|
+
console.print(" (These may be archived issues)")
|
|
165
|
+
|
|
166
|
+
dup_ids = validation_report["duplicate_remote_ids"]
|
|
167
|
+
if dup_ids:
|
|
168
|
+
console.print(
|
|
169
|
+
f"\n[bold yellow]⚠️ Duplicate remote IDs: {len(dup_ids)}[/bold yellow]"
|
|
170
|
+
)
|
|
171
|
+
for remote_id, issue_uuids in list(dup_ids.items())[:5]:
|
|
172
|
+
console.print(f" - {remote_id}: {', '.join(issue_uuids)}", style="dim")
|
|
173
|
+
if len(dup_ids) > 5:
|
|
174
|
+
console.print(f" ... and {len(dup_ids) - 5} more")
|
|
175
|
+
|
|
176
|
+
if unparseable_files:
|
|
177
|
+
console.print(
|
|
178
|
+
f"\n[bold yellow]⚠️ Could not parse {len(unparseable_files)} files[/bold yellow]"
|
|
179
|
+
)
|
|
180
|
+
for file_path, error in unparseable_files[:3]:
|
|
181
|
+
console.print(f" - {file_path}: {error}")
|
|
182
|
+
if len(unparseable_files) > 3:
|
|
183
|
+
console.print(f" ... and {len(unparseable_files) - 3} more")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _apply_auto_fix(
|
|
187
|
+
core,
|
|
188
|
+
yaml_remote_ids: dict,
|
|
189
|
+
validation_report: dict,
|
|
190
|
+
console,
|
|
191
|
+
dry_run: bool,
|
|
192
|
+
verbose: bool,
|
|
193
|
+
prune_extra: bool,
|
|
194
|
+
dedupe: bool,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Apply automatic fixes for missing remote links.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
core: RoadmapCore instance with validation coordinator
|
|
200
|
+
yaml_remote_ids: Dict of issue_uuid -> {backend -> remote_id}
|
|
201
|
+
validation_report: Validation report dict to update
|
|
202
|
+
console: Console instance for output
|
|
203
|
+
dry_run: If True, don't actually apply changes
|
|
204
|
+
verbose: If True, show detailed information
|
|
205
|
+
prune_extra: If True, remove links missing from YAML
|
|
206
|
+
dedupe: If True, remove duplicate remote IDs
|
|
207
|
+
"""
|
|
208
|
+
missing = validation_report["missing_in_db"]
|
|
209
|
+
if not _has_auto_fix_work(missing, prune_extra, dedupe):
|
|
210
|
+
console.print("\n✅ No links need fixing", style="bold green")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
_display_auto_fix_plan(
|
|
214
|
+
console,
|
|
215
|
+
missing,
|
|
216
|
+
yaml_remote_ids,
|
|
217
|
+
validation_report,
|
|
218
|
+
dry_run,
|
|
219
|
+
verbose,
|
|
220
|
+
prune_extra,
|
|
221
|
+
dedupe,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
results = core.validation.apply_remote_link_fixes(
|
|
226
|
+
yaml_remote_ids,
|
|
227
|
+
validation_report,
|
|
228
|
+
backend_name="github",
|
|
229
|
+
prune_extra=prune_extra,
|
|
230
|
+
dedupe=dedupe,
|
|
231
|
+
dry_run=dry_run,
|
|
232
|
+
)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.warning("auto_fix_failed", error=str(e), severity="operational")
|
|
235
|
+
console.print(f" ⚠️ Auto-fix failed: {str(e)}", style="yellow")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
_display_auto_fix_results(console, results, dry_run)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _has_auto_fix_work(missing: list[str], prune_extra: bool, dedupe: bool) -> bool:
|
|
242
|
+
return bool(missing or prune_extra or dedupe)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _display_auto_fix_plan(
|
|
246
|
+
console,
|
|
247
|
+
missing: list[str],
|
|
248
|
+
yaml_remote_ids: dict,
|
|
249
|
+
validation_report: dict,
|
|
250
|
+
dry_run: bool,
|
|
251
|
+
verbose: bool,
|
|
252
|
+
prune_extra: bool,
|
|
253
|
+
dedupe: bool,
|
|
254
|
+
) -> None:
|
|
255
|
+
if missing:
|
|
256
|
+
console.print(
|
|
257
|
+
f"\n[bold cyan]🔧 Auto-fixing {len(missing)} missing links...[/bold cyan]"
|
|
258
|
+
)
|
|
259
|
+
if dry_run:
|
|
260
|
+
console.print(
|
|
261
|
+
" (Dry-run mode - no changes will be made)", style="dim yellow"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if verbose and yaml_remote_ids:
|
|
265
|
+
for issue_uuid, remote_ids in yaml_remote_ids.items():
|
|
266
|
+
console.print(
|
|
267
|
+
f" 📄 {issue_uuid}: {', '.join(remote_ids.keys())}", style="cyan"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if prune_extra and validation_report.get("extra_in_db"):
|
|
271
|
+
console.print(
|
|
272
|
+
f"\n[bold cyan]🧹 Removing {len(validation_report['extra_in_db'])} extra links...[/bold cyan]"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if dedupe and validation_report.get("duplicate_remote_ids"):
|
|
276
|
+
console.print(
|
|
277
|
+
f"\n[bold cyan]🧹 Removing {len(validation_report['duplicate_remote_ids'])} duplicate remote IDs...[/bold cyan]"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _display_auto_fix_results(console, results: dict, dry_run: bool) -> None:
|
|
282
|
+
if results.get("fixed_count"):
|
|
283
|
+
console.print(
|
|
284
|
+
f"\n[bold green]✅ Fixed {results['fixed_count']} missing remote links[/bold green]"
|
|
285
|
+
)
|
|
286
|
+
if results.get("removed_count"):
|
|
287
|
+
console.print(
|
|
288
|
+
f"[bold green]✅ Removed {results['removed_count']} extra remote links[/bold green]"
|
|
289
|
+
)
|
|
290
|
+
if results.get("deduped_count"):
|
|
291
|
+
console.print(
|
|
292
|
+
f"[bold green]✅ Removed {results['deduped_count']} duplicate remote links[/bold green]"
|
|
293
|
+
)
|
|
294
|
+
if dry_run:
|
|
295
|
+
console.print("Re-run without --dry-run to apply changes", style="dim yellow")
|