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,52 @@
|
|
|
1
|
+
"""Today command - daily workflow summary."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.presentation.daily_summary_presenter import (
|
|
7
|
+
DailySummaryPresenter,
|
|
8
|
+
)
|
|
9
|
+
from roadmap.adapters.cli.services.daily_summary_service import DailySummaryService
|
|
10
|
+
from roadmap.common.console import get_console
|
|
11
|
+
from roadmap.common.logging import verbose_output
|
|
12
|
+
|
|
13
|
+
console = get_console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command("today")
|
|
17
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show verbose output")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@require_initialized
|
|
20
|
+
@verbose_output
|
|
21
|
+
def today(ctx: click.Context, verbose: bool = False): # noqa: F841
|
|
22
|
+
"""Show your daily workflow summary for the upcoming milestone.
|
|
23
|
+
|
|
24
|
+
Displays your assigned issues for the next upcoming milestone:
|
|
25
|
+
- Current in-progress issues assigned to you
|
|
26
|
+
- Overdue issues
|
|
27
|
+
- High-priority tasks to start
|
|
28
|
+
- Today's completed work
|
|
29
|
+
|
|
30
|
+
Only shows issues assigned to you in the upcoming milestone.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
roadmap today
|
|
34
|
+
"""
|
|
35
|
+
core = ctx.obj["core"]
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Use service to get daily summary data
|
|
39
|
+
service = DailySummaryService(core)
|
|
40
|
+
data = service.get_daily_summary_data()
|
|
41
|
+
|
|
42
|
+
# Use presenter to render the data
|
|
43
|
+
presenter = DailySummaryPresenter()
|
|
44
|
+
presenter.render(data)
|
|
45
|
+
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
console.print(f"❌ {e}", style="bold red")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
console.print(f"❌ Failed to generate daily summary: {e}", style="bold red")
|
|
50
|
+
import traceback
|
|
51
|
+
|
|
52
|
+
console.print(traceback.format_exc(), style="dim")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Shared Click option definitions for CLI commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def verbose_option(f):
|
|
7
|
+
"""Add --verbose / -v flag to a command."""
|
|
8
|
+
return click.option(
|
|
9
|
+
"--verbose",
|
|
10
|
+
"-v",
|
|
11
|
+
is_flag=True,
|
|
12
|
+
help="Show detailed debug information and all health check logs",
|
|
13
|
+
)(f)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def format_option(f):
|
|
17
|
+
"""Add --format / -f option to a command."""
|
|
18
|
+
return click.option(
|
|
19
|
+
"--format",
|
|
20
|
+
"-f",
|
|
21
|
+
type=click.Choice(["plain", "json"], case_sensitive=False),
|
|
22
|
+
default="plain",
|
|
23
|
+
help="Output format (default: plain text)",
|
|
24
|
+
)(f)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def details_option(f):
|
|
28
|
+
"""Add --details flag to a command."""
|
|
29
|
+
return click.option(
|
|
30
|
+
"--details",
|
|
31
|
+
is_flag=True,
|
|
32
|
+
help="Show detailed recommendations and fix commands for each check",
|
|
33
|
+
)(f)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def health_check_options(f):
|
|
37
|
+
"""Add all health check options (--verbose, --details, --format) to a command."""
|
|
38
|
+
return verbose_option(details_option(format_option(f)))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Git repository operations and utilities."""
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Git integration module for enhanced Git workflow support."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from roadmap.core.domain import Issue
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class GitCommit:
|
|
14
|
+
"""Represents a Git commit with roadmap-relevant information."""
|
|
15
|
+
|
|
16
|
+
hash: str
|
|
17
|
+
author: str
|
|
18
|
+
date: datetime
|
|
19
|
+
message: str
|
|
20
|
+
files_changed: list[str]
|
|
21
|
+
insertions: int = 0
|
|
22
|
+
deletions: int = 0
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def short_hash(self) -> str:
|
|
26
|
+
"""Get short commit hash."""
|
|
27
|
+
return self.hash[:8]
|
|
28
|
+
|
|
29
|
+
def extract_roadmap_references(self) -> list[str]:
|
|
30
|
+
"""Extract roadmap issue references from commit message."""
|
|
31
|
+
# Enhanced patterns to support multiple formats:
|
|
32
|
+
# 1. [roadmap:issue-id] or [closes roadmap:issue-id] (existing)
|
|
33
|
+
# 2. fixes #issue-id, closes #issue-id (GitHub/GitLab style)
|
|
34
|
+
# 3. resolves #issue-id, resolve #issue-id
|
|
35
|
+
# 4. addresses #issue-id, refs #issue-id
|
|
36
|
+
# Issue IDs can be hex or alphanumeric
|
|
37
|
+
patterns = [
|
|
38
|
+
# Original roadmap: patterns
|
|
39
|
+
r"\[roadmap:([a-zA-Z0-9]{8,})\]",
|
|
40
|
+
r"\[closes roadmap:([a-zA-Z0-9]{8,})\]",
|
|
41
|
+
r"\[fixes roadmap:([a-zA-Z0-9]{8,})\]",
|
|
42
|
+
r"roadmap:([a-zA-Z0-9]{8,})",
|
|
43
|
+
# GitHub/GitLab style patterns
|
|
44
|
+
r"\b(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+#([a-zA-Z0-9]{8,})\b",
|
|
45
|
+
r"\b(?:addresses?|refs?)\s+#([a-zA-Z0-9]{8,})\b",
|
|
46
|
+
# Simple # references
|
|
47
|
+
r"#([a-f0-9]{8})\b", # Hex issue IDs only for this pattern to avoid false positives
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
references = []
|
|
51
|
+
for pattern in patterns:
|
|
52
|
+
matches = re.findall(pattern, self.message, re.IGNORECASE)
|
|
53
|
+
references.extend(matches)
|
|
54
|
+
|
|
55
|
+
return list(set(references)) # Remove duplicates
|
|
56
|
+
|
|
57
|
+
def extract_progress_info(self) -> float | None:
|
|
58
|
+
"""Extract progress percentage from commit message."""
|
|
59
|
+
# Pattern: [progress:25%] or [progress:25]
|
|
60
|
+
patterns = [
|
|
61
|
+
r"\[progress:(\d+)%?\]",
|
|
62
|
+
r"progress:(\d+)%?",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
for pattern in patterns:
|
|
66
|
+
match = re.search(pattern, self.message, re.IGNORECASE)
|
|
67
|
+
if match:
|
|
68
|
+
return float(match.group(1))
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class GitBranch:
|
|
75
|
+
"""Represents a Git branch with roadmap integration info."""
|
|
76
|
+
|
|
77
|
+
name: str
|
|
78
|
+
current: bool = False
|
|
79
|
+
remote: str | None = None
|
|
80
|
+
last_commit: str | None = None
|
|
81
|
+
|
|
82
|
+
def extract_issue_id(self) -> str | None:
|
|
83
|
+
"""Extract issue ID from branch name patterns."""
|
|
84
|
+
# Common patterns:
|
|
85
|
+
# feature/issue-abc12345-description
|
|
86
|
+
# bugfix/abc12345-fix-login
|
|
87
|
+
# abc12345-new-feature
|
|
88
|
+
patterns = [
|
|
89
|
+
r"(?:feature|bugfix|hotfix)/(?:issue-)?([a-f0-9]{8})",
|
|
90
|
+
r"^([a-f0-9]{8})-",
|
|
91
|
+
r"/([a-f0-9]{8})-",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
for pattern in patterns:
|
|
95
|
+
match = re.search(pattern, self.name, re.IGNORECASE)
|
|
96
|
+
if match:
|
|
97
|
+
return match.group(1)
|
|
98
|
+
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def suggests_issue_type(self) -> str | None:
|
|
102
|
+
"""Suggest issue type based on branch name."""
|
|
103
|
+
if self.name.startswith(("feature/", "feat/")):
|
|
104
|
+
return "feature"
|
|
105
|
+
elif self.name.startswith(("bugfix/", "bug/", "fix/")):
|
|
106
|
+
return "bug"
|
|
107
|
+
elif self.name.startswith(("hotfix/", "urgent/")):
|
|
108
|
+
return "hotfix"
|
|
109
|
+
elif self.name.startswith(("docs/", "doc/")):
|
|
110
|
+
return "documentation"
|
|
111
|
+
elif self.name.startswith(("test/", "tests/")):
|
|
112
|
+
return "testing"
|
|
113
|
+
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class GitIntegration:
|
|
118
|
+
"""Orchestrator for Git integration functionality.
|
|
119
|
+
|
|
120
|
+
Coordinates branch management, commit analysis, and repository operations.
|
|
121
|
+
Delegates to focused manager classes for each responsibility.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, repo_path: Path | None = None, config: object | None = None):
|
|
125
|
+
"""Initialize Git integration orchestrator."""
|
|
126
|
+
from roadmap.adapters.git.git_branch_manager import GitBranchManager
|
|
127
|
+
from roadmap.adapters.git.git_command_executor import GitCommandExecutor
|
|
128
|
+
from roadmap.adapters.git.git_commit_analyzer import GitCommitAnalyzer
|
|
129
|
+
from roadmap.adapters.git.git_repository_info import GitRepositoryInfo
|
|
130
|
+
|
|
131
|
+
self.repo_path = repo_path or Path.cwd()
|
|
132
|
+
self.config = config
|
|
133
|
+
|
|
134
|
+
# Initialize managers
|
|
135
|
+
self.executor = GitCommandExecutor(repo_path)
|
|
136
|
+
self.branch_manager = GitBranchManager(repo_path, config)
|
|
137
|
+
self.commit_analyzer = GitCommitAnalyzer(repo_path)
|
|
138
|
+
self.repo_info = GitRepositoryInfo(repo_path)
|
|
139
|
+
|
|
140
|
+
def is_git_repository(self) -> bool:
|
|
141
|
+
"""Check if current directory is in a Git repository."""
|
|
142
|
+
return self.executor.is_git_repository()
|
|
143
|
+
|
|
144
|
+
def get_current_user(self) -> str | None:
|
|
145
|
+
"""Get current Git user name."""
|
|
146
|
+
return self.repo_info.get_current_user()
|
|
147
|
+
|
|
148
|
+
def get_current_email(self) -> str | None:
|
|
149
|
+
"""Get current Git user email."""
|
|
150
|
+
return self.repo_info.get_current_email()
|
|
151
|
+
|
|
152
|
+
def get_current_branch(self) -> GitBranch | None:
|
|
153
|
+
"""Get information about the current branch."""
|
|
154
|
+
return self.branch_manager.get_current_branch()
|
|
155
|
+
|
|
156
|
+
def get_all_branches(self) -> list[GitBranch]:
|
|
157
|
+
"""Get all local branches."""
|
|
158
|
+
return self.branch_manager.get_all_branches()
|
|
159
|
+
|
|
160
|
+
def get_recent_commits(
|
|
161
|
+
self, count: int = 10, since: str | None = None
|
|
162
|
+
) -> list[GitCommit]:
|
|
163
|
+
"""Get recent commits with detailed information."""
|
|
164
|
+
return self.commit_analyzer.get_recent_commits(count, since)
|
|
165
|
+
|
|
166
|
+
def get_commits_for_issue(
|
|
167
|
+
self, issue_id: str, since: str | None = None
|
|
168
|
+
) -> list[GitCommit]:
|
|
169
|
+
"""Get all commits that reference a specific issue."""
|
|
170
|
+
return self.commit_analyzer.get_commits_for_issue(issue_id, since)
|
|
171
|
+
|
|
172
|
+
def suggest_branch_name(self, issue: Issue) -> str:
|
|
173
|
+
"""Suggest a branch name based on issue information."""
|
|
174
|
+
return self.branch_manager.suggest_branch_name(issue)
|
|
175
|
+
|
|
176
|
+
def create_branch_for_issue(
|
|
177
|
+
self, issue: Issue, checkout: bool = True, force: bool = False
|
|
178
|
+
) -> bool:
|
|
179
|
+
"""Create a new branch for an issue."""
|
|
180
|
+
return self.branch_manager.create_branch_for_issue(issue, checkout, force)
|
|
181
|
+
|
|
182
|
+
def auto_create_issue_from_branch(
|
|
183
|
+
self, roadmap_core, branch_name: str | None = None
|
|
184
|
+
) -> str | None:
|
|
185
|
+
"""Automatically create an issue from a branch name if one doesn't exist."""
|
|
186
|
+
return self.branch_manager.auto_create_issue_from_branch(
|
|
187
|
+
roadmap_core, branch_name
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def get_repository_info(self) -> dict[str, Any]:
|
|
191
|
+
"""Get general repository information."""
|
|
192
|
+
return self.repo_info.get_repository_info()
|
|
193
|
+
|
|
194
|
+
def parse_commit_message_for_updates(self, commit: GitCommit) -> dict[str, Any]:
|
|
195
|
+
"""Parse commit message for roadmap updates."""
|
|
196
|
+
return self.commit_analyzer.parse_commit_message_for_updates(commit)
|
|
197
|
+
|
|
198
|
+
def auto_update_issues_from_commits(
|
|
199
|
+
self, roadmap_core, commits: list[GitCommit] | None = None
|
|
200
|
+
) -> dict[str, list[str]]:
|
|
201
|
+
"""Automatically update issues based on commit messages."""
|
|
202
|
+
return self.commit_analyzer.auto_update_issues_from_commits(
|
|
203
|
+
roadmap_core, commits
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def get_branch_linked_issues(self, branch_name: str) -> list[str]:
|
|
207
|
+
"""Get issue IDs linked to a specific branch."""
|
|
208
|
+
return self.branch_manager.get_branch_linked_issues(branch_name)
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Git branch management and issue linking."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from structlog import get_logger
|
|
7
|
+
|
|
8
|
+
from roadmap.adapters.git.git import GitBranch
|
|
9
|
+
from roadmap.adapters.git.git_command_executor import GitCommandExecutor
|
|
10
|
+
|
|
11
|
+
logger = get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GitBranchManager:
|
|
15
|
+
"""Manages Git branches and links them to roadmap issues."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, repo_path: Path | None = None, config: object | None = None):
|
|
18
|
+
"""Initialize branch manager."""
|
|
19
|
+
self.executor = GitCommandExecutor(repo_path)
|
|
20
|
+
self.repo_path = repo_path or Path.cwd()
|
|
21
|
+
self.config = config
|
|
22
|
+
|
|
23
|
+
def get_current_branch(self) -> GitBranch | None:
|
|
24
|
+
"""Get information about the current branch."""
|
|
25
|
+
branch_name = self.executor.run(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
26
|
+
if not branch_name or branch_name == "HEAD":
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
# Get remote tracking branch
|
|
30
|
+
remote = self.executor.run(
|
|
31
|
+
["rev-parse", "--abbrev-ref", f"{branch_name}@{{upstream}}"]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Get last commit
|
|
35
|
+
last_commit = self.executor.run(["rev-parse", "HEAD"])
|
|
36
|
+
|
|
37
|
+
return GitBranch(
|
|
38
|
+
name=branch_name, current=True, remote=remote, last_commit=last_commit
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def get_all_branches(self) -> list[GitBranch]:
|
|
42
|
+
"""Get all local branches."""
|
|
43
|
+
output = self.executor.run(["branch", "--format=%(refname:short)|%(HEAD)"])
|
|
44
|
+
if not output:
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
branches = []
|
|
48
|
+
for line in output.split("\n"):
|
|
49
|
+
if "|" not in line:
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
name, head_marker = line.split("|", 1)
|
|
53
|
+
is_current = head_marker.strip() == "*"
|
|
54
|
+
|
|
55
|
+
branches.append(GitBranch(name=name.strip(), current=is_current))
|
|
56
|
+
|
|
57
|
+
return branches
|
|
58
|
+
|
|
59
|
+
def _generate_title_slug(self, title: str) -> str:
|
|
60
|
+
"""Generate slug from issue title.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
title: Issue title to slugify
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Slug suitable for branch name
|
|
67
|
+
"""
|
|
68
|
+
title_slug = re.sub(r"[^a-zA-Z0-9\s-]", "", title.lower())
|
|
69
|
+
title_slug = re.sub(r"\s+", "-", title_slug)
|
|
70
|
+
return title_slug[:40] # Limit length
|
|
71
|
+
|
|
72
|
+
def _determine_prefix(self, issue) -> str:
|
|
73
|
+
"""Determine branch prefix based on issue type/priority.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
issue: Issue to inspect
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Prefix for branch name
|
|
80
|
+
"""
|
|
81
|
+
prefix = "feature"
|
|
82
|
+
if hasattr(issue, "issue_type"):
|
|
83
|
+
if issue.issue_type == "bug":
|
|
84
|
+
prefix = "bugfix"
|
|
85
|
+
elif issue.issue_type == "documentation":
|
|
86
|
+
prefix = "docs"
|
|
87
|
+
elif issue.priority.value == "critical":
|
|
88
|
+
prefix = "hotfix"
|
|
89
|
+
return prefix
|
|
90
|
+
|
|
91
|
+
def _get_branch_template(self) -> str | None:
|
|
92
|
+
"""Get branch name template from config.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Template string or None
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
if self.config:
|
|
99
|
+
defaults = getattr(self.config, "defaults", None)
|
|
100
|
+
if defaults and hasattr(defaults, "get"):
|
|
101
|
+
return defaults.get("branch_name_template")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.debug("get_branch_name_template_failed", error=str(e))
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _format_branch_name(
|
|
107
|
+
self, issue_id: int, slug: str, prefix: str, template: str | None
|
|
108
|
+
) -> str:
|
|
109
|
+
"""Format branch name using template or defaults.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
issue_id: Issue ID
|
|
113
|
+
slug: Title slug
|
|
114
|
+
prefix: Branch prefix
|
|
115
|
+
template: Template string or None
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Formatted branch name
|
|
119
|
+
"""
|
|
120
|
+
if template:
|
|
121
|
+
try:
|
|
122
|
+
return template.format(id=issue_id, slug=slug, prefix=prefix)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.debug(
|
|
125
|
+
"format_branch_name_template_failed",
|
|
126
|
+
template=template,
|
|
127
|
+
error=str(e),
|
|
128
|
+
severity="operational",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return f"{prefix}/{issue_id}-{slug}"
|
|
132
|
+
|
|
133
|
+
def suggest_branch_name(self, issue) -> str:
|
|
134
|
+
"""Suggest a branch name based on issue information."""
|
|
135
|
+
title_slug = self._generate_title_slug(issue.title)
|
|
136
|
+
prefix = self._determine_prefix(issue)
|
|
137
|
+
template = self._get_branch_template()
|
|
138
|
+
return self._format_branch_name(issue.id, title_slug, prefix, template)
|
|
139
|
+
|
|
140
|
+
def _check_working_tree_clean(self, force: bool) -> bool:
|
|
141
|
+
"""Check if working tree has uncommitted changes.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
force: Skip dirty working tree check
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if working tree is clean or force is True
|
|
148
|
+
"""
|
|
149
|
+
if force:
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
status_output = self.executor.run(["status", "--porcelain"]) or ""
|
|
153
|
+
status_lines = [line for line in status_output.splitlines() if line.strip()]
|
|
154
|
+
has_substantive_changes = any(
|
|
155
|
+
not line.startswith("??") for line in status_lines
|
|
156
|
+
)
|
|
157
|
+
return not has_substantive_changes
|
|
158
|
+
|
|
159
|
+
def _branch_already_exists(self, branch_name: str) -> bool:
|
|
160
|
+
"""Check if branch already exists.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
branch_name: Name of branch to check
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
True if branch exists
|
|
167
|
+
"""
|
|
168
|
+
existing = self.executor.run(["rev-parse", "--verify", branch_name])
|
|
169
|
+
if not existing:
|
|
170
|
+
existing = self.executor.run(
|
|
171
|
+
["rev-parse", "--verify", f"refs/heads/{branch_name}"]
|
|
172
|
+
)
|
|
173
|
+
return existing is not None
|
|
174
|
+
|
|
175
|
+
def _handle_existing_branch(self, branch_name: str, checkout: bool) -> bool:
|
|
176
|
+
"""Handle existing branch (checkout or skip).
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
branch_name: Name of existing branch
|
|
180
|
+
checkout: Whether to checkout the branch
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
True if operation succeeded
|
|
184
|
+
"""
|
|
185
|
+
if checkout:
|
|
186
|
+
co = self.executor.run(["checkout", branch_name])
|
|
187
|
+
return co is not None
|
|
188
|
+
else:
|
|
189
|
+
return True
|
|
190
|
+
|
|
191
|
+
def _get_current_branch(self) -> str | None:
|
|
192
|
+
"""Get current branch name.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Current branch name or None
|
|
196
|
+
"""
|
|
197
|
+
return self.executor.run(["rev-parse", "--abbrev-ref", "HEAD"]) or None
|
|
198
|
+
|
|
199
|
+
def _create_and_checkout_branch(self, branch_name: str, checkout: bool) -> bool:
|
|
200
|
+
"""Create new branch and optionally return to previous branch.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
branch_name: Name of new branch
|
|
204
|
+
checkout: Whether to stay on new branch
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if successful
|
|
208
|
+
"""
|
|
209
|
+
current_branch = self._get_current_branch()
|
|
210
|
+
|
|
211
|
+
result = self.executor.run(["checkout", "-b", branch_name])
|
|
212
|
+
if result is None:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
if not checkout:
|
|
216
|
+
if current_branch:
|
|
217
|
+
self.executor.run(["checkout", current_branch])
|
|
218
|
+
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
def create_branch_for_issue(
|
|
222
|
+
self, issue, checkout: bool = True, force: bool = False
|
|
223
|
+
) -> bool:
|
|
224
|
+
"""Create a new branch for an issue.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
issue: Issue object with id and title
|
|
228
|
+
checkout: Whether to checkout the new branch
|
|
229
|
+
force: Skip dirty working tree check
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if successful, False otherwise
|
|
233
|
+
"""
|
|
234
|
+
if not self.executor.is_git_repository():
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
branch_name = self.suggest_branch_name(issue)
|
|
238
|
+
|
|
239
|
+
if not self._check_working_tree_clean(force):
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
if self._branch_already_exists(branch_name):
|
|
243
|
+
return self._handle_existing_branch(branch_name, checkout)
|
|
244
|
+
|
|
245
|
+
return self._create_and_checkout_branch(branch_name, checkout)
|
|
246
|
+
|
|
247
|
+
def _check_existing_issue_for_branch(
|
|
248
|
+
self, branch_name: str, roadmap_core
|
|
249
|
+
) -> str | None:
|
|
250
|
+
"""Check if branch already has associated issue.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
branch_name: Branch name
|
|
254
|
+
roadmap_core: RoadmapCore instance
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
None if no existing issue
|
|
258
|
+
"""
|
|
259
|
+
branch = GitBranch(branch_name)
|
|
260
|
+
existing_issue_id = branch.extract_issue_id()
|
|
261
|
+
if existing_issue_id and roadmap_core.issues.get(existing_issue_id):
|
|
262
|
+
return existing_issue_id
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def _build_auto_created_issue_data(
|
|
266
|
+
self, branch_name: str, roadmap_core
|
|
267
|
+
) -> dict | None:
|
|
268
|
+
"""Build issue data from branch name.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
branch_name: Branch name
|
|
272
|
+
roadmap_core: RoadmapCore instance
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Issue data dict or None if can't extract
|
|
276
|
+
"""
|
|
277
|
+
branch = GitBranch(branch_name)
|
|
278
|
+
issue_type = branch.suggests_issue_type() or "feature"
|
|
279
|
+
title = self._extract_title_from_branch_name(branch_name)
|
|
280
|
+
if not title:
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
assignee = self._get_current_user(roadmap_core) or "Unknown"
|
|
284
|
+
content = f"Auto-created from branch: `{branch_name}`\n\nThis issue was automatically created when switching to the branch `{branch_name}`."
|
|
285
|
+
|
|
286
|
+
issue_data = {
|
|
287
|
+
"title": title,
|
|
288
|
+
"content": content,
|
|
289
|
+
"assignee": assignee,
|
|
290
|
+
"priority": "medium",
|
|
291
|
+
"status": "in_progress",
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if hasattr(roadmap_core.issues, "create_with_type"):
|
|
295
|
+
issue_data["issue_type"] = issue_type
|
|
296
|
+
|
|
297
|
+
return issue_data
|
|
298
|
+
|
|
299
|
+
def auto_create_issue_from_branch(
|
|
300
|
+
self, roadmap_core, branch_name: str | None = None
|
|
301
|
+
) -> str | None:
|
|
302
|
+
"""Automatically create an issue from a branch name if one doesn't exist.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
roadmap_core: RoadmapCore instance for issue operations
|
|
306
|
+
branch_name: Branch name to analyze. If None, uses current branch.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Issue ID if created, None if not created or already exists
|
|
310
|
+
"""
|
|
311
|
+
if branch_name is None:
|
|
312
|
+
current_branch = self.get_current_branch()
|
|
313
|
+
if not current_branch:
|
|
314
|
+
return None
|
|
315
|
+
branch_name = current_branch.name
|
|
316
|
+
|
|
317
|
+
# Don't create issues for main branches
|
|
318
|
+
if branch_name in ["main", "master", "develop", "dev"]:
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
# Check if issue already exists
|
|
322
|
+
if self._check_existing_issue_for_branch(branch_name, roadmap_core):
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
# Build issue data
|
|
326
|
+
issue_data = self._build_auto_created_issue_data(branch_name, roadmap_core)
|
|
327
|
+
if not issue_data:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
# Create the issue
|
|
331
|
+
try:
|
|
332
|
+
issue = roadmap_core.issues.create(**issue_data)
|
|
333
|
+
return issue.id
|
|
334
|
+
except Exception as e:
|
|
335
|
+
logger.error(
|
|
336
|
+
"auto_issue_creation_failed", error=str(e), severity="operational"
|
|
337
|
+
)
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
def _extract_title_from_branch_name(self, branch_name: str) -> str | None:
|
|
341
|
+
"""Extract a readable title from a branch name."""
|
|
342
|
+
# Remove common prefixes
|
|
343
|
+
name = branch_name
|
|
344
|
+
prefixes = [
|
|
345
|
+
"feature/",
|
|
346
|
+
"bugfix/",
|
|
347
|
+
"hotfix/",
|
|
348
|
+
"docs/",
|
|
349
|
+
"test/",
|
|
350
|
+
"feat/",
|
|
351
|
+
"bug/",
|
|
352
|
+
"fix/",
|
|
353
|
+
]
|
|
354
|
+
for prefix in prefixes:
|
|
355
|
+
if name.startswith(prefix):
|
|
356
|
+
name = name[len(prefix) :]
|
|
357
|
+
break
|
|
358
|
+
|
|
359
|
+
# Remove issue ID if present
|
|
360
|
+
name = re.sub(r"^[a-f0-9]{8}-", "", name)
|
|
361
|
+
name = re.sub(r"^issue-[a-f0-9]{8}-", "", name)
|
|
362
|
+
|
|
363
|
+
# Replace hyphens/underscores with spaces and title case
|
|
364
|
+
name = re.sub(r"[-_]", " ", name)
|
|
365
|
+
name = name.strip()
|
|
366
|
+
|
|
367
|
+
if not name:
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
# Title case
|
|
371
|
+
words = name.split()
|
|
372
|
+
title_words = []
|
|
373
|
+
for word in words:
|
|
374
|
+
if len(word) > 3: # Title case for longer words
|
|
375
|
+
title_words.append(word.capitalize())
|
|
376
|
+
else: # Keep short words lowercase unless first word
|
|
377
|
+
title_words.append(word.lower() if title_words else word.capitalize())
|
|
378
|
+
|
|
379
|
+
return " ".join(title_words)
|
|
380
|
+
|
|
381
|
+
def _get_current_user(self, roadmap_core) -> str | None:
|
|
382
|
+
"""Get current Git user name."""
|
|
383
|
+
return self.executor.run(["config", "user.name"])
|
|
384
|
+
|
|
385
|
+
def get_branch_linked_issues(self, branch_name: str) -> list[str]:
|
|
386
|
+
"""Get issue IDs linked to a specific branch."""
|
|
387
|
+
try:
|
|
388
|
+
# Create a GitBranch object and extract issue ID
|
|
389
|
+
branch = GitBranch(branch_name)
|
|
390
|
+
issue_id = branch.extract_issue_id()
|
|
391
|
+
|
|
392
|
+
if issue_id:
|
|
393
|
+
return [issue_id]
|
|
394
|
+
else:
|
|
395
|
+
return []
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.debug(
|
|
398
|
+
"branch_issue_extraction_failed", branch_name=branch_name, error=str(e)
|
|
399
|
+
)
|
|
400
|
+
return []
|