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,168 @@
|
|
|
1
|
+
"""Repository for project persistence operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from roadmap.common.errors import OperationType, safe_operation
|
|
6
|
+
from roadmap.common.logging import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectRepository:
|
|
12
|
+
"""Handles all project-related database operations."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, get_connection, transaction):
|
|
15
|
+
"""Initialize repository with database connection methods.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
get_connection: Callable that returns sqlite3 Connection
|
|
19
|
+
transaction: Context manager for database transactions
|
|
20
|
+
"""
|
|
21
|
+
self._get_connection = get_connection
|
|
22
|
+
self._transaction = transaction
|
|
23
|
+
|
|
24
|
+
@safe_operation(OperationType.CREATE, "Project", include_traceback=True)
|
|
25
|
+
@safe_operation(OperationType.CREATE, "Project", include_traceback=True)
|
|
26
|
+
def create(self, project_data: dict[str, Any]) -> str:
|
|
27
|
+
"""Create a new project.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
project_data: Dictionary with project fields
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Project ID
|
|
34
|
+
"""
|
|
35
|
+
with self._transaction() as conn:
|
|
36
|
+
conn.execute(
|
|
37
|
+
"""
|
|
38
|
+
INSERT INTO projects (id, name, description, status, metadata)
|
|
39
|
+
VALUES (?, ?, ?, ?, ?)
|
|
40
|
+
""",
|
|
41
|
+
(
|
|
42
|
+
project_data["id"],
|
|
43
|
+
project_data["name"],
|
|
44
|
+
project_data.get("description"),
|
|
45
|
+
project_data.get("status", "active"),
|
|
46
|
+
project_data.get("metadata"),
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
logger.info("Created project", project_id=project_data["id"])
|
|
51
|
+
return project_data["id"]
|
|
52
|
+
|
|
53
|
+
def get(self, project_id: str) -> dict[str, Any] | None:
|
|
54
|
+
"""Get project by ID.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
project_id: Project identifier
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Project data or None if not found
|
|
61
|
+
"""
|
|
62
|
+
conn = self._get_connection()
|
|
63
|
+
row = conn.execute(
|
|
64
|
+
"SELECT * FROM projects WHERE id = ?", (project_id,)
|
|
65
|
+
).fetchone()
|
|
66
|
+
|
|
67
|
+
return dict(row) if row else None
|
|
68
|
+
|
|
69
|
+
def list_all(self) -> list[dict[str, Any]]:
|
|
70
|
+
"""List all projects.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of project data dictionaries
|
|
74
|
+
"""
|
|
75
|
+
conn = self._get_connection()
|
|
76
|
+
rows = conn.execute(
|
|
77
|
+
"SELECT * FROM projects ORDER BY created_at DESC"
|
|
78
|
+
).fetchall()
|
|
79
|
+
return [dict(row) for row in rows]
|
|
80
|
+
|
|
81
|
+
@safe_operation(OperationType.UPDATE, "Project")
|
|
82
|
+
@safe_operation(OperationType.UPDATE, "Project")
|
|
83
|
+
def update(self, project_id: str, updates: dict[str, Any]) -> bool:
|
|
84
|
+
"""Update project.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
project_id: Project identifier
|
|
88
|
+
updates: Dictionary of fields to update
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if successful, False otherwise
|
|
92
|
+
"""
|
|
93
|
+
if not updates:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
set_clause = ", ".join(f"{key} = ?" for key in updates.keys())
|
|
97
|
+
values = list(updates.values()) + [project_id]
|
|
98
|
+
|
|
99
|
+
with self._transaction() as conn:
|
|
100
|
+
cursor = conn.execute(
|
|
101
|
+
f"""
|
|
102
|
+
UPDATE projects SET {set_clause} WHERE id = ?
|
|
103
|
+
""",
|
|
104
|
+
values,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
updated = cursor.rowcount > 0
|
|
108
|
+
if updated:
|
|
109
|
+
logger.info(
|
|
110
|
+
"Updated project", project_id=project_id, updates=list(updates.keys())
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return updated
|
|
114
|
+
|
|
115
|
+
@safe_operation(OperationType.DELETE, "Project", include_traceback=True)
|
|
116
|
+
@safe_operation(OperationType.DELETE, "Project", include_traceback=True)
|
|
117
|
+
def delete(self, project_id: str) -> bool:
|
|
118
|
+
"""Delete project and all related data.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
project_id: Project identifier
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if successful, False otherwise
|
|
125
|
+
"""
|
|
126
|
+
with self._transaction() as conn:
|
|
127
|
+
cursor = conn.execute("DELETE FROM projects WHERE id = ?", (project_id,))
|
|
128
|
+
|
|
129
|
+
deleted = cursor.rowcount > 0
|
|
130
|
+
if deleted:
|
|
131
|
+
logger.info("Deleted project", project_id=project_id)
|
|
132
|
+
|
|
133
|
+
return deleted
|
|
134
|
+
|
|
135
|
+
@safe_operation(OperationType.UPDATE, "Project")
|
|
136
|
+
@safe_operation(OperationType.UPDATE, "Project")
|
|
137
|
+
def mark_archived(self, project_id: str, archived: bool = True) -> bool:
|
|
138
|
+
"""Mark a project as archived or unarchived.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
project_id: Project identifier
|
|
142
|
+
archived: True to archive, False to unarchive
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if successful, False otherwise
|
|
146
|
+
"""
|
|
147
|
+
with self._transaction() as conn:
|
|
148
|
+
if archived:
|
|
149
|
+
cursor = conn.execute(
|
|
150
|
+
"UPDATE projects SET archived = 1, archived_at = CURRENT_TIMESTAMP WHERE id = ?",
|
|
151
|
+
(project_id,),
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
cursor = conn.execute(
|
|
155
|
+
"UPDATE projects SET archived = 0, archived_at = NULL WHERE id = ?",
|
|
156
|
+
(project_id,),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
updated = cursor.rowcount > 0
|
|
160
|
+
if updated:
|
|
161
|
+
action = "archived" if archived else "unarchived"
|
|
162
|
+
logger.info(
|
|
163
|
+
"project_status_changed",
|
|
164
|
+
project_id=project_id,
|
|
165
|
+
action=action,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return updated
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""Repository for managing issue-to-backend remote ID mappings.
|
|
2
|
+
|
|
3
|
+
Provides fast lookups and mutations for remote backend IDs stored in the
|
|
4
|
+
issue_remote_links database table. This enables efficient sync operations
|
|
5
|
+
without scanning YAML files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from roadmap.common.logging import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RemoteLinkRepository:
|
|
14
|
+
"""Handles issue remote link database operations.
|
|
15
|
+
|
|
16
|
+
Provides CRUD operations for tracking which issues are linked to
|
|
17
|
+
which remote backend IDs (e.g., GitHub issue numbers, GitLab MRs, etc).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, get_connection, transaction):
|
|
21
|
+
"""Initialize repository with database connection methods.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
get_connection: Callable that returns sqlite3 Connection
|
|
25
|
+
transaction: Context manager for database transactions
|
|
26
|
+
"""
|
|
27
|
+
self._get_connection = get_connection
|
|
28
|
+
self._transaction = transaction
|
|
29
|
+
|
|
30
|
+
def link_issue(
|
|
31
|
+
self, issue_uuid: str, backend_name: str, remote_id: str | int
|
|
32
|
+
) -> bool:
|
|
33
|
+
"""Link an issue to a remote backend ID.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
issue_uuid: Local issue UUID
|
|
37
|
+
backend_name: Backend name (e.g., 'github', 'gitlab')
|
|
38
|
+
remote_id: Remote ID in that backend (string or int)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if link created/updated, False if error
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
remote_id_str = str(remote_id)
|
|
45
|
+
with self._transaction() as conn:
|
|
46
|
+
conn.execute(
|
|
47
|
+
"""
|
|
48
|
+
INSERT OR REPLACE INTO issue_remote_links
|
|
49
|
+
(issue_uuid, backend_name, remote_id)
|
|
50
|
+
VALUES (?, ?, ?)
|
|
51
|
+
""",
|
|
52
|
+
(issue_uuid, backend_name, remote_id_str),
|
|
53
|
+
)
|
|
54
|
+
logger.debug(
|
|
55
|
+
"Linked issue to remote backend",
|
|
56
|
+
issue_uuid=issue_uuid,
|
|
57
|
+
backend_name=backend_name,
|
|
58
|
+
remote_id=remote_id_str,
|
|
59
|
+
)
|
|
60
|
+
return True
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.warning(
|
|
63
|
+
"Failed to link issue",
|
|
64
|
+
issue_uuid=issue_uuid,
|
|
65
|
+
backend_name=backend_name,
|
|
66
|
+
remote_id=str(remote_id),
|
|
67
|
+
error_type=type(e).__name__,
|
|
68
|
+
error_message=str(e),
|
|
69
|
+
error_details=repr(e),
|
|
70
|
+
severity="operational",
|
|
71
|
+
)
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
def unlink_issue(self, issue_uuid: str, backend_name: str) -> bool:
|
|
75
|
+
"""Unlink an issue from a remote backend.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
issue_uuid: Local issue UUID
|
|
79
|
+
backend_name: Backend name
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if unlinked, False if error or not found
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
with self._transaction() as conn:
|
|
86
|
+
cursor = conn.execute(
|
|
87
|
+
"""
|
|
88
|
+
DELETE FROM issue_remote_links
|
|
89
|
+
WHERE issue_uuid = ? AND backend_name = ?
|
|
90
|
+
""",
|
|
91
|
+
(issue_uuid, backend_name),
|
|
92
|
+
)
|
|
93
|
+
if cursor.rowcount > 0:
|
|
94
|
+
logger.debug(
|
|
95
|
+
"Unlinked issue from remote backend",
|
|
96
|
+
issue_uuid=issue_uuid,
|
|
97
|
+
backend_name=backend_name,
|
|
98
|
+
)
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.warning(
|
|
103
|
+
"Failed to unlink issue",
|
|
104
|
+
issue_uuid=issue_uuid,
|
|
105
|
+
backend_name=backend_name,
|
|
106
|
+
error_type=type(e).__name__,
|
|
107
|
+
error_message=str(e),
|
|
108
|
+
severity="operational",
|
|
109
|
+
)
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def get_remote_id(self, issue_uuid: str, backend_name: str) -> str | int | None:
|
|
113
|
+
"""Get the remote ID for an issue on a specific backend.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
issue_uuid: Local issue UUID
|
|
117
|
+
backend_name: Backend name (e.g., 'github')
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Remote ID (as string) or None if not linked
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
conn = self._get_connection()
|
|
124
|
+
row = conn.execute(
|
|
125
|
+
"""
|
|
126
|
+
SELECT remote_id FROM issue_remote_links
|
|
127
|
+
WHERE issue_uuid = ? AND backend_name = ?
|
|
128
|
+
""",
|
|
129
|
+
(issue_uuid, backend_name),
|
|
130
|
+
).fetchone()
|
|
131
|
+
if row:
|
|
132
|
+
# Try to convert to int if it looks like one
|
|
133
|
+
remote_id = row["remote_id"]
|
|
134
|
+
try:
|
|
135
|
+
return int(remote_id)
|
|
136
|
+
except (ValueError, TypeError):
|
|
137
|
+
return remote_id
|
|
138
|
+
return None
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.warning(
|
|
141
|
+
"Failed to get remote ID",
|
|
142
|
+
issue_uuid=issue_uuid,
|
|
143
|
+
backend_name=backend_name,
|
|
144
|
+
error_type=type(e).__name__,
|
|
145
|
+
error_message=str(e),
|
|
146
|
+
severity="operational",
|
|
147
|
+
)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def get_issue_uuid(self, backend_name: str, remote_id: str | int) -> str | None:
|
|
151
|
+
"""Reverse lookup: get local issue UUID from remote backend ID.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
backend_name: Backend name (e.g., 'github')
|
|
155
|
+
remote_id: Remote ID in that backend
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Issue UUID or None if not found
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
remote_id_str = str(remote_id)
|
|
162
|
+
conn = self._get_connection()
|
|
163
|
+
row = conn.execute(
|
|
164
|
+
"""
|
|
165
|
+
SELECT issue_uuid FROM issue_remote_links
|
|
166
|
+
WHERE backend_name = ? AND remote_id = ?
|
|
167
|
+
""",
|
|
168
|
+
(backend_name, remote_id_str),
|
|
169
|
+
).fetchone()
|
|
170
|
+
if row:
|
|
171
|
+
return row["issue_uuid"]
|
|
172
|
+
return None
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.warning(
|
|
175
|
+
"Failed to get issue UUID",
|
|
176
|
+
backend_name=backend_name,
|
|
177
|
+
remote_id=str(remote_id),
|
|
178
|
+
error_type=type(e).__name__,
|
|
179
|
+
error_message=str(e),
|
|
180
|
+
)
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def get_all_links_for_issue(self, issue_uuid: str) -> dict[str, str | int]:
|
|
184
|
+
"""Get all remote links for an issue (all backends).
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
issue_uuid: Local issue UUID
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Dict mapping backend_name -> remote_id
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
conn = self._get_connection()
|
|
194
|
+
rows = conn.execute(
|
|
195
|
+
"""
|
|
196
|
+
SELECT backend_name, remote_id FROM issue_remote_links
|
|
197
|
+
WHERE issue_uuid = ?
|
|
198
|
+
""",
|
|
199
|
+
(issue_uuid,),
|
|
200
|
+
).fetchall()
|
|
201
|
+
result = {}
|
|
202
|
+
for row in rows:
|
|
203
|
+
backend_name = row["backend_name"]
|
|
204
|
+
remote_id_str = row["remote_id"]
|
|
205
|
+
# Try to convert to int if it looks like one
|
|
206
|
+
try:
|
|
207
|
+
result[backend_name] = int(remote_id_str)
|
|
208
|
+
except (ValueError, TypeError):
|
|
209
|
+
result[backend_name] = remote_id_str
|
|
210
|
+
return result
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.warning(
|
|
213
|
+
"Failed to get all links for issue",
|
|
214
|
+
issue_uuid=issue_uuid,
|
|
215
|
+
error_type=type(e).__name__,
|
|
216
|
+
error_message=str(e),
|
|
217
|
+
)
|
|
218
|
+
return {}
|
|
219
|
+
|
|
220
|
+
def get_all_links_for_backend(self, backend_name: str) -> dict[str, str | int]:
|
|
221
|
+
"""Get all remote links for a specific backend.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
backend_name: Backend name (e.g., 'github')
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dict mapping issue_uuid -> remote_id
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
conn = self._get_connection()
|
|
231
|
+
rows = conn.execute(
|
|
232
|
+
"""
|
|
233
|
+
SELECT issue_uuid, remote_id FROM issue_remote_links
|
|
234
|
+
WHERE backend_name = ?
|
|
235
|
+
""",
|
|
236
|
+
(backend_name,),
|
|
237
|
+
).fetchall()
|
|
238
|
+
result = {}
|
|
239
|
+
for row in rows:
|
|
240
|
+
issue_uuid = row["issue_uuid"]
|
|
241
|
+
remote_id_str = row["remote_id"]
|
|
242
|
+
# Try to convert to int if it looks like one
|
|
243
|
+
try:
|
|
244
|
+
result[issue_uuid] = int(remote_id_str)
|
|
245
|
+
except (ValueError, TypeError):
|
|
246
|
+
result[issue_uuid] = remote_id_str
|
|
247
|
+
return result
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.warning(
|
|
250
|
+
"Failed to get all links for backend",
|
|
251
|
+
backend_name=backend_name,
|
|
252
|
+
error_type=type(e).__name__,
|
|
253
|
+
error_message=str(e),
|
|
254
|
+
)
|
|
255
|
+
return {}
|
|
256
|
+
|
|
257
|
+
def validate_link(
|
|
258
|
+
self, issue_uuid: str, backend_name: str, remote_id: str | int
|
|
259
|
+
) -> bool:
|
|
260
|
+
"""Check if a link is valid (exists and matches remote_id).
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
issue_uuid: Local issue UUID
|
|
264
|
+
backend_name: Backend name
|
|
265
|
+
remote_id: Expected remote ID
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if link exists and matches, False otherwise
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
existing_id = self.get_remote_id(issue_uuid, backend_name)
|
|
272
|
+
if existing_id is None:
|
|
273
|
+
return False
|
|
274
|
+
# Compare as strings to handle int/str variations
|
|
275
|
+
return str(existing_id) == str(remote_id)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.warning(
|
|
278
|
+
"Failed to validate link",
|
|
279
|
+
issue_uuid=issue_uuid,
|
|
280
|
+
backend_name=backend_name,
|
|
281
|
+
error_type=type(e).__name__,
|
|
282
|
+
error_message=str(e),
|
|
283
|
+
)
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
def bulk_import_from_yaml(
|
|
287
|
+
self, issues_with_remote_ids: dict[str, dict[str, str | int]]
|
|
288
|
+
) -> int:
|
|
289
|
+
"""Bulk import remote links from Issue objects with remote_ids.
|
|
290
|
+
|
|
291
|
+
This is typically called during initialization to sync existing
|
|
292
|
+
remote_ids from YAML files into the database.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
issues_with_remote_ids: Dict mapping issue_uuid -> dict of backend_name -> remote_id
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Number of links successfully imported
|
|
299
|
+
"""
|
|
300
|
+
count = 0
|
|
301
|
+
try:
|
|
302
|
+
with self._transaction() as conn:
|
|
303
|
+
for issue_uuid, remote_ids_dict in issues_with_remote_ids.items():
|
|
304
|
+
if not remote_ids_dict:
|
|
305
|
+
continue
|
|
306
|
+
for backend_name, remote_id in remote_ids_dict.items():
|
|
307
|
+
try:
|
|
308
|
+
remote_id_str = str(remote_id)
|
|
309
|
+
conn.execute(
|
|
310
|
+
"""
|
|
311
|
+
INSERT OR IGNORE INTO issue_remote_links
|
|
312
|
+
(issue_uuid, backend_name, remote_id)
|
|
313
|
+
VALUES (?, ?, ?)
|
|
314
|
+
""",
|
|
315
|
+
(issue_uuid, backend_name, remote_id_str),
|
|
316
|
+
)
|
|
317
|
+
count += 1
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.debug(
|
|
320
|
+
"Failed to import link",
|
|
321
|
+
issue_uuid=issue_uuid,
|
|
322
|
+
backend_name=backend_name,
|
|
323
|
+
remote_id=remote_id_str,
|
|
324
|
+
error_type=type(e).__name__,
|
|
325
|
+
error_message=str(e),
|
|
326
|
+
)
|
|
327
|
+
logger.info("Bulk imported remote links", count=count)
|
|
328
|
+
return count
|
|
329
|
+
except Exception as e:
|
|
330
|
+
logger.error(
|
|
331
|
+
"Bulk import of remote links failed",
|
|
332
|
+
count_imported=count,
|
|
333
|
+
error_type=type(e).__name__,
|
|
334
|
+
error_message=str(e),
|
|
335
|
+
)
|
|
336
|
+
return count
|
|
337
|
+
|
|
338
|
+
def clear_all(self) -> bool:
|
|
339
|
+
"""Clear all remote links (useful for resetting/testing).
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
True if successful, False if error
|
|
343
|
+
"""
|
|
344
|
+
try:
|
|
345
|
+
with self._transaction() as conn:
|
|
346
|
+
conn.execute("DELETE FROM issue_remote_links")
|
|
347
|
+
logger.info("Cleared all remote links")
|
|
348
|
+
return True
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(
|
|
351
|
+
"Failed to clear all remote links",
|
|
352
|
+
error_type=type(e).__name__,
|
|
353
|
+
error_message=str(e),
|
|
354
|
+
)
|
|
355
|
+
return False
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Repository for sync state persistence operations."""
|
|
2
|
+
|
|
3
|
+
from roadmap.common.logging import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SyncStateRepository:
|
|
9
|
+
"""Handles all sync state database operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, get_connection, transaction):
|
|
12
|
+
"""Initialize repository with database connection methods.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
get_connection: Callable that returns sqlite3 Connection
|
|
16
|
+
transaction: Context manager for database transactions
|
|
17
|
+
"""
|
|
18
|
+
self._get_connection = get_connection
|
|
19
|
+
self._transaction = transaction
|
|
20
|
+
|
|
21
|
+
def get(self, key: str) -> str | None:
|
|
22
|
+
"""Get sync state value by key.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
key: State key
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
State value or None if not found
|
|
29
|
+
"""
|
|
30
|
+
conn = self._get_connection()
|
|
31
|
+
row = conn.execute(
|
|
32
|
+
"SELECT value FROM sync_metadata WHERE key = ?", (key,)
|
|
33
|
+
).fetchone()
|
|
34
|
+
return row["value"] if row else None
|
|
35
|
+
|
|
36
|
+
def set(self, key: str, value: str) -> None:
|
|
37
|
+
"""Set sync state value.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
key: State key
|
|
41
|
+
value: State value
|
|
42
|
+
"""
|
|
43
|
+
with self._transaction() as conn:
|
|
44
|
+
conn.execute(
|
|
45
|
+
"""
|
|
46
|
+
INSERT OR REPLACE INTO sync_metadata (key, value) VALUES (?, ?)
|
|
47
|
+
""",
|
|
48
|
+
(key, value),
|
|
49
|
+
)
|
|
50
|
+
logger.debug("Set sync state", key=key)
|
|
51
|
+
|
|
52
|
+
def delete(self, key: str) -> None:
|
|
53
|
+
"""Delete sync state value.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
key: State key
|
|
57
|
+
"""
|
|
58
|
+
with self._transaction() as conn:
|
|
59
|
+
conn.execute("DELETE FROM sync_metadata WHERE key = ?", (key,))
|
|
60
|
+
logger.debug("Deleted sync state", key=key)
|
|
61
|
+
|
|
62
|
+
def clear_all(self) -> None:
|
|
63
|
+
"""Clear all sync state values."""
|
|
64
|
+
with self._transaction() as conn:
|
|
65
|
+
conn.execute("DELETE FROM sync_metadata")
|
|
66
|
+
logger.info("Cleared all sync state")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Storage layer with modularized state management.
|
|
2
|
+
|
|
3
|
+
Organizes large query and conflict operations into separate service files:
|
|
4
|
+
- state_manager.py: Main StateManager facade (290 LOC after refactor)
|
|
5
|
+
- connection_manager.py: Database connection and transaction management (60 LOC)
|
|
6
|
+
- project_storage.py: Project CRUD operations (80 LOC)
|
|
7
|
+
- milestone_storage.py: Milestone CRUD operations (70 LOC)
|
|
8
|
+
- issue_storage.py: Issue CRUD operations (80 LOC)
|
|
9
|
+
- sync_state_storage.py: Sync state and file synchronization (180 LOC)
|
|
10
|
+
- queries.py: Complex database queries and aggregations (190 LOC)
|
|
11
|
+
- conflicts.py: Git conflict detection and handling (80 LOC)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .connection_manager import ConnectionManager
|
|
15
|
+
from .issue_storage import IssueStorage
|
|
16
|
+
from .milestone_storage import MilestoneStorage
|
|
17
|
+
from .project_storage import ProjectStorage
|
|
18
|
+
from .state_manager import (
|
|
19
|
+
DatabaseError,
|
|
20
|
+
StateManager,
|
|
21
|
+
get_state_manager,
|
|
22
|
+
initialize_state_manager,
|
|
23
|
+
)
|
|
24
|
+
from .sync_state_storage import SyncStateStorage
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ConnectionManager",
|
|
28
|
+
"DatabaseError",
|
|
29
|
+
"IssueStorage",
|
|
30
|
+
"MilestoneStorage",
|
|
31
|
+
"ProjectStorage",
|
|
32
|
+
"StateManager",
|
|
33
|
+
"SyncStateStorage",
|
|
34
|
+
"get_state_manager",
|
|
35
|
+
"initialize_state_manager",
|
|
36
|
+
]
|