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,204 @@
|
|
|
1
|
+
"""Service for fetching milestones from GitHub API."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from structlog import get_logger
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.sync.backends.github_client import GitHubClientWrapper
|
|
8
|
+
from roadmap.common.logging import log_error_with_context
|
|
9
|
+
from roadmap.core.models.sync_models import SyncMilestone
|
|
10
|
+
|
|
11
|
+
logger = get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GitHubMilestoneFetchService:
|
|
15
|
+
"""Handles fetching milestones from GitHub API."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
github_client: GitHubClientWrapper,
|
|
20
|
+
config: dict,
|
|
21
|
+
):
|
|
22
|
+
"""Initialize milestone fetch service.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
github_client: GitHubClientWrapper for API access
|
|
26
|
+
config: Configuration dict with 'owner', 'repo'
|
|
27
|
+
"""
|
|
28
|
+
self.github_client = github_client
|
|
29
|
+
self.config = config
|
|
30
|
+
|
|
31
|
+
def get_milestones(self, state: str = "all") -> dict[str, SyncMilestone]:
|
|
32
|
+
"""Fetch all milestones from GitHub.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
state: Milestone state filter ('open', 'closed', 'all')
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict mapping milestone numbers (as strings) to SyncMilestone objects,
|
|
39
|
+
or empty dict if fetch fails.
|
|
40
|
+
"""
|
|
41
|
+
if not self.github_client:
|
|
42
|
+
logger.warning("github_milestone_fetch_no_client")
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
owner = self.config.get("owner")
|
|
46
|
+
repo = self.config.get("repo")
|
|
47
|
+
|
|
48
|
+
if not owner or not repo:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"github_milestone_fetch_missing_config", owner=owner, repo=repo
|
|
51
|
+
)
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
logger.debug(
|
|
56
|
+
"github_milestone_fetch_start",
|
|
57
|
+
owner=owner,
|
|
58
|
+
repo=repo,
|
|
59
|
+
state=state,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Get milestones using GitHub API
|
|
63
|
+
# Import handler and create with proper session
|
|
64
|
+
import requests
|
|
65
|
+
|
|
66
|
+
from roadmap.adapters.github.handlers.milestones import MilestoneHandler
|
|
67
|
+
|
|
68
|
+
# Create a requests session with authentication
|
|
69
|
+
session = requests.Session()
|
|
70
|
+
token = self.config.get("token")
|
|
71
|
+
if token:
|
|
72
|
+
session.headers.update(
|
|
73
|
+
{
|
|
74
|
+
"Authorization": f"token {token}",
|
|
75
|
+
"Accept": "application/vnd.github.v3+json",
|
|
76
|
+
"User-Agent": "roadmap-cli/1.0",
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
milestone_handler = MilestoneHandler(
|
|
81
|
+
session=session,
|
|
82
|
+
owner=owner,
|
|
83
|
+
repo=repo,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
milestones_data = milestone_handler.get_milestones(state=state)
|
|
87
|
+
|
|
88
|
+
logger.info(
|
|
89
|
+
"github_milestones_fetched",
|
|
90
|
+
owner=owner,
|
|
91
|
+
repo=repo,
|
|
92
|
+
count=len(milestones_data),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Convert each GitHub milestone to SyncMilestone
|
|
96
|
+
result = {}
|
|
97
|
+
failed_conversions = 0
|
|
98
|
+
for milestone_dict in milestones_data:
|
|
99
|
+
try:
|
|
100
|
+
sync_milestone = self._dict_to_sync_milestone(milestone_dict)
|
|
101
|
+
milestone_number = str(milestone_dict.get("number", ""))
|
|
102
|
+
result[milestone_number] = sync_milestone
|
|
103
|
+
except Exception as e:
|
|
104
|
+
failed_conversions += 1
|
|
105
|
+
milestone_number = (
|
|
106
|
+
milestone_dict.get("number")
|
|
107
|
+
if isinstance(milestone_dict, dict)
|
|
108
|
+
else "unknown"
|
|
109
|
+
)
|
|
110
|
+
log_error_with_context(
|
|
111
|
+
e,
|
|
112
|
+
operation="convert_github_milestone_to_sync",
|
|
113
|
+
entity_type="Milestone",
|
|
114
|
+
entity_id=str(milestone_number),
|
|
115
|
+
additional_context={
|
|
116
|
+
"owner": owner,
|
|
117
|
+
"repo": repo,
|
|
118
|
+
"total_milestones": len(milestones_data),
|
|
119
|
+
},
|
|
120
|
+
include_traceback=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if failed_conversions > 0:
|
|
124
|
+
logger.warning(
|
|
125
|
+
"github_milestone_conversions_failed",
|
|
126
|
+
owner=owner,
|
|
127
|
+
repo=repo,
|
|
128
|
+
total=len(milestones_data),
|
|
129
|
+
failed=failed_conversions,
|
|
130
|
+
successful=len(result),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
log_error_with_context(
|
|
137
|
+
e,
|
|
138
|
+
operation="fetch_github_milestones",
|
|
139
|
+
entity_type="Milestone",
|
|
140
|
+
additional_context={"owner": owner, "repo": repo, "state": state},
|
|
141
|
+
include_traceback=True,
|
|
142
|
+
)
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
def _dict_to_sync_milestone(self, milestone_dict: dict) -> SyncMilestone:
|
|
146
|
+
"""Convert GitHub milestone dict to SyncMilestone.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
milestone_dict: GitHub API milestone response
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
SyncMilestone instance
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If required fields are missing
|
|
156
|
+
"""
|
|
157
|
+
milestone_number = milestone_dict.get("number")
|
|
158
|
+
if not milestone_number:
|
|
159
|
+
raise ValueError("GitHub milestone missing 'number' field")
|
|
160
|
+
|
|
161
|
+
title = milestone_dict.get("title")
|
|
162
|
+
if not title:
|
|
163
|
+
raise ValueError(f"GitHub milestone {milestone_number} missing 'title'")
|
|
164
|
+
|
|
165
|
+
# Parse state (GitHub uses "open", "closed")
|
|
166
|
+
state = milestone_dict.get("state", "open")
|
|
167
|
+
|
|
168
|
+
# Parse due date
|
|
169
|
+
due_date = None
|
|
170
|
+
due_on_str = milestone_dict.get("due_on")
|
|
171
|
+
if due_on_str:
|
|
172
|
+
try:
|
|
173
|
+
# GitHub returns ISO 8601 format with Z suffix
|
|
174
|
+
due_date = datetime.fromisoformat(due_on_str.replace("Z", "+00:00"))
|
|
175
|
+
except (ValueError, AttributeError) as e:
|
|
176
|
+
logger.debug(
|
|
177
|
+
"github_milestone_due_date_parse_failed",
|
|
178
|
+
milestone_number=milestone_number,
|
|
179
|
+
due_on_str=due_on_str,
|
|
180
|
+
error=str(e),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Create SyncMilestone
|
|
184
|
+
sync_milestone = SyncMilestone(
|
|
185
|
+
id=f"gh_milestone_{milestone_number}",
|
|
186
|
+
name=title,
|
|
187
|
+
status=state,
|
|
188
|
+
headline=milestone_dict.get("description", "")[:100]
|
|
189
|
+
if milestone_dict.get("description")
|
|
190
|
+
else None,
|
|
191
|
+
due_date=due_date,
|
|
192
|
+
backend_name="github",
|
|
193
|
+
backend_id=milestone_number,
|
|
194
|
+
remote_ids={"github": milestone_number},
|
|
195
|
+
raw_response=milestone_dict,
|
|
196
|
+
metadata={
|
|
197
|
+
"open_issues": milestone_dict.get("open_issues", 0),
|
|
198
|
+
"closed_issues": milestone_dict.get("closed_issues", 0),
|
|
199
|
+
"created_at": milestone_dict.get("created_at"),
|
|
200
|
+
"updated_at": milestone_dict.get("updated_at"),
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return sync_milestone
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Vanilla Git backend for self-hosting scenarios.
|
|
2
|
+
|
|
3
|
+
For self-hosted deployments (no remote database), this backend is a no-op.
|
|
4
|
+
The sync workflow is:
|
|
5
|
+
1. User runs 'roadmap sync' to sync with GitHub API (via github_sync_backend)
|
|
6
|
+
2. Sync creates/modifies .roadmap/issues/*.md files
|
|
7
|
+
3. User manually runs 'git add' and 'git commit' to persist changes
|
|
8
|
+
4. User manually runs 'git push' to push to git remote
|
|
9
|
+
|
|
10
|
+
This backend does not perform git operations - that's the user's responsibility.
|
|
11
|
+
|
|
12
|
+
Updated to use Result<T, SyncError> pattern for explicit error handling.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from roadmap.common.result import Ok, Result
|
|
18
|
+
from roadmap.core.domain.issue import Issue
|
|
19
|
+
from roadmap.core.interfaces import (
|
|
20
|
+
SyncConflict,
|
|
21
|
+
SyncReport,
|
|
22
|
+
)
|
|
23
|
+
from roadmap.core.models.sync_models import (
|
|
24
|
+
SyncIssue,
|
|
25
|
+
SyncMilestone,
|
|
26
|
+
SyncProject,
|
|
27
|
+
)
|
|
28
|
+
from roadmap.core.services.sync.sync_errors import SyncError
|
|
29
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VanillaGitSyncBackend:
|
|
33
|
+
"""No-op backend for self-hosting scenarios.
|
|
34
|
+
|
|
35
|
+
For self-hosted deployments without a remote database, this backend
|
|
36
|
+
delegates all operations to the user. The user is responsible for:
|
|
37
|
+
1. Git add/commit of modified .roadmap/issues/ files
|
|
38
|
+
2. Git push to sync with remote
|
|
39
|
+
|
|
40
|
+
This backend exists for interface compatibility but does not perform
|
|
41
|
+
any git operations.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
core: RoadmapCore instance with access to local issues
|
|
45
|
+
config: Configuration dict (unused, kept for interface compatibility)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, core: RoadmapCore, config: dict[str, Any]):
|
|
49
|
+
"""Initialize vanilla git sync backend.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
core: RoadmapCore instance
|
|
53
|
+
config: Configuration dict (unused)
|
|
54
|
+
"""
|
|
55
|
+
self.core = core
|
|
56
|
+
self.config = config
|
|
57
|
+
|
|
58
|
+
def get_backend_name(self) -> str:
|
|
59
|
+
"""Get the canonical name of this backend.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
'git' - used as key in Issue.remote_ids dict
|
|
63
|
+
"""
|
|
64
|
+
return "git"
|
|
65
|
+
|
|
66
|
+
def authenticate(self) -> Result[bool, SyncError]:
|
|
67
|
+
"""No-op authentication for self-hosting.
|
|
68
|
+
|
|
69
|
+
For self-hosted scenarios without a remote database, authentication
|
|
70
|
+
always succeeds - the user is responsible for git operations.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Ok(True) - always succeeds for self-hosted scenario
|
|
74
|
+
"""
|
|
75
|
+
return Ok(True)
|
|
76
|
+
|
|
77
|
+
def get_issues(self) -> Result[dict[str, SyncIssue], SyncError]:
|
|
78
|
+
"""No-op: Get issues from remote.
|
|
79
|
+
|
|
80
|
+
For self-hosting without a remote database, always returns empty dict.
|
|
81
|
+
The user is responsible for syncing via git operations.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Ok with empty dict (no remote issues in self-hosted scenario)
|
|
85
|
+
"""
|
|
86
|
+
return Ok({})
|
|
87
|
+
|
|
88
|
+
def push_issues(self, local_issues: list[Issue]) -> Result[SyncReport, SyncError]:
|
|
89
|
+
"""No-op: Push multiple issues.
|
|
90
|
+
|
|
91
|
+
For self-hosting, the user is responsible for git operations.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
local_issues: List of Issue objects (unused)
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Ok with empty SyncReport (no-op)
|
|
98
|
+
"""
|
|
99
|
+
return Ok(SyncReport())
|
|
100
|
+
|
|
101
|
+
def push_issue(self, local_issue: Issue) -> Result[bool, SyncError]:
|
|
102
|
+
"""No-op: Push a single issue.
|
|
103
|
+
|
|
104
|
+
For self-hosting, the user is responsible for git operations.
|
|
105
|
+
Delegates to push_issues for consistency.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
local_issue: The Issue object (unused)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Ok(True) - no-op succeeds
|
|
112
|
+
"""
|
|
113
|
+
report_result = self.push_issues([local_issue])
|
|
114
|
+
if report_result.is_err():
|
|
115
|
+
return report_result # type: ignore[return-value]
|
|
116
|
+
|
|
117
|
+
report = report_result.unwrap()
|
|
118
|
+
if len(report.pushed) > 0 or len(report.errors) == 0:
|
|
119
|
+
return Ok(True)
|
|
120
|
+
return Ok(True) # No-op always succeeds
|
|
121
|
+
|
|
122
|
+
def pull_issues(self, issue_ids: list[str]) -> Result[SyncReport, SyncError]:
|
|
123
|
+
"""No-op: Pull specified remote issues.
|
|
124
|
+
|
|
125
|
+
For self-hosting, the user is responsible for git operations.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
issue_ids: List of issue IDs to pull (unused)
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Ok with empty SyncReport (no-op)
|
|
132
|
+
"""
|
|
133
|
+
return Ok(SyncReport())
|
|
134
|
+
|
|
135
|
+
def pull_issue(self, issue_id: str) -> Result[bool, SyncError]:
|
|
136
|
+
"""No-op: Pull a single issue.
|
|
137
|
+
|
|
138
|
+
For self-hosting, the user is responsible for git operations.
|
|
139
|
+
Delegates to pull_issues for consistency.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
issue_id: The issue ID (unused)
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Ok(True) - no-op succeeds
|
|
146
|
+
"""
|
|
147
|
+
report_result = self.pull_issues([issue_id])
|
|
148
|
+
if report_result.is_err():
|
|
149
|
+
return report_result # type: ignore[return-value]
|
|
150
|
+
|
|
151
|
+
return Ok(True) # No-op always succeeds
|
|
152
|
+
|
|
153
|
+
def get_conflict_resolution_options(self, conflict: SyncConflict) -> list[str]:
|
|
154
|
+
"""Get available resolution strategies.
|
|
155
|
+
|
|
156
|
+
For self-hosting, no conflict resolution options (no remote to conflict with).
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
conflict: The SyncConflict (unused)
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Empty list (no remote database)
|
|
163
|
+
"""
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
def resolve_conflict(self, conflict: SyncConflict, resolution: str) -> bool:
|
|
167
|
+
"""No-op: Resolve a conflict.
|
|
168
|
+
|
|
169
|
+
For self-hosting, no conflicts can occur (no remote database).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
conflict: The SyncConflict (unused)
|
|
173
|
+
resolution: The resolution strategy (unused)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True (no-op succeeds)
|
|
177
|
+
"""
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def get_milestones(self) -> Result[dict[str, SyncMilestone], SyncError]:
|
|
181
|
+
"""No-op: Get milestones from remote.
|
|
182
|
+
|
|
183
|
+
For self-hosting without a remote database, always returns empty dict.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Ok with empty dict (no remote milestones in self-hosted scenario)
|
|
187
|
+
"""
|
|
188
|
+
return Ok({})
|
|
189
|
+
|
|
190
|
+
def get_projects(self) -> Result[dict[str, SyncProject], SyncError]:
|
|
191
|
+
"""No-op: Get projects from remote.
|
|
192
|
+
|
|
193
|
+
For self-hosting without a remote database, always returns empty dict.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Ok with empty dict (no remote projects in self-hosted scenario)
|
|
197
|
+
"""
|
|
198
|
+
return Ok({})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Shared services for sync backends."""
|
|
2
|
+
|
|
3
|
+
from .issue_persistence_service import IssuePersistenceService
|
|
4
|
+
from .issue_state_service import IssueStateService
|
|
5
|
+
from .sync_linking_service import SyncLinkingService
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"IssueStateService",
|
|
9
|
+
"IssuePersistenceService",
|
|
10
|
+
"SyncLinkingService",
|
|
11
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Handles baseline state loading and updates for sync operations.
|
|
2
|
+
|
|
3
|
+
Extracted from SyncMergeEngine to separate baseline management concerns.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import fields
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from structlog import get_logger
|
|
11
|
+
|
|
12
|
+
from roadmap.core.services.sync.sync_state import IssueBaseState, SyncState
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaselineStateHandler:
|
|
18
|
+
"""Manages baseline state loading and updates."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, core: Any, state_update_service: Any):
|
|
21
|
+
"""Initialize with core services.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
core: RoadmapCore instance with database access
|
|
25
|
+
state_update_service: SyncStateUpdateService for baseline updates
|
|
26
|
+
"""
|
|
27
|
+
self.core = core
|
|
28
|
+
self.state_update_service = state_update_service
|
|
29
|
+
|
|
30
|
+
def load_baseline_state(self) -> SyncState | None:
|
|
31
|
+
"""Load baseline state from database.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
SyncState object if baseline exists, None otherwise
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
db_baseline = self.core.db.get_sync_baseline()
|
|
38
|
+
if db_baseline:
|
|
39
|
+
logger.debug(
|
|
40
|
+
"baseline_loaded_from_database", issue_count=len(db_baseline)
|
|
41
|
+
)
|
|
42
|
+
base_issues = {}
|
|
43
|
+
issue_field_names = {field.name for field in fields(IssueBaseState)}
|
|
44
|
+
for issue_id, data in db_baseline.items():
|
|
45
|
+
issue_kwargs = {
|
|
46
|
+
"id": issue_id,
|
|
47
|
+
"title": data.get("title", ""),
|
|
48
|
+
"assignee": data.get("assignee"),
|
|
49
|
+
"headline": data.get("headline", ""),
|
|
50
|
+
"content": data.get("content", ""),
|
|
51
|
+
"description": data.get("description", ""),
|
|
52
|
+
"labels": data.get("labels", []),
|
|
53
|
+
}
|
|
54
|
+
status_value = data.get("status", "todo")
|
|
55
|
+
if "status" in issue_field_names:
|
|
56
|
+
issue_kwargs["status"] = status_value
|
|
57
|
+
elif "state" in issue_field_names:
|
|
58
|
+
issue_kwargs["state"] = status_value
|
|
59
|
+
|
|
60
|
+
base_issues[issue_id] = IssueBaseState(**issue_kwargs)
|
|
61
|
+
|
|
62
|
+
sync_state = SyncState(
|
|
63
|
+
last_sync_time=datetime.now(UTC),
|
|
64
|
+
base_issues=base_issues,
|
|
65
|
+
)
|
|
66
|
+
return sync_state
|
|
67
|
+
|
|
68
|
+
logger.debug("baseline_not_found_in_database")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(
|
|
73
|
+
"baseline_load_failed", error=str(e), error_type=type(e).__name__
|
|
74
|
+
)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def update_baseline_for_pulled(self, pulled_remote_ids: list[str]) -> None:
|
|
78
|
+
"""Update baseline after pulling changes.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
pulled_remote_ids: List of remote issue IDs that were pulled
|
|
82
|
+
"""
|
|
83
|
+
self.state_update_service.update_baseline_for_pulled(pulled_remote_ids)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Converts issue changes to conflicts for resolution.
|
|
2
|
+
|
|
3
|
+
Extracted from SyncMergeEngine to separate conflict conversion logic.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from structlog import get_logger
|
|
7
|
+
|
|
8
|
+
from roadmap.core.services.sync.sync_conflict_resolver import Conflict, ConflictField
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConflictConverter:
|
|
14
|
+
"""Converts issue changes to conflict objects."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, state_comparator):
|
|
17
|
+
"""Initialize with state comparator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
state_comparator: SyncStateComparator for timestamp extraction
|
|
21
|
+
"""
|
|
22
|
+
self.state_comparator = state_comparator
|
|
23
|
+
|
|
24
|
+
def convert_changes_to_conflicts(self, issue_changes: list) -> list[Conflict]:
|
|
25
|
+
"""Convert issue changes with conflicts to conflict objects.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
issue_changes: List of IssueChange objects with conflict info
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of Conflict objects ready for resolution
|
|
32
|
+
"""
|
|
33
|
+
conflicts: list[Conflict] = []
|
|
34
|
+
|
|
35
|
+
for change in issue_changes:
|
|
36
|
+
if (
|
|
37
|
+
not change.has_conflict
|
|
38
|
+
or not change.local_state
|
|
39
|
+
or not change.remote_state
|
|
40
|
+
):
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
conflicting_fields = []
|
|
44
|
+
if change.local_changes:
|
|
45
|
+
for field_name, _change_info in change.local_changes.items():
|
|
46
|
+
if field_name in change.remote_changes:
|
|
47
|
+
conflict_field = ConflictField(
|
|
48
|
+
field_name=field_name,
|
|
49
|
+
local_value=change.local_state.__dict__.get(
|
|
50
|
+
field_name, None
|
|
51
|
+
),
|
|
52
|
+
remote_value=change.remote_state.get(field_name),
|
|
53
|
+
local_updated=change.local_state.updated,
|
|
54
|
+
remote_updated=self.state_comparator._extract_timestamp(
|
|
55
|
+
change.remote_state, "updated_at"
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
conflicting_fields.append(conflict_field)
|
|
59
|
+
|
|
60
|
+
if conflicting_fields:
|
|
61
|
+
conflict = Conflict(
|
|
62
|
+
issue_id=change.issue_id,
|
|
63
|
+
local_issue=change.local_state,
|
|
64
|
+
remote_issue=change.remote_state,
|
|
65
|
+
fields=conflicting_fields,
|
|
66
|
+
local_updated=change.local_state.updated,
|
|
67
|
+
remote_updated=self.state_comparator._extract_timestamp(
|
|
68
|
+
change.remote_state, "updated_at"
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
conflicts.append(conflict)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.warning(
|
|
75
|
+
"conflict_conversion_failed",
|
|
76
|
+
issue_id=change.issue_id,
|
|
77
|
+
operation="analyze_conflicts",
|
|
78
|
+
error_type=type(e).__name__,
|
|
79
|
+
error=str(e),
|
|
80
|
+
is_recoverable=True,
|
|
81
|
+
suggested_action="skip_issue",
|
|
82
|
+
)
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
return conflicts
|