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 @@
|
|
|
1
|
+
"""Package initialization file."""
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Comment management service for entities (Issues, Projects, Milestones)."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
|
|
6
|
+
from roadmap.common.errors.exceptions import ValidationError
|
|
7
|
+
from roadmap.common.logging.error_logging import (
|
|
8
|
+
log_validation_error,
|
|
9
|
+
)
|
|
10
|
+
from roadmap.common.observability.instrumentation import traced
|
|
11
|
+
from roadmap.core.domain import Comment
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommentService:
|
|
15
|
+
"""Service for managing comments on entities."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def generate_comment_id() -> int:
|
|
19
|
+
"""Generate a unique comment ID.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A unique comment ID
|
|
23
|
+
"""
|
|
24
|
+
uid = uuid.uuid4()
|
|
25
|
+
return int(str(uid.int)[:15])
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
@traced("create_comment")
|
|
29
|
+
def create_comment(
|
|
30
|
+
author: str,
|
|
31
|
+
body: str,
|
|
32
|
+
entity_id: str | None = None,
|
|
33
|
+
in_reply_to: int | None = None,
|
|
34
|
+
) -> Comment:
|
|
35
|
+
"""Create a new comment.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
author: Author name/username
|
|
39
|
+
body: Comment body (markdown)
|
|
40
|
+
entity_id: Optional entity ID the comment is on
|
|
41
|
+
in_reply_to: Optional ID of comment this replies to (for threading)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
New Comment instance
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ValidationError: If inputs are invalid
|
|
48
|
+
"""
|
|
49
|
+
# Validate inputs
|
|
50
|
+
if not author or not author.strip():
|
|
51
|
+
error = ValidationError(
|
|
52
|
+
domain_message="Comment author cannot be empty",
|
|
53
|
+
user_message="Comment author cannot be empty",
|
|
54
|
+
)
|
|
55
|
+
log_validation_error(
|
|
56
|
+
error,
|
|
57
|
+
entity_type="Comment",
|
|
58
|
+
field_name="author",
|
|
59
|
+
proposed_value=author,
|
|
60
|
+
)
|
|
61
|
+
raise error
|
|
62
|
+
|
|
63
|
+
if not body or not body.strip():
|
|
64
|
+
error = ValidationError(
|
|
65
|
+
domain_message="Comment body cannot be empty",
|
|
66
|
+
user_message="Comment body cannot be empty",
|
|
67
|
+
)
|
|
68
|
+
log_validation_error(
|
|
69
|
+
error,
|
|
70
|
+
entity_type="Comment",
|
|
71
|
+
field_name="body",
|
|
72
|
+
proposed_value=body,
|
|
73
|
+
)
|
|
74
|
+
raise error
|
|
75
|
+
|
|
76
|
+
now = datetime.now(UTC)
|
|
77
|
+
|
|
78
|
+
return Comment(
|
|
79
|
+
id=CommentService.generate_comment_id(),
|
|
80
|
+
issue_id=entity_id or "",
|
|
81
|
+
author=author.strip(),
|
|
82
|
+
body=body.strip(),
|
|
83
|
+
created_at=now,
|
|
84
|
+
updated_at=now,
|
|
85
|
+
in_reply_to=in_reply_to,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
@traced("validate_comment_thread")
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _validate_comment_field(comment: Comment) -> list[str]:
|
|
92
|
+
"""Validate individual comment fields.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
comment: Comment to validate
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of field validation errors
|
|
99
|
+
"""
|
|
100
|
+
errors = []
|
|
101
|
+
|
|
102
|
+
if not isinstance(comment.created_at, datetime):
|
|
103
|
+
errors.append(f"Comment {comment.id}: created_at is not a valid datetime")
|
|
104
|
+
if not isinstance(comment.updated_at, datetime):
|
|
105
|
+
errors.append(f"Comment {comment.id}: updated_at is not a valid datetime")
|
|
106
|
+
|
|
107
|
+
if not comment.author or not comment.author.strip():
|
|
108
|
+
errors.append(f"Comment {comment.id}: author cannot be empty")
|
|
109
|
+
|
|
110
|
+
if not comment.body or not comment.body.strip():
|
|
111
|
+
errors.append(f"Comment {comment.id}: body cannot be empty")
|
|
112
|
+
|
|
113
|
+
return errors
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _validate_duplicate_ids(comments: list[Comment]) -> tuple[list[str], set[str]]:
|
|
117
|
+
"""Validate for duplicate comment IDs.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
comments: List of comments to check
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Tuple of (error_messages, comment_ids_set)
|
|
124
|
+
"""
|
|
125
|
+
errors = []
|
|
126
|
+
comment_ids = set()
|
|
127
|
+
|
|
128
|
+
for comment in comments:
|
|
129
|
+
if comment.id in comment_ids:
|
|
130
|
+
errors.append(f"Duplicate comment ID: {comment.id}")
|
|
131
|
+
comment_ids.add(comment.id)
|
|
132
|
+
|
|
133
|
+
return errors, comment_ids
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _validate_reply_references(
|
|
137
|
+
comments: list[Comment], comment_ids: set[str]
|
|
138
|
+
) -> list[str]:
|
|
139
|
+
"""Validate in_reply_to references point to existing comments.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
comments: List of comments
|
|
143
|
+
comment_ids: Set of known comment IDs
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
List of reference validation errors
|
|
147
|
+
"""
|
|
148
|
+
errors = []
|
|
149
|
+
|
|
150
|
+
for comment in comments:
|
|
151
|
+
if (
|
|
152
|
+
comment.in_reply_to is not None
|
|
153
|
+
and comment.in_reply_to not in comment_ids
|
|
154
|
+
):
|
|
155
|
+
if comment_ids:
|
|
156
|
+
errors.append(
|
|
157
|
+
f"Comment {comment.id}: in_reply_to references non-existent comment {comment.in_reply_to}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return errors
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _validate_circular_references(comments: list[Comment]) -> list[str]:
|
|
164
|
+
"""Validate for circular references in reply chain.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
comments: List of comments
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of circular reference errors
|
|
171
|
+
"""
|
|
172
|
+
errors = []
|
|
173
|
+
|
|
174
|
+
for comment in comments:
|
|
175
|
+
if comment.in_reply_to is None:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
chain = set()
|
|
179
|
+
current_id = comment.in_reply_to
|
|
180
|
+
max_depth = len(comments) + 1
|
|
181
|
+
|
|
182
|
+
while current_id is not None and max_depth > 0:
|
|
183
|
+
if current_id == comment.id:
|
|
184
|
+
errors.append(f"Comment {comment.id}: circular reference detected")
|
|
185
|
+
break
|
|
186
|
+
if current_id in chain:
|
|
187
|
+
break
|
|
188
|
+
chain.add(current_id)
|
|
189
|
+
current_comment = next(
|
|
190
|
+
(c for c in comments if c.id == current_id), None
|
|
191
|
+
)
|
|
192
|
+
current_id = current_comment.in_reply_to if current_comment else None
|
|
193
|
+
max_depth -= 1
|
|
194
|
+
|
|
195
|
+
return errors
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def validate_comment_thread(comments: list[Comment]) -> list[str]:
|
|
199
|
+
"""Validate comment threads for errors.
|
|
200
|
+
|
|
201
|
+
Checks for:
|
|
202
|
+
- Duplicate comment IDs
|
|
203
|
+
- Invalid in_reply_to references
|
|
204
|
+
- Circular references
|
|
205
|
+
- Valid datetime fields
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
comments: List of comments to validate
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List of error messages (empty if valid)
|
|
212
|
+
"""
|
|
213
|
+
errors = []
|
|
214
|
+
|
|
215
|
+
# Check for duplicate IDs
|
|
216
|
+
dup_errors, comment_ids = CommentService._validate_duplicate_ids(comments)
|
|
217
|
+
errors.extend(dup_errors)
|
|
218
|
+
|
|
219
|
+
# Check field validity
|
|
220
|
+
for comment in comments:
|
|
221
|
+
field_errors = CommentService._validate_comment_field(comment)
|
|
222
|
+
errors.extend(field_errors)
|
|
223
|
+
|
|
224
|
+
# Check reply references
|
|
225
|
+
ref_errors = CommentService._validate_reply_references(comments, comment_ids)
|
|
226
|
+
errors.extend(ref_errors)
|
|
227
|
+
|
|
228
|
+
# Check for circular references
|
|
229
|
+
circular_errors = CommentService._validate_circular_references(comments)
|
|
230
|
+
errors.extend(circular_errors)
|
|
231
|
+
|
|
232
|
+
return errors
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
@traced("build_comment_threads")
|
|
236
|
+
def build_comment_threads(
|
|
237
|
+
comments: list[Comment],
|
|
238
|
+
) -> dict[int | None, list[Comment]]:
|
|
239
|
+
"""Build comment thread hierarchy from flat list.
|
|
240
|
+
|
|
241
|
+
Groups comments by their parent (in_reply_to) for display.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
comments: List of comments
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Dictionary mapping parent comment ID -> list of replies
|
|
248
|
+
"""
|
|
249
|
+
threads: dict[int | None, list[Comment]] = {}
|
|
250
|
+
|
|
251
|
+
for comment in comments:
|
|
252
|
+
parent_id = comment.in_reply_to
|
|
253
|
+
if parent_id not in threads:
|
|
254
|
+
threads[parent_id] = []
|
|
255
|
+
threads[parent_id].append(comment)
|
|
256
|
+
|
|
257
|
+
# Sort by created_at within each thread
|
|
258
|
+
for thread in threads.values():
|
|
259
|
+
thread.sort(key=lambda c: c.created_at)
|
|
260
|
+
|
|
261
|
+
return threads
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
@traced("format_comment_for_display")
|
|
265
|
+
def format_comment_for_display(comment: Comment, indent: int = 0) -> str:
|
|
266
|
+
"""Format a comment for CLI display.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
comment: Comment to format
|
|
270
|
+
indent: Indentation level (for threaded replies)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Formatted comment string
|
|
274
|
+
"""
|
|
275
|
+
indent_str = " " * indent
|
|
276
|
+
timestamp = comment.created_at.strftime("%Y-%m-%d %H:%M")
|
|
277
|
+
return f"{indent_str}💬 {comment.author} ({timestamp}):\n{indent_str} {comment.body}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package initialization file."""
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Auto-sync service for Git hooks - handles GitHub sync on Git events.
|
|
2
|
+
|
|
3
|
+
This service integrates with Git hooks to automatically sync issues with GitHub
|
|
4
|
+
when Git events occur (commits, checkouts, merges). It provides:
|
|
5
|
+
- Post-commit auto-sync with optional user confirmation
|
|
6
|
+
- Configurable auto-sync behavior
|
|
7
|
+
- Sync history tracking via SyncMetadataService
|
|
8
|
+
- Smart conflict resolution
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import structlog
|
|
15
|
+
|
|
16
|
+
from roadmap.common.console import get_console
|
|
17
|
+
from roadmap.core.services.github.github_integration_service import (
|
|
18
|
+
GitHubIntegrationService,
|
|
19
|
+
)
|
|
20
|
+
from roadmap.core.services.sync.sync_metadata_service import SyncMetadataService
|
|
21
|
+
|
|
22
|
+
logger = structlog.get_logger()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GitHookAutoSyncConfig:
|
|
26
|
+
"""Configuration for git hook auto-sync behavior."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
auto_sync_enabled: bool = False,
|
|
31
|
+
sync_on_commit: bool = False,
|
|
32
|
+
sync_on_checkout: bool = False,
|
|
33
|
+
sync_on_merge: bool = False,
|
|
34
|
+
confirm_before_sync: bool = True,
|
|
35
|
+
force_local: bool = False,
|
|
36
|
+
force_github: bool = False,
|
|
37
|
+
):
|
|
38
|
+
"""Initialize auto-sync configuration.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
auto_sync_enabled: Master switch for all auto-sync
|
|
42
|
+
sync_on_commit: Auto-sync after commits
|
|
43
|
+
sync_on_checkout: Auto-sync on branch changes
|
|
44
|
+
sync_on_merge: Auto-sync on merge operations
|
|
45
|
+
confirm_before_sync: Ask user before syncing
|
|
46
|
+
force_local: Resolve conflicts by keeping local changes
|
|
47
|
+
force_github: Resolve conflicts by keeping GitHub changes
|
|
48
|
+
"""
|
|
49
|
+
self.auto_sync_enabled = auto_sync_enabled
|
|
50
|
+
self.sync_on_commit = sync_on_commit
|
|
51
|
+
self.sync_on_checkout = sync_on_checkout
|
|
52
|
+
self.sync_on_merge = sync_on_merge
|
|
53
|
+
self.confirm_before_sync = confirm_before_sync
|
|
54
|
+
self.force_local = force_local
|
|
55
|
+
self.force_github = force_github
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict[str, Any]:
|
|
58
|
+
"""Convert to dictionary for storage."""
|
|
59
|
+
return {
|
|
60
|
+
"auto_sync_enabled": self.auto_sync_enabled,
|
|
61
|
+
"sync_on_commit": self.sync_on_commit,
|
|
62
|
+
"sync_on_checkout": self.sync_on_checkout,
|
|
63
|
+
"sync_on_merge": self.sync_on_merge,
|
|
64
|
+
"confirm_before_sync": self.confirm_before_sync,
|
|
65
|
+
"force_local": self.force_local,
|
|
66
|
+
"force_github": self.force_github,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_dict(cls, data: dict[str, Any]) -> "GitHookAutoSyncConfig":
|
|
71
|
+
"""Create from dictionary (loaded from storage)."""
|
|
72
|
+
return cls(
|
|
73
|
+
auto_sync_enabled=data.get("auto_sync_enabled", False),
|
|
74
|
+
sync_on_commit=data.get("sync_on_commit", False),
|
|
75
|
+
sync_on_checkout=data.get("sync_on_checkout", False),
|
|
76
|
+
sync_on_merge=data.get("sync_on_merge", False),
|
|
77
|
+
confirm_before_sync=data.get("confirm_before_sync", True),
|
|
78
|
+
force_local=data.get("force_local", False),
|
|
79
|
+
force_github=data.get("force_github", False),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class GitHookAutoSyncService:
|
|
84
|
+
"""Service for handling automatic GitHub sync on Git events."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, core):
|
|
87
|
+
"""Initialize auto-sync service.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
core: RoadmapCore instance
|
|
91
|
+
"""
|
|
92
|
+
self.core = core
|
|
93
|
+
self.console = get_console()
|
|
94
|
+
self.metadata_service = SyncMetadataService(core)
|
|
95
|
+
self.config = GitHookAutoSyncConfig()
|
|
96
|
+
|
|
97
|
+
def set_config(self, config: GitHookAutoSyncConfig) -> None:
|
|
98
|
+
"""Set auto-sync configuration.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
config: GitHookAutoSyncConfig instance
|
|
102
|
+
"""
|
|
103
|
+
self.config = config
|
|
104
|
+
|
|
105
|
+
def should_sync_on_event(self, event: str) -> bool:
|
|
106
|
+
"""Check if sync should occur for a given event.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
event: Event name ('commit', 'checkout', 'merge')
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if auto-sync is enabled and configured for this event
|
|
113
|
+
"""
|
|
114
|
+
if not self.config.auto_sync_enabled:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
event_map = {
|
|
118
|
+
"commit": self.config.sync_on_commit,
|
|
119
|
+
"checkout": self.config.sync_on_checkout,
|
|
120
|
+
"merge": self.config.sync_on_merge,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return event_map.get(event, False)
|
|
124
|
+
|
|
125
|
+
def auto_sync_on_commit(
|
|
126
|
+
self, commit_sha: str | None = None, confirm: bool = True
|
|
127
|
+
) -> bool:
|
|
128
|
+
"""Automatically sync issues on git commit.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
commit_sha: Git commit SHA (optional, for logging)
|
|
132
|
+
confirm: Whether to ask for confirmation
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if sync completed successfully
|
|
136
|
+
"""
|
|
137
|
+
if not self.should_sync_on_event("commit"):
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
return self._perform_auto_sync(
|
|
141
|
+
event="commit",
|
|
142
|
+
commit_sha=commit_sha,
|
|
143
|
+
confirm=confirm or self.config.confirm_before_sync,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def auto_sync_on_checkout(
|
|
147
|
+
self, branch: str | None = None, confirm: bool = True
|
|
148
|
+
) -> bool:
|
|
149
|
+
"""Automatically sync issues on branch checkout.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
branch: Branch name (optional, for logging)
|
|
153
|
+
confirm: Whether to ask for confirmation
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if sync completed successfully
|
|
157
|
+
"""
|
|
158
|
+
if not self.should_sync_on_event("checkout"):
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
return self._perform_auto_sync(
|
|
162
|
+
event="checkout",
|
|
163
|
+
branch=branch,
|
|
164
|
+
confirm=confirm or self.config.confirm_before_sync,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def auto_sync_on_merge(
|
|
168
|
+
self, commit_sha: str | None = None, confirm: bool = True
|
|
169
|
+
) -> bool:
|
|
170
|
+
"""Automatically sync issues on merge.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
commit_sha: Merge commit SHA (optional, for logging)
|
|
174
|
+
confirm: Whether to ask for confirmation
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
True if sync completed successfully
|
|
178
|
+
"""
|
|
179
|
+
if not self.should_sync_on_event("merge"):
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
return self._perform_auto_sync(
|
|
183
|
+
event="merge",
|
|
184
|
+
commit_sha=commit_sha,
|
|
185
|
+
confirm=confirm or self.config.confirm_before_sync,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _perform_auto_sync(
|
|
189
|
+
self,
|
|
190
|
+
event: str,
|
|
191
|
+
commit_sha: str | None = None,
|
|
192
|
+
branch: str | None = None,
|
|
193
|
+
confirm: bool = True,
|
|
194
|
+
) -> bool:
|
|
195
|
+
"""Perform auto-sync on git event.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
event: Event type ('commit', 'checkout', 'merge')
|
|
199
|
+
commit_sha: Git commit SHA if applicable
|
|
200
|
+
branch: Branch name if applicable
|
|
201
|
+
confirm: Whether to ask for confirmation
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
True if sync completed successfully
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
roadmap_root = Path.cwd()
|
|
208
|
+
|
|
209
|
+
# Resolve GitHub config
|
|
210
|
+
gh_service = GitHubIntegrationService(
|
|
211
|
+
roadmap_root, roadmap_root / ".github/config.json"
|
|
212
|
+
)
|
|
213
|
+
config_result = gh_service.get_github_config()
|
|
214
|
+
|
|
215
|
+
# Handle both tuple (real code) and dict (mocked code) returns
|
|
216
|
+
if isinstance(config_result, tuple):
|
|
217
|
+
owner, repo, token = config_result
|
|
218
|
+
config = {"owner": owner, "repo": repo, "token": token}
|
|
219
|
+
else:
|
|
220
|
+
config = config_result
|
|
221
|
+
|
|
222
|
+
if not config:
|
|
223
|
+
# GitHub not configured - silently skip
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
# Get linked issues
|
|
227
|
+
linked_issues = [
|
|
228
|
+
issue
|
|
229
|
+
for issue in self.core.issues.all()
|
|
230
|
+
if getattr(issue, "github_issue", None)
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
if not linked_issues:
|
|
234
|
+
# No linked issues - nothing to sync
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
# Display what will be synced (brief, non-intrusive)
|
|
238
|
+
self.console.print(f"[dim]🔄 Auto-syncing issues ({event})...[/dim]")
|
|
239
|
+
|
|
240
|
+
# Get GitHub backend for sync
|
|
241
|
+
from roadmap.infrastructure.sync_gateway import SyncGateway
|
|
242
|
+
|
|
243
|
+
backend = SyncGateway.get_sync_backend("github", self.core, config)
|
|
244
|
+
if not backend:
|
|
245
|
+
self.console.print(
|
|
246
|
+
"[yellow] ⚠️ GitHub backend not available - skipping sync[/yellow]"
|
|
247
|
+
)
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
# Create cache orchestrator for actual sync (uses three-way merge)
|
|
251
|
+
from roadmap.core.services.sync.sync_conflict_resolver import (
|
|
252
|
+
SyncConflictResolver,
|
|
253
|
+
)
|
|
254
|
+
from roadmap.core.services.sync.sync_state_comparator import (
|
|
255
|
+
SyncStateComparator,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
orchestrator = SyncGateway.create_sync_cache_orchestrator(
|
|
259
|
+
self.core,
|
|
260
|
+
backend,
|
|
261
|
+
conflict_resolver=SyncConflictResolver(),
|
|
262
|
+
state_comparator=SyncStateComparator(),
|
|
263
|
+
show_progress=False, # Silent mode for hooks
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Run sync detection (dry-run first to preview)
|
|
267
|
+
report = orchestrator.sync_all_issues(
|
|
268
|
+
dry_run=True,
|
|
269
|
+
force_local=self.config.force_local,
|
|
270
|
+
force_remote=self.config.force_github,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if not report.changes:
|
|
274
|
+
self.console.print("[dim] ✓ No changes to sync[/dim]")
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
# Handle conflicts
|
|
278
|
+
if report.conflicts_detected > 0:
|
|
279
|
+
if self.config.force_local:
|
|
280
|
+
self.console.print(
|
|
281
|
+
"[dim] ⚠️ Resolving conflicts with --force-local[/dim]"
|
|
282
|
+
)
|
|
283
|
+
elif self.config.force_github:
|
|
284
|
+
self.console.print(
|
|
285
|
+
"[dim] ⚠️ Resolving conflicts with --force-github[/dim]"
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
# Cannot proceed without conflict resolution
|
|
289
|
+
self.console.print(
|
|
290
|
+
"[yellow] ⚠️ Conflicts detected - skipping sync. "
|
|
291
|
+
"Use 'roadmap sync --force-local' or '--force-remote' to resolve.[/yellow]"
|
|
292
|
+
)
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
# Ask for confirmation if enabled
|
|
296
|
+
if confirm:
|
|
297
|
+
self.console.print(f" • {len(report.changes)} change(s) to apply")
|
|
298
|
+
response = input(" Continue with sync? [y/N]: ").strip().lower() == "y"
|
|
299
|
+
if not response:
|
|
300
|
+
self.console.print("[dim] Sync cancelled[/dim]")
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
# Apply changes (dry-run=False)
|
|
304
|
+
apply_report = orchestrator.sync_all_issues(
|
|
305
|
+
dry_run=False,
|
|
306
|
+
force_local=self.config.force_local,
|
|
307
|
+
force_remote=self.config.force_github,
|
|
308
|
+
show_progress=False,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if apply_report.error:
|
|
312
|
+
self.console.print(f"[red] ✗ Sync failed: {apply_report.error}[/red]")
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
# Success
|
|
316
|
+
self.console.print("[green] ✓ Synced successfully[/green]")
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
# Log error but don't interrupt the git operation
|
|
321
|
+
self.console.print(f"[yellow] ⚠️ Auto-sync error: {str(e)}[/yellow]")
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def get_config(self) -> GitHookAutoSyncConfig:
|
|
325
|
+
"""Get current auto-sync configuration.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Current GitHookAutoSyncConfig
|
|
329
|
+
"""
|
|
330
|
+
return self.config
|
|
331
|
+
|
|
332
|
+
def get_sync_stats(self) -> dict[str, Any]:
|
|
333
|
+
"""Get sync statistics from metadata service.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Dictionary with sync statistics
|
|
337
|
+
"""
|
|
338
|
+
all_issues = self.core.issues.all()
|
|
339
|
+
linked_issues = [
|
|
340
|
+
issue for issue in all_issues if getattr(issue, "github_issue", None)
|
|
341
|
+
]
|
|
342
|
+
return self.metadata_service.get_statistics(linked_issues)
|
|
343
|
+
|
|
344
|
+
def load_config_from_file(self, config_path: Path) -> bool:
|
|
345
|
+
"""Load auto-sync config from file.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
config_path: Path to config file
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if config loaded successfully
|
|
352
|
+
"""
|
|
353
|
+
try:
|
|
354
|
+
import json
|
|
355
|
+
|
|
356
|
+
if not config_path.exists():
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
with open(config_path) as f:
|
|
360
|
+
data = json.load(f)
|
|
361
|
+
|
|
362
|
+
config_data = data.get("auto_sync", {})
|
|
363
|
+
if config_data:
|
|
364
|
+
self.config = GitHookAutoSyncConfig.from_dict(config_data)
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
return False
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.debug("config_load_failed", error=str(e), action="load_config")
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
def save_config_to_file(self, config_path: Path) -> bool:
|
|
373
|
+
"""Save auto-sync config to file.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
config_path: Path to config file
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
True if config saved successfully
|
|
380
|
+
"""
|
|
381
|
+
try:
|
|
382
|
+
import json
|
|
383
|
+
|
|
384
|
+
# Ensure parent directory exists
|
|
385
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
386
|
+
|
|
387
|
+
# Load existing config or create new
|
|
388
|
+
config_dict = {}
|
|
389
|
+
if config_path.exists():
|
|
390
|
+
with open(config_path) as f:
|
|
391
|
+
config_dict = json.load(f)
|
|
392
|
+
|
|
393
|
+
# Update auto_sync section
|
|
394
|
+
config_dict["auto_sync"] = self.config.to_dict()
|
|
395
|
+
|
|
396
|
+
# Save back to file
|
|
397
|
+
with open(config_path, "w") as f:
|
|
398
|
+
json.dump(config_dict, f, indent=2)
|
|
399
|
+
|
|
400
|
+
return True
|
|
401
|
+
except Exception as e:
|
|
402
|
+
logger.debug("config_save_failed", error=str(e), action="save_config")
|
|
403
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package initialization file."""
|