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,27 @@
|
|
|
1
|
+
"""Security event logging utilities."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from structlog import get_logger
|
|
7
|
+
|
|
8
|
+
# Security logger
|
|
9
|
+
security_logger = get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def log_security_event(event_type: str, details: dict[str, Any] | None = None) -> None:
|
|
13
|
+
"""Log a security event with structured data.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
event_type: Type of security event
|
|
17
|
+
details: Additional event details
|
|
18
|
+
"""
|
|
19
|
+
if details is None:
|
|
20
|
+
details = {}
|
|
21
|
+
|
|
22
|
+
# Log as structured data
|
|
23
|
+
security_logger.info(
|
|
24
|
+
event_type,
|
|
25
|
+
timestamp=datetime.now(UTC).isoformat(),
|
|
26
|
+
**details,
|
|
27
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Path validation and security utilities."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .exceptions import PathValidationError
|
|
6
|
+
from .logging import log_security_event
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _check_traversal_patterns(path_str: str, allow_absolute: bool) -> None:
|
|
10
|
+
"""Check for directory traversal patterns."""
|
|
11
|
+
if ".." in path_str and not allow_absolute:
|
|
12
|
+
raise PathValidationError(
|
|
13
|
+
f"Path contains potential directory traversal: {path_str}"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resolve_path_safely(path: Path) -> Path:
|
|
18
|
+
"""Resolve path safely, handling missing directories."""
|
|
19
|
+
try:
|
|
20
|
+
return path.resolve()
|
|
21
|
+
except (FileNotFoundError, OSError):
|
|
22
|
+
# If resolve() fails due to missing current directory, handle gracefully
|
|
23
|
+
if path.is_absolute():
|
|
24
|
+
return path
|
|
25
|
+
else:
|
|
26
|
+
# For relative paths when cwd is missing, return as-is (caller context should handle)
|
|
27
|
+
return path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _resolve_relative_to_base(path: Path, base_dir: Path) -> Path:
|
|
31
|
+
"""Resolve path relative to base directory."""
|
|
32
|
+
try:
|
|
33
|
+
return path.resolve()
|
|
34
|
+
except (FileNotFoundError, OSError):
|
|
35
|
+
# If resolve fails, work with absolute version or relative to base
|
|
36
|
+
if path.is_absolute():
|
|
37
|
+
return path
|
|
38
|
+
else:
|
|
39
|
+
return base_dir / path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_base_safely(base_dir: Path) -> Path:
|
|
43
|
+
"""Resolve base directory safely."""
|
|
44
|
+
try:
|
|
45
|
+
return base_dir.resolve()
|
|
46
|
+
except (FileNotFoundError, OSError):
|
|
47
|
+
return base_dir
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _check_absolute_allowed(path: Path, allow_absolute: bool) -> None:
|
|
51
|
+
"""Check if absolute path is allowed."""
|
|
52
|
+
if not allow_absolute and path.is_absolute():
|
|
53
|
+
raise PathValidationError(f"Absolute paths not allowed: {path}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _check_within_base(
|
|
57
|
+
resolved_path: Path, resolved_base: Path, original_path: Path
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Check if path is within base directory."""
|
|
60
|
+
try:
|
|
61
|
+
resolved_path.relative_to(resolved_base)
|
|
62
|
+
except ValueError as e:
|
|
63
|
+
raise PathValidationError(
|
|
64
|
+
f"Path outside allowed directory: {original_path}"
|
|
65
|
+
) from e
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _check_dangerous_components(resolved_path: Path) -> None:
|
|
69
|
+
"""Check for dangerous path components."""
|
|
70
|
+
path_parts = resolved_path.parts
|
|
71
|
+
dangerous_parts = {"..", ".", "~"}
|
|
72
|
+
if any(part in dangerous_parts for part in path_parts):
|
|
73
|
+
raise PathValidationError(
|
|
74
|
+
f"Path contains dangerous components: {resolved_path}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_path(
|
|
79
|
+
path: str | Path,
|
|
80
|
+
base_dir: str | Path | None = None,
|
|
81
|
+
allow_absolute: bool = False,
|
|
82
|
+
) -> Path:
|
|
83
|
+
"""Validate that a path is safe and within allowed boundaries.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
path: Path to validate
|
|
87
|
+
base_dir: Base directory that path must be within (optional)
|
|
88
|
+
allow_absolute: Whether to allow absolute paths
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Resolved safe path
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
PathValidationError: If path is unsafe or outside boundaries
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
# Convert to Path object if string
|
|
98
|
+
if isinstance(path, str):
|
|
99
|
+
path = Path(path)
|
|
100
|
+
|
|
101
|
+
# If no base directory specified, just check for basic safety
|
|
102
|
+
if base_dir is None:
|
|
103
|
+
# Still check for directory traversal patterns
|
|
104
|
+
path_str = str(path)
|
|
105
|
+
_check_traversal_patterns(path_str, allow_absolute)
|
|
106
|
+
return _resolve_path_safely(path)
|
|
107
|
+
|
|
108
|
+
# Convert base_dir to Path if needed
|
|
109
|
+
if isinstance(base_dir, str):
|
|
110
|
+
base_dir = Path(base_dir)
|
|
111
|
+
|
|
112
|
+
# Resolve paths
|
|
113
|
+
resolved_path = _resolve_relative_to_base(path, base_dir)
|
|
114
|
+
resolved_base = _resolve_base_safely(base_dir)
|
|
115
|
+
|
|
116
|
+
# Validate
|
|
117
|
+
_check_absolute_allowed(path, allow_absolute)
|
|
118
|
+
_check_within_base(resolved_path, resolved_base, path)
|
|
119
|
+
_check_dangerous_components(resolved_path)
|
|
120
|
+
|
|
121
|
+
log_security_event(
|
|
122
|
+
"path_validated",
|
|
123
|
+
{
|
|
124
|
+
"path": str(path),
|
|
125
|
+
"resolved_path": str(resolved_path),
|
|
126
|
+
"base_dir": str(base_dir) if base_dir else None,
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return resolved_path
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
log_security_event(
|
|
134
|
+
"path_validation_failed",
|
|
135
|
+
{"path": str(path), "base_dir": str(base_dir), "error": str(e)},
|
|
136
|
+
)
|
|
137
|
+
raise
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Secure temporary file utilities."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .exceptions import SecurityError
|
|
8
|
+
from .logging import log_security_event
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_secure_temp_file(prefix: str = "roadmap_", suffix: str = ".tmp") -> Path:
|
|
12
|
+
"""Create a secure temporary file.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
prefix: Prefix for temporary filename
|
|
16
|
+
suffix: Suffix for temporary filename
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Path to the secure temporary file
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
SecurityError: If temporary file creation fails
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
# Create secure temporary file
|
|
26
|
+
fd, temp_path = tempfile.mkstemp(prefix=prefix, suffix=suffix)
|
|
27
|
+
os.close(fd) # Close the file descriptor
|
|
28
|
+
|
|
29
|
+
# Convert to Path object
|
|
30
|
+
temp_file = Path(temp_path)
|
|
31
|
+
|
|
32
|
+
# Set secure permissions (owner read/write only)
|
|
33
|
+
temp_file.chmod(0o600)
|
|
34
|
+
|
|
35
|
+
log_security_event("temp_file_created", {"path": str(temp_file)})
|
|
36
|
+
|
|
37
|
+
return temp_file
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
log_security_event("temp_file_creation_failed", {"error": str(e)})
|
|
41
|
+
raise SecurityError(f"Failed to create secure temporary file: {e}") from e
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Service Utilities - Decorators, metrics, performance, and retry logic.
|
|
2
|
+
|
|
3
|
+
This module contains cross-cutting concerns like logging, metrics, performance
|
|
4
|
+
monitoring, and retry logic used by services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .decorators import service_operation
|
|
8
|
+
from .logging_utils import (
|
|
9
|
+
log_collection_operation,
|
|
10
|
+
log_entry,
|
|
11
|
+
log_event,
|
|
12
|
+
log_exit,
|
|
13
|
+
log_metric,
|
|
14
|
+
log_operation,
|
|
15
|
+
log_state_change,
|
|
16
|
+
)
|
|
17
|
+
from .metrics import MetricsCollector, OperationMetric, get_metrics_collector
|
|
18
|
+
from .performance import OperationTimer, async_timed_operation, timed_operation
|
|
19
|
+
from .profiling import (
|
|
20
|
+
OperationProfile,
|
|
21
|
+
PerformanceProfiler,
|
|
22
|
+
PerformanceReport,
|
|
23
|
+
get_profiler,
|
|
24
|
+
)
|
|
25
|
+
from .retry import (
|
|
26
|
+
API_RETRY,
|
|
27
|
+
DATABASE_RETRY,
|
|
28
|
+
NETWORK_RETRY,
|
|
29
|
+
RetryConfig,
|
|
30
|
+
async_retry,
|
|
31
|
+
retry,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Decorators
|
|
36
|
+
"service_operation",
|
|
37
|
+
# Logging
|
|
38
|
+
"log_collection_operation",
|
|
39
|
+
"log_entry",
|
|
40
|
+
"log_event",
|
|
41
|
+
"log_exit",
|
|
42
|
+
"log_metric",
|
|
43
|
+
"log_operation",
|
|
44
|
+
"log_state_change",
|
|
45
|
+
# Metrics
|
|
46
|
+
"MetricsCollector",
|
|
47
|
+
"OperationMetric",
|
|
48
|
+
"get_metrics_collector",
|
|
49
|
+
# Performance
|
|
50
|
+
"OperationTimer",
|
|
51
|
+
"async_timed_operation",
|
|
52
|
+
"timed_operation",
|
|
53
|
+
# Profiling
|
|
54
|
+
"OperationProfile",
|
|
55
|
+
"PerformanceProfiler",
|
|
56
|
+
"PerformanceReport",
|
|
57
|
+
"get_profiler",
|
|
58
|
+
# Retry
|
|
59
|
+
"API_RETRY",
|
|
60
|
+
"DATABASE_RETRY",
|
|
61
|
+
"NETWORK_RETRY",
|
|
62
|
+
"RetryConfig",
|
|
63
|
+
"async_retry",
|
|
64
|
+
"retry",
|
|
65
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Decorators for service operations with intelligent error handling and logging.
|
|
2
|
+
|
|
3
|
+
Provides consistent patterns for:
|
|
4
|
+
- Exception handling across service methods
|
|
5
|
+
- Configurable logging levels for different failure scenarios
|
|
6
|
+
- Optional stack trace capture for debugging
|
|
7
|
+
- Production-ready error reporting
|
|
8
|
+
|
|
9
|
+
The @service_operation decorator is the foundation for eliminating silent failures
|
|
10
|
+
and providing production observability.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import traceback as tb_module
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
from roadmap.common.logging import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def service_operation(
|
|
24
|
+
default_return: Any = None,
|
|
25
|
+
error_message: str | None = None,
|
|
26
|
+
log_level: Literal["debug", "info", "warning", "error"] = "error",
|
|
27
|
+
include_traceback: bool = False,
|
|
28
|
+
log_success: bool = False,
|
|
29
|
+
):
|
|
30
|
+
"""Provide intelligent error handling for service methods.
|
|
31
|
+
|
|
32
|
+
**Key Features:**
|
|
33
|
+
- Mandatory error logging (no silent failures)
|
|
34
|
+
- Configurable logging severity based on failure type
|
|
35
|
+
- Optional stack traces for debugging
|
|
36
|
+
- Automatic error context enrichment
|
|
37
|
+
- Consistent return value handling
|
|
38
|
+
|
|
39
|
+
**Log Level Guidance:**
|
|
40
|
+
|
|
41
|
+
Use `"warning"` for operational failures (expected to sometimes fail):
|
|
42
|
+
- File not found, item not found, permission denied
|
|
43
|
+
- Intended for: get/list operations, optional checks, lookups
|
|
44
|
+
- Example: @service_operation(log_level="warning")
|
|
45
|
+
|
|
46
|
+
Use `"error"` for unexpected failures (should rarely happen):
|
|
47
|
+
- Database corruption, file system errors, system resource issues
|
|
48
|
+
- Intended for: create/update/delete, critical operations
|
|
49
|
+
- Example: @service_operation(log_level="error")
|
|
50
|
+
|
|
51
|
+
Use `"debug"` for health/status checks (non-critical, potentially noisy):
|
|
52
|
+
- Polling operations, availability checks, status queries
|
|
53
|
+
- Intended for: frequent checking operations
|
|
54
|
+
- Example: @service_operation(log_level="debug")
|
|
55
|
+
|
|
56
|
+
Use `"info"` sparingly for business milestones (use debug/warning otherwise)
|
|
57
|
+
- Intended for: significant business events worth tracking
|
|
58
|
+
- Example: @service_operation(log_level="info")
|
|
59
|
+
|
|
60
|
+
**Parameters:**
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
default_return: Value to return on error (default {})
|
|
64
|
+
error_message: Custom error message (auto-generated if None)
|
|
65
|
+
log_level: Logging severity - "debug"|"info"|"warning"|"error"
|
|
66
|
+
include_traceback: Include full stack trace in logs (for debugging)
|
|
67
|
+
log_success: Whether to log on successful completion
|
|
68
|
+
|
|
69
|
+
**Usage Examples:**
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Database read operation (expected to sometimes fail)
|
|
73
|
+
@service_operation(default_return=None, log_level="warning")
|
|
74
|
+
def get_issue(self, issue_id: str) -> Issue | None:
|
|
75
|
+
return self._find_issue(issue_id)
|
|
76
|
+
|
|
77
|
+
# File parsing with debugging support
|
|
78
|
+
@service_operation(
|
|
79
|
+
default_return=[],
|
|
80
|
+
log_level="warning",
|
|
81
|
+
include_traceback=True
|
|
82
|
+
)
|
|
83
|
+
def list_issues(self):
|
|
84
|
+
return FileEnumerationService.enumerate_and_parse(...)
|
|
85
|
+
|
|
86
|
+
# Database write operation (should rarely fail)
|
|
87
|
+
@service_operation(
|
|
88
|
+
default_return=False,
|
|
89
|
+
log_level="error",
|
|
90
|
+
include_traceback=True
|
|
91
|
+
)
|
|
92
|
+
def save_issue(self, issue):
|
|
93
|
+
return self.db.save(issue)
|
|
94
|
+
|
|
95
|
+
# Health check polling (less critical)
|
|
96
|
+
@service_operation(default_return=False, log_level="debug")
|
|
97
|
+
def is_healthy(self) -> bool:
|
|
98
|
+
return self.check_readiness()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Error Logging Output:**
|
|
102
|
+
|
|
103
|
+
When a decorated function raises an exception:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
WARNING: Error in get_issue | error=not found | error_type=FileNotFoundError | operation=get_issue
|
|
107
|
+
ERROR: Error in save_issue | error=connection lost | error_type=ConnectionError | operation=save_issue | traceback=<full trace>
|
|
108
|
+
DEBUG: Error in is_healthy | error=timeout | error_type=TimeoutError | operation=is_healthy
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Testing:**
|
|
112
|
+
|
|
113
|
+
The decorator ensures all failures are logged. Test that:
|
|
114
|
+
- Successful calls don't log errors
|
|
115
|
+
- Failed calls log at the specified level
|
|
116
|
+
- Default return value is returned on error
|
|
117
|
+
- Exception details are captured in logs
|
|
118
|
+
- Traceback is included when requested
|
|
119
|
+
"""
|
|
120
|
+
# Validate log level
|
|
121
|
+
valid_levels = {"debug", "info", "warning", "error"}
|
|
122
|
+
if log_level not in valid_levels:
|
|
123
|
+
raise ValueError(f"log_level must be one of {valid_levels}, got '{log_level}'")
|
|
124
|
+
|
|
125
|
+
def decorator(func: Callable) -> Callable:
|
|
126
|
+
@wraps(func)
|
|
127
|
+
def wrapper(self, *args, **kwargs) -> Any:
|
|
128
|
+
try:
|
|
129
|
+
result = func(self, *args, **kwargs)
|
|
130
|
+
if log_success:
|
|
131
|
+
logger.debug(
|
|
132
|
+
"operation_completed",
|
|
133
|
+
operation=func.__name__,
|
|
134
|
+
)
|
|
135
|
+
return result
|
|
136
|
+
except Exception as e:
|
|
137
|
+
_log_operation_error(
|
|
138
|
+
func=func,
|
|
139
|
+
error=e,
|
|
140
|
+
error_message=error_message,
|
|
141
|
+
log_level=log_level,
|
|
142
|
+
include_traceback=include_traceback,
|
|
143
|
+
)
|
|
144
|
+
return default_return if default_return is not None else {}
|
|
145
|
+
|
|
146
|
+
return wrapper
|
|
147
|
+
|
|
148
|
+
return decorator
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _log_operation_error(
|
|
152
|
+
func: Callable,
|
|
153
|
+
error: Exception,
|
|
154
|
+
error_message: str | None,
|
|
155
|
+
log_level: str,
|
|
156
|
+
include_traceback: bool,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Log operation errors consistently with context.
|
|
159
|
+
|
|
160
|
+
Captures error details and routes to appropriate logger method
|
|
161
|
+
based on configured log level.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
func: The function that failed
|
|
165
|
+
error: The exception that was caught
|
|
166
|
+
error_message: Optional custom message
|
|
167
|
+
log_level: Logging level to use ("debug"|"info"|"warning"|"error")
|
|
168
|
+
include_traceback: Whether to include full stack trace
|
|
169
|
+
"""
|
|
170
|
+
msg = error_message or f"Error in {func.__name__}"
|
|
171
|
+
|
|
172
|
+
log_data = {
|
|
173
|
+
"error": str(error),
|
|
174
|
+
"error_type": type(error).__name__,
|
|
175
|
+
"operation": func.__name__,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if include_traceback:
|
|
179
|
+
log_data["traceback"] = tb_module.format_exc()
|
|
180
|
+
|
|
181
|
+
# Route to appropriate logger method
|
|
182
|
+
log_func = getattr(logger, log_level, logger.error)
|
|
183
|
+
log_func(msg, **log_data)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Logging utilities for comprehensive structured logging across the application.
|
|
2
|
+
|
|
3
|
+
Provides helpers for:
|
|
4
|
+
- Method entry/exit logging with timing
|
|
5
|
+
- Business logic event logging
|
|
6
|
+
- State change tracking
|
|
7
|
+
- Performance metrics
|
|
8
|
+
- Contextual information capture
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
from collections.abc import Iterator
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from roadmap.common.logging import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def log_operation(
|
|
23
|
+
operation_name: str,
|
|
24
|
+
level: str = "info",
|
|
25
|
+
**context: Any,
|
|
26
|
+
) -> Iterator[dict[str, Any]]:
|
|
27
|
+
"""Context manager for logging operation execution with timing and context.
|
|
28
|
+
|
|
29
|
+
Logs operation entry, execution time, and exit. Captures any exceptions that occur.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
operation_name: Name of the operation (e.g., "create_issue", "update_project")
|
|
33
|
+
level: Log level ("debug", "info", "warning")
|
|
34
|
+
**context: Additional context to include in logs (e.g., entity_id, count)
|
|
35
|
+
|
|
36
|
+
Yields:
|
|
37
|
+
Dictionary for tracking additional operation metrics
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
with log_operation("create_issue", entity_id=issue_id, priority="HIGH") as metrics:
|
|
41
|
+
result = create_issue()
|
|
42
|
+
metrics["result_count"] = 1
|
|
43
|
+
|
|
44
|
+
The context manager will log:
|
|
45
|
+
- Entry: "{operation_name}_start" with all context
|
|
46
|
+
- Exit: "{operation_name}_complete" with timing and metrics
|
|
47
|
+
- Error: "{operation_name}_failed" if exception occurs
|
|
48
|
+
"""
|
|
49
|
+
start_time = time.time()
|
|
50
|
+
metrics: dict[str, Any] = {}
|
|
51
|
+
log_func = getattr(logger, level)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
log_func(f"{operation_name}_start", **context)
|
|
55
|
+
yield metrics
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
elapsed = time.time() - start_time
|
|
59
|
+
log_func(
|
|
60
|
+
f"{operation_name}_failed",
|
|
61
|
+
elapsed_seconds=round(elapsed, 3),
|
|
62
|
+
error_type=type(e).__name__,
|
|
63
|
+
**context,
|
|
64
|
+
**metrics,
|
|
65
|
+
)
|
|
66
|
+
raise
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
elapsed = time.time() - start_time
|
|
70
|
+
log_func(
|
|
71
|
+
f"{operation_name}_complete",
|
|
72
|
+
elapsed_seconds=round(elapsed, 3),
|
|
73
|
+
**context,
|
|
74
|
+
**metrics,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def log_entry(
|
|
79
|
+
operation_name: str,
|
|
80
|
+
level: str = "debug",
|
|
81
|
+
**parameters: Any,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Log method entry with parameters.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
operation_name: Name of the operation
|
|
87
|
+
level: Log level
|
|
88
|
+
**parameters: Method parameters to log
|
|
89
|
+
"""
|
|
90
|
+
log_func = getattr(logger, level)
|
|
91
|
+
log_func(f"{operation_name}_start", **parameters)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def log_exit(
|
|
95
|
+
operation_name: str,
|
|
96
|
+
level: str = "debug",
|
|
97
|
+
elapsed_seconds: float | None = None,
|
|
98
|
+
**result: Any,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Log method exit with result and timing.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
operation_name: Name of the operation
|
|
104
|
+
level: Log level
|
|
105
|
+
elapsed_seconds: Execution time in seconds
|
|
106
|
+
**result: Result information to log
|
|
107
|
+
"""
|
|
108
|
+
log_func = getattr(logger, level)
|
|
109
|
+
if elapsed_seconds is not None:
|
|
110
|
+
result["elapsed_seconds"] = round(elapsed_seconds, 3)
|
|
111
|
+
log_func(f"{operation_name}_complete", **result)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def log_event(
|
|
115
|
+
event_name: str,
|
|
116
|
+
level: str = "info",
|
|
117
|
+
**details: Any,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Log a business logic event or milestone.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
event_name: Name of the event (e.g., "issue_assigned", "milestone_closed")
|
|
123
|
+
level: Log level
|
|
124
|
+
**details: Event details
|
|
125
|
+
"""
|
|
126
|
+
log_func = getattr(logger, level)
|
|
127
|
+
log_func(event_name, **details)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def log_state_change(
|
|
131
|
+
entity_type: str,
|
|
132
|
+
entity_id: str,
|
|
133
|
+
old_state: dict[str, Any],
|
|
134
|
+
new_state: dict[str, Any],
|
|
135
|
+
level: str = "info",
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Log a state change for an entity.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
entity_type: Type of entity (e.g., "Issue", "Milestone")
|
|
141
|
+
entity_id: Entity identifier
|
|
142
|
+
old_state: Previous state
|
|
143
|
+
new_state: New state
|
|
144
|
+
level: Log level
|
|
145
|
+
"""
|
|
146
|
+
changes = {}
|
|
147
|
+
all_keys = set(old_state.keys()) | set(new_state.keys())
|
|
148
|
+
|
|
149
|
+
for key in all_keys:
|
|
150
|
+
old_val = old_state.get(key)
|
|
151
|
+
new_val = new_state.get(key)
|
|
152
|
+
if old_val != new_val:
|
|
153
|
+
changes[key] = {"from": old_val, "to": new_val}
|
|
154
|
+
|
|
155
|
+
log_func = getattr(logger, level)
|
|
156
|
+
log_func(
|
|
157
|
+
f"{entity_type.lower()}_state_changed",
|
|
158
|
+
entity_type=entity_type,
|
|
159
|
+
entity_id=entity_id,
|
|
160
|
+
changes=changes,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def log_collection_operation(
|
|
165
|
+
collection_name: str,
|
|
166
|
+
count: int,
|
|
167
|
+
operation: str = "processed",
|
|
168
|
+
level: str = "debug",
|
|
169
|
+
**context: Any,
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Log collection processing (e.g., parsed 5 issues).
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
collection_name: Name of the collection (e.g., "issues", "milestones")
|
|
175
|
+
count: Number of items processed
|
|
176
|
+
operation: Operation performed (e.g., "processed", "created", "updated")
|
|
177
|
+
level: Log level
|
|
178
|
+
**context: Additional context
|
|
179
|
+
"""
|
|
180
|
+
log_func = getattr(logger, level)
|
|
181
|
+
log_func(
|
|
182
|
+
f"{collection_name}_{operation}",
|
|
183
|
+
count=count,
|
|
184
|
+
**context,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def log_metric(
|
|
189
|
+
metric_name: str,
|
|
190
|
+
value: float | int,
|
|
191
|
+
unit: str = "",
|
|
192
|
+
level: str = "debug",
|
|
193
|
+
**context: Any,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Log a performance or business metric.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
metric_name: Name of the metric (e.g., "query_time", "issue_count")
|
|
199
|
+
value: Metric value
|
|
200
|
+
unit: Unit of measurement (e.g., "ms", "seconds", "items")
|
|
201
|
+
level: Log level
|
|
202
|
+
**context: Additional context
|
|
203
|
+
"""
|
|
204
|
+
log_func = getattr(logger, level)
|
|
205
|
+
data: dict[str, Any] = {"value": value}
|
|
206
|
+
if unit:
|
|
207
|
+
data["unit"] = unit
|
|
208
|
+
log_func(metric_name, **data, **context)
|