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,182 @@
|
|
|
1
|
+
"""Initialization workflow service for roadmap CLI.
|
|
2
|
+
|
|
3
|
+
Orchestrates the core initialization workflow steps.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from roadmap.common.console import get_console
|
|
12
|
+
from roadmap.common.errors.exceptions import OperationError
|
|
13
|
+
from roadmap.common.security import create_secure_directory
|
|
14
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
15
|
+
|
|
16
|
+
from .utils import InitializationManifest
|
|
17
|
+
|
|
18
|
+
logger = structlog.get_logger()
|
|
19
|
+
|
|
20
|
+
console = get_console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InitializationWorkflow:
|
|
24
|
+
"""Orchestrates the initialization workflow steps."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, core: RoadmapCore):
|
|
27
|
+
"""Initialize InitializationWorkflow.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
core: Core roadmap instance.
|
|
31
|
+
"""
|
|
32
|
+
self.core = core
|
|
33
|
+
|
|
34
|
+
def cleanup_existing(self) -> bool:
|
|
35
|
+
"""Remove existing roadmap directory.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if successful, False otherwise
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
OperationError: If removal fails
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
if self.core.roadmap_dir.exists():
|
|
45
|
+
shutil.rmtree(self.core.roadmap_dir)
|
|
46
|
+
return True
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise OperationError(
|
|
49
|
+
operation="remove existing roadmap", reason=str(e)
|
|
50
|
+
) from e
|
|
51
|
+
|
|
52
|
+
def create_structure(self) -> None:
|
|
53
|
+
"""Create the basic roadmap structure."""
|
|
54
|
+
self.core.initialize()
|
|
55
|
+
|
|
56
|
+
def create_structure_preserve_data(self) -> bool:
|
|
57
|
+
"""Create roadmap structure while preserving existing data.
|
|
58
|
+
|
|
59
|
+
If roadmap already exists, creates only missing directories and templates.
|
|
60
|
+
Returns True if successful, raises OperationError otherwise.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
OperationError: If creation fails
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
roadmap_dir = self.core.roadmap_dir
|
|
67
|
+
|
|
68
|
+
# If directory doesn't exist, just do normal init
|
|
69
|
+
if not roadmap_dir.exists():
|
|
70
|
+
self.core.initialize()
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
# Roadmap exists - create only missing parts
|
|
74
|
+
# Ensure subdirectories exist
|
|
75
|
+
for subdir in [
|
|
76
|
+
self.core.issues_dir,
|
|
77
|
+
self.core.milestones_dir,
|
|
78
|
+
self.core.projects_dir,
|
|
79
|
+
self.core.templates_dir,
|
|
80
|
+
self.core.artifacts_dir,
|
|
81
|
+
]:
|
|
82
|
+
create_secure_directory(subdir, 0o755)
|
|
83
|
+
|
|
84
|
+
# Create templates only if missing
|
|
85
|
+
self._create_missing_templates()
|
|
86
|
+
|
|
87
|
+
# Update .gitignore (safe to call multiple times)
|
|
88
|
+
self.core._init_manager._update_gitignore()
|
|
89
|
+
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise OperationError(
|
|
93
|
+
operation="create roadmap structure", reason=str(e)
|
|
94
|
+
) from e
|
|
95
|
+
|
|
96
|
+
def _create_missing_templates(self) -> None:
|
|
97
|
+
"""Create template files only if they don't exist."""
|
|
98
|
+
templates_dir = self.core.templates_dir
|
|
99
|
+
|
|
100
|
+
# Check if templates already exist
|
|
101
|
+
if list(templates_dir.glob("*.md")):
|
|
102
|
+
return # Templates already exist, skip
|
|
103
|
+
|
|
104
|
+
# Otherwise create them via initialization manager
|
|
105
|
+
self.core._init_manager._create_default_templates()
|
|
106
|
+
|
|
107
|
+
def generate_config_file(self, user_name: str | None = None) -> None:
|
|
108
|
+
"""Generate the config file with user information.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
user_name: User name to store in config, auto-detected if None
|
|
112
|
+
"""
|
|
113
|
+
from roadmap.common.configuration import ConfigManager
|
|
114
|
+
|
|
115
|
+
# Auto-detect user if not provided
|
|
116
|
+
if not user_name:
|
|
117
|
+
user_name = ConfigManager.auto_detect_user()
|
|
118
|
+
if not user_name:
|
|
119
|
+
user_name = "unknown"
|
|
120
|
+
|
|
121
|
+
# Auto-detect GitHub info
|
|
122
|
+
github_owner = ConfigManager.auto_detect_github_username()
|
|
123
|
+
|
|
124
|
+
# Create default config
|
|
125
|
+
config = ConfigManager.create_default_config(
|
|
126
|
+
user_name=user_name,
|
|
127
|
+
github_owner=github_owner,
|
|
128
|
+
github_repo=None, # User would configure repo separately
|
|
129
|
+
github_enabled=False, # Disabled by default
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Save config
|
|
133
|
+
manager = ConfigManager(self.core.config_file)
|
|
134
|
+
manager.save(config)
|
|
135
|
+
|
|
136
|
+
def ensure_gitignore_entry(self) -> None:
|
|
137
|
+
"""Ensure config.yaml is in .gitignore to prevent accidental commits."""
|
|
138
|
+
gitignore_path = Path.cwd() / ".gitignore"
|
|
139
|
+
config_entry = ".roadmap/config.yaml"
|
|
140
|
+
config_entry_with_comment = (
|
|
141
|
+
".roadmap/config.yaml # Local user configuration (not to be shared)"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# If .gitignore doesn't exist, create it
|
|
145
|
+
if not gitignore_path.exists():
|
|
146
|
+
gitignore_path.write_text(
|
|
147
|
+
f"{config_entry_with_comment}\n.env.local\n.env.*.local\n"
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Read existing .gitignore
|
|
152
|
+
content = gitignore_path.read_text()
|
|
153
|
+
|
|
154
|
+
# Check if entry already exists (with or without comment)
|
|
155
|
+
if config_entry in content:
|
|
156
|
+
return # Already there
|
|
157
|
+
|
|
158
|
+
# Append entry to .gitignore
|
|
159
|
+
if not content.endswith("\n"):
|
|
160
|
+
content += "\n"
|
|
161
|
+
|
|
162
|
+
content += f"\n{config_entry_with_comment}\n"
|
|
163
|
+
gitignore_path.write_text(content)
|
|
164
|
+
|
|
165
|
+
def record_created_paths(self, manifest: InitializationManifest) -> None:
|
|
166
|
+
"""Record all created paths in the manifest."""
|
|
167
|
+
manifest.add_path(self.core.roadmap_dir)
|
|
168
|
+
manifest.add_path(self.core.projects_dir)
|
|
169
|
+
manifest.add_path(self.core.templates_dir)
|
|
170
|
+
manifest.add_path(self.core.config_file)
|
|
171
|
+
|
|
172
|
+
def rollback_on_error(self) -> None:
|
|
173
|
+
"""Remove created roadmap directory on error."""
|
|
174
|
+
if self.core.roadmap_dir.exists():
|
|
175
|
+
try:
|
|
176
|
+
shutil.rmtree(self.core.roadmap_dir)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.debug(
|
|
179
|
+
"roadmap_dir_cleanup_failed",
|
|
180
|
+
error=str(e),
|
|
181
|
+
action="cleanup_roadmap_dir",
|
|
182
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Initialization service for roadmap CLI.
|
|
2
|
+
|
|
3
|
+
This module handles project initialization workflow including
|
|
4
|
+
project detection, creation, GitHub setup, and validation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from roadmap.common.logging import get_logger
|
|
11
|
+
from roadmap.common.logging.error_logging import (
|
|
12
|
+
log_error_with_context,
|
|
13
|
+
)
|
|
14
|
+
from roadmap.common.observability.instrumentation import traced
|
|
15
|
+
from roadmap.core.services.initialization import (
|
|
16
|
+
InitializationValidator,
|
|
17
|
+
InitializationWorkflow,
|
|
18
|
+
)
|
|
19
|
+
from roadmap.core.services.project_init.detection import ProjectDetectionService
|
|
20
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProjectInitializationService:
|
|
26
|
+
"""Orchestrates project initialization workflow."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, core: RoadmapCore):
|
|
29
|
+
"""Initialize the service.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
core: RoadmapCore instance for this initialization
|
|
33
|
+
"""
|
|
34
|
+
self.core = core
|
|
35
|
+
self.workflow = InitializationWorkflow(core)
|
|
36
|
+
|
|
37
|
+
@traced("validate_prerequisites")
|
|
38
|
+
def validate_prerequisites(self, force: bool = False) -> tuple[bool, str]:
|
|
39
|
+
"""Validate initialization prerequisites.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
force: Whether force re-initialization is requested
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Tuple of (is_valid, error_message)
|
|
46
|
+
"""
|
|
47
|
+
lock_path = Path.cwd() / ".roadmap_init.lock"
|
|
48
|
+
is_valid, error_msg = InitializationValidator.validate_lockfile(lock_path)
|
|
49
|
+
if not is_valid:
|
|
50
|
+
return is_valid, error_msg or ""
|
|
51
|
+
|
|
52
|
+
is_valid, error_msg = InitializationValidator.check_existing_roadmap(
|
|
53
|
+
self.core, force
|
|
54
|
+
)
|
|
55
|
+
return is_valid, error_msg or ""
|
|
56
|
+
|
|
57
|
+
@traced("handle_force_reinitialization")
|
|
58
|
+
def handle_force_reinitialization(self) -> bool:
|
|
59
|
+
"""Handle force re-initialization when roadmap already exists.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if successful
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
return self.workflow.cleanup_existing()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
log_error_with_context(
|
|
68
|
+
e,
|
|
69
|
+
operation="cleanup_existing",
|
|
70
|
+
entity_type="Roadmap",
|
|
71
|
+
)
|
|
72
|
+
logger.error(
|
|
73
|
+
"Failed to cleanup existing roadmap",
|
|
74
|
+
error=str(e),
|
|
75
|
+
severity="operational",
|
|
76
|
+
)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
@traced("detect_existing_projects")
|
|
80
|
+
def detect_existing_projects(self) -> list[dict[str, Any]]:
|
|
81
|
+
"""Detect existing projects in the roadmap.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of project info dictionaries
|
|
85
|
+
"""
|
|
86
|
+
projects_dir = self.core.roadmap_dir / "projects"
|
|
87
|
+
return ProjectDetectionService.detect_existing_projects(projects_dir)
|
|
88
|
+
|
|
89
|
+
@traced("validate_finalization")
|
|
90
|
+
def validate_finalization(self, project_info: dict[str, Any] | None = None) -> bool:
|
|
91
|
+
"""Validate the initialized roadmap and finalize.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
project_info: Project info dict if project was created
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if validation passes
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
return InitializationValidator.post_init_validate(
|
|
101
|
+
self.core, self.core.roadmap_dir.name, project_info
|
|
102
|
+
)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
log_error_with_context(
|
|
105
|
+
e,
|
|
106
|
+
operation="post_init_validate",
|
|
107
|
+
entity_type="Roadmap",
|
|
108
|
+
additional_context={"project_info": project_info},
|
|
109
|
+
)
|
|
110
|
+
logger.error("Validation failed", error=str(e), severity="operational")
|
|
111
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package initialization file."""
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Assignee validation strategies for RoadmapCore."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol
|
|
4
|
+
|
|
5
|
+
from roadmap.common.logging.error_logging import (
|
|
6
|
+
log_error_with_context,
|
|
7
|
+
log_validation_error,
|
|
8
|
+
)
|
|
9
|
+
from roadmap.common.observability.instrumentation import traced
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AssigneeValidationResult:
|
|
13
|
+
"""Result of assignee validation."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, is_valid: bool, message: str = "", canonical_id: str = ""):
|
|
16
|
+
"""Initialize validation result.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
is_valid: Whether the assignee is valid
|
|
20
|
+
message: Error message if invalid, or warning message
|
|
21
|
+
canonical_id: Canonical ID if valid
|
|
22
|
+
"""
|
|
23
|
+
self.is_valid = is_valid
|
|
24
|
+
self.message = message
|
|
25
|
+
self.canonical_id = canonical_id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GitHubBackend(Protocol):
|
|
29
|
+
"""Protocol for GitHub-based validation backends.
|
|
30
|
+
|
|
31
|
+
Abstracts GitHub validation away from core logic.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def validate(self, assignee: str) -> tuple[bool, str, str]:
|
|
35
|
+
"""Validate assignee against GitHub.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
assignee: Username to validate
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple of (is_valid, error_message, canonical_id)
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class IdentitySystemValidator:
|
|
47
|
+
"""Validates assignees using the identity management system."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, root_path: str | Any):
|
|
50
|
+
"""Initialize with roadmap root path.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
root_path: Path to roadmap root directory
|
|
54
|
+
"""
|
|
55
|
+
self.root_path = root_path
|
|
56
|
+
|
|
57
|
+
def validate(self, assignee: str) -> AssigneeValidationResult:
|
|
58
|
+
"""Validate assignee using identity management system.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
assignee: Username to validate
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Validation result with canonical ID if successful
|
|
65
|
+
"""
|
|
66
|
+
# Identity system not available - return special marker
|
|
67
|
+
return AssigneeValidationResult(
|
|
68
|
+
is_valid=False, message="", canonical_id="identity-unavailable"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def get_validation_mode(self) -> str:
|
|
72
|
+
"""Get the configured validation mode.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Validation mode string (e.g., 'hybrid', 'local-only', 'github-only')
|
|
76
|
+
"""
|
|
77
|
+
return "local-only"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LocalValidator:
|
|
81
|
+
"""Validates assignees using basic local rules."""
|
|
82
|
+
|
|
83
|
+
@traced("validate_assignee_local")
|
|
84
|
+
def validate(self, assignee: str) -> AssigneeValidationResult:
|
|
85
|
+
"""Validate assignee using basic local rules.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
assignee: Username to validate
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Validation result
|
|
92
|
+
"""
|
|
93
|
+
# Basic validation for local usage
|
|
94
|
+
if len(assignee) >= 2 and not any(char in assignee for char in "<>{}[]()"):
|
|
95
|
+
return AssigneeValidationResult(is_valid=True, canonical_id=assignee)
|
|
96
|
+
else:
|
|
97
|
+
error = ValueError(f"'{assignee}' is not a valid assignee name")
|
|
98
|
+
log_validation_error(
|
|
99
|
+
error,
|
|
100
|
+
entity_type="Assignee",
|
|
101
|
+
field_name="assignee",
|
|
102
|
+
proposed_value=assignee,
|
|
103
|
+
)
|
|
104
|
+
return AssigneeValidationResult(
|
|
105
|
+
is_valid=False, message=f"'{assignee}' is not a valid assignee name"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class AssigneeValidationStrategy:
|
|
110
|
+
"""Orchestrates assignee validation using multiple strategies."""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
root_path: str | Any,
|
|
115
|
+
github_config: tuple[str, str, str] | None = None,
|
|
116
|
+
cached_members: set[str] | list[str] | None = None,
|
|
117
|
+
):
|
|
118
|
+
"""Initialize validation strategy.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
root_path: Path to roadmap root directory
|
|
122
|
+
github_config: Optional tuple of (token, owner, repo) for GitHub validation
|
|
123
|
+
cached_members: Optional cached team members for performance
|
|
124
|
+
"""
|
|
125
|
+
self.root_path = root_path
|
|
126
|
+
self.github_config = github_config
|
|
127
|
+
self.cached_members = cached_members
|
|
128
|
+
|
|
129
|
+
self.identity_validator = IdentitySystemValidator(root_path)
|
|
130
|
+
self.local_validator = LocalValidator()
|
|
131
|
+
|
|
132
|
+
def _validate_empty(self, assignee: str) -> tuple[bool, str, str] | None:
|
|
133
|
+
"""Check if assignee is empty."""
|
|
134
|
+
if not assignee or not assignee.strip():
|
|
135
|
+
return False, "Assignee cannot be empty", ""
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _try_identity_validation(self, assignee: str) -> tuple[bool, str, str] | None:
|
|
139
|
+
"""Try validating with identity system."""
|
|
140
|
+
identity_result = self.identity_validator.validate(assignee)
|
|
141
|
+
if identity_result.is_valid:
|
|
142
|
+
return True, "", identity_result.canonical_id
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
def _try_identity_unavailable_path(
|
|
146
|
+
self, assignee: str, identity_result
|
|
147
|
+
) -> tuple[bool, str, str] | None:
|
|
148
|
+
"""Handle case where identity system is unavailable."""
|
|
149
|
+
if identity_result.canonical_id != "identity-unavailable":
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
if self.github_config:
|
|
153
|
+
token, owner, repo = self.github_config
|
|
154
|
+
if token and owner and repo:
|
|
155
|
+
github_result = self._validate_with_github(assignee)
|
|
156
|
+
if github_result.is_valid:
|
|
157
|
+
return True, "", github_result.canonical_id
|
|
158
|
+
else:
|
|
159
|
+
return False, github_result.message, ""
|
|
160
|
+
|
|
161
|
+
local_result = self.local_validator.validate(assignee)
|
|
162
|
+
if local_result.is_valid:
|
|
163
|
+
return True, "", local_result.canonical_id
|
|
164
|
+
else:
|
|
165
|
+
return False, local_result.message, ""
|
|
166
|
+
|
|
167
|
+
def _try_identity_failed_path(self, assignee: str, validation_mode: str):
|
|
168
|
+
"""Try fallback validation when identity system failed."""
|
|
169
|
+
if self._should_use_github_validation(validation_mode):
|
|
170
|
+
github_result = self._validate_with_github(assignee)
|
|
171
|
+
if github_result.is_valid:
|
|
172
|
+
return True, "", github_result.canonical_id
|
|
173
|
+
elif validation_mode == "github-only":
|
|
174
|
+
return False, github_result.message, ""
|
|
175
|
+
|
|
176
|
+
if self._should_use_local_validation(validation_mode):
|
|
177
|
+
local_result = self.local_validator.validate(assignee)
|
|
178
|
+
if local_result.is_valid:
|
|
179
|
+
return True, "", local_result.canonical_id
|
|
180
|
+
else:
|
|
181
|
+
return False, local_result.message, ""
|
|
182
|
+
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
@traced("validate_assignee_strategy")
|
|
186
|
+
def validate(self, assignee: str) -> tuple[bool, str, str]:
|
|
187
|
+
"""Validate assignee using appropriate strategy.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
assignee: Username to validate
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Tuple of (is_valid, error_message, canonical_id)
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
# Check for empty assignee
|
|
197
|
+
empty_result = self._validate_empty(assignee)
|
|
198
|
+
if empty_result is not None:
|
|
199
|
+
is_valid, message, _ = empty_result
|
|
200
|
+
if not is_valid:
|
|
201
|
+
log_validation_error(
|
|
202
|
+
ValueError(message),
|
|
203
|
+
entity_type="Assignee",
|
|
204
|
+
field_name="assignee",
|
|
205
|
+
proposed_value=assignee,
|
|
206
|
+
)
|
|
207
|
+
return empty_result
|
|
208
|
+
|
|
209
|
+
assignee = assignee.strip()
|
|
210
|
+
|
|
211
|
+
# Try identity system first
|
|
212
|
+
identity_result = self.identity_validator.validate(assignee)
|
|
213
|
+
identity_valid = self._try_identity_validation(assignee)
|
|
214
|
+
if identity_valid is not None:
|
|
215
|
+
return identity_valid
|
|
216
|
+
|
|
217
|
+
# Handle identity unavailable case
|
|
218
|
+
identity_unavailable_result = self._try_identity_unavailable_path(
|
|
219
|
+
assignee, identity_result
|
|
220
|
+
)
|
|
221
|
+
if identity_unavailable_result is not None:
|
|
222
|
+
return identity_unavailable_result
|
|
223
|
+
|
|
224
|
+
# Identity system is available but validation failed
|
|
225
|
+
validation_mode = identity_result.canonical_id or "local-only"
|
|
226
|
+
|
|
227
|
+
# Try fallback validation paths
|
|
228
|
+
fallback_result = self._try_identity_failed_path(assignee, validation_mode)
|
|
229
|
+
if fallback_result is not None:
|
|
230
|
+
return fallback_result
|
|
231
|
+
|
|
232
|
+
# No fallback available, return identity system result
|
|
233
|
+
if not identity_result.is_valid:
|
|
234
|
+
log_validation_error(
|
|
235
|
+
ValueError(identity_result.message),
|
|
236
|
+
entity_type="Assignee",
|
|
237
|
+
field_name="assignee",
|
|
238
|
+
proposed_value=assignee,
|
|
239
|
+
)
|
|
240
|
+
return False, identity_result.message, ""
|
|
241
|
+
except Exception as e:
|
|
242
|
+
log_error_with_context(
|
|
243
|
+
e,
|
|
244
|
+
operation="validate_assignee",
|
|
245
|
+
entity_type="Assignee",
|
|
246
|
+
additional_context={"assignee": assignee},
|
|
247
|
+
)
|
|
248
|
+
raise
|
|
249
|
+
|
|
250
|
+
def _should_use_github_validation(self, validation_mode: str) -> bool:
|
|
251
|
+
"""Check if GitHub validation should be attempted.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
validation_mode: Current validation mode
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
True if GitHub validation should be used
|
|
258
|
+
"""
|
|
259
|
+
if not self.github_config:
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
token, owner, repo = self.github_config
|
|
263
|
+
if not (token and owner and repo):
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
return validation_mode in ["hybrid", "github-only"]
|
|
267
|
+
|
|
268
|
+
def _should_use_local_validation(self, validation_mode: str) -> bool:
|
|
269
|
+
"""Check if local validation should be attempted.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
validation_mode: Current validation mode
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
True if local validation should be used
|
|
276
|
+
"""
|
|
277
|
+
# Use local validation if GitHub is not configured
|
|
278
|
+
if not self.github_config:
|
|
279
|
+
return validation_mode in ["local-only", "hybrid"]
|
|
280
|
+
|
|
281
|
+
token, owner, repo = self.github_config
|
|
282
|
+
if not (token and owner and repo):
|
|
283
|
+
return validation_mode in ["local-only", "hybrid"]
|
|
284
|
+
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
@traced("validate_assignee_github")
|
|
288
|
+
def _validate_with_github(self, assignee: str) -> AssigneeValidationResult:
|
|
289
|
+
"""Validate using GitHub.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
assignee: Username to validate
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Validation result
|
|
296
|
+
"""
|
|
297
|
+
if not self.github_config:
|
|
298
|
+
return AssigneeValidationResult(
|
|
299
|
+
is_valid=False, message="GitHub not configured"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
token, owner, repo = self.github_config
|
|
303
|
+
# Import here to avoid hard dependency on infrastructure
|
|
304
|
+
from roadmap.infrastructure.validation.github_validator import (
|
|
305
|
+
GitHubAssigneeValidator,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
github_validator = GitHubAssigneeValidator(
|
|
309
|
+
token, owner, repo, self.cached_members
|
|
310
|
+
)
|
|
311
|
+
result = github_validator.validate(assignee)
|
|
312
|
+
return result
|