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,370 @@
|
|
|
1
|
+
"""Analysis commands for roadmap insights and reporting."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from structlog import get_logger
|
|
8
|
+
|
|
9
|
+
from roadmap.common.console import get_console
|
|
10
|
+
from roadmap.common.logging import verbose_output
|
|
11
|
+
from roadmap.core.services.issue_helpers.issue_filters import IssueQueryService
|
|
12
|
+
from roadmap.core.services.utils.critical_path_calculator import CriticalPathCalculator
|
|
13
|
+
|
|
14
|
+
console = get_console()
|
|
15
|
+
logger = get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
def analysis():
|
|
20
|
+
"""Analysis and insights commands."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@analysis.command("critical-path")
|
|
25
|
+
@click.option(
|
|
26
|
+
"--milestone",
|
|
27
|
+
"-m",
|
|
28
|
+
help="Analyze critical path for specific milestone only",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--include-closed",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="Include closed issues in analysis",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--export",
|
|
37
|
+
type=click.Choice(["json", "csv"]),
|
|
38
|
+
help="Export format instead of displaying ASCII graph",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--output",
|
|
42
|
+
"-o",
|
|
43
|
+
help="Output file path for export",
|
|
44
|
+
)
|
|
45
|
+
@click.pass_context
|
|
46
|
+
@verbose_output
|
|
47
|
+
def critical_path(
|
|
48
|
+
ctx: click.Context,
|
|
49
|
+
milestone: str | None,
|
|
50
|
+
include_closed: bool,
|
|
51
|
+
export: str | None,
|
|
52
|
+
output: str | None,
|
|
53
|
+
):
|
|
54
|
+
"""Analyze critical path of issues and dependencies.
|
|
55
|
+
|
|
56
|
+
Displays the longest dependency chain in your roadmap and identifies
|
|
57
|
+
the issues that have the most impact on project completion.
|
|
58
|
+
|
|
59
|
+
By default, shows an ASCII dependency graph with a summary of key metrics.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
logger.debug(
|
|
63
|
+
"Starting critical path analysis",
|
|
64
|
+
extra={
|
|
65
|
+
"milestone": milestone,
|
|
66
|
+
"include_closed": include_closed,
|
|
67
|
+
"export": export,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
core = _get_core(ctx)
|
|
72
|
+
if not core or not core.is_initialized():
|
|
73
|
+
logger.warning("Roadmap not initialized", severity="operational")
|
|
74
|
+
console.print(
|
|
75
|
+
"❌ Roadmap not initialized. Run 'roadmap init' first.",
|
|
76
|
+
style="bold red",
|
|
77
|
+
)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# Load issues using query service
|
|
81
|
+
logger.debug("Loading issues from query service")
|
|
82
|
+
try:
|
|
83
|
+
query_service = IssueQueryService(core)
|
|
84
|
+
if milestone:
|
|
85
|
+
logger.debug(
|
|
86
|
+
"filtering_issues_for_milestone",
|
|
87
|
+
milestone=milestone,
|
|
88
|
+
)
|
|
89
|
+
issues_domain, filter_desc = query_service.get_filtered_issues(
|
|
90
|
+
milestone=milestone
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
logger.debug("Loading all issues")
|
|
94
|
+
issues_domain, filter_desc = query_service.get_filtered_issues()
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(
|
|
97
|
+
"failed_to_load_issues",
|
|
98
|
+
error=str(e),
|
|
99
|
+
severity="operational",
|
|
100
|
+
exc_info=True,
|
|
101
|
+
)
|
|
102
|
+
console.print(f"❌ Failed to load issues: {str(e)}", style="bold red")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
logger.debug(
|
|
106
|
+
"issues_loaded",
|
|
107
|
+
count=len(issues_domain),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Filter closed issues unless explicitly included
|
|
111
|
+
if not include_closed:
|
|
112
|
+
original_count = len(issues_domain)
|
|
113
|
+
issues_domain = [i for i in issues_domain if i.status != "closed"]
|
|
114
|
+
filtered_count = original_count - len(issues_domain)
|
|
115
|
+
if filtered_count > 0:
|
|
116
|
+
logger.debug(
|
|
117
|
+
"issues_filtered_for_status",
|
|
118
|
+
filtered_count=filtered_count,
|
|
119
|
+
remaining_count=len(issues_domain),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if not issues_domain:
|
|
123
|
+
logger.info(
|
|
124
|
+
"No active issues to analyze",
|
|
125
|
+
extra={"milestone": milestone, "include_closed": include_closed},
|
|
126
|
+
)
|
|
127
|
+
console.print(
|
|
128
|
+
f"ℹ️ No active issues to analyze.{f' In milestone: {milestone}' if milestone else ''}",
|
|
129
|
+
style="yellow",
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# Calculate critical path
|
|
134
|
+
logger.debug(
|
|
135
|
+
"calculating_critical_path",
|
|
136
|
+
issue_count=len(issues_domain),
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
calculator = CriticalPathCalculator()
|
|
140
|
+
result = calculator.calculate_critical_path(issues_domain)
|
|
141
|
+
logger.debug(
|
|
142
|
+
"Critical path calculation complete",
|
|
143
|
+
extra={
|
|
144
|
+
"total_duration": result.total_duration,
|
|
145
|
+
"critical_issues": len(result.critical_issue_ids),
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(
|
|
150
|
+
"critical_path_calculation_failed",
|
|
151
|
+
error=str(e),
|
|
152
|
+
severity="operational",
|
|
153
|
+
exc_info=True,
|
|
154
|
+
)
|
|
155
|
+
console.print(
|
|
156
|
+
f"❌ Critical path calculation failed: {str(e)}", style="bold red"
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# Output results
|
|
161
|
+
logger.debug(
|
|
162
|
+
"outputting_critical_path_results",
|
|
163
|
+
export_format=export or "display",
|
|
164
|
+
)
|
|
165
|
+
if export:
|
|
166
|
+
_export_critical_path(result, issues_domain, export, output)
|
|
167
|
+
else:
|
|
168
|
+
_display_critical_path(result, milestone)
|
|
169
|
+
|
|
170
|
+
logger.info("Critical path analysis completed successfully")
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(
|
|
174
|
+
"critical_path_command_failed",
|
|
175
|
+
error=str(e),
|
|
176
|
+
severity="operational",
|
|
177
|
+
exc_info=True,
|
|
178
|
+
)
|
|
179
|
+
console.print(f"❌ Unexpected error: {str(e)}", style="bold red")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _get_core(ctx: click.Context):
|
|
183
|
+
"""Get RoadmapCore from context.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
ctx: Click context object
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
RoadmapCore instance or None if not available
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
return ctx.obj.get("core") if ctx.obj else None
|
|
193
|
+
except (AttributeError, TypeError) as e:
|
|
194
|
+
logger.warning(
|
|
195
|
+
"failed_to_get_core_from_context",
|
|
196
|
+
error=str(e),
|
|
197
|
+
severity="operational",
|
|
198
|
+
)
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _display_critical_path(result, milestone: str | None = None):
|
|
203
|
+
"""Display critical path as ASCII graph with summary.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
result: CriticalPathResult from calculator
|
|
207
|
+
milestone: Optional milestone name for context
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
logger.debug("Formatting critical path for display")
|
|
211
|
+
from roadmap.adapters.cli.analysis.presenter import CriticalPathPresenter
|
|
212
|
+
|
|
213
|
+
presenter = CriticalPathPresenter()
|
|
214
|
+
output = presenter.format_critical_path(result, milestone)
|
|
215
|
+
console.print(output)
|
|
216
|
+
logger.debug("Critical path displayed successfully")
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(
|
|
219
|
+
"failed_to_format_critical_path_display",
|
|
220
|
+
error=str(e),
|
|
221
|
+
severity="operational",
|
|
222
|
+
exc_info=True,
|
|
223
|
+
)
|
|
224
|
+
console.print(f"❌ Failed to format output: {str(e)}", style="bold red")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _export_critical_path(result, issues: list, format: str, output_path: str | None):
|
|
228
|
+
"""Export critical path data to file or stdout.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
result: CriticalPathResult from calculator
|
|
232
|
+
issues: List of Issue domain objects
|
|
233
|
+
format: Export format ('json' or 'csv')
|
|
234
|
+
output_path: Optional file path for export
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
logger.debug(
|
|
238
|
+
"exporting_critical_path",
|
|
239
|
+
export_format=format,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if format == "json":
|
|
243
|
+
data = _format_json_export(result, issues)
|
|
244
|
+
output_str = json.dumps(data, indent=2, default=str)
|
|
245
|
+
elif format == "csv":
|
|
246
|
+
output_str = _format_csv_export(result, issues)
|
|
247
|
+
else:
|
|
248
|
+
logger.error(
|
|
249
|
+
"unsupported_export_format",
|
|
250
|
+
format=format,
|
|
251
|
+
error=None,
|
|
252
|
+
severity="config_error",
|
|
253
|
+
)
|
|
254
|
+
console.print("❌ Unsupported export format", style="bold red")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
if output_path:
|
|
258
|
+
try:
|
|
259
|
+
path = Path(output_path)
|
|
260
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
path.write_text(output_str)
|
|
262
|
+
logger.info(
|
|
263
|
+
"critical_path_exported",
|
|
264
|
+
output_path=str(path),
|
|
265
|
+
)
|
|
266
|
+
console.print(f"✅ Exported to {path}", style="bold green")
|
|
267
|
+
except OSError as e:
|
|
268
|
+
logger.error(
|
|
269
|
+
"failed_to_write_export_file",
|
|
270
|
+
error=str(e),
|
|
271
|
+
severity="operational",
|
|
272
|
+
exc_info=True,
|
|
273
|
+
)
|
|
274
|
+
console.print(
|
|
275
|
+
f"❌ Failed to write export file: {str(e)}", style="bold red"
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
logger.debug("Outputting export to stdout")
|
|
279
|
+
console.print(output_str)
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.error(
|
|
283
|
+
"critical_path_export_failed",
|
|
284
|
+
error=str(e),
|
|
285
|
+
severity="operational",
|
|
286
|
+
exc_info=True,
|
|
287
|
+
)
|
|
288
|
+
console.print(f"❌ Export failed: {str(e)}", style="bold red")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _format_json_export(result, issues: list) -> dict:
|
|
292
|
+
"""Format critical path data as JSON.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
result: CriticalPathResult from calculator
|
|
296
|
+
issues: List of Issue domain objects
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Dictionary with critical path and summary data
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
logger.debug("Formatting critical path as JSON")
|
|
303
|
+
return {
|
|
304
|
+
"critical_path": [
|
|
305
|
+
{
|
|
306
|
+
"issue_id": node.issue_id,
|
|
307
|
+
"title": node.issue_title,
|
|
308
|
+
"duration_hours": node.duration_hours,
|
|
309
|
+
"is_critical": node.is_critical,
|
|
310
|
+
"slack_time": node.slack_time,
|
|
311
|
+
}
|
|
312
|
+
for node in result.critical_path
|
|
313
|
+
],
|
|
314
|
+
"summary": {
|
|
315
|
+
"total_duration": result.total_duration,
|
|
316
|
+
"critical_issue_count": len(result.critical_issue_ids),
|
|
317
|
+
"blocking_issues": result.blocking_issues,
|
|
318
|
+
"project_end_date": result.project_end_date.isoformat()
|
|
319
|
+
if result.project_end_date
|
|
320
|
+
else None,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
except Exception as e:
|
|
324
|
+
logger.error(
|
|
325
|
+
"json_formatting_failed",
|
|
326
|
+
error=str(e),
|
|
327
|
+
severity="operational",
|
|
328
|
+
exc_info=True,
|
|
329
|
+
)
|
|
330
|
+
raise
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _format_csv_export(result, issues: list) -> str:
|
|
334
|
+
"""Format critical path data as CSV.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
result: CriticalPathResult from calculator
|
|
338
|
+
issues: List of Issue domain objects
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
CSV-formatted string
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
logger.debug("Formatting critical path as CSV")
|
|
345
|
+
lines = [
|
|
346
|
+
"issue_id,title,duration_hours,dependencies,slack_hours,is_critical,status"
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
for issue in issues:
|
|
350
|
+
deps = ",".join(issue.depends_on) if issue.depends_on else ""
|
|
351
|
+
# Find corresponding node
|
|
352
|
+
node = next(
|
|
353
|
+
(n for n in result.critical_path if n.issue_id == issue.id), None
|
|
354
|
+
)
|
|
355
|
+
slack = f"{node.slack_time:.1f}" if node else "N/A"
|
|
356
|
+
critical = "yes" if issue.id in result.critical_issue_ids else "no"
|
|
357
|
+
|
|
358
|
+
lines.append(
|
|
359
|
+
f'"{issue.id}","{issue.title}",{issue.estimated_hours or 0},"{deps}",{slack},{critical},{issue.status}'
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return "\n".join(lines)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.error(
|
|
365
|
+
"csv_formatting_failed",
|
|
366
|
+
error=str(e),
|
|
367
|
+
severity="operational",
|
|
368
|
+
exc_info=True,
|
|
369
|
+
)
|
|
370
|
+
raise
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Presenter for critical path analysis output."""
|
|
2
|
+
|
|
3
|
+
from roadmap.core.services.utils.critical_path_calculator import CriticalPathResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CriticalPathPresenter:
|
|
7
|
+
"""Formats critical path analysis for CLI display."""
|
|
8
|
+
|
|
9
|
+
def format_critical_path(
|
|
10
|
+
self, result: CriticalPathResult, milestone: str | None = None
|
|
11
|
+
) -> str:
|
|
12
|
+
"""Format critical path as ASCII graph with summary.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
result: CriticalPathResult from calculator
|
|
16
|
+
milestone: Optional milestone name for context
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Formatted string with dependency graph and summary
|
|
20
|
+
"""
|
|
21
|
+
lines = []
|
|
22
|
+
|
|
23
|
+
# Title
|
|
24
|
+
title = "🎯 Critical Path Analysis"
|
|
25
|
+
if milestone:
|
|
26
|
+
title += f" - {milestone}"
|
|
27
|
+
lines.append(f"\n{title}")
|
|
28
|
+
lines.append("=" * len(title))
|
|
29
|
+
lines.append("")
|
|
30
|
+
|
|
31
|
+
# Dependency graph
|
|
32
|
+
if result.critical_path:
|
|
33
|
+
lines.extend(self._format_dependency_graph(result.critical_path))
|
|
34
|
+
else:
|
|
35
|
+
lines.append("(No critical path - all issues independent)")
|
|
36
|
+
|
|
37
|
+
lines.append("")
|
|
38
|
+
|
|
39
|
+
# Summary
|
|
40
|
+
lines.extend(self._format_summary(result))
|
|
41
|
+
|
|
42
|
+
return "\n".join(lines)
|
|
43
|
+
|
|
44
|
+
def _format_dependency_graph(self, critical_path: list) -> list[str]:
|
|
45
|
+
"""Format the critical path as an ASCII dependency graph."""
|
|
46
|
+
lines = ["📊 Critical Path (Longest Dependency Chain):", ""]
|
|
47
|
+
|
|
48
|
+
for i, node in enumerate(critical_path):
|
|
49
|
+
# Draw the node
|
|
50
|
+
prefix = " " if i > 0 else ""
|
|
51
|
+
connector = "└─ " if i > 0 else ""
|
|
52
|
+
|
|
53
|
+
duration_str = f"{node.duration_hours:.1f}h"
|
|
54
|
+
slack_str = (
|
|
55
|
+
f"(slack: {node.slack_time:.1f}h)" if node.slack_time > 0 else ""
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
line = f"{prefix}{connector}{node.issue_id}: {node.issue_title[:40]:<40} [{duration_str:>6}] {slack_str}"
|
|
59
|
+
lines.append(line)
|
|
60
|
+
|
|
61
|
+
# Add vertical connector if not last
|
|
62
|
+
if i < len(critical_path) - 1:
|
|
63
|
+
lines.append(" │")
|
|
64
|
+
|
|
65
|
+
return lines
|
|
66
|
+
|
|
67
|
+
def _format_summary(self, result: CriticalPathResult) -> list[str]:
|
|
68
|
+
"""Format the summary section."""
|
|
69
|
+
lines = ["📈 Summary", "─" * 50]
|
|
70
|
+
|
|
71
|
+
# Duration and count
|
|
72
|
+
lines.append(f"Total Duration: {result.total_duration:.1f} hours")
|
|
73
|
+
if result.total_duration >= 8:
|
|
74
|
+
business_days = result.total_duration / 8.0
|
|
75
|
+
lines.append(f" ~{business_days:.1f} business days")
|
|
76
|
+
|
|
77
|
+
lines.append(f"Critical Issues: {len(result.critical_issue_ids)}")
|
|
78
|
+
|
|
79
|
+
# Risk assessment
|
|
80
|
+
risk_level = self._assess_risk(result)
|
|
81
|
+
lines.append(f"Risk Level: {risk_level}")
|
|
82
|
+
|
|
83
|
+
# Top blockers
|
|
84
|
+
if result.blocking_issues:
|
|
85
|
+
blockers = [
|
|
86
|
+
(issue_id, len(blocked))
|
|
87
|
+
for issue_id, blocked in result.blocking_issues.items()
|
|
88
|
+
if len(blocked) > 0
|
|
89
|
+
]
|
|
90
|
+
blockers.sort(key=lambda x: x[1], reverse=True)
|
|
91
|
+
|
|
92
|
+
if blockers:
|
|
93
|
+
lines.append("")
|
|
94
|
+
lines.append("🔴 Top Blockers (most dependent issues):")
|
|
95
|
+
for issue_id, count in blockers[:3]:
|
|
96
|
+
lines.append(
|
|
97
|
+
f" • {issue_id} blocks {count} issue{'s' if count != 1 else ''}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Project end date
|
|
101
|
+
if result.project_end_date:
|
|
102
|
+
date_str = result.project_end_date.strftime("%b %d, %Y")
|
|
103
|
+
lines.append(f"Estimated Complete: {date_str}")
|
|
104
|
+
|
|
105
|
+
return lines
|
|
106
|
+
|
|
107
|
+
def _assess_risk(self, result: CriticalPathResult) -> str:
|
|
108
|
+
"""Assess risk level based on critical path metrics."""
|
|
109
|
+
if not result.critical_issue_ids:
|
|
110
|
+
return "🟢 LOW (no critical path)"
|
|
111
|
+
|
|
112
|
+
# Calculate risk based on slack time and duration
|
|
113
|
+
avg_slack = (
|
|
114
|
+
sum(n.slack_time for n in result.critical_path) / len(result.critical_path)
|
|
115
|
+
if result.critical_path
|
|
116
|
+
else 0
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Heuristic: if average slack is low relative to duration, risk is high
|
|
120
|
+
if avg_slack < 4: # Less than half a business day
|
|
121
|
+
return "🔴 HIGH (minimal slack time)"
|
|
122
|
+
elif avg_slack < 12: # Less than 1.5 business days
|
|
123
|
+
return "🟡 MEDIUM (some slack available)"
|
|
124
|
+
else:
|
|
125
|
+
return "🟢 LOW (adequate slack time)"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Shared utilities for archive and restore CLI commands.
|
|
2
|
+
|
|
3
|
+
Consolidates common error handling and validation patterns across
|
|
4
|
+
archive/restore commands for issues, milestones, and projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
10
|
+
from roadmap.common.console import get_console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_archive_parse_error(
|
|
14
|
+
error: Exception,
|
|
15
|
+
entity_type: str,
|
|
16
|
+
entity_id: str,
|
|
17
|
+
archive_dir: str,
|
|
18
|
+
console: Console | None = None,
|
|
19
|
+
extra_context: dict | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Handle errors when parsing archived entity files.
|
|
22
|
+
|
|
23
|
+
Provides consistent error handling across archive/restore commands.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
error: The exception that occurred
|
|
27
|
+
entity_type: Type of entity ("issue", "milestone", "project")
|
|
28
|
+
entity_id: ID or name of the entity
|
|
29
|
+
archive_dir: Path to the archive directory
|
|
30
|
+
console: Optional console instance (uses default if not provided)
|
|
31
|
+
extra_context: Optional additional context dict to merge with base context
|
|
32
|
+
"""
|
|
33
|
+
if console is None:
|
|
34
|
+
console = get_console()
|
|
35
|
+
|
|
36
|
+
context = {"archive_dir": str(archive_dir)}
|
|
37
|
+
if extra_context:
|
|
38
|
+
context.update(extra_context)
|
|
39
|
+
|
|
40
|
+
handle_cli_error(
|
|
41
|
+
error=error,
|
|
42
|
+
operation=f"parse_archived_{entity_type}",
|
|
43
|
+
entity_type=entity_type,
|
|
44
|
+
entity_id=entity_id,
|
|
45
|
+
context=context,
|
|
46
|
+
fatal=False,
|
|
47
|
+
)
|
|
48
|
+
console.print(f" • {entity_id} (parse error)", style="red") # type: ignore[union-attr]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def handle_restore_parse_error(
|
|
52
|
+
error: Exception,
|
|
53
|
+
entity_type: str,
|
|
54
|
+
entity_id: str,
|
|
55
|
+
archive_dir: str,
|
|
56
|
+
console: Console | None = None,
|
|
57
|
+
extra_context: dict | None = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Handle errors when parsing files during restore.
|
|
60
|
+
|
|
61
|
+
Provides consistent error handling across restore commands.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
error: The exception that occurred
|
|
65
|
+
entity_type: Type of entity ("issue", "milestone", "project")
|
|
66
|
+
entity_id: ID or name of the entity
|
|
67
|
+
archive_dir: Path to the archive directory
|
|
68
|
+
console: Optional console instance (uses default if not provided)
|
|
69
|
+
extra_context: Optional additional context dict to merge with base context
|
|
70
|
+
"""
|
|
71
|
+
if console is None:
|
|
72
|
+
console = get_console()
|
|
73
|
+
|
|
74
|
+
context = {"archive_dir": str(archive_dir)}
|
|
75
|
+
if extra_context:
|
|
76
|
+
context.update(extra_context)
|
|
77
|
+
|
|
78
|
+
handle_cli_error(
|
|
79
|
+
error=error,
|
|
80
|
+
operation=f"restore_{entity_type}",
|
|
81
|
+
entity_type=entity_type,
|
|
82
|
+
entity_id=entity_id,
|
|
83
|
+
context=context,
|
|
84
|
+
fatal=False,
|
|
85
|
+
)
|
|
86
|
+
console.print(f" • {entity_id} (restore error)", style="red") # type: ignore[union-attr]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""CLI command helpers - Consolidated patterns for common CLI operations.
|
|
2
|
+
|
|
3
|
+
Provides decorators and functions to reduce duplication in CLI commands:
|
|
4
|
+
- Initialization checks
|
|
5
|
+
- Entity validation
|
|
6
|
+
- Operation error handling
|
|
7
|
+
- Confirmation dialogs
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import sys
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Any, TypeVar
|
|
14
|
+
|
|
15
|
+
import click # type: ignore[import-not-found]
|
|
16
|
+
from rich.console import Console # type: ignore[import-not-found]
|
|
17
|
+
|
|
18
|
+
from roadmap.common.logging import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def require_initialized(func: Callable) -> Callable:
|
|
27
|
+
"""Require roadmap to be initialized before command execution.
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
@click.command()
|
|
31
|
+
@click.pass_context
|
|
32
|
+
@require_initialized
|
|
33
|
+
def my_command(ctx: click.Context):
|
|
34
|
+
# ctx.obj["core"] is guaranteed to be initialized
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
Exits with error code 1 if not initialized.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@functools.wraps(func)
|
|
41
|
+
def wrapper(ctx: click.Context, *args: Any, **kwargs: Any) -> Any:
|
|
42
|
+
core = ctx.obj.get("core")
|
|
43
|
+
if not core or not core.is_initialized():
|
|
44
|
+
console.print(
|
|
45
|
+
"❌ Roadmap not initialized. Run 'roadmap init' first.",
|
|
46
|
+
style="bold red",
|
|
47
|
+
)
|
|
48
|
+
ctx.exit(1)
|
|
49
|
+
return func(ctx, *args, **kwargs)
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def ensure_entity_exists(
|
|
55
|
+
core: Any, entity_type: str, entity_id: str, entity: Any = None
|
|
56
|
+
) -> Any:
|
|
57
|
+
"""Ensure entity exists, exit with error if not found.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
core: RoadmapCore instance
|
|
61
|
+
entity_type: Type of entity ('issue', 'project', 'milestone')
|
|
62
|
+
entity_id: ID of entity to look up
|
|
63
|
+
entity: Pre-fetched entity (if None, will look up via core)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The entity object
|
|
67
|
+
|
|
68
|
+
Exits with error code 1 if entity not found.
|
|
69
|
+
"""
|
|
70
|
+
if entity is None:
|
|
71
|
+
# Fetch from core
|
|
72
|
+
entity_collection = getattr(core, f"{entity_type}s", None)
|
|
73
|
+
if entity_collection is None:
|
|
74
|
+
console.print(
|
|
75
|
+
f"❌ Invalid entity type: {entity_type}",
|
|
76
|
+
style="bold red",
|
|
77
|
+
)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
entity = entity_collection.get(entity_id)
|
|
80
|
+
|
|
81
|
+
if not entity:
|
|
82
|
+
console.print(
|
|
83
|
+
f"❌ {entity_type.capitalize()} not found: {entity_id}",
|
|
84
|
+
style="bold red",
|
|
85
|
+
)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
return entity
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def confirm_action(prompt: str, default: bool = False, force: bool = False) -> bool:
|
|
92
|
+
"""Prompt user to confirm an action.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
prompt: Confirmation prompt text
|
|
96
|
+
default: Default answer if user just presses Enter
|
|
97
|
+
force: If True, skip prompt and return True (for --force flag)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if user confirms, False otherwise (displays cancellation message)
|
|
101
|
+
"""
|
|
102
|
+
if force:
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
if not click.confirm(prompt, default=default):
|
|
106
|
+
console.print("❌ Cancelled.", style="yellow")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return True
|