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,162 @@
|
|
|
1
|
+
"""Detects changes for GitHub linked entities."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.core.domain import Issue
|
|
7
|
+
from roadmap.core.services.github.github_issue_client import GitHubIssueClient
|
|
8
|
+
from roadmap.core.services.sync.sync_report import IssueChange
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitHubChangeDetector:
|
|
12
|
+
"""Detects what has changed for GitHub-linked entities."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, github_client: GitHubIssueClient):
|
|
15
|
+
"""Initialize detector with GitHub client.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
github_client: Client for fetching GitHub issues
|
|
19
|
+
"""
|
|
20
|
+
self.github_client = github_client
|
|
21
|
+
|
|
22
|
+
def detect_issue_changes(
|
|
23
|
+
self,
|
|
24
|
+
local_issue: Issue,
|
|
25
|
+
owner: str,
|
|
26
|
+
repo: str,
|
|
27
|
+
last_sync_time: datetime | None = None,
|
|
28
|
+
) -> IssueChange:
|
|
29
|
+
"""Detect changes for a single linked issue.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
local_issue: Local issue to check
|
|
33
|
+
owner: GitHub repository owner
|
|
34
|
+
repo: GitHub repository name
|
|
35
|
+
last_sync_time: When the issue was last synced
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
IssueChange with detected changes
|
|
39
|
+
"""
|
|
40
|
+
change = IssueChange(
|
|
41
|
+
issue_id=local_issue.id,
|
|
42
|
+
title=local_issue.title,
|
|
43
|
+
last_sync_time=last_sync_time,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
if not owner or not repo:
|
|
48
|
+
change.github_changes = {
|
|
49
|
+
"error": "GitHub config incomplete (owner/repo required)"
|
|
50
|
+
}
|
|
51
|
+
return change
|
|
52
|
+
|
|
53
|
+
if not local_issue.github_issue:
|
|
54
|
+
change.github_changes = {"error": "Issue not linked to GitHub"}
|
|
55
|
+
return change
|
|
56
|
+
|
|
57
|
+
# Fetch GitHub issue
|
|
58
|
+
github_issue_number = self._parse_github_issue_number(
|
|
59
|
+
local_issue.github_issue
|
|
60
|
+
)
|
|
61
|
+
github_issue = self.github_client.fetch_issue(
|
|
62
|
+
owner, repo, github_issue_number
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not github_issue:
|
|
66
|
+
change.github_changes = {"issue": "deleted on GitHub"}
|
|
67
|
+
return change
|
|
68
|
+
|
|
69
|
+
# Detect local and GitHub changes
|
|
70
|
+
change.local_changes = self._detect_local_changes(local_issue)
|
|
71
|
+
change.github_changes = self._detect_github_changes(
|
|
72
|
+
local_issue, github_issue
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Determine conflict status
|
|
76
|
+
if change.local_changes and change.github_changes:
|
|
77
|
+
change.has_conflict = True
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
change.github_changes = {"error": str(e)}
|
|
81
|
+
|
|
82
|
+
return change
|
|
83
|
+
|
|
84
|
+
def detect_milestone_changes(self, _local_milestone: Any) -> IssueChange | None:
|
|
85
|
+
"""Detect changes for a milestone.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
_local_milestone: Local milestone to check
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
IssueChange with detected changes, or None if no changes
|
|
92
|
+
"""
|
|
93
|
+
# Placeholder: Milestone detection logic to be extracted
|
|
94
|
+
# from _detect_milestone_changes
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _parse_github_issue_number(github_issue: Any) -> int:
|
|
99
|
+
"""Parse GitHub issue number from string or int.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
github_issue: GitHub issue number (string or int)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
GitHub issue number as integer
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
ValueError: If cannot parse issue number
|
|
109
|
+
"""
|
|
110
|
+
if isinstance(github_issue, str):
|
|
111
|
+
return int(github_issue)
|
|
112
|
+
return github_issue
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _detect_local_changes(_issue: Issue) -> dict[str, Any]:
|
|
116
|
+
"""Detect changes made locally to the issue.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
_issue: Issue to check
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dict of detected local changes
|
|
123
|
+
"""
|
|
124
|
+
changes = {}
|
|
125
|
+
|
|
126
|
+
# Check for local modifications
|
|
127
|
+
# (Placeholder - extract from _detect_local_changes)
|
|
128
|
+
# Note: Issue model doesn't track dirty state, so no local changes detected here
|
|
129
|
+
# This is a placeholder for future enhancement when tracking changes
|
|
130
|
+
|
|
131
|
+
return changes
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _detect_github_changes(
|
|
135
|
+
local_issue: Issue, github_issue: dict[str, Any]
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Detect changes made on GitHub.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
local_issue: Local issue version
|
|
141
|
+
github_issue: GitHub issue version
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict of detected GitHub changes
|
|
145
|
+
"""
|
|
146
|
+
changes = {}
|
|
147
|
+
|
|
148
|
+
# Compare title
|
|
149
|
+
if github_issue.get("title") != local_issue.title:
|
|
150
|
+
changes["title"] = github_issue.get("title")
|
|
151
|
+
|
|
152
|
+
# Compare content
|
|
153
|
+
gh_body = github_issue.get("body", "")
|
|
154
|
+
if gh_body != (local_issue.content or ""):
|
|
155
|
+
changes["content"] = gh_body
|
|
156
|
+
|
|
157
|
+
# Compare status
|
|
158
|
+
gh_status = "closed" if github_issue.get("state") == "closed" else "open"
|
|
159
|
+
if gh_status != local_issue.status:
|
|
160
|
+
changes["status"] = gh_status
|
|
161
|
+
|
|
162
|
+
return changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""GitHub configuration validation service."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from roadmap.core.services.github.github_integration_service import (
|
|
8
|
+
GitHubIntegrationService,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitHubConfigValidator:
|
|
13
|
+
"""Validates GitHub configuration before operations."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config_path: Path):
|
|
16
|
+
"""Initialize validator with config path.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
config_path: Path to .roadmap directory
|
|
20
|
+
"""
|
|
21
|
+
self.config_path = config_path
|
|
22
|
+
self.config_file = config_path / "config.yaml"
|
|
23
|
+
self.service = GitHubIntegrationService(config_path, self.config_file)
|
|
24
|
+
|
|
25
|
+
def validate_config(self) -> tuple[bool, str | None]:
|
|
26
|
+
"""Validate GitHub configuration.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Tuple of (is_valid, error_message)
|
|
30
|
+
"""
|
|
31
|
+
# Check if config exists
|
|
32
|
+
token, _, repo = self.service.get_github_config()
|
|
33
|
+
if not token or not repo:
|
|
34
|
+
return (
|
|
35
|
+
False,
|
|
36
|
+
"GitHub not configured. Run: roadmap init --github-repo owner/repo --github-token <token>",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Check required fields
|
|
40
|
+
if not repo:
|
|
41
|
+
return False, "GitHub repo not configured in config.yaml"
|
|
42
|
+
|
|
43
|
+
if not token:
|
|
44
|
+
return False, "GitHub token not configured in config.yaml"
|
|
45
|
+
|
|
46
|
+
# Validate token format (basic check)
|
|
47
|
+
if not isinstance(token, str) or len(token) < 10:
|
|
48
|
+
return False, "GitHub token appears invalid (too short)"
|
|
49
|
+
|
|
50
|
+
return True, None
|
|
51
|
+
|
|
52
|
+
def validate_token(self) -> tuple[bool, str | None]:
|
|
53
|
+
"""Validate GitHub token accessibility.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Tuple of (is_valid, error_message)
|
|
57
|
+
"""
|
|
58
|
+
token, _, _ = self.service.get_github_config()
|
|
59
|
+
if not token:
|
|
60
|
+
return False, "GitHub token not set"
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Test token by making a simple API call
|
|
64
|
+
headers = {
|
|
65
|
+
"Authorization": f"token {token}",
|
|
66
|
+
"Accept": "application/vnd.github.v3+json",
|
|
67
|
+
}
|
|
68
|
+
response = requests.get(
|
|
69
|
+
"https://api.github.com/user",
|
|
70
|
+
headers=headers,
|
|
71
|
+
timeout=5,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if response.status_code == 401:
|
|
75
|
+
return False, "GitHub token is invalid or expired"
|
|
76
|
+
elif response.status_code == 403:
|
|
77
|
+
return False, "GitHub token has insufficient permissions"
|
|
78
|
+
elif response.status_code == 200:
|
|
79
|
+
return True, None
|
|
80
|
+
else:
|
|
81
|
+
return (
|
|
82
|
+
False,
|
|
83
|
+
f"GitHub API error: {response.status_code}",
|
|
84
|
+
)
|
|
85
|
+
except requests.RequestException as e:
|
|
86
|
+
return False, f"Failed to reach GitHub: {str(e)}"
|
|
87
|
+
except Exception as e:
|
|
88
|
+
return False, f"Unexpected error validating token: {str(e)}"
|
|
89
|
+
|
|
90
|
+
def validate_repo_access(self) -> tuple[bool, str | None]:
|
|
91
|
+
"""Validate access to configured repository.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple of (is_valid, error_message)
|
|
95
|
+
"""
|
|
96
|
+
token, _, repo = self.service.get_github_config()
|
|
97
|
+
if not token or not repo:
|
|
98
|
+
return False, "GitHub config incomplete"
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Test repo access
|
|
102
|
+
headers = {
|
|
103
|
+
"Authorization": f"token {token}",
|
|
104
|
+
"Accept": "application/vnd.github.v3+json",
|
|
105
|
+
}
|
|
106
|
+
url = f"https://api.github.com/repos/{repo}"
|
|
107
|
+
response = requests.get(url, headers=headers, timeout=5)
|
|
108
|
+
|
|
109
|
+
if response.status_code == 401:
|
|
110
|
+
return False, "GitHub token is invalid or expired"
|
|
111
|
+
elif response.status_code == 403:
|
|
112
|
+
return False, f"Access denied to repository {repo}"
|
|
113
|
+
elif response.status_code == 404:
|
|
114
|
+
return False, f"Repository not found: {repo}"
|
|
115
|
+
elif response.status_code == 200:
|
|
116
|
+
return True, None
|
|
117
|
+
else:
|
|
118
|
+
return (
|
|
119
|
+
False,
|
|
120
|
+
f"GitHub API error: {response.status_code}",
|
|
121
|
+
)
|
|
122
|
+
except requests.RequestException as e:
|
|
123
|
+
return False, f"Failed to reach GitHub: {str(e)}"
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return False, f"Unexpected error validating repo: {str(e)}"
|
|
126
|
+
|
|
127
|
+
def validate_all(self) -> tuple[bool, str | None]:
|
|
128
|
+
"""Run all validations.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Tuple of (is_valid, error_message)
|
|
132
|
+
"""
|
|
133
|
+
# Config validation
|
|
134
|
+
is_valid, error = self.validate_config()
|
|
135
|
+
if not is_valid:
|
|
136
|
+
return False, error
|
|
137
|
+
|
|
138
|
+
# Token validation
|
|
139
|
+
is_valid, error = self.validate_token()
|
|
140
|
+
if not is_valid:
|
|
141
|
+
return False, error
|
|
142
|
+
|
|
143
|
+
# Repo access validation
|
|
144
|
+
is_valid, error = self.validate_repo_access()
|
|
145
|
+
if not is_valid:
|
|
146
|
+
return False, error
|
|
147
|
+
|
|
148
|
+
return True, None
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""GitHub conflict detection service."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.common.datetime_parser import UnifiedDateTimeParser
|
|
7
|
+
from roadmap.core.domain.issue import Issue
|
|
8
|
+
from roadmap.core.services.github.github_integration_service import (
|
|
9
|
+
GitHubIntegrationService,
|
|
10
|
+
)
|
|
11
|
+
from roadmap.core.services.github.github_issue_client import GitHubIssueClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GitHubConflictDetector:
|
|
15
|
+
"""Detects conflicts between local and GitHub issues."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, integration_service: GitHubIntegrationService):
|
|
18
|
+
"""Initialize conflict detector.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
integration_service: GitHub integration service instance
|
|
22
|
+
"""
|
|
23
|
+
self.service = integration_service
|
|
24
|
+
token, owner, repo = integration_service.get_github_config()
|
|
25
|
+
self.owner = owner
|
|
26
|
+
self.repo = repo
|
|
27
|
+
self.client = GitHubIssueClient(token)
|
|
28
|
+
|
|
29
|
+
def detect_conflicts(
|
|
30
|
+
self, local_issue: Issue, github_number: int
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Detect conflicts between local and GitHub versions.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
local_issue: Local issue entity
|
|
36
|
+
github_number: GitHub issue number
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict with conflict information:
|
|
40
|
+
{
|
|
41
|
+
'has_conflicts': bool,
|
|
42
|
+
'local_modified': bool,
|
|
43
|
+
'github_modified': bool,
|
|
44
|
+
'last_sync': datetime | None,
|
|
45
|
+
'warnings': List[str]
|
|
46
|
+
}
|
|
47
|
+
"""
|
|
48
|
+
conflicts = {
|
|
49
|
+
"has_conflicts": False,
|
|
50
|
+
"local_modified": False,
|
|
51
|
+
"github_modified": False,
|
|
52
|
+
"last_sync": None,
|
|
53
|
+
"warnings": [],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Get last sync time from issue metadata if available
|
|
57
|
+
last_sync = self._get_last_sync_time(local_issue)
|
|
58
|
+
conflicts["last_sync"] = last_sync
|
|
59
|
+
|
|
60
|
+
# If no last sync recorded, can't detect conflicts
|
|
61
|
+
if not last_sync:
|
|
62
|
+
conflicts["warnings"].append(
|
|
63
|
+
"No sync history available. First sync will update all fields."
|
|
64
|
+
)
|
|
65
|
+
return conflicts
|
|
66
|
+
|
|
67
|
+
# Check if we have required GitHub config
|
|
68
|
+
if not self.owner or not self.repo:
|
|
69
|
+
conflicts["warnings"].append(
|
|
70
|
+
"GitHub configuration missing. Cannot check for GitHub changes."
|
|
71
|
+
)
|
|
72
|
+
return conflicts
|
|
73
|
+
|
|
74
|
+
# Check if GitHub issue was modified after last sync
|
|
75
|
+
try:
|
|
76
|
+
github_issue = self.client.fetch_issue(self.owner, self.repo, github_number)
|
|
77
|
+
github_updated_at = self._parse_github_timestamp(
|
|
78
|
+
github_issue.get("updated_at")
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if github_updated_at and github_updated_at > last_sync:
|
|
82
|
+
conflicts["github_modified"] = True
|
|
83
|
+
conflicts["warnings"].append(
|
|
84
|
+
"GitHub issue was modified after last sync"
|
|
85
|
+
)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
conflicts["warnings"].append(
|
|
88
|
+
f"Could not check GitHub for changes: {str(e)}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Check if local issue was modified after last sync
|
|
92
|
+
if self._is_local_modified_after_sync(local_issue, last_sync):
|
|
93
|
+
conflicts["local_modified"] = True
|
|
94
|
+
conflicts["warnings"].append("Local issue was modified after last sync")
|
|
95
|
+
|
|
96
|
+
# Set has_conflicts flag if both were modified
|
|
97
|
+
if conflicts["local_modified"] and conflicts["github_modified"]:
|
|
98
|
+
conflicts["has_conflicts"] = True
|
|
99
|
+
conflicts["warnings"].append(
|
|
100
|
+
"⚠️ Both local and GitHub versions have changes. Review carefully before syncing."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return conflicts
|
|
104
|
+
|
|
105
|
+
def _get_last_sync_time(self, issue: Issue) -> datetime | None:
|
|
106
|
+
"""Get last sync timestamp from issue.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
issue: Issue entity
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Last sync datetime or None
|
|
113
|
+
"""
|
|
114
|
+
# Check if issue has sync metadata with sync timestamp
|
|
115
|
+
if (
|
|
116
|
+
issue.github_sync_metadata
|
|
117
|
+
and "sync_timestamp" in issue.github_sync_metadata
|
|
118
|
+
):
|
|
119
|
+
return issue.github_sync_metadata["sync_timestamp"]
|
|
120
|
+
|
|
121
|
+
# Check issue updated_at as fallback
|
|
122
|
+
if issue.updated:
|
|
123
|
+
return issue.updated
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def _is_local_modified_after_sync(self, issue: Issue, last_sync: datetime) -> bool:
|
|
128
|
+
"""Check if local issue was modified after sync time.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
issue: Issue entity
|
|
132
|
+
last_sync: Last sync timestamp
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if modified after sync
|
|
136
|
+
"""
|
|
137
|
+
if not issue.updated:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
updated_at = issue.updated
|
|
141
|
+
return updated_at > last_sync
|
|
142
|
+
|
|
143
|
+
def _parse_github_timestamp(self, timestamp: str | None) -> datetime | None:
|
|
144
|
+
"""Parse GitHub API timestamp.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
timestamp: GitHub timestamp string
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Parsed datetime or None
|
|
151
|
+
"""
|
|
152
|
+
if not timestamp:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
return UnifiedDateTimeParser.parse_github_timestamp(timestamp)
|
|
157
|
+
except (ValueError, AttributeError):
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def get_conflict_summary(self, conflicts: dict[str, Any]) -> str:
|
|
161
|
+
"""Get human-readable conflict summary.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
conflicts: Conflicts dict from detect_conflicts()
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Summary string
|
|
168
|
+
"""
|
|
169
|
+
if not conflicts["warnings"]:
|
|
170
|
+
return "No conflicts detected. Safe to sync."
|
|
171
|
+
|
|
172
|
+
summary = "Conflict Detection Summary:\n"
|
|
173
|
+
for warning in conflicts["warnings"]:
|
|
174
|
+
summary += f" • {warning}\n"
|
|
175
|
+
|
|
176
|
+
if conflicts["has_conflicts"]:
|
|
177
|
+
summary += "\nRecommendation: Review changes manually before syncing."
|
|
178
|
+
|
|
179
|
+
return summary
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Classifies GitHub entities (active/archived)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitHubEntityClassifier:
|
|
9
|
+
"""Separates entities into active and archived categories."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def classify_entity_state(entity: Any) -> bool:
|
|
13
|
+
"""Determine if entity is archived.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
entity: Entity object to check
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if archived, False if active
|
|
20
|
+
|
|
21
|
+
Note:
|
|
22
|
+
Handles mocks safely by checking isinstance
|
|
23
|
+
"""
|
|
24
|
+
is_archived = False
|
|
25
|
+
if hasattr(entity, "archived"):
|
|
26
|
+
archived_attr = getattr(entity, "archived", False)
|
|
27
|
+
# Check if it's an actual boolean value, not a mock
|
|
28
|
+
if isinstance(archived_attr, bool):
|
|
29
|
+
is_archived = archived_attr
|
|
30
|
+
return is_archived
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def separate_by_state(cls, entities: list[T]) -> tuple[list[T], list[T]]:
|
|
34
|
+
"""Separate entities into active and archived.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
entities: List of entities to classify
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Tuple of (active_entities, archived_entities)
|
|
41
|
+
"""
|
|
42
|
+
active = []
|
|
43
|
+
archived = []
|
|
44
|
+
|
|
45
|
+
for entity in entities:
|
|
46
|
+
if cls.classify_entity_state(entity):
|
|
47
|
+
archived.append(entity)
|
|
48
|
+
else:
|
|
49
|
+
active.append(entity)
|
|
50
|
+
|
|
51
|
+
return active, archived
|