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,144 @@
|
|
|
1
|
+
"""Service for issue update operations.
|
|
2
|
+
|
|
3
|
+
Consolidates business logic for updating issues, including building update
|
|
4
|
+
dictionaries from CLI parameters, resolving assignees, and displaying results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
from roadmap.common.errors.exceptions import ValidationError
|
|
9
|
+
from roadmap.common.update_constants import DISPLAYABLE_UPDATE_FIELDS
|
|
10
|
+
from roadmap.core.domain import Priority, Status
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IssueUpdateService:
|
|
14
|
+
"""Service for updating issues with all supporting operations."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, core):
|
|
17
|
+
"""Initialize service with core instance.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
core: RoadmapCore instance
|
|
21
|
+
"""
|
|
22
|
+
self.core = core
|
|
23
|
+
self._console = get_console()
|
|
24
|
+
|
|
25
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
26
|
+
# Update Dictionary Building
|
|
27
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
def build_update_dict(
|
|
30
|
+
self,
|
|
31
|
+
title: str | None = None,
|
|
32
|
+
priority: str | None = None,
|
|
33
|
+
status: str | None = None,
|
|
34
|
+
assignee: str | None = None,
|
|
35
|
+
milestone: str | None = None,
|
|
36
|
+
description: str | None = None,
|
|
37
|
+
estimate: float | None = None,
|
|
38
|
+
) -> dict:
|
|
39
|
+
"""Build update dictionary from provided parameters.
|
|
40
|
+
|
|
41
|
+
Validates and normalizes all update fields, resolving assignees and
|
|
42
|
+
converting enum strings to proper types.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
title: New title (optional)
|
|
46
|
+
priority: New priority as string (optional)
|
|
47
|
+
status: New status as string (optional)
|
|
48
|
+
assignee: New assignee (optional)
|
|
49
|
+
milestone: New milestone (optional)
|
|
50
|
+
description: New description (optional)
|
|
51
|
+
estimate: New estimate in hours (optional)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dictionary of updates ready to apply to an issue
|
|
55
|
+
"""
|
|
56
|
+
updates = {}
|
|
57
|
+
|
|
58
|
+
if title:
|
|
59
|
+
updates["title"] = title
|
|
60
|
+
if priority:
|
|
61
|
+
updates["priority"] = Priority(priority)
|
|
62
|
+
if status:
|
|
63
|
+
updates["status"] = Status(status)
|
|
64
|
+
if assignee is not None:
|
|
65
|
+
assignee_value = self.resolve_assignee_for_update(assignee)
|
|
66
|
+
updates["assignee"] = assignee_value
|
|
67
|
+
if milestone:
|
|
68
|
+
updates["milestone"] = milestone
|
|
69
|
+
if description:
|
|
70
|
+
updates["description"] = description
|
|
71
|
+
if estimate is not None:
|
|
72
|
+
updates["estimated_hours"] = estimate
|
|
73
|
+
|
|
74
|
+
return updates
|
|
75
|
+
|
|
76
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
77
|
+
# Assignee Resolution for Updates
|
|
78
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
def resolve_assignee_for_update(self, assignee: str):
|
|
81
|
+
"""Resolve and validate assignee for update operation.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
assignee: Assignee to resolve
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
- Resolved assignee string
|
|
88
|
+
- None for unassignment (empty string)
|
|
89
|
+
- False if validation failed
|
|
90
|
+
"""
|
|
91
|
+
# Convert empty string to None for proper unassignment
|
|
92
|
+
if assignee == "":
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
# Validate assignee
|
|
96
|
+
is_valid, result = self.core.validate_assignee(assignee)
|
|
97
|
+
if not is_valid:
|
|
98
|
+
raise ValidationError(
|
|
99
|
+
domain_message=f"Assignee validation failed: {result}",
|
|
100
|
+
user_message=f"Invalid assignee: {result}",
|
|
101
|
+
)
|
|
102
|
+
elif result and "Warning:" in result:
|
|
103
|
+
self._console.print(f"⚠️ {result}", style="bold yellow")
|
|
104
|
+
return assignee
|
|
105
|
+
else:
|
|
106
|
+
canonical_assignee = self.core.team.get_canonical_assignee(assignee)
|
|
107
|
+
if canonical_assignee != assignee:
|
|
108
|
+
self._console.print(
|
|
109
|
+
f"🔄 Resolved '{assignee}' to '{canonical_assignee}'",
|
|
110
|
+
style="dim",
|
|
111
|
+
)
|
|
112
|
+
return canonical_assignee
|
|
113
|
+
|
|
114
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
115
|
+
# Display Results
|
|
116
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
def display_update_result(
|
|
119
|
+
self, updated_issue, updates: dict, reason: str | None = None
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Display the results of an issue update.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
updated_issue: The updated issue object
|
|
125
|
+
updates: Dictionary of fields that were updated
|
|
126
|
+
reason: Optional reason/comment for the update
|
|
127
|
+
"""
|
|
128
|
+
self._console.print(
|
|
129
|
+
f"✅ Updated issue: {updated_issue.title}", style="bold green"
|
|
130
|
+
)
|
|
131
|
+
self._console.print(f" ID: {updated_issue.id}", style="cyan")
|
|
132
|
+
|
|
133
|
+
# Show what was updated
|
|
134
|
+
for field, value in updates.items():
|
|
135
|
+
if field == "estimated_hours":
|
|
136
|
+
display_value = updated_issue.estimated_time_display
|
|
137
|
+
self._console.print(f" estimate: {display_value}", style="cyan")
|
|
138
|
+
elif field in DISPLAYABLE_UPDATE_FIELDS:
|
|
139
|
+
# Format enum values to show just the string value
|
|
140
|
+
display_value = value.value if hasattr(value, "value") else value
|
|
141
|
+
self._console.print(f" {field}: {display_value}", style="cyan")
|
|
142
|
+
|
|
143
|
+
if reason:
|
|
144
|
+
self._console.print(f" reason: {reason}", style="dim")
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Service for starting work on issues.
|
|
2
|
+
|
|
3
|
+
Consolidates business logic for updating issue status, setting start dates,
|
|
4
|
+
and optionally creating Git branches.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from roadmap.common.console import get_console
|
|
12
|
+
from roadmap.core.domain import Status
|
|
13
|
+
|
|
14
|
+
logger = structlog.get_logger()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StartIssueService:
|
|
18
|
+
"""Service for starting work on issues."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, core):
|
|
21
|
+
"""Initialize service with core instance.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
core: RoadmapCore instance
|
|
25
|
+
"""
|
|
26
|
+
self.core = core
|
|
27
|
+
self._console = get_console()
|
|
28
|
+
|
|
29
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
30
|
+
# Start Date Parsing
|
|
31
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def parse_start_date(date_str: str | None) -> datetime:
|
|
35
|
+
"""Parse start date from string input.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
date_str: Date string in format YYYY-MM-DD or YYYY-MM-DD HH:MM
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Parsed datetime, or current time if not provided or invalid
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If date string is invalid
|
|
45
|
+
"""
|
|
46
|
+
if not date_str:
|
|
47
|
+
return datetime.now(UTC)
|
|
48
|
+
|
|
49
|
+
# Try full datetime format first
|
|
50
|
+
try:
|
|
51
|
+
return datetime.strptime(date_str, "%Y-%m-%d %H:%M")
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
logger.debug(
|
|
54
|
+
"date_parse_failed",
|
|
55
|
+
date_str=date_str,
|
|
56
|
+
error=str(e),
|
|
57
|
+
action="parse_datetime",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Try date-only format
|
|
61
|
+
try:
|
|
62
|
+
return datetime.strptime(date_str, "%Y-%m-%d")
|
|
63
|
+
except ValueError as e:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Invalid date format: {date_str}. Use YYYY-MM-DD or YYYY-MM-DD HH:MM"
|
|
66
|
+
) from e
|
|
67
|
+
|
|
68
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
69
|
+
# Start Work Workflow
|
|
70
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
def start_work(self, issue_id: str, start_date: datetime) -> object | None:
|
|
73
|
+
"""Update issue to start work.
|
|
74
|
+
|
|
75
|
+
Sets status to IN_PROGRESS, sets actual start date, and initializes
|
|
76
|
+
progress tracking.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
issue_id: ID of issue to start
|
|
80
|
+
start_date: Datetime to set as start time
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Updated issue if successful, None otherwise
|
|
84
|
+
"""
|
|
85
|
+
return self.core.issues.update(
|
|
86
|
+
issue_id,
|
|
87
|
+
actual_start_date=start_date,
|
|
88
|
+
status=Status.IN_PROGRESS,
|
|
89
|
+
progress_percentage=0.0,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
93
|
+
# Branch Creation Decision
|
|
94
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
def should_create_branch(self, git_branch_flag: bool) -> bool:
|
|
97
|
+
"""Determine if git branch should be created based on flag and config.
|
|
98
|
+
|
|
99
|
+
Currently only respects explicit --create-branch flag.
|
|
100
|
+
Auto-creation can be re-enabled via config in the future.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
git_branch_flag: Whether --create-branch flag was passed
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Whether to create a branch
|
|
107
|
+
"""
|
|
108
|
+
if git_branch_flag:
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
# Check config for auto_branch default (disabled for now - can be re-enabled later)
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
115
|
+
# Display Results
|
|
116
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
def display_started(self, issue, start_date: datetime) -> None:
|
|
119
|
+
"""Display success message for started issue.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
issue: Issue that was started
|
|
123
|
+
start_date: Datetime when work started
|
|
124
|
+
"""
|
|
125
|
+
self._console.print(f"🚀 Started work on: {issue.title}", style="bold green")
|
|
126
|
+
self._console.print(
|
|
127
|
+
f" Started: {start_date.strftime('%Y-%m-%d %H:%M')}", style="cyan"
|
|
128
|
+
)
|
|
129
|
+
self._console.print(" Status: In Progress", style="yellow")
|
|
130
|
+
|
|
131
|
+
def display_branch_created(self, branch_name: str, checkout: bool) -> None:
|
|
132
|
+
"""Display git branch creation success.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
branch_name: Name of created branch
|
|
136
|
+
checkout: Whether branch was checked out
|
|
137
|
+
"""
|
|
138
|
+
self._console.print(
|
|
139
|
+
f"🌿 Created Git branch: {branch_name}",
|
|
140
|
+
style="green",
|
|
141
|
+
)
|
|
142
|
+
if checkout:
|
|
143
|
+
self._console.print(
|
|
144
|
+
f"✅ Checked out branch: {branch_name}",
|
|
145
|
+
style="green",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def display_branch_warning(self) -> None:
|
|
149
|
+
"""Display warning when branch creation fails."""
|
|
150
|
+
try:
|
|
151
|
+
status_output = (
|
|
152
|
+
self.core.git._run_git_command(["status", "--porcelain"]) or ""
|
|
153
|
+
)
|
|
154
|
+
if status_output.strip():
|
|
155
|
+
self._console.print(
|
|
156
|
+
"⚠️ Working tree has uncommitted changes — branch creation skipped. Use --force to override.",
|
|
157
|
+
style="yellow",
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
self._console.print(
|
|
161
|
+
"⚠️ Failed to create or checkout branch. See git for details.",
|
|
162
|
+
style="yellow",
|
|
163
|
+
)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.debug(
|
|
166
|
+
"branch_creation_failed",
|
|
167
|
+
operation="create_branch",
|
|
168
|
+
error=str(e),
|
|
169
|
+
action="Showing user message",
|
|
170
|
+
)
|
|
171
|
+
self._console.print(
|
|
172
|
+
"⚠️ Failed to create or checkout branch. See git for details.",
|
|
173
|
+
style="yellow",
|
|
174
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Issue-related helper utilities for filtering, updating, and management.
|
|
2
|
+
|
|
3
|
+
DEPRECATED: This module is deprecated. Import directly from issue_filter_service instead.
|
|
4
|
+
Example: from roadmap.core.services.issue.issue_filter_service import IssueQueryService
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Backward compatibility re-exports
|
|
8
|
+
from roadmap.core.services.issue.issue_filter_service import (
|
|
9
|
+
IssueFilterValidator,
|
|
10
|
+
IssueQueryService,
|
|
11
|
+
WorkloadCalculator,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"IssueFilterValidator",
|
|
16
|
+
"IssueQueryService",
|
|
17
|
+
"WorkloadCalculator",
|
|
18
|
+
]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Issue filtering logic extracted from complex CLI command."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC
|
|
4
|
+
|
|
5
|
+
from roadmap.core.domain import Issue, Priority, Status
|
|
6
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IssueFilterValidator:
|
|
10
|
+
"""Validates filter combinations for issue queries."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def validate_filters(
|
|
14
|
+
assignee: str | None,
|
|
15
|
+
my_issues: bool,
|
|
16
|
+
backlog: bool,
|
|
17
|
+
unassigned: bool,
|
|
18
|
+
next_milestone: bool,
|
|
19
|
+
milestone: str | None,
|
|
20
|
+
) -> tuple[bool, str | None]:
|
|
21
|
+
"""Validate that filter combinations are valid.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Tuple of (is_valid, error_message)
|
|
25
|
+
"""
|
|
26
|
+
# Check for conflicting assignee filters
|
|
27
|
+
assignee_filters = [assignee is not None, my_issues]
|
|
28
|
+
if sum(bool(f) for f in assignee_filters) > 1:
|
|
29
|
+
return False, "Cannot combine --assignee and --my-issues filters"
|
|
30
|
+
|
|
31
|
+
# Check for conflicting milestone filters
|
|
32
|
+
exclusive_filters = [backlog, unassigned, next_milestone, milestone is not None]
|
|
33
|
+
if sum(bool(f) for f in exclusive_filters) > 1:
|
|
34
|
+
return (
|
|
35
|
+
False,
|
|
36
|
+
"Cannot combine --backlog, --unassigned, --next-milestone, and --milestone filters",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return True, None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IssueQueryService:
|
|
43
|
+
"""Service for querying issues with different filter criteria."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, core: RoadmapCore):
|
|
46
|
+
"""Initialize IssueQueryService.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
core: Core roadmap instance.
|
|
50
|
+
"""
|
|
51
|
+
self.core = core
|
|
52
|
+
|
|
53
|
+
def _get_issues_for_assignee_filters(
|
|
54
|
+
self, my_issues: bool, assignee: str | None
|
|
55
|
+
) -> tuple[list[Issue], str] | None:
|
|
56
|
+
"""Get issues for assignee-based filters. Returns None if no match."""
|
|
57
|
+
if my_issues:
|
|
58
|
+
return self.core.team.get_my_issues(), "my"
|
|
59
|
+
if assignee:
|
|
60
|
+
return self.core.team.get_assigned_issues(
|
|
61
|
+
assignee
|
|
62
|
+
), f"assigned to {assignee}"
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def _get_issues_for_collection_filters(
|
|
66
|
+
self,
|
|
67
|
+
backlog: bool,
|
|
68
|
+
unassigned: bool,
|
|
69
|
+
next_milestone: bool,
|
|
70
|
+
milestone: str | None,
|
|
71
|
+
) -> tuple[list[Issue], str] | None:
|
|
72
|
+
"""Get issues for collection-based filters. Returns None if no match."""
|
|
73
|
+
if backlog or unassigned:
|
|
74
|
+
return self.core.issues.get_backlog(), "backlog"
|
|
75
|
+
|
|
76
|
+
if next_milestone:
|
|
77
|
+
next_ms = self.core.milestones.get_next()
|
|
78
|
+
if not next_ms:
|
|
79
|
+
return [], "" # Handled by caller
|
|
80
|
+
return (
|
|
81
|
+
self.core.issues.get_by_milestone(next_ms.name),
|
|
82
|
+
f"next milestone ({next_ms.name})",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if milestone:
|
|
86
|
+
return (
|
|
87
|
+
self.core.issues.get_by_milestone(milestone),
|
|
88
|
+
f"milestone '{milestone}'",
|
|
89
|
+
)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def _get_overdue_issues(self) -> tuple[list[Issue], str]:
|
|
93
|
+
"""Get overdue issues."""
|
|
94
|
+
from datetime import datetime
|
|
95
|
+
|
|
96
|
+
all_issues = self.core.issues.list()
|
|
97
|
+
now = datetime.now(UTC)
|
|
98
|
+
overdue_issues: list[Issue] = []
|
|
99
|
+
for issue in all_issues:
|
|
100
|
+
if not issue.due_date:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
due = issue.due_date
|
|
104
|
+
if isinstance(due, datetime):
|
|
105
|
+
if due.tzinfo:
|
|
106
|
+
due_aware = due.astimezone(UTC)
|
|
107
|
+
else:
|
|
108
|
+
due_aware = due.replace(tzinfo=UTC)
|
|
109
|
+
else:
|
|
110
|
+
due_aware = datetime.fromisoformat(str(due)).replace(tzinfo=UTC)
|
|
111
|
+
|
|
112
|
+
if due_aware < now:
|
|
113
|
+
overdue_issues.append(issue)
|
|
114
|
+
|
|
115
|
+
return overdue_issues, "overdue"
|
|
116
|
+
|
|
117
|
+
def get_filtered_issues(
|
|
118
|
+
self,
|
|
119
|
+
milestone: str | None = None,
|
|
120
|
+
backlog: bool = False,
|
|
121
|
+
unassigned: bool = False,
|
|
122
|
+
next_milestone: bool = False,
|
|
123
|
+
assignee: str | None = None,
|
|
124
|
+
my_issues: bool = False,
|
|
125
|
+
overdue: bool = False,
|
|
126
|
+
) -> tuple[list[Issue], str]:
|
|
127
|
+
"""Get issues based on primary filter criteria.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Tuple of (issues, filter_description)
|
|
131
|
+
"""
|
|
132
|
+
# Try assignee filters first
|
|
133
|
+
result = self._get_issues_for_assignee_filters(my_issues, assignee)
|
|
134
|
+
if result is not None:
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
# Try collection filters
|
|
138
|
+
result = self._get_issues_for_collection_filters(
|
|
139
|
+
backlog, unassigned, next_milestone, milestone
|
|
140
|
+
)
|
|
141
|
+
if result is not None:
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
# Handle overdue filter
|
|
145
|
+
if overdue:
|
|
146
|
+
return self._get_overdue_issues()
|
|
147
|
+
|
|
148
|
+
# Show all issues
|
|
149
|
+
return self.core.issues.list(), "all"
|
|
150
|
+
|
|
151
|
+
def apply_additional_filters(
|
|
152
|
+
self,
|
|
153
|
+
issues: list[Issue],
|
|
154
|
+
filter_description: str,
|
|
155
|
+
open_only: bool = False,
|
|
156
|
+
blocked_only: bool = False,
|
|
157
|
+
status: str | None = None,
|
|
158
|
+
priority: str | None = None,
|
|
159
|
+
issue_type: str | None = None,
|
|
160
|
+
overdue: bool = False,
|
|
161
|
+
) -> tuple[list[Issue], str]:
|
|
162
|
+
"""Apply additional filters to issue list.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Tuple of (filtered_issues, updated_description)
|
|
166
|
+
"""
|
|
167
|
+
result = issues
|
|
168
|
+
description = filter_description
|
|
169
|
+
|
|
170
|
+
# Apply each filter in sequence
|
|
171
|
+
if open_only:
|
|
172
|
+
result, description = self._filter_by_open_status(result, description)
|
|
173
|
+
|
|
174
|
+
if blocked_only:
|
|
175
|
+
result, description = self._filter_by_blocked_status(result, description)
|
|
176
|
+
|
|
177
|
+
if status:
|
|
178
|
+
result, description = self._filter_by_status(result, description, status)
|
|
179
|
+
|
|
180
|
+
if priority:
|
|
181
|
+
result, description = self._filter_by_priority(
|
|
182
|
+
result, description, priority
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if issue_type:
|
|
186
|
+
result, description = self._filter_by_type(result, description, issue_type)
|
|
187
|
+
|
|
188
|
+
if overdue:
|
|
189
|
+
result, description = self._filter_by_overdue(result, description)
|
|
190
|
+
|
|
191
|
+
return result, description
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _filter_by_open_status(
|
|
195
|
+
issues: list[Issue], description: str
|
|
196
|
+
) -> tuple[list[Issue], str]:
|
|
197
|
+
"""Filter to only open issues (not closed)."""
|
|
198
|
+
filtered = [i for i in issues if i.status != Status.CLOSED]
|
|
199
|
+
return filtered, description + " open"
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def _filter_by_blocked_status(
|
|
203
|
+
issues: list[Issue], description: str
|
|
204
|
+
) -> tuple[list[Issue], str]:
|
|
205
|
+
"""Filter to only blocked issues."""
|
|
206
|
+
filtered = [i for i in issues if i.status == Status.BLOCKED]
|
|
207
|
+
return filtered, description + " blocked"
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def _filter_by_status(
|
|
211
|
+
issues: list[Issue], description: str, status: str
|
|
212
|
+
) -> tuple[list[Issue], str]:
|
|
213
|
+
"""Filter by specific status."""
|
|
214
|
+
filtered = [i for i in issues if i.status == Status(status)]
|
|
215
|
+
return filtered, description + f" {status}"
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def _filter_by_priority(
|
|
219
|
+
issues: list[Issue], description: str, priority: str
|
|
220
|
+
) -> tuple[list[Issue], str]:
|
|
221
|
+
"""Filter by priority level."""
|
|
222
|
+
filtered = [i for i in issues if i.priority == Priority(priority)]
|
|
223
|
+
return filtered, description + f" {priority} priority"
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _filter_by_type(
|
|
227
|
+
issues: list[Issue], description: str, issue_type: str
|
|
228
|
+
) -> tuple[list[Issue], str]:
|
|
229
|
+
"""Filter by issue type."""
|
|
230
|
+
from roadmap.core.domain import IssueType
|
|
231
|
+
|
|
232
|
+
filtered = [i for i in issues if i.issue_type == IssueType(issue_type)]
|
|
233
|
+
return filtered, description + f" {issue_type}"
|
|
234
|
+
|
|
235
|
+
@staticmethod
|
|
236
|
+
def _filter_by_overdue(
|
|
237
|
+
issues: list[Issue], description: str
|
|
238
|
+
) -> tuple[list[Issue], str]:
|
|
239
|
+
"""Filter to only overdue issues."""
|
|
240
|
+
from datetime import datetime
|
|
241
|
+
|
|
242
|
+
now = datetime.now(UTC)
|
|
243
|
+
filtered: list[Issue] = []
|
|
244
|
+
for i in issues:
|
|
245
|
+
if not i.due_date:
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
due = i.due_date
|
|
249
|
+
if isinstance(due, datetime):
|
|
250
|
+
if due.tzinfo:
|
|
251
|
+
due_aware = due.astimezone(UTC)
|
|
252
|
+
else:
|
|
253
|
+
due_aware = due.replace(tzinfo=UTC)
|
|
254
|
+
else:
|
|
255
|
+
due_aware = datetime.fromisoformat(str(due)).replace(tzinfo=UTC)
|
|
256
|
+
|
|
257
|
+
if due_aware < now:
|
|
258
|
+
filtered.append(i)
|
|
259
|
+
|
|
260
|
+
return filtered, description + " overdue"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class WorkloadCalculator:
|
|
264
|
+
"""Calculates time estimates and workload breakdowns."""
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def calculate_workload(issues: list[Issue]) -> dict:
|
|
268
|
+
"""Calculate workload statistics for a list of issues.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary with total_hours, status_breakdown
|
|
272
|
+
"""
|
|
273
|
+
total_hours = sum(issue.estimated_hours or 0 for issue in issues)
|
|
274
|
+
|
|
275
|
+
status_counts = {}
|
|
276
|
+
for issue in issues:
|
|
277
|
+
status = issue.status.value
|
|
278
|
+
if status not in status_counts:
|
|
279
|
+
status_counts[status] = {"count": 0, "hours": 0}
|
|
280
|
+
status_counts[status]["count"] += 1
|
|
281
|
+
status_counts[status]["hours"] += issue.estimated_hours or 0
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
"total_hours": total_hours,
|
|
285
|
+
"status_breakdown": status_counts,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def format_time_display(hours: float) -> str:
|
|
290
|
+
"""Format time in hours to human-readable display."""
|
|
291
|
+
if hours < 1:
|
|
292
|
+
return f"{hours * 60:.0f}m"
|
|
293
|
+
elif hours <= 24:
|
|
294
|
+
return f"{hours:.1f}h"
|
|
295
|
+
else:
|
|
296
|
+
return f"{hours / 8:.1f}d"
|