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,162 @@
|
|
|
1
|
+
"""Fixer for milestone files that fail to parse (invisible to CLI)."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from structlog import get_logger
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
8
|
+
from roadmap.adapters.persistence.parser.milestone import MilestoneParser
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MilestoneParseErrorFixer(HealthFixer):
|
|
14
|
+
"""Detects milestone files that exist on disk but fail to parse.
|
|
15
|
+
|
|
16
|
+
Safety: REVIEW (regenerates files from minimal defaults, discarding
|
|
17
|
+
unrecoverable YAML content)
|
|
18
|
+
|
|
19
|
+
A milestone file that fails to parse is silently excluded from
|
|
20
|
+
``milestone list`` and ``milestone view``, causing confusing
|
|
21
|
+
inconsistencies (issues are queryable by milestone name but the
|
|
22
|
+
milestone itself is "not found"). This fixer surfaces those files
|
|
23
|
+
so the user is not left guessing why a milestone disappeared.
|
|
24
|
+
|
|
25
|
+
The ``apply()`` fix regenerates each broken file with sensible
|
|
26
|
+
defaults derived from the filename, which replicates the manual
|
|
27
|
+
workaround of running ``roadmap milestone create``.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def fix_type(self) -> str:
|
|
32
|
+
"""Return fixer type identifier."""
|
|
33
|
+
return "milestone_parse_errors"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def safety_level(self) -> FixSafety:
|
|
37
|
+
"""Return safety level - REVIEW because we overwrite files."""
|
|
38
|
+
return FixSafety.REVIEW
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def description(self) -> str:
|
|
42
|
+
"""Return fixer description."""
|
|
43
|
+
return "Detect milestone files that fail to parse and are invisible to the CLI"
|
|
44
|
+
|
|
45
|
+
def scan(self) -> dict:
|
|
46
|
+
"""Scan for milestone files that cannot be parsed.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dict with found, count, message, details
|
|
50
|
+
"""
|
|
51
|
+
broken = self._find_broken_milestone_files()
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"found": len(broken) > 0,
|
|
55
|
+
"count": len(broken),
|
|
56
|
+
"message": f"Found {len(broken)} milestone file(s) that fail to parse",
|
|
57
|
+
"details": broken,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def dry_run(self) -> FixResult:
|
|
61
|
+
"""Preview which milestone files would be regenerated.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
FixResult with dry_run=True
|
|
65
|
+
"""
|
|
66
|
+
broken = self._find_broken_milestone_files()
|
|
67
|
+
|
|
68
|
+
return FixResult(
|
|
69
|
+
fix_type=self.fix_type,
|
|
70
|
+
success=True,
|
|
71
|
+
dry_run=True,
|
|
72
|
+
message=f"Would regenerate {len(broken)} milestone file(s) from defaults",
|
|
73
|
+
affected_items=[item["file"] for item in broken],
|
|
74
|
+
items_count=len(broken),
|
|
75
|
+
changes_made=0,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
79
|
+
"""Regenerate broken milestone files with minimal valid defaults.
|
|
80
|
+
|
|
81
|
+
Each broken file is overwritten with a new milestone whose ``name``
|
|
82
|
+
is derived from the filename stem, preserving the original file
|
|
83
|
+
location. This replicates the manual workaround of recreating the
|
|
84
|
+
milestone via ``roadmap milestone create``.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
force: Ignored (safety handled by REVIEW level and orchestrator).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
FixResult
|
|
91
|
+
"""
|
|
92
|
+
broken = self._find_broken_milestone_files()
|
|
93
|
+
fixed_count = 0
|
|
94
|
+
failed_items: list[str] = []
|
|
95
|
+
|
|
96
|
+
for item in broken:
|
|
97
|
+
file_path = Path(item["file"])
|
|
98
|
+
milestone_name = item["name"]
|
|
99
|
+
try:
|
|
100
|
+
milestone = self.core.milestones.create(
|
|
101
|
+
name=milestone_name,
|
|
102
|
+
headline="",
|
|
103
|
+
)
|
|
104
|
+
if milestone:
|
|
105
|
+
fixed_count += 1
|
|
106
|
+
else:
|
|
107
|
+
failed_items.append(str(file_path))
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(
|
|
110
|
+
"milestone_parse_error_fix_failed",
|
|
111
|
+
file=str(file_path),
|
|
112
|
+
error=str(e),
|
|
113
|
+
severity="data_error",
|
|
114
|
+
)
|
|
115
|
+
failed_items.append(str(file_path))
|
|
116
|
+
|
|
117
|
+
return FixResult(
|
|
118
|
+
fix_type=self.fix_type,
|
|
119
|
+
success=len(failed_items) == 0,
|
|
120
|
+
dry_run=False,
|
|
121
|
+
message=f"Regenerated {fixed_count}/{len(broken)} broken milestone file(s)",
|
|
122
|
+
affected_items=[
|
|
123
|
+
item["file"] for item in broken if item["file"] not in failed_items
|
|
124
|
+
],
|
|
125
|
+
items_count=len(broken),
|
|
126
|
+
changes_made=fixed_count,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def _find_broken_milestone_files(self) -> list[dict]:
|
|
130
|
+
"""Find milestone files that raise exceptions during parsing.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of dicts with 'file', 'name', 'error' keys
|
|
134
|
+
"""
|
|
135
|
+
broken: list[dict] = []
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
milestones_dir = Path(".roadmap/milestones").resolve()
|
|
139
|
+
if not milestones_dir.exists():
|
|
140
|
+
return broken
|
|
141
|
+
|
|
142
|
+
for md_file in sorted(milestones_dir.glob("*.md")):
|
|
143
|
+
if ".backup" in md_file.name:
|
|
144
|
+
continue
|
|
145
|
+
try:
|
|
146
|
+
MilestoneParser.parse_milestone_file(md_file)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
broken.append(
|
|
149
|
+
{
|
|
150
|
+
"file": str(md_file),
|
|
151
|
+
"name": md_file.stem,
|
|
152
|
+
"error": f"{type(e).__name__}: {e}",
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(
|
|
157
|
+
"find_broken_milestone_files_failed",
|
|
158
|
+
error=str(e),
|
|
159
|
+
severity="data_error",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return broken
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Fixer for issues with invalid milestone references."""
|
|
2
|
+
|
|
3
|
+
from difflib import get_close_matches
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from structlog import get_logger
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MilestoneValidationFixer(HealthFixer):
|
|
14
|
+
"""Validates that all issue milestones point to existing milestones.
|
|
15
|
+
|
|
16
|
+
Safety: REVIEW (may reassign issues to different milestones)
|
|
17
|
+
|
|
18
|
+
Issues with invalid milestone references are either:
|
|
19
|
+
1. Moved to backlog if no similar milestone found
|
|
20
|
+
2. Updated to closest matching milestone if found
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def fix_type(self) -> str:
|
|
25
|
+
"""Return fixer type identifier."""
|
|
26
|
+
return "milestone_validation"
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def safety_level(self) -> FixSafety:
|
|
30
|
+
"""Return safety level - REVIEW because we may reassign milestones."""
|
|
31
|
+
return FixSafety.REVIEW
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def description(self) -> str:
|
|
35
|
+
"""Return fixer description."""
|
|
36
|
+
return "Validate and fix invalid milestone references in issues"
|
|
37
|
+
|
|
38
|
+
def scan(self) -> dict:
|
|
39
|
+
"""Scan for issues with invalid milestone references.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dict with found, count, message, details
|
|
43
|
+
"""
|
|
44
|
+
invalid = self._find_invalid_milestones()
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"found": len(invalid) > 0,
|
|
48
|
+
"count": len(invalid),
|
|
49
|
+
"message": f"Found {len(invalid)} issue(s) with invalid milestone(s)",
|
|
50
|
+
"details": [
|
|
51
|
+
{
|
|
52
|
+
"id": iss["id"],
|
|
53
|
+
"title": iss["title"],
|
|
54
|
+
"invalid_milestone": iss["milestone"],
|
|
55
|
+
}
|
|
56
|
+
for iss in invalid
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def dry_run(self) -> FixResult:
|
|
61
|
+
"""Preview which issues would be fixed.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
FixResult with dry_run=True
|
|
65
|
+
"""
|
|
66
|
+
invalid = self._find_invalid_milestones()
|
|
67
|
+
|
|
68
|
+
affected = []
|
|
69
|
+
for iss in invalid:
|
|
70
|
+
suggestion = self._get_suggestion(iss["milestone"])
|
|
71
|
+
if suggestion:
|
|
72
|
+
affected.append(f"{iss['id']} ({iss['milestone']} → {suggestion})")
|
|
73
|
+
else:
|
|
74
|
+
affected.append(f"{iss['id']} ({iss['milestone']} → backlog)")
|
|
75
|
+
|
|
76
|
+
return FixResult(
|
|
77
|
+
fix_type=self.fix_type,
|
|
78
|
+
success=True,
|
|
79
|
+
dry_run=True,
|
|
80
|
+
message=f"Would fix {len(invalid)} issue(s) with invalid milestone(s)",
|
|
81
|
+
affected_items=affected,
|
|
82
|
+
items_count=len(invalid),
|
|
83
|
+
changes_made=0,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
87
|
+
"""Fix issues with invalid milestone references.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
force: Ignored (SAFE fixers apply automatically)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
FixResult with fix results
|
|
94
|
+
"""
|
|
95
|
+
_ = force
|
|
96
|
+
invalid = self._find_invalid_milestones()
|
|
97
|
+
fixed_count = 0
|
|
98
|
+
failed_items = []
|
|
99
|
+
|
|
100
|
+
for issue_data in invalid:
|
|
101
|
+
try:
|
|
102
|
+
issue_id = issue_data["id"]
|
|
103
|
+
old_milestone = issue_data["milestone"]
|
|
104
|
+
|
|
105
|
+
# Try to find a suggestion
|
|
106
|
+
suggestion = self._get_suggestion(old_milestone)
|
|
107
|
+
if not suggestion:
|
|
108
|
+
# No similar milestone, move to backlog
|
|
109
|
+
suggestion = "backlog"
|
|
110
|
+
|
|
111
|
+
# Assign to suggested milestone
|
|
112
|
+
if self.core.issues.assign_to_milestone(issue_id, suggestion):
|
|
113
|
+
fixed_count += 1
|
|
114
|
+
else:
|
|
115
|
+
failed_items.append(issue_id)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(
|
|
118
|
+
"fix_invalid_milestone_failed",
|
|
119
|
+
issue_id=issue_data["id"],
|
|
120
|
+
error=str(e),
|
|
121
|
+
severity="data_error",
|
|
122
|
+
)
|
|
123
|
+
failed_items.append(issue_data["id"])
|
|
124
|
+
|
|
125
|
+
return FixResult(
|
|
126
|
+
fix_type=self.fix_type,
|
|
127
|
+
success=len(failed_items) == 0,
|
|
128
|
+
dry_run=False,
|
|
129
|
+
message=f"Fixed {fixed_count}/{len(invalid)} issue(s) with invalid milestone(s)",
|
|
130
|
+
affected_items=[
|
|
131
|
+
iss["id"] for iss in invalid if iss["id"] not in failed_items
|
|
132
|
+
],
|
|
133
|
+
items_count=len(invalid),
|
|
134
|
+
changes_made=fixed_count,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _find_invalid_milestones(self) -> list[dict]:
|
|
138
|
+
"""Find issues with milestone references that don't exist.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of dicts with id, title, milestone
|
|
142
|
+
"""
|
|
143
|
+
invalid = []
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
# Get all available milestone names
|
|
147
|
+
milestones_dir = Path(".roadmap/milestones").resolve()
|
|
148
|
+
if not milestones_dir.exists():
|
|
149
|
+
return invalid
|
|
150
|
+
|
|
151
|
+
available_milestones = {f.stem for f in milestones_dir.glob("*.md")}
|
|
152
|
+
|
|
153
|
+
# Check all issues
|
|
154
|
+
for issue in self.core.issues.list():
|
|
155
|
+
if not issue.milestone:
|
|
156
|
+
# No milestone is valid (goes to backlog)
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# "backlog" is a valid pseudo-milestone (no file on disk)
|
|
160
|
+
if issue.milestone.lower() == "backlog":
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Convert milestone name to safe filename for comparison
|
|
164
|
+
safe_name = "".join(
|
|
165
|
+
c for c in issue.milestone if c.isalnum() or c in (" ", "-", "_")
|
|
166
|
+
).strip()
|
|
167
|
+
safe_name = safe_name.replace(" ", "-").lower()
|
|
168
|
+
|
|
169
|
+
if safe_name not in available_milestones:
|
|
170
|
+
invalid.append(
|
|
171
|
+
{
|
|
172
|
+
"id": issue.id,
|
|
173
|
+
"title": issue.title,
|
|
174
|
+
"milestone": issue.milestone,
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(
|
|
180
|
+
"find_invalid_milestones_failed", error=str(e), severity="data_error"
|
|
181
|
+
)
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
return invalid
|
|
185
|
+
|
|
186
|
+
def _get_suggestion(self, invalid_milestone: str) -> str | None:
|
|
187
|
+
"""Find a suggested milestone for an invalid one.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
invalid_milestone: The invalid milestone name
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Suggested milestone name, or None if no good match found
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
milestones_dir = Path(".roadmap/milestones").resolve()
|
|
197
|
+
if not milestones_dir.exists():
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
available = [f.stem for f in milestones_dir.glob("*.md")]
|
|
201
|
+
|
|
202
|
+
# Find close matches
|
|
203
|
+
matches = get_close_matches(invalid_milestone, available, n=1, cutoff=0.6)
|
|
204
|
+
return matches[0] if matches else None
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.debug(
|
|
207
|
+
"get_milestone_suggestion_failed",
|
|
208
|
+
milestone=invalid_milestone,
|
|
209
|
+
error=str(e),
|
|
210
|
+
)
|
|
211
|
+
return None
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Fixer for old backup files."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime, timedelta
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OldBackupsFixer(HealthFixer):
|
|
10
|
+
"""Removes old backup files to free up storage.
|
|
11
|
+
|
|
12
|
+
Safety: SAFE (automatically applies)
|
|
13
|
+
|
|
14
|
+
Removes backup files older than 90 days.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
BACKUP_THRESHOLD_DAYS = 90
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def fix_type(self) -> str:
|
|
21
|
+
"""Return fixer type identifier."""
|
|
22
|
+
return "old_backups"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def safety_level(self) -> FixSafety:
|
|
26
|
+
"""Return safety level - SAFE because backups are not primary data."""
|
|
27
|
+
return FixSafety.SAFE
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def description(self) -> str:
|
|
31
|
+
"""Return fixer description."""
|
|
32
|
+
return f"Delete backup files older than {self.BACKUP_THRESHOLD_DAYS} days"
|
|
33
|
+
|
|
34
|
+
def scan(self) -> dict:
|
|
35
|
+
"""Scan for old backup files.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with found, count, message, details
|
|
39
|
+
"""
|
|
40
|
+
old_backups = self._find_old_backups()
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
"found": len(old_backups) > 0,
|
|
44
|
+
"count": len(old_backups),
|
|
45
|
+
"message": f"Found {len(old_backups)} old backup file(s) to clean up",
|
|
46
|
+
"details": [
|
|
47
|
+
{"path": str(p), "age_days": self._get_age_days(p)} for p in old_backups
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def dry_run(self) -> FixResult:
|
|
52
|
+
"""Preview which backups would be deleted.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
FixResult with dry_run=True
|
|
56
|
+
"""
|
|
57
|
+
old_backups = self._find_old_backups()
|
|
58
|
+
|
|
59
|
+
return FixResult(
|
|
60
|
+
fix_type=self.fix_type,
|
|
61
|
+
success=True,
|
|
62
|
+
dry_run=True,
|
|
63
|
+
message=f"Would delete {len(old_backups)} old backup file(s)",
|
|
64
|
+
affected_items=[str(p) for p in old_backups],
|
|
65
|
+
items_count=len(old_backups),
|
|
66
|
+
changes_made=0,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
70
|
+
"""Delete old backup files.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
force: Ignored (SAFE fixers apply automatically)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
FixResult with results of deletion
|
|
77
|
+
"""
|
|
78
|
+
_ = force
|
|
79
|
+
old_backups = self._find_old_backups()
|
|
80
|
+
deleted_count = 0
|
|
81
|
+
failed_items = []
|
|
82
|
+
|
|
83
|
+
for backup_path in old_backups:
|
|
84
|
+
try:
|
|
85
|
+
backup_path.unlink()
|
|
86
|
+
deleted_count += 1
|
|
87
|
+
except Exception:
|
|
88
|
+
failed_items.append(str(backup_path))
|
|
89
|
+
|
|
90
|
+
success = len(failed_items) == 0
|
|
91
|
+
|
|
92
|
+
return FixResult(
|
|
93
|
+
fix_type=self.fix_type,
|
|
94
|
+
success=success,
|
|
95
|
+
dry_run=False,
|
|
96
|
+
message=f"Deleted {deleted_count}/{len(old_backups)} old backup file(s)",
|
|
97
|
+
affected_items=[str(p) for p in old_backups],
|
|
98
|
+
items_count=len(old_backups),
|
|
99
|
+
changes_made=deleted_count,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _find_old_backups(self) -> list[Path]:
|
|
103
|
+
"""Find backup files older than threshold.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of Path objects for old backups
|
|
107
|
+
"""
|
|
108
|
+
old_backups = []
|
|
109
|
+
backup_dir = Path(".roadmap/backups")
|
|
110
|
+
|
|
111
|
+
if not backup_dir.exists():
|
|
112
|
+
return old_backups
|
|
113
|
+
|
|
114
|
+
now = datetime.now(UTC)
|
|
115
|
+
threshold = now - timedelta(days=self.BACKUP_THRESHOLD_DAYS)
|
|
116
|
+
|
|
117
|
+
for backup_file in backup_dir.glob("*.db"):
|
|
118
|
+
mtime = datetime.fromtimestamp(backup_file.stat().st_mtime)
|
|
119
|
+
if mtime < threshold:
|
|
120
|
+
old_backups.append(backup_file)
|
|
121
|
+
|
|
122
|
+
return sorted(old_backups)
|
|
123
|
+
|
|
124
|
+
def _get_age_days(self, path: Path) -> int:
|
|
125
|
+
"""Get age of file in days.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
path: Path to file
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Age in days
|
|
132
|
+
"""
|
|
133
|
+
mtime = datetime.fromtimestamp(path.stat().st_mtime)
|
|
134
|
+
age = datetime.now(UTC) - mtime
|
|
135
|
+
return age.days
|