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,383 @@
|
|
|
1
|
+
"""Error types for sync operations.
|
|
2
|
+
|
|
3
|
+
This module defines error types used with the Result pattern for sync operations,
|
|
4
|
+
enabling explicit error handling without exceptions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SyncErrorType(StrEnum):
|
|
13
|
+
"""Types of errors that can occur during sync operations."""
|
|
14
|
+
|
|
15
|
+
# Authentication errors
|
|
16
|
+
AUTHENTICATION_FAILED = "authentication_failed"
|
|
17
|
+
TOKEN_EXPIRED = "token_expired"
|
|
18
|
+
PERMISSION_DENIED = "permission_denied"
|
|
19
|
+
|
|
20
|
+
# Network errors
|
|
21
|
+
NETWORK_ERROR = "network_error"
|
|
22
|
+
TIMEOUT = "timeout"
|
|
23
|
+
SERVICE_UNAVAILABLE = "service_unavailable"
|
|
24
|
+
API_RATE_LIMIT = "api_rate_limit"
|
|
25
|
+
|
|
26
|
+
# Data errors
|
|
27
|
+
INVALID_DATA = "invalid_data"
|
|
28
|
+
SCHEMA_MISMATCH = "schema_mismatch"
|
|
29
|
+
DUPLICATE_ENTITY = "duplicate_entity"
|
|
30
|
+
VALIDATION_ERROR = "validation_error"
|
|
31
|
+
|
|
32
|
+
# Resource errors
|
|
33
|
+
RESOURCE_NOT_FOUND = "resource_not_found"
|
|
34
|
+
RESOURCE_DELETED = "resource_deleted"
|
|
35
|
+
MILESTONE_NOT_FOUND = "milestone_not_found"
|
|
36
|
+
PROJECT_NOT_FOUND = "project_not_found"
|
|
37
|
+
|
|
38
|
+
# Conflict errors
|
|
39
|
+
CONFLICT = "conflict"
|
|
40
|
+
MERGE_CONFLICT = "merge_conflict"
|
|
41
|
+
|
|
42
|
+
# System errors
|
|
43
|
+
DATABASE_ERROR = "database_error"
|
|
44
|
+
FILE_SYSTEM_ERROR = "file_system_error"
|
|
45
|
+
CONFIGURATION_ERROR = "configuration_error"
|
|
46
|
+
|
|
47
|
+
# Circuit breaker errors
|
|
48
|
+
CIRCUIT_BREAKER_OPEN = "circuit_breaker_open"
|
|
49
|
+
RETRY_EXHAUSTED = "retry_exhausted"
|
|
50
|
+
|
|
51
|
+
# Unknown
|
|
52
|
+
UNKNOWN_ERROR = "unknown_error"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class SyncError:
|
|
57
|
+
"""Error information for sync operations.
|
|
58
|
+
|
|
59
|
+
Provides structured error information for use with Result pattern,
|
|
60
|
+
including error type, message, and recovery suggestions.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
error_type: The category of error
|
|
64
|
+
message: Human-readable error message
|
|
65
|
+
entity_type: Type of entity affected (Issue, Milestone, Project, etc.)
|
|
66
|
+
entity_id: Identifier of affected entity (optional)
|
|
67
|
+
is_recoverable: Whether the error can be recovered from
|
|
68
|
+
suggested_fix: User-actionable recovery suggestion (optional)
|
|
69
|
+
metadata: Additional error context (optional)
|
|
70
|
+
cause: Original exception if wrapped (optional)
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
error_type: SyncErrorType
|
|
74
|
+
message: str
|
|
75
|
+
entity_type: str = "Unknown"
|
|
76
|
+
entity_id: str | None = None
|
|
77
|
+
is_recoverable: bool = True
|
|
78
|
+
suggested_fix: str | None = None
|
|
79
|
+
metadata: dict[str, Any] | None = None
|
|
80
|
+
cause: Exception | None = None
|
|
81
|
+
|
|
82
|
+
def __str__(self) -> str:
|
|
83
|
+
"""Get string representation of error.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Formatted error message
|
|
87
|
+
"""
|
|
88
|
+
parts = [f"{self.error_type.value}: {self.message}"]
|
|
89
|
+
if self.entity_id:
|
|
90
|
+
parts.append(f"(entity: {self.entity_type} #{self.entity_id})")
|
|
91
|
+
if self.suggested_fix:
|
|
92
|
+
parts.append(f"Fix: {self.suggested_fix}")
|
|
93
|
+
return " ".join(parts)
|
|
94
|
+
|
|
95
|
+
def with_entity(
|
|
96
|
+
self, entity_type: str, entity_id: str | None = None
|
|
97
|
+
) -> "SyncError":
|
|
98
|
+
"""Create a new error with entity information.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
entity_type: Type of entity (Issue, Milestone, etc.)
|
|
102
|
+
entity_id: Entity identifier
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
New SyncError with entity information
|
|
106
|
+
"""
|
|
107
|
+
return SyncError(
|
|
108
|
+
error_type=self.error_type,
|
|
109
|
+
message=self.message,
|
|
110
|
+
entity_type=entity_type,
|
|
111
|
+
entity_id=entity_id,
|
|
112
|
+
is_recoverable=self.is_recoverable,
|
|
113
|
+
suggested_fix=self.suggested_fix,
|
|
114
|
+
metadata=self.metadata,
|
|
115
|
+
cause=self.cause,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def with_suggestion(self, suggested_fix: str) -> "SyncError":
|
|
119
|
+
"""Create a new error with a recovery suggestion.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
suggested_fix: User-actionable suggestion
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
New SyncError with suggestion
|
|
126
|
+
"""
|
|
127
|
+
return SyncError(
|
|
128
|
+
error_type=self.error_type,
|
|
129
|
+
message=self.message,
|
|
130
|
+
entity_type=self.entity_type,
|
|
131
|
+
entity_id=self.entity_id,
|
|
132
|
+
is_recoverable=self.is_recoverable,
|
|
133
|
+
suggested_fix=suggested_fix,
|
|
134
|
+
metadata=self.metadata,
|
|
135
|
+
cause=self.cause,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def from_exception(
|
|
140
|
+
cls,
|
|
141
|
+
exception: Exception,
|
|
142
|
+
error_type: SyncErrorType = SyncErrorType.UNKNOWN_ERROR,
|
|
143
|
+
entity_type: str = "Unknown",
|
|
144
|
+
entity_id: str | None = None,
|
|
145
|
+
) -> "SyncError":
|
|
146
|
+
"""Create SyncError from an exception.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
exception: The exception to wrap
|
|
150
|
+
error_type: Category of error (default: UNKNOWN_ERROR)
|
|
151
|
+
entity_type: Type of affected entity
|
|
152
|
+
entity_id: Identifier of affected entity
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
SyncError wrapping the exception
|
|
156
|
+
"""
|
|
157
|
+
# Infer error type from exception if possible
|
|
158
|
+
inferred_type = cls._infer_error_type(exception)
|
|
159
|
+
if inferred_type:
|
|
160
|
+
error_type = inferred_type
|
|
161
|
+
|
|
162
|
+
return cls(
|
|
163
|
+
error_type=error_type,
|
|
164
|
+
message=str(exception),
|
|
165
|
+
entity_type=entity_type,
|
|
166
|
+
entity_id=entity_id,
|
|
167
|
+
is_recoverable=cls._is_recoverable(error_type),
|
|
168
|
+
suggested_fix=cls._get_suggested_fix(error_type),
|
|
169
|
+
cause=exception,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _infer_error_type(exception: Exception) -> SyncErrorType | None:
|
|
174
|
+
"""Infer error type from exception class and message.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
exception: The exception to analyze
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Inferred error type or None
|
|
181
|
+
"""
|
|
182
|
+
exc_name = type(exception).__name__
|
|
183
|
+
exc_msg = str(exception).lower()
|
|
184
|
+
|
|
185
|
+
# Check exception class names first
|
|
186
|
+
class_mappings = {
|
|
187
|
+
("TimeoutError", "ConnectTimeout"): SyncErrorType.TIMEOUT,
|
|
188
|
+
("ConnectionError", "ConnectError"): SyncErrorType.NETWORK_ERROR,
|
|
189
|
+
("ValidationError",): SyncErrorType.VALIDATION_ERROR,
|
|
190
|
+
(
|
|
191
|
+
"IntegrityError",
|
|
192
|
+
"DatabaseError",
|
|
193
|
+
"OperationalError",
|
|
194
|
+
): SyncErrorType.DATABASE_ERROR,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for classes, error_type in class_mappings.items():
|
|
198
|
+
if exc_name in classes:
|
|
199
|
+
return error_type
|
|
200
|
+
|
|
201
|
+
# Check message patterns - order matters for specificity
|
|
202
|
+
message_patterns = [
|
|
203
|
+
# More specific patterns first
|
|
204
|
+
(["token", "expired"], SyncErrorType.TOKEN_EXPIRED),
|
|
205
|
+
(["milestone", "not found"], SyncErrorType.MILESTONE_NOT_FOUND),
|
|
206
|
+
(["project", "not found"], SyncErrorType.PROJECT_NOT_FOUND),
|
|
207
|
+
# Less specific patterns
|
|
208
|
+
(["timeout"], SyncErrorType.TIMEOUT),
|
|
209
|
+
(["connection"], SyncErrorType.NETWORK_ERROR),
|
|
210
|
+
(["rate limit", "429"], SyncErrorType.API_RATE_LIMIT),
|
|
211
|
+
(["auth", "401"], SyncErrorType.AUTHENTICATION_FAILED),
|
|
212
|
+
(["permission", "403"], SyncErrorType.PERMISSION_DENIED),
|
|
213
|
+
(["not found", "404"], SyncErrorType.RESOURCE_NOT_FOUND),
|
|
214
|
+
(["validation"], SyncErrorType.VALIDATION_ERROR),
|
|
215
|
+
(["duplicate", "unique"], SyncErrorType.DUPLICATE_ENTITY),
|
|
216
|
+
(["schema"], SyncErrorType.SCHEMA_MISMATCH),
|
|
217
|
+
(["conflict"], SyncErrorType.CONFLICT),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
for patterns, error_type in message_patterns:
|
|
221
|
+
if all(pattern in exc_msg for pattern in patterns):
|
|
222
|
+
return error_type
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def _is_recoverable(error_type: SyncErrorType) -> bool:
|
|
228
|
+
"""Determine if an error type is recoverable.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
error_type: The error type
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
True if the error can be recovered from
|
|
235
|
+
"""
|
|
236
|
+
non_recoverable = {
|
|
237
|
+
SyncErrorType.PERMISSION_DENIED,
|
|
238
|
+
SyncErrorType.SCHEMA_MISMATCH,
|
|
239
|
+
SyncErrorType.CONFIGURATION_ERROR,
|
|
240
|
+
}
|
|
241
|
+
return error_type not in non_recoverable
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _get_suggested_fix(error_type: SyncErrorType) -> str | None:
|
|
245
|
+
"""Get suggested fix for an error type.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
error_type: The error type
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Suggested fix or None
|
|
252
|
+
"""
|
|
253
|
+
fixes = {
|
|
254
|
+
SyncErrorType.AUTHENTICATION_FAILED: "Check your credentials and token",
|
|
255
|
+
SyncErrorType.TOKEN_EXPIRED: "Refresh or regenerate your access token",
|
|
256
|
+
SyncErrorType.PERMISSION_DENIED: "Verify you have required permissions",
|
|
257
|
+
SyncErrorType.API_RATE_LIMIT: "Wait for rate limit to reset or reduce request frequency",
|
|
258
|
+
SyncErrorType.TIMEOUT: "Check network connection and try again",
|
|
259
|
+
SyncErrorType.NETWORK_ERROR: "Check network connection and try again",
|
|
260
|
+
SyncErrorType.RESOURCE_NOT_FOUND: "Verify the resource exists and ID is correct",
|
|
261
|
+
SyncErrorType.DUPLICATE_ENTITY: "Use duplicate detection to resolve conflicts",
|
|
262
|
+
SyncErrorType.CIRCUIT_BREAKER_OPEN: "Wait for circuit breaker to reset, then retry",
|
|
263
|
+
SyncErrorType.RETRY_EXHAUSTED: "Check error logs and resolve underlying issue",
|
|
264
|
+
SyncErrorType.CONFIGURATION_ERROR: "Review and fix configuration settings",
|
|
265
|
+
}
|
|
266
|
+
return fixes.get(error_type)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# Common error constructors for convenience
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def authentication_error(message: str = "Authentication failed") -> SyncError:
|
|
273
|
+
"""Create an authentication error.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
message: Error message
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
SyncError with AUTHENTICATION_FAILED type
|
|
280
|
+
"""
|
|
281
|
+
return SyncError(
|
|
282
|
+
error_type=SyncErrorType.AUTHENTICATION_FAILED,
|
|
283
|
+
message=message,
|
|
284
|
+
is_recoverable=True,
|
|
285
|
+
suggested_fix="Check your credentials and token",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def network_error(message: str = "Network error occurred") -> SyncError:
|
|
290
|
+
"""Create a network error.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
message: Error message
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
SyncError with NETWORK_ERROR type
|
|
297
|
+
"""
|
|
298
|
+
return SyncError(
|
|
299
|
+
error_type=SyncErrorType.NETWORK_ERROR,
|
|
300
|
+
message=message,
|
|
301
|
+
is_recoverable=True,
|
|
302
|
+
suggested_fix="Check network connection and try again",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def rate_limit_error(retry_after: int | None = None) -> SyncError:
|
|
307
|
+
"""Create a rate limit error.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
retry_after: Seconds until rate limit resets
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
SyncError with API_RATE_LIMIT type
|
|
314
|
+
"""
|
|
315
|
+
message = "API rate limit exceeded"
|
|
316
|
+
if retry_after:
|
|
317
|
+
message += f" (retry after {retry_after}s)"
|
|
318
|
+
|
|
319
|
+
metadata = {"retry_after": retry_after} if retry_after else None
|
|
320
|
+
|
|
321
|
+
return SyncError(
|
|
322
|
+
error_type=SyncErrorType.API_RATE_LIMIT,
|
|
323
|
+
message=message,
|
|
324
|
+
is_recoverable=True,
|
|
325
|
+
suggested_fix="Wait for rate limit to reset or reduce request frequency",
|
|
326
|
+
metadata=metadata,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def resource_not_found_error(resource_type: str, resource_id: str) -> SyncError:
|
|
331
|
+
"""Create a resource not found error.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
resource_type: Type of resource (Issue, Milestone, etc.)
|
|
335
|
+
resource_id: Resource identifier
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
SyncError with RESOURCE_NOT_FOUND type
|
|
339
|
+
"""
|
|
340
|
+
return SyncError(
|
|
341
|
+
error_type=SyncErrorType.RESOURCE_NOT_FOUND,
|
|
342
|
+
message=f"{resource_type} not found",
|
|
343
|
+
entity_type=resource_type,
|
|
344
|
+
entity_id=resource_id,
|
|
345
|
+
is_recoverable=False,
|
|
346
|
+
suggested_fix="Verify the resource exists and ID is correct",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def conflict_error(
|
|
351
|
+
entity_type: str,
|
|
352
|
+
entity_id: str,
|
|
353
|
+
message: str = "Conflict detected",
|
|
354
|
+
) -> SyncError:
|
|
355
|
+
"""Create a conflict error.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
entity_type: Type of entity
|
|
359
|
+
entity_id: Entity identifier
|
|
360
|
+
message: Conflict description
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
SyncError with CONFLICT type
|
|
364
|
+
"""
|
|
365
|
+
return SyncError(
|
|
366
|
+
error_type=SyncErrorType.CONFLICT,
|
|
367
|
+
message=message,
|
|
368
|
+
entity_type=entity_type,
|
|
369
|
+
entity_id=entity_id,
|
|
370
|
+
is_recoverable=True,
|
|
371
|
+
suggested_fix="Resolve the conflict manually or use --interactive mode",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
__all__ = [
|
|
376
|
+
"SyncError",
|
|
377
|
+
"SyncErrorType",
|
|
378
|
+
"authentication_error",
|
|
379
|
+
"network_error",
|
|
380
|
+
"rate_limit_error",
|
|
381
|
+
"resource_not_found_error",
|
|
382
|
+
"conflict_error",
|
|
383
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Helpers for normalizing remote keys to local UUIDs.
|
|
2
|
+
|
|
3
|
+
This module encapsulates the mapping logic previously embedded in
|
|
4
|
+
`SyncStateComparator._normalize_remote_keys`, allowing focused testing and
|
|
5
|
+
re-use by other components.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import structlog
|
|
14
|
+
|
|
15
|
+
logger = structlog.get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _build_remote_id_mapping(
|
|
19
|
+
local: dict[str, Any],
|
|
20
|
+
backend: Any,
|
|
21
|
+
remote_link_repo: Any,
|
|
22
|
+
logger: Any | None = None,
|
|
23
|
+
) -> tuple[dict[str, str], bool]:
|
|
24
|
+
"""Build reverse mapping from remote ID to local UUID.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
(remote_id_to_local_uuid dict, db_lookup_used bool)
|
|
28
|
+
"""
|
|
29
|
+
remote_id_to_local_uuid: dict[str, str] = {}
|
|
30
|
+
db_lookup_available = False
|
|
31
|
+
backend_name = backend.get_backend_name()
|
|
32
|
+
|
|
33
|
+
# Try database lookup first
|
|
34
|
+
if remote_link_repo:
|
|
35
|
+
db_lookup_available = True
|
|
36
|
+
db_links = remote_link_repo.get_all_links_for_backend(backend_name)
|
|
37
|
+
for issue_uuid, remote_id in db_links.items():
|
|
38
|
+
remote_id_key = str(remote_id)
|
|
39
|
+
remote_id_to_local_uuid[remote_id_key] = issue_uuid
|
|
40
|
+
if logger is not None:
|
|
41
|
+
try:
|
|
42
|
+
logger.debug(
|
|
43
|
+
"loaded_remote_links_from_database",
|
|
44
|
+
backend=backend_name,
|
|
45
|
+
link_count=len(remote_id_to_local_uuid),
|
|
46
|
+
)
|
|
47
|
+
except Exception as logging_error:
|
|
48
|
+
logger.error(
|
|
49
|
+
"logger_failed",
|
|
50
|
+
operation="log_remote_links_loaded",
|
|
51
|
+
error=str(logging_error),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Supplement from YAML if DB is missing entries
|
|
55
|
+
if not db_lookup_available or len(remote_id_to_local_uuid) < len(local):
|
|
56
|
+
for local_uuid, local_issue in local.items():
|
|
57
|
+
remote_ids = getattr(local_issue, "remote_ids", None)
|
|
58
|
+
if remote_ids and backend_name in remote_ids:
|
|
59
|
+
remote_id = remote_ids[backend_name]
|
|
60
|
+
remote_id_key = str(remote_id)
|
|
61
|
+
if remote_id_key not in remote_id_to_local_uuid:
|
|
62
|
+
remote_id_to_local_uuid[remote_id_key] = local_uuid
|
|
63
|
+
if logger is not None:
|
|
64
|
+
try:
|
|
65
|
+
logger.debug(
|
|
66
|
+
"loaded_remote_id_from_yaml",
|
|
67
|
+
remote_id=remote_id_key,
|
|
68
|
+
local_uuid=local_uuid,
|
|
69
|
+
backend=backend_name,
|
|
70
|
+
)
|
|
71
|
+
except Exception as logging_error:
|
|
72
|
+
logger.error(
|
|
73
|
+
"logger_failed",
|
|
74
|
+
operation="log_loaded_remote_id_from_yaml",
|
|
75
|
+
error=str(logging_error),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return remote_id_to_local_uuid, db_lookup_available
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _apply_remote_normalization(
|
|
82
|
+
remote: Mapping[str, Any],
|
|
83
|
+
remote_id_to_local_uuid: dict[str, str],
|
|
84
|
+
backend_name: str,
|
|
85
|
+
logger: Any | None = None,
|
|
86
|
+
) -> tuple[dict[str, Any], int]:
|
|
87
|
+
"""Apply ID mapping to normalize remote issues.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
(normalized_remote dict, count of unmatched)
|
|
91
|
+
"""
|
|
92
|
+
normalized_remote: dict[str, Any] = {}
|
|
93
|
+
unmatched_count = 0
|
|
94
|
+
|
|
95
|
+
# Partition remote issues into matched and unmatched
|
|
96
|
+
for remote_key, remote_issue in remote.items():
|
|
97
|
+
remote_key_str = str(remote_key)
|
|
98
|
+
if remote_key_str in remote_id_to_local_uuid:
|
|
99
|
+
local_uuid = remote_id_to_local_uuid[remote_key_str]
|
|
100
|
+
normalized_remote[local_uuid] = remote_issue
|
|
101
|
+
if logger is not None:
|
|
102
|
+
try:
|
|
103
|
+
logger.debug(
|
|
104
|
+
"normalized_remote_key",
|
|
105
|
+
original_key=remote_key_str,
|
|
106
|
+
normalized_to=local_uuid,
|
|
107
|
+
)
|
|
108
|
+
except Exception as logging_error:
|
|
109
|
+
logger.error(
|
|
110
|
+
"logger_failed",
|
|
111
|
+
operation="log_normalized_remote_key",
|
|
112
|
+
error=str(logging_error),
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
prefixed_key = f"_remote_{remote_key}"
|
|
116
|
+
normalized_remote[prefixed_key] = remote_issue
|
|
117
|
+
unmatched_count += 1
|
|
118
|
+
if logger is not None:
|
|
119
|
+
try:
|
|
120
|
+
logger.debug(
|
|
121
|
+
"new_remote_issue",
|
|
122
|
+
remote_key=str(remote_key),
|
|
123
|
+
prefixed_key=prefixed_key,
|
|
124
|
+
)
|
|
125
|
+
except Exception as logging_error:
|
|
126
|
+
logger.error(
|
|
127
|
+
"logger_failed",
|
|
128
|
+
operation="log_new_remote_issue",
|
|
129
|
+
error=str(logging_error),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return normalized_remote, unmatched_count
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def normalize_remote_keys(
|
|
136
|
+
local: dict[str, Any],
|
|
137
|
+
remote: Mapping[str, Any],
|
|
138
|
+
backend: Any | None,
|
|
139
|
+
logger: Any | None = None,
|
|
140
|
+
) -> tuple[dict[str, Any], Mapping[str, Any]]:
|
|
141
|
+
"""Normalize remote issue keys to match local issue UUIDs.
|
|
142
|
+
|
|
143
|
+
Returns (local, normalized_remote) where `normalized_remote` is keyed by
|
|
144
|
+
local UUIDs where a mapping exists, and unmatched remote keys are
|
|
145
|
+
preserved with a `_remote_{key}` prefix.
|
|
146
|
+
"""
|
|
147
|
+
if backend is None:
|
|
148
|
+
return local, remote
|
|
149
|
+
|
|
150
|
+
backend_name = backend.get_backend_name()
|
|
151
|
+
remote_link_repo = getattr(backend, "remote_link_repo", None)
|
|
152
|
+
|
|
153
|
+
# Build ID mapping from DB + YAML
|
|
154
|
+
remote_id_to_local_uuid, db_lookup_available = _build_remote_id_mapping(
|
|
155
|
+
local, backend, remote_link_repo, logger
|
|
156
|
+
)
|
|
157
|
+
if logger is not None:
|
|
158
|
+
try:
|
|
159
|
+
logger.info(
|
|
160
|
+
"remote_link_mapping_built",
|
|
161
|
+
backend=backend_name,
|
|
162
|
+
db_lookup_used=db_lookup_available,
|
|
163
|
+
local_issue_count=len(local),
|
|
164
|
+
remote_issue_count=len(remote),
|
|
165
|
+
mapped_links=len(remote_id_to_local_uuid),
|
|
166
|
+
)
|
|
167
|
+
except Exception as logging_error:
|
|
168
|
+
logger.error(
|
|
169
|
+
"logger_failed",
|
|
170
|
+
operation="log_remote_link_mapping_built",
|
|
171
|
+
error=str(logging_error),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Apply mapping to normalize remote
|
|
175
|
+
normalized_remote, unmatched_count = _apply_remote_normalization(
|
|
176
|
+
remote, remote_id_to_local_uuid, backend_name, logger
|
|
177
|
+
)
|
|
178
|
+
if logger is not None:
|
|
179
|
+
try:
|
|
180
|
+
sample_unmatched = [
|
|
181
|
+
str(key)
|
|
182
|
+
for key in list(remote.keys())[:5]
|
|
183
|
+
if str(key) not in remote_id_to_local_uuid
|
|
184
|
+
]
|
|
185
|
+
logger.debug(
|
|
186
|
+
"remote_link_mapping_sample",
|
|
187
|
+
backend=backend_name,
|
|
188
|
+
sample_unmatched=sample_unmatched,
|
|
189
|
+
)
|
|
190
|
+
except Exception as logging_error:
|
|
191
|
+
logger.error(
|
|
192
|
+
"logger_failed",
|
|
193
|
+
operation="log_remote_link_mapping_sample",
|
|
194
|
+
error=str(logging_error),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if logger is not None:
|
|
198
|
+
try:
|
|
199
|
+
logger.info(
|
|
200
|
+
"remote_keys_normalized",
|
|
201
|
+
original_remote_count=len(remote),
|
|
202
|
+
normalized_count=len(normalized_remote),
|
|
203
|
+
matched=len(remote) - unmatched_count,
|
|
204
|
+
unmatched=unmatched_count,
|
|
205
|
+
backend=backend_name,
|
|
206
|
+
db_lookup_used=db_lookup_available,
|
|
207
|
+
)
|
|
208
|
+
if unmatched_count > 0 and not db_lookup_available:
|
|
209
|
+
logger.warning(
|
|
210
|
+
"remote_keys_unmatched_no_db_links",
|
|
211
|
+
unmatched=unmatched_count,
|
|
212
|
+
backend=backend_name,
|
|
213
|
+
hint="remote issues appear unlinked to local IDs",
|
|
214
|
+
)
|
|
215
|
+
except Exception as logging_error:
|
|
216
|
+
logger.error(
|
|
217
|
+
"logger_failed",
|
|
218
|
+
operation="log_remote_keys_normalized",
|
|
219
|
+
error=str(logging_error),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return local, normalized_remote
|