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,483 @@
|
|
|
1
|
+
"""Cleanup command - comprehensive roadmap maintenance and optimization."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.presentation.cleanup_presenter import CleanupPresenter
|
|
8
|
+
from roadmap.common.console import get_console
|
|
9
|
+
from roadmap.common.logging import verbose_output
|
|
10
|
+
from roadmap.common.models import CleanupParams
|
|
11
|
+
from roadmap.core.services.health.backup_cleanup_service import BackupCleanupService
|
|
12
|
+
from roadmap.core.services.health.file_repair_service import FileRepairService
|
|
13
|
+
from roadmap.core.services.validators import (
|
|
14
|
+
DataIntegrityValidator,
|
|
15
|
+
DuplicateIssuesValidator,
|
|
16
|
+
FolderStructureValidator,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
console = get_console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.option(
|
|
24
|
+
"--keep",
|
|
25
|
+
type=int,
|
|
26
|
+
default=10,
|
|
27
|
+
help="Number of most recent backups to keep per issue (default: 10)",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--days",
|
|
31
|
+
type=int,
|
|
32
|
+
help="Delete backups older than this many days (default: no age-based limit)",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--dry-run",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="Preview all cleanup actions without making changes",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--force",
|
|
41
|
+
is_flag=True,
|
|
42
|
+
help="Skip confirmation prompts",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"--backups-only",
|
|
46
|
+
is_flag=True,
|
|
47
|
+
help="Only clean up old backup files (skip folder and duplicate fixes)",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--check-folders",
|
|
51
|
+
is_flag=True,
|
|
52
|
+
help="Only check/report folder structure issues",
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"--check-duplicates",
|
|
56
|
+
is_flag=True,
|
|
57
|
+
help="Only check/report duplicate issues",
|
|
58
|
+
)
|
|
59
|
+
@click.option(
|
|
60
|
+
"--check-malformed",
|
|
61
|
+
is_flag=True,
|
|
62
|
+
help="Only check/report malformed YAML files",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--verbose",
|
|
66
|
+
"-v",
|
|
67
|
+
is_flag=True,
|
|
68
|
+
help="Show detailed debug information",
|
|
69
|
+
)
|
|
70
|
+
@click.pass_context
|
|
71
|
+
@verbose_output
|
|
72
|
+
def cleanup(
|
|
73
|
+
ctx: click.Context,
|
|
74
|
+
keep: int,
|
|
75
|
+
days: int | None,
|
|
76
|
+
dry_run: bool,
|
|
77
|
+
force: bool,
|
|
78
|
+
backups_only: bool,
|
|
79
|
+
check_folders: bool,
|
|
80
|
+
check_duplicates: bool,
|
|
81
|
+
check_malformed: bool,
|
|
82
|
+
verbose: bool,
|
|
83
|
+
):
|
|
84
|
+
"""Comprehensive roadmap cleanup - fix backups, folders, duplicates, and malformed files.
|
|
85
|
+
|
|
86
|
+
By default, performs all cleanup operations:
|
|
87
|
+
1. Removes old backup files (keeps 10 most recent per issue)
|
|
88
|
+
2. Moves misplaced issues to correct milestone folders
|
|
89
|
+
3. Resolves duplicate issues (keeps latest version)
|
|
90
|
+
4. Fixes malformed YAML in issue files
|
|
91
|
+
|
|
92
|
+
Use --dry-run to preview all changes without making them.
|
|
93
|
+
Use --force to skip confirmation prompts.
|
|
94
|
+
|
|
95
|
+
Use individual options to target specific cleanup tasks:
|
|
96
|
+
- --backups-only: Only clean up old backups
|
|
97
|
+
- --check-folders: Only report folder structure issues
|
|
98
|
+
- --check-duplicates: Only report duplicate issues
|
|
99
|
+
- --check-malformed: Only report and fix malformed files
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
roadmap cleanup # Fix everything interactively
|
|
103
|
+
roadmap cleanup --dry-run # Preview all changes
|
|
104
|
+
roadmap cleanup --force # Fix everything without prompting
|
|
105
|
+
roadmap cleanup --backups-only # Only clean up backups
|
|
106
|
+
roadmap cleanup --check-folders # Only check folder issues
|
|
107
|
+
roadmap cleanup --check-malformed # Only fix malformed YAML
|
|
108
|
+
"""
|
|
109
|
+
core = ctx.obj["core"]
|
|
110
|
+
presenter = CleanupPresenter()
|
|
111
|
+
|
|
112
|
+
# Create structured parameter object
|
|
113
|
+
params = CleanupParams(
|
|
114
|
+
keep=keep,
|
|
115
|
+
days=days,
|
|
116
|
+
dry_run=dry_run,
|
|
117
|
+
force=force,
|
|
118
|
+
backups_only=backups_only,
|
|
119
|
+
check_folders=check_folders,
|
|
120
|
+
check_duplicates=check_duplicates,
|
|
121
|
+
check_malformed=check_malformed,
|
|
122
|
+
verbose=verbose,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if not core.is_initialized():
|
|
126
|
+
presenter.present_no_roadmap()
|
|
127
|
+
ctx.exit(1)
|
|
128
|
+
|
|
129
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
130
|
+
issues_dir = roadmap_dir / "issues"
|
|
131
|
+
|
|
132
|
+
# Handle specific flag modes
|
|
133
|
+
if params.check_folders:
|
|
134
|
+
_handle_check_folders(issues_dir, roadmap_dir, core, presenter)
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if params.check_duplicates:
|
|
138
|
+
_handle_check_duplicates(issues_dir, roadmap_dir, presenter)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
if params.check_malformed:
|
|
142
|
+
_handle_check_malformed(
|
|
143
|
+
issues_dir, roadmap_dir, params.dry_run, params.force, presenter
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Default or backups-only mode: run comprehensive cleanup
|
|
148
|
+
try:
|
|
149
|
+
# 1. Clean up backups
|
|
150
|
+
_run_backup_cleanup(
|
|
151
|
+
roadmap_dir,
|
|
152
|
+
params.keep,
|
|
153
|
+
params.days,
|
|
154
|
+
params.dry_run,
|
|
155
|
+
params.force,
|
|
156
|
+
presenter,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# 2. Move misplaced issues and duplicates (skip if backups-only)
|
|
160
|
+
if not params.backups_only:
|
|
161
|
+
if not params.dry_run:
|
|
162
|
+
_resolve_folder_issues(issues_dir, roadmap_dir, core, force, presenter)
|
|
163
|
+
_resolve_duplicates(issues_dir, roadmap_dir, force, presenter)
|
|
164
|
+
|
|
165
|
+
# 3. Fix malformed YAML files
|
|
166
|
+
_fix_malformed_files(issues_dir, presenter)
|
|
167
|
+
else:
|
|
168
|
+
presenter.present_dry_run_skipped()
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
presenter.present_cleanup_error(str(e))
|
|
172
|
+
ctx.exit(1)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _handle_check_folders(
|
|
176
|
+
issues_dir: Path, roadmap_dir: Path, core: object, presenter: CleanupPresenter
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Check and report folder structure issues."""
|
|
179
|
+
if not issues_dir.exists():
|
|
180
|
+
presenter.present_no_issues_dir()
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
issues = FolderStructureValidator.scan_for_folder_structure_issues(issues_dir, core)
|
|
184
|
+
|
|
185
|
+
if not issues:
|
|
186
|
+
presenter.present_folder_check_clean()
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
presenter.present_folder_issues(issues)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _handle_check_duplicates(
|
|
193
|
+
issues_dir: Path, roadmap_dir: Path, presenter: CleanupPresenter
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Check and report duplicate issues."""
|
|
196
|
+
if not issues_dir.exists():
|
|
197
|
+
presenter.present_no_issues_dir()
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
|
|
201
|
+
|
|
202
|
+
if not duplicates:
|
|
203
|
+
presenter.present_duplicates_check_clean()
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
presenter.present_duplicate_issues(duplicates, roadmap_dir)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _handle_check_malformed(
|
|
210
|
+
issues_dir: Path,
|
|
211
|
+
roadmap_dir: Path,
|
|
212
|
+
dry_run: bool,
|
|
213
|
+
force: bool,
|
|
214
|
+
presenter: CleanupPresenter,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Check and fix malformed YAML files."""
|
|
217
|
+
if not issues_dir.exists():
|
|
218
|
+
presenter.present_no_issues_dir()
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
malformed = DataIntegrityValidator.scan_for_data_integrity_issues(issues_dir)
|
|
222
|
+
|
|
223
|
+
if not malformed["malformed_files"]:
|
|
224
|
+
presenter.present_malformed_check_clean()
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
presenter.present_malformed_files(malformed["malformed_files"])
|
|
228
|
+
|
|
229
|
+
if dry_run:
|
|
230
|
+
console.print("[DRY RUN] Would fix the above files", style="dim")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
if not force:
|
|
234
|
+
if not click.confirm("Fix malformed files?", default=True):
|
|
235
|
+
console.print("Skipped.", style="yellow")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
repair_service = FileRepairService()
|
|
239
|
+
result = repair_service.repair_files(
|
|
240
|
+
issues_dir, malformed["malformed_files"], dry_run=False
|
|
241
|
+
)
|
|
242
|
+
presenter.present_malformed_repair_result(result)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _run_backup_cleanup(
|
|
246
|
+
roadmap_dir: Path,
|
|
247
|
+
keep: int,
|
|
248
|
+
days: int | None,
|
|
249
|
+
dry_run: bool,
|
|
250
|
+
force: bool,
|
|
251
|
+
presenter: CleanupPresenter,
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Run backup cleanup using service."""
|
|
254
|
+
backup_service = BackupCleanupService()
|
|
255
|
+
|
|
256
|
+
# Get files that would be deleted
|
|
257
|
+
backups_dir = roadmap_dir / "backups"
|
|
258
|
+
if not backups_dir.exists():
|
|
259
|
+
presenter.present_no_backups()
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
files_to_delete = backup_service._select_backups_for_deletion(
|
|
263
|
+
backups_dir, keep, days
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if not files_to_delete:
|
|
267
|
+
presenter.present_no_cleanup_needed()
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
# Dry run: show what would be deleted
|
|
271
|
+
if dry_run:
|
|
272
|
+
presenter.present_backup_cleanup_dry_run(list(files_to_delete)) # type: ignore
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
# Confirm deletion
|
|
276
|
+
total_size = sum(f["size"] for f in files_to_delete)
|
|
277
|
+
size_mb = total_size / (1024 * 1024)
|
|
278
|
+
|
|
279
|
+
if not force:
|
|
280
|
+
console.print(
|
|
281
|
+
f"\n⚠️ About to delete {len(files_to_delete)} backup file(s) ({size_mb:.2f} MB):",
|
|
282
|
+
style="bold yellow",
|
|
283
|
+
)
|
|
284
|
+
console.print(
|
|
285
|
+
f" This will free approximately {size_mb:.2f} MB of disk space.",
|
|
286
|
+
style="yellow",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if not click.confirm("\nProceed with cleanup?", default=False):
|
|
290
|
+
console.print("❌ Cancelled.", style="yellow")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
# Perform cleanup
|
|
294
|
+
result = backup_service.cleanup_backups(roadmap_dir, keep, days, dry_run=False)
|
|
295
|
+
presenter.present_backup_cleanup_result(result)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _build_move_list(issues_dir: Path, issues: dict) -> list:
|
|
299
|
+
"""Build list of moves to make from issue structure issues.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
issues_dir: Path to issues directory
|
|
303
|
+
issues: Dictionary of misplaced and orphaned issues
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of move operations
|
|
307
|
+
"""
|
|
308
|
+
moves_to_make = []
|
|
309
|
+
|
|
310
|
+
if issues.get("misplaced"):
|
|
311
|
+
for item in issues["misplaced"]:
|
|
312
|
+
moves_to_make.append(
|
|
313
|
+
{
|
|
314
|
+
"from": Path(item["current_location"]),
|
|
315
|
+
"to": Path(item["expected_location"]),
|
|
316
|
+
"issue_id": item["issue_id"],
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if issues.get("orphaned"):
|
|
321
|
+
for item in issues["orphaned"]:
|
|
322
|
+
backlog_dest = issues_dir / "backlog" / Path(item["location"]).name
|
|
323
|
+
moves_to_make.append(
|
|
324
|
+
{
|
|
325
|
+
"from": Path(item["location"]),
|
|
326
|
+
"to": backlog_dest,
|
|
327
|
+
"issue_id": item["issue_id"],
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return moves_to_make
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _confirm_folder_moves(force: bool) -> bool:
|
|
335
|
+
"""Confirm folder moves with user.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
force: Skip confirmation if True
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
True if user confirmed or force is True
|
|
342
|
+
"""
|
|
343
|
+
if force:
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
console.print(
|
|
347
|
+
"\n⚠️ About to move files:",
|
|
348
|
+
style="bold yellow",
|
|
349
|
+
)
|
|
350
|
+
return click.confirm("\nProceed with moves?", default=False)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _perform_folder_moves(moves_to_make: list) -> tuple[int, int]:
|
|
354
|
+
"""Perform folder move operations.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
moves_to_make: List of move operations to perform
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Tuple of (moved_count, failed_count)
|
|
361
|
+
"""
|
|
362
|
+
moved_count = 0
|
|
363
|
+
failed_count = 0
|
|
364
|
+
|
|
365
|
+
for move in moves_to_make:
|
|
366
|
+
try:
|
|
367
|
+
move["to"].parent.mkdir(parents=True, exist_ok=True)
|
|
368
|
+
move["from"].rename(move["to"])
|
|
369
|
+
moved_count += 1
|
|
370
|
+
except Exception as e:
|
|
371
|
+
console.print(
|
|
372
|
+
f"⚠️ Failed to move {move['from'].name}: {e}",
|
|
373
|
+
style="yellow",
|
|
374
|
+
)
|
|
375
|
+
failed_count += 1
|
|
376
|
+
|
|
377
|
+
return moved_count, failed_count
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _resolve_folder_issues(
|
|
381
|
+
issues_dir: Path,
|
|
382
|
+
roadmap_dir: Path,
|
|
383
|
+
core: object,
|
|
384
|
+
force: bool,
|
|
385
|
+
presenter: CleanupPresenter,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Move misplaced and orphaned issues to correct folders."""
|
|
388
|
+
if not issues_dir.exists():
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
issues = FolderStructureValidator.scan_for_folder_structure_issues(issues_dir, core)
|
|
392
|
+
|
|
393
|
+
if not issues:
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
misplaced_count = len(issues.get("misplaced", []))
|
|
397
|
+
orphaned_count = len(issues.get("orphaned", []))
|
|
398
|
+
total = misplaced_count + orphaned_count
|
|
399
|
+
|
|
400
|
+
if total == 0:
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
moves_to_make = _build_move_list(issues_dir, issues)
|
|
404
|
+
|
|
405
|
+
if not moves_to_make:
|
|
406
|
+
return
|
|
407
|
+
|
|
408
|
+
presenter.present_folder_moves(moves_to_make)
|
|
409
|
+
|
|
410
|
+
# Confirm moves
|
|
411
|
+
if not _confirm_folder_moves(force):
|
|
412
|
+
console.print("❌ Cancelled.", style="yellow")
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# Perform moves
|
|
416
|
+
moved_count, failed_count = _perform_folder_moves(moves_to_make)
|
|
417
|
+
presenter.present_folder_moves_result(moved_count, failed_count)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _resolve_duplicates(
|
|
421
|
+
issues_dir: Path, roadmap_dir: Path, force: bool, presenter: CleanupPresenter
|
|
422
|
+
) -> None:
|
|
423
|
+
"""Resolve duplicate issues by keeping the latest version."""
|
|
424
|
+
if not issues_dir.exists():
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
|
|
428
|
+
|
|
429
|
+
if not duplicates:
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
presenter.present_duplicate_resolution(duplicates, roadmap_dir)
|
|
433
|
+
|
|
434
|
+
files_to_delete = []
|
|
435
|
+
# Orchestrate deletion: keep newest, remove others
|
|
436
|
+
for _issue_id, files in sorted(duplicates.items()):
|
|
437
|
+
sorted_files = sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
|
|
438
|
+
to_remove = sorted_files[1:]
|
|
439
|
+
files_to_delete.extend(to_remove)
|
|
440
|
+
|
|
441
|
+
if not files_to_delete:
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# Confirm deletion
|
|
445
|
+
if not force:
|
|
446
|
+
console.print(
|
|
447
|
+
f"\n⚠️ About to delete {len(files_to_delete)} duplicate file(s):",
|
|
448
|
+
style="bold yellow",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if not click.confirm("\nProceed with deletion?", default=False):
|
|
452
|
+
console.print("❌ Cancelled.", style="yellow")
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
# Perform deletion (application layer only)
|
|
456
|
+
deleted_count = 0
|
|
457
|
+
failed_count = 0
|
|
458
|
+
for f in files_to_delete:
|
|
459
|
+
try:
|
|
460
|
+
f.unlink()
|
|
461
|
+
deleted_count += 1
|
|
462
|
+
except Exception as e:
|
|
463
|
+
console.print(
|
|
464
|
+
f"⚠️ Failed to delete {f.name}: {e}",
|
|
465
|
+
style="yellow",
|
|
466
|
+
)
|
|
467
|
+
failed_count += 1
|
|
468
|
+
|
|
469
|
+
presenter.present_duplicate_resolution_result(deleted_count, failed_count)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _fix_malformed_files(issues_dir: Path, presenter: CleanupPresenter) -> None:
|
|
473
|
+
"""Fix malformed YAML files in issues directory."""
|
|
474
|
+
malformed = DataIntegrityValidator.scan_for_data_integrity_issues(issues_dir)
|
|
475
|
+
|
|
476
|
+
if not malformed["malformed_files"]:
|
|
477
|
+
return
|
|
478
|
+
|
|
479
|
+
repair_service = FileRepairService()
|
|
480
|
+
result = repair_service.repair_files(
|
|
481
|
+
issues_dir, malformed["malformed_files"], dry_run=False
|
|
482
|
+
)
|
|
483
|
+
presenter.present_malformed_repair_result(result)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Observability layer - Health checks and monitoring.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- Health checks: Application and infrastructure health monitoring
|
|
5
|
+
- Specialized health checkers: Directory, data, and entity health checks
|
|
6
|
+
- Health formatters: Output formatting for health check results
|
|
7
|
+
"""
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Health checks for monitoring application and infrastructure status.
|
|
2
|
+
|
|
3
|
+
This module orchestrates health checks by delegating to specialized checkers:
|
|
4
|
+
- DirectoryHealthChecker: File system structure checks
|
|
5
|
+
- DataHealthChecker: Database and data integrity checks
|
|
6
|
+
- EntityHealthChecker: Business entity validation checks
|
|
7
|
+
|
|
8
|
+
These replace the original monolithic HealthCheck class to improve separation of concerns.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
|
|
13
|
+
from roadmap.common.logging import get_logger
|
|
14
|
+
from roadmap.core.domain.health import HealthStatus
|
|
15
|
+
from roadmap.infrastructure.observability.specialized_health_checkers import (
|
|
16
|
+
DataHealthChecker,
|
|
17
|
+
DirectoryHealthChecker,
|
|
18
|
+
EntityHealthChecker,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HealthCheck:
|
|
25
|
+
"""Application health checks orchestrator.
|
|
26
|
+
|
|
27
|
+
Delegates to specialized health checkers organized by concern:
|
|
28
|
+
- DirectoryHealthChecker: File system and directory checks
|
|
29
|
+
- DataHealthChecker: Database and data integrity checks
|
|
30
|
+
- EntityHealthChecker: Business entity validation checks
|
|
31
|
+
|
|
32
|
+
This thin facade coordinates checks across multiple domain areas
|
|
33
|
+
while keeping each area's logic properly separated.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""Initialize HealthCheck with specialized checkers."""
|
|
38
|
+
self.directory_checker = DirectoryHealthChecker()
|
|
39
|
+
self.data_checker = DataHealthChecker()
|
|
40
|
+
self.entity_checker = EntityHealthChecker()
|
|
41
|
+
|
|
42
|
+
# Delegate to DirectoryHealthChecker
|
|
43
|
+
@staticmethod
|
|
44
|
+
def check_roadmap_directory() -> tuple[HealthStatus, str]:
|
|
45
|
+
"""Check if .roadmap directory exists and is accessible."""
|
|
46
|
+
return DirectoryHealthChecker.check_roadmap_directory()
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def check_state_file() -> tuple[HealthStatus, str]:
|
|
50
|
+
"""Check if state database exists and is readable."""
|
|
51
|
+
return DirectoryHealthChecker.check_state_file()
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def check_issues_directory() -> tuple[HealthStatus, str]:
|
|
55
|
+
"""Check if issues directory exists and is accessible."""
|
|
56
|
+
return DirectoryHealthChecker.check_issues_directory()
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def check_milestones_directory() -> tuple[HealthStatus, str]:
|
|
60
|
+
"""Check if milestones directory exists and is accessible."""
|
|
61
|
+
return DirectoryHealthChecker.check_milestones_directory()
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def check_folder_structure(core) -> tuple[HealthStatus, str]:
|
|
65
|
+
"""Check if issues are in correct milestone folders."""
|
|
66
|
+
return DirectoryHealthChecker.check_folder_structure(core)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def check_old_backups() -> tuple[HealthStatus, str]:
|
|
70
|
+
"""Check for old backup files that could be cleaned up."""
|
|
71
|
+
return DirectoryHealthChecker.check_old_backups()
|
|
72
|
+
|
|
73
|
+
# Delegate to DataHealthChecker
|
|
74
|
+
@staticmethod
|
|
75
|
+
def check_git_repository() -> tuple[HealthStatus, str]:
|
|
76
|
+
"""Check if Git repository exists and is accessible."""
|
|
77
|
+
return DataHealthChecker.check_git_repository()
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def check_database_integrity() -> tuple[HealthStatus, str]:
|
|
81
|
+
"""Check SQLite database integrity."""
|
|
82
|
+
return DataHealthChecker.check_database_integrity()
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def check_data_integrity() -> tuple[HealthStatus, str]:
|
|
86
|
+
"""Run comprehensive data integrity checks."""
|
|
87
|
+
return DataHealthChecker.check_data_integrity()
|
|
88
|
+
|
|
89
|
+
# Delegate to EntityHealthChecker
|
|
90
|
+
@staticmethod
|
|
91
|
+
def check_duplicate_issues(core) -> tuple[HealthStatus, str]:
|
|
92
|
+
"""Check for duplicate issues."""
|
|
93
|
+
return EntityHealthChecker.check_duplicate_issues(core)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def check_archivable_issues(core) -> tuple[HealthStatus, str]:
|
|
97
|
+
"""Check for closed issues that could be archived."""
|
|
98
|
+
return EntityHealthChecker.check_archivable_issues(core)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def check_archivable_milestones(core) -> tuple[HealthStatus, str]:
|
|
102
|
+
"""Check for closed milestones that could be archived."""
|
|
103
|
+
return EntityHealthChecker.check_archivable_milestones(core)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def check_orphaned_issues(core) -> tuple[HealthStatus, str]:
|
|
107
|
+
"""Check for issues not assigned to any milestone."""
|
|
108
|
+
return EntityHealthChecker.check_orphaned_issues(core)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def check_orphaned_milestones(core) -> tuple[HealthStatus, str]:
|
|
112
|
+
"""Check for empty milestones with no issues."""
|
|
113
|
+
return EntityHealthChecker.check_orphaned_milestones(core)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def check_duplicate_milestones(core) -> tuple[HealthStatus, str]:
|
|
117
|
+
"""Check for duplicate milestone names."""
|
|
118
|
+
return EntityHealthChecker.check_duplicate_milestones(core)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def check_unlinked_issues(core) -> tuple[HealthStatus, str]:
|
|
122
|
+
"""Check for remote issues not linked to local issues."""
|
|
123
|
+
return EntityHealthChecker.check_unlinked_issues(core)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def run_all_checks(core) -> dict[str, tuple[HealthStatus, str]]:
|
|
127
|
+
"""Run all health checks.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
core: RoadmapCore instance for entity checks
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary mapping check names to (status, message) tuples
|
|
134
|
+
"""
|
|
135
|
+
checks = {}
|
|
136
|
+
|
|
137
|
+
# Directory checks
|
|
138
|
+
checks["roadmap_directory"] = HealthCheck.check_roadmap_directory()
|
|
139
|
+
checks["state_file"] = HealthCheck.check_state_file()
|
|
140
|
+
checks["issues_directory"] = HealthCheck.check_issues_directory()
|
|
141
|
+
checks["milestones_directory"] = HealthCheck.check_milestones_directory()
|
|
142
|
+
|
|
143
|
+
# Data checks
|
|
144
|
+
checks["git_repository"] = HealthCheck.check_git_repository()
|
|
145
|
+
checks["database_integrity"] = HealthCheck.check_database_integrity()
|
|
146
|
+
checks["data_integrity"] = HealthCheck.check_data_integrity()
|
|
147
|
+
|
|
148
|
+
# Entity checks (require core)
|
|
149
|
+
if core:
|
|
150
|
+
checks["folder_structure"] = HealthCheck.check_folder_structure(core)
|
|
151
|
+
checks["duplicate_issues"] = HealthCheck.check_duplicate_issues(core)
|
|
152
|
+
checks["archivable_issues"] = HealthCheck.check_archivable_issues(core)
|
|
153
|
+
checks["archivable_milestones"] = HealthCheck.check_archivable_milestones(
|
|
154
|
+
core
|
|
155
|
+
)
|
|
156
|
+
checks["orphaned_issues"] = HealthCheck.check_orphaned_issues(core)
|
|
157
|
+
checks["orphaned_milestones"] = HealthCheck.check_orphaned_milestones(core)
|
|
158
|
+
checks["duplicate_milestones"] = HealthCheck.check_duplicate_milestones(
|
|
159
|
+
core
|
|
160
|
+
)
|
|
161
|
+
checks["unlinked_issues"] = HealthCheck.check_unlinked_issues(core)
|
|
162
|
+
|
|
163
|
+
checks["old_backups"] = HealthCheck.check_old_backups()
|
|
164
|
+
|
|
165
|
+
return checks
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def get_overall_status(
|
|
169
|
+
checks: Mapping[str, tuple[HealthStatus, str]],
|
|
170
|
+
) -> HealthStatus:
|
|
171
|
+
"""Get overall health status from check results.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
checks: Dictionary of check results
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Overall HealthStatus (UNHEALTHY if any unhealthy, DEGRADED if any degraded, else HEALTHY)
|
|
178
|
+
"""
|
|
179
|
+
if not checks:
|
|
180
|
+
return HealthStatus.HEALTHY
|
|
181
|
+
|
|
182
|
+
has_unhealthy = False
|
|
183
|
+
has_degraded = False
|
|
184
|
+
|
|
185
|
+
for status, _message in checks.values():
|
|
186
|
+
if status == HealthStatus.UNHEALTHY:
|
|
187
|
+
has_unhealthy = True
|
|
188
|
+
elif status == HealthStatus.DEGRADED:
|
|
189
|
+
has_degraded = True
|
|
190
|
+
|
|
191
|
+
if has_unhealthy:
|
|
192
|
+
return HealthStatus.UNHEALTHY
|
|
193
|
+
elif has_degraded:
|
|
194
|
+
return HealthStatus.DEGRADED
|
|
195
|
+
else:
|
|
196
|
+
return HealthStatus.HEALTHY
|