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,207 @@
|
|
|
1
|
+
"""Sync state and file synchronization operations."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.common.errors import OperationType, safe_operation
|
|
7
|
+
from roadmap.common.logging import get_logger
|
|
8
|
+
|
|
9
|
+
from ..file_synchronizer import FileSynchronizer
|
|
10
|
+
from ..repositories import SyncStateRepository
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SyncStateStorage:
|
|
16
|
+
"""Handles sync state tracking and file synchronization operations."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
sync_state_repo: SyncStateRepository,
|
|
21
|
+
file_synchronizer: FileSynchronizer,
|
|
22
|
+
):
|
|
23
|
+
"""Initialize sync state storage.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
sync_state_repo: SyncStateRepository instance for sync state persistence
|
|
27
|
+
file_synchronizer: FileSynchronizer instance for file operations
|
|
28
|
+
"""
|
|
29
|
+
self._sync_state_repo = sync_state_repo
|
|
30
|
+
self._file_synchronizer = file_synchronizer
|
|
31
|
+
logger.debug("sync_state_storage_initialized")
|
|
32
|
+
|
|
33
|
+
# Sync state operations
|
|
34
|
+
def get_sync_state(self, key: str) -> str | None:
|
|
35
|
+
"""Get sync state value.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
key: State key identifier
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
str: State value or None if not found
|
|
42
|
+
"""
|
|
43
|
+
logger.debug("Getting sync state", key=key)
|
|
44
|
+
return self._sync_state_repo.get(key)
|
|
45
|
+
|
|
46
|
+
def set_sync_state(self, key: str, value: str):
|
|
47
|
+
"""Set sync state value.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
key: State key identifier
|
|
51
|
+
value: State value to store
|
|
52
|
+
"""
|
|
53
|
+
logger.debug("Setting sync state", key=key, value_length=len(value))
|
|
54
|
+
self._sync_state_repo.set(key, value)
|
|
55
|
+
|
|
56
|
+
# File sync operations
|
|
57
|
+
def get_file_sync_status(self, file_path: str) -> dict[str, Any] | None:
|
|
58
|
+
"""Get sync status for a file.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
file_path: Path to file
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
dict: Sync status data or None if not found
|
|
65
|
+
"""
|
|
66
|
+
logger.debug("Getting file sync status", file_path=file_path)
|
|
67
|
+
return self._file_synchronizer.get_file_sync_status(file_path)
|
|
68
|
+
|
|
69
|
+
@safe_operation(OperationType.UPDATE, "FileSync")
|
|
70
|
+
def update_file_sync_status(
|
|
71
|
+
self, file_path: str, content_hash: str, file_size: int, last_modified: Any
|
|
72
|
+
):
|
|
73
|
+
"""Update sync status for a file.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
file_path: Path to file
|
|
77
|
+
content_hash: Hash of file content
|
|
78
|
+
file_size: Size of file in bytes
|
|
79
|
+
last_modified: Last modification timestamp
|
|
80
|
+
"""
|
|
81
|
+
logger.debug(
|
|
82
|
+
"Updating file sync status", file_path=file_path, file_size=file_size
|
|
83
|
+
)
|
|
84
|
+
self._file_synchronizer.update_file_sync_status(
|
|
85
|
+
file_path, content_hash, file_size, last_modified
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def has_file_changed(self, file_path: Path) -> bool:
|
|
89
|
+
"""Check if file has changed since last sync.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_path: Path to file
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
bool: True if file has changed
|
|
96
|
+
"""
|
|
97
|
+
result = self._file_synchronizer.has_file_changed(file_path)
|
|
98
|
+
logger.debug("File change check", file_path=str(file_path), changed=result)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
@safe_operation(OperationType.SYNC, "Directory", retryable=True, max_retries=2)
|
|
102
|
+
def sync_directory_incremental(self, roadmap_dir: Path) -> dict[str, Any]:
|
|
103
|
+
"""Incrementally sync .roadmap directory to database.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
roadmap_dir: Path to .roadmap directory
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict: Sync results with files_synced count
|
|
110
|
+
"""
|
|
111
|
+
logger.info("Incremental sync started", roadmap_dir=str(roadmap_dir))
|
|
112
|
+
result = self._file_synchronizer.sync_directory_incremental(roadmap_dir)
|
|
113
|
+
logger.info(
|
|
114
|
+
"Incremental sync completed",
|
|
115
|
+
roadmap_dir=str(roadmap_dir),
|
|
116
|
+
files_synced=result.get("files_synced", 0),
|
|
117
|
+
)
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
@safe_operation(OperationType.SYNC, "Directory", retryable=True, max_retries=2)
|
|
121
|
+
def full_rebuild_from_git(self, roadmap_dir: Path) -> dict[str, Any]:
|
|
122
|
+
"""Full rebuild of database from git files.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
roadmap_dir: Path to .roadmap directory
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
dict: Rebuild results with files_processed count
|
|
129
|
+
"""
|
|
130
|
+
logger.info("Full rebuild from git started", roadmap_dir=str(roadmap_dir))
|
|
131
|
+
result = self._file_synchronizer.full_rebuild_from_git(roadmap_dir)
|
|
132
|
+
logger.info(
|
|
133
|
+
"Full rebuild completed",
|
|
134
|
+
roadmap_dir=str(roadmap_dir),
|
|
135
|
+
files_processed=result.get("files_processed", 0),
|
|
136
|
+
)
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
def should_do_full_rebuild(self, roadmap_dir: Path, threshold: int = 50) -> bool:
|
|
140
|
+
"""Determine if full rebuild is needed vs incremental sync.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
roadmap_dir: Path to .roadmap directory
|
|
144
|
+
threshold: Number of changed files before full rebuild recommended
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True if full rebuild is recommended
|
|
148
|
+
"""
|
|
149
|
+
result = self._file_synchronizer.should_do_full_rebuild(roadmap_dir, threshold)
|
|
150
|
+
logger.debug("Full rebuild check", roadmap_dir=str(roadmap_dir), result=result)
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
@safe_operation(OperationType.SYNC, "Directory")
|
|
154
|
+
def smart_sync(self, roadmap_dir: Path | None = None) -> dict[str, Any]:
|
|
155
|
+
"""Smart sync that chooses between incremental and full rebuild.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
roadmap_dir: Path to .roadmap directory. Defaults to .roadmap in cwd
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
dict: Sync results
|
|
162
|
+
"""
|
|
163
|
+
if roadmap_dir is None:
|
|
164
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
165
|
+
|
|
166
|
+
logger.info("Smart sync started", roadmap_dir=str(roadmap_dir))
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
if self.should_do_full_rebuild(roadmap_dir):
|
|
170
|
+
logger.info("Choosing full rebuild", roadmap_dir=str(roadmap_dir))
|
|
171
|
+
result = self.full_rebuild_from_git(roadmap_dir)
|
|
172
|
+
else:
|
|
173
|
+
logger.info("Choosing incremental sync", roadmap_dir=str(roadmap_dir))
|
|
174
|
+
result = self.sync_directory_incremental(roadmap_dir)
|
|
175
|
+
|
|
176
|
+
logger.info(
|
|
177
|
+
"Smart sync completed",
|
|
178
|
+
roadmap_dir=str(roadmap_dir),
|
|
179
|
+
result_keys=list(result.keys()),
|
|
180
|
+
)
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(
|
|
185
|
+
"Smart sync failed",
|
|
186
|
+
roadmap_dir=str(roadmap_dir),
|
|
187
|
+
error=str(e),
|
|
188
|
+
error_type=type(e).__name__,
|
|
189
|
+
)
|
|
190
|
+
return {"error": str(e), "files_failed": 1}
|
|
191
|
+
|
|
192
|
+
# File changes checking - needs state manager context
|
|
193
|
+
def has_file_changes(self, state_manager) -> bool:
|
|
194
|
+
"""Check if .roadmap/ files have changes since last sync.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
state_manager: StateManager instance for database access
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
bool: True if changes detected
|
|
201
|
+
"""
|
|
202
|
+
from .queries import QueryService
|
|
203
|
+
|
|
204
|
+
logger.debug("Checking file changes")
|
|
205
|
+
result = QueryService(state_manager).has_file_changes()
|
|
206
|
+
logger.debug("File changes check completed", has_changes=result)
|
|
207
|
+
return result
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Repository for persisting and querying sync metrics.
|
|
2
|
+
|
|
3
|
+
This module provides database persistence for sync operation metrics,
|
|
4
|
+
allowing historical tracking and analysis of sync operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import UTC, datetime, timedelta
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from roadmap.adapters.persistence.database_manager import DatabaseManager
|
|
13
|
+
from roadmap.common.logging import get_logger
|
|
14
|
+
from roadmap.core.observability.sync_metrics import SyncMetrics
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncMetricsRepository:
|
|
20
|
+
"""Repository for sync metrics persistence and queries."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, db_manager: DatabaseManager):
|
|
23
|
+
"""Initialize the repository.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
db_manager: DatabaseManager instance for database access
|
|
27
|
+
"""
|
|
28
|
+
self.db_manager = db_manager
|
|
29
|
+
|
|
30
|
+
def save(self, metrics: SyncMetrics) -> bool:
|
|
31
|
+
"""Save sync metrics to database.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
metrics: SyncMetrics object to persist
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if successful, False otherwise
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
record_id = str(uuid.uuid4())
|
|
41
|
+
metrics_dict = metrics.to_dict()
|
|
42
|
+
metrics_json = json.dumps(metrics_dict)
|
|
43
|
+
|
|
44
|
+
with self.db_manager.transaction() as conn:
|
|
45
|
+
conn.execute(
|
|
46
|
+
"""
|
|
47
|
+
INSERT INTO sync_metrics (
|
|
48
|
+
id,
|
|
49
|
+
operation_id,
|
|
50
|
+
backend_type,
|
|
51
|
+
duration_seconds,
|
|
52
|
+
metrics_json,
|
|
53
|
+
created_at
|
|
54
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
55
|
+
""",
|
|
56
|
+
(
|
|
57
|
+
record_id,
|
|
58
|
+
metrics.operation_id,
|
|
59
|
+
metrics.backend_type,
|
|
60
|
+
metrics.duration_seconds,
|
|
61
|
+
metrics_json,
|
|
62
|
+
datetime.now(UTC).isoformat(),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
logger.info(
|
|
67
|
+
"sync_metrics_saved",
|
|
68
|
+
operation_id=metrics.operation_id,
|
|
69
|
+
backend_type=metrics.backend_type,
|
|
70
|
+
duration_seconds=metrics.duration_seconds,
|
|
71
|
+
)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(
|
|
76
|
+
"sync_metrics_save_failed",
|
|
77
|
+
error=str(e),
|
|
78
|
+
operation_id=metrics.operation_id,
|
|
79
|
+
severity="operational",
|
|
80
|
+
)
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def get_latest(self, backend_type: str | None = None) -> SyncMetrics | None:
|
|
84
|
+
"""Get the most recent sync metrics.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
backend_type: Optional filter by backend type
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
SyncMetrics if found, None otherwise
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
conn = self.db_manager._get_connection()
|
|
94
|
+
|
|
95
|
+
query = "SELECT metrics_json FROM sync_metrics"
|
|
96
|
+
params = []
|
|
97
|
+
|
|
98
|
+
if backend_type:
|
|
99
|
+
query += " WHERE backend_type = ?"
|
|
100
|
+
params.append(backend_type)
|
|
101
|
+
|
|
102
|
+
query += " ORDER BY created_at DESC LIMIT 1"
|
|
103
|
+
|
|
104
|
+
cursor = conn.execute(query, params)
|
|
105
|
+
row = cursor.fetchone()
|
|
106
|
+
|
|
107
|
+
if not row:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
metrics_dict = json.loads(row[0])
|
|
111
|
+
return self._dict_to_metrics(metrics_dict)
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(
|
|
115
|
+
"sync_metrics_get_latest_failed",
|
|
116
|
+
error=str(e),
|
|
117
|
+
backend_type=backend_type,
|
|
118
|
+
severity="operational",
|
|
119
|
+
)
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def list_by_date(
|
|
123
|
+
self, backend_type: str | None = None, days: int = 7
|
|
124
|
+
) -> list[SyncMetrics]:
|
|
125
|
+
"""List sync metrics from the last N days.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
backend_type: Optional filter by backend type
|
|
129
|
+
days: Number of days to look back (default: 7)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
List of SyncMetrics objects
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
conn = self.db_manager._get_connection()
|
|
136
|
+
cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
|
|
137
|
+
|
|
138
|
+
query = "SELECT metrics_json FROM sync_metrics WHERE created_at >= ?"
|
|
139
|
+
params = [cutoff_date]
|
|
140
|
+
|
|
141
|
+
if backend_type:
|
|
142
|
+
query += " AND backend_type = ?"
|
|
143
|
+
params.append(backend_type)
|
|
144
|
+
|
|
145
|
+
query += " ORDER BY created_at DESC"
|
|
146
|
+
|
|
147
|
+
cursor = conn.execute(query, params)
|
|
148
|
+
rows = cursor.fetchall()
|
|
149
|
+
|
|
150
|
+
metrics_list = []
|
|
151
|
+
for row in rows:
|
|
152
|
+
try:
|
|
153
|
+
metrics_dict = json.loads(row[0])
|
|
154
|
+
metrics = self._dict_to_metrics(metrics_dict)
|
|
155
|
+
metrics_list.append(metrics)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.warning(
|
|
158
|
+
"sync_metrics_deserialization_failed",
|
|
159
|
+
error=str(e),
|
|
160
|
+
severity="operational",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return metrics_list
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(
|
|
167
|
+
"sync_metrics_list_by_date_failed",
|
|
168
|
+
error=str(e),
|
|
169
|
+
backend_type=backend_type,
|
|
170
|
+
days=days,
|
|
171
|
+
severity="operational",
|
|
172
|
+
)
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
def get_by_operation_id(self, operation_id: str) -> SyncMetrics | None:
|
|
176
|
+
"""Get metrics for a specific operation ID.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
operation_id: The operation ID to look up
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
SyncMetrics if found, None otherwise
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
conn = self.db_manager._get_connection()
|
|
186
|
+
|
|
187
|
+
cursor = conn.execute(
|
|
188
|
+
"SELECT metrics_json FROM sync_metrics WHERE operation_id = ? LIMIT 1",
|
|
189
|
+
(operation_id,),
|
|
190
|
+
)
|
|
191
|
+
row = cursor.fetchone()
|
|
192
|
+
|
|
193
|
+
if not row:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
metrics_dict = json.loads(row[0])
|
|
197
|
+
return self._dict_to_metrics(metrics_dict)
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(
|
|
201
|
+
"sync_metrics_get_by_operation_id_failed",
|
|
202
|
+
error=str(e),
|
|
203
|
+
operation_id=operation_id,
|
|
204
|
+
severity="operational",
|
|
205
|
+
)
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def get_statistics(
|
|
209
|
+
self, backend_type: str | None = None, days: int = 30
|
|
210
|
+
) -> dict[str, Any]:
|
|
211
|
+
"""Get aggregate statistics for sync metrics.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
backend_type: Optional filter by backend type
|
|
215
|
+
days: Number of days to analyze (default: 30)
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary with statistics
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
conn = self.db_manager._get_connection()
|
|
222
|
+
cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
|
|
223
|
+
|
|
224
|
+
query = "SELECT metrics_json FROM sync_metrics WHERE created_at >= ?"
|
|
225
|
+
params = [cutoff_date]
|
|
226
|
+
|
|
227
|
+
if backend_type:
|
|
228
|
+
query += " AND backend_type = ?"
|
|
229
|
+
params.append(backend_type)
|
|
230
|
+
|
|
231
|
+
cursor = conn.execute(query, params)
|
|
232
|
+
rows = cursor.fetchall()
|
|
233
|
+
|
|
234
|
+
if not rows:
|
|
235
|
+
return {
|
|
236
|
+
"total_syncs": 0,
|
|
237
|
+
"avg_duration_seconds": 0.0,
|
|
238
|
+
"total_duplicates_detected": 0,
|
|
239
|
+
"total_conflicts_detected": 0,
|
|
240
|
+
"total_errors": 0,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
total_duration = 0.0
|
|
244
|
+
total_duplicates = 0
|
|
245
|
+
total_conflicts = 0
|
|
246
|
+
total_errors = 0
|
|
247
|
+
total_syncs = 0
|
|
248
|
+
|
|
249
|
+
for row in rows:
|
|
250
|
+
try:
|
|
251
|
+
metrics_dict = json.loads(row[0])
|
|
252
|
+
total_duration += metrics_dict.get("duration_seconds", 0.0)
|
|
253
|
+
total_duplicates += metrics_dict.get("duplicates_detected", 0)
|
|
254
|
+
total_conflicts += metrics_dict.get("conflicts_detected", 0)
|
|
255
|
+
total_errors += metrics_dict.get("errors_count", 0)
|
|
256
|
+
total_syncs += 1
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.warning(
|
|
259
|
+
"sync_metrics_stats_parse_failed",
|
|
260
|
+
error=str(e),
|
|
261
|
+
severity="operational",
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
"total_syncs": total_syncs,
|
|
266
|
+
"avg_duration_seconds": (
|
|
267
|
+
total_duration / total_syncs if total_syncs > 0 else 0.0
|
|
268
|
+
),
|
|
269
|
+
"total_duplicates_detected": total_duplicates,
|
|
270
|
+
"total_conflicts_detected": total_conflicts,
|
|
271
|
+
"total_errors": total_errors,
|
|
272
|
+
"backends": backend_type or "all",
|
|
273
|
+
"period_days": days,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(
|
|
278
|
+
"sync_metrics_get_statistics_failed",
|
|
279
|
+
error=str(e),
|
|
280
|
+
backend_type=backend_type,
|
|
281
|
+
days=days,
|
|
282
|
+
severity="operational",
|
|
283
|
+
)
|
|
284
|
+
return {}
|
|
285
|
+
|
|
286
|
+
def delete_old_metrics(self, days: int = 90) -> int:
|
|
287
|
+
"""Delete metrics older than N days.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
days: Number of days to keep (default: 90)
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Number of records deleted
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
|
|
297
|
+
|
|
298
|
+
with self.db_manager.transaction() as conn:
|
|
299
|
+
cursor = conn.execute(
|
|
300
|
+
"DELETE FROM sync_metrics WHERE created_at < ?",
|
|
301
|
+
(cutoff_date,),
|
|
302
|
+
)
|
|
303
|
+
deleted_count = cursor.rowcount
|
|
304
|
+
|
|
305
|
+
logger.info(
|
|
306
|
+
"sync_metrics_old_records_deleted",
|
|
307
|
+
deleted_count=deleted_count,
|
|
308
|
+
days=days,
|
|
309
|
+
)
|
|
310
|
+
return deleted_count
|
|
311
|
+
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(
|
|
314
|
+
"sync_metrics_delete_old_failed",
|
|
315
|
+
error=str(e),
|
|
316
|
+
days=days,
|
|
317
|
+
severity="operational",
|
|
318
|
+
)
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _dict_to_metrics(metrics_dict: dict[str, Any]) -> SyncMetrics:
|
|
323
|
+
"""Convert dictionary back to SyncMetrics object.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
metrics_dict: Dictionary from metrics_json
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
SyncMetrics object
|
|
330
|
+
"""
|
|
331
|
+
# Extract main fields
|
|
332
|
+
metrics = SyncMetrics(
|
|
333
|
+
operation_id=metrics_dict.get("operation_id", ""),
|
|
334
|
+
backend_type=metrics_dict.get("backend_type", ""),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Set all tracked fields from dictionary
|
|
338
|
+
for key, value in metrics_dict.items():
|
|
339
|
+
if hasattr(metrics, key):
|
|
340
|
+
setattr(metrics, key, value)
|
|
341
|
+
|
|
342
|
+
return metrics
|