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,97 @@
|
|
|
1
|
+
"""Hook registry and detection for Git hooks."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from structlog import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HookRegistry:
|
|
12
|
+
"""Registry and metadata for available Git hooks."""
|
|
13
|
+
|
|
14
|
+
AVAILABLE_HOOKS = ["post-commit", "pre-push", "post-merge", "post-checkout"]
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def get_available_hooks(cls) -> list[str]:
|
|
18
|
+
"""Get list of all available hook names."""
|
|
19
|
+
return cls.AVAILABLE_HOOKS.copy()
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def is_valid_hook(cls, hook_name: str) -> bool:
|
|
23
|
+
"""Check if hook name is valid."""
|
|
24
|
+
return hook_name in cls.AVAILABLE_HOOKS
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def validate_hooks(cls, hooks: list[str] | None) -> list[str]:
|
|
28
|
+
"""Validate and filter hook list.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
hooks: List of hook names to validate
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Validated list of hook names
|
|
35
|
+
"""
|
|
36
|
+
if hooks is None:
|
|
37
|
+
return cls.get_available_hooks()
|
|
38
|
+
return [h for h in hooks if cls.is_valid_hook(h)]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HookStatus:
|
|
42
|
+
"""Utility for checking hook installation status."""
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def get_hook_file(hook_name: str, hooks_dir: Path) -> Path:
|
|
46
|
+
"""Get the file path for a hook."""
|
|
47
|
+
return hooks_dir / hook_name
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def is_hook_installed(hook_file: Path) -> bool:
|
|
51
|
+
"""Check if hook file exists and is readable."""
|
|
52
|
+
if not hook_file.exists():
|
|
53
|
+
return False
|
|
54
|
+
try:
|
|
55
|
+
hook_file.read_text()
|
|
56
|
+
return True
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(
|
|
59
|
+
"hook_install_check_failed", error=str(e), severity="operational"
|
|
60
|
+
)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def is_roadmap_hook(hook_file: Path) -> bool:
|
|
65
|
+
"""Check if hook file is a roadmap hook."""
|
|
66
|
+
if not hook_file.exists():
|
|
67
|
+
return False
|
|
68
|
+
try:
|
|
69
|
+
content = hook_file.read_text()
|
|
70
|
+
return "roadmap-hook" in content
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(
|
|
73
|
+
"hook_content_check_failed", error=str(e), severity="operational"
|
|
74
|
+
)
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def is_executable(hook_file: Path) -> bool:
|
|
79
|
+
"""Check if hook file is executable."""
|
|
80
|
+
if not hook_file.exists():
|
|
81
|
+
return False
|
|
82
|
+
try:
|
|
83
|
+
return bool(hook_file.stat().st_mode & 0o111)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.debug("hook_executable_check_failed", error=str(e))
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_status_dict(hook_name: str, hook_file: Path) -> dict[str, Any]:
|
|
90
|
+
"""Get complete status dictionary for a hook."""
|
|
91
|
+
return {
|
|
92
|
+
"installed": HookStatus.is_hook_installed(hook_file),
|
|
93
|
+
"is_roadmap_hook": HookStatus.is_roadmap_hook(hook_file),
|
|
94
|
+
"executable": HookStatus.is_executable(hook_file),
|
|
95
|
+
"file_exists": hook_file.exists(),
|
|
96
|
+
"file_path": str(hook_file) if hook_file.exists() else None,
|
|
97
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Hook script generation for Git hooks."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HookContentGenerator:
|
|
7
|
+
"""Generates content for Git hook bash scripts."""
|
|
8
|
+
|
|
9
|
+
BASH_TEMPLATE = """#!/bin/bash
|
|
10
|
+
# roadmap-hook: {hook_name}
|
|
11
|
+
# Auto-generated Git hook for roadmap integration
|
|
12
|
+
|
|
13
|
+
# Get the directory of this script
|
|
14
|
+
HOOK_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)"
|
|
15
|
+
REPO_ROOT="$(cd "$HOOK_DIR/../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
# Change to repository root
|
|
18
|
+
cd "$REPO_ROOT"
|
|
19
|
+
|
|
20
|
+
# Execute roadmap hook handler
|
|
21
|
+
{python_exec} << 'PYTHON_HOOK_EOF'
|
|
22
|
+
import sys
|
|
23
|
+
sys.path.insert(0, '.')
|
|
24
|
+
try:
|
|
25
|
+
from roadmap.adapters.git.git_hooks_manager import GitHookManager
|
|
26
|
+
from roadmap.infrastructure.coordination.core import RoadmapCore
|
|
27
|
+
core = RoadmapCore()
|
|
28
|
+
hook_manager = GitHookManager(core)
|
|
29
|
+
getattr(hook_manager, "{handler_name}")()
|
|
30
|
+
except Exception as e:
|
|
31
|
+
# Silent fail to avoid breaking Git operations
|
|
32
|
+
pass
|
|
33
|
+
PYTHON_HOOK_EOF
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_python_executable() -> str:
|
|
38
|
+
"""Get the path to Python executable."""
|
|
39
|
+
return shutil.which("python") or shutil.which("python3") or "python"
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def generate(cls, hook_name: str) -> str:
|
|
43
|
+
"""Generate hook script content for given hook name.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
hook_name: Name of the hook (e.g., 'post-commit')
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Complete bash script content for the hook
|
|
50
|
+
"""
|
|
51
|
+
handler_name = hook_name.replace("-", "_")
|
|
52
|
+
python_exec = cls.get_python_executable()
|
|
53
|
+
|
|
54
|
+
return cls.BASH_TEMPLATE.format(
|
|
55
|
+
hook_name=hook_name,
|
|
56
|
+
handler_name=handler_name,
|
|
57
|
+
python_exec=python_exec,
|
|
58
|
+
)
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""GitSyncMonitor - Detect and sync file changes to database cache."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from roadmap.adapters.git.git_command_executor import GitCommandExecutor
|
|
7
|
+
from roadmap.common.logging import get_logger
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitSyncMonitor:
|
|
13
|
+
"""Detects file changes via git diff and syncs them to database cache.
|
|
14
|
+
|
|
15
|
+
This class enables cheap change detection:
|
|
16
|
+
- Uses `git diff --name-only` for millisecond change detection
|
|
17
|
+
- Tracks last synced commit to avoid redundant work
|
|
18
|
+
- Filters changes to only .roadmap/issues/ files
|
|
19
|
+
- Syncs only changed files to database (vs filesystem scan)
|
|
20
|
+
|
|
21
|
+
Typical usage:
|
|
22
|
+
monitor = GitSyncMonitor(repo_path, state_manager)
|
|
23
|
+
changes = monitor.detect_changes()
|
|
24
|
+
if changes:
|
|
25
|
+
monitor.sync_to_database(changes)
|
|
26
|
+
|
|
27
|
+
This enables:
|
|
28
|
+
- `roadmap list`: git diff (50ms) + DB query (5ms) = 55ms total
|
|
29
|
+
- Instead of: filesystem scan (1000ms) + parse (500ms) = 1500ms total
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, repo_path: Path | None = None, state_manager: Any | None = None):
|
|
33
|
+
"""Initialize GitSyncMonitor.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
repo_path: Path to git repository root
|
|
37
|
+
state_manager: StateManager for database operations (optional for Phase 1)
|
|
38
|
+
"""
|
|
39
|
+
self.repo_path = repo_path or Path.cwd()
|
|
40
|
+
self.state_manager = state_manager
|
|
41
|
+
self.git_executor = GitCommandExecutor(self.repo_path)
|
|
42
|
+
|
|
43
|
+
# Cache last known commit to avoid re-checking
|
|
44
|
+
self._cached_last_synced_commit: str | None = None
|
|
45
|
+
self._cached_current_commit: str | None = None
|
|
46
|
+
|
|
47
|
+
def detect_changes(self) -> dict[str, str]:
|
|
48
|
+
"""Detect changes since last sync.
|
|
49
|
+
|
|
50
|
+
Uses `git diff --name-only` for fast change detection.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dictionary mapping file paths to change types:
|
|
54
|
+
- "modified": File exists and changed
|
|
55
|
+
- "added": New file
|
|
56
|
+
- "deleted": File was removed
|
|
57
|
+
- Empty dict if no changes detected
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
{
|
|
61
|
+
".roadmap/issues/issue-123.yaml": "modified",
|
|
62
|
+
".roadmap/issues/issue-456.yaml": "added",
|
|
63
|
+
".roadmap/issues/issue-789.yaml": "deleted",
|
|
64
|
+
}
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
# Check if we're in a git repo
|
|
68
|
+
if not self.git_executor.is_git_repository():
|
|
69
|
+
logger.debug("Not in a git repository, skipping git sync")
|
|
70
|
+
return {}
|
|
71
|
+
|
|
72
|
+
# Get current commit
|
|
73
|
+
current_commit = self._get_current_commit()
|
|
74
|
+
if not current_commit:
|
|
75
|
+
logger.debug("Unable to get current commit")
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
# Get last synced commit
|
|
79
|
+
last_synced = self._get_last_synced_commit()
|
|
80
|
+
if last_synced == current_commit:
|
|
81
|
+
logger.debug(
|
|
82
|
+
"Already synced to current commit",
|
|
83
|
+
current=current_commit[:8],
|
|
84
|
+
)
|
|
85
|
+
return {}
|
|
86
|
+
|
|
87
|
+
# Get changed files
|
|
88
|
+
changes = self._get_changed_files(last_synced)
|
|
89
|
+
logger.debug(
|
|
90
|
+
"Detected file changes",
|
|
91
|
+
current=current_commit[:8],
|
|
92
|
+
previous=last_synced[:8] if last_synced else "none",
|
|
93
|
+
change_count=len(changes),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return changes
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(
|
|
100
|
+
"Error detecting changes",
|
|
101
|
+
error=str(e),
|
|
102
|
+
error_type=type(e).__name__,
|
|
103
|
+
severity="system_error",
|
|
104
|
+
)
|
|
105
|
+
return {}
|
|
106
|
+
|
|
107
|
+
def sync_to_database(self, changes: dict[str, str]) -> bool:
|
|
108
|
+
"""Sync detected changes to database cache.
|
|
109
|
+
|
|
110
|
+
This syncs remote_ids from changed YAML files to the database cache,
|
|
111
|
+
enabling fast O(1) lookups during sync operations instead of scanning
|
|
112
|
+
all YAML files.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
changes: Dictionary of changes from detect_changes()
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if sync successful, False otherwise
|
|
119
|
+
"""
|
|
120
|
+
if not changes:
|
|
121
|
+
logger.debug("No changes to sync")
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
if not self.state_manager:
|
|
125
|
+
logger.warning("State manager not configured, skipping database sync")
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
logger.debug("Syncing changes to database", change_count=len(changes))
|
|
130
|
+
|
|
131
|
+
# Sync remote_ids from changed files to database
|
|
132
|
+
from roadmap.adapters.persistence.parser import IssueParser
|
|
133
|
+
|
|
134
|
+
if self.state_manager.remote_links:
|
|
135
|
+
synced_count = 0
|
|
136
|
+
for file_path, change_type in changes.items():
|
|
137
|
+
try:
|
|
138
|
+
if change_type == "deleted":
|
|
139
|
+
# For deleted files, we can't read them, so skip
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Load issue from file using IssueParser
|
|
143
|
+
file_path_obj = Path(file_path)
|
|
144
|
+
if not file_path_obj.is_absolute():
|
|
145
|
+
# Make path absolute relative to repo root
|
|
146
|
+
file_path_obj = self.repo_path / file_path_obj
|
|
147
|
+
|
|
148
|
+
if not file_path_obj.exists():
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
issue = IssueParser.parse_issue_file(file_path_obj)
|
|
152
|
+
if issue and issue.remote_ids:
|
|
153
|
+
# Sync remote_ids to database
|
|
154
|
+
for backend_name, remote_id in issue.remote_ids.items():
|
|
155
|
+
if self.state_manager.remote_links.link_issue(
|
|
156
|
+
issue.id, backend_name, remote_id
|
|
157
|
+
):
|
|
158
|
+
synced_count += 1
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning(
|
|
161
|
+
"Failed to sync remote_ids for file",
|
|
162
|
+
file_path=file_path,
|
|
163
|
+
error=str(e),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if synced_count > 0:
|
|
167
|
+
logger.debug(
|
|
168
|
+
"Synced remote_ids to database",
|
|
169
|
+
synced_count=synced_count,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Update last synced commit
|
|
173
|
+
self._save_last_synced_commit()
|
|
174
|
+
|
|
175
|
+
logger.debug("Sync to database complete")
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(
|
|
180
|
+
"Error syncing to database",
|
|
181
|
+
error=str(e),
|
|
182
|
+
error_type=type(e).__name__,
|
|
183
|
+
severity="system_error",
|
|
184
|
+
)
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def _get_changed_files(self, base_commit: str | None = None) -> dict[str, str]:
|
|
188
|
+
"""Get list of changed files since base commit.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
base_commit: Commit SHA to compare against. If None, uses initial commit.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Dictionary mapping file paths to change types
|
|
195
|
+
"""
|
|
196
|
+
changes: dict[str, str] = {}
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# If no base commit (first sync), get all issues files
|
|
200
|
+
if not base_commit:
|
|
201
|
+
return self._get_all_issues_files()
|
|
202
|
+
|
|
203
|
+
# Get changed files between base and current
|
|
204
|
+
changed_files = self.git_executor.run(
|
|
205
|
+
["diff", "--name-status", base_commit, "HEAD"]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not changed_files:
|
|
209
|
+
return {}
|
|
210
|
+
|
|
211
|
+
# Parse git diff output
|
|
212
|
+
# Format: M\tpath/to/file (Modified)
|
|
213
|
+
# A\tpath/to/file (Added)
|
|
214
|
+
# D\tpath/to/file (Deleted)
|
|
215
|
+
for line in changed_files.split("\n"):
|
|
216
|
+
if not line.strip():
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
parts = line.split("\t", 1)
|
|
220
|
+
if len(parts) != 2:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
status, path = parts
|
|
224
|
+
path = path.strip()
|
|
225
|
+
|
|
226
|
+
# Only track .roadmap/issues/ changes
|
|
227
|
+
if not self._is_issues_file(path):
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
# Map git status to our status
|
|
231
|
+
status_map = {"M": "modified", "A": "added", "D": "deleted"}
|
|
232
|
+
changes[path] = status_map.get(status, "modified")
|
|
233
|
+
|
|
234
|
+
return changes
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(
|
|
238
|
+
"Error getting changed files",
|
|
239
|
+
error=str(e),
|
|
240
|
+
error_type=type(e).__name__,
|
|
241
|
+
severity="system_error",
|
|
242
|
+
)
|
|
243
|
+
return {}
|
|
244
|
+
|
|
245
|
+
def _get_all_issues_files(self) -> dict[str, str]:
|
|
246
|
+
"""Get all issues files for initial sync.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dictionary mapping all issue files to "added" status
|
|
250
|
+
"""
|
|
251
|
+
all_files: dict[str, str] = {}
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
# Use git ls-files to get all tracked files
|
|
255
|
+
git_files = self.git_executor.run(["ls-files"])
|
|
256
|
+
if not git_files:
|
|
257
|
+
return {}
|
|
258
|
+
|
|
259
|
+
for path in git_files.split("\n"):
|
|
260
|
+
path = path.strip()
|
|
261
|
+
if self._is_issues_file(path):
|
|
262
|
+
all_files[path] = "added"
|
|
263
|
+
|
|
264
|
+
logger.debug(
|
|
265
|
+
"Found all issues files for initial sync",
|
|
266
|
+
file_count=len(all_files),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return all_files
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(
|
|
273
|
+
"Error getting all issues files",
|
|
274
|
+
error=str(e),
|
|
275
|
+
error_type=type(e).__name__,
|
|
276
|
+
)
|
|
277
|
+
return {}
|
|
278
|
+
|
|
279
|
+
def _is_issues_file(self, path: str) -> bool:
|
|
280
|
+
"""Check if path is a .roadmap/issues/ file.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
path: File path to check
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
True if file is in .roadmap/issues/ or .roadmap/archive/issues/
|
|
287
|
+
"""
|
|
288
|
+
return ".roadmap/issues/" in path or ".roadmap/archive/issues/" in path
|
|
289
|
+
|
|
290
|
+
def _get_current_commit(self) -> str | None:
|
|
291
|
+
"""Get the current HEAD commit SHA.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Commit SHA, or None if not in a valid git state
|
|
295
|
+
"""
|
|
296
|
+
if self._cached_current_commit:
|
|
297
|
+
return self._cached_current_commit
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
commit = self.git_executor.run(["rev-parse", "HEAD"])
|
|
301
|
+
self._cached_current_commit = commit
|
|
302
|
+
return commit
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(
|
|
305
|
+
"Error getting current commit",
|
|
306
|
+
error=str(e),
|
|
307
|
+
error_type=type(e).__name__,
|
|
308
|
+
severity="system_error",
|
|
309
|
+
)
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
def _get_last_synced_commit(self) -> str | None:
|
|
313
|
+
"""Get the last commit we synced to database.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Commit SHA, or None if never synced
|
|
317
|
+
"""
|
|
318
|
+
if self._cached_last_synced_commit is not None:
|
|
319
|
+
return self._cached_last_synced_commit
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
# Read from database sync_metadata table
|
|
323
|
+
if self.state_manager:
|
|
324
|
+
conn = self.state_manager._get_connection()
|
|
325
|
+
cursor = conn.cursor()
|
|
326
|
+
cursor.execute(
|
|
327
|
+
"SELECT value FROM sync_metadata WHERE key = ?",
|
|
328
|
+
("last_synced_commit",),
|
|
329
|
+
)
|
|
330
|
+
row = cursor.fetchone()
|
|
331
|
+
if row:
|
|
332
|
+
commit = row[0]
|
|
333
|
+
self._cached_last_synced_commit = commit
|
|
334
|
+
return commit
|
|
335
|
+
|
|
336
|
+
# No previous sync
|
|
337
|
+
self._cached_last_synced_commit = None
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
logger.error(
|
|
342
|
+
"Error getting last synced commit",
|
|
343
|
+
error=str(e),
|
|
344
|
+
severity="system_error",
|
|
345
|
+
error_type=type(e).__name__,
|
|
346
|
+
)
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
def _save_last_synced_commit(self) -> bool:
|
|
350
|
+
"""Save current commit as the last synced commit.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
True if save successful
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
current_commit = self._get_current_commit()
|
|
357
|
+
if not current_commit:
|
|
358
|
+
logger.error("Cannot save last synced commit: no current commit")
|
|
359
|
+
return False
|
|
360
|
+
|
|
361
|
+
# Write to database sync_metadata table
|
|
362
|
+
if not self.state_manager:
|
|
363
|
+
logger.warning("State manager not configured, cannot save sync commit")
|
|
364
|
+
return False
|
|
365
|
+
|
|
366
|
+
conn = self.state_manager._get_connection()
|
|
367
|
+
cursor = conn.cursor()
|
|
368
|
+
|
|
369
|
+
# Try to update first, then insert if not found
|
|
370
|
+
cursor.execute(
|
|
371
|
+
"UPDATE sync_metadata SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?",
|
|
372
|
+
(current_commit, "last_synced_commit"),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# If no rows were updated, insert new row
|
|
376
|
+
if cursor.rowcount == 0:
|
|
377
|
+
cursor.execute(
|
|
378
|
+
"INSERT INTO sync_metadata (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)",
|
|
379
|
+
("last_synced_commit", current_commit),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
conn.commit()
|
|
383
|
+
|
|
384
|
+
# Update cache
|
|
385
|
+
self._cached_last_synced_commit = current_commit
|
|
386
|
+
|
|
387
|
+
logger.debug(
|
|
388
|
+
"Saved last synced commit",
|
|
389
|
+
commit=current_commit[:8],
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
logger.error(
|
|
396
|
+
"Error saving last synced commit",
|
|
397
|
+
error=str(e),
|
|
398
|
+
error_type=type(e).__name__,
|
|
399
|
+
severity="system_error",
|
|
400
|
+
)
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
def clear_cache(self) -> None:
|
|
404
|
+
"""Clear internal commit caches.
|
|
405
|
+
|
|
406
|
+
Useful for testing or forcing a fresh check.
|
|
407
|
+
"""
|
|
408
|
+
self._cached_last_synced_commit = None
|
|
409
|
+
self._cached_current_commit = None
|