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,440 @@
|
|
|
1
|
+
"""Unified file operations utilities for roadmap CLI application.
|
|
2
|
+
|
|
3
|
+
This module provides centralized, consistent file operations to eliminate
|
|
4
|
+
DRY violations across the codebase. All file operations should use these
|
|
5
|
+
utilities to ensure consistent error handling, logging, and behavior.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
FileOperationError: Base exception for file operation failures
|
|
9
|
+
SecureFileManager: Context manager for secure file operations
|
|
10
|
+
|
|
11
|
+
Functions:
|
|
12
|
+
ensure_directory_exists: Safely create directories with proper error handling
|
|
13
|
+
safe_write_file: Write files with atomic operations and backup support
|
|
14
|
+
safe_read_file: Read files with proper error handling and encoding
|
|
15
|
+
file_exists_check: Unified file existence checking with optional validation
|
|
16
|
+
get_file_size: Get file size with error handling
|
|
17
|
+
backup_file: Create backup of existing file before modification
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
import tempfile
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from structlog import get_logger
|
|
27
|
+
|
|
28
|
+
# File operations logger
|
|
29
|
+
file_logger = get_logger()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FileOperationError(Exception):
|
|
33
|
+
"""Base exception for file operation failures."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self, message: str, path: Path | None = None, operation: str | None = None
|
|
37
|
+
):
|
|
38
|
+
"""Initialize FileOperationError.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
message: Error message.
|
|
42
|
+
path: Path involved in the operation.
|
|
43
|
+
operation: Name of the operation that failed.
|
|
44
|
+
"""
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
self.path = path
|
|
47
|
+
self.operation = operation
|
|
48
|
+
file_logger.error(
|
|
49
|
+
f"FileOperationError: {message} (path: {path}, operation: {operation})"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DirectoryCreationError(FileOperationError):
|
|
54
|
+
"""Exception raised for directory creation failures."""
|
|
55
|
+
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FileReadError(FileOperationError):
|
|
60
|
+
"""Exception raised for file reading failures."""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class FileWriteError(FileOperationError):
|
|
66
|
+
"""Exception raised for file writing failures."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@contextmanager
|
|
72
|
+
def SecureFileManager(file_path: str | Path, mode: str = "w", **kwargs):
|
|
73
|
+
"""Context manager for secure file operations with automatic cleanup.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
file_path: Path to the file
|
|
77
|
+
mode: File open mode
|
|
78
|
+
**kwargs: Additional arguments passed to open()
|
|
79
|
+
|
|
80
|
+
Yields:
|
|
81
|
+
File object
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
FileOperationError: If file operation fails
|
|
85
|
+
"""
|
|
86
|
+
file_path = Path(file_path)
|
|
87
|
+
temp_file = None
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# Ensure parent directory exists
|
|
91
|
+
ensure_directory_exists(file_path.parent)
|
|
92
|
+
|
|
93
|
+
# For write operations, use temporary file for atomicity
|
|
94
|
+
if "w" in mode or "a" in mode:
|
|
95
|
+
temp_file = tempfile.NamedTemporaryFile(
|
|
96
|
+
mode=mode, dir=file_path.parent, delete=False, **kwargs
|
|
97
|
+
)
|
|
98
|
+
yield temp_file
|
|
99
|
+
temp_file.close()
|
|
100
|
+
|
|
101
|
+
# Atomic move to final location
|
|
102
|
+
shutil.move(temp_file.name, file_path)
|
|
103
|
+
temp_file = None
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
# For read operations, open directly
|
|
107
|
+
with open(file_path, mode, **kwargs) as f:
|
|
108
|
+
yield f
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
if temp_file and hasattr(temp_file, "name"):
|
|
112
|
+
try:
|
|
113
|
+
os.unlink(temp_file.name)
|
|
114
|
+
except OSError as cleanup_error:
|
|
115
|
+
file_logger.debug(
|
|
116
|
+
"failed_to_cleanup_temp_file",
|
|
117
|
+
error=str(cleanup_error),
|
|
118
|
+
severity="operational",
|
|
119
|
+
)
|
|
120
|
+
raise FileOperationError(
|
|
121
|
+
f"Secure file operation failed: {e}", Path(file_path), mode
|
|
122
|
+
) from e
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def ensure_directory_exists(
|
|
126
|
+
directory_path: str | Path,
|
|
127
|
+
permissions: int = 0o755,
|
|
128
|
+
parents: bool = True,
|
|
129
|
+
exist_ok: bool = True,
|
|
130
|
+
) -> Path:
|
|
131
|
+
"""Safely create directory with proper error handling and logging.
|
|
132
|
+
|
|
133
|
+
This function centralizes all directory creation logic to eliminate
|
|
134
|
+
the repeated pattern: directory.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
directory_path: Path to the directory to create
|
|
138
|
+
permissions: Directory permissions (default: 0o755)
|
|
139
|
+
parents: Create parent directories if needed (default: True)
|
|
140
|
+
exist_ok: Don't raise error if directory already exists (default: True)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Path object of the created directory
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
DirectoryCreationError: If directory creation fails
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
directory_path = Path(directory_path)
|
|
150
|
+
directory_path.mkdir(mode=permissions, parents=parents, exist_ok=exist_ok)
|
|
151
|
+
|
|
152
|
+
file_logger.debug(
|
|
153
|
+
"directory_ensured",
|
|
154
|
+
directory_path=str(directory_path),
|
|
155
|
+
)
|
|
156
|
+
return directory_path
|
|
157
|
+
|
|
158
|
+
except OSError as e:
|
|
159
|
+
raise DirectoryCreationError(
|
|
160
|
+
f"Failed to create directory: {e}", Path(directory_path), "mkdir"
|
|
161
|
+
) from e
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def safe_write_file(
|
|
165
|
+
file_path: str | Path,
|
|
166
|
+
content: str,
|
|
167
|
+
encoding: str = "utf-8",
|
|
168
|
+
backup: bool = False,
|
|
169
|
+
atomic: bool = True,
|
|
170
|
+
) -> Path:
|
|
171
|
+
"""Write file content safely with atomic operations and optional backup.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
file_path: Path to the file to write
|
|
175
|
+
content: Content to write to the file
|
|
176
|
+
encoding: File encoding (default: utf-8)
|
|
177
|
+
backup: Create backup of existing file (default: False)
|
|
178
|
+
atomic: Use atomic write operation (default: True)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Path object of the written file
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
FileWriteError: If file writing fails
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
file_path = Path(file_path)
|
|
188
|
+
|
|
189
|
+
# Ensure parent directory exists
|
|
190
|
+
ensure_directory_exists(file_path.parent)
|
|
191
|
+
|
|
192
|
+
# Create backup if requested and file exists
|
|
193
|
+
if backup and file_path.exists():
|
|
194
|
+
backup_file(file_path)
|
|
195
|
+
|
|
196
|
+
if atomic:
|
|
197
|
+
# Use atomic write with temporary file
|
|
198
|
+
with SecureFileManager(file_path, "w", encoding=encoding) as f:
|
|
199
|
+
f.write(content)
|
|
200
|
+
else:
|
|
201
|
+
# Direct write
|
|
202
|
+
with open(file_path, "w", encoding=encoding) as f:
|
|
203
|
+
f.write(content)
|
|
204
|
+
|
|
205
|
+
file_logger.debug(
|
|
206
|
+
"file_written_safely",
|
|
207
|
+
file_path=str(file_path),
|
|
208
|
+
char_count=len(content),
|
|
209
|
+
)
|
|
210
|
+
return file_path
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
raise FileWriteError(
|
|
214
|
+
f"Failed to write file: {e}", Path(file_path), "write"
|
|
215
|
+
) from e
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def safe_read_file(
|
|
219
|
+
file_path: str | Path, encoding: str = "utf-8", default: str | None = None
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Read file content safely with proper error handling.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
file_path: Path to the file to read
|
|
225
|
+
encoding: File encoding (default: utf-8)
|
|
226
|
+
default: Default content to return if file doesn't exist
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
File content as string
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
FileReadError: If file reading fails
|
|
233
|
+
FileNotFoundError: If file doesn't exist and no default provided
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
file_path = Path(file_path)
|
|
237
|
+
|
|
238
|
+
if not file_path.exists():
|
|
239
|
+
if default is not None:
|
|
240
|
+
file_logger.debug(
|
|
241
|
+
"file_not_found_returning_default",
|
|
242
|
+
file_path=str(file_path),
|
|
243
|
+
)
|
|
244
|
+
return default
|
|
245
|
+
else:
|
|
246
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
247
|
+
|
|
248
|
+
content = file_path.read_text(encoding=encoding)
|
|
249
|
+
file_logger.debug(
|
|
250
|
+
"file_read_safely",
|
|
251
|
+
file_path=str(file_path),
|
|
252
|
+
char_count=len(content),
|
|
253
|
+
)
|
|
254
|
+
return content
|
|
255
|
+
|
|
256
|
+
except FileNotFoundError:
|
|
257
|
+
raise
|
|
258
|
+
except Exception as e:
|
|
259
|
+
raise FileReadError(f"Failed to read file: {e}", Path(file_path), "read") from e
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def file_exists_check(
|
|
263
|
+
file_path: str | Path,
|
|
264
|
+
must_be_file: bool = True,
|
|
265
|
+
must_be_readable: bool = False,
|
|
266
|
+
min_size: int | None = None,
|
|
267
|
+
) -> bool:
|
|
268
|
+
"""Unified file existence checking with optional validation.
|
|
269
|
+
|
|
270
|
+
This function centralizes all file existence checks to eliminate
|
|
271
|
+
the repeated patterns: if file.exists() or if not file.exists()
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
file_path: Path to check
|
|
275
|
+
must_be_file: Ensure path is a file, not directory (default: True)
|
|
276
|
+
must_be_readable: Ensure file is readable (default: False)
|
|
277
|
+
min_size: Minimum file size in bytes (default: None)
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if file exists and meets all criteria, False otherwise
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
file_path = Path(file_path)
|
|
284
|
+
|
|
285
|
+
# Basic existence check
|
|
286
|
+
if not file_path.exists():
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
# Check if it's actually a file
|
|
290
|
+
if must_be_file and not file_path.is_file():
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
# Check if file is readable
|
|
294
|
+
if must_be_readable and not os.access(file_path, os.R_OK):
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
# Check minimum size
|
|
298
|
+
if min_size is not None:
|
|
299
|
+
if file_path.stat().st_size < min_size:
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
file_logger.warning(
|
|
306
|
+
"file_existence_check_failed",
|
|
307
|
+
file_path=str(file_path),
|
|
308
|
+
error=str(e),
|
|
309
|
+
severity="operational",
|
|
310
|
+
)
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def get_file_size(file_path: str | Path) -> int:
|
|
315
|
+
"""Get file size with error handling.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
file_path: Path to the file
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
File size in bytes
|
|
322
|
+
|
|
323
|
+
Raises:
|
|
324
|
+
FileOperationError: If unable to get file size
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
file_path = Path(file_path)
|
|
328
|
+
if not file_path.exists():
|
|
329
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
330
|
+
|
|
331
|
+
size = file_path.stat().st_size
|
|
332
|
+
file_logger.debug(
|
|
333
|
+
"file_size_retrieved",
|
|
334
|
+
file_path=str(file_path),
|
|
335
|
+
size_bytes=size,
|
|
336
|
+
)
|
|
337
|
+
return size
|
|
338
|
+
|
|
339
|
+
except Exception as e:
|
|
340
|
+
raise FileOperationError(
|
|
341
|
+
f"Failed to get file size: {e}", Path(file_path), "stat"
|
|
342
|
+
) from e
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def backup_file(
|
|
346
|
+
file_path: str | Path,
|
|
347
|
+
backup_suffix: str = ".backup",
|
|
348
|
+
backup_dir: str | Path | None = None,
|
|
349
|
+
) -> Path | None:
|
|
350
|
+
"""Create backup of existing file before modification.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
file_path: Path to the file to backup
|
|
354
|
+
backup_suffix: Suffix to add to backup file (default: .backup)
|
|
355
|
+
backup_dir: Directory to store backups (default: same as original)
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Path to backup file if created, None if original doesn't exist
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
FileOperationError: If backup creation fails
|
|
362
|
+
"""
|
|
363
|
+
try:
|
|
364
|
+
file_path = Path(file_path)
|
|
365
|
+
|
|
366
|
+
if not file_path.exists():
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
if backup_dir:
|
|
370
|
+
backup_dir = Path(backup_dir)
|
|
371
|
+
ensure_directory_exists(backup_dir)
|
|
372
|
+
backup_path = backup_dir / f"{file_path.name}{backup_suffix}"
|
|
373
|
+
else:
|
|
374
|
+
backup_path = file_path.with_suffix(f"{file_path.suffix}{backup_suffix}")
|
|
375
|
+
|
|
376
|
+
shutil.copy2(file_path, backup_path)
|
|
377
|
+
file_logger.info(
|
|
378
|
+
"file_backed_up",
|
|
379
|
+
file_path=str(file_path),
|
|
380
|
+
backup_path=str(backup_path),
|
|
381
|
+
)
|
|
382
|
+
return backup_path
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
raise FileOperationError(
|
|
386
|
+
f"Failed to create backup: {e}", Path(file_path), "backup"
|
|
387
|
+
) from e
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def cleanup_temp_files(directory: str | Path, pattern: str = "*.tmp") -> int:
|
|
391
|
+
"""Clean up temporary files in a directory.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
directory: Directory to clean
|
|
395
|
+
pattern: File pattern to match (default: *.tmp)
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Number of files cleaned up
|
|
399
|
+
|
|
400
|
+
Raises:
|
|
401
|
+
FileOperationError: If cleanup fails
|
|
402
|
+
"""
|
|
403
|
+
try:
|
|
404
|
+
directory = Path(directory)
|
|
405
|
+
count = 0
|
|
406
|
+
|
|
407
|
+
if not directory.exists():
|
|
408
|
+
return 0
|
|
409
|
+
|
|
410
|
+
for temp_file in directory.glob(pattern):
|
|
411
|
+
try:
|
|
412
|
+
temp_file.unlink()
|
|
413
|
+
count += 1
|
|
414
|
+
file_logger.debug(
|
|
415
|
+
"temp_file_cleaned_up",
|
|
416
|
+
temp_file=str(temp_file),
|
|
417
|
+
)
|
|
418
|
+
except OSError as e:
|
|
419
|
+
file_logger.warning(
|
|
420
|
+
"failed_to_remove_temp_file",
|
|
421
|
+
temp_file=str(temp_file),
|
|
422
|
+
error=str(e),
|
|
423
|
+
severity="operational",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if count > 0:
|
|
427
|
+
file_logger.info(
|
|
428
|
+
"temporary_files_cleaned_up",
|
|
429
|
+
count=count,
|
|
430
|
+
directory=str(directory),
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
return count
|
|
434
|
+
|
|
435
|
+
except Exception as e:
|
|
436
|
+
raise FileOperationError(
|
|
437
|
+
f"Failed to cleanup temp files: {e}",
|
|
438
|
+
Path(directory) if isinstance(directory, str) else directory,
|
|
439
|
+
"cleanup",
|
|
440
|
+
) from e
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Shared utilities for roadmap directory path construction."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_roadmap_paths(
|
|
8
|
+
root_path: Path, roadmap_dir_name: str = ".roadmap"
|
|
9
|
+
) -> dict[str, Any]:
|
|
10
|
+
"""Build all standard roadmap directory and file paths.
|
|
11
|
+
|
|
12
|
+
Consolidates the common pattern of constructing roadmap paths
|
|
13
|
+
used across the application (core, initialization, etc).
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
root_path: Root directory of the project
|
|
17
|
+
roadmap_dir_name: Name of the roadmap directory (default: .roadmap)
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Dictionary containing all standard roadmap paths
|
|
21
|
+
"""
|
|
22
|
+
roadmap_dir = root_path / roadmap_dir_name
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"root_path": root_path,
|
|
26
|
+
"roadmap_dir_name": roadmap_dir_name,
|
|
27
|
+
"roadmap_dir": roadmap_dir,
|
|
28
|
+
"issues_dir": roadmap_dir / "issues",
|
|
29
|
+
"milestones_dir": roadmap_dir / "milestones",
|
|
30
|
+
"projects_dir": roadmap_dir / "projects",
|
|
31
|
+
"templates_dir": roadmap_dir / "templates",
|
|
32
|
+
"artifacts_dir": roadmap_dir / "artifacts",
|
|
33
|
+
"config_file": roadmap_dir / "config.yaml",
|
|
34
|
+
"db_dir": roadmap_dir / "db",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["build_roadmap_paths"]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Utilities for status summary calculations.
|
|
2
|
+
|
|
3
|
+
Consolidates repeated patterns for aggregating status counts from
|
|
4
|
+
health checks and validation results.
|
|
5
|
+
|
|
6
|
+
Eliminates duplicate status counting logic from:
|
|
7
|
+
- HealthCheckService.get_health_summary()
|
|
8
|
+
- Infrastructure validators
|
|
9
|
+
- Data integrity validators
|
|
10
|
+
- Other status aggregation code
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections import Counter
|
|
14
|
+
from collections.abc import Mapping, Sequence
|
|
15
|
+
from enum import Enum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StatusSummary:
|
|
19
|
+
"""Utility for computing status summaries from check results.
|
|
20
|
+
|
|
21
|
+
Provides consistent methods for:
|
|
22
|
+
- Counting items by status enum value
|
|
23
|
+
- Aggregating health check results
|
|
24
|
+
- Creating summary dictionaries
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
|
|
28
|
+
# Count items by status
|
|
29
|
+
counts = StatusSummary.count_by_status([
|
|
30
|
+
("check1", HealthStatus.HEALTHY),
|
|
31
|
+
("check2", HealthStatus.DEGRADED),
|
|
32
|
+
("check3", HealthStatus.HEALTHY),
|
|
33
|
+
])
|
|
34
|
+
# Returns: {"healthy": 2, "degraded": 1}
|
|
35
|
+
|
|
36
|
+
# Summarize health checks dict
|
|
37
|
+
summary = StatusSummary.summarize_checks({
|
|
38
|
+
"roadmap_dir": (HealthStatus.HEALTHY, "OK"),
|
|
39
|
+
"database": (HealthStatus.DEGRADED, "Lag detected"),
|
|
40
|
+
"git": (HealthStatus.UNHEALTHY, "Not a repo"),
|
|
41
|
+
})
|
|
42
|
+
# Returns: {
|
|
43
|
+
# "total": 3,
|
|
44
|
+
# "healthy": 1,
|
|
45
|
+
# "degraded": 1,
|
|
46
|
+
# "unhealthy": 1,
|
|
47
|
+
# }
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def count_by_status(items: Sequence[tuple[str, Enum]]) -> dict[str, int]:
|
|
52
|
+
"""Count items by status enum value.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
items: List of (label, status_enum) tuples
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict mapping status values to counts
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
items = [
|
|
62
|
+
("check1", HealthStatus.HEALTHY),
|
|
63
|
+
("check2", HealthStatus.HEALTHY),
|
|
64
|
+
("check3", HealthStatus.DEGRADED),
|
|
65
|
+
]
|
|
66
|
+
counts = StatusSummary.count_by_status(items)
|
|
67
|
+
# Returns: {"healthy": 2, "degraded": 1}
|
|
68
|
+
"""
|
|
69
|
+
counter = Counter(status.value for _, status in items)
|
|
70
|
+
return dict(counter)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def summarize_checks(
|
|
74
|
+
checks: Mapping[str, tuple[Enum, str]], status_enum_class=None
|
|
75
|
+
) -> dict[str, int]:
|
|
76
|
+
"""Get summary counts from checks dict with generic status enum.
|
|
77
|
+
|
|
78
|
+
Aggregates check results into overall counts by status, without
|
|
79
|
+
hardcoding specific status values. The method is generic and works
|
|
80
|
+
with any status enum.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
checks: Dict of {check_name: (status_enum, message)}
|
|
84
|
+
status_enum_class: Optional status enum class for type validation.
|
|
85
|
+
If provided, all unique status values will be counted.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with counts: {
|
|
89
|
+
"total": count,
|
|
90
|
+
"status1": count,
|
|
91
|
+
"status2": count,
|
|
92
|
+
...
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
from roadmap.core.domain.health import HealthStatus
|
|
97
|
+
|
|
98
|
+
checks = {
|
|
99
|
+
"roadmap_dir": (HealthStatus.HEALTHY, "OK"),
|
|
100
|
+
"database": (HealthStatus.DEGRADED, "Lag"),
|
|
101
|
+
"git": (HealthStatus.UNHEALTHY, "Not a repo"),
|
|
102
|
+
}
|
|
103
|
+
summary = StatusSummary.summarize_checks(checks, HealthStatus)
|
|
104
|
+
# Returns: {
|
|
105
|
+
# "total": 3,
|
|
106
|
+
# "healthy": 1,
|
|
107
|
+
# "degraded": 1,
|
|
108
|
+
# "unhealthy": 1,
|
|
109
|
+
# }
|
|
110
|
+
"""
|
|
111
|
+
statuses = [status for status, _ in checks.values()]
|
|
112
|
+
|
|
113
|
+
total = len(statuses)
|
|
114
|
+
counts = {"total": total}
|
|
115
|
+
|
|
116
|
+
# Count by status value
|
|
117
|
+
counter = Counter(s.value for s in statuses)
|
|
118
|
+
counts.update(dict(counter))
|
|
119
|
+
|
|
120
|
+
# If no explicit enum class, try to infer from first status
|
|
121
|
+
if not status_enum_class and statuses:
|
|
122
|
+
status_enum_class = type(statuses[0])
|
|
123
|
+
|
|
124
|
+
# Ensure all enum values are represented (with 0 count if missing)
|
|
125
|
+
if status_enum_class:
|
|
126
|
+
for member in status_enum_class:
|
|
127
|
+
if member.value not in counts:
|
|
128
|
+
counts[member.value] = 0
|
|
129
|
+
|
|
130
|
+
return counts
|