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,75 @@
|
|
|
1
|
+
"""Validator for duplicate issues."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from roadmap.core.services.validator_base import BaseValidator, HealthStatus
|
|
7
|
+
|
|
8
|
+
from . import extract_issue_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DuplicateIssuesValidator(BaseValidator):
|
|
12
|
+
"""Validator for duplicate issues."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def get_check_name() -> str:
|
|
16
|
+
"""Get the name of this check.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
String identifier for the duplicate_issues check.
|
|
20
|
+
"""
|
|
21
|
+
return "duplicate_issues"
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def scan_for_duplicate_issues(issues_dir: Path) -> dict[str, list[Path]]:
|
|
25
|
+
"""Scan all issue files and identify duplicates by issue ID.
|
|
26
|
+
|
|
27
|
+
Returns a dict mapping issue_id -> list of file paths where duplicates exist (2+ occurrences).
|
|
28
|
+
"""
|
|
29
|
+
issues_by_id = defaultdict(list)
|
|
30
|
+
|
|
31
|
+
# Scan all issue markdown files recursively
|
|
32
|
+
for issue_file in issues_dir.glob("**/*.md"):
|
|
33
|
+
# Skip backup files
|
|
34
|
+
if ".backup" in issue_file.name:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
issue_id = extract_issue_id(issue_file.name)
|
|
38
|
+
if issue_id:
|
|
39
|
+
issues_by_id[issue_id].append(issue_file)
|
|
40
|
+
|
|
41
|
+
# Return only duplicates (2+ occurrences)
|
|
42
|
+
duplicates = {
|
|
43
|
+
issue_id: files
|
|
44
|
+
for issue_id, files in issues_by_id.items()
|
|
45
|
+
if len(files) > 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return duplicates
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def perform_check() -> tuple[str, str]:
|
|
52
|
+
"""Check for duplicate issues.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Tuple of (status, message) describing the health check result
|
|
56
|
+
"""
|
|
57
|
+
issues_dir = Path(".roadmap/issues")
|
|
58
|
+
if not issues_dir.exists():
|
|
59
|
+
return (
|
|
60
|
+
HealthStatus.HEALTHY,
|
|
61
|
+
"Issues directory not found (not initialized yet)",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
|
|
65
|
+
|
|
66
|
+
if not duplicates:
|
|
67
|
+
return HealthStatus.HEALTHY, "No duplicate issues found"
|
|
68
|
+
|
|
69
|
+
total_duplicates = sum(len(files) - 1 for files in duplicates.values())
|
|
70
|
+
message = (
|
|
71
|
+
f"⚠️ {len(duplicates)} issue ID(s) have duplicates "
|
|
72
|
+
f"({total_duplicates} duplicate files total): "
|
|
73
|
+
"Manual cleanup required"
|
|
74
|
+
)
|
|
75
|
+
return HealthStatus.DEGRADED, message
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Validator for duplicate milestones."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
from roadmap.core.services.validator_base import BaseValidator, HealthStatus
|
|
9
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
10
|
+
|
|
11
|
+
logger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DuplicateMilestonesValidator(BaseValidator):
|
|
15
|
+
"""Validator for duplicate milestones (by name and file)."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_check_name() -> str:
|
|
19
|
+
"""Get the name of this check.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
String identifier for the duplicate_milestones check.
|
|
23
|
+
"""
|
|
24
|
+
return "duplicate_milestones"
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def scan_for_duplicate_files(milestones_dir: Path) -> dict[str, list[Path]]:
|
|
28
|
+
"""Scan all milestone files and identify duplicates by milestone name.
|
|
29
|
+
|
|
30
|
+
Returns a dict mapping milestone_name -> list of file paths where duplicates exist (2+ occurrences).
|
|
31
|
+
"""
|
|
32
|
+
milestones_by_name = defaultdict(list)
|
|
33
|
+
|
|
34
|
+
# Scan all milestone markdown files
|
|
35
|
+
for milestone_file in milestones_dir.glob("*.md"):
|
|
36
|
+
# Skip backup files
|
|
37
|
+
if ".backup" in milestone_file.name:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
# Extract milestone name from filename (remove .md extension)
|
|
41
|
+
milestone_name = milestone_file.stem
|
|
42
|
+
milestones_by_name[milestone_name].append(milestone_file)
|
|
43
|
+
|
|
44
|
+
# Return only duplicates (2+ occurrences)
|
|
45
|
+
duplicates = {
|
|
46
|
+
name: files for name, files in milestones_by_name.items() if len(files) > 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return duplicates
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def scan_for_duplicate_names(core) -> list[dict]:
|
|
53
|
+
"""Scan for duplicate milestone names in the database.
|
|
54
|
+
|
|
55
|
+
Returns a list of duplicate milestone names with count.
|
|
56
|
+
"""
|
|
57
|
+
duplicates = []
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
all_milestones = core.milestones.list()
|
|
61
|
+
|
|
62
|
+
# Count occurrences of each name
|
|
63
|
+
name_counts = {}
|
|
64
|
+
for milestone in all_milestones:
|
|
65
|
+
name_counts[milestone.name] = name_counts.get(milestone.name, 0) + 1
|
|
66
|
+
|
|
67
|
+
# Find duplicates
|
|
68
|
+
for name, count in name_counts.items():
|
|
69
|
+
if count > 1:
|
|
70
|
+
duplicates.append(
|
|
71
|
+
{
|
|
72
|
+
"name": name,
|
|
73
|
+
"count": count,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.debug(
|
|
79
|
+
"scan_duplicate_names_failed",
|
|
80
|
+
operation="scan_for_duplicate_names",
|
|
81
|
+
error=str(e),
|
|
82
|
+
action="Returning empty duplicates",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return duplicates
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def perform_check() -> tuple[str, str]:
|
|
89
|
+
"""Check for duplicate milestones (both file and name duplicates).
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Tuple of (status, message) describing the health check result
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
milestones_dir = Path(".roadmap/milestones")
|
|
96
|
+
if not milestones_dir.exists():
|
|
97
|
+
return (
|
|
98
|
+
HealthStatus.HEALTHY,
|
|
99
|
+
"Milestones directory not found (not initialized yet)",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Check for file duplicates
|
|
103
|
+
file_duplicates = DuplicateMilestonesValidator.scan_for_duplicate_files(
|
|
104
|
+
milestones_dir
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Check for name duplicates in database
|
|
108
|
+
core = RoadmapCore()
|
|
109
|
+
name_duplicates = DuplicateMilestonesValidator.scan_for_duplicate_names(
|
|
110
|
+
core
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
issues = []
|
|
114
|
+
if file_duplicates:
|
|
115
|
+
total_file_duplicates = sum(
|
|
116
|
+
len(files) - 1 for files in file_duplicates.values()
|
|
117
|
+
)
|
|
118
|
+
issues.append(
|
|
119
|
+
f"{len(file_duplicates)} milestone name(s) have duplicate files "
|
|
120
|
+
f"({total_file_duplicates} duplicate files total)"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if name_duplicates:
|
|
124
|
+
issues.append(
|
|
125
|
+
f"{len(name_duplicates)} milestone name(s) appear multiple times in database"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if not issues:
|
|
129
|
+
return HealthStatus.HEALTHY, "No duplicate milestones found"
|
|
130
|
+
|
|
131
|
+
message = f"⚠️ {'; '.join(issues)}: Manual cleanup required"
|
|
132
|
+
return HealthStatus.DEGRADED, message
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.debug(
|
|
136
|
+
"duplicate_milestones_check_failed",
|
|
137
|
+
operation="perform_check",
|
|
138
|
+
error=str(e),
|
|
139
|
+
action="Returning healthy status",
|
|
140
|
+
)
|
|
141
|
+
return HealthStatus.HEALTHY, "Could not check for duplicate milestones"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Validator for folder structure and issue placement."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from roadmap.common.logging import get_logger
|
|
6
|
+
from roadmap.core.services.validator_base import BaseValidator, HealthStatus
|
|
7
|
+
|
|
8
|
+
from . import extract_issue_id
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FolderStructureValidator(BaseValidator):
|
|
14
|
+
"""Validator for folder structure and issue placement."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_check_name() -> str:
|
|
18
|
+
"""Get the name of this check.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
String identifier for the folder_structure check.
|
|
22
|
+
"""
|
|
23
|
+
return "folder_structure"
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _check_root_issues(issues_dir: Path, core, misplaced_list: list) -> None:
|
|
27
|
+
"""Check root level issues for milestone placement issues."""
|
|
28
|
+
for issue_file in issues_dir.glob("*.md"):
|
|
29
|
+
if ".backup" in issue_file.name:
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
issue_id = extract_issue_id(issue_file.name)
|
|
34
|
+
if not issue_id:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
issue = core.issue_service.get_issue(issue_id)
|
|
38
|
+
if issue and issue.milestone:
|
|
39
|
+
milestone_folder = issues_dir / issue.milestone
|
|
40
|
+
if milestone_folder.exists():
|
|
41
|
+
misplaced_list.append(
|
|
42
|
+
{
|
|
43
|
+
"issue_id": issue.id,
|
|
44
|
+
"title": issue.title,
|
|
45
|
+
"current_location": str(issue_file),
|
|
46
|
+
"assigned_milestone": issue.milestone,
|
|
47
|
+
"expected_location": str(
|
|
48
|
+
milestone_folder / issue_file.name
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.debug(
|
|
54
|
+
"issue_parse_failed",
|
|
55
|
+
operation="parse_issue",
|
|
56
|
+
file=str(issue_file),
|
|
57
|
+
error=str(e),
|
|
58
|
+
action="Skipping issue",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _process_milestone_file(
|
|
63
|
+
issue_file: Path, core, milestone_folder: Path, orphaned_list: list
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Process single file in milestone folder for orphaned issues.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
issue_file: File to process
|
|
69
|
+
core: RoadmapCore instance
|
|
70
|
+
milestone_folder: Milestone folder path
|
|
71
|
+
orphaned_list: List to append orphaned issues to
|
|
72
|
+
"""
|
|
73
|
+
if ".backup" in issue_file.name:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
issue_id = extract_issue_id(issue_file.name)
|
|
78
|
+
if not issue_id:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
issue = core.issue_service.get_issue(issue_id)
|
|
82
|
+
if issue and not issue.milestone:
|
|
83
|
+
orphaned_list.append(
|
|
84
|
+
{
|
|
85
|
+
"issue_id": issue.id,
|
|
86
|
+
"title": issue.title,
|
|
87
|
+
"location": str(issue_file),
|
|
88
|
+
"folder": milestone_folder.name,
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.debug(
|
|
93
|
+
"milestone_folder_check_failed",
|
|
94
|
+
operation="check_milestone_folders",
|
|
95
|
+
error=str(e),
|
|
96
|
+
action="Continuing to next folder",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _check_milestone_folders(issues_dir: Path, core, orphaned_list: list) -> None:
|
|
101
|
+
"""Check milestone folders for orphaned issues."""
|
|
102
|
+
for milestone_folder in issues_dir.glob("*/"):
|
|
103
|
+
if not milestone_folder.is_dir() or milestone_folder.name.startswith("."):
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if milestone_folder.name == "backlog":
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
for issue_file in milestone_folder.glob("*.md"):
|
|
110
|
+
FolderStructureValidator._process_milestone_file(
|
|
111
|
+
issue_file, core, milestone_folder, orphaned_list
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def scan_for_folder_structure_issues(
|
|
116
|
+
issues_dir: Path, core
|
|
117
|
+
) -> dict[str, list[dict]]:
|
|
118
|
+
"""Verify issues are in correct milestone folders.
|
|
119
|
+
|
|
120
|
+
Returns a dict of potential issues:
|
|
121
|
+
- 'misplaced': Issues in root when they belong in a milestone subfolder
|
|
122
|
+
- 'orphaned': Issues with milestone assignments but not in milestone folder
|
|
123
|
+
"""
|
|
124
|
+
misplaced = []
|
|
125
|
+
orphaned = []
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
FolderStructureValidator._check_root_issues(issues_dir, core, misplaced)
|
|
129
|
+
FolderStructureValidator._check_milestone_folders(
|
|
130
|
+
issues_dir, core, orphaned
|
|
131
|
+
)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(
|
|
134
|
+
"folder_structure_check_failed", error=str(e), severity="system_error"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
{
|
|
139
|
+
"misplaced": misplaced,
|
|
140
|
+
"orphaned": orphaned,
|
|
141
|
+
}
|
|
142
|
+
if (misplaced or orphaned)
|
|
143
|
+
else {}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def perform_check() -> tuple[str, str]:
|
|
148
|
+
"""Check folder structure and issue placement.
|
|
149
|
+
|
|
150
|
+
Note: This validator requires core access but is called without it.
|
|
151
|
+
For now, return HEALTHY to avoid blocking health checks.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Tuple of (status, message) describing the health check result
|
|
155
|
+
"""
|
|
156
|
+
issues_dir = Path(".roadmap/issues")
|
|
157
|
+
if not issues_dir.exists():
|
|
158
|
+
return (
|
|
159
|
+
HealthStatus.HEALTHY,
|
|
160
|
+
"Issues directory not found (not initialized yet)",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Simplified check without core access for now
|
|
164
|
+
# Full check requires core.issue_service which we don't have in this context
|
|
165
|
+
return HealthStatus.HEALTHY, "Folder structure check (simplified)"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Health status utilities for validators.
|
|
2
|
+
|
|
3
|
+
Common utilities for determining overall health status from individual checks.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from roadmap.core.services.validator_base import HealthStatus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_overall_status(checks: dict) -> str:
|
|
10
|
+
"""Get overall status from all validation checks.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
checks: Dictionary of check results with (status, message) tuples
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Overall status: 'healthy', 'degraded', or 'unhealthy'
|
|
17
|
+
"""
|
|
18
|
+
if not checks:
|
|
19
|
+
return HealthStatus.UNHEALTHY
|
|
20
|
+
|
|
21
|
+
statuses = [status for status, _ in checks.values()]
|
|
22
|
+
|
|
23
|
+
if HealthStatus.UNHEALTHY in statuses:
|
|
24
|
+
return HealthStatus.UNHEALTHY
|
|
25
|
+
elif HealthStatus.DEGRADED in statuses:
|
|
26
|
+
return HealthStatus.DEGRADED
|
|
27
|
+
else:
|
|
28
|
+
return HealthStatus.HEALTHY
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Validator for milestone naming conventions."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MilestoneNamingValidator:
|
|
8
|
+
"""Validates milestone names against naming conventions.
|
|
9
|
+
|
|
10
|
+
Convention:
|
|
11
|
+
- Display names should match safe filenames (no conversion needed)
|
|
12
|
+
- Allowed characters: alphanumeric, hyphens, underscores
|
|
13
|
+
- Must start with alphanumeric
|
|
14
|
+
- Recommended patterns:
|
|
15
|
+
- Versions: v0-7-0, v0-8-0, v1-0-0 (hyphens, unambiguous)
|
|
16
|
+
- Sprints: sprint-1, sprint-q1-2025
|
|
17
|
+
- Phases: phase-1, phase-beta
|
|
18
|
+
- Releases: release-dec-2025, release-2025-q1
|
|
19
|
+
- Backlog: backlog
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Pattern: alphanumeric, hyphens, underscores. Can contain dots only in version pattern
|
|
23
|
+
# Examples: v0-7-0, sprint-1, phase-beta, backlog
|
|
24
|
+
# Note: v070, v080, v100 are deprecated due to ambiguity (v100 = v1.0.0 or v10.0?)
|
|
25
|
+
VALID_PATTERN = re.compile(
|
|
26
|
+
r"^[a-zA-Z0-9][a-zA-Z0-9._\-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def is_valid_name(name: str) -> bool:
|
|
31
|
+
"""Check if a milestone name is valid.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name: The milestone name to validate
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if valid, False otherwise
|
|
38
|
+
"""
|
|
39
|
+
if not name or len(name) == 0:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Must match pattern
|
|
43
|
+
if not MilestoneNamingValidator.VALID_PATTERN.match(name):
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# Should not have consecutive hyphens or underscores
|
|
47
|
+
if "--" in name or "__" in name:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def get_safe_name(name: str) -> str:
|
|
54
|
+
"""Convert a milestone name to safe filename format.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: The milestone name
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Safe filename version (lowercase, spaces→hyphens, etc.)
|
|
61
|
+
"""
|
|
62
|
+
# Remove/replace invalid chars
|
|
63
|
+
safe = "".join(c for c in name if c.isalnum() or c in (".", "-", "_")).strip()
|
|
64
|
+
|
|
65
|
+
# Replace spaces with hyphens
|
|
66
|
+
safe = safe.replace(" ", "-")
|
|
67
|
+
|
|
68
|
+
# Lowercase
|
|
69
|
+
safe = safe.lower()
|
|
70
|
+
|
|
71
|
+
return safe
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def validate_with_feedback(name: str) -> tuple[bool, str | None]:
|
|
75
|
+
"""Validate a name and provide feedback.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: The milestone name to validate
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Tuple of (is_valid, error_message)
|
|
82
|
+
If valid, error_message is None
|
|
83
|
+
"""
|
|
84
|
+
if not name:
|
|
85
|
+
return False, "Milestone name cannot be empty"
|
|
86
|
+
|
|
87
|
+
if len(name) > 100:
|
|
88
|
+
return False, "Milestone name must be 100 characters or less"
|
|
89
|
+
|
|
90
|
+
if not MilestoneNamingValidator.VALID_PATTERN.match(name):
|
|
91
|
+
return False, (
|
|
92
|
+
"Milestone name must contain only alphanumeric characters, "
|
|
93
|
+
"hyphens, underscores, and dots. "
|
|
94
|
+
f"Got: '{name}'. "
|
|
95
|
+
"Examples: v0-7-0, v1-0-0, sprint-1, phase-beta, backlog"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if "--" in name or "__" in name:
|
|
99
|
+
return (
|
|
100
|
+
False,
|
|
101
|
+
"Milestone name cannot contain consecutive hyphens or underscores",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Warn if safe name differs from display name (lossy conversion)
|
|
105
|
+
safe = MilestoneNamingValidator.get_safe_name(name)
|
|
106
|
+
if safe != name:
|
|
107
|
+
return False, (
|
|
108
|
+
f"Milestone name will be converted to '{safe}' for filesystem. "
|
|
109
|
+
"To avoid confusion, use the filesystem name directly. "
|
|
110
|
+
f"Suggested: '{safe}'"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return True, None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def find_naming_conflicts(milestones_dir: Path) -> list[tuple[str, str]]:
|
|
117
|
+
"""Find milestone names that could create naming conflicts.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
milestones_dir: Path to .roadmap/milestones/
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of (display_name, safe_name) pairs that might conflict
|
|
124
|
+
"""
|
|
125
|
+
conflicts = []
|
|
126
|
+
safe_name_to_display = {}
|
|
127
|
+
|
|
128
|
+
if not milestones_dir.exists():
|
|
129
|
+
return conflicts
|
|
130
|
+
|
|
131
|
+
for f in milestones_dir.glob("*.md"):
|
|
132
|
+
display_name = f.stem
|
|
133
|
+
safe_name = MilestoneNamingValidator.get_safe_name(display_name)
|
|
134
|
+
|
|
135
|
+
if safe_name != display_name:
|
|
136
|
+
conflicts.append((display_name, safe_name))
|
|
137
|
+
|
|
138
|
+
# Check for collision (two different display names → same safe name)
|
|
139
|
+
if safe_name in safe_name_to_display:
|
|
140
|
+
other = safe_name_to_display[safe_name]
|
|
141
|
+
if other != display_name:
|
|
142
|
+
conflicts.append((display_name, safe_name))
|
|
143
|
+
else:
|
|
144
|
+
safe_name_to_display[safe_name] = display_name
|
|
145
|
+
|
|
146
|
+
return conflicts
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Validator for missing headlines in issues, milestones, and projects."""
|
|
2
|
+
|
|
3
|
+
from roadmap.common.logging import get_logger
|
|
4
|
+
from roadmap.core.services.validator_base import BaseValidator, HealthStatus
|
|
5
|
+
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MissingHeadlinesValidator(BaseValidator):
|
|
10
|
+
"""Validator for missing headlines in entities."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_check_name() -> str:
|
|
14
|
+
"""Get the name of this check.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
String identifier for the missing_headlines check.
|
|
18
|
+
"""
|
|
19
|
+
return "missing_headlines"
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def _collect_missing_headline_ids(list_callable, missing_list: list) -> None:
|
|
23
|
+
"""Append IDs of entities with empty headlines to *missing_list*."""
|
|
24
|
+
try:
|
|
25
|
+
for entity in list_callable():
|
|
26
|
+
if not entity.headline or entity.headline.strip() == "":
|
|
27
|
+
missing_list.append(entity.id)
|
|
28
|
+
except (AttributeError, TypeError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def check_missing_headlines(core) -> tuple[str, str]:
|
|
33
|
+
"""Check for entities with missing or empty headlines.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Tuple of (status, message) describing the health check result
|
|
37
|
+
"""
|
|
38
|
+
missing_entities: dict[str, list] = {
|
|
39
|
+
"issues": [],
|
|
40
|
+
"milestones": [],
|
|
41
|
+
"projects": [],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
MissingHeadlinesValidator._collect_missing_headline_ids(
|
|
46
|
+
core.issue_service.list_issues, missing_entities["issues"]
|
|
47
|
+
)
|
|
48
|
+
MissingHeadlinesValidator._collect_missing_headline_ids(
|
|
49
|
+
core.milestone_service.list_milestones, missing_entities["milestones"]
|
|
50
|
+
)
|
|
51
|
+
MissingHeadlinesValidator._collect_missing_headline_ids(
|
|
52
|
+
core.project_service.list_projects, missing_entities["projects"]
|
|
53
|
+
)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.debug("error_checking_headlines", error=str(e))
|
|
56
|
+
return (
|
|
57
|
+
HealthStatus.DEGRADED,
|
|
58
|
+
f"Error checking headlines: {str(e)}",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Count missing headlines
|
|
62
|
+
total_missing = (
|
|
63
|
+
len(missing_entities["issues"])
|
|
64
|
+
+ len(missing_entities["milestones"])
|
|
65
|
+
+ len(missing_entities["projects"])
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if total_missing == 0:
|
|
69
|
+
return HealthStatus.HEALTHY, "All entities have headlines"
|
|
70
|
+
|
|
71
|
+
# Build detailed message
|
|
72
|
+
parts = []
|
|
73
|
+
if missing_entities["issues"]:
|
|
74
|
+
parts.append(f"{len(missing_entities['issues'])} issue(s)")
|
|
75
|
+
if missing_entities["milestones"]:
|
|
76
|
+
parts.append(f"{len(missing_entities['milestones'])} milestone(s)")
|
|
77
|
+
if missing_entities["projects"]:
|
|
78
|
+
parts.append(f"{len(missing_entities['projects'])} project(s)")
|
|
79
|
+
|
|
80
|
+
message = (
|
|
81
|
+
f"⚠️ {total_missing} entity(ies) missing headlines: {', '.join(parts)}. "
|
|
82
|
+
"Run 'roadmap fix missing-headlines' to auto-populate from content"
|
|
83
|
+
)
|
|
84
|
+
return HealthStatus.DEGRADED, message
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def perform_check(core=None) -> tuple[str, str]:
|
|
88
|
+
"""Check for missing headlines.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
core: RoadmapCore instance (optional, for backward compatibility)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple of (status, message) describing the health check result
|
|
95
|
+
"""
|
|
96
|
+
# For now, return healthy - actual implementation will use core
|
|
97
|
+
return HealthStatus.HEALTHY, "Headlines validation not yet fully integrated"
|