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,150 @@
|
|
|
1
|
+
"""GitHub Issues handler."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.github.handlers.base import BaseGitHubHandler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IssueHandler(BaseGitHubHandler):
|
|
9
|
+
"""Handler for GitHub Issues API operations."""
|
|
10
|
+
|
|
11
|
+
def get_issues(
|
|
12
|
+
self,
|
|
13
|
+
state: str = "all",
|
|
14
|
+
labels: list[str] | None = None,
|
|
15
|
+
milestone: str | None = None,
|
|
16
|
+
assignee: str | None = None,
|
|
17
|
+
per_page: int = 100,
|
|
18
|
+
) -> list[dict[str, Any]]:
|
|
19
|
+
"""Get issues from the repository, handling pagination.
|
|
20
|
+
|
|
21
|
+
Automatically fetches all pages of issues. Returns complete list
|
|
22
|
+
regardless of total count.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
state: Issue state ('open', 'closed', 'all')
|
|
26
|
+
labels: Optional list of label names to filter by
|
|
27
|
+
milestone: Optional milestone title to filter by
|
|
28
|
+
assignee: Optional assignee username to filter by
|
|
29
|
+
per_page: Items per page (max 100)
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of all issue dictionaries across all pages
|
|
33
|
+
"""
|
|
34
|
+
from structlog import get_logger
|
|
35
|
+
|
|
36
|
+
logger = get_logger()
|
|
37
|
+
logger.info("issue_handler_get_issues_called", state=state, per_page=per_page)
|
|
38
|
+
|
|
39
|
+
self._check_repository()
|
|
40
|
+
|
|
41
|
+
params = {
|
|
42
|
+
"state": state,
|
|
43
|
+
"sort": "created",
|
|
44
|
+
"direction": "desc",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if labels:
|
|
48
|
+
params["labels"] = ",".join(labels)
|
|
49
|
+
if milestone:
|
|
50
|
+
params["milestone"] = milestone
|
|
51
|
+
if assignee:
|
|
52
|
+
params["assignee"] = assignee
|
|
53
|
+
|
|
54
|
+
all_issues = self._paginate_request(
|
|
55
|
+
"GET",
|
|
56
|
+
f"/repos/{self.owner}/{self.repo}/issues",
|
|
57
|
+
params=params,
|
|
58
|
+
per_page=per_page,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
issues_only = [issue for issue in all_issues if "pull_request" not in issue]
|
|
62
|
+
pr_count = len(all_issues) - len(issues_only)
|
|
63
|
+
if pr_count:
|
|
64
|
+
logger.info(
|
|
65
|
+
"issue_handler_prs_skipped",
|
|
66
|
+
skipped_count=pr_count,
|
|
67
|
+
issues_count=len(issues_only),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
logger.info(
|
|
71
|
+
"issue_handler_get_issues_complete",
|
|
72
|
+
total_issues=len(issues_only),
|
|
73
|
+
)
|
|
74
|
+
return issues_only
|
|
75
|
+
|
|
76
|
+
def get_issue(self, issue_number: int) -> dict[str, Any]:
|
|
77
|
+
"""Get a specific issue by number."""
|
|
78
|
+
self._check_repository()
|
|
79
|
+
response = self._make_request(
|
|
80
|
+
"GET", f"/repos/{self.owner}/{self.repo}/issues/{issue_number}"
|
|
81
|
+
)
|
|
82
|
+
return response.json()
|
|
83
|
+
|
|
84
|
+
def create_issue(
|
|
85
|
+
self,
|
|
86
|
+
title: str,
|
|
87
|
+
body: str | None = None,
|
|
88
|
+
labels: list[str] | None = None,
|
|
89
|
+
assignees: list[str] | None = None,
|
|
90
|
+
milestone: int | None = None,
|
|
91
|
+
) -> dict[str, Any]:
|
|
92
|
+
"""Create a new issue."""
|
|
93
|
+
self._check_repository()
|
|
94
|
+
|
|
95
|
+
data: dict[str, Any] = {"title": title}
|
|
96
|
+
|
|
97
|
+
if body:
|
|
98
|
+
data["body"] = body
|
|
99
|
+
if labels:
|
|
100
|
+
data["labels"] = labels
|
|
101
|
+
if assignees:
|
|
102
|
+
data["assignees"] = assignees
|
|
103
|
+
if milestone:
|
|
104
|
+
data["milestone"] = milestone
|
|
105
|
+
|
|
106
|
+
response = self._make_request(
|
|
107
|
+
"POST", f"/repos/{self.owner}/{self.repo}/issues", json=data
|
|
108
|
+
)
|
|
109
|
+
return response.json()
|
|
110
|
+
|
|
111
|
+
def update_issue(
|
|
112
|
+
self,
|
|
113
|
+
issue_number: int,
|
|
114
|
+
title: str | None = None,
|
|
115
|
+
body: str | None = None,
|
|
116
|
+
state: str | None = None,
|
|
117
|
+
labels: list[str] | None = None,
|
|
118
|
+
assignees: list[str] | None = None,
|
|
119
|
+
milestone: int | None = None,
|
|
120
|
+
) -> dict[str, Any]:
|
|
121
|
+
"""Update an existing issue."""
|
|
122
|
+
self._check_repository()
|
|
123
|
+
|
|
124
|
+
data = {}
|
|
125
|
+
|
|
126
|
+
if title is not None:
|
|
127
|
+
data["title"] = title
|
|
128
|
+
if body is not None:
|
|
129
|
+
data["body"] = body
|
|
130
|
+
if state is not None:
|
|
131
|
+
data["state"] = state
|
|
132
|
+
if labels is not None:
|
|
133
|
+
data["labels"] = labels
|
|
134
|
+
if assignees is not None:
|
|
135
|
+
data["assignees"] = assignees
|
|
136
|
+
if milestone is not None:
|
|
137
|
+
data["milestone"] = milestone
|
|
138
|
+
|
|
139
|
+
response = self._make_request(
|
|
140
|
+
"PATCH", f"/repos/{self.owner}/{self.repo}/issues/{issue_number}", json=data
|
|
141
|
+
)
|
|
142
|
+
return response.json()
|
|
143
|
+
|
|
144
|
+
def close_issue(self, issue_number: int) -> dict[str, Any]:
|
|
145
|
+
"""Close an issue."""
|
|
146
|
+
return self.update_issue(issue_number, state="closed")
|
|
147
|
+
|
|
148
|
+
def reopen_issue(self, issue_number: int) -> dict[str, Any]:
|
|
149
|
+
"""Reopen a closed issue."""
|
|
150
|
+
return self.update_issue(issue_number, state="open")
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""GitHub Labels handler."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from structlog import get_logger
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.github.handlers.base import BaseGitHubHandler
|
|
8
|
+
from roadmap.core.domain import Priority, Status
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LabelHandler(BaseGitHubHandler):
|
|
14
|
+
"""Handler for GitHub Labels API operations."""
|
|
15
|
+
|
|
16
|
+
def get_labels(self) -> list[dict[str, Any]]:
|
|
17
|
+
"""Get repository labels.
|
|
18
|
+
|
|
19
|
+
Handles pagination automatically - returns all labels.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of all repository labels across all pages
|
|
23
|
+
"""
|
|
24
|
+
self._check_repository()
|
|
25
|
+
return self._paginate_request("GET", f"/repos/{self.owner}/{self.repo}/labels")
|
|
26
|
+
|
|
27
|
+
def create_label(
|
|
28
|
+
self, name: str, color: str, description: str | None = None
|
|
29
|
+
) -> dict[str, Any]:
|
|
30
|
+
"""Create a new label."""
|
|
31
|
+
self._check_repository()
|
|
32
|
+
|
|
33
|
+
data = {"name": name, "color": color.lstrip("#")} # Remove # if present
|
|
34
|
+
|
|
35
|
+
if description:
|
|
36
|
+
data["description"] = description
|
|
37
|
+
|
|
38
|
+
response = self._make_request(
|
|
39
|
+
"POST", f"/repos/{self.owner}/{self.repo}/labels", json=data
|
|
40
|
+
)
|
|
41
|
+
return response.json()
|
|
42
|
+
|
|
43
|
+
def update_label(
|
|
44
|
+
self,
|
|
45
|
+
label_name: str,
|
|
46
|
+
new_name: str | None = None,
|
|
47
|
+
color: str | None = None,
|
|
48
|
+
description: str | None = None,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""Update an existing label."""
|
|
51
|
+
self._check_repository()
|
|
52
|
+
|
|
53
|
+
data = {}
|
|
54
|
+
|
|
55
|
+
if new_name is not None:
|
|
56
|
+
data["new_name"] = new_name
|
|
57
|
+
if color is not None:
|
|
58
|
+
data["color"] = color.lstrip("#")
|
|
59
|
+
if description is not None:
|
|
60
|
+
data["description"] = description
|
|
61
|
+
|
|
62
|
+
response = self._make_request(
|
|
63
|
+
"PATCH", f"/repos/{self.owner}/{self.repo}/labels/{label_name}", json=data
|
|
64
|
+
)
|
|
65
|
+
return response.json()
|
|
66
|
+
|
|
67
|
+
def delete_label(self, label_name: str) -> None:
|
|
68
|
+
"""Delete a label."""
|
|
69
|
+
self._check_repository()
|
|
70
|
+
self._make_request(
|
|
71
|
+
"DELETE", f"/repos/{self.owner}/{self.repo}/labels/{label_name}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def priority_to_labels(self, priority: Priority) -> list[str]:
|
|
75
|
+
"""Convert priority to GitHub labels."""
|
|
76
|
+
priority_labels = {
|
|
77
|
+
Priority.CRITICAL: ["priority:critical"],
|
|
78
|
+
Priority.HIGH: ["priority:high"],
|
|
79
|
+
Priority.MEDIUM: ["priority:medium"],
|
|
80
|
+
Priority.LOW: ["priority:low"],
|
|
81
|
+
}
|
|
82
|
+
return priority_labels.get(priority, [])
|
|
83
|
+
|
|
84
|
+
def status_to_labels(self, status: Status) -> list[str]:
|
|
85
|
+
"""Convert status to GitHub labels."""
|
|
86
|
+
status_labels = {
|
|
87
|
+
Status.TODO: ["status:todo"],
|
|
88
|
+
Status.IN_PROGRESS: ["status:in-progress"],
|
|
89
|
+
Status.BLOCKED: ["status:blocked"],
|
|
90
|
+
Status.REVIEW: ["status:review"],
|
|
91
|
+
Status.CLOSED: ["status:done"],
|
|
92
|
+
}
|
|
93
|
+
return status_labels.get(status, [])
|
|
94
|
+
|
|
95
|
+
def labels_to_priority(self, labels: list[str]) -> Priority | None:
|
|
96
|
+
"""Extract priority from GitHub labels."""
|
|
97
|
+
label_names = [
|
|
98
|
+
label.get("name", label) if isinstance(label, dict) else label
|
|
99
|
+
for label in labels
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for label in label_names:
|
|
103
|
+
if label == "priority:critical":
|
|
104
|
+
return Priority.CRITICAL
|
|
105
|
+
elif label == "priority:high":
|
|
106
|
+
return Priority.HIGH
|
|
107
|
+
elif label == "priority:medium":
|
|
108
|
+
return Priority.MEDIUM
|
|
109
|
+
elif label == "priority:low":
|
|
110
|
+
return Priority.LOW
|
|
111
|
+
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def labels_to_status(self, labels: list[str]) -> Status | None:
|
|
115
|
+
"""Extract status from GitHub labels."""
|
|
116
|
+
label_names = [
|
|
117
|
+
label.get("name", label) if isinstance(label, dict) else label
|
|
118
|
+
for label in labels
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
for label in label_names:
|
|
122
|
+
if label == "status:todo":
|
|
123
|
+
return Status.TODO
|
|
124
|
+
elif label == "status:in-progress":
|
|
125
|
+
return Status.IN_PROGRESS
|
|
126
|
+
elif label == "status:blocked":
|
|
127
|
+
return Status.BLOCKED
|
|
128
|
+
elif label == "status:review":
|
|
129
|
+
return Status.REVIEW
|
|
130
|
+
elif label == "status:done":
|
|
131
|
+
return Status.CLOSED
|
|
132
|
+
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def setup_default_labels(self) -> None:
|
|
136
|
+
"""Set up default labels for priority and status."""
|
|
137
|
+
self._check_repository()
|
|
138
|
+
|
|
139
|
+
# Priority labels
|
|
140
|
+
priority_labels = [
|
|
141
|
+
("priority:critical", "FF0000", "Critical priority"),
|
|
142
|
+
("priority:high", "FF9900", "High priority"),
|
|
143
|
+
("priority:medium", "FFFF00", "Medium priority"),
|
|
144
|
+
("priority:low", "00FF00", "Low priority"),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# Status labels
|
|
148
|
+
status_labels = [
|
|
149
|
+
("status:todo", "CCCCCC", "To do"),
|
|
150
|
+
("status:in-progress", "0366D6", "In progress"),
|
|
151
|
+
("status:blocked", "D73A49", "Blocked"),
|
|
152
|
+
("status:review", "A371F7", "In review"),
|
|
153
|
+
("status:done", "28A745", "Done"),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
existing_labels = [label["name"] for label in self.get_labels()]
|
|
157
|
+
|
|
158
|
+
for name, color, description in priority_labels + status_labels:
|
|
159
|
+
if name not in existing_labels:
|
|
160
|
+
try:
|
|
161
|
+
self.create_label(name, color, description)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
# Label might have been created by another process
|
|
164
|
+
logger.debug("create_label_failed", label_name=name, error=str(e))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""GitHub Milestones handler."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.github.handlers.base import BaseGitHubHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MilestoneHandler(BaseGitHubHandler):
|
|
10
|
+
"""Handler for GitHub Milestones API operations."""
|
|
11
|
+
|
|
12
|
+
def get_milestones(self, state: str = "open") -> list[dict[str, Any]]:
|
|
13
|
+
"""Get milestones from the repository.
|
|
14
|
+
|
|
15
|
+
Handles pagination automatically - returns all milestones.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
state: Milestone state ('open', 'closed', 'all')
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of all milestones across all pages
|
|
22
|
+
"""
|
|
23
|
+
self._check_repository()
|
|
24
|
+
|
|
25
|
+
params = {"state": state}
|
|
26
|
+
return self._paginate_request(
|
|
27
|
+
"GET", f"/repos/{self.owner}/{self.repo}/milestones", params=params
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def get_milestone(self, milestone_number: int) -> dict[str, Any]:
|
|
31
|
+
"""Get a specific milestone by number."""
|
|
32
|
+
self._check_repository()
|
|
33
|
+
response = self._make_request(
|
|
34
|
+
"GET", f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}"
|
|
35
|
+
)
|
|
36
|
+
return response.json()
|
|
37
|
+
|
|
38
|
+
def create_milestone(
|
|
39
|
+
self,
|
|
40
|
+
title: str,
|
|
41
|
+
description: str | None = None,
|
|
42
|
+
due_date: datetime | None = None,
|
|
43
|
+
state: str = "open",
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""Create a new milestone."""
|
|
46
|
+
self._check_repository()
|
|
47
|
+
|
|
48
|
+
data = {"title": title, "state": state}
|
|
49
|
+
|
|
50
|
+
if description:
|
|
51
|
+
data["description"] = description
|
|
52
|
+
if due_date:
|
|
53
|
+
# GitHub expects ISO 8601 format with timezone (Z for UTC)
|
|
54
|
+
if due_date.tzinfo is None:
|
|
55
|
+
# Assume UTC for naive datetime
|
|
56
|
+
data["due_on"] = due_date.isoformat() + "Z"
|
|
57
|
+
else:
|
|
58
|
+
data["due_on"] = due_date.isoformat()
|
|
59
|
+
|
|
60
|
+
response = self._make_request(
|
|
61
|
+
"POST", f"/repos/{self.owner}/{self.repo}/milestones", json=data
|
|
62
|
+
)
|
|
63
|
+
return response.json()
|
|
64
|
+
|
|
65
|
+
def update_milestone(
|
|
66
|
+
self,
|
|
67
|
+
milestone_number: int,
|
|
68
|
+
title: str | None = None,
|
|
69
|
+
description: str | None = None,
|
|
70
|
+
due_date: datetime | None = None,
|
|
71
|
+
state: str | None = None,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
"""Update an existing milestone."""
|
|
74
|
+
self._check_repository()
|
|
75
|
+
|
|
76
|
+
data = {}
|
|
77
|
+
|
|
78
|
+
if title is not None:
|
|
79
|
+
data["title"] = title
|
|
80
|
+
if description is not None:
|
|
81
|
+
data["description"] = description
|
|
82
|
+
if due_date is not None:
|
|
83
|
+
# GitHub expects ISO 8601 format with timezone (Z for UTC)
|
|
84
|
+
if due_date.tzinfo is None:
|
|
85
|
+
# Assume UTC for naive datetime
|
|
86
|
+
data["due_on"] = due_date.isoformat() + "Z"
|
|
87
|
+
else:
|
|
88
|
+
data["due_on"] = due_date.isoformat()
|
|
89
|
+
if state is not None:
|
|
90
|
+
data["state"] = state
|
|
91
|
+
|
|
92
|
+
response = self._make_request(
|
|
93
|
+
"PATCH",
|
|
94
|
+
f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}",
|
|
95
|
+
json=data,
|
|
96
|
+
)
|
|
97
|
+
return response.json()
|
|
98
|
+
|
|
99
|
+
def delete_milestone(self, milestone_number: int) -> None:
|
|
100
|
+
"""Delete a milestone."""
|
|
101
|
+
self._check_repository()
|
|
102
|
+
self._make_request(
|
|
103
|
+
"DELETE", f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}"
|
|
104
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Persistence Layer - Data Serialization and Storage.
|
|
2
|
+
|
|
3
|
+
This layer handles reading and writing data to files, including:
|
|
4
|
+
- YAML frontmatter parsing
|
|
5
|
+
- File locking mechanisms
|
|
6
|
+
- Enhanced persistence with backup/recovery
|
|
7
|
+
- Database infrastructure management
|
|
8
|
+
- File synchronization
|
|
9
|
+
|
|
10
|
+
Modules:
|
|
11
|
+
- database_manager.py: SQLite connection and schema management
|
|
12
|
+
- file_synchronizer.py: File-to-database synchronization
|
|
13
|
+
- parser.py: Issue/Milestone/Project YAML frontmatter parsing
|
|
14
|
+
- persistence.py: Enhanced YAML validation and recovery
|
|
15
|
+
- file_locking.py: File-based locking mechanism
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .conflict_resolver import ConflictResolver
|
|
19
|
+
from .database_manager import DatabaseManager
|
|
20
|
+
from .file_synchronizer import FileSynchronizer
|
|
21
|
+
from .parser import FrontmatterParser, IssueParser, MilestoneParser, ProjectParser
|
|
22
|
+
from .persistence import enhanced_persistence
|
|
23
|
+
from .sync_state_tracker import SyncStateTracker
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ConflictResolver",
|
|
27
|
+
"DatabaseManager",
|
|
28
|
+
"FileSynchronizer",
|
|
29
|
+
"FrontmatterParser",
|
|
30
|
+
"IssueParser",
|
|
31
|
+
"MilestoneParser",
|
|
32
|
+
"ProjectParser",
|
|
33
|
+
"SyncStateTracker",
|
|
34
|
+
"enhanced_persistence",
|
|
35
|
+
]
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Conflict detection and resolution for file synchronization.
|
|
2
|
+
|
|
3
|
+
This module handles detection and management of conflicts that arise during
|
|
4
|
+
file synchronization operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from roadmap.common.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConflictResolver:
|
|
16
|
+
"""Handles detection and resolution of synchronization conflicts."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, data_dir: Path):
|
|
19
|
+
"""Initialize the conflict resolver.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data_dir: Root directory for data files
|
|
23
|
+
"""
|
|
24
|
+
self.data_dir = data_dir
|
|
25
|
+
|
|
26
|
+
def detect_conflicts(self) -> list[str]:
|
|
27
|
+
"""Detect files with git merge conflict markers.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of file paths containing conflict markers
|
|
31
|
+
"""
|
|
32
|
+
conflict_files = []
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
for file_path in self.data_dir.rglob("*.md"):
|
|
36
|
+
if self._has_conflict_markers(file_path):
|
|
37
|
+
conflict_files.append(str(file_path))
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.warning(
|
|
40
|
+
"error_detecting_conflicts",
|
|
41
|
+
error=str(e),
|
|
42
|
+
severity="operational",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return conflict_files
|
|
46
|
+
|
|
47
|
+
def _has_conflict_markers(self, file_path: Path) -> bool:
|
|
48
|
+
"""Check if a file contains git conflict markers.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
file_path: Path to file to check
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if file contains conflict markers
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
with open(file_path, encoding="utf-8") as f:
|
|
58
|
+
content = f.read()
|
|
59
|
+
# Check for standard git conflict markers
|
|
60
|
+
return (
|
|
61
|
+
"<<<<<<< HEAD" in content
|
|
62
|
+
or "<<<<<<<" in content
|
|
63
|
+
or "=======" in content
|
|
64
|
+
or ">>>>>>>" in content
|
|
65
|
+
)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.debug("conflict_marker_check_failed", error=str(e))
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def resolve_conflict(self, file_path: Path, resolution: str) -> bool:
|
|
71
|
+
"""Resolve a file conflict by removing markers.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path: Path to file with conflict
|
|
75
|
+
resolution: Which version to keep - "ours" or "theirs"
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if resolution was successful
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
with open(file_path, encoding="utf-8") as f:
|
|
82
|
+
content = f.read()
|
|
83
|
+
|
|
84
|
+
# Remove conflict markers and keep appropriate version
|
|
85
|
+
resolved = self._remove_conflict_markers(content, resolution)
|
|
86
|
+
|
|
87
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
88
|
+
f.write(resolved)
|
|
89
|
+
|
|
90
|
+
logger.info(
|
|
91
|
+
"conflict_resolved",
|
|
92
|
+
filepath=file_path.name,
|
|
93
|
+
resolution=resolution,
|
|
94
|
+
)
|
|
95
|
+
return True
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(
|
|
98
|
+
"error_resolving_conflict",
|
|
99
|
+
file_path=str(file_path),
|
|
100
|
+
error=str(e),
|
|
101
|
+
severity="operational",
|
|
102
|
+
)
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def _remove_conflict_markers(self, content: str, resolution: str) -> str:
|
|
106
|
+
"""Remove conflict markers from content, keeping specified version.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
content: File content with conflict markers
|
|
110
|
+
resolution: Which version to keep - "ours" or "theirs"
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Content with conflict markers removed
|
|
114
|
+
"""
|
|
115
|
+
lines = content.split("\n")
|
|
116
|
+
resolved_lines = []
|
|
117
|
+
in_conflict = False
|
|
118
|
+
ours_section = []
|
|
119
|
+
theirs_section = []
|
|
120
|
+
current_section = None
|
|
121
|
+
|
|
122
|
+
for line in lines:
|
|
123
|
+
if line.startswith("<<<<<<< HEAD"):
|
|
124
|
+
in_conflict = True
|
|
125
|
+
current_section = "ours"
|
|
126
|
+
ours_section = []
|
|
127
|
+
theirs_section = []
|
|
128
|
+
elif line.startswith("======="):
|
|
129
|
+
if in_conflict:
|
|
130
|
+
current_section = "theirs"
|
|
131
|
+
elif line.startswith(">>>>>>>"):
|
|
132
|
+
in_conflict = False
|
|
133
|
+
if resolution == "ours":
|
|
134
|
+
resolved_lines.extend(ours_section)
|
|
135
|
+
else:
|
|
136
|
+
resolved_lines.extend(theirs_section)
|
|
137
|
+
ours_section = []
|
|
138
|
+
theirs_section = []
|
|
139
|
+
elif in_conflict:
|
|
140
|
+
if current_section == "ours":
|
|
141
|
+
ours_section.append(line)
|
|
142
|
+
else:
|
|
143
|
+
theirs_section.append(line)
|
|
144
|
+
else:
|
|
145
|
+
resolved_lines.append(line)
|
|
146
|
+
|
|
147
|
+
return "\n".join(resolved_lines)
|
|
148
|
+
|
|
149
|
+
def auto_resolve_conflicts(
|
|
150
|
+
self, sync_state_tracker, resolution: str = "ours"
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""Automatically resolve all detected conflicts.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
sync_state_tracker: SyncStateTracker instance for updating state
|
|
156
|
+
resolution: Which version to keep for all conflicts
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
True if all conflicts resolved successfully
|
|
160
|
+
"""
|
|
161
|
+
conflict_files = self.detect_conflicts()
|
|
162
|
+
|
|
163
|
+
if not conflict_files:
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
all_resolved = True
|
|
167
|
+
for file_path in conflict_files:
|
|
168
|
+
if not self.resolve_conflict(Path(file_path), resolution):
|
|
169
|
+
all_resolved = False
|
|
170
|
+
|
|
171
|
+
if all_resolved:
|
|
172
|
+
sync_state_tracker.clear_conflicts()
|
|
173
|
+
else:
|
|
174
|
+
sync_state_tracker.mark_conflicts_detected(conflict_files)
|
|
175
|
+
|
|
176
|
+
return all_resolved
|
|
177
|
+
|
|
178
|
+
def get_conflict_summary(self) -> dict[str, Any]:
|
|
179
|
+
"""Get summary of detected conflicts.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary with conflict information
|
|
183
|
+
"""
|
|
184
|
+
conflict_files = self.detect_conflicts()
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"has_conflicts": len(conflict_files) > 0,
|
|
188
|
+
"conflict_count": len(conflict_files),
|
|
189
|
+
"files": conflict_files,
|
|
190
|
+
}
|