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,274 @@
|
|
|
1
|
+
"""Enhanced baseline building with progress display.
|
|
2
|
+
|
|
3
|
+
This module provides progress bar integration for sync state rebuilding,
|
|
4
|
+
allowing users to see real-time feedback during optimization phases.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from time import time
|
|
10
|
+
|
|
11
|
+
from structlog import get_logger
|
|
12
|
+
|
|
13
|
+
from roadmap.core.services.baseline.optimized_baseline_builder import (
|
|
14
|
+
CachedBaselineState,
|
|
15
|
+
OptimizedBaselineBuilder,
|
|
16
|
+
)
|
|
17
|
+
from roadmap.core.services.sync.sync_state import SyncState
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProgressTrackingBaselineBuilder:
|
|
23
|
+
"""Wraps OptimizedBaselineBuilder with progress bar support.
|
|
24
|
+
|
|
25
|
+
Provides real-time feedback during baseline rebuilding operations,
|
|
26
|
+
showing which phase is active (detection, rebuilding, merging).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
issues_dir: Path,
|
|
32
|
+
show_progress: bool = True,
|
|
33
|
+
):
|
|
34
|
+
"""Initialize with progress tracking.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
issues_dir: Path to issues directory
|
|
38
|
+
show_progress: Whether to show progress bars
|
|
39
|
+
"""
|
|
40
|
+
self.builder = OptimizedBaselineBuilder(issues_dir)
|
|
41
|
+
self.show_progress = show_progress
|
|
42
|
+
self._progress = None
|
|
43
|
+
self._current_task = None
|
|
44
|
+
|
|
45
|
+
def set_progress_context(self, progress) -> None:
|
|
46
|
+
"""Set progress context from rich.progress.Progress.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
progress: Progress instance from rich library
|
|
50
|
+
"""
|
|
51
|
+
self._progress = progress
|
|
52
|
+
self.builder.set_progress_context(progress)
|
|
53
|
+
|
|
54
|
+
def _log_phase(self, phase: str, details: dict | None = None) -> None:
|
|
55
|
+
"""Log phase transition with progress.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
phase: Name of phase
|
|
59
|
+
details: Optional details dict
|
|
60
|
+
"""
|
|
61
|
+
logger.info(
|
|
62
|
+
"baseline_rebuild_phase",
|
|
63
|
+
phase=phase,
|
|
64
|
+
**(details or {}),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def rebuild_with_progress(
|
|
68
|
+
self,
|
|
69
|
+
all_issue_files: list[Path],
|
|
70
|
+
cached_state: SyncState | None,
|
|
71
|
+
git_ref: str = "HEAD~1",
|
|
72
|
+
) -> tuple[dict[str, Path] | None, list[str] | None, CachedBaselineState]:
|
|
73
|
+
"""Rebuild baseline with progress tracking.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
all_issue_files: All current issue file paths
|
|
77
|
+
cached_state: Previous sync state (if any)
|
|
78
|
+
git_ref: Git reference for change detection
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Tuple of (issues_to_update, deleted_issues, metrics)
|
|
82
|
+
- issues_to_update: {issue_id: path} dict for issues to rebuild
|
|
83
|
+
- deleted_issues: [issue_id] list of deleted issues
|
|
84
|
+
- metrics: CachedBaselineState with rebuild stats
|
|
85
|
+
"""
|
|
86
|
+
start_time = time()
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# Phase 1: Determine rebuild strategy
|
|
90
|
+
self._log_phase(
|
|
91
|
+
"Determining rebuild strategy",
|
|
92
|
+
{"cached": cached_state is not None},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
should_full = self.builder.should_rebuild_all(
|
|
96
|
+
cached_state,
|
|
97
|
+
time_since_last_sync=None,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if should_full:
|
|
101
|
+
return self._rebuild_full_with_progress(all_issue_files, start_time)
|
|
102
|
+
elif cached_state is not None:
|
|
103
|
+
return self._rebuild_incremental_with_progress(
|
|
104
|
+
all_issue_files, cached_state, git_ref, start_time
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# No cached state and not full rebuild - treat as full
|
|
108
|
+
return self._rebuild_full_with_progress(all_issue_files, start_time)
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(
|
|
112
|
+
"baseline_rebuild_error",
|
|
113
|
+
error=str(e),
|
|
114
|
+
error_type=type(e).__name__,
|
|
115
|
+
severity="system_error",
|
|
116
|
+
)
|
|
117
|
+
# Return empty rebuild (will fall back to default)
|
|
118
|
+
return (
|
|
119
|
+
None,
|
|
120
|
+
None,
|
|
121
|
+
CachedBaselineState(
|
|
122
|
+
SyncState(last_sync_time=datetime.now(UTC), base_issues={}),
|
|
123
|
+
from_cache=False,
|
|
124
|
+
rebuilt_issues=0,
|
|
125
|
+
reused_issues=0,
|
|
126
|
+
rebuild_time_ms=time() - start_time,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _rebuild_full_with_progress(
|
|
131
|
+
self,
|
|
132
|
+
all_issue_files: list[Path],
|
|
133
|
+
start_time: float,
|
|
134
|
+
) -> tuple[dict[str, Path], list[str], CachedBaselineState]:
|
|
135
|
+
"""Full rebuild with progress display.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
all_issue_files: All files
|
|
139
|
+
start_time: Rebuild start time
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Tuple of (issues_to_update, [], metrics)
|
|
143
|
+
"""
|
|
144
|
+
self._log_phase("Full rebuild", {"issue_count": len(all_issue_files)})
|
|
145
|
+
|
|
146
|
+
# All issues need rebuilding
|
|
147
|
+
issues_to_update = {}
|
|
148
|
+
for idx, file_path in enumerate(all_issue_files):
|
|
149
|
+
relative = str(file_path.relative_to(self.builder.issues_dir))
|
|
150
|
+
issue_id = self.builder.extract_issue_id_from_path(relative)
|
|
151
|
+
if issue_id:
|
|
152
|
+
issues_to_update[issue_id] = file_path
|
|
153
|
+
|
|
154
|
+
# Log progress every 10 items
|
|
155
|
+
if (idx + 1) % 10 == 0 or idx == 0:
|
|
156
|
+
self._log_phase(
|
|
157
|
+
f"Preparing rebuild ({idx + 1}/{len(all_issue_files)})",
|
|
158
|
+
{"prepared": idx + 1},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
rebuild_time_ms = (time() - start_time) * 1000
|
|
162
|
+
|
|
163
|
+
self._log_phase(
|
|
164
|
+
f"Full rebuild ready ({len(issues_to_update)} issues)",
|
|
165
|
+
{"rebuild_time_ms": f"{rebuild_time_ms:.1f}"},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
issues_to_update,
|
|
170
|
+
[],
|
|
171
|
+
CachedBaselineState(
|
|
172
|
+
SyncState(
|
|
173
|
+
last_sync_time=datetime.now(UTC),
|
|
174
|
+
base_issues={},
|
|
175
|
+
),
|
|
176
|
+
from_cache=False,
|
|
177
|
+
rebuilt_issues=len(issues_to_update),
|
|
178
|
+
reused_issues=0,
|
|
179
|
+
rebuild_time_ms=rebuild_time_ms,
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _rebuild_incremental_with_progress(
|
|
184
|
+
self,
|
|
185
|
+
all_issue_files: list[Path],
|
|
186
|
+
cached_state: SyncState,
|
|
187
|
+
git_ref: str,
|
|
188
|
+
start_time: float,
|
|
189
|
+
) -> tuple[dict[str, Path], list[str], CachedBaselineState]:
|
|
190
|
+
"""Incremental rebuild with progress display.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
all_issue_files: All files
|
|
194
|
+
cached_state: Previous state
|
|
195
|
+
git_ref: Git reference
|
|
196
|
+
start_time: Rebuild start time
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Tuple of (issues_to_update, deleted_issues, metrics)
|
|
200
|
+
"""
|
|
201
|
+
self._log_phase("Detecting file changes", {"git_ref": git_ref})
|
|
202
|
+
|
|
203
|
+
# Phase 2: Get changed files
|
|
204
|
+
changed_files = self.builder.get_changed_issue_files(git_ref)
|
|
205
|
+
self._log_phase(
|
|
206
|
+
"File changes detected",
|
|
207
|
+
{"changed_count": len(changed_files)},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Phase 3: Determine which issues need updates
|
|
211
|
+
self._log_phase(
|
|
212
|
+
f"Analyzing {len(all_issue_files)} issues",
|
|
213
|
+
{"cached_count": len(cached_state.base_issues)},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
issues_to_update, deleted_issues = self.builder.get_incremental_update_issues(
|
|
217
|
+
all_issue_files, changed_files, cached_state
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
rebuild_time_ms = (time() - start_time) * 1000
|
|
221
|
+
reused_count = len(cached_state.base_issues) - len(issues_to_update)
|
|
222
|
+
|
|
223
|
+
self._log_phase(
|
|
224
|
+
"Rebuild analysis complete",
|
|
225
|
+
{
|
|
226
|
+
"rebuild_count": len(issues_to_update),
|
|
227
|
+
"reused_count": reused_count,
|
|
228
|
+
"deleted_count": len(deleted_issues),
|
|
229
|
+
"rebuild_time_ms": f"{rebuild_time_ms:.1f}",
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
issues_to_update,
|
|
235
|
+
deleted_issues,
|
|
236
|
+
CachedBaselineState(
|
|
237
|
+
SyncState(
|
|
238
|
+
last_sync_time=cached_state.last_sync_time,
|
|
239
|
+
base_issues=cached_state.base_issues,
|
|
240
|
+
),
|
|
241
|
+
from_cache=False,
|
|
242
|
+
rebuilt_issues=len(issues_to_update),
|
|
243
|
+
reused_issues=reused_count,
|
|
244
|
+
rebuild_time_ms=rebuild_time_ms,
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def create_progress_builder(
|
|
250
|
+
issues_dir: Path,
|
|
251
|
+
show_progress: bool = True,
|
|
252
|
+
) -> ProgressTrackingBaselineBuilder | None:
|
|
253
|
+
"""Create a progress-enabled baseline builder.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
issues_dir: Path to issues directory
|
|
257
|
+
show_progress: Whether to show progress
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
ProgressTrackingBaselineBuilder or None if progress not supported
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> from rich.progress import Progress
|
|
264
|
+
>>> builder = create_progress_builder(Path("roadmap/issues"))
|
|
265
|
+
>>> with Progress() as progress:
|
|
266
|
+
... builder.set_progress_context(progress)
|
|
267
|
+
... updates, deleted, metrics = builder.rebuild_with_progress(files, state)
|
|
268
|
+
... print(metrics.to_dict())
|
|
269
|
+
"""
|
|
270
|
+
if not show_progress:
|
|
271
|
+
logger.debug("progress_bars_disabled")
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
return ProgressTrackingBaselineBuilder(issues_dir, show_progress)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Baseline snapshot retrieval from remote sources."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
BaselineSnapshot = dict[str, Any]
|
|
8
|
+
|
|
9
|
+
logger = structlog.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaselineRetriever:
|
|
13
|
+
"""Retrieve the current sync baseline from available stores.
|
|
14
|
+
|
|
15
|
+
Prefers `core.db.get_sync_baseline()` when available, then a provided
|
|
16
|
+
`db_session.get_sync_baseline()`. If neither is present, returns an
|
|
17
|
+
empty baseline snapshot.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_current_baseline(
|
|
22
|
+
core: Any = None, db_session: Any = None
|
|
23
|
+
) -> BaselineSnapshot:
|
|
24
|
+
"""Get the current sync baseline from available stores.
|
|
25
|
+
|
|
26
|
+
Tries multiple sources in order: core.db, db_session, or empty dict.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
core: Optional core instance with db attribute.
|
|
30
|
+
db_session: Optional database session.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Baseline snapshot dictionary.
|
|
34
|
+
"""
|
|
35
|
+
if (
|
|
36
|
+
core is not None
|
|
37
|
+
and getattr(core, "db", None)
|
|
38
|
+
and hasattr(core.db, "get_sync_baseline")
|
|
39
|
+
):
|
|
40
|
+
try:
|
|
41
|
+
return core.db.get_sync_baseline() or {}
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.debug(
|
|
44
|
+
"get_sync_baseline_failed",
|
|
45
|
+
operation="get_sync_baseline",
|
|
46
|
+
error=str(e),
|
|
47
|
+
action="Using fallback empty dict",
|
|
48
|
+
)
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
if db_session is not None and hasattr(db_session, "get_sync_baseline"):
|
|
52
|
+
try:
|
|
53
|
+
return db_session.get_sync_baseline() or {}
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.debug(
|
|
56
|
+
"get_sync_baseline_db_failed",
|
|
57
|
+
operation="get_sync_baseline_db_session",
|
|
58
|
+
error=str(e),
|
|
59
|
+
action="Using empty baseline",
|
|
60
|
+
)
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
return {}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Interactive baseline selection utility for team onboarding.
|
|
2
|
+
|
|
3
|
+
Provides guidance and interactive selection for teams creating their
|
|
4
|
+
initial baseline during first sync. Ensures explicit, informed choices
|
|
5
|
+
about the agreed-upon starting state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from structlog import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaselineStrategy(StrEnum):
|
|
17
|
+
"""Strategy for creating initial baseline."""
|
|
18
|
+
|
|
19
|
+
LOCAL = "local"
|
|
20
|
+
"""Use local state as baseline - local code is source of truth."""
|
|
21
|
+
|
|
22
|
+
REMOTE = "remote"
|
|
23
|
+
"""Use remote state as baseline - remote system is source of truth."""
|
|
24
|
+
|
|
25
|
+
INTERACTIVE = "interactive"
|
|
26
|
+
"""Let user choose per-issue which state to use as baseline."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaselineSelectionResult:
|
|
30
|
+
"""Result of baseline selection for one or more issues."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
strategy: BaselineStrategy,
|
|
35
|
+
selected_issues: dict[str, str] | None = None,
|
|
36
|
+
):
|
|
37
|
+
"""Initialize result.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
strategy: The strategy chosen (LOCAL, REMOTE, or INTERACTIVE)
|
|
41
|
+
selected_issues: For INTERACTIVE, dict of issue_id -> "local" or "remote"
|
|
42
|
+
"""
|
|
43
|
+
self.strategy = strategy
|
|
44
|
+
self.selected_issues = selected_issues or {}
|
|
45
|
+
|
|
46
|
+
def get_baseline_for_issue(
|
|
47
|
+
self,
|
|
48
|
+
issue_id: str,
|
|
49
|
+
local_issue: Any,
|
|
50
|
+
remote_issue: Any,
|
|
51
|
+
) -> Any:
|
|
52
|
+
"""Get the issue state to use as baseline.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
issue_id: The issue ID
|
|
56
|
+
local_issue: The local issue object
|
|
57
|
+
remote_issue: The remote issue object
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The issue object/dict to use as baseline
|
|
61
|
+
"""
|
|
62
|
+
if self.strategy == BaselineStrategy.LOCAL:
|
|
63
|
+
return local_issue
|
|
64
|
+
elif self.strategy == BaselineStrategy.REMOTE:
|
|
65
|
+
return remote_issue
|
|
66
|
+
elif self.strategy == BaselineStrategy.INTERACTIVE:
|
|
67
|
+
choice = self.selected_issues.get(issue_id, "local")
|
|
68
|
+
return local_issue if choice == "local" else remote_issue
|
|
69
|
+
|
|
70
|
+
return local_issue # Default to local
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class InteractiveBaselineSelector:
|
|
74
|
+
"""Guides teams through baseline selection during first sync."""
|
|
75
|
+
|
|
76
|
+
def __init__(self, console_output_fn=None):
|
|
77
|
+
"""Initialize selector.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
console_output_fn: Function to use for output (for testing)
|
|
81
|
+
"""
|
|
82
|
+
self.console_output_fn = console_output_fn or self._default_output
|
|
83
|
+
self.logger = get_logger()
|
|
84
|
+
|
|
85
|
+
def _default_output(self, message: str):
|
|
86
|
+
"""Output message to console."""
|
|
87
|
+
print(message)
|
|
88
|
+
|
|
89
|
+
def prompt_baseline_strategy(self) -> BaselineStrategy:
|
|
90
|
+
"""Prompt team to choose baseline strategy.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Selected BaselineStrategy
|
|
94
|
+
"""
|
|
95
|
+
self.logger.info("baseline_strategy_prompt")
|
|
96
|
+
|
|
97
|
+
self.console_output_fn("\n" + "=" * 60)
|
|
98
|
+
self.console_output_fn("BASELINE SELECTION - First Sync Configuration")
|
|
99
|
+
self.console_output_fn("=" * 60)
|
|
100
|
+
self.console_output_fn(
|
|
101
|
+
"""
|
|
102
|
+
This is your first sync. We need to establish the agreed-upon baseline state
|
|
103
|
+
that both local and remote systems accept as the starting point.
|
|
104
|
+
|
|
105
|
+
Choose one of these strategies:
|
|
106
|
+
|
|
107
|
+
1. LOCAL - Use your local code as the baseline
|
|
108
|
+
Use this if: Local code is your source of truth
|
|
109
|
+
Effect: Remote will pull all local changes
|
|
110
|
+
Risk: May overwrite remote-only changes
|
|
111
|
+
|
|
112
|
+
2. REMOTE - Use the remote system as the baseline
|
|
113
|
+
Use this if: Remote system is your source of truth
|
|
114
|
+
Effect: Local will pull all remote changes
|
|
115
|
+
Risk: May overwrite local-only changes
|
|
116
|
+
|
|
117
|
+
3. INTERACTIVE - Choose per-issue which is the baseline
|
|
118
|
+
Use this if: Different issues have different sources of truth
|
|
119
|
+
Effect: You'll be asked for each changed issue
|
|
120
|
+
Risk: Most time-consuming but most flexible
|
|
121
|
+
"""
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
while True:
|
|
125
|
+
choice = input("\nEnter your choice (1, 2, or 3): ").strip()
|
|
126
|
+
if choice == "1":
|
|
127
|
+
self.console_output_fn("✓ Selected: LOCAL baseline strategy")
|
|
128
|
+
self.logger.info("baseline_strategy_selected", strategy="local")
|
|
129
|
+
return BaselineStrategy.LOCAL
|
|
130
|
+
elif choice == "2":
|
|
131
|
+
self.console_output_fn("✓ Selected: REMOTE baseline strategy")
|
|
132
|
+
self.logger.info("baseline_strategy_selected", strategy="remote")
|
|
133
|
+
return BaselineStrategy.REMOTE
|
|
134
|
+
elif choice == "3":
|
|
135
|
+
self.console_output_fn("✓ Selected: INTERACTIVE baseline strategy")
|
|
136
|
+
self.logger.info("baseline_strategy_selected", strategy="interactive")
|
|
137
|
+
return BaselineStrategy.INTERACTIVE
|
|
138
|
+
else:
|
|
139
|
+
self.console_output_fn("Invalid choice. Please enter 1, 2, or 3.")
|
|
140
|
+
|
|
141
|
+
def prompt_interactive_selections(
|
|
142
|
+
self,
|
|
143
|
+
issues_with_changes: list[dict[str, Any]],
|
|
144
|
+
) -> dict[str, str]:
|
|
145
|
+
"""Prompt user to select baseline for each changed issue.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
issues_with_changes: List of issues with dicts containing:
|
|
149
|
+
- issue_id: str
|
|
150
|
+
- title: str
|
|
151
|
+
- local_changes: dict or None
|
|
152
|
+
- remote_changes: dict or None
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Dict of issue_id -> "local" or "remote"
|
|
156
|
+
"""
|
|
157
|
+
self.logger.info(
|
|
158
|
+
"interactive_baseline_selection_start",
|
|
159
|
+
issue_count=len(issues_with_changes),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
selections = {}
|
|
163
|
+
|
|
164
|
+
self.console_output_fn("\n" + "=" * 60)
|
|
165
|
+
self.console_output_fn("INTERACTIVE BASELINE SELECTION")
|
|
166
|
+
self.console_output_fn("=" * 60)
|
|
167
|
+
self.console_output_fn(
|
|
168
|
+
f"\nFor each of {len(issues_with_changes)} changed issues, "
|
|
169
|
+
"choose which is the baseline:\n"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for idx, issue in enumerate(issues_with_changes, 1):
|
|
173
|
+
issue_id = issue.get("issue_id", "unknown")
|
|
174
|
+
title = issue.get("title", "Untitled")
|
|
175
|
+
local_changes = issue.get("local_changes", {})
|
|
176
|
+
remote_changes = issue.get("remote_changes", {})
|
|
177
|
+
|
|
178
|
+
self.console_output_fn(f"\n[{idx}/{len(issues_with_changes)}] {title}")
|
|
179
|
+
self.console_output_fn(f" ID: {issue_id}")
|
|
180
|
+
|
|
181
|
+
if local_changes:
|
|
182
|
+
self.console_output_fn(f" Local changes: {local_changes}")
|
|
183
|
+
if remote_changes:
|
|
184
|
+
self.console_output_fn(f" Remote changes: {remote_changes}")
|
|
185
|
+
|
|
186
|
+
choice = None
|
|
187
|
+
while choice is None:
|
|
188
|
+
response = (
|
|
189
|
+
input("\n Use LOCAL or REMOTE as baseline? (L/R): ")
|
|
190
|
+
.strip()
|
|
191
|
+
.upper()
|
|
192
|
+
)
|
|
193
|
+
if response in ("L", "LOCAL"):
|
|
194
|
+
choice = "local"
|
|
195
|
+
self.console_output_fn(" → Using LOCAL as baseline")
|
|
196
|
+
elif response in ("R", "REMOTE"):
|
|
197
|
+
choice = "remote"
|
|
198
|
+
self.console_output_fn(" → Using REMOTE as baseline")
|
|
199
|
+
else:
|
|
200
|
+
self.console_output_fn(" Invalid choice. Enter L or R.")
|
|
201
|
+
|
|
202
|
+
selections[issue_id] = choice
|
|
203
|
+
self.logger.debug(
|
|
204
|
+
"interactive_baseline_selected",
|
|
205
|
+
issue_id=issue_id,
|
|
206
|
+
choice=choice,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
self.console_output_fn("\n✓ Baseline selection complete\n")
|
|
210
|
+
self.logger.info(
|
|
211
|
+
"interactive_baseline_selection_complete",
|
|
212
|
+
selections=selections,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return selections
|
|
216
|
+
|
|
217
|
+
def select_baseline(
|
|
218
|
+
self,
|
|
219
|
+
issues_with_changes: list[dict[str, Any]] | None = None,
|
|
220
|
+
strategy: BaselineStrategy | None = None,
|
|
221
|
+
) -> BaselineSelectionResult:
|
|
222
|
+
"""Guide complete baseline selection process.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
issues_with_changes: Issues with changes (for interactive mode)
|
|
226
|
+
strategy: Override strategy (for testing/automation)
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
BaselineSelectionResult with strategy and selections
|
|
230
|
+
"""
|
|
231
|
+
self.logger.info("baseline_selection_start")
|
|
232
|
+
|
|
233
|
+
# Get strategy if not provided
|
|
234
|
+
if strategy is None:
|
|
235
|
+
strategy = self.prompt_baseline_strategy()
|
|
236
|
+
|
|
237
|
+
# Get specific selections if interactive
|
|
238
|
+
selected_issues = {}
|
|
239
|
+
if strategy == BaselineStrategy.INTERACTIVE and issues_with_changes:
|
|
240
|
+
selected_issues = self.prompt_interactive_selections(issues_with_changes)
|
|
241
|
+
|
|
242
|
+
result = BaselineSelectionResult(
|
|
243
|
+
strategy=strategy,
|
|
244
|
+
selected_issues=selected_issues,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
self.logger.info("baseline_selection_complete", strategy=strategy.value)
|
|
248
|
+
return result
|