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,489 @@
|
|
|
1
|
+
"""Secure credential management for GitHub tokens."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from roadmap.common.errors import OperationType, safe_operation
|
|
8
|
+
from roadmap.common.logging import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CredentialManagerError(Exception):
|
|
14
|
+
"""Exception raised for credential manager errors."""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CredentialManager:
|
|
20
|
+
"""Cross-platform secure credential manager."""
|
|
21
|
+
|
|
22
|
+
SERVICE_NAME = "roadmap-cli"
|
|
23
|
+
ACCOUNT_NAME = "github-token"
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize credential manager."""
|
|
27
|
+
self.system = platform.system().lower()
|
|
28
|
+
|
|
29
|
+
@safe_operation(OperationType.SAVE, "Token", include_traceback=True)
|
|
30
|
+
def store_token(self, token: str, repo_info: dict[str, str] | None = None) -> bool:
|
|
31
|
+
"""Store GitHub token securely.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
token: GitHub personal access token
|
|
35
|
+
repo_info: Optional repository information (owner, repo)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if stored successfully, False otherwise
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
CredentialManagerError: If token storage fails
|
|
42
|
+
"""
|
|
43
|
+
logger.info("storing_github_token")
|
|
44
|
+
try:
|
|
45
|
+
if self.system == "darwin":
|
|
46
|
+
result = self._store_token_keychain(token, repo_info)
|
|
47
|
+
elif self.system == "windows":
|
|
48
|
+
result = self._store_token_wincred(token, repo_info)
|
|
49
|
+
elif self.system == "linux":
|
|
50
|
+
result = self._store_token_secretservice(token, repo_info)
|
|
51
|
+
else:
|
|
52
|
+
# Fallback for unsupported systems
|
|
53
|
+
result = self._store_token_fallback(token, repo_info)
|
|
54
|
+
|
|
55
|
+
logger.info("github_token_stored", system=self.system)
|
|
56
|
+
return result
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(
|
|
59
|
+
"github_token_store_failed",
|
|
60
|
+
error=str(e),
|
|
61
|
+
system=self.system,
|
|
62
|
+
severity="config_error",
|
|
63
|
+
)
|
|
64
|
+
raise CredentialManagerError(f"Failed to store token: {e}") from e
|
|
65
|
+
|
|
66
|
+
def get_token(self) -> str | None:
|
|
67
|
+
"""Retrieve GitHub token securely.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
GitHub token if found, None otherwise
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
# Always check environment variable first
|
|
74
|
+
env_token = os.getenv("GITHUB_TOKEN")
|
|
75
|
+
if env_token:
|
|
76
|
+
logger.debug("github_token_retrieved_from_env")
|
|
77
|
+
return env_token
|
|
78
|
+
|
|
79
|
+
if self.system == "darwin":
|
|
80
|
+
token = self._get_token_keychain()
|
|
81
|
+
elif self.system == "windows":
|
|
82
|
+
token = self._get_token_wincred()
|
|
83
|
+
elif self.system == "linux":
|
|
84
|
+
token = self._get_token_secretservice()
|
|
85
|
+
else:
|
|
86
|
+
# Fallback for unsupported systems
|
|
87
|
+
token = self._get_token_fallback()
|
|
88
|
+
|
|
89
|
+
if token:
|
|
90
|
+
logger.debug("github_token_retrieved", system=self.system)
|
|
91
|
+
else:
|
|
92
|
+
logger.debug("github_token_not_found", system=self.system)
|
|
93
|
+
|
|
94
|
+
return token
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Silently fail and return None - credential retrieval should be non-blocking
|
|
97
|
+
logger.warning(
|
|
98
|
+
"github_token_retrieval_failed",
|
|
99
|
+
error=str(e),
|
|
100
|
+
system=self.system,
|
|
101
|
+
severity="operational",
|
|
102
|
+
)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
@safe_operation(OperationType.DELETE, "Token")
|
|
106
|
+
def delete_token(self) -> bool:
|
|
107
|
+
"""Delete stored GitHub token.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if deleted successfully, False otherwise
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
CredentialManagerError: If token deletion fails
|
|
114
|
+
"""
|
|
115
|
+
logger.info("deleting_github_token")
|
|
116
|
+
try:
|
|
117
|
+
if self.system == "darwin":
|
|
118
|
+
result = self._delete_token_keychain()
|
|
119
|
+
elif self.system == "windows":
|
|
120
|
+
result = self._delete_token_wincred()
|
|
121
|
+
elif self.system == "linux":
|
|
122
|
+
result = self._delete_token_secretservice()
|
|
123
|
+
else:
|
|
124
|
+
# Fallback for unsupported systems
|
|
125
|
+
result = self._delete_token_fallback()
|
|
126
|
+
|
|
127
|
+
logger.info("github_token_deleted", system=self.system, success=result)
|
|
128
|
+
return result
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(
|
|
131
|
+
"github_token_deletion_failed",
|
|
132
|
+
error=str(e),
|
|
133
|
+
system=self.system,
|
|
134
|
+
severity="config_error",
|
|
135
|
+
)
|
|
136
|
+
raise CredentialManagerError(f"Failed to delete token: {e}") from e
|
|
137
|
+
|
|
138
|
+
def is_available(self) -> bool:
|
|
139
|
+
"""Check if credential manager is available on this system.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
True if credential manager is available, False otherwise
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
if self.system == "darwin":
|
|
146
|
+
return self._check_keychain_available()
|
|
147
|
+
elif self.system == "windows":
|
|
148
|
+
return self._check_wincred_available()
|
|
149
|
+
elif self.system == "linux":
|
|
150
|
+
return self._check_secretservice_available()
|
|
151
|
+
else:
|
|
152
|
+
return False
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.debug("secretservice_availability_check_failed", error=str(e))
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
# macOS Keychain implementation
|
|
158
|
+
def _store_token_keychain(
|
|
159
|
+
self, token: str, repo_info: dict[str, str] | None = None
|
|
160
|
+
) -> bool:
|
|
161
|
+
"""Store token in macOS Keychain."""
|
|
162
|
+
cmd = [
|
|
163
|
+
"security",
|
|
164
|
+
"add-generic-password",
|
|
165
|
+
"-s",
|
|
166
|
+
self.SERVICE_NAME,
|
|
167
|
+
"-a",
|
|
168
|
+
self.ACCOUNT_NAME,
|
|
169
|
+
"-w",
|
|
170
|
+
token,
|
|
171
|
+
"-U", # Update if exists
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
if repo_info:
|
|
175
|
+
# Add repository info as a comment
|
|
176
|
+
comment = f"GitHub token for {repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
|
|
177
|
+
cmd.extend(["-j", comment])
|
|
178
|
+
|
|
179
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
180
|
+
return result.returncode == 0
|
|
181
|
+
|
|
182
|
+
def _get_token_keychain(self) -> str | None:
|
|
183
|
+
"""Get token from macOS Keychain."""
|
|
184
|
+
cmd = [
|
|
185
|
+
"security",
|
|
186
|
+
"find-generic-password",
|
|
187
|
+
"-s",
|
|
188
|
+
self.SERVICE_NAME,
|
|
189
|
+
"-a",
|
|
190
|
+
self.ACCOUNT_NAME,
|
|
191
|
+
"-w", # Output password only
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
195
|
+
if result.returncode == 0:
|
|
196
|
+
return result.stdout.strip()
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
def _delete_token_keychain(self) -> bool:
|
|
200
|
+
"""Delete token from macOS Keychain."""
|
|
201
|
+
cmd = [
|
|
202
|
+
"security",
|
|
203
|
+
"delete-generic-password",
|
|
204
|
+
"-s",
|
|
205
|
+
self.SERVICE_NAME,
|
|
206
|
+
"-a",
|
|
207
|
+
self.ACCOUNT_NAME,
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
211
|
+
return result.returncode == 0
|
|
212
|
+
|
|
213
|
+
def _check_keychain_available(self) -> bool:
|
|
214
|
+
"""Check if macOS Keychain is available."""
|
|
215
|
+
try:
|
|
216
|
+
result = subprocess.run(["security", "-h"], capture_output=True, text=True)
|
|
217
|
+
return result.returncode == 0
|
|
218
|
+
except FileNotFoundError as e:
|
|
219
|
+
logger.debug("security_command_not_found", error=str(e))
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# Windows Credential Manager implementation
|
|
223
|
+
def _store_token_wincred(
|
|
224
|
+
self, token: str, repo_info: dict[str, str] | None = None
|
|
225
|
+
) -> bool:
|
|
226
|
+
"""Store token in Windows Credential Manager."""
|
|
227
|
+
try:
|
|
228
|
+
import keyring
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
from keyring.errors import NoKeyringError
|
|
232
|
+
except Exception:
|
|
233
|
+
NoKeyringError = None
|
|
234
|
+
|
|
235
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
236
|
+
if repo_info:
|
|
237
|
+
target_name += (
|
|
238
|
+
f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
keyring.set_password(self.SERVICE_NAME, target_name, token)
|
|
243
|
+
return True
|
|
244
|
+
except Exception as e:
|
|
245
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
246
|
+
logger.debug("keyring_no_backend_store_wincred", error=str(e))
|
|
247
|
+
return self._store_token_cmdkey(token, repo_info)
|
|
248
|
+
logger.debug("keyring_store_wincred_failed", error=str(e))
|
|
249
|
+
return False
|
|
250
|
+
except ImportError as e:
|
|
251
|
+
logger.debug("keyring_import_failed_keychain", error=str(e))
|
|
252
|
+
# Fallback to cmdkey if keyring not available
|
|
253
|
+
return self._store_token_cmdkey(token, repo_info)
|
|
254
|
+
|
|
255
|
+
def _get_token_wincred(self) -> str | None:
|
|
256
|
+
"""Get token from Windows Credential Manager."""
|
|
257
|
+
try:
|
|
258
|
+
import keyring
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
from keyring.errors import NoKeyringError
|
|
262
|
+
except Exception:
|
|
263
|
+
NoKeyringError = None
|
|
264
|
+
|
|
265
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
266
|
+
try:
|
|
267
|
+
return keyring.get_password(self.SERVICE_NAME, target_name)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
270
|
+
logger.debug("keyring_no_backend_get_wincred", error=str(e))
|
|
271
|
+
return self._get_token_cmdkey()
|
|
272
|
+
logger.debug("keyring_get_wincred_failed", error=str(e))
|
|
273
|
+
return None
|
|
274
|
+
except ImportError as e:
|
|
275
|
+
logger.debug("keyring_import_failed_get_wincred", error=str(e))
|
|
276
|
+
# Fallback to cmdkey if keyring not available
|
|
277
|
+
return self._get_token_cmdkey()
|
|
278
|
+
|
|
279
|
+
def _delete_token_wincred(self) -> bool:
|
|
280
|
+
"""Delete token from Windows Credential Manager."""
|
|
281
|
+
try:
|
|
282
|
+
import keyring
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
from keyring.errors import NoKeyringError
|
|
286
|
+
except Exception:
|
|
287
|
+
NoKeyringError = None
|
|
288
|
+
|
|
289
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
290
|
+
try:
|
|
291
|
+
keyring.delete_password(self.SERVICE_NAME, target_name)
|
|
292
|
+
return True
|
|
293
|
+
except Exception as e:
|
|
294
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
295
|
+
logger.debug("keyring_no_backend_delete_wincred", error=str(e))
|
|
296
|
+
return self._delete_token_cmdkey()
|
|
297
|
+
logger.debug("keyring_delete_wincred_failed", error=str(e))
|
|
298
|
+
return False
|
|
299
|
+
except ImportError as e:
|
|
300
|
+
logger.debug("keyring_import_failed_delete_wincred", error=str(e))
|
|
301
|
+
# Fallback to cmdkey if keyring not available
|
|
302
|
+
return self._delete_token_cmdkey()
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(
|
|
305
|
+
"wincred_token_deletion_failed", error=str(e), severity="config_error"
|
|
306
|
+
)
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
def _check_wincred_available(self) -> bool:
|
|
310
|
+
"""Check if Windows Credential Manager is available."""
|
|
311
|
+
try:
|
|
312
|
+
return True
|
|
313
|
+
except ImportError as e:
|
|
314
|
+
logger.debug("keyring_import_failed_check_wincred", error=str(e))
|
|
315
|
+
try:
|
|
316
|
+
result = subprocess.run(
|
|
317
|
+
["cmdkey", "/?"], capture_output=True, text=True
|
|
318
|
+
)
|
|
319
|
+
return result.returncode == 0
|
|
320
|
+
except FileNotFoundError as e:
|
|
321
|
+
logger.debug("cmdkey_not_found", error=str(e))
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def _store_token_cmdkey(
|
|
325
|
+
self, token: str, repo_info: dict[str, str] | None = None
|
|
326
|
+
) -> bool:
|
|
327
|
+
"""Store token using Windows cmdkey."""
|
|
328
|
+
target_name = self.SERVICE_NAME
|
|
329
|
+
if repo_info:
|
|
330
|
+
target_name += f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
|
|
331
|
+
|
|
332
|
+
cmd = [
|
|
333
|
+
"cmdkey",
|
|
334
|
+
f"/generic:{target_name}",
|
|
335
|
+
f"/user:{self.ACCOUNT_NAME}",
|
|
336
|
+
f"/pass:{token}",
|
|
337
|
+
]
|
|
338
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
339
|
+
return result.returncode == 0
|
|
340
|
+
|
|
341
|
+
def _get_token_cmdkey(self) -> str | None:
|
|
342
|
+
"""Get token using Windows cmdkey (limited functionality)."""
|
|
343
|
+
# cmdkey doesn't provide a way to retrieve passwords, only store/delete
|
|
344
|
+
# This is a limitation of the cmdkey approach
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def _delete_token_cmdkey(self) -> bool:
|
|
348
|
+
"""Delete token using Windows cmdkey."""
|
|
349
|
+
cmd = ["cmdkey", f"/delete:{self.SERVICE_NAME}"]
|
|
350
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
351
|
+
return result.returncode == 0
|
|
352
|
+
|
|
353
|
+
# Linux Secret Service implementation
|
|
354
|
+
def _store_token_secretservice(
|
|
355
|
+
self, token: str, repo_info: dict[str, str] | None = None
|
|
356
|
+
) -> bool:
|
|
357
|
+
"""Store token in Linux Secret Service."""
|
|
358
|
+
try:
|
|
359
|
+
import keyring
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
from keyring.errors import NoKeyringError
|
|
363
|
+
except Exception:
|
|
364
|
+
NoKeyringError = None
|
|
365
|
+
|
|
366
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
367
|
+
if repo_info:
|
|
368
|
+
target_name += (
|
|
369
|
+
f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
keyring.set_password(self.SERVICE_NAME, target_name, token)
|
|
374
|
+
return True
|
|
375
|
+
except Exception as e:
|
|
376
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
377
|
+
logger.debug("keyring_no_backend_store_secretservice", error=str(e))
|
|
378
|
+
return self._store_token_fallback(token, repo_info)
|
|
379
|
+
logger.debug("keyring_store_secretservice_failed", error=str(e))
|
|
380
|
+
return False
|
|
381
|
+
except ImportError as e:
|
|
382
|
+
logger.debug("keyring_import_failed_store_secretservice", error=str(e))
|
|
383
|
+
# Fallback if keyring not available
|
|
384
|
+
return self._store_token_fallback(token, repo_info)
|
|
385
|
+
|
|
386
|
+
def _get_token_secretservice(self) -> str | None:
|
|
387
|
+
"""Get token from Linux Secret Service."""
|
|
388
|
+
try:
|
|
389
|
+
import keyring
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
from keyring.errors import NoKeyringError
|
|
393
|
+
except Exception:
|
|
394
|
+
NoKeyringError = None
|
|
395
|
+
|
|
396
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
397
|
+
try:
|
|
398
|
+
return keyring.get_password(self.SERVICE_NAME, target_name)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
401
|
+
logger.debug("keyring_no_backend_get_secretservice", error=str(e))
|
|
402
|
+
return self._get_token_fallback()
|
|
403
|
+
logger.debug("keyring_get_secretservice_failed", error=str(e))
|
|
404
|
+
return None
|
|
405
|
+
except ImportError as e:
|
|
406
|
+
logger.debug("keyring_import_failed_get_secretservice", error=str(e))
|
|
407
|
+
# Fallback if keyring not available
|
|
408
|
+
return self._get_token_fallback()
|
|
409
|
+
|
|
410
|
+
def _delete_token_secretservice(self) -> bool:
|
|
411
|
+
"""Delete token from Linux Secret Service."""
|
|
412
|
+
try:
|
|
413
|
+
import keyring
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
from keyring.errors import NoKeyringError
|
|
417
|
+
except Exception:
|
|
418
|
+
NoKeyringError = None
|
|
419
|
+
|
|
420
|
+
target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
|
|
421
|
+
try:
|
|
422
|
+
keyring.delete_password(self.SERVICE_NAME, target_name)
|
|
423
|
+
return True
|
|
424
|
+
except Exception as e:
|
|
425
|
+
if NoKeyringError and isinstance(e, NoKeyringError):
|
|
426
|
+
logger.debug(
|
|
427
|
+
"keyring_no_backend_delete_secretservice", error=str(e)
|
|
428
|
+
)
|
|
429
|
+
return self._delete_token_fallback()
|
|
430
|
+
logger.debug("keyring_delete_secretservice_failed", error=str(e))
|
|
431
|
+
return False
|
|
432
|
+
except ImportError as e:
|
|
433
|
+
logger.debug("keyring_import_failed_delete_token", error=str(e))
|
|
434
|
+
# Fallback if keyring not available
|
|
435
|
+
return self._delete_token_fallback()
|
|
436
|
+
except Exception as e:
|
|
437
|
+
logger.debug("delete_token_secretservice_failed", error=str(e))
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
def _check_secretservice_available(self) -> bool:
|
|
441
|
+
"""Check if Linux Secret Service is available."""
|
|
442
|
+
try:
|
|
443
|
+
import keyring
|
|
444
|
+
|
|
445
|
+
# Try to access the keyring to see if it's functional
|
|
446
|
+
keyring.get_keyring()
|
|
447
|
+
return True
|
|
448
|
+
except ImportError as e:
|
|
449
|
+
logger.debug("keyring_import_failed_check_available", error=str(e))
|
|
450
|
+
return False
|
|
451
|
+
except Exception as e:
|
|
452
|
+
logger.debug("check_secretservice_available_failed", error=str(e))
|
|
453
|
+
return False
|
|
454
|
+
|
|
455
|
+
# Fallback implementation for unsupported systems
|
|
456
|
+
def _store_token_fallback(
|
|
457
|
+
self, token: str, repo_info: dict[str, str] | None = None
|
|
458
|
+
) -> bool:
|
|
459
|
+
"""Fallback token storage (warns user to use environment variable)."""
|
|
460
|
+
# Don't actually store - just return False to indicate secure storage unavailable
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
def _get_token_fallback(self) -> str | None:
|
|
464
|
+
"""Fallback token retrieval (environment variable only)."""
|
|
465
|
+
return os.getenv("GITHUB_TOKEN")
|
|
466
|
+
|
|
467
|
+
def _delete_token_fallback(self) -> bool:
|
|
468
|
+
"""Fallback token deletion (no-op)."""
|
|
469
|
+
return True
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def get_credential_manager() -> CredentialManager:
|
|
473
|
+
"""Get a credential manager instance."""
|
|
474
|
+
return CredentialManager()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def mask_token(token: str) -> str:
|
|
478
|
+
"""Mask a token for display purposes.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
token: Token to mask
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Masked token showing only last 4 characters
|
|
485
|
+
"""
|
|
486
|
+
if not token or len(token) < 8:
|
|
487
|
+
return "****"
|
|
488
|
+
|
|
489
|
+
return f"****{token[-4:]}"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Gateway to sync adapter layer for core services.
|
|
2
|
+
|
|
3
|
+
This module mediates all core service access to sync adapter implementations,
|
|
4
|
+
ensuring proper layer separation between Core and Adapters layers.
|
|
5
|
+
|
|
6
|
+
All imports from roadmap.adapters.sync are localized to this module.
|
|
7
|
+
Core services import from this gateway instead of directly from adapters.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SyncGateway:
|
|
14
|
+
"""Gateway for sync adapter operations.
|
|
15
|
+
|
|
16
|
+
Provides a centralized interface for core services to access sync
|
|
17
|
+
backend and orchestration functionality without direct adapter imports.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_sync_backend(
|
|
22
|
+
backend_type: Literal["github", "git"], core: Any, config: Any
|
|
23
|
+
) -> Any:
|
|
24
|
+
"""Get a sync backend for the specified type.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
backend_type: Type of backend (e.g., "github", "git")
|
|
28
|
+
core: Core context object
|
|
29
|
+
config: Configuration for the backend
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Backend instance or None if not available
|
|
33
|
+
"""
|
|
34
|
+
from roadmap.adapters.sync.backend_factory import get_sync_backend
|
|
35
|
+
|
|
36
|
+
return get_sync_backend(backend_type, core, config)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def create_sync_cache_orchestrator(
|
|
40
|
+
core: Any,
|
|
41
|
+
backend: Any,
|
|
42
|
+
conflict_resolver: Any = None,
|
|
43
|
+
state_comparator: Any = None,
|
|
44
|
+
show_progress: bool = True,
|
|
45
|
+
**kwargs: Any,
|
|
46
|
+
) -> Any:
|
|
47
|
+
"""Create a sync cache orchestrator for three-way merge sync operations.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
core: Core context object
|
|
51
|
+
backend: Sync backend instance
|
|
52
|
+
conflict_resolver: Resolver for sync conflicts (optional)
|
|
53
|
+
state_comparator: Comparator for sync states (optional)
|
|
54
|
+
show_progress: Whether to show progress during sync (default: True)
|
|
55
|
+
**kwargs: Additional keyword arguments passed to orchestrator
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
SyncCacheOrchestrator instance
|
|
59
|
+
"""
|
|
60
|
+
from roadmap.adapters.sync.sync_cache_orchestrator import (
|
|
61
|
+
SyncCacheOrchestrator,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return SyncCacheOrchestrator(
|
|
65
|
+
core,
|
|
66
|
+
backend,
|
|
67
|
+
conflict_resolver=conflict_resolver,
|
|
68
|
+
state_comparator=state_comparator,
|
|
69
|
+
show_progress=show_progress,
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def link_issue_in_database(
|
|
75
|
+
repo: Any, local_id: str, backend: Any, remote_id: str
|
|
76
|
+
) -> bool:
|
|
77
|
+
"""Link an issue in the database for sync tracking.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
repo: Database repository
|
|
81
|
+
local_id: Local issue identifier
|
|
82
|
+
backend: Sync backend
|
|
83
|
+
remote_id: Remote issue identifier
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if linking was successful
|
|
87
|
+
"""
|
|
88
|
+
from roadmap.adapters.sync.services.sync_linking_service import (
|
|
89
|
+
SyncLinkingService,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return SyncLinkingService.link_issue_in_database(
|
|
93
|
+
repo, local_id, backend, remote_id
|
|
94
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Validation layer - Data and configuration validation.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- GitHub validators: GitHub API client and assignee validation
|
|
5
|
+
- Validators: Assignee, assignee pool, and milestone consistency validation
|
|
6
|
+
- File enumeration: Directory and file structure validation
|
|
7
|
+
"""
|