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,162 @@
|
|
|
1
|
+
"""Fixer for normalizing issue labels to alphabetically sorted order."""
|
|
2
|
+
|
|
3
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LabelNormalizationFixer(HealthFixer):
|
|
7
|
+
"""Normalizes issue labels to alphabetically sorted order.
|
|
8
|
+
|
|
9
|
+
Safety: SAFE (just reorders labels, no data loss)
|
|
10
|
+
|
|
11
|
+
GitHub may return labels in different order than local storage, causing false
|
|
12
|
+
"needs push" detection during sync. This fixer ensures labels are always
|
|
13
|
+
stored in alphabetical order for consistency.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def fix_type(self) -> str:
|
|
18
|
+
"""Return fixer type identifier."""
|
|
19
|
+
return "label_normalization"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def safety_level(self) -> FixSafety:
|
|
23
|
+
"""Return safety level - SAFE because we're just reordering labels."""
|
|
24
|
+
return FixSafety.SAFE
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def description(self) -> str:
|
|
28
|
+
"""Return fixer description."""
|
|
29
|
+
return "Normalize issue labels to alphabetical order"
|
|
30
|
+
|
|
31
|
+
def scan(self) -> dict:
|
|
32
|
+
"""Scan for issues with unsorted labels.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dict with found, count, message, details
|
|
36
|
+
"""
|
|
37
|
+
unsorted_issues = self._find_unsorted_labels()
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"found": len(unsorted_issues) > 0,
|
|
41
|
+
"count": len(unsorted_issues),
|
|
42
|
+
"message": f"Found {len(unsorted_issues)} issue(s) with unsorted labels",
|
|
43
|
+
"details": [
|
|
44
|
+
{
|
|
45
|
+
"id": iss["id"],
|
|
46
|
+
"title": iss["title"],
|
|
47
|
+
"current": iss["current_labels"],
|
|
48
|
+
"sorted": iss["sorted_labels"],
|
|
49
|
+
}
|
|
50
|
+
for iss in unsorted_issues
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def dry_run(self) -> FixResult:
|
|
55
|
+
"""Preview which issues would have labels sorted.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
FixResult with dry_run=True
|
|
59
|
+
"""
|
|
60
|
+
unsorted_issues = self._find_unsorted_labels()
|
|
61
|
+
|
|
62
|
+
affected = [
|
|
63
|
+
f"{iss['id']}: {iss['current_labels']} → {iss['sorted_labels']}"
|
|
64
|
+
for iss in unsorted_issues
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return FixResult(
|
|
68
|
+
fix_type=self.fix_type,
|
|
69
|
+
success=True,
|
|
70
|
+
dry_run=True,
|
|
71
|
+
message=f"Would normalize {len(unsorted_issues)} issue label(s)",
|
|
72
|
+
affected_items=affected,
|
|
73
|
+
items_count=len(unsorted_issues),
|
|
74
|
+
changes_made=0,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
78
|
+
"""Sort labels in all issues to alphabetical order.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
force: If True, skip confirmation prompt
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
FixResult with results of applying fixes
|
|
85
|
+
"""
|
|
86
|
+
unsorted_issues = self._find_unsorted_labels()
|
|
87
|
+
|
|
88
|
+
if not unsorted_issues:
|
|
89
|
+
return FixResult(
|
|
90
|
+
fix_type=self.fix_type,
|
|
91
|
+
success=True,
|
|
92
|
+
dry_run=False,
|
|
93
|
+
message="No issues found with unsorted labels",
|
|
94
|
+
affected_items=[],
|
|
95
|
+
items_count=0,
|
|
96
|
+
changes_made=0,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
affected = []
|
|
100
|
+
changes = 0
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
for issue_data in unsorted_issues:
|
|
104
|
+
issue = issue_data["issue"]
|
|
105
|
+
sorted_labels = issue_data["sorted_labels"]
|
|
106
|
+
|
|
107
|
+
# Save the issue with sorted labels via the update API
|
|
108
|
+
self.core.issues.update(issue.id, labels=sorted_labels)
|
|
109
|
+
|
|
110
|
+
affected.append(f"{issue.id} ({len(sorted_labels)} labels sorted)")
|
|
111
|
+
changes += 1
|
|
112
|
+
|
|
113
|
+
return FixResult(
|
|
114
|
+
fix_type=self.fix_type,
|
|
115
|
+
success=True,
|
|
116
|
+
dry_run=False,
|
|
117
|
+
message=f"Normalized labels in {changes} issue(s)",
|
|
118
|
+
affected_items=affected,
|
|
119
|
+
items_count=len(unsorted_issues),
|
|
120
|
+
changes_made=changes,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return FixResult(
|
|
125
|
+
fix_type=self.fix_type,
|
|
126
|
+
success=False,
|
|
127
|
+
dry_run=False,
|
|
128
|
+
message=f"Failed to normalize labels: {str(e)}",
|
|
129
|
+
affected_items=affected,
|
|
130
|
+
items_count=len(unsorted_issues),
|
|
131
|
+
changes_made=changes,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _find_unsorted_labels(self) -> list[dict]:
|
|
135
|
+
"""Find all issues with unsorted labels.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of dicts with issue data and label info
|
|
139
|
+
"""
|
|
140
|
+
unsorted_issues = []
|
|
141
|
+
|
|
142
|
+
# Get all local issues including archived
|
|
143
|
+
all_issues = self.core.issues.list_all_including_archived()
|
|
144
|
+
|
|
145
|
+
for issue in all_issues:
|
|
146
|
+
if not issue.labels:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# Check if labels are sorted
|
|
150
|
+
sorted_labels = sorted(issue.labels)
|
|
151
|
+
if issue.labels != sorted_labels:
|
|
152
|
+
unsorted_issues.append(
|
|
153
|
+
{
|
|
154
|
+
"id": issue.id,
|
|
155
|
+
"issue": issue,
|
|
156
|
+
"title": issue.title,
|
|
157
|
+
"current_labels": issue.labels,
|
|
158
|
+
"sorted_labels": sorted_labels,
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return unsorted_issues
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Fixer for normalizing milestone names to safe format in issue metadata."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from structlog import get_logger
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
8
|
+
|
|
9
|
+
logger = get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MilestoneNameNormalizationFixer(HealthFixer):
|
|
13
|
+
"""Normalizes issue milestone metadata to match milestone filenames.
|
|
14
|
+
|
|
15
|
+
Safety: SAFE (just normalizes names to match actual milestones)
|
|
16
|
+
|
|
17
|
+
Issues may have milestone names like 'v.0.8.0' but milestone files are 'v080.md'.
|
|
18
|
+
This fixer updates issue metadata to use the safe filename format.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def fix_type(self) -> str:
|
|
23
|
+
"""Return fixer type identifier."""
|
|
24
|
+
return "milestone_name_normalization"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def safety_level(self) -> FixSafety:
|
|
28
|
+
"""Return safety level - SAFE because we're just normalizing names."""
|
|
29
|
+
return FixSafety.SAFE
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def description(self) -> str:
|
|
33
|
+
"""Return fixer description."""
|
|
34
|
+
return "Normalize milestone names to match milestone filenames"
|
|
35
|
+
|
|
36
|
+
def scan(self) -> dict:
|
|
37
|
+
"""Scan for issues with non-normalized milestone names.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with found, count, message, details
|
|
41
|
+
"""
|
|
42
|
+
mismatched = self._find_mismatched_milestone_names()
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"found": len(mismatched) > 0,
|
|
46
|
+
"count": len(mismatched),
|
|
47
|
+
"message": f"Found {len(mismatched)} issue(s) with non-normalized milestone name(s)",
|
|
48
|
+
"details": [
|
|
49
|
+
{
|
|
50
|
+
"id": iss["id"],
|
|
51
|
+
"title": iss["title"],
|
|
52
|
+
"current": iss["current_name"],
|
|
53
|
+
"should_be": iss["normalized_name"],
|
|
54
|
+
}
|
|
55
|
+
for iss in mismatched
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def dry_run(self) -> FixResult:
|
|
60
|
+
"""Preview which issues would be fixed.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
FixResult with dry_run=True
|
|
64
|
+
"""
|
|
65
|
+
mismatched = self._find_mismatched_milestone_names()
|
|
66
|
+
|
|
67
|
+
affected = [
|
|
68
|
+
f"{iss['id']} ({iss['current_name']} → {iss['normalized_name']})"
|
|
69
|
+
for iss in mismatched
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
return FixResult(
|
|
73
|
+
fix_type=self.fix_type,
|
|
74
|
+
success=True,
|
|
75
|
+
dry_run=True,
|
|
76
|
+
message=f"Would normalize {len(mismatched)} issue milestone name(s)",
|
|
77
|
+
affected_items=affected,
|
|
78
|
+
items_count=len(mismatched),
|
|
79
|
+
changes_made=0,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
83
|
+
"""Normalize milestone names in issue metadata.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
force: Ignored (SAFE fixers apply automatically)
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
FixResult with fix results
|
|
90
|
+
"""
|
|
91
|
+
_ = force
|
|
92
|
+
mismatched = self._find_mismatched_milestone_names()
|
|
93
|
+
fixed_count = 0
|
|
94
|
+
failed_items = []
|
|
95
|
+
|
|
96
|
+
for issue_data in mismatched:
|
|
97
|
+
try:
|
|
98
|
+
issue_id = issue_data["id"]
|
|
99
|
+
normalized_name = issue_data["normalized_name"]
|
|
100
|
+
|
|
101
|
+
# Assign to normalized milestone name
|
|
102
|
+
if self.core.issues.assign_to_milestone(issue_id, normalized_name):
|
|
103
|
+
fixed_count += 1
|
|
104
|
+
else:
|
|
105
|
+
failed_items.append(issue_id)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(
|
|
108
|
+
"normalize_milestone_failed",
|
|
109
|
+
issue_id=issue_data["id"],
|
|
110
|
+
error=str(e),
|
|
111
|
+
severity="data_error",
|
|
112
|
+
)
|
|
113
|
+
failed_items.append(issue_data["id"])
|
|
114
|
+
|
|
115
|
+
return FixResult(
|
|
116
|
+
fix_type=self.fix_type,
|
|
117
|
+
success=len(failed_items) == 0,
|
|
118
|
+
dry_run=False,
|
|
119
|
+
message=f"Normalized {fixed_count}/{len(mismatched)} issue milestone name(s)",
|
|
120
|
+
affected_items=[
|
|
121
|
+
iss["id"] for iss in mismatched if iss["id"] not in failed_items
|
|
122
|
+
],
|
|
123
|
+
items_count=len(mismatched),
|
|
124
|
+
changes_made=fixed_count,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _find_mismatched_milestone_names(self) -> list[dict]:
|
|
128
|
+
"""Find issues where milestone name doesn't match safe filename.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of dicts with id, title, current_name, normalized_name
|
|
132
|
+
"""
|
|
133
|
+
mismatched = []
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
milestones_dir = Path(".roadmap/milestones").resolve()
|
|
137
|
+
if not milestones_dir.exists():
|
|
138
|
+
return mismatched
|
|
139
|
+
|
|
140
|
+
# Build map of display names to safe names
|
|
141
|
+
safe_name_to_display = {}
|
|
142
|
+
for f in milestones_dir.glob("*.md"):
|
|
143
|
+
safe_name = f.stem
|
|
144
|
+
# Try to find what display name might map to this
|
|
145
|
+
safe_name_to_display[safe_name] = safe_name
|
|
146
|
+
|
|
147
|
+
# Check all issues
|
|
148
|
+
for issue in self.core.issues.list():
|
|
149
|
+
if not issue.milestone:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# Convert issue's milestone to safe name
|
|
153
|
+
safe_name = "".join(
|
|
154
|
+
c for c in issue.milestone if c.isalnum() or c in (" ", "-", "_")
|
|
155
|
+
).strip()
|
|
156
|
+
safe_name = safe_name.replace(" ", "-").lower()
|
|
157
|
+
|
|
158
|
+
# Check if milestone file exists with this safe name
|
|
159
|
+
milestone_file = milestones_dir / f"{safe_name}.md"
|
|
160
|
+
if not milestone_file.exists():
|
|
161
|
+
# Milestone doesn't exist, skip
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# If the safe name differs from the issue's milestone, it needs normalizing
|
|
165
|
+
if safe_name != issue.milestone:
|
|
166
|
+
mismatched.append(
|
|
167
|
+
{
|
|
168
|
+
"id": issue.id,
|
|
169
|
+
"title": issue.title,
|
|
170
|
+
"current_name": issue.milestone,
|
|
171
|
+
"normalized_name": safe_name,
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(
|
|
177
|
+
"find_mismatched_milestones_failed", error=str(e), severity="data_error"
|
|
178
|
+
)
|
|
179
|
+
return []
|
|
180
|
+
|
|
181
|
+
return mismatched
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Fixer for milestone naming convention compliance.
|
|
2
|
+
|
|
3
|
+
This fixer ensures that:
|
|
4
|
+
1. Issue milestone values use actual milestone filenames (safe names)
|
|
5
|
+
2. No display name variants (v.0.8.0) are used instead of safe names (v080)
|
|
6
|
+
3. All naming is consistent across the system
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from structlog import get_logger
|
|
12
|
+
|
|
13
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
14
|
+
|
|
15
|
+
logger = get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MilestoneNamingComplianceFixer(HealthFixer):
|
|
19
|
+
"""Fixes milestone names to match actual milestone filenames.
|
|
20
|
+
|
|
21
|
+
Converts display names like "v.0.8.0", "Future (Post-v1.0)" to their
|
|
22
|
+
actual safe milestone names like "v080", "future-post-v10".
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
DISPLAY_TO_SAFE_MILESTONE = {
|
|
26
|
+
"v.0.7.0": "v070",
|
|
27
|
+
"v.0.8.0": "v080",
|
|
28
|
+
"v.0.9.0": "v090",
|
|
29
|
+
"v.1.0.0": "v100",
|
|
30
|
+
"v.0.8": "v080",
|
|
31
|
+
"v0.8": "v080",
|
|
32
|
+
"v0.8.0": "v080",
|
|
33
|
+
"v.0.7": "v070",
|
|
34
|
+
"v0.7": "v070",
|
|
35
|
+
"v.0.9": "v090",
|
|
36
|
+
"v0.9": "v090",
|
|
37
|
+
"v.1.0": "v100",
|
|
38
|
+
"v1.0": "v100",
|
|
39
|
+
"Future (Post-v1.0)": "future-post-v10",
|
|
40
|
+
"Development": "backlog",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def __init__(self, core):
|
|
44
|
+
"""Initialize fixer with core instance."""
|
|
45
|
+
super().__init__(core)
|
|
46
|
+
self.logger = logger
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def fix_type(self) -> str:
|
|
50
|
+
"""Return fixer type identifier."""
|
|
51
|
+
return "milestone_naming_compliance"
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def safety_level(self) -> FixSafety:
|
|
55
|
+
"""Return safety level - SAFE because we fix to actual milestone names."""
|
|
56
|
+
return FixSafety.SAFE
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def description(self) -> str:
|
|
60
|
+
"""Return fixer description."""
|
|
61
|
+
return "Fix milestone naming to match actual milestone filenames"
|
|
62
|
+
|
|
63
|
+
def scan(self) -> dict:
|
|
64
|
+
"""Scan for issues with non-compliant milestone names.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dict with found, count, message, details
|
|
68
|
+
"""
|
|
69
|
+
non_compliant = self._find_non_compliant_milestones()
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"found": len(non_compliant) > 0,
|
|
73
|
+
"count": len(non_compliant),
|
|
74
|
+
"message": f"Found {len(non_compliant)} issue(s) with non-compliant milestone name(s)",
|
|
75
|
+
"details": [
|
|
76
|
+
{
|
|
77
|
+
"id": iss["id"],
|
|
78
|
+
"title": iss["title"],
|
|
79
|
+
"current": iss["current_milestone"],
|
|
80
|
+
"should_be": iss["safe_milestone"],
|
|
81
|
+
}
|
|
82
|
+
for iss in non_compliant
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def dry_run(self) -> FixResult:
|
|
87
|
+
"""Preview which issues would be fixed.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
FixResult with dry_run=True
|
|
91
|
+
"""
|
|
92
|
+
non_compliant = self._find_non_compliant_milestones()
|
|
93
|
+
|
|
94
|
+
affected = []
|
|
95
|
+
for iss in non_compliant:
|
|
96
|
+
affected.append(
|
|
97
|
+
f"{iss['id']} ({iss['current_milestone']} → {iss['safe_milestone']})"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return FixResult(
|
|
101
|
+
fix_type=self.fix_type,
|
|
102
|
+
success=True,
|
|
103
|
+
dry_run=True,
|
|
104
|
+
message=f"Would fix {len(non_compliant)} issue(s) with non-compliant milestone name(s)",
|
|
105
|
+
affected_items=affected,
|
|
106
|
+
items_count=len(non_compliant),
|
|
107
|
+
changes_made=0,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
111
|
+
"""Apply milestone naming compliance fixes.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
force: Not used for SAFE fixers
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
FixResult with fix details
|
|
118
|
+
"""
|
|
119
|
+
non_compliant = self._find_non_compliant_milestones()
|
|
120
|
+
|
|
121
|
+
if not non_compliant:
|
|
122
|
+
return FixResult(
|
|
123
|
+
fix_type=self.fix_type,
|
|
124
|
+
success=True,
|
|
125
|
+
dry_run=False,
|
|
126
|
+
message="No non-compliant milestone names found",
|
|
127
|
+
items_count=0,
|
|
128
|
+
changes_made=0,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
affected = []
|
|
132
|
+
fixed = 0
|
|
133
|
+
failed = 0
|
|
134
|
+
|
|
135
|
+
for iss in non_compliant:
|
|
136
|
+
try:
|
|
137
|
+
# Update the issue file directly
|
|
138
|
+
file_path = Path(iss["file"])
|
|
139
|
+
content = file_path.read_text(encoding="utf-8")
|
|
140
|
+
|
|
141
|
+
# Replace milestone value
|
|
142
|
+
old_line = f"milestone: {iss['current_milestone']}"
|
|
143
|
+
new_line = f"milestone: {iss['safe_milestone']}"
|
|
144
|
+
|
|
145
|
+
updated_content = content.replace(old_line, new_line, 1)
|
|
146
|
+
file_path.write_text(updated_content, encoding="utf-8")
|
|
147
|
+
|
|
148
|
+
affected.append(
|
|
149
|
+
f"{iss['id']} ({iss['current_milestone']} → {iss['safe_milestone']})"
|
|
150
|
+
)
|
|
151
|
+
fixed += 1
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.logger.error(f"Failed to fix milestone for {iss['id']}: {e}")
|
|
154
|
+
failed += 1
|
|
155
|
+
|
|
156
|
+
return FixResult(
|
|
157
|
+
fix_type=self.fix_type,
|
|
158
|
+
success=failed == 0,
|
|
159
|
+
dry_run=False,
|
|
160
|
+
message=f"Fixed {fixed} issue(s) with non-compliant milestone name(s)",
|
|
161
|
+
affected_items=affected,
|
|
162
|
+
items_count=len(non_compliant),
|
|
163
|
+
changes_made=fixed,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _find_non_compliant_milestones(self) -> list[dict]:
|
|
167
|
+
"""Find all issues with non-compliant milestone names.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of issue dicts with: id, title, current_milestone, safe_milestone
|
|
171
|
+
"""
|
|
172
|
+
issues: list[dict] = []
|
|
173
|
+
issues_dir = Path(".roadmap/issues").resolve()
|
|
174
|
+
milestones_dir = Path(".roadmap/milestones").resolve()
|
|
175
|
+
|
|
176
|
+
# Get all actual milestone files (safe names)
|
|
177
|
+
actual_milestones = self._load_actual_milestones(milestones_dir)
|
|
178
|
+
|
|
179
|
+
if not issues_dir.exists():
|
|
180
|
+
return issues
|
|
181
|
+
|
|
182
|
+
for issue_file in issues_dir.rglob("*.md"):
|
|
183
|
+
if issue_file.name.startswith("."):
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
content = issue_file.read_text(encoding="utf-8")
|
|
187
|
+
current_milestone, title = self._parse_issue_frontmatter(content)
|
|
188
|
+
|
|
189
|
+
if not current_milestone:
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if current_milestone in actual_milestones:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
safe_milestone = self._resolve_safe_milestone(
|
|
196
|
+
current_milestone,
|
|
197
|
+
actual_milestones,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if safe_milestone != current_milestone:
|
|
201
|
+
issues.append(
|
|
202
|
+
{
|
|
203
|
+
"id": issue_file.stem.split("-")[0],
|
|
204
|
+
"title": title or issue_file.stem,
|
|
205
|
+
"file": str(issue_file),
|
|
206
|
+
"current_milestone": current_milestone,
|
|
207
|
+
"safe_milestone": safe_milestone,
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return issues
|
|
212
|
+
|
|
213
|
+
def _load_actual_milestones(self, milestones_dir: Path) -> set[str]:
|
|
214
|
+
"""Load safe milestone names from milestone filenames."""
|
|
215
|
+
if not milestones_dir.exists():
|
|
216
|
+
return set()
|
|
217
|
+
return {milestone_file.stem for milestone_file in milestones_dir.glob("*.md")}
|
|
218
|
+
|
|
219
|
+
def _parse_issue_frontmatter(self, content: str) -> tuple[str | None, str | None]:
|
|
220
|
+
"""Extract milestone and title values from issue file frontmatter lines."""
|
|
221
|
+
current_milestone = None
|
|
222
|
+
title = None
|
|
223
|
+
for line in content.split("\n"):
|
|
224
|
+
if line.startswith("milestone:"):
|
|
225
|
+
current_milestone = line.replace("milestone:", "").strip()
|
|
226
|
+
elif line.startswith("title:"):
|
|
227
|
+
title = line.replace("title:", "").strip().strip("'\"")
|
|
228
|
+
return current_milestone, title
|
|
229
|
+
|
|
230
|
+
def _resolve_safe_milestone(
|
|
231
|
+
self,
|
|
232
|
+
current_milestone: str,
|
|
233
|
+
actual_milestones: set[str],
|
|
234
|
+
) -> str:
|
|
235
|
+
"""Resolve a milestone value to a safe milestone name."""
|
|
236
|
+
if not current_milestone or current_milestone.isspace():
|
|
237
|
+
return "backlog"
|
|
238
|
+
|
|
239
|
+
safe_milestone = self.DISPLAY_TO_SAFE_MILESTONE.get(
|
|
240
|
+
current_milestone, "backlog"
|
|
241
|
+
)
|
|
242
|
+
if safe_milestone not in actual_milestones:
|
|
243
|
+
return "backlog"
|
|
244
|
+
return safe_milestone
|