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,455 @@
|
|
|
1
|
+
"""Git hook installation and management for roadmap integration."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from structlog import get_logger
|
|
9
|
+
|
|
10
|
+
from roadmap.adapters.persistence.parser import IssueParser
|
|
11
|
+
from roadmap.core.domain import MilestoneStatus, Status
|
|
12
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
13
|
+
|
|
14
|
+
from .git import GitCommit, GitIntegration
|
|
15
|
+
from .hook_installer import HookInstaller
|
|
16
|
+
from .hook_registry import HookRegistry, HookStatus
|
|
17
|
+
|
|
18
|
+
logger = get_logger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GitHookManager:
|
|
22
|
+
"""Manages Git hooks for automated roadmap integration."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, roadmap_core: RoadmapCore):
|
|
25
|
+
"""Initialize GitHookManager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
roadmap_core: Core roadmap integration instance.
|
|
29
|
+
"""
|
|
30
|
+
self.core = roadmap_core
|
|
31
|
+
self.git_integration = GitIntegration()
|
|
32
|
+
self.hooks_dir = (
|
|
33
|
+
Path(".git/hooks") if self.git_integration.is_git_repository() else None
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def install_hooks(self, hooks: list[str] | None = None) -> bool:
|
|
37
|
+
"""Install roadmap Git hooks.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
hooks: List of hook names to install. If None, installs all available hooks.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if hooks were installed successfully.
|
|
44
|
+
"""
|
|
45
|
+
installer = HookInstaller(self.hooks_dir)
|
|
46
|
+
return installer.install(hooks)
|
|
47
|
+
|
|
48
|
+
def uninstall_hooks(self, hooks: list[str] | None = None) -> bool:
|
|
49
|
+
"""Remove roadmap Git hooks.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
hooks: List of hook names to uninstall. If None, uninstalls all.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if hooks were uninstalled successfully.
|
|
56
|
+
"""
|
|
57
|
+
installer = HookInstaller(self.hooks_dir)
|
|
58
|
+
return installer.uninstall(hooks)
|
|
59
|
+
|
|
60
|
+
def get_hooks_status(self) -> dict[str, dict[str, Any]]:
|
|
61
|
+
"""Get the status of all installed roadmap hooks.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dictionary with hook names as keys and status information as values.
|
|
65
|
+
"""
|
|
66
|
+
if not self.hooks_dir or not self.hooks_dir.exists():
|
|
67
|
+
return {}
|
|
68
|
+
|
|
69
|
+
status = {}
|
|
70
|
+
for hook_name in HookRegistry.get_available_hooks():
|
|
71
|
+
hook_file = self.hooks_dir / hook_name
|
|
72
|
+
status[hook_name] = HookStatus.get_status_dict(hook_name, hook_file)
|
|
73
|
+
|
|
74
|
+
return status
|
|
75
|
+
|
|
76
|
+
def get_hook_config(self) -> dict[str, Any]:
|
|
77
|
+
"""Get the current hook configuration.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Dictionary with hook configuration settings.
|
|
81
|
+
"""
|
|
82
|
+
return {
|
|
83
|
+
"hooks_directory": str(self.hooks_dir) if self.hooks_dir else None,
|
|
84
|
+
"repository_root": str(Path.cwd()),
|
|
85
|
+
"git_repository": self.git_integration.is_git_repository(),
|
|
86
|
+
"available_hooks": HookRegistry.get_available_hooks(),
|
|
87
|
+
"core_initialized": self.core is not None,
|
|
88
|
+
"hooks_status": self.get_hooks_status(),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def handle_post_commit(self):
|
|
92
|
+
"""Handle post-commit hook - log commit info and trigger auto-sync.
|
|
93
|
+
|
|
94
|
+
Note: CI tracking (post-1.0 feature) has been moved to future/ci_tracking.py
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
# Get the latest commit SHA for logging
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True
|
|
100
|
+
)
|
|
101
|
+
latest_commit_sha = result.stdout.strip()
|
|
102
|
+
|
|
103
|
+
if not latest_commit_sha:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Log the commit for basic tracking
|
|
107
|
+
self._log_hook_activity("Commit", latest_commit_sha[:7])
|
|
108
|
+
|
|
109
|
+
# Trigger auto-sync if enabled
|
|
110
|
+
self._trigger_auto_sync_on_commit(latest_commit_sha)
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
# Silent fail to avoid breaking Git operations
|
|
114
|
+
logger.error(
|
|
115
|
+
"handle_post_commit_failed", error=str(e), severity="operational"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def handle_post_checkout(self):
|
|
119
|
+
"""Handle post-checkout hook - track branch changes and trigger auto-sync.
|
|
120
|
+
|
|
121
|
+
Note: Advanced CI branch tracking (post-1.0 feature) has been moved to future/ci_tracking.py
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
# Get current branch
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
["git", "branch", "--show-current"],
|
|
127
|
+
capture_output=True,
|
|
128
|
+
text=True,
|
|
129
|
+
check=True,
|
|
130
|
+
)
|
|
131
|
+
branch_name = result.stdout.strip()
|
|
132
|
+
|
|
133
|
+
if not branch_name:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Log the branch checkout for basic tracking
|
|
137
|
+
self._log_hook_activity("Checkout", branch_name)
|
|
138
|
+
|
|
139
|
+
# Trigger auto-sync if enabled
|
|
140
|
+
self._trigger_auto_sync_on_checkout(branch_name)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
# Silent fail to avoid breaking Git operations
|
|
144
|
+
logger.error(
|
|
145
|
+
"handle_post_checkout_failed", error=str(e), severity="operational"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def handle_pre_push(self):
|
|
149
|
+
"""Handle pre-push hook - basic push notification and trigger auto-sync.
|
|
150
|
+
|
|
151
|
+
Note: Advanced CI automation (post-1.0 feature) has been moved to future/ci_tracking.py
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
# Get current branch
|
|
155
|
+
result = subprocess.run(
|
|
156
|
+
["git", "branch", "--show-current"],
|
|
157
|
+
capture_output=True,
|
|
158
|
+
text=True,
|
|
159
|
+
check=True,
|
|
160
|
+
)
|
|
161
|
+
current_branch = result.stdout.strip()
|
|
162
|
+
|
|
163
|
+
if not current_branch:
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
# Log the push for basic tracking
|
|
167
|
+
self._log_hook_activity("Push", current_branch)
|
|
168
|
+
|
|
169
|
+
# Note: We don't auto-sync on pre-push to avoid blocking the push
|
|
170
|
+
# Users can manually sync before pushing if desired
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
# Silent fail to avoid breaking Git operations
|
|
174
|
+
logger.error("handle_pre_push_failed", error=str(e), severity="operational")
|
|
175
|
+
|
|
176
|
+
def handle_post_merge(self):
|
|
177
|
+
"""Handle post-merge hook - update milestone progress and trigger auto-sync."""
|
|
178
|
+
try:
|
|
179
|
+
# After a merge, check if any milestones should be updated
|
|
180
|
+
self._update_milestone_progress()
|
|
181
|
+
|
|
182
|
+
# Trigger auto-sync if enabled
|
|
183
|
+
try:
|
|
184
|
+
result = subprocess.run(
|
|
185
|
+
["git", "rev-parse", "HEAD"],
|
|
186
|
+
capture_output=True,
|
|
187
|
+
text=True,
|
|
188
|
+
check=True,
|
|
189
|
+
)
|
|
190
|
+
merge_commit_sha = result.stdout.strip()
|
|
191
|
+
self._trigger_auto_sync_on_merge(merge_commit_sha)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.debug("trigger_auto_sync_on_merge_failed", error=str(e))
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(
|
|
197
|
+
"handle_post_merge_failed", error=str(e), severity="operational"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _log_hook_activity(self, activity_type: str, activity_detail: str) -> None:
|
|
201
|
+
"""Log hook activity to .git/roadmap-hooks.log.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
activity_type: Type of activity (Commit, Checkout, Push, etc)
|
|
205
|
+
activity_detail: Details of the activity
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
log_file = Path(".git/roadmap-hooks.log")
|
|
209
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
210
|
+
log_entry = f"{timestamp} - {activity_type}: {activity_detail}\n"
|
|
211
|
+
|
|
212
|
+
with open(log_file, "a") as f:
|
|
213
|
+
f.write(log_entry)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.debug(
|
|
216
|
+
"log_hook_activity_failed", error=str(e)
|
|
217
|
+
) # Silent fail for logging
|
|
218
|
+
|
|
219
|
+
def _update_issue_from_commit(
|
|
220
|
+
self, issue_id: str, commit: GitCommit, progress: float | None = None
|
|
221
|
+
):
|
|
222
|
+
"""Update an issue based on commit information."""
|
|
223
|
+
try:
|
|
224
|
+
issue = self.core.issues.get(issue_id)
|
|
225
|
+
if not issue:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# Update progress if specified
|
|
229
|
+
if progress is not None:
|
|
230
|
+
issue.progress_percentage = min(100.0, max(0.0, progress))
|
|
231
|
+
|
|
232
|
+
# Update status based on commit activity
|
|
233
|
+
if issue.status == Status.TODO and progress and progress > 0:
|
|
234
|
+
issue.status = Status.IN_PROGRESS
|
|
235
|
+
|
|
236
|
+
# Add commit reference to issue history
|
|
237
|
+
if not hasattr(issue, "git_commits"):
|
|
238
|
+
issue.git_commits = []
|
|
239
|
+
|
|
240
|
+
commit_ref = {
|
|
241
|
+
"hash": commit.hash,
|
|
242
|
+
"message": commit.message,
|
|
243
|
+
"date": commit.date.isoformat(),
|
|
244
|
+
"progress": progress,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Avoid duplicates
|
|
248
|
+
if not any(ref["hash"] == commit.hash for ref in issue.git_commits):
|
|
249
|
+
issue.git_commits.append(commit_ref)
|
|
250
|
+
|
|
251
|
+
# Save updated issue
|
|
252
|
+
issue_path = self.core.issues_dir / issue.filename
|
|
253
|
+
IssueParser.save_issue_file(issue, issue_path)
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error(
|
|
257
|
+
"update_issue_from_commit_failed", error=str(e), severity="operational"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def _complete_issue_from_commit(self, issue_id: str, commit: GitCommit):
|
|
261
|
+
"""Mark an issue as completed based on commit message."""
|
|
262
|
+
try:
|
|
263
|
+
issue = self.core.issues.get(issue_id)
|
|
264
|
+
if not issue:
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# Mark as closed if not already
|
|
268
|
+
if issue.status != Status.CLOSED:
|
|
269
|
+
issue.status = Status.CLOSED
|
|
270
|
+
issue.progress_percentage = 100.0
|
|
271
|
+
issue.completed_date = datetime.now(UTC).isoformat()
|
|
272
|
+
|
|
273
|
+
# Add completion commit reference
|
|
274
|
+
if not hasattr(issue, "git_commits"):
|
|
275
|
+
issue.git_commits = []
|
|
276
|
+
|
|
277
|
+
completion_ref = {
|
|
278
|
+
"hash": commit.hash,
|
|
279
|
+
"message": commit.message,
|
|
280
|
+
"date": commit.date.isoformat(),
|
|
281
|
+
"completion": True,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
issue.git_commits.append(completion_ref)
|
|
285
|
+
issue_path = self.core.issues_dir / issue.filename
|
|
286
|
+
IssueParser.save_issue_file(issue, issue_path)
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(
|
|
290
|
+
"complete_issue_from_commit_failed",
|
|
291
|
+
error=str(e),
|
|
292
|
+
severity="operational",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _is_milestone_active(self, milestone) -> bool:
|
|
296
|
+
"""Check if milestone is active.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
milestone: Milestone object to check
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
True if milestone status is active
|
|
303
|
+
"""
|
|
304
|
+
milestone_status = str(milestone.status).lower() if milestone.status else ""
|
|
305
|
+
return milestone_status == "active"
|
|
306
|
+
|
|
307
|
+
def _get_milestone_issues(self, milestone) -> list:
|
|
308
|
+
"""Get all issues in a milestone.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
milestone: Milestone object
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of issues in the milestone
|
|
315
|
+
"""
|
|
316
|
+
return [
|
|
317
|
+
issue
|
|
318
|
+
for issue in self.core.issues.list()
|
|
319
|
+
if issue.milestone == milestone.name
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
def _calculate_milestone_progress(self, milestone_issues: list) -> float:
|
|
323
|
+
"""Calculate progress percentage for milestone.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
milestone_issues: List of issues in the milestone
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Progress percentage (0-100)
|
|
330
|
+
"""
|
|
331
|
+
if not milestone_issues:
|
|
332
|
+
return 0.0
|
|
333
|
+
|
|
334
|
+
total_issues = len(milestone_issues)
|
|
335
|
+
completed_issues = len(
|
|
336
|
+
[i for i in milestone_issues if i.status == Status.CLOSED]
|
|
337
|
+
)
|
|
338
|
+
return (completed_issues / total_issues) * 100
|
|
339
|
+
|
|
340
|
+
def _update_milestone_attributes(self, milestone, progress: float) -> None:
|
|
341
|
+
"""Update milestone attributes based on progress.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
milestone: Milestone object to update
|
|
345
|
+
progress: Progress percentage
|
|
346
|
+
"""
|
|
347
|
+
# Update calculated progress
|
|
348
|
+
if hasattr(milestone, "calculated_progress"):
|
|
349
|
+
milestone.calculated_progress = progress
|
|
350
|
+
|
|
351
|
+
# Auto-complete if 100% done
|
|
352
|
+
if progress >= 100 and milestone.status != MilestoneStatus.CLOSED:
|
|
353
|
+
milestone.status = MilestoneStatus.CLOSED
|
|
354
|
+
if hasattr(milestone, "actual_end_date"):
|
|
355
|
+
milestone.actual_end_date = datetime.now(UTC)
|
|
356
|
+
|
|
357
|
+
def _save_milestone(self, milestone) -> None:
|
|
358
|
+
"""Save updated milestone to storage.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
milestone: Milestone object to save
|
|
362
|
+
"""
|
|
363
|
+
save_method = getattr(self.core, "save_milestone", None)
|
|
364
|
+
if save_method and callable(save_method):
|
|
365
|
+
save_method(milestone)
|
|
366
|
+
|
|
367
|
+
def _update_milestone_progress(self):
|
|
368
|
+
"""Update milestone progress based on completed issues."""
|
|
369
|
+
try:
|
|
370
|
+
milestones = self.core.milestones.list()
|
|
371
|
+
|
|
372
|
+
for milestone in milestones:
|
|
373
|
+
if self._is_milestone_active(milestone):
|
|
374
|
+
milestone_issues = self._get_milestone_issues(milestone)
|
|
375
|
+
|
|
376
|
+
if milestone_issues:
|
|
377
|
+
progress = self._calculate_milestone_progress(milestone_issues)
|
|
378
|
+
self._update_milestone_attributes(milestone, progress)
|
|
379
|
+
self._save_milestone(milestone)
|
|
380
|
+
|
|
381
|
+
except Exception as e:
|
|
382
|
+
logger.error(
|
|
383
|
+
"update_milestone_progress_failed", error=str(e), severity="operational"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _trigger_auto_sync_on_commit(self, commit_sha: str | None = None):
|
|
387
|
+
"""Trigger auto-sync after commit if enabled.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
commit_sha: Git commit SHA for logging
|
|
391
|
+
"""
|
|
392
|
+
self._trigger_auto_sync_operation(
|
|
393
|
+
"auto_sync_on_commit",
|
|
394
|
+
commit_sha=commit_sha,
|
|
395
|
+
confirm=False, # Non-interactive in hooks
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def _trigger_auto_sync_on_checkout(self, branch: str | None = None):
|
|
399
|
+
"""Trigger auto-sync after checkout if enabled.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
branch: Branch name for logging
|
|
403
|
+
"""
|
|
404
|
+
self._trigger_auto_sync_operation(
|
|
405
|
+
"auto_sync_on_checkout",
|
|
406
|
+
branch=branch,
|
|
407
|
+
confirm=False, # Non-interactive in hooks
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _trigger_auto_sync_on_merge(self, commit_sha: str | None = None):
|
|
411
|
+
"""Trigger auto-sync after merge if enabled.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
commit_sha: Merge commit SHA for logging
|
|
415
|
+
"""
|
|
416
|
+
self._trigger_auto_sync_operation(
|
|
417
|
+
"auto_sync_on_merge",
|
|
418
|
+
commit_sha=commit_sha,
|
|
419
|
+
confirm=False, # Non-interactive in hooks
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def _trigger_auto_sync_operation(self, operation: str, **kwargs) -> None:
|
|
423
|
+
"""Execute an auto-sync operation with common error handling.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
operation: The name of the auto-sync method to call
|
|
427
|
+
**kwargs: Arguments to pass to the sync method
|
|
428
|
+
"""
|
|
429
|
+
try:
|
|
430
|
+
from roadmap.core.services.git.git_hook_auto_sync_service import (
|
|
431
|
+
GitHookAutoSyncService,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Load config
|
|
435
|
+
config_path = Path(".roadmap") / "config.json"
|
|
436
|
+
if not config_path.exists():
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
# Initialize service and load config
|
|
440
|
+
service = GitHookAutoSyncService(self.core)
|
|
441
|
+
if not service.load_config_from_file(config_path):
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# Trigger sync if enabled (no confirmation in hooks)
|
|
445
|
+
method = getattr(service, operation, None)
|
|
446
|
+
if method:
|
|
447
|
+
method(**kwargs)
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
# Silent fail to avoid breaking Git operations
|
|
451
|
+
logger.error(
|
|
452
|
+
"trigger_auto_sync_operation_failed",
|
|
453
|
+
error=str(e),
|
|
454
|
+
severity="operational",
|
|
455
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Git repository information retrieval."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.git.git_branch_manager import GitBranchManager
|
|
8
|
+
from roadmap.adapters.git.git_command_executor import GitCommandExecutor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitRepositoryInfo:
|
|
12
|
+
"""Retrieves Git repository information and user details."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, repo_path: Path | None = None):
|
|
15
|
+
"""Initialize repository info manager."""
|
|
16
|
+
self.executor = GitCommandExecutor(repo_path)
|
|
17
|
+
self.branch_manager = GitBranchManager(repo_path)
|
|
18
|
+
|
|
19
|
+
def get_current_user(self) -> str | None:
|
|
20
|
+
"""Get current Git user name."""
|
|
21
|
+
return self.executor.run(["config", "user.name"])
|
|
22
|
+
|
|
23
|
+
def get_current_email(self) -> str | None:
|
|
24
|
+
"""Get current Git user email."""
|
|
25
|
+
return self.executor.run(["config", "user.email"])
|
|
26
|
+
|
|
27
|
+
def get_repository_info(self) -> dict[str, Any]:
|
|
28
|
+
"""Get general repository information.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary with repo metadata: origin_url, github_owner, github_repo,
|
|
32
|
+
current_branch, repo_root, total_commits
|
|
33
|
+
"""
|
|
34
|
+
if not self.executor.is_git_repository():
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
info = {}
|
|
38
|
+
|
|
39
|
+
# Remote origin URL
|
|
40
|
+
origin_url = self.executor.run(["config", "--get", "remote.origin.url"])
|
|
41
|
+
if origin_url:
|
|
42
|
+
info["origin_url"] = origin_url
|
|
43
|
+
|
|
44
|
+
# Try to extract GitHub repo info
|
|
45
|
+
github_match = re.search(r"github\.com[:/]([^/]+)/([^/.]+)", origin_url)
|
|
46
|
+
if github_match:
|
|
47
|
+
info["github_owner"] = github_match.group(1)
|
|
48
|
+
info["github_repo"] = github_match.group(2)
|
|
49
|
+
|
|
50
|
+
# Current branch
|
|
51
|
+
current_branch = self.branch_manager.get_current_branch()
|
|
52
|
+
if current_branch:
|
|
53
|
+
info["current_branch"] = current_branch.name
|
|
54
|
+
|
|
55
|
+
# Repository root
|
|
56
|
+
repo_root = self.executor.run(["rev-parse", "--show-toplevel"])
|
|
57
|
+
if repo_root:
|
|
58
|
+
info["repo_root"] = repo_root
|
|
59
|
+
|
|
60
|
+
# Total commits
|
|
61
|
+
commit_count = self.executor.run(["rev-list", "--count", "HEAD"])
|
|
62
|
+
if commit_count:
|
|
63
|
+
info["total_commits"] = int(commit_count)
|
|
64
|
+
|
|
65
|
+
return info
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Hook installation and removal operations."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from structlog import get_logger
|
|
6
|
+
|
|
7
|
+
from .hook_registry import HookRegistry
|
|
8
|
+
from .hook_script_generator import HookContentGenerator
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HookInstaller:
|
|
14
|
+
"""Handles installation and uninstallation of Git hooks."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, hooks_dir: Path | None):
|
|
17
|
+
"""Initialize with Git hooks directory.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
hooks_dir: Path to .git/hooks directory, or None if not in repo
|
|
21
|
+
"""
|
|
22
|
+
self.hooks_dir = hooks_dir
|
|
23
|
+
|
|
24
|
+
def can_install(self) -> bool:
|
|
25
|
+
"""Check if hooks can be installed in this repository."""
|
|
26
|
+
return self.hooks_dir is not None and self.hooks_dir.exists()
|
|
27
|
+
|
|
28
|
+
def install(self, hooks: list[str] | None = None) -> bool:
|
|
29
|
+
"""Install specified hooks.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
hooks: List of hook names to install, or None for all
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
True if successful
|
|
36
|
+
"""
|
|
37
|
+
if not self.can_install():
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
hooks_to_install = HookRegistry.validate_hooks(hooks)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
for hook_name in hooks_to_install:
|
|
44
|
+
self._install_single_hook(hook_name)
|
|
45
|
+
return True
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error installing hooks: {e}")
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
def uninstall(self, hooks: list[str] | None = None) -> bool:
|
|
51
|
+
"""Uninstall specified hooks (or all if not specified).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
hooks: List of hook names to uninstall, or None for all
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
True if successful
|
|
58
|
+
"""
|
|
59
|
+
if not self.hooks_dir:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
hooks_to_remove = HookRegistry.validate_hooks(hooks)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
for hook_name in hooks_to_remove:
|
|
66
|
+
self._uninstall_single_hook(hook_name)
|
|
67
|
+
return True
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error("hook_uninstall_failed", error=str(e), severity="operational")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def _install_single_hook(self, hook_name: str) -> None:
|
|
73
|
+
"""Install a single hook.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
hook_name: Name of the hook to install
|
|
77
|
+
"""
|
|
78
|
+
if not self.hooks_dir:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
hook_file = self.hooks_dir / hook_name
|
|
82
|
+
content = HookContentGenerator.generate(hook_name)
|
|
83
|
+
|
|
84
|
+
# Write hook file
|
|
85
|
+
hook_file.write_text(content)
|
|
86
|
+
|
|
87
|
+
# Make it executable
|
|
88
|
+
hook_file.chmod(0o755)
|
|
89
|
+
|
|
90
|
+
def _uninstall_single_hook(self, hook_name: str) -> None:
|
|
91
|
+
"""Uninstall a single hook (if it's a roadmap hook).
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
hook_name: Name of the hook to uninstall
|
|
95
|
+
"""
|
|
96
|
+
if not self.hooks_dir:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
hook_file = self.hooks_dir / hook_name
|
|
100
|
+
if not hook_file.exists():
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Only remove if it's our hook
|
|
104
|
+
content = hook_file.read_text()
|
|
105
|
+
if "roadmap-hook" in content:
|
|
106
|
+
hook_file.unlink()
|