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,492 @@
|
|
|
1
|
+
"""Database infrastructure manager for SQLite persistence layer.
|
|
2
|
+
|
|
3
|
+
This module handles database connection, initialization, and migrations.
|
|
4
|
+
It separates database infrastructure concerns from the state management layer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sqlite3
|
|
8
|
+
import threading
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from roadmap.common.logging import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatabaseError(Exception):
|
|
19
|
+
"""Base exception for database operations."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DatabaseManager:
|
|
25
|
+
"""Manages SQLite database connections, initialization, and migrations."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, db_path: str | Path | None = None):
|
|
28
|
+
"""Initialize the database manager.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
db_path: Path to SQLite database file. Defaults to ~/.roadmap/roadmap.db
|
|
32
|
+
"""
|
|
33
|
+
if db_path is None:
|
|
34
|
+
db_path = Path.home() / ".roadmap" / "roadmap.db"
|
|
35
|
+
|
|
36
|
+
self.db_path = Path(db_path)
|
|
37
|
+
try:
|
|
38
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
except OSError as e:
|
|
40
|
+
logger.error(
|
|
41
|
+
"database_directory_create_failed",
|
|
42
|
+
db_path=str(self.db_path),
|
|
43
|
+
error=str(e),
|
|
44
|
+
error_type=type(e).__name__,
|
|
45
|
+
severity="system_error",
|
|
46
|
+
)
|
|
47
|
+
raise DatabaseError(
|
|
48
|
+
f"Failed to create database directory for {self.db_path}: {e}"
|
|
49
|
+
) from e
|
|
50
|
+
|
|
51
|
+
# Thread-local storage for connections
|
|
52
|
+
self._local = threading.local()
|
|
53
|
+
|
|
54
|
+
logger.info("Initializing database manager", db_path=str(self.db_path))
|
|
55
|
+
try:
|
|
56
|
+
self._init_database()
|
|
57
|
+
except sqlite3.Error as e:
|
|
58
|
+
logger.error(
|
|
59
|
+
"database_schema_init_failed",
|
|
60
|
+
db_path=str(self.db_path),
|
|
61
|
+
error=str(e),
|
|
62
|
+
error_type=type(e).__name__,
|
|
63
|
+
severity="system_error",
|
|
64
|
+
)
|
|
65
|
+
raise DatabaseError(
|
|
66
|
+
f"Failed to initialize database schema at {self.db_path}: {e}"
|
|
67
|
+
) from e
|
|
68
|
+
|
|
69
|
+
def validate_read_write(self) -> None:
|
|
70
|
+
"""Validate that the database is readable and writable."""
|
|
71
|
+
conn = self._get_connection()
|
|
72
|
+
try:
|
|
73
|
+
conn.execute(
|
|
74
|
+
"""
|
|
75
|
+
CREATE TABLE IF NOT EXISTS db_health_check (
|
|
76
|
+
id INTEGER PRIMARY KEY,
|
|
77
|
+
checked_at TIMESTAMP
|
|
78
|
+
)
|
|
79
|
+
"""
|
|
80
|
+
)
|
|
81
|
+
conn.execute(
|
|
82
|
+
"INSERT INTO db_health_check (checked_at) VALUES (?)",
|
|
83
|
+
(datetime.now(UTC).isoformat(),),
|
|
84
|
+
)
|
|
85
|
+
row_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
|
86
|
+
conn.execute("DELETE FROM db_health_check WHERE id = ?", (row_id,))
|
|
87
|
+
except sqlite3.Error as e:
|
|
88
|
+
logger.error(
|
|
89
|
+
"database_read_write_failed",
|
|
90
|
+
db_path=str(self.db_path),
|
|
91
|
+
error=str(e),
|
|
92
|
+
error_type=type(e).__name__,
|
|
93
|
+
severity="system_error",
|
|
94
|
+
)
|
|
95
|
+
raise DatabaseError(
|
|
96
|
+
f"Database read/write check failed for {self.db_path}: {e}"
|
|
97
|
+
) from e
|
|
98
|
+
|
|
99
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
100
|
+
"""Get thread-local database connection."""
|
|
101
|
+
if not hasattr(self._local, "connection"):
|
|
102
|
+
self._local.connection = sqlite3.connect(
|
|
103
|
+
str(self.db_path),
|
|
104
|
+
check_same_thread=False,
|
|
105
|
+
timeout=30.0,
|
|
106
|
+
isolation_level=None, # Autocommit mode
|
|
107
|
+
)
|
|
108
|
+
# Register datetime adapter for Python 3.12+
|
|
109
|
+
sqlite3.register_adapter(datetime, lambda dt: dt.isoformat())
|
|
110
|
+
sqlite3.register_converter(
|
|
111
|
+
"TIMESTAMP", lambda val: datetime.fromisoformat(val.decode())
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Enable foreign keys and WAL mode for better performance
|
|
115
|
+
self._local.connection.execute("PRAGMA foreign_keys = ON")
|
|
116
|
+
self._local.connection.execute("PRAGMA journal_mode = WAL")
|
|
117
|
+
self._local.connection.execute("PRAGMA synchronous = NORMAL")
|
|
118
|
+
self._local.connection.row_factory = sqlite3.Row
|
|
119
|
+
|
|
120
|
+
return self._local.connection
|
|
121
|
+
|
|
122
|
+
@contextmanager
|
|
123
|
+
def transaction(self):
|
|
124
|
+
"""Context manager for database transactions."""
|
|
125
|
+
conn = self._get_connection()
|
|
126
|
+
try:
|
|
127
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
128
|
+
yield conn
|
|
129
|
+
conn.execute("COMMIT")
|
|
130
|
+
except Exception:
|
|
131
|
+
try:
|
|
132
|
+
conn.execute("ROLLBACK")
|
|
133
|
+
except sqlite3.OperationalError:
|
|
134
|
+
# Transaction might not be active
|
|
135
|
+
logger.debug(
|
|
136
|
+
"rollback_failed_transaction_not_active", severity="operational"
|
|
137
|
+
)
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
def _init_database(self):
|
|
141
|
+
"""Initialize database schema."""
|
|
142
|
+
schema_sql = """
|
|
143
|
+
-- Projects table
|
|
144
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
145
|
+
id TEXT PRIMARY KEY,
|
|
146
|
+
name TEXT NOT NULL,
|
|
147
|
+
description TEXT,
|
|
148
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
149
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
150
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
151
|
+
metadata TEXT -- JSON for additional data
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
-- Milestones table
|
|
155
|
+
CREATE TABLE IF NOT EXISTS milestones (
|
|
156
|
+
id TEXT PRIMARY KEY,
|
|
157
|
+
project_id TEXT NOT NULL,
|
|
158
|
+
title TEXT NOT NULL,
|
|
159
|
+
description TEXT,
|
|
160
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
161
|
+
due_date DATE,
|
|
162
|
+
progress_percentage REAL DEFAULT 0.0,
|
|
163
|
+
archived BOOLEAN DEFAULT 0,
|
|
164
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
165
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
166
|
+
metadata TEXT, -- JSON for additional data
|
|
167
|
+
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
-- Issues table
|
|
171
|
+
CREATE TABLE IF NOT EXISTS issues (
|
|
172
|
+
id TEXT PRIMARY KEY,
|
|
173
|
+
project_id TEXT,
|
|
174
|
+
milestone_id TEXT,
|
|
175
|
+
title TEXT NOT NULL,
|
|
176
|
+
headline TEXT DEFAULT '', -- Short summary for list views
|
|
177
|
+
description TEXT, -- Full markdown content
|
|
178
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
179
|
+
priority TEXT NOT NULL DEFAULT 'medium',
|
|
180
|
+
issue_type TEXT NOT NULL DEFAULT 'task',
|
|
181
|
+
assignee TEXT,
|
|
182
|
+
estimate_hours REAL,
|
|
183
|
+
archived BOOLEAN DEFAULT 0,
|
|
184
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
185
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
186
|
+
due_date DATE,
|
|
187
|
+
metadata TEXT, -- JSON for additional data
|
|
188
|
+
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
|
|
189
|
+
FOREIGN KEY (milestone_id) REFERENCES milestones (id) ON DELETE SET NULL
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
-- Issue dependencies table
|
|
193
|
+
CREATE TABLE IF NOT EXISTS issue_dependencies (
|
|
194
|
+
issue_id TEXT NOT NULL,
|
|
195
|
+
depends_on_id TEXT NOT NULL,
|
|
196
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
197
|
+
PRIMARY KEY (issue_id, depends_on_id),
|
|
198
|
+
FOREIGN KEY (issue_id) REFERENCES issues (id) ON DELETE CASCADE,
|
|
199
|
+
FOREIGN KEY (depends_on_id) REFERENCES issues (id) ON DELETE CASCADE
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
-- Issue labels table (many-to-many)
|
|
203
|
+
CREATE TABLE IF NOT EXISTS issue_labels (
|
|
204
|
+
issue_id TEXT NOT NULL,
|
|
205
|
+
label TEXT NOT NULL,
|
|
206
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
207
|
+
PRIMARY KEY (issue_id, label),
|
|
208
|
+
FOREIGN KEY (issue_id) REFERENCES issues (id) ON DELETE CASCADE
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
-- Comments table
|
|
212
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
213
|
+
id TEXT PRIMARY KEY,
|
|
214
|
+
issue_id TEXT NOT NULL,
|
|
215
|
+
author TEXT NOT NULL,
|
|
216
|
+
content TEXT NOT NULL,
|
|
217
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
218
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
219
|
+
metadata TEXT, -- JSON for additional data
|
|
220
|
+
FOREIGN KEY (issue_id) REFERENCES issues (id) ON DELETE CASCADE
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
-- Sync base state (three-way merge baseline from last successful sync)
|
|
224
|
+
-- Note: issue_id does NOT have a foreign key constraint because sync baseline
|
|
225
|
+
-- can be saved before issues are persisted to the database (during sync operations)
|
|
226
|
+
CREATE TABLE IF NOT EXISTS sync_base_state (
|
|
227
|
+
issue_id TEXT PRIMARY KEY,
|
|
228
|
+
status TEXT NOT NULL,
|
|
229
|
+
assignee TEXT,
|
|
230
|
+
milestone TEXT,
|
|
231
|
+
description TEXT,
|
|
232
|
+
labels TEXT, -- JSON array of label strings
|
|
233
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
234
|
+
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
-- Sync metadata (overall sync state)
|
|
238
|
+
CREATE TABLE IF NOT EXISTS sync_metadata (
|
|
239
|
+
key TEXT PRIMARY KEY,
|
|
240
|
+
value TEXT NOT NULL,
|
|
241
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
-- File synchronization tracking
|
|
245
|
+
CREATE TABLE IF NOT EXISTS file_sync_state (
|
|
246
|
+
file_path TEXT PRIMARY KEY,
|
|
247
|
+
content_hash TEXT NOT NULL,
|
|
248
|
+
file_size INTEGER,
|
|
249
|
+
last_modified TIMESTAMP,
|
|
250
|
+
last_synced TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
-- Indexes for performance
|
|
254
|
+
CREATE INDEX IF NOT EXISTS idx_sync_base_state_synced_at ON sync_base_state (synced_at);
|
|
255
|
+
CREATE INDEX IF NOT EXISTS idx_milestones_project_id ON milestones (project_id);
|
|
256
|
+
CREATE INDEX IF NOT EXISTS idx_issues_project_id ON issues (project_id);
|
|
257
|
+
CREATE INDEX IF NOT EXISTS idx_issues_milestone_id ON issues (milestone_id);
|
|
258
|
+
CREATE INDEX IF NOT EXISTS idx_issues_assignee ON issues (assignee);
|
|
259
|
+
CREATE INDEX IF NOT EXISTS idx_issues_status ON issues (status);
|
|
260
|
+
CREATE INDEX IF NOT EXISTS idx_comments_issue_id ON comments (issue_id);
|
|
261
|
+
|
|
262
|
+
-- Triggers for updated_at timestamps
|
|
263
|
+
CREATE TRIGGER IF NOT EXISTS update_projects_timestamp
|
|
264
|
+
AFTER UPDATE ON projects
|
|
265
|
+
BEGIN
|
|
266
|
+
UPDATE projects SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
267
|
+
END;
|
|
268
|
+
|
|
269
|
+
CREATE TRIGGER IF NOT EXISTS update_milestones_timestamp
|
|
270
|
+
AFTER UPDATE ON milestones
|
|
271
|
+
BEGIN
|
|
272
|
+
UPDATE milestones SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
273
|
+
END;
|
|
274
|
+
|
|
275
|
+
CREATE TRIGGER IF NOT EXISTS update_issues_timestamp
|
|
276
|
+
AFTER UPDATE ON issues
|
|
277
|
+
BEGIN
|
|
278
|
+
UPDATE issues SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
279
|
+
END;
|
|
280
|
+
|
|
281
|
+
CREATE TRIGGER IF NOT EXISTS update_comments_timestamp
|
|
282
|
+
AFTER UPDATE ON comments
|
|
283
|
+
BEGIN
|
|
284
|
+
UPDATE comments SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
285
|
+
END;
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
conn = self._get_connection()
|
|
289
|
+
conn.executescript(schema_sql)
|
|
290
|
+
|
|
291
|
+
logger.info("Database schema initialized")
|
|
292
|
+
|
|
293
|
+
# Run migrations
|
|
294
|
+
self._run_migrations()
|
|
295
|
+
|
|
296
|
+
def _migration_archive_columns(self, cursor) -> list[str]:
|
|
297
|
+
"""Migration 1: add archived/archived_at to projects, milestones, issues."""
|
|
298
|
+
result = []
|
|
299
|
+
for table in ("projects", "milestones", "issues"):
|
|
300
|
+
cursor.execute(f"PRAGMA table_info({table})") # noqa: S608
|
|
301
|
+
if "archived" not in [col[1] for col in cursor.fetchall()]:
|
|
302
|
+
result.append(
|
|
303
|
+
f"ALTER TABLE {table} ADD COLUMN archived INTEGER DEFAULT 0;\n"
|
|
304
|
+
f"ALTER TABLE {table} ADD COLUMN archived_at TIMESTAMP NULL;"
|
|
305
|
+
)
|
|
306
|
+
return result
|
|
307
|
+
|
|
308
|
+
def _migration_sync_base_state(self, cursor) -> list[str]:
|
|
309
|
+
"""Migration 2: create sync_base_state table."""
|
|
310
|
+
cursor.execute(
|
|
311
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='sync_base_state'"
|
|
312
|
+
)
|
|
313
|
+
if cursor.fetchone():
|
|
314
|
+
return []
|
|
315
|
+
return [
|
|
316
|
+
"""
|
|
317
|
+
CREATE TABLE sync_base_state (
|
|
318
|
+
issue_id TEXT PRIMARY KEY,
|
|
319
|
+
status TEXT NOT NULL,
|
|
320
|
+
assignee TEXT,
|
|
321
|
+
milestone TEXT,
|
|
322
|
+
description TEXT,
|
|
323
|
+
labels TEXT,
|
|
324
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
325
|
+
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
326
|
+
);
|
|
327
|
+
CREATE INDEX idx_sync_base_state_synced_at ON sync_base_state (synced_at);
|
|
328
|
+
"""
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
def _migration_issue_remote_links(self, cursor) -> list[str]:
|
|
332
|
+
"""Migration 3: create issue_remote_links table."""
|
|
333
|
+
cursor.execute(
|
|
334
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='issue_remote_links'"
|
|
335
|
+
)
|
|
336
|
+
if cursor.fetchone():
|
|
337
|
+
return []
|
|
338
|
+
return [
|
|
339
|
+
"""
|
|
340
|
+
CREATE TABLE issue_remote_links (
|
|
341
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
342
|
+
issue_uuid TEXT NOT NULL,
|
|
343
|
+
backend_name TEXT NOT NULL,
|
|
344
|
+
remote_id TEXT NOT NULL,
|
|
345
|
+
linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
346
|
+
UNIQUE(issue_uuid, backend_name),
|
|
347
|
+
FOREIGN KEY (issue_uuid) REFERENCES issues (id) ON DELETE CASCADE
|
|
348
|
+
);
|
|
349
|
+
CREATE INDEX idx_issue_remote_links_backend ON issue_remote_links (backend_name);
|
|
350
|
+
CREATE INDEX idx_issue_remote_links_issue_uuid ON issue_remote_links (issue_uuid);
|
|
351
|
+
"""
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
def _migration_headline_column(self, cursor) -> list[str]:
|
|
355
|
+
"""Migration 4: add headline column to issues table."""
|
|
356
|
+
cursor.execute("PRAGMA table_info(issues)")
|
|
357
|
+
issue_columns = (
|
|
358
|
+
[row[1] for row in cursor.fetchall()] if cursor.fetchone() else []
|
|
359
|
+
)
|
|
360
|
+
if "headline" in issue_columns:
|
|
361
|
+
return []
|
|
362
|
+
return ["ALTER TABLE issues ADD COLUMN headline TEXT DEFAULT '';"]
|
|
363
|
+
|
|
364
|
+
def _migration_sync_base_state_columns(self, cursor) -> list[str]:
|
|
365
|
+
"""Migration 5: add headline and content columns to sync_base_state."""
|
|
366
|
+
cursor.execute("PRAGMA table_info(sync_base_state)")
|
|
367
|
+
columns = [row[1] for row in cursor.fetchall()] if cursor.fetchone() else []
|
|
368
|
+
if "headline" in columns or "content" in columns:
|
|
369
|
+
return []
|
|
370
|
+
return [
|
|
371
|
+
"""
|
|
372
|
+
ALTER TABLE sync_base_state ADD COLUMN headline TEXT DEFAULT '';
|
|
373
|
+
ALTER TABLE sync_base_state ADD COLUMN content TEXT DEFAULT '';
|
|
374
|
+
"""
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
def _migration_sync_metrics(self, cursor) -> list[str]:
|
|
378
|
+
"""Migration 6: create sync_metrics table."""
|
|
379
|
+
cursor.execute(
|
|
380
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='sync_metrics'"
|
|
381
|
+
)
|
|
382
|
+
if cursor.fetchone():
|
|
383
|
+
return []
|
|
384
|
+
return [
|
|
385
|
+
"""
|
|
386
|
+
CREATE TABLE sync_metrics (
|
|
387
|
+
id TEXT PRIMARY KEY,
|
|
388
|
+
operation_id TEXT NOT NULL UNIQUE,
|
|
389
|
+
backend_type TEXT NOT NULL,
|
|
390
|
+
duration_seconds REAL NOT NULL DEFAULT 0.0,
|
|
391
|
+
metrics_json TEXT NOT NULL, -- Full SyncMetrics.to_dict() as JSON
|
|
392
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
393
|
+
);
|
|
394
|
+
CREATE INDEX IF NOT EXISTS idx_sync_metrics_backend_type ON sync_metrics (backend_type);
|
|
395
|
+
CREATE INDEX IF NOT EXISTS idx_sync_metrics_created_at ON sync_metrics (created_at);
|
|
396
|
+
CREATE INDEX IF NOT EXISTS idx_sync_metrics_operation_id ON sync_metrics (operation_id);
|
|
397
|
+
"""
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
def _run_migrations(self):
|
|
401
|
+
"""Run database migrations for schema updates."""
|
|
402
|
+
conn = self._get_connection()
|
|
403
|
+
cursor = conn.cursor()
|
|
404
|
+
|
|
405
|
+
migrations = (
|
|
406
|
+
self._migration_archive_columns(cursor)
|
|
407
|
+
+ self._migration_sync_base_state(cursor)
|
|
408
|
+
+ self._migration_issue_remote_links(cursor)
|
|
409
|
+
+ self._migration_headline_column(cursor)
|
|
410
|
+
+ self._migration_sync_base_state_columns(cursor)
|
|
411
|
+
+ self._migration_sync_metrics(cursor)
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
for migration_sql in migrations:
|
|
415
|
+
try:
|
|
416
|
+
conn.executescript(migration_sql)
|
|
417
|
+
logger.info("applied_database_migration")
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logger.warning(
|
|
420
|
+
"migration_may_have_been_applied",
|
|
421
|
+
error=str(e),
|
|
422
|
+
severity="operational",
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
conn.commit()
|
|
426
|
+
|
|
427
|
+
def is_initialized(self) -> bool:
|
|
428
|
+
"""Check if database is properly initialized."""
|
|
429
|
+
try:
|
|
430
|
+
conn = self._get_connection()
|
|
431
|
+
result = conn.execute(
|
|
432
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='projects'"
|
|
433
|
+
).fetchone()
|
|
434
|
+
return result is not None
|
|
435
|
+
except Exception as e:
|
|
436
|
+
logger.error(
|
|
437
|
+
"Failed to check database initialization",
|
|
438
|
+
error=str(e),
|
|
439
|
+
severity="infrastructure",
|
|
440
|
+
)
|
|
441
|
+
return False
|
|
442
|
+
|
|
443
|
+
def close(self):
|
|
444
|
+
"""Close database connections."""
|
|
445
|
+
if hasattr(self._local, "connection"):
|
|
446
|
+
self._local.connection.close()
|
|
447
|
+
delattr(self._local, "connection")
|
|
448
|
+
|
|
449
|
+
def vacuum(self):
|
|
450
|
+
"""Optimize database."""
|
|
451
|
+
conn = self._get_connection()
|
|
452
|
+
conn.execute("VACUUM")
|
|
453
|
+
logger.info("Database vacuumed")
|
|
454
|
+
|
|
455
|
+
def database_exists(self) -> bool:
|
|
456
|
+
"""Check if database file exists and has tables."""
|
|
457
|
+
if not self.db_path.exists():
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
conn = self._get_connection()
|
|
462
|
+
# Check if our main tables exist
|
|
463
|
+
tables = conn.execute("""
|
|
464
|
+
SELECT name FROM sqlite_master
|
|
465
|
+
WHERE type='table' AND name IN ('issues', 'milestones', 'projects')
|
|
466
|
+
""").fetchall()
|
|
467
|
+
|
|
468
|
+
# We should have at least our core tables
|
|
469
|
+
return len(tables) >= 3
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.warning(
|
|
473
|
+
"Error checking database existence",
|
|
474
|
+
error=str(e),
|
|
475
|
+
severity="infrastructure",
|
|
476
|
+
)
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
def is_safe_for_writes(self) -> tuple[bool, str]:
|
|
480
|
+
"""Check if database is safe for write operations."""
|
|
481
|
+
try:
|
|
482
|
+
# Check database integrity
|
|
483
|
+
conn = self._get_connection()
|
|
484
|
+
try:
|
|
485
|
+
conn.execute("PRAGMA integrity_check").fetchone()
|
|
486
|
+
except sqlite3.DatabaseError as e:
|
|
487
|
+
return False, f"Database corruption detected: {e}"
|
|
488
|
+
|
|
489
|
+
return True, "Database ready for operations"
|
|
490
|
+
|
|
491
|
+
except Exception as e:
|
|
492
|
+
return False, f"Safety check failed: {e}"
|