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,110 @@
|
|
|
1
|
+
"""Normalizes remote issue state for comparison with local state.
|
|
2
|
+
|
|
3
|
+
Extracted from SyncStateComparator to separate state normalization logic.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from structlog import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RemoteStateNormalizer:
|
|
15
|
+
"""Normalizes remote issue state for sync operations."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def normalize_remote_state(remote: Any) -> dict[str, Any] | None:
|
|
19
|
+
"""Normalize remote issue state to standard format.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
remote: Remote issue object or dict
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Normalized state dict, or None if invalid
|
|
26
|
+
"""
|
|
27
|
+
if remote is None:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if isinstance(remote, dict):
|
|
32
|
+
return remote.copy()
|
|
33
|
+
|
|
34
|
+
# Convert object to dict
|
|
35
|
+
state = {}
|
|
36
|
+
|
|
37
|
+
# Extract standard fields
|
|
38
|
+
for field in [
|
|
39
|
+
"id",
|
|
40
|
+
"status",
|
|
41
|
+
"title",
|
|
42
|
+
"content",
|
|
43
|
+
"labels",
|
|
44
|
+
"assignee",
|
|
45
|
+
"milestone",
|
|
46
|
+
]:
|
|
47
|
+
value = getattr(remote, field, None)
|
|
48
|
+
if value is not None:
|
|
49
|
+
state[field] = value
|
|
50
|
+
|
|
51
|
+
# Handle timestamps
|
|
52
|
+
for ts_field in ["updated_at", "created_at"]:
|
|
53
|
+
if hasattr(remote, ts_field):
|
|
54
|
+
state[ts_field] = getattr(remote, ts_field)
|
|
55
|
+
|
|
56
|
+
return state if state else None
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.debug(
|
|
60
|
+
"normalize_remote_state_failed",
|
|
61
|
+
error=str(e),
|
|
62
|
+
remote_type=type(remote).__name__,
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def extract_timestamp(
|
|
68
|
+
remote: Any, field_name: str = "updated_at"
|
|
69
|
+
) -> datetime | None:
|
|
70
|
+
"""Extract timestamp from remote issue.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
remote: Remote issue object or dict
|
|
74
|
+
field_name: Name of timestamp field (default: "updated_at")
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Datetime object, or None if not found/invalid
|
|
78
|
+
"""
|
|
79
|
+
if remote is None:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
if isinstance(remote, dict):
|
|
84
|
+
value = remote.get(field_name)
|
|
85
|
+
else:
|
|
86
|
+
value = getattr(remote, field_name, None)
|
|
87
|
+
|
|
88
|
+
if value is None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
if isinstance(value, datetime):
|
|
92
|
+
return value
|
|
93
|
+
|
|
94
|
+
if isinstance(value, str):
|
|
95
|
+
# Try ISO format
|
|
96
|
+
try:
|
|
97
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
98
|
+
except (ValueError, AttributeError):
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.debug(
|
|
105
|
+
"normalize_state_failed",
|
|
106
|
+
operation="normalize_state",
|
|
107
|
+
error=str(e),
|
|
108
|
+
action="Returning None",
|
|
109
|
+
)
|
|
110
|
+
return None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Retry policy with exponential backoff strategy."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RetryPolicy:
|
|
7
|
+
"""Simple exponential backoff retry policy.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
rp = RetryPolicy(max_retries=3, base_delay=0.5, factor=2.0)
|
|
11
|
+
for attempt in range(1, rp.max_retries + 1):
|
|
12
|
+
try:
|
|
13
|
+
...
|
|
14
|
+
except Exception:
|
|
15
|
+
if attempt == rp.max_retries:
|
|
16
|
+
raise
|
|
17
|
+
time.sleep(rp.get_backoff_delay(attempt))
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self, max_retries: int = 3, base_delay: float = 0.5, factor: float = 2.0
|
|
22
|
+
):
|
|
23
|
+
"""Initialize RetryPolicy.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
max_retries: Maximum number of retry attempts.
|
|
27
|
+
base_delay: Initial delay in seconds.
|
|
28
|
+
factor: Exponential factor for delay increase.
|
|
29
|
+
"""
|
|
30
|
+
self.max_retries = max_retries
|
|
31
|
+
self.base_delay = base_delay
|
|
32
|
+
self.factor = factor
|
|
33
|
+
|
|
34
|
+
def get_backoff_delay(self, attempt: int) -> float:
|
|
35
|
+
"""Get delay in seconds for a given attempt (1-based)."""
|
|
36
|
+
if attempt <= 1:
|
|
37
|
+
return 0.0
|
|
38
|
+
return self.base_delay * (self.factor ** (attempt - 1))
|
|
39
|
+
|
|
40
|
+
def sleep_for_attempt(self, attempt: int) -> None:
|
|
41
|
+
"""Sleep for the appropriate backoff delay based on attempt number.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
attempt: The attempt number (1-based).
|
|
45
|
+
"""
|
|
46
|
+
delay = self.get_backoff_delay(attempt)
|
|
47
|
+
if delay > 0:
|
|
48
|
+
time.sleep(delay)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ["RetryPolicy"]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Base class for all health validators.
|
|
2
|
+
|
|
3
|
+
Provides consistent error handling, logging, and status reporting patterns
|
|
4
|
+
for health check validators throughout the application.
|
|
5
|
+
|
|
6
|
+
All validators should inherit from BaseValidator and implement:
|
|
7
|
+
- get_check_name(): Return unique check identifier
|
|
8
|
+
- perform_check(): Return (status, message) tuple
|
|
9
|
+
|
|
10
|
+
The base class handles error handling and logging automatically.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
|
|
15
|
+
from roadmap.common.logging import get_logger
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HealthStatus:
|
|
21
|
+
"""Health status constants for validator responses."""
|
|
22
|
+
|
|
23
|
+
HEALTHY = "healthy"
|
|
24
|
+
DEGRADED = "degraded"
|
|
25
|
+
UNHEALTHY = "unhealthy"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseValidator(ABC):
|
|
29
|
+
"""Abstract base class for all health validators.
|
|
30
|
+
|
|
31
|
+
Provides:
|
|
32
|
+
- Consistent error handling and logging
|
|
33
|
+
- Standard status/message return format
|
|
34
|
+
- Simplified subclass implementation (business logic only)
|
|
35
|
+
|
|
36
|
+
Subclasses should override:
|
|
37
|
+
- get_check_name(): Return the unique name of this check
|
|
38
|
+
- perform_check(): Return (status, message) tuple or raise exceptions
|
|
39
|
+
|
|
40
|
+
The check() method wraps perform_check() with try/except, logging,
|
|
41
|
+
and error handling so subclasses focus on business logic only.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
class RoadmapDirectoryValidator(BaseValidator):
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_check_name() -> str:
|
|
47
|
+
return "roadmap_directory"
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def perform_check() -> Tuple[str, str]:
|
|
51
|
+
roadmap_dir = Path(".roadmap")
|
|
52
|
+
if not roadmap_dir.exists():
|
|
53
|
+
return HealthStatus.DEGRADED, ".roadmap not found"
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
test_file = roadmap_dir / ".test"
|
|
57
|
+
test_file.touch()
|
|
58
|
+
test_file.unlink()
|
|
59
|
+
except OSError:
|
|
60
|
+
return HealthStatus.DEGRADED, "Directory not writable"
|
|
61
|
+
|
|
62
|
+
return HealthStatus.HEALTHY, "Accessible and writable"
|
|
63
|
+
|
|
64
|
+
# Usage:
|
|
65
|
+
status, message = RoadmapDirectoryValidator.check()
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get_check_name() -> str:
|
|
71
|
+
"""Return the unique name of this check.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Check name as string (e.g., "roadmap_directory", "git_repository")
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def perform_check() -> tuple[str, str]:
|
|
81
|
+
"""Perform the actual check logic.
|
|
82
|
+
|
|
83
|
+
Should return a tuple of (status, message) where:
|
|
84
|
+
- status is one of: HealthStatus.HEALTHY, .DEGRADED, .UNHEALTHY
|
|
85
|
+
- message is a human-readable description
|
|
86
|
+
|
|
87
|
+
May raise exceptions for error conditions. The check() method
|
|
88
|
+
will catch them and return UNHEALTHY status.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple of (status, message)
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
Any exception on error conditions (will be caught and logged)
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def check(cls) -> tuple[str, str]:
|
|
100
|
+
"""Execute the check with standard error handling and logging.
|
|
101
|
+
|
|
102
|
+
Wraps perform_check() with:
|
|
103
|
+
- Exception handling
|
|
104
|
+
- Automatic logging at appropriate levels
|
|
105
|
+
- Error context enrichment
|
|
106
|
+
- Consistent status reporting
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Tuple of (status, message)
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
status, message = cls.perform_check()
|
|
113
|
+
logger.debug(
|
|
114
|
+
"health_check_completed",
|
|
115
|
+
check_name=cls.get_check_name(),
|
|
116
|
+
status=status,
|
|
117
|
+
)
|
|
118
|
+
return status, message
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(
|
|
121
|
+
"health_check_failed",
|
|
122
|
+
check_name=cls.get_check_name(),
|
|
123
|
+
error=str(e),
|
|
124
|
+
error_type=type(e).__name__,
|
|
125
|
+
severity="system_error",
|
|
126
|
+
)
|
|
127
|
+
return (
|
|
128
|
+
HealthStatus.UNHEALTHY,
|
|
129
|
+
f"Error checking {cls.get_check_name()}: {e}",
|
|
130
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Data integrity validators for health checks."""
|
|
2
|
+
|
|
3
|
+
from ._utils import BackupScanResult, extract_issue_id
|
|
4
|
+
from .archivable_issues_validator import ArchivableIssuesValidator
|
|
5
|
+
from .archivable_milestones_validator import ArchivableMilestonesValidator
|
|
6
|
+
from .backup_validator import BackupValidator
|
|
7
|
+
from .data_integrity_validator import DataIntegrityValidator
|
|
8
|
+
from .duplicate_issues_validator import DuplicateIssuesValidator
|
|
9
|
+
from .duplicate_milestones_validator import DuplicateMilestonesValidator
|
|
10
|
+
from .folder_structure_validator import FolderStructureValidator
|
|
11
|
+
from .missing_headlines_validator import MissingHeadlinesValidator
|
|
12
|
+
from .orphaned_issues_validator import OrphanedIssuesValidator
|
|
13
|
+
from .orphaned_milestones_validator import OrphanedMilestonesValidator
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BackupScanResult",
|
|
17
|
+
"extract_issue_id",
|
|
18
|
+
"DuplicateIssuesValidator",
|
|
19
|
+
"DuplicateMilestonesValidator",
|
|
20
|
+
"FolderStructureValidator",
|
|
21
|
+
"BackupValidator",
|
|
22
|
+
"ArchivableIssuesValidator",
|
|
23
|
+
"ArchivableMilestonesValidator",
|
|
24
|
+
"DataIntegrityValidator",
|
|
25
|
+
"OrphanedIssuesValidator",
|
|
26
|
+
"OrphanedMilestonesValidator",
|
|
27
|
+
"MissingHeadlinesValidator",
|
|
28
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Shared utilities for validators."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BackupScanResult(TypedDict):
|
|
8
|
+
"""Type definition for backup scan results."""
|
|
9
|
+
|
|
10
|
+
files_to_delete: list[dict]
|
|
11
|
+
total_size_bytes: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_issue_id(filename: str) -> str | None:
|
|
15
|
+
"""Extract issue ID from filename (first part before the dashes and title).
|
|
16
|
+
|
|
17
|
+
Issue IDs are 8 hex characters.
|
|
18
|
+
"""
|
|
19
|
+
match = re.match(r"^([a-f0-9]{8})", filename)
|
|
20
|
+
return match.group(1) if match else None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Validator for archivable issues."""
|
|
2
|
+
|
|
3
|
+
from roadmap.common.logging import get_logger
|
|
4
|
+
from roadmap.common.utils.timezone_utils import now_utc
|
|
5
|
+
from roadmap.core.services.validator_base import HealthStatus
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ArchivableIssuesValidator:
|
|
11
|
+
"""Validator for archivable issues."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def scan_for_archivable_issues(core, threshold_days: int = 30) -> list[dict]:
|
|
15
|
+
"""Scan for issues that should be archived (closed >threshold_days ago).
|
|
16
|
+
|
|
17
|
+
Returns a list of issue dicts with id, title, status, closed date, and days_since_close.
|
|
18
|
+
"""
|
|
19
|
+
archivable = []
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
issues = core.issue_service.list_issues()
|
|
23
|
+
now = now_utc()
|
|
24
|
+
|
|
25
|
+
for issue in issues:
|
|
26
|
+
# Check if issue is closed (by status or by completed_date)
|
|
27
|
+
if issue.status.value == "closed" or issue.actual_end_date:
|
|
28
|
+
close_date = issue.actual_end_date or now
|
|
29
|
+
days_since_close = (now - close_date).days
|
|
30
|
+
|
|
31
|
+
if days_since_close > threshold_days:
|
|
32
|
+
archivable.append(
|
|
33
|
+
{
|
|
34
|
+
"id": issue.id,
|
|
35
|
+
"title": issue.title,
|
|
36
|
+
"status": issue.status.value,
|
|
37
|
+
"closed_date": close_date.isoformat()
|
|
38
|
+
if close_date
|
|
39
|
+
else None,
|
|
40
|
+
"days_since_close": days_since_close,
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.debug("scan_archivable_issues_error", error=str(e))
|
|
45
|
+
|
|
46
|
+
return archivable
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def check_archivable_issues(core) -> tuple[str, str]:
|
|
50
|
+
"""Check for archivable issues.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Tuple of (status, message) describing the health check result
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
archivable = ArchivableIssuesValidator.scan_for_archivable_issues(core)
|
|
57
|
+
|
|
58
|
+
if not archivable:
|
|
59
|
+
logger.debug("health_check_archivable_issues", status="none")
|
|
60
|
+
return HealthStatus.HEALTHY, "No issues to archive"
|
|
61
|
+
|
|
62
|
+
message = (
|
|
63
|
+
f"ℹ️ {len(archivable)} issue(s) eligible for archival "
|
|
64
|
+
"(closed >30 days ago): Consider archiving old issues"
|
|
65
|
+
)
|
|
66
|
+
logger.info("health_check_archivable_issues", count=len(archivable))
|
|
67
|
+
return HealthStatus.DEGRADED, message
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.debug("health_check_archivable_issues_failed", error=str(e))
|
|
71
|
+
return HealthStatus.HEALTHY, "Could not check for archivable issues"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Validator for archivable milestones."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from roadmap.common.logging import get_logger
|
|
6
|
+
from roadmap.core.services.validator_base import HealthStatus
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArchivableMilestonesValidator:
|
|
12
|
+
"""Validator for archivable milestones."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def scan_for_archivable_milestones(core, threshold_days: int = 14) -> list[dict]:
|
|
16
|
+
"""Scan for milestones that should be archived (completed >threshold_days ago).
|
|
17
|
+
|
|
18
|
+
Returns a list of milestone dicts with name, status, closed date, issue count, and days_since_close.
|
|
19
|
+
"""
|
|
20
|
+
archivable = []
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
milestones = core.milestone_service.list_milestones()
|
|
24
|
+
now = datetime.now(UTC)
|
|
25
|
+
|
|
26
|
+
for milestone in milestones:
|
|
27
|
+
if milestone.status.value == "closed" and milestone.closed_at:
|
|
28
|
+
days_since_close = (now - milestone.closed_at).days
|
|
29
|
+
|
|
30
|
+
if days_since_close > threshold_days:
|
|
31
|
+
archivable.append(
|
|
32
|
+
{
|
|
33
|
+
"name": milestone.name,
|
|
34
|
+
"status": milestone.status.value,
|
|
35
|
+
"closed_date": milestone.closed_at.isoformat(),
|
|
36
|
+
"days_since_close": days_since_close,
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.debug("scan_archivable_milestones_error", error=str(e))
|
|
41
|
+
|
|
42
|
+
return archivable
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def check_archivable_milestones(core) -> tuple[str, str]:
|
|
46
|
+
"""Check for archivable milestones.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple of (status, message) describing the health check result
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
archivable = ArchivableMilestonesValidator.scan_for_archivable_milestones(
|
|
53
|
+
core
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if not archivable:
|
|
57
|
+
logger.debug("health_check_archivable_milestones", status="none")
|
|
58
|
+
return HealthStatus.HEALTHY, "No milestones to archive"
|
|
59
|
+
|
|
60
|
+
message = (
|
|
61
|
+
f"ℹ️ {len(archivable)} milestone(s) eligible for archival "
|
|
62
|
+
"(closed >14 days ago): Consider archiving old milestones"
|
|
63
|
+
)
|
|
64
|
+
logger.info("health_check_archivable_milestones", count=len(archivable))
|
|
65
|
+
return HealthStatus.DEGRADED, message
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.debug("health_check_archivable_milestones_failed", error=str(e))
|
|
69
|
+
return HealthStatus.HEALTHY, "Could not check for archivable milestones"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Validator for old backup files."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from roadmap.common.logging import get_logger
|
|
6
|
+
|
|
7
|
+
from . import BackupScanResult
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BackupValidator:
|
|
13
|
+
"""Validator for old backup files."""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def scan_for_old_backups(backups_dir: Path, keep: int = 10) -> BackupScanResult:
|
|
17
|
+
"""Scan for old backup files that could be deleted.
|
|
18
|
+
|
|
19
|
+
Returns a dict with:
|
|
20
|
+
- 'files_to_delete': List of backup files that exceed the keep threshold
|
|
21
|
+
- 'total_size_bytes': Total size of files that could be deleted (int)
|
|
22
|
+
"""
|
|
23
|
+
result: BackupScanResult = {"files_to_delete": [], "total_size_bytes": 0}
|
|
24
|
+
|
|
25
|
+
if not backups_dir.exists():
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
backup_files = list(backups_dir.glob("*.backup.md"))
|
|
29
|
+
if not backup_files:
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
# Group backups by issue ID
|
|
33
|
+
backups_by_issue = {}
|
|
34
|
+
for backup_file in backup_files:
|
|
35
|
+
parts = backup_file.stem.split("_")
|
|
36
|
+
issue_key = "_".join(parts[:-1])
|
|
37
|
+
|
|
38
|
+
if issue_key not in backups_by_issue:
|
|
39
|
+
backups_by_issue[issue_key] = []
|
|
40
|
+
|
|
41
|
+
stat = backup_file.stat()
|
|
42
|
+
backups_by_issue[issue_key].append(
|
|
43
|
+
{
|
|
44
|
+
"path": backup_file,
|
|
45
|
+
"mtime": stat.st_mtime,
|
|
46
|
+
"size": stat.st_size,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Find files that exceed keep threshold
|
|
51
|
+
for _issue_key, backups in backups_by_issue.items():
|
|
52
|
+
backups.sort(key=lambda x: x["mtime"], reverse=True)
|
|
53
|
+
|
|
54
|
+
for idx, backup in enumerate(backups):
|
|
55
|
+
if idx >= keep:
|
|
56
|
+
result["files_to_delete"].append(backup)
|
|
57
|
+
result["total_size_bytes"] += backup["size"]
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def check_old_backups() -> tuple[str, str]:
|
|
63
|
+
"""Check for old backup files.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Tuple of (status, message) describing the health check result
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
from roadmap.core.services.validator_base import HealthStatus
|
|
70
|
+
|
|
71
|
+
backups_dir = Path(".roadmap/backups")
|
|
72
|
+
old_backups = BackupValidator.scan_for_old_backups(backups_dir)
|
|
73
|
+
|
|
74
|
+
if not old_backups["files_to_delete"]:
|
|
75
|
+
logger.debug("health_check_old_backups", status="none")
|
|
76
|
+
return HealthStatus.HEALTHY, "No old backups to clean up"
|
|
77
|
+
|
|
78
|
+
count = len(old_backups["files_to_delete"])
|
|
79
|
+
size_mb = old_backups["total_size_bytes"] / (1024 * 1024)
|
|
80
|
+
message = (
|
|
81
|
+
f"ℹ️ {count} old backup file(s) could be deleted "
|
|
82
|
+
f"(~{size_mb:.1f} MB saved): Run 'roadmap cleanup' to remove"
|
|
83
|
+
)
|
|
84
|
+
logger.info("health_check_old_backups", count=count, size_mb=size_mb)
|
|
85
|
+
return HealthStatus.HEALTHY, message
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.debug("health_check_old_backups_failed", error=str(e))
|
|
89
|
+
return HealthStatus.HEALTHY, "Could not check for old backups"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Validator for data integrity."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from roadmap.common.logging import get_logger
|
|
6
|
+
from roadmap.core.services.validator_base import HealthStatus
|
|
7
|
+
from roadmap.infrastructure.persistence_gateway import PersistenceGateway
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataIntegrityValidator:
|
|
13
|
+
"""Validator for data integrity."""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def scan_for_data_integrity_issues(
|
|
17
|
+
issues_dir: Path,
|
|
18
|
+
) -> dict[str, list[str]]:
|
|
19
|
+
"""Scan for data integrity issues (orphaned/missing files).
|
|
20
|
+
|
|
21
|
+
Returns a dict with:
|
|
22
|
+
- 'malformed_files': List of files that couldn't be parsed
|
|
23
|
+
"""
|
|
24
|
+
result = {"malformed_files": []}
|
|
25
|
+
|
|
26
|
+
if not issues_dir.exists():
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
# Scan all issue files recursively
|
|
30
|
+
for issue_file in issues_dir.rglob("*.md"):
|
|
31
|
+
if ".backup" in issue_file.name:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
PersistenceGateway.parse_issue_file(issue_file)
|
|
36
|
+
except Exception:
|
|
37
|
+
# File couldn't be parsed
|
|
38
|
+
result["malformed_files"].append(
|
|
39
|
+
str(issue_file.relative_to(issues_dir))
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def check_data_integrity() -> tuple[str, str]:
|
|
46
|
+
"""Check data integrity.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple of (status, message) describing the health check result
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
issues_dir = Path(".roadmap/issues")
|
|
53
|
+
if not issues_dir.exists():
|
|
54
|
+
return (
|
|
55
|
+
HealthStatus.HEALTHY,
|
|
56
|
+
"Issues directory not found (not initialized yet)",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
integrity_issues = DataIntegrityValidator.scan_for_data_integrity_issues(
|
|
60
|
+
issues_dir
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not integrity_issues["malformed_files"]:
|
|
64
|
+
logger.debug("health_check_data_integrity", status="healthy")
|
|
65
|
+
return HealthStatus.HEALTHY, "No data integrity issues found"
|
|
66
|
+
|
|
67
|
+
message = (
|
|
68
|
+
f"⚠️ {len(integrity_issues['malformed_files'])} malformed file(s) detected: "
|
|
69
|
+
"These files couldn't be parsed - manual review required"
|
|
70
|
+
)
|
|
71
|
+
logger.warning(
|
|
72
|
+
"health_check_data_integrity",
|
|
73
|
+
count=len(integrity_issues["malformed_files"]),
|
|
74
|
+
severity="operational",
|
|
75
|
+
)
|
|
76
|
+
return HealthStatus.DEGRADED, message
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(
|
|
80
|
+
"health_check_data_integrity_failed",
|
|
81
|
+
error=str(e),
|
|
82
|
+
severity="system_error",
|
|
83
|
+
)
|
|
84
|
+
return HealthStatus.UNHEALTHY, f"Error checking data integrity: {e}"
|