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,96 @@
|
|
|
1
|
+
"""ProjectArchive command implementation using base class."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseArchive, EntityType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProjectArchive(BaseArchive):
|
|
10
|
+
"""Archive project command implementation."""
|
|
11
|
+
|
|
12
|
+
entity_type = EntityType.PROJECT
|
|
13
|
+
|
|
14
|
+
def get_entities_to_archive(
|
|
15
|
+
self, entity_id: str | None = None, **kwargs
|
|
16
|
+
) -> list[Any]:
|
|
17
|
+
"""Get projects to archive.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
entity_id: Specific project ID to archive
|
|
21
|
+
**kwargs: Additional arguments
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of projects to archive
|
|
25
|
+
"""
|
|
26
|
+
if entity_id:
|
|
27
|
+
project = self.core.projects.get(entity_id)
|
|
28
|
+
return [project] if project else []
|
|
29
|
+
|
|
30
|
+
if kwargs.get("all_closed"):
|
|
31
|
+
return [
|
|
32
|
+
project
|
|
33
|
+
for project in self.core.projects.list()
|
|
34
|
+
if getattr(project.status, "value", project.status) == "completed"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
def validate_entity_before_archive(
|
|
40
|
+
self, entity: Any, **kwargs
|
|
41
|
+
) -> tuple[bool, str | None]:
|
|
42
|
+
"""Validate project before archiving.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
entity: The project to validate
|
|
46
|
+
**kwargs: Additional arguments (e.g., force flag)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Tuple of (is_valid, error_message)
|
|
50
|
+
"""
|
|
51
|
+
if kwargs.get("force"):
|
|
52
|
+
return True, None
|
|
53
|
+
|
|
54
|
+
status = getattr(entity.status, "value", entity.status)
|
|
55
|
+
if status != "completed":
|
|
56
|
+
return False, f"not completed (status: {status})"
|
|
57
|
+
return True, None
|
|
58
|
+
|
|
59
|
+
def find_entity_files(self, entities: list[Any]) -> list[Path]:
|
|
60
|
+
"""Find project files for entities.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
entities: List of projects
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of file paths
|
|
67
|
+
"""
|
|
68
|
+
roadmap_dir = Path.cwd() / ".roadmap"
|
|
69
|
+
projects_dir = roadmap_dir / "projects"
|
|
70
|
+
|
|
71
|
+
files = []
|
|
72
|
+
for entity in entities:
|
|
73
|
+
# Search for project file by ID prefix
|
|
74
|
+
matching = list(projects_dir.glob(f"{str(entity.id)[:8]}*.md"))
|
|
75
|
+
if matching:
|
|
76
|
+
files.append(matching[0])
|
|
77
|
+
return files
|
|
78
|
+
|
|
79
|
+
def post_archive_hook(
|
|
80
|
+
self, archived_files: list[Path], entities: list[Any], **kwargs
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Handle post-archive state updates.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
archived_files: Files that were archived
|
|
86
|
+
entities: Entities that were archived
|
|
87
|
+
**kwargs: Additional arguments
|
|
88
|
+
"""
|
|
89
|
+
for entity in entities:
|
|
90
|
+
try:
|
|
91
|
+
self.core.db.mark_project_archived(entity.id, archived=True)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
self.console.print(
|
|
94
|
+
f"⚠️ Warning: Failed to mark project {entity.id} as archived: {e}",
|
|
95
|
+
style="yellow",
|
|
96
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Close project command - sets status to closed without archiving."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
from roadmap.common.formatters.text.operations import (
|
|
9
|
+
format_operation_failure,
|
|
10
|
+
format_operation_success,
|
|
11
|
+
)
|
|
12
|
+
from roadmap.common.logging import (
|
|
13
|
+
log_command,
|
|
14
|
+
track_database_operation,
|
|
15
|
+
)
|
|
16
|
+
from roadmap.core.domain import ProjectStatus
|
|
17
|
+
|
|
18
|
+
console = get_console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command("close")
|
|
22
|
+
@click.argument("project_id")
|
|
23
|
+
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@require_initialized
|
|
26
|
+
@log_command("project_close", entity_type="project", track_duration=True)
|
|
27
|
+
def close_project(ctx: click.Context, project_id: str, force: bool):
|
|
28
|
+
"""Close a project (sets status to closed).
|
|
29
|
+
|
|
30
|
+
This command marks a project as completed while keeping its file active.
|
|
31
|
+
Use `roadmap project archive` as the separate cleanup step when the
|
|
32
|
+
project file should move under `.roadmap/archive/projects/`.
|
|
33
|
+
"""
|
|
34
|
+
core = ctx.obj["core"]
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
# Check if project exists
|
|
38
|
+
project = core.projects.get(project_id)
|
|
39
|
+
if not project:
|
|
40
|
+
lines = format_operation_failure("Close", project_id, "Project not found")
|
|
41
|
+
for line in lines:
|
|
42
|
+
console.print(line, style="bold red")
|
|
43
|
+
raise click.Abort()
|
|
44
|
+
|
|
45
|
+
# Confirm closure
|
|
46
|
+
if not force:
|
|
47
|
+
if not click.confirm(f"Close project '{project.name}'?"):
|
|
48
|
+
console.print("❌ Project close cancelled.", style="yellow")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# Close project in database
|
|
52
|
+
with track_database_operation(
|
|
53
|
+
"update", "project", entity_id=project_id, warn_threshold_ms=2000
|
|
54
|
+
):
|
|
55
|
+
updated_project = core.projects.update(
|
|
56
|
+
project_id, status=ProjectStatus.COMPLETED
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if updated_project:
|
|
60
|
+
extra_details = {"Status": "Closed"}
|
|
61
|
+
lines = format_operation_success(
|
|
62
|
+
emoji="✅",
|
|
63
|
+
action="Closed",
|
|
64
|
+
entity_title=updated_project.name,
|
|
65
|
+
entity_id=project_id,
|
|
66
|
+
extra_details=extra_details,
|
|
67
|
+
)
|
|
68
|
+
for line in lines:
|
|
69
|
+
console.print(line, style="bold green" if "Closed" in line else "cyan")
|
|
70
|
+
else:
|
|
71
|
+
lines = format_operation_failure(
|
|
72
|
+
action="close",
|
|
73
|
+
entity_id=project_id,
|
|
74
|
+
error="Failed to update status",
|
|
75
|
+
)
|
|
76
|
+
for line in lines:
|
|
77
|
+
console.print(line, style="bold red")
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
handle_cli_error(
|
|
81
|
+
error=e,
|
|
82
|
+
operation="close_project",
|
|
83
|
+
entity_type="project",
|
|
84
|
+
entity_id=project_id,
|
|
85
|
+
context={"force": force},
|
|
86
|
+
fatal=True,
|
|
87
|
+
)
|
|
88
|
+
lines = format_operation_failure(
|
|
89
|
+
action="close",
|
|
90
|
+
entity_id=project_id,
|
|
91
|
+
error=str(e),
|
|
92
|
+
)
|
|
93
|
+
for line in lines:
|
|
94
|
+
console.print(line, style="bold red")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Create project command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseCreate, EntityType
|
|
7
|
+
from roadmap.adapters.cli.crud.entity_builders import ProjectBuilder
|
|
8
|
+
from roadmap.common.logging import (
|
|
9
|
+
log_command,
|
|
10
|
+
verbose_output,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProjectCreate(BaseCreate):
|
|
15
|
+
"""Create project command implementation."""
|
|
16
|
+
|
|
17
|
+
entity_type = EntityType.PROJECT
|
|
18
|
+
|
|
19
|
+
def build_entity_dict(self, title: str, **kwargs) -> dict:
|
|
20
|
+
"""Build entity dictionary for project creation."""
|
|
21
|
+
return ProjectBuilder.build_create_dict(
|
|
22
|
+
name=title,
|
|
23
|
+
description=kwargs.get("description"),
|
|
24
|
+
repository=kwargs.get("repository"),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command("create")
|
|
29
|
+
@click.option("--title", "-t", required=True, help="Project title")
|
|
30
|
+
@click.option("--description", "-d", help="Project description")
|
|
31
|
+
@click.option("--repository", "-r", help="Repository URL")
|
|
32
|
+
@click.pass_context
|
|
33
|
+
@require_initialized
|
|
34
|
+
@verbose_output
|
|
35
|
+
@log_command("project_create", entity_type="project", track_duration=True)
|
|
36
|
+
def create_project(
|
|
37
|
+
ctx: click.Context,
|
|
38
|
+
title: str,
|
|
39
|
+
description: str,
|
|
40
|
+
repository: str,
|
|
41
|
+
):
|
|
42
|
+
"""Create a new project."""
|
|
43
|
+
core = ctx.obj["core"]
|
|
44
|
+
creator = ProjectCreate(core)
|
|
45
|
+
|
|
46
|
+
creator.execute(
|
|
47
|
+
title=title,
|
|
48
|
+
description=description,
|
|
49
|
+
repository=repository,
|
|
50
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Delete project command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseDelete, EntityType
|
|
7
|
+
from roadmap.common.logging import log_command
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProjectDelete(BaseDelete):
|
|
11
|
+
"""Delete project command implementation."""
|
|
12
|
+
|
|
13
|
+
entity_type = EntityType.PROJECT
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command("delete")
|
|
17
|
+
@click.argument("project_id")
|
|
18
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
@require_initialized
|
|
21
|
+
@log_command("project_delete", entity_type="project", track_duration=True)
|
|
22
|
+
def delete_project(ctx: click.Context, project_id: str, yes: bool):
|
|
23
|
+
"""Delete a project."""
|
|
24
|
+
core = ctx.obj["core"]
|
|
25
|
+
deleter = ProjectDelete(core)
|
|
26
|
+
|
|
27
|
+
deleter.execute(entity_id=project_id, force=yes)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""List projects command."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
|
|
8
|
+
from roadmap.adapters.cli.decorators import with_output_support
|
|
9
|
+
from roadmap.common.console import get_console
|
|
10
|
+
from roadmap.common.datetime_parser import UnifiedDateTimeParser
|
|
11
|
+
from roadmap.common.formatters import ProjectTableFormatter
|
|
12
|
+
from roadmap.common.logging import verbose_output
|
|
13
|
+
from roadmap.common.models import ColumnType
|
|
14
|
+
|
|
15
|
+
console = get_console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_project_metadata(file_path) -> dict | None:
|
|
19
|
+
"""Parse project metadata from markdown file."""
|
|
20
|
+
try:
|
|
21
|
+
content = file_path.read_text()
|
|
22
|
+
if not content.startswith("---"):
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
yaml_end = content.find("---", 3)
|
|
26
|
+
if yaml_end == -1:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
import yaml
|
|
30
|
+
|
|
31
|
+
yaml_content = content[3:yaml_end]
|
|
32
|
+
metadata = yaml.safe_load(yaml_content)
|
|
33
|
+
|
|
34
|
+
# Add ID from frontmatter if present, or extract from filename
|
|
35
|
+
if isinstance(metadata, dict) and not metadata.get("id"):
|
|
36
|
+
# Extract ID from filename (format: {id}-{name}.md)
|
|
37
|
+
filename_stem = file_path.stem
|
|
38
|
+
if "-" in filename_stem:
|
|
39
|
+
metadata["id"] = filename_stem.split("-", 1)[0]
|
|
40
|
+
|
|
41
|
+
return metadata if isinstance(metadata, dict) else None
|
|
42
|
+
except Exception as e:
|
|
43
|
+
from roadmap.common.logging import get_logger
|
|
44
|
+
|
|
45
|
+
logger = get_logger(__name__)
|
|
46
|
+
logger.debug(
|
|
47
|
+
"project_metadata_parse_failed",
|
|
48
|
+
error=str(e),
|
|
49
|
+
action="parse_project_metadata",
|
|
50
|
+
)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _apply_status_filter(metadata, status):
|
|
55
|
+
"""Check if project matches status filter."""
|
|
56
|
+
if not status:
|
|
57
|
+
return True
|
|
58
|
+
return metadata.get("status") == status
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _apply_owner_filter(metadata, owner):
|
|
62
|
+
"""Check if project matches owner filter."""
|
|
63
|
+
if not owner:
|
|
64
|
+
return True
|
|
65
|
+
return metadata.get("owner") == owner
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _apply_priority_filter(metadata, priority):
|
|
69
|
+
"""Check if project matches priority filter."""
|
|
70
|
+
if not priority:
|
|
71
|
+
return True
|
|
72
|
+
return metadata.get("priority") == priority
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _apply_overdue_filter(metadata):
|
|
76
|
+
"""Check if project is overdue."""
|
|
77
|
+
from datetime import datetime
|
|
78
|
+
|
|
79
|
+
target_end = metadata.get("target_end_date")
|
|
80
|
+
if not target_end:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
if isinstance(target_end, str):
|
|
85
|
+
end_date = UnifiedDateTimeParser.parse_iso_datetime(target_end)
|
|
86
|
+
if not end_date:
|
|
87
|
+
return False
|
|
88
|
+
else:
|
|
89
|
+
end_date = target_end
|
|
90
|
+
|
|
91
|
+
end_date = end_date.replace(tzinfo=None) if end_date.tzinfo else end_date
|
|
92
|
+
now = datetime.now(UTC)
|
|
93
|
+
|
|
94
|
+
project_status = metadata.get("status", "")
|
|
95
|
+
if project_status in ["completed", "cancelled"]:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
return end_date < now
|
|
99
|
+
except (ValueError, AttributeError):
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _filter_projects(projects, status, owner, priority, overdue):
|
|
104
|
+
"""Apply all filters to project list."""
|
|
105
|
+
filtered = []
|
|
106
|
+
|
|
107
|
+
for metadata in projects:
|
|
108
|
+
if not _apply_status_filter(metadata, status):
|
|
109
|
+
continue
|
|
110
|
+
if not _apply_owner_filter(metadata, owner):
|
|
111
|
+
continue
|
|
112
|
+
if not _apply_priority_filter(metadata, priority):
|
|
113
|
+
continue
|
|
114
|
+
if overdue and not _apply_overdue_filter(metadata):
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
filtered.append(metadata)
|
|
118
|
+
|
|
119
|
+
return filtered
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@click.command("list")
|
|
123
|
+
@click.option(
|
|
124
|
+
"--status",
|
|
125
|
+
type=click.Choice(["planning", "active", "on-hold", "completed", "cancelled"]),
|
|
126
|
+
help="Filter by status",
|
|
127
|
+
)
|
|
128
|
+
@click.option(
|
|
129
|
+
"--owner",
|
|
130
|
+
help="Filter by owner",
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--priority",
|
|
134
|
+
type=click.Choice(["critical", "high", "medium", "low"]),
|
|
135
|
+
help="Filter by priority",
|
|
136
|
+
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--overdue", is_flag=True, help="Show only overdue projects (past target end date)"
|
|
139
|
+
)
|
|
140
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show verbose output")
|
|
141
|
+
@click.pass_context
|
|
142
|
+
@with_output_support(
|
|
143
|
+
available_columns=["id", "name", "status", "priority", "owner"],
|
|
144
|
+
column_types={
|
|
145
|
+
"id": ColumnType.STRING,
|
|
146
|
+
"name": ColumnType.STRING,
|
|
147
|
+
"status": ColumnType.ENUM,
|
|
148
|
+
"priority": ColumnType.ENUM,
|
|
149
|
+
"owner": ColumnType.STRING,
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
@verbose_output
|
|
153
|
+
def list_projects(
|
|
154
|
+
ctx: click.Context,
|
|
155
|
+
status: str | None,
|
|
156
|
+
owner: str | None,
|
|
157
|
+
priority: str | None,
|
|
158
|
+
overdue: bool,
|
|
159
|
+
verbose: bool, # noqa: F841 - Used by @verbose_output decorator
|
|
160
|
+
):
|
|
161
|
+
"""List all projects with optional filtering.
|
|
162
|
+
|
|
163
|
+
Supports output formatting with --format, --columns, --sort-by, --filter flags.
|
|
164
|
+
"""
|
|
165
|
+
core = ctx.obj["core"]
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
projects_dir = core.roadmap_dir / "projects"
|
|
169
|
+
|
|
170
|
+
if not projects_dir.exists():
|
|
171
|
+
console.print(
|
|
172
|
+
"No projects found. Create one with 'roadmap project create'",
|
|
173
|
+
style="yellow",
|
|
174
|
+
)
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Get all project files
|
|
178
|
+
project_files = list(projects_dir.rglob("*.md"))
|
|
179
|
+
|
|
180
|
+
if not project_files:
|
|
181
|
+
console.print(
|
|
182
|
+
"No projects found. Create one with 'roadmap project create'",
|
|
183
|
+
style="yellow",
|
|
184
|
+
)
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
# Parse all projects
|
|
188
|
+
project_metadatas = []
|
|
189
|
+
for file_path in project_files:
|
|
190
|
+
metadata = _parse_project_metadata(file_path)
|
|
191
|
+
if metadata:
|
|
192
|
+
project_metadatas.append(metadata)
|
|
193
|
+
else:
|
|
194
|
+
console.print(f"⚠️ Error reading {file_path.name}", style="yellow")
|
|
195
|
+
|
|
196
|
+
# Apply filters
|
|
197
|
+
filtered = _filter_projects(project_metadatas, status, owner, priority, overdue)
|
|
198
|
+
|
|
199
|
+
if not filtered:
|
|
200
|
+
console.print("No projects match the specified filters.", style="yellow")
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Convert to TableData for structured output
|
|
204
|
+
description = "filtered" if any([status, owner, priority, overdue]) else "all"
|
|
205
|
+
table_data = ProjectTableFormatter.projects_to_table_data(
|
|
206
|
+
filtered,
|
|
207
|
+
title="Projects",
|
|
208
|
+
description=description,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return table_data
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
handle_cli_error(
|
|
215
|
+
error=e,
|
|
216
|
+
operation="list_projects",
|
|
217
|
+
entity_type="project",
|
|
218
|
+
entity_id="all",
|
|
219
|
+
context={
|
|
220
|
+
"status": status,
|
|
221
|
+
"owner": owner,
|
|
222
|
+
"priority": priority,
|
|
223
|
+
"overdue": overdue,
|
|
224
|
+
},
|
|
225
|
+
fatal=True,
|
|
226
|
+
)
|
|
227
|
+
console.print(f"❌ Failed to list projects: {e}", style="bold red")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Restore project command - move archived projects back to active."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.projects.restore_class import ProjectRestore
|
|
7
|
+
from roadmap.common.console import get_console
|
|
8
|
+
from roadmap.common.logging import (
|
|
9
|
+
log_command,
|
|
10
|
+
verbose_output,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click.argument("project_name", required=False)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--all",
|
|
18
|
+
is_flag=True,
|
|
19
|
+
help="Restore all archived projects",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--dry-run",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help="Preview what would be restored without actually doing it",
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--force",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="Skip confirmation prompt",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--verbose",
|
|
33
|
+
"-v",
|
|
34
|
+
is_flag=True,
|
|
35
|
+
help="Show detailed debug information",
|
|
36
|
+
)
|
|
37
|
+
@click.pass_context
|
|
38
|
+
@verbose_output
|
|
39
|
+
@log_command("project_restore", entity_type="project", track_duration=True)
|
|
40
|
+
@require_initialized
|
|
41
|
+
def restore_project(
|
|
42
|
+
ctx: click.Context,
|
|
43
|
+
project_name: str | None,
|
|
44
|
+
all: bool,
|
|
45
|
+
dry_run: bool,
|
|
46
|
+
force: bool,
|
|
47
|
+
verbose: bool, # noqa: F841
|
|
48
|
+
):
|
|
49
|
+
"""Restore an archived project back to active projects.
|
|
50
|
+
|
|
51
|
+
This moves a project from .roadmap/archive/projects/ back to
|
|
52
|
+
.roadmap/projects/, making it active again.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
roadmap project restore "Project Name"
|
|
56
|
+
roadmap project restore --all
|
|
57
|
+
roadmap project restore "Project Name" --dry-run
|
|
58
|
+
"""
|
|
59
|
+
core = ctx.obj["core"]
|
|
60
|
+
console = get_console()
|
|
61
|
+
|
|
62
|
+
restore = ProjectRestore(core, console)
|
|
63
|
+
|
|
64
|
+
if not project_name and not all:
|
|
65
|
+
console.print(
|
|
66
|
+
"❌ Error: Specify a project name or use --all",
|
|
67
|
+
style="bold red",
|
|
68
|
+
)
|
|
69
|
+
ctx.exit(1)
|
|
70
|
+
|
|
71
|
+
if project_name and all:
|
|
72
|
+
console.print(
|
|
73
|
+
"❌ Error: Cannot specify project name with --all",
|
|
74
|
+
style="bold red",
|
|
75
|
+
)
|
|
76
|
+
ctx.exit(1)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
restore.execute(
|
|
80
|
+
entity_id=project_name,
|
|
81
|
+
all=all,
|
|
82
|
+
dry_run=dry_run,
|
|
83
|
+
force=force,
|
|
84
|
+
)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
console.print(f"❌ Restore operation failed: {str(e)}", style="bold red")
|
|
87
|
+
ctx.exit(1)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""ProjectRestore command implementation using base class."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.crud import BaseRestore, EntityType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProjectRestore(BaseRestore):
|
|
9
|
+
"""Restore project command implementation."""
|
|
10
|
+
|
|
11
|
+
entity_type = EntityType.PROJECT
|
|
12
|
+
|
|
13
|
+
def get_archived_files_to_restore(
|
|
14
|
+
self, entity_id: str | None = None, **filters
|
|
15
|
+
) -> list[Path]:
|
|
16
|
+
"""Get archived project files to restore.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
entity_id: Specific project ID to restore
|
|
20
|
+
**filters: Additional filter criteria
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of archived file paths
|
|
24
|
+
"""
|
|
25
|
+
from roadmap.adapters.cli.crud.crud_helpers import get_archive_dir
|
|
26
|
+
|
|
27
|
+
archive_dir = get_archive_dir(self.entity_type)
|
|
28
|
+
|
|
29
|
+
if not archive_dir.exists():
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
if entity_id:
|
|
33
|
+
# Find specific project file by ID prefix
|
|
34
|
+
matching = list(archive_dir.rglob(f"{entity_id[:8]}*.md"))
|
|
35
|
+
return matching if matching else []
|
|
36
|
+
else:
|
|
37
|
+
# Return all archived projects
|
|
38
|
+
return list(archive_dir.rglob("*.md"))
|
|
39
|
+
|
|
40
|
+
def post_restore_hook(self, restored_files: list[Path], **kwargs) -> None:
|
|
41
|
+
"""Handle post-restore state updates.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
restored_files: Files that were restored
|
|
45
|
+
**kwargs: Additional arguments
|
|
46
|
+
"""
|
|
47
|
+
for file_path in restored_files:
|
|
48
|
+
try:
|
|
49
|
+
# Extract project ID from filename
|
|
50
|
+
project_id = file_path.stem.split("-")[0]
|
|
51
|
+
self.core.db.mark_project_archived(project_id, archived=False)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
self.console.print(
|
|
54
|
+
f"⚠️ Warning: Failed to update restoration status for {file_path.name}: {e}",
|
|
55
|
+
style="yellow",
|
|
56
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Update project command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from roadmap.adapters.cli.cli_command_helpers import require_initialized
|
|
6
|
+
from roadmap.adapters.cli.crud import BaseUpdate, EntityType
|
|
7
|
+
from roadmap.adapters.cli.crud.entity_builders import ProjectBuilder
|
|
8
|
+
from roadmap.common.logging import log_command
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectUpdate(BaseUpdate):
|
|
12
|
+
"""Update project command implementation."""
|
|
13
|
+
|
|
14
|
+
entity_type = EntityType.PROJECT
|
|
15
|
+
|
|
16
|
+
def build_update_dict(self, entity_id: str, **kwargs) -> dict:
|
|
17
|
+
"""Build update dictionary for project."""
|
|
18
|
+
return ProjectBuilder.build_update_dict(
|
|
19
|
+
name=kwargs.get("name"),
|
|
20
|
+
description=kwargs.get("description"),
|
|
21
|
+
repository=kwargs.get("repository"),
|
|
22
|
+
status=kwargs.get("status"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command("update")
|
|
27
|
+
@click.argument("project_id")
|
|
28
|
+
@click.option("--name", help="Update project name")
|
|
29
|
+
@click.option("--description", "-d", help="Update project description")
|
|
30
|
+
@click.option("--repository", "-r", help="Update repository URL")
|
|
31
|
+
@click.option(
|
|
32
|
+
"--status",
|
|
33
|
+
type=click.Choice(["active", "inactive", "completed"]),
|
|
34
|
+
help="Update project status",
|
|
35
|
+
)
|
|
36
|
+
@click.pass_context
|
|
37
|
+
@require_initialized
|
|
38
|
+
@log_command("project_update", entity_type="project", track_duration=True)
|
|
39
|
+
def update_project(
|
|
40
|
+
ctx: click.Context,
|
|
41
|
+
project_id: str,
|
|
42
|
+
name: str,
|
|
43
|
+
description: str,
|
|
44
|
+
repository: str,
|
|
45
|
+
status: str,
|
|
46
|
+
):
|
|
47
|
+
"""Update an existing project."""
|
|
48
|
+
core = ctx.obj["core"]
|
|
49
|
+
updater = ProjectUpdate(core)
|
|
50
|
+
|
|
51
|
+
updater.execute(
|
|
52
|
+
entity_id=project_id,
|
|
53
|
+
name=name,
|
|
54
|
+
description=description,
|
|
55
|
+
repository=repository,
|
|
56
|
+
status=status,
|
|
57
|
+
)
|