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,263 @@
|
|
|
1
|
+
"""Issue-specific health scanner extracted from EntityHealthScanner.
|
|
2
|
+
|
|
3
|
+
This contains the logic that was previously embedded inside
|
|
4
|
+
`entity_health_scanner.EntityHealthScanner` for scanning `Issue` objects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from roadmap.common.constants import Status
|
|
10
|
+
from roadmap.common.logging import get_logger
|
|
11
|
+
from roadmap.common.observability.instrumentation import traced
|
|
12
|
+
from roadmap.core.domain.comment import Comment
|
|
13
|
+
from roadmap.core.domain.issue import Issue
|
|
14
|
+
from roadmap.core.services.health.health_models import (
|
|
15
|
+
EntityHealthReport,
|
|
16
|
+
EntityType,
|
|
17
|
+
HealthIssue,
|
|
18
|
+
HealthSeverity,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IssueHealthScanner:
|
|
25
|
+
"""Scan a single issue for health problems (extracted implementations)."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, core: Any = None):
|
|
28
|
+
"""Initialize IssueHealthScanner.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
core: Core roadmap instance.
|
|
32
|
+
"""
|
|
33
|
+
self.core = core
|
|
34
|
+
|
|
35
|
+
@traced("scan_issue")
|
|
36
|
+
def scan_issue(self, issue: Issue) -> EntityHealthReport:
|
|
37
|
+
"""Scan an issue and return its health report.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
issue: Issue to scan for health problems.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Health report for the scanned issue.
|
|
44
|
+
"""
|
|
45
|
+
report = EntityHealthReport(
|
|
46
|
+
entity_id=issue.id,
|
|
47
|
+
entity_type=EntityType.ISSUE,
|
|
48
|
+
entity_title=issue.title,
|
|
49
|
+
status=issue.status.value,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self._check_issue_description(issue, report)
|
|
53
|
+
self._check_issue_comments(issue, report)
|
|
54
|
+
self._check_issue_estimates(issue, report)
|
|
55
|
+
self._check_issue_dates(issue, report)
|
|
56
|
+
self._check_issue_assignee(issue, report)
|
|
57
|
+
self._check_issue_progress(issue, report)
|
|
58
|
+
self._check_issue_status_consistency(issue, report)
|
|
59
|
+
|
|
60
|
+
return report
|
|
61
|
+
|
|
62
|
+
def _check_issue_description(self, issue: Issue, report: EntityHealthReport):
|
|
63
|
+
if not issue.content or not issue.content.strip():
|
|
64
|
+
report.issues.append(
|
|
65
|
+
HealthIssue(
|
|
66
|
+
code="missing_description",
|
|
67
|
+
message="Issue has no description or content",
|
|
68
|
+
severity=HealthSeverity.INFO,
|
|
69
|
+
category="content",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def _check_issue_comments(self, issue: Issue, report: EntityHealthReport):
|
|
74
|
+
if not issue.comments:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
errors = self._validate_comment_thread(issue.comments)
|
|
78
|
+
for error_msg in errors:
|
|
79
|
+
if "Duplicate" in error_msg:
|
|
80
|
+
code = "duplicate_comment_id"
|
|
81
|
+
severity = HealthSeverity.ERROR
|
|
82
|
+
elif "circular" in error_msg.lower():
|
|
83
|
+
code = "circular_comment_reference"
|
|
84
|
+
severity = HealthSeverity.ERROR
|
|
85
|
+
elif "datetime" in error_msg:
|
|
86
|
+
code = "invalid_comment_datetime"
|
|
87
|
+
severity = HealthSeverity.ERROR
|
|
88
|
+
elif "Empty" in error_msg:
|
|
89
|
+
code = "empty_comment_body"
|
|
90
|
+
severity = HealthSeverity.WARNING
|
|
91
|
+
elif "author" in error_msg.lower():
|
|
92
|
+
code = "missing_comment_author"
|
|
93
|
+
severity = HealthSeverity.WARNING
|
|
94
|
+
else:
|
|
95
|
+
code = "invalid_comment"
|
|
96
|
+
severity = HealthSeverity.WARNING
|
|
97
|
+
|
|
98
|
+
report.issues.append(
|
|
99
|
+
HealthIssue(
|
|
100
|
+
code=code,
|
|
101
|
+
message=error_msg,
|
|
102
|
+
severity=severity,
|
|
103
|
+
category="content",
|
|
104
|
+
details={"comment_count": len(issue.comments)},
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _check_issue_estimates(self, issue: Issue, report: EntityHealthReport):
|
|
109
|
+
if issue.status not in (Status.IN_PROGRESS, Status.CLOSED):
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if issue.estimated_hours is None:
|
|
113
|
+
report.issues.append(
|
|
114
|
+
HealthIssue(
|
|
115
|
+
code="missing_estimate",
|
|
116
|
+
message=f"Issue is {issue.status.value} but has no time estimate",
|
|
117
|
+
severity=HealthSeverity.INFO,
|
|
118
|
+
category="structure",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
elif issue.estimated_hours <= 0:
|
|
122
|
+
report.issues.append(
|
|
123
|
+
HealthIssue(
|
|
124
|
+
code="invalid_estimate",
|
|
125
|
+
message=f"Estimated hours must be positive, got {issue.estimated_hours}",
|
|
126
|
+
severity=HealthSeverity.WARNING,
|
|
127
|
+
category="structure",
|
|
128
|
+
details={"estimated_hours": issue.estimated_hours},
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _check_issue_dates(self, issue: Issue, report: EntityHealthReport):
|
|
133
|
+
if issue.actual_start_date and issue.actual_end_date:
|
|
134
|
+
if issue.actual_start_date > issue.actual_end_date:
|
|
135
|
+
report.issues.append(
|
|
136
|
+
HealthIssue(
|
|
137
|
+
code="invalid_date_range",
|
|
138
|
+
message="Start date is after end date",
|
|
139
|
+
severity=HealthSeverity.ERROR,
|
|
140
|
+
category="structure",
|
|
141
|
+
details={
|
|
142
|
+
"actual_start_date": issue.actual_start_date.isoformat(),
|
|
143
|
+
"actual_end_date": issue.actual_end_date.isoformat(),
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if issue.due_date and issue.actual_end_date:
|
|
149
|
+
if issue.actual_end_date > issue.due_date:
|
|
150
|
+
report.issues.append(
|
|
151
|
+
HealthIssue(
|
|
152
|
+
code="missed_due_date",
|
|
153
|
+
message=f"Issue completed after due date ({issue.due_date.date()})",
|
|
154
|
+
severity=HealthSeverity.INFO,
|
|
155
|
+
category="structure",
|
|
156
|
+
details={
|
|
157
|
+
"due_date": issue.due_date.isoformat(),
|
|
158
|
+
"completed_date": issue.actual_end_date.isoformat(),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _check_issue_assignee(self, issue: Issue, report: EntityHealthReport):
|
|
164
|
+
if issue.previous_assignee and not issue.handoff_date:
|
|
165
|
+
report.issues.append(
|
|
166
|
+
HealthIssue(
|
|
167
|
+
code="missing_handoff_date",
|
|
168
|
+
message=f"Issue was handed off from {issue.previous_assignee} but no handoff date recorded",
|
|
169
|
+
severity=HealthSeverity.WARNING,
|
|
170
|
+
category="structure",
|
|
171
|
+
details={"previous_assignee": issue.previous_assignee},
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _check_issue_progress(self, issue: Issue, report: EntityHealthReport):
|
|
176
|
+
if issue.progress_percentage is not None:
|
|
177
|
+
if not (0 <= issue.progress_percentage <= 100):
|
|
178
|
+
report.issues.append(
|
|
179
|
+
HealthIssue(
|
|
180
|
+
code="invalid_progress_percentage",
|
|
181
|
+
message=f"Progress must be 0-100, got {issue.progress_percentage}",
|
|
182
|
+
severity=HealthSeverity.ERROR,
|
|
183
|
+
category="structure",
|
|
184
|
+
details={"progress_percentage": issue.progress_percentage},
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if issue.status == Status.CLOSED and issue.progress_percentage != 100:
|
|
189
|
+
report.issues.append(
|
|
190
|
+
HealthIssue(
|
|
191
|
+
code="inconsistent_completion",
|
|
192
|
+
message=f"Issue marked Done but only {issue.progress_percentage or 0}% complete",
|
|
193
|
+
severity=HealthSeverity.WARNING,
|
|
194
|
+
category="structure",
|
|
195
|
+
details={"progress_percentage": issue.progress_percentage},
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _check_issue_status_consistency(self, issue: Issue, report: EntityHealthReport):
|
|
200
|
+
if issue.status == Status.CLOSED and not issue.actual_end_date:
|
|
201
|
+
report.issues.append(
|
|
202
|
+
HealthIssue(
|
|
203
|
+
code="missing_completion_date",
|
|
204
|
+
message="Issue marked Done but has no actual end date recorded",
|
|
205
|
+
severity=HealthSeverity.WARNING,
|
|
206
|
+
category="structure",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if issue.actual_start_date and issue.status == Status.TODO:
|
|
211
|
+
report.issues.append(
|
|
212
|
+
HealthIssue(
|
|
213
|
+
code="inconsistent_status",
|
|
214
|
+
message="Issue has a start date but status is still TODO",
|
|
215
|
+
severity=HealthSeverity.WARNING,
|
|
216
|
+
category="structure",
|
|
217
|
+
details={"actual_start_date": issue.actual_start_date.isoformat()},
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _validate_comment_thread(comments: list[Comment]) -> list[str]:
|
|
223
|
+
errors = []
|
|
224
|
+
comment_ids = set()
|
|
225
|
+
|
|
226
|
+
for comment in comments:
|
|
227
|
+
if comment.id in comment_ids:
|
|
228
|
+
errors.append(f"Duplicate comment ID: {comment.id}")
|
|
229
|
+
comment_ids.add(comment.id)
|
|
230
|
+
|
|
231
|
+
if not comment.body or not comment.body.strip():
|
|
232
|
+
errors.append(f"Comment {comment.id}: Empty comment body")
|
|
233
|
+
|
|
234
|
+
if not comment.author or not comment.author.strip():
|
|
235
|
+
errors.append(f"Comment {comment.id}: Missing comment author")
|
|
236
|
+
|
|
237
|
+
# best-effort datetime sanity check
|
|
238
|
+
try:
|
|
239
|
+
_ = comment.created_at
|
|
240
|
+
except Exception:
|
|
241
|
+
errors.append(
|
|
242
|
+
f"Comment {comment.id}: created_at is not a valid datetime"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
for comment in comments:
|
|
246
|
+
if comment.in_reply_to:
|
|
247
|
+
visited = set()
|
|
248
|
+
current_id = comment.in_reply_to
|
|
249
|
+
while current_id:
|
|
250
|
+
if current_id in visited:
|
|
251
|
+
errors.append(
|
|
252
|
+
f"Comment {comment.id}: Circular reference in reply chain"
|
|
253
|
+
)
|
|
254
|
+
break
|
|
255
|
+
visited.add(current_id)
|
|
256
|
+
current_comment = next(
|
|
257
|
+
(c for c in comments if c.id == current_id), None
|
|
258
|
+
)
|
|
259
|
+
current_id = (
|
|
260
|
+
current_comment.in_reply_to if current_comment else None
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return errors
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Helper utilities for sync and core services.
|
|
2
|
+
|
|
3
|
+
DEPRECATED: This module is deprecated. Import directly from status_change_service instead.
|
|
4
|
+
Example: from roadmap.core.services.status_change_service import parse_status_change
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Backward compatibility re-exports
|
|
8
|
+
from roadmap.core.services.status_change_service import (
|
|
9
|
+
extract_issue_status_update,
|
|
10
|
+
extract_milestone_status_update,
|
|
11
|
+
parse_status_change,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"parse_status_change",
|
|
16
|
+
"extract_issue_status_update",
|
|
17
|
+
"extract_milestone_status_update",
|
|
18
|
+
]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Status change parsing and extraction helpers for sync operations.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for parsing and extracting information from
|
|
4
|
+
status change strings in the format "old_status -> new_status".
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_status_change(change_str: str) -> str | None:
|
|
11
|
+
"""Parse and validate change string in format 'old -> new'.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
change_str: Change string in format "old -> new"
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The new status string (stripped), or None if format is invalid
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(change_str, str) or " -> " not in change_str:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
parts = change_str.split(" -> ")
|
|
24
|
+
if len(parts) != 2:
|
|
25
|
+
return None
|
|
26
|
+
return parts[1].strip()
|
|
27
|
+
except (IndexError, AttributeError):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def extract_issue_status_update(
|
|
32
|
+
status_change: str,
|
|
33
|
+
) -> dict[str, Any] | None:
|
|
34
|
+
"""Extract and validate issue status update from change string.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
status_change: Change string in format "old -> new"
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with 'github_state' and 'status_enum', or None if invalid
|
|
41
|
+
"""
|
|
42
|
+
from roadmap.common.constants import Status
|
|
43
|
+
|
|
44
|
+
new_status = parse_status_change(status_change)
|
|
45
|
+
if new_status is None:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Validate that it's a valid status
|
|
50
|
+
status_enum = Status(new_status)
|
|
51
|
+
# Map local status to GitHub state (closed -> closed, others -> open)
|
|
52
|
+
github_state = "closed" if new_status == Status.CLOSED.value else "open"
|
|
53
|
+
return {
|
|
54
|
+
"github_state": github_state,
|
|
55
|
+
"status_enum": status_enum,
|
|
56
|
+
}
|
|
57
|
+
except (ValueError, KeyError):
|
|
58
|
+
# Invalid status - skip the update
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extract_milestone_status_update(
|
|
63
|
+
status_change: str,
|
|
64
|
+
) -> dict[str, Any] | None:
|
|
65
|
+
"""Extract and validate milestone status update from change string.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
status_change: Change string in format "old -> new"
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dict with 'github_state' and 'status_enum', or None if invalid
|
|
72
|
+
"""
|
|
73
|
+
from roadmap.common.constants import MilestoneStatus
|
|
74
|
+
|
|
75
|
+
new_status = parse_status_change(status_change)
|
|
76
|
+
if new_status is None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
github_state = "closed" if new_status == "closed" else "open"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
status_enum = MilestoneStatus(new_status)
|
|
83
|
+
return {
|
|
84
|
+
"github_state": github_state,
|
|
85
|
+
"status_enum": status_enum,
|
|
86
|
+
}
|
|
87
|
+
except (ValueError, KeyError):
|
|
88
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Initialization service utilities and domain logic.
|
|
2
|
+
|
|
3
|
+
Provides utilities for project initialization workflow including locks,
|
|
4
|
+
manifests, validation, and orchestration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from roadmap.core.services.initialization.utils import (
|
|
8
|
+
InitializationLock,
|
|
9
|
+
InitializationManifest,
|
|
10
|
+
)
|
|
11
|
+
from roadmap.core.services.initialization.validator import InitializationValidator
|
|
12
|
+
from roadmap.core.services.initialization.workflow import InitializationWorkflow
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"InitializationLock",
|
|
16
|
+
"InitializationManifest",
|
|
17
|
+
"InitializationValidator",
|
|
18
|
+
"InitializationWorkflow",
|
|
19
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Utility classes for initialization workflow."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from roadmap.common.console import get_console
|
|
12
|
+
|
|
13
|
+
logger = structlog.get_logger()
|
|
14
|
+
|
|
15
|
+
console = get_console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InitializationLock:
|
|
19
|
+
"""Manages initialization lockfile to prevent concurrent inits."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, lock_path: Path):
|
|
22
|
+
"""Initialize InitializationLock.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
lock_path: Path to the lock file.
|
|
26
|
+
"""
|
|
27
|
+
self.lock_path = lock_path
|
|
28
|
+
|
|
29
|
+
def acquire(self) -> bool:
|
|
30
|
+
"""Acquire the lock. Returns False if already locked."""
|
|
31
|
+
if self.lock_path.exists():
|
|
32
|
+
return False
|
|
33
|
+
try:
|
|
34
|
+
self.lock_path.write_text(
|
|
35
|
+
f"pid:{os.getpid()}\nstarted:{datetime.now(UTC).isoformat()}\n"
|
|
36
|
+
)
|
|
37
|
+
return True
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.debug("lockfile_creation_failed", error=str(e), action="create_lock")
|
|
40
|
+
console.print(
|
|
41
|
+
"⚠️ Could not create init lockfile; proceeding with care",
|
|
42
|
+
style="yellow",
|
|
43
|
+
)
|
|
44
|
+
return True # Continue anyway
|
|
45
|
+
|
|
46
|
+
def release(self) -> None:
|
|
47
|
+
"""Release the lock."""
|
|
48
|
+
try:
|
|
49
|
+
if self.lock_path.exists():
|
|
50
|
+
self.lock_path.unlink()
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.debug("lockfile_release_failed", error=str(e), action="release_lock")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class InitializationManifest:
|
|
56
|
+
"""Tracks created files/directories for potential rollback."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, manifest_file: Path):
|
|
59
|
+
"""Initialize InitializationManifest.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
manifest_file: Path to the manifest file.
|
|
63
|
+
"""
|
|
64
|
+
self.manifest_file = manifest_file
|
|
65
|
+
self.data: dict[str, list] = {"created": []}
|
|
66
|
+
|
|
67
|
+
def add_path(self, path: Path) -> None:
|
|
68
|
+
"""Add a path to the manifest."""
|
|
69
|
+
if path.exists():
|
|
70
|
+
self.data["created"].append(str(path))
|
|
71
|
+
self._save()
|
|
72
|
+
|
|
73
|
+
def _save(self) -> None:
|
|
74
|
+
"""Save manifest to disk (best effort)."""
|
|
75
|
+
try:
|
|
76
|
+
self.manifest_file.write_text(json.dumps(self.data))
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.debug("manifest_save_failed", error=str(e), action="save_manifest")
|
|
79
|
+
|
|
80
|
+
def rollback(self) -> None:
|
|
81
|
+
"""Remove all paths tracked in the manifest."""
|
|
82
|
+
if not self.manifest_file.exists():
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
data = json.loads(self.manifest_file.read_text())
|
|
87
|
+
for p in data.get("created", []):
|
|
88
|
+
try:
|
|
89
|
+
ppath = Path(p)
|
|
90
|
+
if ppath.is_file():
|
|
91
|
+
ppath.unlink()
|
|
92
|
+
elif ppath.is_dir():
|
|
93
|
+
shutil.rmtree(ppath)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.debug(
|
|
96
|
+
"manifest_item_removal_failed",
|
|
97
|
+
path=p,
|
|
98
|
+
error=str(e),
|
|
99
|
+
action="remove_item",
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.debug(
|
|
103
|
+
"manifest_rollback_failed", error=str(e), action="rollback_manifest"
|
|
104
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Validation utilities for initialization workflow."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.common.console import get_console
|
|
7
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
8
|
+
|
|
9
|
+
console = get_console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InitializationValidator:
|
|
13
|
+
"""Validates initialization prerequisites and results."""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def check_existing_roadmap(
|
|
17
|
+
core: RoadmapCore, force: bool
|
|
18
|
+
) -> tuple[bool, str | None]:
|
|
19
|
+
"""Check if roadmap already exists.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Tuple of (should_continue, error_message)
|
|
23
|
+
"""
|
|
24
|
+
roadmap_path = core.roadmap_dir
|
|
25
|
+
|
|
26
|
+
# Check if roadmap path exists as a file (not a directory)
|
|
27
|
+
if roadmap_path.exists() and not roadmap_path.is_dir():
|
|
28
|
+
return False, f"Roadmap path exists but is not a directory: {roadmap_path}"
|
|
29
|
+
|
|
30
|
+
if not core.is_initialized():
|
|
31
|
+
return True, None
|
|
32
|
+
|
|
33
|
+
# If force is true, we'll be doing a full reset
|
|
34
|
+
if force:
|
|
35
|
+
return True, None
|
|
36
|
+
|
|
37
|
+
# If roadmap exists but force is not specified, we allow init to continue
|
|
38
|
+
# to update config/metadata without destroying existing data
|
|
39
|
+
return True, None
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def validate_lockfile(lock_path: Path) -> tuple[bool, str | None]:
|
|
43
|
+
"""Check for concurrent initialization."""
|
|
44
|
+
if lock_path.exists():
|
|
45
|
+
return (
|
|
46
|
+
False,
|
|
47
|
+
"Initialization already in progress (lockfile present). Try again later.",
|
|
48
|
+
)
|
|
49
|
+
return True, None
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def post_init_validate(
|
|
53
|
+
core: RoadmapCore, name: str, project_info: dict[str, Any] | None
|
|
54
|
+
) -> bool:
|
|
55
|
+
"""Validate initialization results.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if validation passed, False if warnings were found
|
|
59
|
+
"""
|
|
60
|
+
warnings = []
|
|
61
|
+
|
|
62
|
+
# Check core directories
|
|
63
|
+
if not core.roadmap_dir.exists():
|
|
64
|
+
warnings.append(f"Roadmap directory {name}/ not found")
|
|
65
|
+
if not core.config_file.exists():
|
|
66
|
+
warnings.append("Config file not created")
|
|
67
|
+
|
|
68
|
+
# Check project if one was supposed to be created
|
|
69
|
+
if project_info and not core.projects_dir.exists():
|
|
70
|
+
warnings.append("Projects directory not created")
|
|
71
|
+
|
|
72
|
+
if warnings:
|
|
73
|
+
console.print("⚠️ Validation warnings:", style="yellow")
|
|
74
|
+
for warning in warnings:
|
|
75
|
+
console.print(f" - {warning}", style="yellow")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
return True
|