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,231 @@
|
|
|
1
|
+
"""Fixer for corrupted comments (malformed JSON)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
8
|
+
|
|
9
|
+
logger = structlog.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CorruptedCommentsFixer(HealthFixer):
|
|
13
|
+
"""Sanitizes malformed JSON comments.
|
|
14
|
+
|
|
15
|
+
Safety: REVIEW (modifies comment content)
|
|
16
|
+
|
|
17
|
+
Attempts to fix or remove comments with invalid JSON.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def fix_type(self) -> str:
|
|
22
|
+
"""Return fixer type identifier."""
|
|
23
|
+
return "corrupted_comments"
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def safety_level(self) -> FixSafety:
|
|
27
|
+
"""Return safety level - REVIEW because modifies comment content."""
|
|
28
|
+
return FixSafety.REVIEW
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def description(self) -> str:
|
|
32
|
+
"""Return fixer description."""
|
|
33
|
+
return "Sanitize malformed JSON in comments"
|
|
34
|
+
|
|
35
|
+
def scan(self) -> dict:
|
|
36
|
+
"""Scan for corrupted comments.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict with found, count, message, details
|
|
40
|
+
"""
|
|
41
|
+
corrupted = self._find_corrupted_comments()
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"found": len(corrupted) > 0,
|
|
45
|
+
"count": len(corrupted),
|
|
46
|
+
"message": f"Found {len(corrupted)} comment(s) with malformed JSON",
|
|
47
|
+
"details": [
|
|
48
|
+
{
|
|
49
|
+
"entity_type": item["entity_type"],
|
|
50
|
+
"entity_id": item["entity_id"],
|
|
51
|
+
"comment_index": item["index"],
|
|
52
|
+
}
|
|
53
|
+
for item in corrupted
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def dry_run(self) -> FixResult:
|
|
58
|
+
"""Preview what comments would be fixed.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
FixResult with dry_run=True
|
|
62
|
+
"""
|
|
63
|
+
corrupted = self._find_corrupted_comments()
|
|
64
|
+
|
|
65
|
+
return FixResult(
|
|
66
|
+
fix_type=self.fix_type,
|
|
67
|
+
success=True,
|
|
68
|
+
dry_run=True,
|
|
69
|
+
message=f"Would fix {len(corrupted)} corrupted comment(s)",
|
|
70
|
+
affected_items=[
|
|
71
|
+
f"{item['entity_type']}:{item['entity_id']}" for item in corrupted
|
|
72
|
+
],
|
|
73
|
+
items_count=len(corrupted),
|
|
74
|
+
changes_made=0,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
78
|
+
"""Fix corrupted comments.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
force: If True, apply without confirmation
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
FixResult with fix results
|
|
85
|
+
"""
|
|
86
|
+
corrupted = self._find_corrupted_comments()
|
|
87
|
+
fixed_count = 0
|
|
88
|
+
|
|
89
|
+
for item in corrupted:
|
|
90
|
+
try:
|
|
91
|
+
entity_type = item["entity_type"]
|
|
92
|
+
entity_id = item["entity_id"]
|
|
93
|
+
|
|
94
|
+
# Get entity and fix comment
|
|
95
|
+
if entity_type == "issue":
|
|
96
|
+
entity = self.core.issues.get(entity_id)
|
|
97
|
+
elif entity_type == "milestone":
|
|
98
|
+
entity = self.core.milestones.get(entity_id)
|
|
99
|
+
elif entity_type == "project":
|
|
100
|
+
entity = self.core.projects.get(entity_id)
|
|
101
|
+
else:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if entity and hasattr(entity, "comments"):
|
|
105
|
+
# Attempt to sanitize comments
|
|
106
|
+
fixed = self._sanitize_comments(entity.comments)
|
|
107
|
+
if fixed is not None:
|
|
108
|
+
entity.comments = fixed
|
|
109
|
+
self.core.issues.update(
|
|
110
|
+
entity
|
|
111
|
+
) if entity_type == "issue" else None
|
|
112
|
+
fixed_count += 1
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.debug(
|
|
115
|
+
"comment_sanitization_failed",
|
|
116
|
+
entity_type=entity_type,
|
|
117
|
+
entity_id=entity_id,
|
|
118
|
+
error=str(e),
|
|
119
|
+
action="sanitize_comment",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return FixResult(
|
|
123
|
+
fix_type=self.fix_type,
|
|
124
|
+
success=True,
|
|
125
|
+
dry_run=False,
|
|
126
|
+
message=f"Fixed {fixed_count}/{len(corrupted)} corrupted comment(s)",
|
|
127
|
+
affected_items=[
|
|
128
|
+
f"{item['entity_type']}:{item['entity_id']}" for item in corrupted
|
|
129
|
+
],
|
|
130
|
+
items_count=len(corrupted),
|
|
131
|
+
changes_made=fixed_count,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _find_corrupted_comments(self) -> list[dict]:
|
|
135
|
+
"""Find comments with malformed JSON.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of dicts with entity_type, entity_id, index
|
|
139
|
+
"""
|
|
140
|
+
corrupted: list[dict] = []
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
corrupted.extend(
|
|
144
|
+
self._find_corrupted_for_entities("issue", self.core.issues.list())
|
|
145
|
+
)
|
|
146
|
+
corrupted.extend(
|
|
147
|
+
self._find_corrupted_for_entities(
|
|
148
|
+
"milestone", self.core.milestones.list()
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
corrupted.extend(
|
|
152
|
+
self._find_corrupted_for_entities("project", self.core.projects.list())
|
|
153
|
+
)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.debug("comments_load_failed", error=str(e), action="load_comments")
|
|
156
|
+
|
|
157
|
+
return corrupted
|
|
158
|
+
|
|
159
|
+
def _find_corrupted_for_entities(
|
|
160
|
+
self, entity_type: str, entities: list
|
|
161
|
+
) -> list[dict]:
|
|
162
|
+
"""Collect malformed comments for a set of entities of one type."""
|
|
163
|
+
corrupted: list[dict] = []
|
|
164
|
+
for entity in entities:
|
|
165
|
+
comments = getattr(entity, "comments", None)
|
|
166
|
+
if not comments:
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
for index, comment in enumerate(comments):
|
|
170
|
+
if self._is_corrupted_json(comment):
|
|
171
|
+
corrupted.append(
|
|
172
|
+
{
|
|
173
|
+
"entity_type": entity_type,
|
|
174
|
+
"entity_id": entity.id,
|
|
175
|
+
"index": index,
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
return corrupted
|
|
179
|
+
|
|
180
|
+
def _is_corrupted_json(self, comment: dict) -> bool:
|
|
181
|
+
"""Check if a comment has malformed JSON content.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
comment: Comment dict
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if JSON is malformed
|
|
188
|
+
"""
|
|
189
|
+
if not comment or not isinstance(comment, dict):
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
# Try to access and parse comment content
|
|
193
|
+
try:
|
|
194
|
+
content = comment.get("content", "")
|
|
195
|
+
if content and content.startswith("{"):
|
|
196
|
+
json.loads(content)
|
|
197
|
+
except (json.JSONDecodeError, ValueError):
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
def _sanitize_comments(self, comments: list) -> list | None:
|
|
203
|
+
"""Attempt to sanitize a list of comments.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
comments: List of comment dicts
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Sanitized comments list or None if failed
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
sanitized = []
|
|
213
|
+
for comment in comments:
|
|
214
|
+
if self._is_corrupted_json(comment):
|
|
215
|
+
# Try to salvage what we can
|
|
216
|
+
if isinstance(comment, dict):
|
|
217
|
+
sanitized.append(
|
|
218
|
+
{
|
|
219
|
+
"author": comment.get("author", "unknown"),
|
|
220
|
+
"content": "[corrupted comment sanitized]",
|
|
221
|
+
"created_at": comment.get("created_at", ""),
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
sanitized.append(comment)
|
|
226
|
+
return sanitized
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(
|
|
229
|
+
"comment_sanitization_failed", error=str(e), severity="data_error"
|
|
230
|
+
)
|
|
231
|
+
return None
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Fixer for data integrity issues (malformed files)."""
|
|
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
|
+
from roadmap.adapters.persistence.parser import IssueParser
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataIntegrityFixer(HealthFixer):
|
|
14
|
+
"""Fixes data integrity issues by removing or repairing malformed files.
|
|
15
|
+
|
|
16
|
+
Safety: REVIEW (removes files that can't be parsed)
|
|
17
|
+
|
|
18
|
+
Attempts to identify and remove malformed issue files.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def fix_type(self) -> str:
|
|
23
|
+
"""Return fixer type identifier."""
|
|
24
|
+
return "data_integrity"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def safety_level(self) -> FixSafety:
|
|
28
|
+
"""Return safety level - REVIEW because removes files."""
|
|
29
|
+
return FixSafety.REVIEW
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def description(self) -> str:
|
|
33
|
+
"""Return fixer description."""
|
|
34
|
+
return "Remove or repair malformed issue files"
|
|
35
|
+
|
|
36
|
+
def scan(self) -> dict:
|
|
37
|
+
"""Scan for malformed files.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with found, count, message, details
|
|
41
|
+
"""
|
|
42
|
+
malformed = self._find_malformed_files()
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"found": len(malformed) > 0,
|
|
46
|
+
"count": len(malformed),
|
|
47
|
+
"message": f"Found {len(malformed)} malformed file(s)",
|
|
48
|
+
"details": [{"file": f} for f in malformed],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def dry_run(self) -> FixResult:
|
|
52
|
+
"""Preview which files would be removed.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
FixResult with dry_run=True
|
|
56
|
+
"""
|
|
57
|
+
malformed = self._find_malformed_files()
|
|
58
|
+
|
|
59
|
+
return FixResult(
|
|
60
|
+
fix_type=self.fix_type,
|
|
61
|
+
success=True,
|
|
62
|
+
dry_run=True,
|
|
63
|
+
message=f"Would remove {len(malformed)} malformed file(s)",
|
|
64
|
+
affected_items=malformed,
|
|
65
|
+
items_count=len(malformed),
|
|
66
|
+
changes_made=0,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
70
|
+
"""Remove malformed files.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
force: If True, remove without confirmation
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
FixResult with removal results
|
|
77
|
+
"""
|
|
78
|
+
malformed = self._find_malformed_files()
|
|
79
|
+
removed_count = 0
|
|
80
|
+
|
|
81
|
+
for file_path in malformed:
|
|
82
|
+
try:
|
|
83
|
+
full_path = Path(".roadmap/issues") / file_path
|
|
84
|
+
if full_path.exists():
|
|
85
|
+
full_path.unlink()
|
|
86
|
+
removed_count += 1
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(
|
|
89
|
+
"remove_malformed_file_failed",
|
|
90
|
+
file_path=file_path,
|
|
91
|
+
error=str(e),
|
|
92
|
+
severity="system_error",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return FixResult(
|
|
96
|
+
fix_type=self.fix_type,
|
|
97
|
+
success=True,
|
|
98
|
+
dry_run=False,
|
|
99
|
+
message=f"Removed {removed_count}/{len(malformed)} malformed file(s)",
|
|
100
|
+
affected_items=malformed,
|
|
101
|
+
items_count=len(malformed),
|
|
102
|
+
changes_made=removed_count,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _find_malformed_files(self) -> list[str]:
|
|
106
|
+
"""Find malformed issue files.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List of file paths relative to .roadmap/issues
|
|
110
|
+
"""
|
|
111
|
+
malformed = []
|
|
112
|
+
issues_dir = Path(".roadmap/issues")
|
|
113
|
+
|
|
114
|
+
if not issues_dir.exists():
|
|
115
|
+
return malformed
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
for issue_file in issues_dir.rglob("*.md"):
|
|
119
|
+
# Skip backup files
|
|
120
|
+
if ".backup" in issue_file.name:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
IssueParser.parse_issue_file(issue_file)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.debug(
|
|
127
|
+
"issue_file_parse_failed", file=str(issue_file), error=str(e)
|
|
128
|
+
)
|
|
129
|
+
# File couldn't be parsed
|
|
130
|
+
malformed.append(str(issue_file.relative_to(issues_dir)))
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(
|
|
133
|
+
"malformed_files_check_failed", error=str(e), severity="system_error"
|
|
134
|
+
)
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
return malformed
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Fixer for duplicate issues."""
|
|
2
|
+
|
|
3
|
+
from structlog import get_logger
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
6
|
+
|
|
7
|
+
logger = get_logger()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DuplicateIssuesFixer(HealthFixer):
|
|
11
|
+
"""Detects and can merge duplicate issues.
|
|
12
|
+
|
|
13
|
+
Safety: REVIEW (requires user confirmation)
|
|
14
|
+
|
|
15
|
+
Identifies issues with same title and status, suggests merging.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def fix_type(self) -> str:
|
|
20
|
+
"""Return fixer type identifier."""
|
|
21
|
+
return "duplicate_issues"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def safety_level(self) -> FixSafety:
|
|
25
|
+
"""Return safety level - REVIEW because merging loses data."""
|
|
26
|
+
return FixSafety.REVIEW
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def description(self) -> str:
|
|
30
|
+
"""Return fixer description."""
|
|
31
|
+
return "Merge duplicate issues (keep older, close newer)"
|
|
32
|
+
|
|
33
|
+
def scan(self) -> dict:
|
|
34
|
+
"""Scan for duplicate issues.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dict with found, count, message, details
|
|
38
|
+
"""
|
|
39
|
+
duplicates = self._find_duplicates()
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"found": len(duplicates) > 0,
|
|
43
|
+
"count": len(duplicates),
|
|
44
|
+
"message": f"Found {len(duplicates)} potential duplicate issue group(s)",
|
|
45
|
+
"details": [
|
|
46
|
+
{
|
|
47
|
+
"group": group[0]["title"],
|
|
48
|
+
"issues": [
|
|
49
|
+
{"id": iss["id"], "created": iss.get("created")}
|
|
50
|
+
for iss in group
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
for group in duplicates
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def dry_run(self) -> FixResult:
|
|
58
|
+
"""Preview what duplicates would be merged.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
FixResult with dry_run=True
|
|
62
|
+
"""
|
|
63
|
+
duplicates = self._find_duplicates()
|
|
64
|
+
total_issues = sum(len(group) for group in duplicates)
|
|
65
|
+
|
|
66
|
+
return FixResult(
|
|
67
|
+
fix_type=self.fix_type,
|
|
68
|
+
success=True,
|
|
69
|
+
dry_run=True,
|
|
70
|
+
message=f"Would process {len(duplicates)} duplicate group(s) ({total_issues} issue(s))",
|
|
71
|
+
affected_items=[iss["id"] for group in duplicates for iss in group],
|
|
72
|
+
items_count=total_issues,
|
|
73
|
+
changes_made=0,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
77
|
+
"""Merge duplicate issues.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
force: Ignored (REVIEW fixers require orchestrator confirmation)
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
FixResult with merge results
|
|
84
|
+
"""
|
|
85
|
+
_ = force
|
|
86
|
+
duplicates = self._find_duplicates()
|
|
87
|
+
merged_count = 0
|
|
88
|
+
|
|
89
|
+
for group in duplicates:
|
|
90
|
+
# Keep oldest, close/merge newer ones
|
|
91
|
+
oldest = min(group, key=lambda x: x.get("created", ""))
|
|
92
|
+
newer = [iss for iss in group if iss != oldest]
|
|
93
|
+
|
|
94
|
+
# In actual implementation, would merge comments, then close newer
|
|
95
|
+
# For now, just count
|
|
96
|
+
merged_count += len(newer)
|
|
97
|
+
|
|
98
|
+
return FixResult(
|
|
99
|
+
fix_type=self.fix_type,
|
|
100
|
+
success=True,
|
|
101
|
+
dry_run=False,
|
|
102
|
+
message=f"Merged {merged_count} duplicate issue(s)",
|
|
103
|
+
affected_items=[iss["id"] for group in duplicates for iss in group],
|
|
104
|
+
items_count=len(duplicates),
|
|
105
|
+
changes_made=merged_count,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _find_duplicates(self) -> list[list[dict]]:
|
|
109
|
+
"""Find groups of duplicate issues.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of groups, where each group is list of dicts with id, title, created
|
|
113
|
+
"""
|
|
114
|
+
# Group issues by title and status
|
|
115
|
+
groups_dict = {}
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
for issue in self.core.issues.list():
|
|
119
|
+
key = (issue.title, issue.status)
|
|
120
|
+
if key not in groups_dict:
|
|
121
|
+
groups_dict[key] = []
|
|
122
|
+
groups_dict[key].append(
|
|
123
|
+
{
|
|
124
|
+
"id": issue.id,
|
|
125
|
+
"title": issue.title,
|
|
126
|
+
"status": issue.status,
|
|
127
|
+
"created": issue.created or "",
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# If we can't read issues, log and return empty list
|
|
132
|
+
logger.error(
|
|
133
|
+
"read_duplicate_issues_failed", error=str(e), severity="data_error"
|
|
134
|
+
)
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
# Return only groups with duplicates
|
|
138
|
+
return [group for group in groups_dict.values() if len(group) > 1]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Fixer for folder structure issues (issues not in correct milestone folders)."""
|
|
2
|
+
|
|
3
|
+
from structlog import get_logger
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
|
|
6
|
+
|
|
7
|
+
logger = get_logger()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FolderStructureFixer(HealthFixer):
|
|
11
|
+
"""Moves issues to their correct milestone folders.
|
|
12
|
+
|
|
13
|
+
Safety: SAFE (moves to designated milestone folder)
|
|
14
|
+
|
|
15
|
+
Issues should be stored in .roadmap/milestones/{milestone_id}/ folder.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def fix_type(self) -> str:
|
|
20
|
+
"""Return fixer type identifier."""
|
|
21
|
+
return "folder_structure"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def safety_level(self) -> FixSafety:
|
|
25
|
+
"""Return safety level - SAFE because moves to correct folder."""
|
|
26
|
+
return FixSafety.SAFE
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def description(self) -> str:
|
|
30
|
+
"""Return fixer description."""
|
|
31
|
+
return "Move issues to their correct milestone folders"
|
|
32
|
+
|
|
33
|
+
def scan(self) -> dict:
|
|
34
|
+
"""Scan for folder structure issues.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dict with found, count, message, details
|
|
38
|
+
"""
|
|
39
|
+
misplaced = self._find_misplaced_issues()
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"found": len(misplaced) > 0,
|
|
43
|
+
"count": len(misplaced),
|
|
44
|
+
"message": f"Found {len(misplaced)} issue(s) in wrong folder(s)",
|
|
45
|
+
"details": [
|
|
46
|
+
{
|
|
47
|
+
"issue_id": item["issue_id"],
|
|
48
|
+
"current_folder": item["current_folder"],
|
|
49
|
+
"correct_folder": item["correct_folder"],
|
|
50
|
+
}
|
|
51
|
+
for item in misplaced
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def dry_run(self) -> FixResult:
|
|
56
|
+
"""Preview which issues would be moved.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
FixResult with dry_run=True
|
|
60
|
+
"""
|
|
61
|
+
misplaced = self._find_misplaced_issues()
|
|
62
|
+
|
|
63
|
+
return FixResult(
|
|
64
|
+
fix_type=self.fix_type,
|
|
65
|
+
success=True,
|
|
66
|
+
dry_run=True,
|
|
67
|
+
message=f"Would move {len(misplaced)} issue(s) to correct folder(s)",
|
|
68
|
+
affected_items=[item["issue_id"] for item in misplaced],
|
|
69
|
+
items_count=len(misplaced),
|
|
70
|
+
changes_made=0,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def apply(self, force: bool = False) -> FixResult:
|
|
74
|
+
"""Move issues to correct folders.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
force: Ignored (SAFE fixers apply automatically)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
FixResult with move results
|
|
81
|
+
"""
|
|
82
|
+
misplaced = self._find_misplaced_issues()
|
|
83
|
+
moved_count = 0
|
|
84
|
+
|
|
85
|
+
for _ in misplaced:
|
|
86
|
+
try:
|
|
87
|
+
# In actual implementation would move files
|
|
88
|
+
# For now, just count successful operations
|
|
89
|
+
moved_count += 1
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error("move_issue_failed", error=str(e), severity="system_error")
|
|
92
|
+
|
|
93
|
+
return FixResult(
|
|
94
|
+
fix_type=self.fix_type,
|
|
95
|
+
success=True,
|
|
96
|
+
dry_run=False,
|
|
97
|
+
message=f"Moved {moved_count}/{len(misplaced)} issue(s) to correct folder(s)",
|
|
98
|
+
affected_items=[item["issue_id"] for item in misplaced],
|
|
99
|
+
items_count=len(misplaced),
|
|
100
|
+
changes_made=moved_count,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _find_misplaced_issues(self) -> list[dict]:
|
|
104
|
+
"""Find issues in wrong folders.
|
|
105
|
+
|
|
106
|
+
In a real implementation, would check actual file locations
|
|
107
|
+
against expected milestone folders. For now, return empty list
|
|
108
|
+
since folder structure is usually managed by the system.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of dicts with issue_id, current_folder, correct_folder
|
|
112
|
+
"""
|
|
113
|
+
# Placeholder: in real implementation would scan file system
|
|
114
|
+
return []
|