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,290 @@
|
|
|
1
|
+
"""Batch processing for parallel sync operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from structlog import get_logger
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from roadmap.core.domain.issue import Issue
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BatchProcessor:
|
|
19
|
+
"""Process items in parallel batches for improved performance."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, max_workers: int = 5, batch_size: int = 10):
|
|
22
|
+
"""Initialize batch processor.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
max_workers: Maximum number of parallel workers
|
|
26
|
+
batch_size: Number of items to process per batch
|
|
27
|
+
"""
|
|
28
|
+
self.max_workers = max_workers
|
|
29
|
+
self.batch_size = batch_size
|
|
30
|
+
|
|
31
|
+
def process_batch(
|
|
32
|
+
self,
|
|
33
|
+
items: list[Any],
|
|
34
|
+
process_func: Callable[[Any], Any],
|
|
35
|
+
on_progress: Callable[[int, Any], None] | None = None,
|
|
36
|
+
) -> tuple[list[Any], list[tuple[Any, Exception]]]:
|
|
37
|
+
"""Process items in parallel batches.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
items: List of items to process
|
|
41
|
+
process_func: Function to apply to each item
|
|
42
|
+
on_progress: Optional callback for progress updates (index, result)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Tuple of (successful_results, failed_items_with_errors)
|
|
46
|
+
"""
|
|
47
|
+
results = []
|
|
48
|
+
errors = []
|
|
49
|
+
|
|
50
|
+
# Split into batches
|
|
51
|
+
batches = [
|
|
52
|
+
items[i : i + self.batch_size]
|
|
53
|
+
for i in range(0, len(items), self.batch_size)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
logger.info(
|
|
57
|
+
"batch_processing_started",
|
|
58
|
+
total_items=len(items),
|
|
59
|
+
batches=len(batches),
|
|
60
|
+
batch_size=self.batch_size,
|
|
61
|
+
max_workers=self.max_workers,
|
|
62
|
+
action="process_batch",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Process each batch in parallel
|
|
66
|
+
for batch_idx, batch in enumerate(batches):
|
|
67
|
+
batch_results, batch_errors = self._process_single_batch(
|
|
68
|
+
batch, process_func, batch_idx * self.batch_size, on_progress
|
|
69
|
+
)
|
|
70
|
+
results.extend(batch_results)
|
|
71
|
+
errors.extend(batch_errors)
|
|
72
|
+
|
|
73
|
+
logger.info(
|
|
74
|
+
"batch_processing_completed",
|
|
75
|
+
total_items=len(items),
|
|
76
|
+
successful=len(results),
|
|
77
|
+
failed=len(errors),
|
|
78
|
+
action="process_batch",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return results, errors
|
|
82
|
+
|
|
83
|
+
def _process_single_batch(
|
|
84
|
+
self,
|
|
85
|
+
batch: list[Any],
|
|
86
|
+
process_func: Callable[[Any], Any],
|
|
87
|
+
offset: int,
|
|
88
|
+
on_progress: Callable[[int, Any], None] | None = None,
|
|
89
|
+
) -> tuple[list[Any], list[tuple[Any, Exception]]]:
|
|
90
|
+
"""Process a single batch using thread pool.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
batch: Batch of items to process
|
|
94
|
+
process_func: Function to apply to each item
|
|
95
|
+
offset: Offset for progress reporting
|
|
96
|
+
on_progress: Optional callback for progress updates
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Tuple of (results, errors)
|
|
100
|
+
"""
|
|
101
|
+
results = []
|
|
102
|
+
errors = []
|
|
103
|
+
|
|
104
|
+
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
|
105
|
+
# Submit all tasks
|
|
106
|
+
future_to_item = {
|
|
107
|
+
executor.submit(process_func, item): (idx + offset, item)
|
|
108
|
+
for idx, item in enumerate(batch)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Collect results as they complete
|
|
112
|
+
for future in as_completed(future_to_item):
|
|
113
|
+
idx, item = future_to_item[future]
|
|
114
|
+
try:
|
|
115
|
+
result = future.result()
|
|
116
|
+
results.append(result)
|
|
117
|
+
|
|
118
|
+
if on_progress:
|
|
119
|
+
on_progress(idx, result)
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(
|
|
123
|
+
"batch_item_failed",
|
|
124
|
+
index=idx,
|
|
125
|
+
item=str(item)[:100],
|
|
126
|
+
error=str(e),
|
|
127
|
+
action="process_item",
|
|
128
|
+
)
|
|
129
|
+
errors.append((item, e))
|
|
130
|
+
|
|
131
|
+
return results, errors
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class AsyncBatchProcessor:
|
|
135
|
+
"""Async batch processor for async operations."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, batch_size: int = 10, max_concurrent: int = 5):
|
|
138
|
+
"""Initialize async batch processor.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
batch_size: Number of items per batch
|
|
142
|
+
max_concurrent: Maximum concurrent tasks
|
|
143
|
+
"""
|
|
144
|
+
self.batch_size = batch_size
|
|
145
|
+
self.max_concurrent = max_concurrent
|
|
146
|
+
|
|
147
|
+
async def process_batch(
|
|
148
|
+
self,
|
|
149
|
+
items: list[Any],
|
|
150
|
+
process_func: Callable[[Any], Any],
|
|
151
|
+
on_progress: Callable[[int, Any], None] | None = None,
|
|
152
|
+
) -> tuple[list[Any], list[tuple[Any, Exception]]]:
|
|
153
|
+
"""Process items asynchronously in batches.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
items: List of items to process
|
|
157
|
+
process_func: Async function to apply to each item
|
|
158
|
+
on_progress: Optional callback for progress updates
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Tuple of (results, errors)
|
|
162
|
+
"""
|
|
163
|
+
results = []
|
|
164
|
+
errors = []
|
|
165
|
+
|
|
166
|
+
# Split into batches
|
|
167
|
+
batches = [
|
|
168
|
+
items[i : i + self.batch_size]
|
|
169
|
+
for i in range(0, len(items), self.batch_size)
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
logger.info(
|
|
173
|
+
"async_batch_processing_started",
|
|
174
|
+
total_items=len(items),
|
|
175
|
+
batches=len(batches),
|
|
176
|
+
batch_size=self.batch_size,
|
|
177
|
+
max_concurrent=self.max_concurrent,
|
|
178
|
+
action="process_batch",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Process batches
|
|
182
|
+
for batch_idx, batch in enumerate(batches):
|
|
183
|
+
batch_results, batch_errors = await self._process_single_batch(
|
|
184
|
+
batch, process_func, batch_idx * self.batch_size, on_progress
|
|
185
|
+
)
|
|
186
|
+
results.extend(batch_results)
|
|
187
|
+
errors.extend(batch_errors)
|
|
188
|
+
|
|
189
|
+
logger.info(
|
|
190
|
+
"async_batch_processing_completed",
|
|
191
|
+
total_items=len(items),
|
|
192
|
+
successful=len(results),
|
|
193
|
+
failed=len(errors),
|
|
194
|
+
action="process_batch",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return results, errors
|
|
198
|
+
|
|
199
|
+
async def _process_single_batch(
|
|
200
|
+
self,
|
|
201
|
+
batch: list[Any],
|
|
202
|
+
process_func: Callable[[Any], Any],
|
|
203
|
+
offset: int,
|
|
204
|
+
on_progress: Callable[[int, Any], None] | None = None,
|
|
205
|
+
) -> tuple[list[Any], list[tuple[Any, Exception]]]:
|
|
206
|
+
"""Process a single batch asynchronously.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
batch: Batch of items to process
|
|
210
|
+
process_func: Async function to apply
|
|
211
|
+
offset: Offset for progress reporting
|
|
212
|
+
on_progress: Optional callback for progress
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Tuple of (results, errors)
|
|
216
|
+
"""
|
|
217
|
+
results = []
|
|
218
|
+
errors = []
|
|
219
|
+
|
|
220
|
+
# Create semaphore to limit concurrency
|
|
221
|
+
semaphore = asyncio.Semaphore(self.max_concurrent)
|
|
222
|
+
|
|
223
|
+
async def process_with_semaphore(idx: int, item: Any):
|
|
224
|
+
async with semaphore:
|
|
225
|
+
try:
|
|
226
|
+
result = await process_func(item)
|
|
227
|
+
results.append(result)
|
|
228
|
+
|
|
229
|
+
if on_progress:
|
|
230
|
+
on_progress(idx + offset, result)
|
|
231
|
+
|
|
232
|
+
return result
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(
|
|
235
|
+
"async_batch_item_failed",
|
|
236
|
+
index=idx + offset,
|
|
237
|
+
item=str(item)[:100],
|
|
238
|
+
error=str(e),
|
|
239
|
+
action="process_item",
|
|
240
|
+
)
|
|
241
|
+
errors.append((item, e))
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# Process all items in batch concurrently
|
|
245
|
+
await asyncio.gather(
|
|
246
|
+
*[process_with_semaphore(idx, item) for idx, item in enumerate(batch)],
|
|
247
|
+
return_exceptions=True,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return results, errors
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def chunk_list(items: list[Any], chunk_size: int) -> list[list[Any]]:
|
|
254
|
+
"""Split a list into chunks of specified size.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
items: List to chunk
|
|
258
|
+
chunk_size: Size of each chunk
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of chunks
|
|
262
|
+
"""
|
|
263
|
+
return [items[i : i + chunk_size] for i in range(0, len(items), chunk_size)]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def batch_issues_by_milestone(issues: list[Issue]) -> dict[str, list[Issue]]:
|
|
267
|
+
"""Group issues by milestone for batch processing.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
issues: List of issues
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dict mapping milestone names to issue lists
|
|
274
|
+
"""
|
|
275
|
+
batches: dict[str, list[Issue]] = {}
|
|
276
|
+
|
|
277
|
+
for issue in issues:
|
|
278
|
+
milestone = issue.milestone or "_no_milestone"
|
|
279
|
+
if milestone not in batches:
|
|
280
|
+
batches[milestone] = []
|
|
281
|
+
batches[milestone].append(issue)
|
|
282
|
+
|
|
283
|
+
logger.info(
|
|
284
|
+
"issues_batched_by_milestone",
|
|
285
|
+
total_issues=len(issues),
|
|
286
|
+
milestones=len(batches),
|
|
287
|
+
action="batch_issues",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return batches
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Conflict resolution strategies for handling merge conflicts.
|
|
2
|
+
|
|
3
|
+
When three-way merge detects a true conflict (both sides changed
|
|
4
|
+
differently), this module applies field-level strategies to decide
|
|
5
|
+
what to do with that conflict.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConflictResolutionStrategy(StrEnum):
|
|
13
|
+
"""Strategy for resolving a conflict."""
|
|
14
|
+
|
|
15
|
+
FLAG_FOR_REVIEW = "flag_for_review" # Return None, needs manual review
|
|
16
|
+
GITHUB_WINS = "github_wins" # Use remote value
|
|
17
|
+
LOCAL_WINS = "local_wins" # Use local value
|
|
18
|
+
MERGE_UNION = "merge_union" # Combine lists (for labels)
|
|
19
|
+
MERGE_APPEND = "merge_append" # Append both (for comments/description)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConflictResolver:
|
|
23
|
+
"""Applies field-level strategies to resolve conflicts.
|
|
24
|
+
|
|
25
|
+
Each field has a predefined strategy for what to do when
|
|
26
|
+
the three-way merge detects a conflict on that field.
|
|
27
|
+
|
|
28
|
+
Critical fields (status, assignee) are flagged for review.
|
|
29
|
+
Non-critical fields (labels, comments) are auto-merged.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Field-level rules (from the sync implementation plan)
|
|
33
|
+
RULES = {
|
|
34
|
+
# Critical fields - flag for review (user must decide)
|
|
35
|
+
"status": ConflictResolutionStrategy.FLAG_FOR_REVIEW,
|
|
36
|
+
"assignee": ConflictResolutionStrategy.FLAG_FOR_REVIEW,
|
|
37
|
+
"milestone": ConflictResolutionStrategy.FLAG_FOR_REVIEW,
|
|
38
|
+
# Merge-friendly fields
|
|
39
|
+
"labels": ConflictResolutionStrategy.MERGE_UNION,
|
|
40
|
+
"description": ConflictResolutionStrategy.MERGE_APPEND,
|
|
41
|
+
"comments": ConflictResolutionStrategy.MERGE_APPEND,
|
|
42
|
+
# Metadata - GitHub is authoritative
|
|
43
|
+
"created_at": ConflictResolutionStrategy.GITHUB_WINS,
|
|
44
|
+
"updated_at": ConflictResolutionStrategy.GITHUB_WINS,
|
|
45
|
+
# Default: flag for review if not in RULES
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def resolve_conflict(
|
|
49
|
+
self,
|
|
50
|
+
field: str,
|
|
51
|
+
base: Any,
|
|
52
|
+
local: Any,
|
|
53
|
+
remote: Any,
|
|
54
|
+
) -> tuple[Any, bool]:
|
|
55
|
+
"""Resolve a single field conflict.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
field: Field name
|
|
59
|
+
base: Base value (from last sync)
|
|
60
|
+
local: Local value (current local state)
|
|
61
|
+
remote: Remote value (current remote state)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
(resolved_value, is_flagged)
|
|
65
|
+
where is_flagged=True means this needs manual review
|
|
66
|
+
"""
|
|
67
|
+
strategy = self.RULES.get(field, ConflictResolutionStrategy.FLAG_FOR_REVIEW)
|
|
68
|
+
|
|
69
|
+
if strategy == ConflictResolutionStrategy.FLAG_FOR_REVIEW:
|
|
70
|
+
# Return None to indicate this needs manual review
|
|
71
|
+
return None, True
|
|
72
|
+
|
|
73
|
+
elif strategy == ConflictResolutionStrategy.GITHUB_WINS:
|
|
74
|
+
return remote, False
|
|
75
|
+
|
|
76
|
+
elif strategy == ConflictResolutionStrategy.LOCAL_WINS:
|
|
77
|
+
return local, False
|
|
78
|
+
|
|
79
|
+
elif strategy == ConflictResolutionStrategy.MERGE_UNION:
|
|
80
|
+
# For labels: combine both lists, deduplicate
|
|
81
|
+
local_list = local if isinstance(local, list) else [local] if local else []
|
|
82
|
+
remote_list = (
|
|
83
|
+
remote if isinstance(remote, list) else [remote] if remote else []
|
|
84
|
+
)
|
|
85
|
+
merged = list(set(local_list + remote_list))
|
|
86
|
+
return merged, False
|
|
87
|
+
|
|
88
|
+
elif strategy == ConflictResolutionStrategy.MERGE_APPEND:
|
|
89
|
+
# For comments/description: append both with source markers
|
|
90
|
+
local_str = str(local) if local else ""
|
|
91
|
+
remote_str = str(remote) if remote else ""
|
|
92
|
+
result = f"{local_str}\n\n--- REMOTE CHANGES ---\n{remote_str}"
|
|
93
|
+
# Still flag for manual cleanup of markers
|
|
94
|
+
return result, True
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
# Unexpected strategy, flag for review
|
|
98
|
+
return None, True
|
|
99
|
+
|
|
100
|
+
def resolve_issue_conflicts(
|
|
101
|
+
self,
|
|
102
|
+
issue_id: str,
|
|
103
|
+
base: dict[str, Any],
|
|
104
|
+
local: dict[str, Any],
|
|
105
|
+
remote: dict[str, Any],
|
|
106
|
+
conflict_fields: list[str],
|
|
107
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
108
|
+
"""Resolve all conflicts in an issue.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
issue_id: Issue identifier (for logging)
|
|
112
|
+
base: Base version of issue
|
|
113
|
+
local: Local version of issue
|
|
114
|
+
remote: Remote version of issue
|
|
115
|
+
conflict_fields: List of field names that have conflicts
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
(resolved_fields, list_of_fields_still_flagged)
|
|
119
|
+
|
|
120
|
+
The resolved_fields dict contains fields that were
|
|
121
|
+
auto-resolved. Fields still flagged need manual review.
|
|
122
|
+
"""
|
|
123
|
+
resolved = {}
|
|
124
|
+
flagged = []
|
|
125
|
+
|
|
126
|
+
for field in conflict_fields:
|
|
127
|
+
value, is_flagged = self.resolve_conflict(
|
|
128
|
+
field,
|
|
129
|
+
base.get(field),
|
|
130
|
+
local.get(field),
|
|
131
|
+
remote.get(field),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if is_flagged:
|
|
135
|
+
flagged.append(field)
|
|
136
|
+
# Don't include in resolved yet; needs manual review
|
|
137
|
+
else:
|
|
138
|
+
if value is not None:
|
|
139
|
+
resolved[field] = value
|
|
140
|
+
|
|
141
|
+
return resolved, flagged
|
|
142
|
+
|
|
143
|
+
def has_critical_conflicts(self, flagged_fields: list[str]) -> bool:
|
|
144
|
+
"""Check if flagged fields include critical ones.
|
|
145
|
+
|
|
146
|
+
Critical fields are status, assignee, milestone.
|
|
147
|
+
If these have conflicts, user MUST review.
|
|
148
|
+
"""
|
|
149
|
+
critical = {"status", "assignee", "milestone"}
|
|
150
|
+
return any(field in critical for field in flagged_fields)
|
|
151
|
+
|
|
152
|
+
def get_strategy_for_field(self, field: str) -> ConflictResolutionStrategy:
|
|
153
|
+
"""Get the strategy for a specific field."""
|
|
154
|
+
return self.RULES.get(field, ConflictResolutionStrategy.FLAG_FOR_REVIEW)
|