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,439 @@
|
|
|
1
|
+
"""GitHub integration service for roadmap application.
|
|
2
|
+
|
|
3
|
+
This module handles GitHub-related operations including authentication,
|
|
4
|
+
team member queries, assignee validation, and GitHub configuration management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from roadmap.common.configuration import ConfigManager
|
|
13
|
+
from roadmap.common.errors import (
|
|
14
|
+
ErrorHandler,
|
|
15
|
+
ErrorSeverity,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from roadmap.common.errors.error_standards import OperationType, safe_operation
|
|
19
|
+
from roadmap.common.logging import get_logger
|
|
20
|
+
from roadmap.common.logging.error_logging import (
|
|
21
|
+
log_error_with_context,
|
|
22
|
+
log_external_service_error,
|
|
23
|
+
log_validation_error,
|
|
24
|
+
)
|
|
25
|
+
from roadmap.common.observability.instrumentation import traced
|
|
26
|
+
from roadmap.infrastructure.security.credentials import get_credential_manager
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from roadmap.core.interfaces import GitHubBackendInterface # noqa: F401
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GitHubIntegrationService:
|
|
35
|
+
"""Manages GitHub integration and team member operations."""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
root_path: Path,
|
|
40
|
+
config_file: Path,
|
|
41
|
+
github_backend: "GitHubBackendInterface | None" = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initialize GitHub integration service.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
root_path: Root project path
|
|
47
|
+
config_file: Path to config.yaml file
|
|
48
|
+
github_backend: Optional GitHub backend interface. If not provided,
|
|
49
|
+
will create GitHubBackendAdapter instance when needed.
|
|
50
|
+
"""
|
|
51
|
+
self.root_path = root_path
|
|
52
|
+
self.config_file = config_file
|
|
53
|
+
self._github_backend = github_backend
|
|
54
|
+
self._team_members_cache: list[str] | None = None
|
|
55
|
+
self._cache_timestamp: datetime | None = None
|
|
56
|
+
self._last_canonical_assignee: str | None = None
|
|
57
|
+
|
|
58
|
+
def _get_github_backend(self) -> "GitHubBackendInterface":
|
|
59
|
+
"""Lazy-load GitHub backend interface.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
GitHubBackendInterface implementation
|
|
63
|
+
"""
|
|
64
|
+
if self._github_backend is None:
|
|
65
|
+
raise RuntimeError(
|
|
66
|
+
"GitHub backend not configured. Ensure RoadmapCore is initialized with backend."
|
|
67
|
+
)
|
|
68
|
+
return self._github_backend
|
|
69
|
+
|
|
70
|
+
def _get_credentials(self) -> tuple[str | None, str | None, str | None]:
|
|
71
|
+
"""Get GitHub credentials from config and environment.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Tuple of (token, owner, repo) or (None, None, None) if not configured
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
config_manager = ConfigManager(self.config_file)
|
|
78
|
+
config = config_manager.load()
|
|
79
|
+
github_config = getattr(config, "github", None)
|
|
80
|
+
|
|
81
|
+
# Get owner and repo from config
|
|
82
|
+
if github_config is None:
|
|
83
|
+
owner = None
|
|
84
|
+
repo = None
|
|
85
|
+
elif isinstance(github_config, dict):
|
|
86
|
+
owner = github_config.get("owner")
|
|
87
|
+
repo = github_config.get("repo")
|
|
88
|
+
else:
|
|
89
|
+
# github_config is a GitHubConfig object
|
|
90
|
+
owner = getattr(github_config, "owner", None)
|
|
91
|
+
repo = getattr(github_config, "repo", None)
|
|
92
|
+
|
|
93
|
+
if not isinstance(owner, str) or not owner:
|
|
94
|
+
owner = None
|
|
95
|
+
if not isinstance(repo, str) or not repo:
|
|
96
|
+
repo = None
|
|
97
|
+
|
|
98
|
+
# Get token from credentials manager or environment
|
|
99
|
+
credential_manager = get_credential_manager()
|
|
100
|
+
token = credential_manager.get_token()
|
|
101
|
+
|
|
102
|
+
if not token:
|
|
103
|
+
token = os.getenv("GITHUB_TOKEN")
|
|
104
|
+
|
|
105
|
+
return token, owner, repo
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug("credentials_retrieval_failed", error=str(e))
|
|
108
|
+
return None, None, None
|
|
109
|
+
|
|
110
|
+
@traced("get_github_config")
|
|
111
|
+
@safe_operation(OperationType.READ, "GitHubAPI", retryable=True)
|
|
112
|
+
def get_github_config(self) -> tuple[str | None, str | None, str | None]:
|
|
113
|
+
"""Get GitHub configuration from config file and credentials.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Tuple of (token, owner, repo) or (None, None, None) if not configured
|
|
117
|
+
"""
|
|
118
|
+
logger.info("getting_github_config")
|
|
119
|
+
try:
|
|
120
|
+
token, owner, repo = self._get_credentials()
|
|
121
|
+
|
|
122
|
+
if not owner or not repo:
|
|
123
|
+
log_validation_error(
|
|
124
|
+
ValueError("Missing GitHub owner or repo in config"),
|
|
125
|
+
entity_type="GitHubConfig",
|
|
126
|
+
field_name="owner_or_repo",
|
|
127
|
+
proposed_value={"owner": owner, "repo": repo},
|
|
128
|
+
)
|
|
129
|
+
return None, None, None
|
|
130
|
+
|
|
131
|
+
return token, owner, repo
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
log_error_with_context(
|
|
135
|
+
e,
|
|
136
|
+
operation="get_github_config",
|
|
137
|
+
entity_type="GitHubAPI",
|
|
138
|
+
include_traceback=False,
|
|
139
|
+
)
|
|
140
|
+
logger.debug("github_config_retrieval_failed", error=str(e))
|
|
141
|
+
return None, None, None
|
|
142
|
+
|
|
143
|
+
@safe_operation(OperationType.READ, "GitHubAPI", retryable=True)
|
|
144
|
+
@traced("get_team_members")
|
|
145
|
+
def get_team_members(self) -> list[str]:
|
|
146
|
+
"""Get team members from GitHub repository.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of usernames if GitHub is configured, empty list otherwise
|
|
150
|
+
"""
|
|
151
|
+
logger.info("getting_team_members_from_github")
|
|
152
|
+
try:
|
|
153
|
+
token, owner, repo = self.get_github_config()
|
|
154
|
+
if not token or not owner or not repo:
|
|
155
|
+
logger.debug("github_not_configured")
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
# Get team members via backend interface
|
|
159
|
+
try:
|
|
160
|
+
backend = self._get_github_backend()
|
|
161
|
+
members = (
|
|
162
|
+
backend.list_repositories()
|
|
163
|
+
if hasattr(backend, "list_repositories")
|
|
164
|
+
else []
|
|
165
|
+
)
|
|
166
|
+
logger.debug("team_members_retrieved", count=len(members))
|
|
167
|
+
return members if isinstance(members, list) else []
|
|
168
|
+
except Exception as api_error:
|
|
169
|
+
log_external_service_error(
|
|
170
|
+
api_error,
|
|
171
|
+
service_name="GitHub",
|
|
172
|
+
operation="get_team_members",
|
|
173
|
+
)
|
|
174
|
+
return []
|
|
175
|
+
except Exception as e:
|
|
176
|
+
log_error_with_context(
|
|
177
|
+
e,
|
|
178
|
+
operation="get_team_members",
|
|
179
|
+
entity_type="GitHubAPI",
|
|
180
|
+
)
|
|
181
|
+
logger.debug("team_members_retrieval_failed", error=str(e))
|
|
182
|
+
# Return empty list if GitHub is not configured or accessible
|
|
183
|
+
return []
|
|
184
|
+
|
|
185
|
+
@safe_operation(OperationType.READ, "GitHubUser", retryable=True)
|
|
186
|
+
@traced("get_current_user")
|
|
187
|
+
def get_current_user(self, config_file: Path | None = None) -> str | None:
|
|
188
|
+
"""Get the current user from config.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
config_file: Optional override path to config file
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Current user's name from config if set, None otherwise
|
|
195
|
+
"""
|
|
196
|
+
logger.info("getting_current_user")
|
|
197
|
+
try:
|
|
198
|
+
config_path = config_file or self.config_file
|
|
199
|
+
config_manager = ConfigManager(config_path)
|
|
200
|
+
config = config_manager.load()
|
|
201
|
+
if config and hasattr(config, "user"):
|
|
202
|
+
user = getattr(config, "user", None)
|
|
203
|
+
if user and hasattr(user, "name"):
|
|
204
|
+
name = user.name
|
|
205
|
+
logger.debug("current_user_retrieved", user=name)
|
|
206
|
+
return name
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.debug("current_user_retrieval_failed", error=str(e))
|
|
209
|
+
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
@safe_operation(OperationType.READ, "GitHubAPI", retryable=True)
|
|
213
|
+
def get_cached_team_members(self) -> list[str]:
|
|
214
|
+
"""Get team members with caching (5 minute cache).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
List of cached team member usernames
|
|
218
|
+
"""
|
|
219
|
+
logger.info("getting_cached_team_members")
|
|
220
|
+
# Check if cache is valid (5 minutes)
|
|
221
|
+
if (
|
|
222
|
+
self._team_members_cache is not None
|
|
223
|
+
and self._cache_timestamp is not None
|
|
224
|
+
and datetime.now(UTC) - self._cache_timestamp < timedelta(minutes=5)
|
|
225
|
+
):
|
|
226
|
+
logger.debug("using_cached_team_members")
|
|
227
|
+
return self._team_members_cache
|
|
228
|
+
|
|
229
|
+
# Refresh cache
|
|
230
|
+
team_members = self.get_team_members()
|
|
231
|
+
self._team_members_cache = team_members
|
|
232
|
+
self._cache_timestamp = datetime.now(UTC)
|
|
233
|
+
logger.debug("team_members_cache_refreshed", count=len(team_members))
|
|
234
|
+
|
|
235
|
+
return team_members
|
|
236
|
+
|
|
237
|
+
@safe_operation(OperationType.READ, "Assignee", retryable=True)
|
|
238
|
+
@traced("validate_assignee")
|
|
239
|
+
def validate_assignee(self, assignee: str) -> tuple[bool, str]:
|
|
240
|
+
"""Validate an assignee using the identity management system.
|
|
241
|
+
|
|
242
|
+
This validation integrates with the identity management system while
|
|
243
|
+
maintaining backward compatibility with the original API.
|
|
244
|
+
|
|
245
|
+
Only validates against GitHub if the sync backend is configured to use
|
|
246
|
+
GitHub. If using vanilla Git backend, validation is skipped.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
assignee: Username to validate
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Tuple of (is_valid, error_message)
|
|
253
|
+
- (True, "") if valid (backward compatible)
|
|
254
|
+
- (False, error_message) if invalid
|
|
255
|
+
"""
|
|
256
|
+
logger.info("validating_assignee", assignee=assignee)
|
|
257
|
+
|
|
258
|
+
# Check which sync backend is configured
|
|
259
|
+
try:
|
|
260
|
+
config_manager = ConfigManager(self.config_file)
|
|
261
|
+
config = config_manager.load()
|
|
262
|
+
github_config_obj = getattr(config, "github", None)
|
|
263
|
+
|
|
264
|
+
# Determine sync backend
|
|
265
|
+
if github_config_obj is None:
|
|
266
|
+
sync_backend = "git"
|
|
267
|
+
elif isinstance(github_config_obj, dict):
|
|
268
|
+
sync_backend = github_config_obj.get("sync_backend", "github")
|
|
269
|
+
else:
|
|
270
|
+
sync_backend = getattr(github_config_obj, "sync_backend", "github")
|
|
271
|
+
|
|
272
|
+
# Only validate against GitHub if using GitHub backend
|
|
273
|
+
if sync_backend != "github":
|
|
274
|
+
logger.info(
|
|
275
|
+
"skipping_assignee_validation",
|
|
276
|
+
reason=f"sync_backend is {sync_backend}, not github",
|
|
277
|
+
assignee=assignee,
|
|
278
|
+
)
|
|
279
|
+
return True, ""
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.debug(
|
|
282
|
+
"sync_backend_detection_failed_continuing_validation",
|
|
283
|
+
error=str(e),
|
|
284
|
+
)
|
|
285
|
+
# If we can't determine backend, continue with validation
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
from roadmap.core.services.issue.assignee_validation_service import (
|
|
289
|
+
AssigneeValidationStrategy,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Get GitHub config if available
|
|
293
|
+
github_config = None
|
|
294
|
+
try:
|
|
295
|
+
token, owner, repo = self.get_github_config()
|
|
296
|
+
if token and owner and repo:
|
|
297
|
+
github_config = (token, owner, repo)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.debug("github_config_for_validation_failed", error=str(e))
|
|
300
|
+
|
|
301
|
+
# Get cached team members
|
|
302
|
+
cached_members = None
|
|
303
|
+
try:
|
|
304
|
+
cached_members = self.get_cached_team_members()
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.debug("cached_members_for_validation_failed", error=str(e))
|
|
307
|
+
|
|
308
|
+
# Use validation strategy
|
|
309
|
+
strategy = AssigneeValidationStrategy(
|
|
310
|
+
self.root_path, github_config, cached_members
|
|
311
|
+
)
|
|
312
|
+
is_valid, error_message, canonical_id = strategy.validate(assignee)
|
|
313
|
+
|
|
314
|
+
if is_valid:
|
|
315
|
+
self._last_canonical_assignee = canonical_id
|
|
316
|
+
logger.debug("assignee_validated", assignee=assignee)
|
|
317
|
+
return True, ""
|
|
318
|
+
else:
|
|
319
|
+
logger.warning(
|
|
320
|
+
"assignee_validation_failed",
|
|
321
|
+
assignee=assignee,
|
|
322
|
+
severity="data_error",
|
|
323
|
+
)
|
|
324
|
+
return False, error_message
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
# Fall back to legacy validation if strategy fails
|
|
328
|
+
logger.debug("strategy_validation_failed_using_legacy", error=str(e))
|
|
329
|
+
return self._legacy_validate_assignee(assignee)
|
|
330
|
+
|
|
331
|
+
def _legacy_validate_assignee(self, assignee: str) -> tuple[bool, str]:
|
|
332
|
+
"""Legacy validation fallback for when validation strategy fails.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
assignee: Username to validate
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Tuple of (is_valid, error_message)
|
|
339
|
+
"""
|
|
340
|
+
if not assignee or not assignee.strip():
|
|
341
|
+
return False, "Assignee cannot be empty"
|
|
342
|
+
|
|
343
|
+
assignee = assignee.strip()
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
token, owner, repo = self.get_github_config()
|
|
347
|
+
if not token or not owner or not repo:
|
|
348
|
+
# If GitHub is not configured, allow any assignee without validation
|
|
349
|
+
# This supports local-only roadmap usage without GitHub integration
|
|
350
|
+
self._last_canonical_assignee = assignee
|
|
351
|
+
logger.debug(
|
|
352
|
+
"github_not_configured_allowing_assignee", assignee=assignee
|
|
353
|
+
)
|
|
354
|
+
return True, ""
|
|
355
|
+
|
|
356
|
+
# GitHub is configured - perform validation against repository access
|
|
357
|
+
|
|
358
|
+
# First check against cached team members for performance
|
|
359
|
+
team_members = self.get_cached_team_members()
|
|
360
|
+
if team_members and assignee in team_members:
|
|
361
|
+
self._last_canonical_assignee = assignee
|
|
362
|
+
logger.debug("assignee_in_cached_members", assignee=assignee)
|
|
363
|
+
return True, ""
|
|
364
|
+
|
|
365
|
+
# If not in cache or cache is empty, do full validation via API
|
|
366
|
+
from roadmap.infrastructure.github_gateway import GitHubGateway
|
|
367
|
+
|
|
368
|
+
client = GitHubGateway.get_github_client(
|
|
369
|
+
{"token": token, "owner": owner, "repo": repo}
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# This will do the full GitHub API validation
|
|
373
|
+
github_valid, github_error = client.validate_assignee(assignee) # type: ignore[attr-defined]
|
|
374
|
+
if github_valid:
|
|
375
|
+
self._last_canonical_assignee = assignee
|
|
376
|
+
logger.debug("assignee_validated_via_github", assignee=assignee)
|
|
377
|
+
return True, ""
|
|
378
|
+
else:
|
|
379
|
+
logger.warning(
|
|
380
|
+
"assignee_not_in_github", assignee=assignee, severity="data_error"
|
|
381
|
+
)
|
|
382
|
+
return False, github_error
|
|
383
|
+
|
|
384
|
+
except Exception as fallback_error:
|
|
385
|
+
# If validation fails due to network/API issues, allow the assignment
|
|
386
|
+
# but log a warning that validation couldn't be performed
|
|
387
|
+
error_handler = ErrorHandler()
|
|
388
|
+
error_handler.handle_error(
|
|
389
|
+
ValidationError(
|
|
390
|
+
f"Could not validate assignee '{assignee}' - validation unavailable",
|
|
391
|
+
field="assignee",
|
|
392
|
+
value=assignee,
|
|
393
|
+
severity=ErrorSeverity.WARNING,
|
|
394
|
+
cause=fallback_error,
|
|
395
|
+
),
|
|
396
|
+
show_traceback=False,
|
|
397
|
+
exit_on_critical=False,
|
|
398
|
+
)
|
|
399
|
+
warning_msg = (
|
|
400
|
+
f"Warning: Could not validate assignee (validation unavailable): "
|
|
401
|
+
f"{str(fallback_error)}"
|
|
402
|
+
)
|
|
403
|
+
self._last_canonical_assignee = assignee
|
|
404
|
+
logger.warning(
|
|
405
|
+
"assignee_validation_unavailable",
|
|
406
|
+
error=str(fallback_error),
|
|
407
|
+
severity="infrastructure",
|
|
408
|
+
)
|
|
409
|
+
return True, warning_msg
|
|
410
|
+
|
|
411
|
+
@traced("get_canonical_assignee")
|
|
412
|
+
def get_canonical_assignee(self, assignee: str) -> str:
|
|
413
|
+
"""Get the canonical form of an assignee name.
|
|
414
|
+
|
|
415
|
+
This method should be called after validate_assignee to get the canonical form.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
assignee: Input assignee name
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Canonical assignee name (may be same as input if no mapping exists)
|
|
422
|
+
"""
|
|
423
|
+
# Fallback to original assignee
|
|
424
|
+
logger.debug("using_original_assignee", assignee=assignee)
|
|
425
|
+
return assignee
|
|
426
|
+
|
|
427
|
+
def get_last_canonical_assignee(self) -> str | None:
|
|
428
|
+
"""Get the last validated canonical assignee.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
Last canonical assignee or None
|
|
432
|
+
"""
|
|
433
|
+
return self._last_canonical_assignee
|
|
434
|
+
|
|
435
|
+
def clear_cache(self) -> None:
|
|
436
|
+
"""Clear the team members cache."""
|
|
437
|
+
self._team_members_cache = None
|
|
438
|
+
self._cache_timestamp = None
|
|
439
|
+
logger.debug("team_members_cache_cleared")
|