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,652 @@
|
|
|
1
|
+
"""State manager and database errors for persistence layer."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from roadmap.common.logging import get_logger
|
|
9
|
+
|
|
10
|
+
from ..database_manager import DatabaseManager
|
|
11
|
+
from ..file_synchronizer import FileSynchronizer
|
|
12
|
+
from ..repositories import (
|
|
13
|
+
IssueRepository,
|
|
14
|
+
MilestoneRepository,
|
|
15
|
+
ProjectRepository,
|
|
16
|
+
RemoteLinkRepository,
|
|
17
|
+
SyncStateRepository,
|
|
18
|
+
)
|
|
19
|
+
from .connection_manager import ConnectionManager
|
|
20
|
+
from .issue_storage import IssueStorage
|
|
21
|
+
from .milestone_storage import MilestoneStorage
|
|
22
|
+
from .project_storage import ProjectStorage
|
|
23
|
+
from .sync_state_storage import SyncStateStorage
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from roadmap.adapters.git.sync_monitor import GitSyncMonitor # noqa: F401
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DatabaseError(Exception):
|
|
32
|
+
"""Base exception for database operations."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StateManager:
|
|
38
|
+
"""SQLite-based state manager for roadmap data.
|
|
39
|
+
|
|
40
|
+
This class serves as a facade, delegating entity-specific operations
|
|
41
|
+
to specialized repository classes while maintaining connection management
|
|
42
|
+
and transaction handling.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
db_path: str | Path | None = None,
|
|
48
|
+
git_sync_monitor: "GitSyncMonitor | None" = None,
|
|
49
|
+
):
|
|
50
|
+
"""Initialize the state manager.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
db_path: Path to SQLite database file. Defaults to ~/.roadmap/roadmap.db
|
|
54
|
+
git_sync_monitor: Optional GitSyncMonitor for cache invalidation via git diff.
|
|
55
|
+
If provided, database will sync on access.
|
|
56
|
+
"""
|
|
57
|
+
if db_path is None:
|
|
58
|
+
db_path = Path.home() / ".roadmap" / "roadmap.db"
|
|
59
|
+
|
|
60
|
+
self.db_path = Path(db_path)
|
|
61
|
+
self._git_sync_monitor = git_sync_monitor
|
|
62
|
+
self._db_manager = DatabaseManager(db_path)
|
|
63
|
+
self._file_synchronizer = FileSynchronizer(
|
|
64
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Initialize repositories
|
|
68
|
+
self._project_repo = ProjectRepository(
|
|
69
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
70
|
+
)
|
|
71
|
+
self._milestone_repo = MilestoneRepository(
|
|
72
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
73
|
+
)
|
|
74
|
+
self._issue_repo = IssueRepository(
|
|
75
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
76
|
+
)
|
|
77
|
+
self._sync_state_repo = SyncStateRepository(
|
|
78
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
79
|
+
)
|
|
80
|
+
self._remote_link_repo = RemoteLinkRepository(
|
|
81
|
+
self._db_manager._get_connection, self._db_manager.transaction
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Initialize storage layers
|
|
85
|
+
self._connection_manager = ConnectionManager(self._db_manager)
|
|
86
|
+
self._project_storage = ProjectStorage(self._project_repo)
|
|
87
|
+
self._milestone_storage = MilestoneStorage(self._milestone_repo)
|
|
88
|
+
self._issue_storage = IssueStorage(self._issue_repo)
|
|
89
|
+
self._sync_state_storage = SyncStateStorage(
|
|
90
|
+
self._sync_state_repo, self._file_synchronizer
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Expose database manager's _local for backward compatibility with tests
|
|
94
|
+
self._local = self._db_manager._local
|
|
95
|
+
|
|
96
|
+
logger.info(
|
|
97
|
+
"Initializing state manager",
|
|
98
|
+
db_path=str(self.db_path),
|
|
99
|
+
has_git_monitor=git_sync_monitor is not None,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Note: Remote links initialization moved to RoadmapCore.initialize_remote_links()
|
|
103
|
+
# because StateManager doesn't have access to roadmap_dir
|
|
104
|
+
|
|
105
|
+
# Connection management - delegate to ConnectionManager
|
|
106
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
107
|
+
"""Get thread-local database connection."""
|
|
108
|
+
return self._connection_manager.get_connection()
|
|
109
|
+
|
|
110
|
+
def transaction(self):
|
|
111
|
+
"""Context manager for database transactions."""
|
|
112
|
+
return self._connection_manager.transaction()
|
|
113
|
+
|
|
114
|
+
def _sync_git_state(self) -> None:
|
|
115
|
+
"""Sync git state to database if GitSyncMonitor is configured.
|
|
116
|
+
|
|
117
|
+
This ensures the database cache is up-to-date with git before
|
|
118
|
+
any repository access. Called transparently before repo operations.
|
|
119
|
+
"""
|
|
120
|
+
if self._git_sync_monitor is None:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
changes = self._git_sync_monitor.detect_changes()
|
|
125
|
+
if changes:
|
|
126
|
+
self._git_sync_monitor.sync_to_database(changes)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning(
|
|
129
|
+
"Git sync failed, proceeding with potentially stale cache",
|
|
130
|
+
error=str(e),
|
|
131
|
+
error_type=type(e).__name__,
|
|
132
|
+
severity="operational",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _initialize_remote_links_from_yaml(self) -> None:
|
|
136
|
+
"""Initialize database remote_links from YAML files on startup.
|
|
137
|
+
|
|
138
|
+
This is a one-time initialization that loads all remote_ids found in
|
|
139
|
+
YAML frontmatter into the database cache for fast O(1) lookups during
|
|
140
|
+
sync operations.
|
|
141
|
+
|
|
142
|
+
Only loads links if database is empty (first initialization or reset).
|
|
143
|
+
|
|
144
|
+
Note: This method is deprecated. Use initialize_remote_links(roadmap_dir) instead.
|
|
145
|
+
"""
|
|
146
|
+
# This is kept for backward compatibility but does nothing
|
|
147
|
+
# The actual initialization is now done by initialize_remote_links()
|
|
148
|
+
|
|
149
|
+
def initialize_remote_links(self, roadmap_dir: Path | str) -> None:
|
|
150
|
+
"""Initialize remote_links from YAML files in the given roadmap directory.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
roadmap_dir: Path to the roadmap directory
|
|
154
|
+
"""
|
|
155
|
+
self._initialize_remote_links_impl(roadmap_dir)
|
|
156
|
+
|
|
157
|
+
def _initialize_remote_links_impl(self, roadmap_dir: Path | str) -> None:
|
|
158
|
+
"""Initialize remote links from storage.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
roadmap_dir: Path to the roadmap directory
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
# Check if remote_links table already has data (skip if so)
|
|
165
|
+
existing_github_links = self._remote_link_repo.get_all_links_for_backend(
|
|
166
|
+
"github"
|
|
167
|
+
)
|
|
168
|
+
if existing_github_links:
|
|
169
|
+
logger.debug(
|
|
170
|
+
"remote_links_already_initialized",
|
|
171
|
+
existing_count=len(existing_github_links),
|
|
172
|
+
)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
# Load all issue files from roadmap_dir/.roadmap/issues directory
|
|
176
|
+
from roadmap.adapters.persistence.parser.issue import IssueParser
|
|
177
|
+
|
|
178
|
+
issues_dir = Path(roadmap_dir) / ".roadmap" / "issues"
|
|
179
|
+
|
|
180
|
+
if not issues_dir.exists():
|
|
181
|
+
logger.debug("issues_directory_not_found", path=str(issues_dir))
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Collect all issue files from all subdirectories
|
|
185
|
+
issue_files = list(issues_dir.glob("**/*.md"))
|
|
186
|
+
if not issue_files:
|
|
187
|
+
logger.debug("no_issue_files_found", path=str(issues_dir))
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
# Build dict of issue_uuid -> dict[backend_name -> remote_id]
|
|
191
|
+
issues_with_remote_ids = {}
|
|
192
|
+
for file_path in issue_files:
|
|
193
|
+
try:
|
|
194
|
+
issue = IssueParser.parse_issue_file(file_path)
|
|
195
|
+
if issue.remote_ids:
|
|
196
|
+
issues_with_remote_ids[issue.id] = issue.remote_ids
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.warning(
|
|
199
|
+
"failed_to_parse_issue_file",
|
|
200
|
+
file_path=str(file_path),
|
|
201
|
+
error=str(e),
|
|
202
|
+
)
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
if not issues_with_remote_ids:
|
|
206
|
+
logger.debug("no_remote_ids_found_in_yaml_files")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# Bulk import into database
|
|
210
|
+
count = self._remote_link_repo.bulk_import_from_yaml(issues_with_remote_ids)
|
|
211
|
+
logger.info(
|
|
212
|
+
"initialized_remote_links_from_yaml",
|
|
213
|
+
total_files=len(issue_files),
|
|
214
|
+
issues_with_links=len(issues_with_remote_ids),
|
|
215
|
+
links_imported=count,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(
|
|
220
|
+
"failed_to_initialize_remote_links",
|
|
221
|
+
error=str(e),
|
|
222
|
+
error_type=type(e).__name__,
|
|
223
|
+
)
|
|
224
|
+
# Don't raise - allow startup to proceed even if initialization fails
|
|
225
|
+
|
|
226
|
+
def _init_database(self):
|
|
227
|
+
"""Initialize database schema."""
|
|
228
|
+
# Delegated to DatabaseManager during initialization
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
def _run_migrations(self):
|
|
232
|
+
"""Run database migrations for schema updates."""
|
|
233
|
+
# Delegated to DatabaseManager during initialization
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
def is_initialized(self) -> bool:
|
|
237
|
+
"""Check if database is properly initialized."""
|
|
238
|
+
return self._connection_manager.is_initialized()
|
|
239
|
+
|
|
240
|
+
def close(self):
|
|
241
|
+
"""Close database connections."""
|
|
242
|
+
self._connection_manager.close()
|
|
243
|
+
|
|
244
|
+
def vacuum(self):
|
|
245
|
+
"""Optimize database."""
|
|
246
|
+
self._connection_manager.vacuum()
|
|
247
|
+
|
|
248
|
+
def database_exists(self) -> bool:
|
|
249
|
+
"""Check if database file exists and has tables."""
|
|
250
|
+
return self._connection_manager.database_exists()
|
|
251
|
+
|
|
252
|
+
# Repository access - with automatic git sync
|
|
253
|
+
def get_issue_repository(self) -> IssueRepository:
|
|
254
|
+
"""Get issue repository after syncing git state.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
IssueRepository with database synced to latest git state
|
|
258
|
+
"""
|
|
259
|
+
self._sync_git_state()
|
|
260
|
+
return self._issue_repo
|
|
261
|
+
|
|
262
|
+
def get_milestone_repository(self) -> MilestoneRepository:
|
|
263
|
+
"""Get milestone repository after syncing git state.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
MilestoneRepository with database synced to latest git state
|
|
267
|
+
"""
|
|
268
|
+
self._sync_git_state()
|
|
269
|
+
return self._milestone_repo
|
|
270
|
+
|
|
271
|
+
def get_project_repository(self) -> ProjectRepository:
|
|
272
|
+
"""Get project repository after syncing git state.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
ProjectRepository with database synced to latest git state
|
|
276
|
+
"""
|
|
277
|
+
self._sync_git_state()
|
|
278
|
+
return self._project_repo
|
|
279
|
+
|
|
280
|
+
# Project operations - delegate to ProjectStorage
|
|
281
|
+
def create_project(self, project_data: dict[str, Any]) -> str:
|
|
282
|
+
"""Create a new project."""
|
|
283
|
+
return self._project_storage.create(project_data)
|
|
284
|
+
|
|
285
|
+
def get_project(self, project_id: str) -> dict[str, Any] | None:
|
|
286
|
+
"""Get project by ID."""
|
|
287
|
+
return self._project_storage.get(project_id)
|
|
288
|
+
|
|
289
|
+
def list_projects(self) -> list[dict[str, Any]]:
|
|
290
|
+
"""List all projects."""
|
|
291
|
+
return self._project_storage.list_all()
|
|
292
|
+
|
|
293
|
+
def update_project(self, project_id: str, updates: dict[str, Any]) -> bool:
|
|
294
|
+
"""Update project."""
|
|
295
|
+
return self._project_storage.update(project_id, updates)
|
|
296
|
+
|
|
297
|
+
def delete_project(self, project_id: str) -> bool:
|
|
298
|
+
"""Delete project and all related data."""
|
|
299
|
+
return self._project_storage.delete(project_id)
|
|
300
|
+
|
|
301
|
+
def mark_project_archived(self, project_id: str, archived: bool = True) -> bool:
|
|
302
|
+
"""Mark a project as archived or unarchived."""
|
|
303
|
+
return self._project_storage.mark_archived(project_id, archived)
|
|
304
|
+
|
|
305
|
+
# Milestone operations - delegate to MilestoneStorage
|
|
306
|
+
def create_milestone(self, milestone_data: dict[str, Any]) -> str:
|
|
307
|
+
"""Create a new milestone."""
|
|
308
|
+
return self._milestone_storage.create(milestone_data)
|
|
309
|
+
|
|
310
|
+
def get_milestone(self, milestone_id: str) -> dict[str, Any] | None:
|
|
311
|
+
"""Get milestone by ID."""
|
|
312
|
+
return self._milestone_storage.get(milestone_id)
|
|
313
|
+
|
|
314
|
+
def update_milestone(self, milestone_id: str, updates: dict[str, Any]) -> bool:
|
|
315
|
+
"""Update milestone."""
|
|
316
|
+
return self._milestone_storage.update(milestone_id, updates)
|
|
317
|
+
|
|
318
|
+
def mark_milestone_archived(self, milestone_id: str, archived: bool = True) -> bool:
|
|
319
|
+
"""Mark a milestone as archived or unarchived."""
|
|
320
|
+
return self._milestone_storage.mark_archived(milestone_id, archived)
|
|
321
|
+
|
|
322
|
+
# Issue operations - delegate to IssueStorage
|
|
323
|
+
def create_issue(self, issue_data: dict[str, Any]) -> str:
|
|
324
|
+
"""Create a new issue."""
|
|
325
|
+
return self._issue_storage.create(issue_data)
|
|
326
|
+
|
|
327
|
+
def get_issue(self, issue_id: str) -> dict[str, Any] | None:
|
|
328
|
+
"""Get issue by ID."""
|
|
329
|
+
return self._issue_storage.get(issue_id)
|
|
330
|
+
|
|
331
|
+
def update_issue(self, issue_id: str, updates: dict[str, Any]) -> bool:
|
|
332
|
+
"""Update issue."""
|
|
333
|
+
return self._issue_storage.update(issue_id, updates)
|
|
334
|
+
|
|
335
|
+
def delete_issue(self, issue_id: str) -> bool:
|
|
336
|
+
"""Delete issue."""
|
|
337
|
+
return self._issue_storage.delete(issue_id)
|
|
338
|
+
|
|
339
|
+
def mark_issue_archived(self, issue_id: str, archived: bool = True) -> bool:
|
|
340
|
+
"""Mark an issue as archived or unarchived."""
|
|
341
|
+
return self._issue_storage.mark_archived(issue_id, archived)
|
|
342
|
+
|
|
343
|
+
# Sync state operations - delegate to SyncStateStorage
|
|
344
|
+
def get_sync_state(self, key: str) -> str | None:
|
|
345
|
+
"""Get sync state value."""
|
|
346
|
+
return self._sync_state_storage.get_sync_state(key)
|
|
347
|
+
|
|
348
|
+
def set_sync_state(self, key: str, value: str):
|
|
349
|
+
"""Set sync state value."""
|
|
350
|
+
self._sync_state_storage.set_sync_state(key, value)
|
|
351
|
+
|
|
352
|
+
# Remote link operations - delegate to RemoteLinkRepository
|
|
353
|
+
@property
|
|
354
|
+
def remote_links(self) -> RemoteLinkRepository:
|
|
355
|
+
"""Get remote link repository for sync backend operations."""
|
|
356
|
+
return self._remote_link_repo
|
|
357
|
+
|
|
358
|
+
# Sync baseline operations - store/retrieve baseline for three-way merge
|
|
359
|
+
def get_sync_baseline(self) -> dict[str, Any] | None:
|
|
360
|
+
"""Get the sync baseline from database.
|
|
361
|
+
|
|
362
|
+
The baseline represents the state of issues as they were during the last
|
|
363
|
+
successful sync. Used for three-way merge to detect conflicts.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Dictionary mapping issue_id to baseline state, or None if no baseline exists
|
|
367
|
+
"""
|
|
368
|
+
try:
|
|
369
|
+
import json
|
|
370
|
+
|
|
371
|
+
self._sync_git_state()
|
|
372
|
+
|
|
373
|
+
conn = self._get_connection()
|
|
374
|
+
rows = conn.execute(
|
|
375
|
+
"""
|
|
376
|
+
SELECT issue_id, status, assignee, milestone, description, headline, content, labels, synced_at
|
|
377
|
+
FROM sync_base_state
|
|
378
|
+
ORDER BY synced_at DESC
|
|
379
|
+
"""
|
|
380
|
+
).fetchall()
|
|
381
|
+
|
|
382
|
+
if not rows:
|
|
383
|
+
logger.info("sync_baseline_not_found_empty_database")
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
baseline = {}
|
|
387
|
+
for row in rows:
|
|
388
|
+
try:
|
|
389
|
+
baseline[row["issue_id"]] = {
|
|
390
|
+
"status": row["status"],
|
|
391
|
+
"assignee": row["assignee"],
|
|
392
|
+
"milestone": row["milestone"],
|
|
393
|
+
"description": row["description"],
|
|
394
|
+
"headline": row["headline"],
|
|
395
|
+
"content": row["content"],
|
|
396
|
+
"labels": json.loads(row["labels"]) if row["labels"] else [],
|
|
397
|
+
"synced_at": row["synced_at"],
|
|
398
|
+
}
|
|
399
|
+
except json.JSONDecodeError as je:
|
|
400
|
+
logger.warning(
|
|
401
|
+
"sync_baseline_labels_parse_error",
|
|
402
|
+
issue_id=row["issue_id"],
|
|
403
|
+
labels_str=row["labels"],
|
|
404
|
+
error=str(je),
|
|
405
|
+
)
|
|
406
|
+
# Fall back to empty labels for this issue
|
|
407
|
+
baseline[row["issue_id"]] = {
|
|
408
|
+
"status": row["status"],
|
|
409
|
+
"assignee": row["assignee"],
|
|
410
|
+
"milestone": row["milestone"],
|
|
411
|
+
"description": row["description"],
|
|
412
|
+
"headline": row["headline"],
|
|
413
|
+
"content": row["content"],
|
|
414
|
+
"labels": [],
|
|
415
|
+
"synced_at": row["synced_at"],
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
logger.info(
|
|
419
|
+
"sync_baseline_retrieved_from_database",
|
|
420
|
+
issue_count=len(baseline),
|
|
421
|
+
synced_at=rows[0]["synced_at"] if rows else None,
|
|
422
|
+
)
|
|
423
|
+
return baseline
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
logger.error(
|
|
427
|
+
"error_loading_sync_baseline",
|
|
428
|
+
error=str(e),
|
|
429
|
+
error_type=type(e).__name__,
|
|
430
|
+
exc_info=True,
|
|
431
|
+
)
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
def save_sync_baseline(self, baseline: dict[str, Any]) -> bool:
|
|
435
|
+
"""Save the current sync baseline to database.
|
|
436
|
+
|
|
437
|
+
Called after a successful sync to establish the new baseline for the
|
|
438
|
+
next sync's three-way merge.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
baseline: Dictionary mapping issue_id to state dict with keys:
|
|
442
|
+
status, assignee, milestone, headline, content, labels
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
True if saved successfully, False otherwise
|
|
446
|
+
"""
|
|
447
|
+
try:
|
|
448
|
+
import json
|
|
449
|
+
|
|
450
|
+
now = datetime.now(UTC).isoformat()
|
|
451
|
+
|
|
452
|
+
logger.debug(
|
|
453
|
+
"saving_sync_baseline_to_database",
|
|
454
|
+
issue_count=len(baseline),
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# Get connection and disable FK constraints for this operation
|
|
458
|
+
conn = self._connection_manager.get_connection()
|
|
459
|
+
conn.execute("PRAGMA foreign_keys = OFF")
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
463
|
+
# Clear old baseline
|
|
464
|
+
conn.execute("DELETE FROM sync_base_state")
|
|
465
|
+
logger.debug("cleared_old_sync_baseline_from_database")
|
|
466
|
+
|
|
467
|
+
# Insert new baseline
|
|
468
|
+
for issue_id, state in baseline.items():
|
|
469
|
+
conn.execute(
|
|
470
|
+
"""
|
|
471
|
+
INSERT INTO sync_base_state
|
|
472
|
+
(issue_id, status, assignee, milestone, description, headline, content, labels, synced_at)
|
|
473
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
474
|
+
""",
|
|
475
|
+
(
|
|
476
|
+
issue_id,
|
|
477
|
+
state.get("status"),
|
|
478
|
+
state.get("assignee"),
|
|
479
|
+
state.get("milestone"),
|
|
480
|
+
state.get("description"), # Keep for backwards compat
|
|
481
|
+
state.get("headline"),
|
|
482
|
+
state.get("content"),
|
|
483
|
+
json.dumps(state.get("labels", [])),
|
|
484
|
+
now,
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
conn.execute("COMMIT")
|
|
488
|
+
finally:
|
|
489
|
+
# Re-enable foreign keys
|
|
490
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
491
|
+
|
|
492
|
+
logger.info(
|
|
493
|
+
"sync_baseline_saved_to_database",
|
|
494
|
+
issue_count=len(baseline),
|
|
495
|
+
synced_at=now,
|
|
496
|
+
)
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
except Exception as e:
|
|
500
|
+
logger.error(
|
|
501
|
+
"error_saving_sync_baseline",
|
|
502
|
+
error=str(e),
|
|
503
|
+
error_type=type(e).__name__,
|
|
504
|
+
issue_count=len(baseline),
|
|
505
|
+
exc_info=True,
|
|
506
|
+
)
|
|
507
|
+
return False
|
|
508
|
+
|
|
509
|
+
def clear_sync_baseline(self) -> bool:
|
|
510
|
+
"""Clear the sync baseline (used before first sync).
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
True if cleared successfully
|
|
514
|
+
"""
|
|
515
|
+
try:
|
|
516
|
+
logger.debug("clearing_sync_baseline_from_database")
|
|
517
|
+
|
|
518
|
+
with self.transaction() as conn:
|
|
519
|
+
conn.execute("DELETE FROM sync_base_state")
|
|
520
|
+
|
|
521
|
+
logger.info("sync_baseline_cleared_from_database")
|
|
522
|
+
return True
|
|
523
|
+
except Exception as e:
|
|
524
|
+
logger.error(
|
|
525
|
+
"error_clearing_sync_baseline",
|
|
526
|
+
error=str(e),
|
|
527
|
+
error_type=type(e).__name__,
|
|
528
|
+
exc_info=True,
|
|
529
|
+
)
|
|
530
|
+
return False
|
|
531
|
+
|
|
532
|
+
# File sync delegation - delegate to SyncStateStorage
|
|
533
|
+
def get_file_sync_status(self, file_path: str) -> dict[str, Any] | None:
|
|
534
|
+
"""Get sync status for a file."""
|
|
535
|
+
return self._sync_state_storage.get_file_sync_status(file_path)
|
|
536
|
+
|
|
537
|
+
def update_file_sync_status(
|
|
538
|
+
self, file_path: str, content_hash: str, file_size: int, last_modified: Any
|
|
539
|
+
):
|
|
540
|
+
"""Update sync status for a file."""
|
|
541
|
+
self._sync_state_storage.update_file_sync_status(
|
|
542
|
+
file_path, content_hash, file_size, last_modified
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def has_file_changed(self, file_path: Path) -> bool:
|
|
546
|
+
"""Check if file has changed since last sync."""
|
|
547
|
+
return self._sync_state_storage.has_file_changed(file_path)
|
|
548
|
+
|
|
549
|
+
def sync_directory_incremental(self, roadmap_dir: Path) -> dict[str, Any]:
|
|
550
|
+
"""Incrementally sync .roadmap directory to database."""
|
|
551
|
+
return self._sync_state_storage.sync_directory_incremental(roadmap_dir)
|
|
552
|
+
|
|
553
|
+
def full_rebuild_from_git(self, roadmap_dir: Path) -> dict[str, Any]:
|
|
554
|
+
"""Full rebuild of database from git files."""
|
|
555
|
+
return self._sync_state_storage.full_rebuild_from_git(roadmap_dir)
|
|
556
|
+
|
|
557
|
+
def should_do_full_rebuild(self, roadmap_dir: Path, threshold: int = 50) -> bool:
|
|
558
|
+
"""Determine if full rebuild is needed vs incremental sync."""
|
|
559
|
+
return self._sync_state_storage.should_do_full_rebuild(roadmap_dir, threshold)
|
|
560
|
+
|
|
561
|
+
def smart_sync(self, roadmap_dir: Path | None = None) -> dict[str, Any]:
|
|
562
|
+
"""Smart sync that chooses between incremental and full rebuild."""
|
|
563
|
+
return self._sync_state_storage.smart_sync(roadmap_dir)
|
|
564
|
+
|
|
565
|
+
# Query operations - these delegate complex queries to query service
|
|
566
|
+
def has_file_changes(self) -> bool:
|
|
567
|
+
"""Check if .roadmap/ files have changes since last sync."""
|
|
568
|
+
return self._sync_state_storage.has_file_changes(self)
|
|
569
|
+
|
|
570
|
+
def get_all_issues(self) -> list[dict[str, Any]]:
|
|
571
|
+
"""Get all issues from database."""
|
|
572
|
+
from .queries import QueryService
|
|
573
|
+
|
|
574
|
+
logger.debug("getting_all_issues")
|
|
575
|
+
issues = QueryService(self).get_all_issues()
|
|
576
|
+
logger.debug("get_all_issues_completed", issue_count=len(issues))
|
|
577
|
+
return issues
|
|
578
|
+
|
|
579
|
+
def get_all_milestones(self) -> list[dict[str, Any]]:
|
|
580
|
+
"""Get all milestones from database."""
|
|
581
|
+
from .queries import QueryService
|
|
582
|
+
|
|
583
|
+
return QueryService(self).get_all_milestones()
|
|
584
|
+
|
|
585
|
+
def get_milestone_progress(self, milestone_name: str) -> dict[str, int]:
|
|
586
|
+
"""Get progress stats for a milestone."""
|
|
587
|
+
from .queries import QueryService
|
|
588
|
+
|
|
589
|
+
return QueryService(self).get_milestone_progress(milestone_name)
|
|
590
|
+
|
|
591
|
+
def get_issues_by_status(self) -> dict[str, int]:
|
|
592
|
+
"""Get issue counts by status."""
|
|
593
|
+
from .queries import QueryService
|
|
594
|
+
|
|
595
|
+
return QueryService(self).get_issues_by_status()
|
|
596
|
+
|
|
597
|
+
# Conflict operations - delegate to conflict service
|
|
598
|
+
def check_git_conflicts(self, roadmap_dir: Path | None = None) -> list[str]:
|
|
599
|
+
"""Check for git conflicts in .roadmap directory."""
|
|
600
|
+
from .conflicts import ConflictService
|
|
601
|
+
|
|
602
|
+
return ConflictService(self).check_git_conflicts(roadmap_dir)
|
|
603
|
+
|
|
604
|
+
def has_git_conflicts(self) -> bool:
|
|
605
|
+
"""Check if there are unresolved git conflicts."""
|
|
606
|
+
from .conflicts import ConflictService
|
|
607
|
+
|
|
608
|
+
return ConflictService(self).has_git_conflicts()
|
|
609
|
+
|
|
610
|
+
def get_conflict_files(self) -> list[str]:
|
|
611
|
+
"""Get list of files with git conflicts."""
|
|
612
|
+
from .conflicts import ConflictService
|
|
613
|
+
|
|
614
|
+
return ConflictService(self).get_conflict_files()
|
|
615
|
+
|
|
616
|
+
def is_safe_for_writes(self) -> tuple[bool, str]:
|
|
617
|
+
"""Check if database is safe for write operations."""
|
|
618
|
+
try:
|
|
619
|
+
# Check for git conflicts
|
|
620
|
+
if self.has_git_conflicts():
|
|
621
|
+
conflict_files = self.get_conflict_files()
|
|
622
|
+
return (
|
|
623
|
+
False,
|
|
624
|
+
f"Git conflicts detected in {len(conflict_files)} files. Resolve conflicts first.",
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Delegate to connection manager for safety check
|
|
628
|
+
return self._connection_manager.is_safe_for_writes()
|
|
629
|
+
|
|
630
|
+
except Exception as e:
|
|
631
|
+
return False, f"Safety check failed: {e}"
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
# Global state manager instance
|
|
635
|
+
_state_manager: StateManager | None = None
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def get_state_manager(db_path: str | Path | None = None) -> StateManager:
|
|
639
|
+
"""Get the global state manager instance."""
|
|
640
|
+
global _state_manager
|
|
641
|
+
|
|
642
|
+
if _state_manager is None:
|
|
643
|
+
_state_manager = StateManager(db_path)
|
|
644
|
+
|
|
645
|
+
return _state_manager
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def initialize_state_manager(db_path: str | Path | None = None) -> StateManager:
|
|
649
|
+
"""Initialize the global state manager."""
|
|
650
|
+
global _state_manager
|
|
651
|
+
_state_manager = StateManager(db_path)
|
|
652
|
+
return _state_manager
|