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,198 @@
|
|
|
1
|
+
"""Configuration management commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from structlog import get_logger
|
|
5
|
+
|
|
6
|
+
from roadmap.common.configuration import ConfigLoader
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
|
|
9
|
+
logger = get_logger()
|
|
10
|
+
console = get_console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group("config")
|
|
14
|
+
def config():
|
|
15
|
+
"""Manage roadmap configuration."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@config.command("view")
|
|
19
|
+
@click.option(
|
|
20
|
+
"--project",
|
|
21
|
+
is_flag=True,
|
|
22
|
+
help="Show project-level config instead of user-level",
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--level",
|
|
26
|
+
type=click.Choice(["user", "project", "merged"], case_sensitive=False),
|
|
27
|
+
default="merged",
|
|
28
|
+
help="Which config level to view (default: merged)",
|
|
29
|
+
)
|
|
30
|
+
def view(project: bool, level: str):
|
|
31
|
+
"""View current configuration."""
|
|
32
|
+
# Handle legacy --project flag
|
|
33
|
+
if project:
|
|
34
|
+
level = "project"
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
if level == "user":
|
|
38
|
+
config_obj = ConfigLoader.load_user_config()
|
|
39
|
+
source = "User-level config (~/.roadmap/config.yaml)"
|
|
40
|
+
elif level == "project":
|
|
41
|
+
config_obj = ConfigLoader.load_project_config()
|
|
42
|
+
source = "Project-level config (.roadmap/config.yaml)"
|
|
43
|
+
else: # merged
|
|
44
|
+
config_obj = ConfigLoader.load_config()
|
|
45
|
+
source = "Merged configuration (defaults + user + project)"
|
|
46
|
+
|
|
47
|
+
if config_obj is None and level != "merged":
|
|
48
|
+
console.print(f"[yellow]No {level} configuration file found.[/yellow]")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
console.print(f"\n[bold blue]{source}[/bold blue]")
|
|
52
|
+
console.print("=" * 60)
|
|
53
|
+
|
|
54
|
+
if config_obj:
|
|
55
|
+
# Pretty-print as YAML-like structure
|
|
56
|
+
data = config_obj.model_dump(exclude_none=True)
|
|
57
|
+
_print_config_dict(data, indent=0)
|
|
58
|
+
else:
|
|
59
|
+
console.print("[dim]Using all defaults[/dim]")
|
|
60
|
+
|
|
61
|
+
console.print()
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
|
|
65
|
+
raise click.ClickException(str(e)) from e
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@config.command("get")
|
|
69
|
+
@click.argument("key")
|
|
70
|
+
def get_cmd(key: str):
|
|
71
|
+
"""Get a configuration value by key (e.g., 'export.directory')."""
|
|
72
|
+
try:
|
|
73
|
+
value = ConfigLoader.get_config_value(key)
|
|
74
|
+
if value is None:
|
|
75
|
+
console.print(
|
|
76
|
+
f"[yellow]Configuration key '{key}' not found.[/yellow]",
|
|
77
|
+
style="yellow",
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
console.print(f"[cyan]{key}[/cyan]: {value}")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
|
|
83
|
+
raise click.ClickException(str(e)) from e
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@config.command("set")
|
|
87
|
+
@click.argument("key")
|
|
88
|
+
@click.argument("value")
|
|
89
|
+
@click.option(
|
|
90
|
+
"--project",
|
|
91
|
+
is_flag=True,
|
|
92
|
+
help="Set in project-level config instead of user-level",
|
|
93
|
+
)
|
|
94
|
+
def set_cmd(key: str, value: str, project: bool):
|
|
95
|
+
"""Set a configuration value by key (e.g., 'export.format json')."""
|
|
96
|
+
try:
|
|
97
|
+
# Try to parse value as boolean or other types
|
|
98
|
+
parsed_value = _parse_config_value(value)
|
|
99
|
+
|
|
100
|
+
success = ConfigLoader.set_config_value(
|
|
101
|
+
key, parsed_value, project_level=project
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if success:
|
|
105
|
+
level = "project" if project else "user"
|
|
106
|
+
console.print(
|
|
107
|
+
f"[green]✓[/green] Set [cyan]{key}[/cyan] = {parsed_value} in {level} config"
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
console.print(f"[red]✗[/red] Failed to set {key}", style="bold red")
|
|
111
|
+
raise click.ClickException(f"Failed to set {key}")
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
|
|
115
|
+
raise click.ClickException(str(e)) from e
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@config.command("reset")
|
|
119
|
+
@click.option(
|
|
120
|
+
"--project",
|
|
121
|
+
is_flag=True,
|
|
122
|
+
help="Reset project-level config instead of user-level",
|
|
123
|
+
)
|
|
124
|
+
@click.confirmation_option(prompt="Are you sure you want to reset the configuration?")
|
|
125
|
+
def reset(project: bool):
|
|
126
|
+
"""Reset configuration to defaults."""
|
|
127
|
+
try:
|
|
128
|
+
if project:
|
|
129
|
+
path = ConfigLoader.get_project_config_path()
|
|
130
|
+
path.unlink(missing_ok=True)
|
|
131
|
+
console.print(
|
|
132
|
+
"[green]✓[/green] Reset project-level configuration", style="green"
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
path = ConfigLoader.get_user_config_path()
|
|
136
|
+
path.unlink(missing_ok=True)
|
|
137
|
+
console.print(
|
|
138
|
+
"[green]✓[/green] Reset user-level configuration", style="green"
|
|
139
|
+
)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
|
|
142
|
+
raise click.ClickException(str(e)) from e
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _print_config_dict(data: dict, indent: int = 0):
|
|
146
|
+
"""Pretty-print configuration dictionary.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data: Dictionary to print
|
|
150
|
+
indent: Current indentation level
|
|
151
|
+
"""
|
|
152
|
+
for key, value in data.items():
|
|
153
|
+
if isinstance(value, dict):
|
|
154
|
+
console.print(f"{' ' * indent}[bold]{key}:[/bold]")
|
|
155
|
+
_print_config_dict(value, indent + 1)
|
|
156
|
+
else:
|
|
157
|
+
console.print(f"{' ' * indent}[cyan]{key}[/cyan]: {value}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _parse_config_value(value: str):
|
|
161
|
+
"""Parse string value to appropriate Python type.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
value: String value to parse
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Parsed value (bool, int, float, or str)
|
|
168
|
+
"""
|
|
169
|
+
# Try boolean
|
|
170
|
+
if value.lower() in ("true", "yes", "on"):
|
|
171
|
+
return True
|
|
172
|
+
if value.lower() in ("false", "no", "off"):
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
# Try integer
|
|
176
|
+
try:
|
|
177
|
+
return int(value)
|
|
178
|
+
except ValueError as e:
|
|
179
|
+
logger.debug(
|
|
180
|
+
"int_conversion_failed",
|
|
181
|
+
value=value,
|
|
182
|
+
error=str(e),
|
|
183
|
+
action="parse_config_value",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Try float
|
|
187
|
+
try:
|
|
188
|
+
return float(value)
|
|
189
|
+
except ValueError as e:
|
|
190
|
+
logger.debug(
|
|
191
|
+
"float_conversion_failed",
|
|
192
|
+
value=value,
|
|
193
|
+
error=str(e),
|
|
194
|
+
action="parse_config_value",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Return as string
|
|
198
|
+
return value
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Utility functions for CLI operations.
|
|
2
|
+
|
|
3
|
+
This module re-exports core utilities from roadmap.common to maintain
|
|
4
|
+
backward compatibility with existing CLI code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Re-export console utilities from common module to avoid duplication
|
|
8
|
+
from roadmap.common.console import get_console, is_testing_environment
|
|
9
|
+
|
|
10
|
+
__all__ = ["get_console", "is_testing_environment"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Core CLI commands: init and status.
|
|
2
|
+
|
|
3
|
+
These are the fundamental commands needed to get started with Roadmap.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.cli.init import init
|
|
9
|
+
from roadmap.adapters.cli.status import health, status
|
|
10
|
+
from roadmap.common.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
@click.pass_context
|
|
17
|
+
def core(ctx: click.Context) -> None:
|
|
18
|
+
"""Core roadmap commands for initialization and status."""
|
|
19
|
+
if ctx.obj is None:
|
|
20
|
+
ctx.obj = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Register commands
|
|
24
|
+
core.add_command(init)
|
|
25
|
+
core.add_command(status)
|
|
26
|
+
core.add_command(health)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""CRUD operations base classes and shared utilities.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- Base classes for Create, Update, Delete, Archive, Restore operations
|
|
5
|
+
- Shared CRUD helpers and utilities
|
|
6
|
+
- Entity-specific builders and validators
|
|
7
|
+
- Template method pattern for extensible CRUD implementations
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from roadmap.adapters.cli.crud.base_archive import BaseArchive
|
|
11
|
+
from roadmap.adapters.cli.crud.base_create import BaseCreate
|
|
12
|
+
from roadmap.adapters.cli.crud.base_delete import BaseDelete
|
|
13
|
+
from roadmap.adapters.cli.crud.base_restore import BaseRestore
|
|
14
|
+
from roadmap.adapters.cli.crud.base_update import BaseUpdate
|
|
15
|
+
from roadmap.adapters.cli.crud.crud_helpers import (
|
|
16
|
+
CRUDOperation,
|
|
17
|
+
EntityType,
|
|
18
|
+
collect_archive_files,
|
|
19
|
+
display_archive_success,
|
|
20
|
+
display_restore_success,
|
|
21
|
+
format_entity_id,
|
|
22
|
+
format_entity_title,
|
|
23
|
+
get_active_dir,
|
|
24
|
+
get_archive_dir,
|
|
25
|
+
validate_entity_exists,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# Enums
|
|
30
|
+
"EntityType",
|
|
31
|
+
"CRUDOperation",
|
|
32
|
+
# Base classes
|
|
33
|
+
"BaseCreate",
|
|
34
|
+
"BaseUpdate",
|
|
35
|
+
"BaseDelete",
|
|
36
|
+
"BaseArchive",
|
|
37
|
+
"BaseRestore",
|
|
38
|
+
# Helpers
|
|
39
|
+
"validate_entity_exists",
|
|
40
|
+
"format_entity_title",
|
|
41
|
+
"format_entity_id",
|
|
42
|
+
"collect_archive_files",
|
|
43
|
+
"get_archive_dir",
|
|
44
|
+
"get_active_dir",
|
|
45
|
+
"display_archive_success",
|
|
46
|
+
"display_restore_success",
|
|
47
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Base class for archive operations across all entity types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from roadmap.adapters.cli.crud.crud_helpers import (
|
|
10
|
+
EntityType,
|
|
11
|
+
display_archive_success,
|
|
12
|
+
get_active_dir,
|
|
13
|
+
get_archive_dir,
|
|
14
|
+
)
|
|
15
|
+
from roadmap.common.console import get_console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseArchive(ABC):
|
|
19
|
+
"""Abstract base class for entity archive commands using Template Method pattern.
|
|
20
|
+
|
|
21
|
+
Subclasses must implement:
|
|
22
|
+
- entity_type: The EntityType this archives
|
|
23
|
+
- get_entities_to_archive(): Determine what entities to archive based on filters
|
|
24
|
+
- post_archive_hook(): Update database and handle entity-specific logic
|
|
25
|
+
|
|
26
|
+
Subclasses may override:
|
|
27
|
+
- validate_entity_before_archive(): Custom validation before archiving
|
|
28
|
+
- find_entity_files(): Custom file finding logic (default: filesystem search)
|
|
29
|
+
|
|
30
|
+
This class handles the overall archive workflow:
|
|
31
|
+
1. Determine entities to archive (via get_entities_to_archive)
|
|
32
|
+
2. Find corresponding files
|
|
33
|
+
3. Move files to archive
|
|
34
|
+
4. Update state via post_archive_hook
|
|
35
|
+
5. Display results
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
entity_type: EntityType
|
|
39
|
+
|
|
40
|
+
def __init__(self, core: Any, console: Any = None) -> None:
|
|
41
|
+
"""Initialize archive command.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
core: The core application context
|
|
45
|
+
console: Optional console for output (uses default if not provided)
|
|
46
|
+
"""
|
|
47
|
+
self.core = core
|
|
48
|
+
self.console = console or get_console()
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def get_entities_to_archive(
|
|
52
|
+
self, entity_id: str | None = None, **filters
|
|
53
|
+
) -> list[Any]:
|
|
54
|
+
"""Determine which entities should be archived.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
entity_id: Optional specific entity ID to archive
|
|
58
|
+
**filters: Entity-specific filter criteria (e.g., all_closed, orphaned)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of entities to archive
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def post_archive_hook(
|
|
66
|
+
self, archived_files: list[Path], entities: list[Any], **kwargs
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Handle entity-specific state updates after archiving.
|
|
69
|
+
|
|
70
|
+
Override to update database, send notifications, etc.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
archived_files: List of files that were archived
|
|
74
|
+
entities: List of entities that were archived
|
|
75
|
+
**kwargs: Original CLI arguments
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def validate_entity_before_archive(
|
|
79
|
+
self, entity: Any, **kwargs
|
|
80
|
+
) -> tuple[bool, str | None]:
|
|
81
|
+
"""Validate entity before archiving.
|
|
82
|
+
|
|
83
|
+
Allows subclasses to add entity-specific validation rules.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
entity: The entity being considered for archive
|
|
87
|
+
**kwargs: Original CLI arguments (e.g., force flag)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Tuple of (is_valid, error_message_or_none)
|
|
91
|
+
"""
|
|
92
|
+
return True, None
|
|
93
|
+
|
|
94
|
+
def find_entity_files(self, entities: list[Any]) -> list[Path]:
|
|
95
|
+
"""Find file paths corresponding to entities.
|
|
96
|
+
|
|
97
|
+
Override if entity-to-file mapping is non-standard.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
entities: List of entities to find files for
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of file paths
|
|
104
|
+
"""
|
|
105
|
+
active_dir = get_active_dir(self.entity_type)
|
|
106
|
+
files = []
|
|
107
|
+
for entity in entities:
|
|
108
|
+
# Search by entity ID prefix
|
|
109
|
+
matching = list(active_dir.rglob(f"{str(entity.id)[:8]}*.md"))
|
|
110
|
+
if matching:
|
|
111
|
+
files.append(matching[0])
|
|
112
|
+
return files
|
|
113
|
+
|
|
114
|
+
def execute(self, entity_id: str | None = None, **kwargs) -> bool:
|
|
115
|
+
"""Execute archive operation (Template Method).
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
entity_id: Optional specific entity ID to archive
|
|
119
|
+
**kwargs: Entity-specific filters and options
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if archive succeeded
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
# Step 1: Determine which entities to archive
|
|
126
|
+
entities = self.get_entities_to_archive(entity_id, **kwargs)
|
|
127
|
+
|
|
128
|
+
if not entities:
|
|
129
|
+
self.console.print(
|
|
130
|
+
f"No {self.entity_type.value}s to archive",
|
|
131
|
+
style="yellow",
|
|
132
|
+
)
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Step 2: Validate entities
|
|
136
|
+
invalid_entities = []
|
|
137
|
+
for entity in entities:
|
|
138
|
+
is_valid, error_msg = self.validate_entity_before_archive(
|
|
139
|
+
entity, **kwargs
|
|
140
|
+
)
|
|
141
|
+
if not is_valid:
|
|
142
|
+
invalid_entities.append((entity, error_msg))
|
|
143
|
+
|
|
144
|
+
if invalid_entities:
|
|
145
|
+
for entity, error_msg in invalid_entities:
|
|
146
|
+
self.console.print(
|
|
147
|
+
f"⚠️ Cannot archive {entity.id}: {error_msg}",
|
|
148
|
+
style="yellow",
|
|
149
|
+
)
|
|
150
|
+
# Continue with valid entities if any
|
|
151
|
+
entities = [e for e in entities if (e, None) not in invalid_entities]
|
|
152
|
+
if not entities:
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
# Step 3: Find corresponding files
|
|
156
|
+
files_to_archive = self.find_entity_files(entities)
|
|
157
|
+
|
|
158
|
+
if not files_to_archive:
|
|
159
|
+
self.console.print(
|
|
160
|
+
f"Could not find files for {len(entities)} {self.entity_type.value}(s)",
|
|
161
|
+
style="yellow",
|
|
162
|
+
)
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
# Step 4: Move files to archive
|
|
166
|
+
archive_dir = get_archive_dir(self.entity_type)
|
|
167
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
archived_files = []
|
|
170
|
+
failed_count = 0
|
|
171
|
+
|
|
172
|
+
for file_path in files_to_archive:
|
|
173
|
+
try:
|
|
174
|
+
archive_file = archive_dir / file_path.name
|
|
175
|
+
if archive_file.exists():
|
|
176
|
+
failed_count += 1
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
archive_file.write_text(file_path.read_text())
|
|
180
|
+
file_path.unlink()
|
|
181
|
+
archived_files.append(archive_file)
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.console.print(
|
|
185
|
+
f"⚠️ Failed to archive {file_path.name}: {str(e)}",
|
|
186
|
+
style="yellow",
|
|
187
|
+
)
|
|
188
|
+
failed_count += 1
|
|
189
|
+
|
|
190
|
+
# Step 5: Update state via hook
|
|
191
|
+
self.post_archive_hook(archived_files, entities, **kwargs)
|
|
192
|
+
|
|
193
|
+
# Step 6: Display results
|
|
194
|
+
display_archive_success(
|
|
195
|
+
self.entity_type,
|
|
196
|
+
len(archived_files),
|
|
197
|
+
failed_count,
|
|
198
|
+
console=self.console,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
self.console.print(
|
|
205
|
+
f"❌ Archive operation failed: {str(e)}",
|
|
206
|
+
style="red",
|
|
207
|
+
)
|
|
208
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Base class for create operations across all entity types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.cli.crud.crud_helpers import EntityType
|
|
7
|
+
from roadmap.adapters.cli.crud.crud_utils import (
|
|
8
|
+
create_entity_by_type,
|
|
9
|
+
get_entity_id,
|
|
10
|
+
get_entity_title,
|
|
11
|
+
)
|
|
12
|
+
from roadmap.adapters.cli.presentation.crud_presenter import CreatePresenter
|
|
13
|
+
from roadmap.common.console import get_console
|
|
14
|
+
from roadmap.common.errors.exceptions import ValidationError
|
|
15
|
+
from roadmap.common.logging import log_audit_event
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseCreate(ABC):
|
|
19
|
+
"""Abstract base class for entity creation commands.
|
|
20
|
+
|
|
21
|
+
Subclasses implement:
|
|
22
|
+
- entity_type: The EntityType this creates
|
|
23
|
+
- build_entity_dict(): Convert CLI args to entity dict
|
|
24
|
+
- post_create_hook(): Optional hook after creation (default: no-op)
|
|
25
|
+
|
|
26
|
+
Uses Template Method pattern for extensibility.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
entity_type: EntityType
|
|
30
|
+
|
|
31
|
+
def __init__(self, core: Any, console: Any = None) -> None:
|
|
32
|
+
"""Initialize create command.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
core: The core application context
|
|
36
|
+
console: Optional console for output (uses default if not provided)
|
|
37
|
+
"""
|
|
38
|
+
self.core = core
|
|
39
|
+
self.console = console or get_console()
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def build_entity_dict(self, **kwargs) -> dict[str, Any]:
|
|
43
|
+
"""Convert CLI arguments to entity dictionary.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
**kwargs: All click option values
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary of entity fields to create
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValidationError: If arguments are invalid
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def post_create_hook(self, entity: Any, **kwargs) -> None: # noqa: B027
|
|
57
|
+
"""Handle entity creation completion with optional custom logic.
|
|
58
|
+
|
|
59
|
+
Override to handle:
|
|
60
|
+
- Git branch creation
|
|
61
|
+
- Milestone assignments
|
|
62
|
+
- Custom notifications
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
entity: The created entity
|
|
66
|
+
**kwargs: Original CLI arguments
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def execute(self, title: str, **kwargs) -> Any | None:
|
|
71
|
+
"""Execute the create operation.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
title: Entity title/name
|
|
75
|
+
**kwargs: All other CLI arguments
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Created entity, or None if creation failed
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
# Build entity from arguments
|
|
82
|
+
entity_dict = self.build_entity_dict(title=title, **kwargs)
|
|
83
|
+
|
|
84
|
+
# Create via appropriate service
|
|
85
|
+
entity = self._create_entity(entity_dict)
|
|
86
|
+
if entity is None:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Run post-creation hooks
|
|
90
|
+
self.post_create_hook(entity, **kwargs)
|
|
91
|
+
|
|
92
|
+
# Log audit event for successful creation
|
|
93
|
+
entity_id = self._get_id(entity)
|
|
94
|
+
log_audit_event(
|
|
95
|
+
action="create",
|
|
96
|
+
entity_type=self.entity_type.value,
|
|
97
|
+
entity_id=entity_id,
|
|
98
|
+
after=entity_dict,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Display success
|
|
102
|
+
self._display_success(entity)
|
|
103
|
+
|
|
104
|
+
return entity
|
|
105
|
+
|
|
106
|
+
except ValidationError:
|
|
107
|
+
# ValidationError will be handled by centralized CLI exception handler
|
|
108
|
+
# which formats it and directs to stderr
|
|
109
|
+
raise
|
|
110
|
+
except Exception:
|
|
111
|
+
# Other exceptions will also be handled by centralized handler
|
|
112
|
+
raise
|
|
113
|
+
|
|
114
|
+
def _create_entity(self, entity_dict: dict[str, Any]) -> Any | None:
|
|
115
|
+
"""Create entity via appropriate service.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
entity_dict: Dictionary of entity fields
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Created entity or None if failed
|
|
122
|
+
"""
|
|
123
|
+
return create_entity_by_type(self.core, self.entity_type, entity_dict)
|
|
124
|
+
|
|
125
|
+
def _display_success(self, entity: Any) -> None:
|
|
126
|
+
"""Display success message using presenter.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
entity: The created entity
|
|
130
|
+
"""
|
|
131
|
+
presenter = CreatePresenter()
|
|
132
|
+
presenter.render(entity)
|
|
133
|
+
|
|
134
|
+
def _get_title(self, entity: Any) -> str:
|
|
135
|
+
"""Get entity title/name.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
entity: The entity object
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Title or name string
|
|
142
|
+
"""
|
|
143
|
+
return get_entity_title(entity)
|
|
144
|
+
|
|
145
|
+
def _get_id(self, entity: Any) -> str:
|
|
146
|
+
"""Get entity ID.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
entity: The entity object
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Entity ID string
|
|
153
|
+
"""
|
|
154
|
+
return get_entity_id(entity)
|