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,217 @@
|
|
|
1
|
+
"""Error logging utilities for structured error handling and diagnostics.
|
|
2
|
+
|
|
3
|
+
Provides functions to log errors with context, classify errors, and provide
|
|
4
|
+
recovery suggestions for better debugging and operational visibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import traceback
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from roadmap.common.logging.loggers import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ErrorClassification:
|
|
16
|
+
"""Error classification constants."""
|
|
17
|
+
|
|
18
|
+
USER_ERROR = "user_error" # User input or usage error
|
|
19
|
+
SYSTEM_ERROR = "system_error" # Internal system failure
|
|
20
|
+
EXTERNAL_ERROR = "external_error" # External service failure
|
|
21
|
+
UNKNOWN_ERROR = "unknown_error" # Unclassified
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def classify_error(error: Exception) -> str:
|
|
25
|
+
"""Classify an error to help with diagnostics and recovery.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
error: The exception to classify
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Error classification string
|
|
32
|
+
"""
|
|
33
|
+
# Import these locally to avoid circular imports
|
|
34
|
+
from roadmap.common.errors import RoadmapError, ValidationError
|
|
35
|
+
|
|
36
|
+
if isinstance(error, ValidationError | ValueError | TypeError | KeyError):
|
|
37
|
+
return ErrorClassification.USER_ERROR
|
|
38
|
+
|
|
39
|
+
if isinstance(error, OSError | RoadmapError):
|
|
40
|
+
return ErrorClassification.SYSTEM_ERROR
|
|
41
|
+
|
|
42
|
+
if isinstance(error, ConnectionError | TimeoutError):
|
|
43
|
+
return ErrorClassification.EXTERNAL_ERROR
|
|
44
|
+
|
|
45
|
+
return ErrorClassification.UNKNOWN_ERROR
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_error_recoverable(error: Exception) -> bool:
|
|
49
|
+
"""Determine if an error is recoverable with retry.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
error: The exception to evaluate
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if the error is likely recoverable with retry
|
|
56
|
+
"""
|
|
57
|
+
recoverable_errors = (
|
|
58
|
+
ConnectionError,
|
|
59
|
+
TimeoutError,
|
|
60
|
+
BlockingIOError,
|
|
61
|
+
BrokenPipeError,
|
|
62
|
+
)
|
|
63
|
+
return isinstance(error, recoverable_errors)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def suggest_recovery(error: Exception, context: dict | None = None) -> str:
|
|
67
|
+
"""Suggest a recovery action for an error.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
error: The exception to suggest recovery for
|
|
71
|
+
context: Additional context about the operation
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Suggested recovery action
|
|
75
|
+
"""
|
|
76
|
+
classification = classify_error(error)
|
|
77
|
+
|
|
78
|
+
if is_error_recoverable(error):
|
|
79
|
+
return "retry"
|
|
80
|
+
|
|
81
|
+
if classification == ErrorClassification.USER_ERROR:
|
|
82
|
+
return "validate_input"
|
|
83
|
+
|
|
84
|
+
if classification == ErrorClassification.EXTERNAL_ERROR:
|
|
85
|
+
return "check_connectivity"
|
|
86
|
+
|
|
87
|
+
if classification == ErrorClassification.SYSTEM_ERROR:
|
|
88
|
+
return "manual_intervention"
|
|
89
|
+
|
|
90
|
+
return "contact_support"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def log_error_with_context(
|
|
94
|
+
error: Exception,
|
|
95
|
+
operation: str,
|
|
96
|
+
entity_type: str | None = None,
|
|
97
|
+
entity_id: str | None = None,
|
|
98
|
+
additional_context: dict | None = None,
|
|
99
|
+
include_traceback: bool = False,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Log an error with comprehensive context for debugging.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
error: The exception that occurred
|
|
105
|
+
operation: Name of the operation that failed
|
|
106
|
+
entity_type: Type of entity being operated on
|
|
107
|
+
entity_id: ID of the entity
|
|
108
|
+
additional_context: Any additional context dictionary
|
|
109
|
+
include_traceback: Whether to include full traceback
|
|
110
|
+
"""
|
|
111
|
+
classification = classify_error(error)
|
|
112
|
+
recoverable = is_error_recoverable(error)
|
|
113
|
+
recovery_action = suggest_recovery(error)
|
|
114
|
+
|
|
115
|
+
context = {
|
|
116
|
+
"operation": operation,
|
|
117
|
+
"entity_type": entity_type,
|
|
118
|
+
"entity_id": entity_id,
|
|
119
|
+
"error_type": type(error).__name__,
|
|
120
|
+
"error_message": str(error),
|
|
121
|
+
"error_classification": classification,
|
|
122
|
+
"is_recoverable": recoverable,
|
|
123
|
+
"suggested_action": recovery_action,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if additional_context:
|
|
127
|
+
context.update(additional_context)
|
|
128
|
+
|
|
129
|
+
if include_traceback:
|
|
130
|
+
context["traceback"] = traceback.format_exc()
|
|
131
|
+
|
|
132
|
+
logger.error("operation_failed", **context)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def log_validation_error(
|
|
136
|
+
error: Exception,
|
|
137
|
+
entity_type: str,
|
|
138
|
+
field_name: str | None = None,
|
|
139
|
+
proposed_value: Any | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Log a validation error with field-specific context.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
error: The validation error
|
|
145
|
+
entity_type: Type of entity being validated
|
|
146
|
+
field_name: Name of the field that failed validation
|
|
147
|
+
proposed_value: The value that failed validation
|
|
148
|
+
"""
|
|
149
|
+
logger.warning(
|
|
150
|
+
"validation_error",
|
|
151
|
+
error_type=type(error).__name__,
|
|
152
|
+
error_message=str(error),
|
|
153
|
+
entity_type=entity_type,
|
|
154
|
+
field_name=field_name,
|
|
155
|
+
proposed_value=proposed_value,
|
|
156
|
+
suggested_action="validate_input",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def log_database_error(
|
|
161
|
+
error: Exception,
|
|
162
|
+
operation: str,
|
|
163
|
+
entity_type: str | None = None,
|
|
164
|
+
entity_id: str | None = None,
|
|
165
|
+
retry_count: int = 0,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Log a database error with recovery context.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
error: The database error
|
|
171
|
+
operation: Database operation that failed (create, read, update, delete)
|
|
172
|
+
entity_type: Type of entity
|
|
173
|
+
entity_id: ID of the entity
|
|
174
|
+
retry_count: Number of retries attempted
|
|
175
|
+
"""
|
|
176
|
+
recoverable = is_error_recoverable(error)
|
|
177
|
+
recovery_action = "retry" if recoverable else "manual_intervention"
|
|
178
|
+
|
|
179
|
+
logger.error(
|
|
180
|
+
"database_operation_failed",
|
|
181
|
+
operation=operation,
|
|
182
|
+
entity_type=entity_type,
|
|
183
|
+
entity_id=entity_id,
|
|
184
|
+
error_type=type(error).__name__,
|
|
185
|
+
error_message=str(error),
|
|
186
|
+
retry_count=retry_count,
|
|
187
|
+
is_recoverable=recoverable,
|
|
188
|
+
suggested_action=recovery_action,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def log_external_service_error(
|
|
193
|
+
error: Exception,
|
|
194
|
+
service_name: str,
|
|
195
|
+
operation: str,
|
|
196
|
+
retry_count: int = 0,
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Log an external service error (GitHub API, etc.).
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
error: The error from external service
|
|
202
|
+
service_name: Name of the external service
|
|
203
|
+
operation: Operation being performed
|
|
204
|
+
retry_count: Number of retries attempted
|
|
205
|
+
"""
|
|
206
|
+
logger.error(
|
|
207
|
+
"external_service_error",
|
|
208
|
+
service_name=service_name,
|
|
209
|
+
operation=operation,
|
|
210
|
+
error_type=type(error).__name__,
|
|
211
|
+
error_message=str(error),
|
|
212
|
+
retry_count=retry_count,
|
|
213
|
+
is_recoverable=is_error_recoverable(error),
|
|
214
|
+
suggested_action="check_connectivity"
|
|
215
|
+
if is_error_recoverable(error)
|
|
216
|
+
else "contact_support",
|
|
217
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Log formatters and data scrubbing for structured logging."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
# Sensitive keys that should be redacted from logs
|
|
6
|
+
SENSITIVE_KEYS = {
|
|
7
|
+
"token",
|
|
8
|
+
"password",
|
|
9
|
+
"secret",
|
|
10
|
+
"api_key",
|
|
11
|
+
"auth",
|
|
12
|
+
"credential",
|
|
13
|
+
"github_token",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StructuredFormatter:
|
|
18
|
+
"""Custom formatter that includes structured fields from event data.
|
|
19
|
+
|
|
20
|
+
This formatter enhances logging to include any extra fields that were
|
|
21
|
+
passed as structured data via structlog. This makes error context
|
|
22
|
+
(error_type, error_message, etc.) visible in console/file output.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def format(self, record: dict[str, Any]) -> str:
|
|
26
|
+
"""Format event dict with structured fields.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
record: Event dictionary from structlog
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Formatted log message with structured fields
|
|
33
|
+
"""
|
|
34
|
+
# Get the base message
|
|
35
|
+
base_msg = record.get("event", "")
|
|
36
|
+
|
|
37
|
+
# Collect extra fields that aren't standard structlog attributes
|
|
38
|
+
standard_attrs = {
|
|
39
|
+
"event",
|
|
40
|
+
"log_level",
|
|
41
|
+
"timestamp",
|
|
42
|
+
"_from_structlog",
|
|
43
|
+
"_record",
|
|
44
|
+
"_logger",
|
|
45
|
+
"_method_name",
|
|
46
|
+
"_fn_module",
|
|
47
|
+
"_fn_name",
|
|
48
|
+
"_fn_lineno",
|
|
49
|
+
"exception",
|
|
50
|
+
"exc_info",
|
|
51
|
+
"stack_info",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
extra_fields = {}
|
|
55
|
+
for key, value in record.items():
|
|
56
|
+
if key not in standard_attrs and not key.startswith("_"):
|
|
57
|
+
extra_fields[key] = value
|
|
58
|
+
|
|
59
|
+
# Append extra fields to message if any exist
|
|
60
|
+
if extra_fields:
|
|
61
|
+
# Format structured fields nicely
|
|
62
|
+
field_strs = []
|
|
63
|
+
for key, value in sorted(extra_fields.items()):
|
|
64
|
+
# Truncate very long values
|
|
65
|
+
value_str = str(value)
|
|
66
|
+
if len(value_str) > 200:
|
|
67
|
+
value_str = value_str[:200] + "..."
|
|
68
|
+
field_strs.append(f"{key}={value_str}")
|
|
69
|
+
|
|
70
|
+
base_msg += " | " + " ".join(field_strs)
|
|
71
|
+
|
|
72
|
+
return base_msg
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def scrub_sensitive_data(_logger, _method_name, event_dict):
|
|
76
|
+
"""Structlog processor to remove sensitive data from logs."""
|
|
77
|
+
|
|
78
|
+
def scrub_value(key, value):
|
|
79
|
+
"""Recursively scrub sensitive values."""
|
|
80
|
+
if any(sensitive in key.lower() for sensitive in SENSITIVE_KEYS):
|
|
81
|
+
return "***REDACTED***"
|
|
82
|
+
if isinstance(value, dict):
|
|
83
|
+
return {k: scrub_value(k, v) for k, v in value.items()}
|
|
84
|
+
if isinstance(value, list | tuple):
|
|
85
|
+
return type(value)(scrub_value(f"item_{i}", v) for i, v in enumerate(value))
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
return {k: scrub_value(k, v) for k, v in event_dict.items()}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def include_structured_fields_in_message(_logger, _method_name, event_dict):
|
|
92
|
+
"""Include structured fields (error_type, error_message, etc.) in the log message.
|
|
93
|
+
|
|
94
|
+
This processor appends structured fields to the message so they're visible
|
|
95
|
+
in console/file output, not just in the LogRecord attributes.
|
|
96
|
+
"""
|
|
97
|
+
# Extract message
|
|
98
|
+
msg = event_dict.get("event", "")
|
|
99
|
+
|
|
100
|
+
# Collect non-standard fields (excluding stdlib ones)
|
|
101
|
+
standard_fields = {
|
|
102
|
+
"event",
|
|
103
|
+
"log_level",
|
|
104
|
+
"timestamp",
|
|
105
|
+
"correlation_id",
|
|
106
|
+
"_from_structlog",
|
|
107
|
+
}
|
|
108
|
+
extra_fields = {k: v for k, v in event_dict.items() if k not in standard_fields}
|
|
109
|
+
|
|
110
|
+
# Append extra fields to message if any exist
|
|
111
|
+
if extra_fields:
|
|
112
|
+
field_strs = []
|
|
113
|
+
for key in sorted(extra_fields.keys()):
|
|
114
|
+
value = extra_fields[key]
|
|
115
|
+
value_str = str(value)
|
|
116
|
+
# Truncate very long values
|
|
117
|
+
if len(value_str) > 100:
|
|
118
|
+
value_str = value_str[:100] + "..."
|
|
119
|
+
field_strs.append(f"{key}={value_str}")
|
|
120
|
+
|
|
121
|
+
if field_strs:
|
|
122
|
+
event_dict["event"] = msg + " | " + " ".join(field_strs)
|
|
123
|
+
|
|
124
|
+
return event_dict
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Logger factory functions for different application layers."""
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_logger(name: str = "roadmap") -> structlog.stdlib.BoundLogger:
|
|
7
|
+
"""Get a logger instance.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
name: Logger name
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Configured structlog logger
|
|
14
|
+
"""
|
|
15
|
+
return structlog.get_logger(name)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_domain_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
19
|
+
"""Get logger for domain layer.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
name: Component name (e.g., "issue", "milestone")
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Logger with domain namespace
|
|
26
|
+
"""
|
|
27
|
+
return get_logger(f"roadmap.domain.{name}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_application_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
31
|
+
"""Get logger for application layer.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name: Component name (e.g., "issue_service", "sync_service")
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Logger with application namespace
|
|
38
|
+
"""
|
|
39
|
+
return get_logger(f"roadmap.application.{name}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_infrastructure_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
43
|
+
"""Get logger for infrastructure layer.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
name: Component name (e.g., "github", "storage")
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Logger with infrastructure namespace
|
|
50
|
+
"""
|
|
51
|
+
return get_logger(f"roadmap.infrastructure.{name}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_presentation_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
55
|
+
"""Get logger for presentation layer.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Component name (e.g., "cli.issues", "cli.milestones")
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Logger with presentation namespace
|
|
62
|
+
"""
|
|
63
|
+
return get_logger(f"roadmap.presentation.{name}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_security_logger():
|
|
67
|
+
"""Get the security logger for backwards compatibility."""
|
|
68
|
+
return get_logger("roadmap.security")
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Performance tracking utilities for operation timing and monitoring.
|
|
2
|
+
|
|
3
|
+
Provides context managers and decorators to track operation duration,
|
|
4
|
+
identify bottlenecks, and monitor system performance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Generator
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
|
|
11
|
+
from roadmap.common.logging.loggers import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contextmanager
|
|
17
|
+
def track_operation_time(
|
|
18
|
+
operation_name: str,
|
|
19
|
+
warn_threshold_ms: int = 5000,
|
|
20
|
+
log_level: str = "debug",
|
|
21
|
+
) -> Generator[dict, None, None]:
|
|
22
|
+
"""Context manager to track operation timing.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
operation_name: Name of the operation being timed
|
|
26
|
+
warn_threshold_ms: Log warning if operation exceeds this time
|
|
27
|
+
log_level: Log level to use for normal operations (debug or info)
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
with track_operation_time("sync_issues", warn_threshold_ms=5000):
|
|
31
|
+
sync_to_github()
|
|
32
|
+
"""
|
|
33
|
+
start_time = time.time()
|
|
34
|
+
result = {"duration_ms": 0, "exceeded_threshold": False}
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
yield result
|
|
38
|
+
finally:
|
|
39
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
40
|
+
result["duration_ms"] = duration_ms
|
|
41
|
+
|
|
42
|
+
if duration_ms > warn_threshold_ms:
|
|
43
|
+
result["exceeded_threshold"] = True
|
|
44
|
+
logger.warning(
|
|
45
|
+
"operation_slow",
|
|
46
|
+
operation_name=operation_name,
|
|
47
|
+
duration_ms=duration_ms,
|
|
48
|
+
threshold_ms=warn_threshold_ms,
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
log_func = getattr(logger, log_level, logger.debug)
|
|
52
|
+
log_func(
|
|
53
|
+
"operation_completed",
|
|
54
|
+
operation_name=operation_name,
|
|
55
|
+
duration_ms=duration_ms,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@contextmanager
|
|
60
|
+
def track_database_operation(
|
|
61
|
+
operation: str,
|
|
62
|
+
entity_type: str,
|
|
63
|
+
entity_id: str | None = None,
|
|
64
|
+
warn_threshold_ms: int = 1000,
|
|
65
|
+
) -> Generator[dict, None, None]:
|
|
66
|
+
"""Context manager to track database operation timing.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
operation: Type of operation (create, read, update, delete)
|
|
70
|
+
entity_type: Type of entity
|
|
71
|
+
entity_id: ID of the entity (if applicable)
|
|
72
|
+
warn_threshold_ms: Warn if operation exceeds this time
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
with track_database_operation("create", "issue", entity_id="123"):
|
|
76
|
+
db.create_issue(...)
|
|
77
|
+
"""
|
|
78
|
+
start_time = time.time()
|
|
79
|
+
result = {"duration_ms": 0, "exceeded_threshold": False}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
yield result
|
|
83
|
+
finally:
|
|
84
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
85
|
+
result["duration_ms"] = duration_ms
|
|
86
|
+
|
|
87
|
+
if duration_ms > warn_threshold_ms:
|
|
88
|
+
result["exceeded_threshold"] = True
|
|
89
|
+
logger.warning(
|
|
90
|
+
"database_operation_slow",
|
|
91
|
+
operation=operation,
|
|
92
|
+
entity_type=entity_type,
|
|
93
|
+
entity_id=entity_id,
|
|
94
|
+
duration_ms=duration_ms,
|
|
95
|
+
threshold_ms=warn_threshold_ms,
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
logger.debug(
|
|
99
|
+
"database_operation_completed",
|
|
100
|
+
operation=operation,
|
|
101
|
+
entity_type=entity_type,
|
|
102
|
+
entity_id=entity_id,
|
|
103
|
+
duration_ms=duration_ms,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@contextmanager
|
|
108
|
+
def track_file_operation(
|
|
109
|
+
operation: str,
|
|
110
|
+
file_path: str,
|
|
111
|
+
warn_threshold_ms: int = 500,
|
|
112
|
+
) -> Generator[dict, None, None]:
|
|
113
|
+
"""Context manager to track file I/O operation timing.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
operation: Type of operation (read, write, sync)
|
|
117
|
+
file_path: Path to the file
|
|
118
|
+
warn_threshold_ms: Warn if operation exceeds this time
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
with track_file_operation("sync", "issue.md"):
|
|
122
|
+
sync_issue_file(...)
|
|
123
|
+
"""
|
|
124
|
+
start_time = time.time()
|
|
125
|
+
result = {"duration_ms": 0, "exceeded_threshold": False}
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
yield result
|
|
129
|
+
finally:
|
|
130
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
131
|
+
result["duration_ms"] = duration_ms
|
|
132
|
+
|
|
133
|
+
if duration_ms > warn_threshold_ms:
|
|
134
|
+
result["exceeded_threshold"] = True
|
|
135
|
+
logger.warning(
|
|
136
|
+
"file_operation_slow",
|
|
137
|
+
operation=operation,
|
|
138
|
+
file_path=file_path,
|
|
139
|
+
duration_ms=duration_ms,
|
|
140
|
+
threshold_ms=warn_threshold_ms,
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
logger.debug(
|
|
144
|
+
"file_operation_completed",
|
|
145
|
+
operation=operation,
|
|
146
|
+
file_path=file_path,
|
|
147
|
+
duration_ms=duration_ms,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@contextmanager
|
|
152
|
+
def track_sync_operation(
|
|
153
|
+
operation_name: str,
|
|
154
|
+
entity_count: int | None = None,
|
|
155
|
+
warn_threshold_ms: int = 10000,
|
|
156
|
+
) -> Generator[dict, None, None]:
|
|
157
|
+
"""Context manager to track sync operation timing and throughput.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
operation_name: Name of the sync operation
|
|
161
|
+
entity_count: Number of entities being synced
|
|
162
|
+
warn_threshold_ms: Warn if operation exceeds this time
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
with track_sync_operation("sync_to_github", entity_count=10):
|
|
166
|
+
github_client.sync_issues(...)
|
|
167
|
+
"""
|
|
168
|
+
start_time = time.time()
|
|
169
|
+
result = {
|
|
170
|
+
"duration_ms": 0,
|
|
171
|
+
"exceeded_threshold": False,
|
|
172
|
+
"throughput_items_per_sec": 0,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
yield result
|
|
177
|
+
finally:
|
|
178
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
179
|
+
result["duration_ms"] = duration_ms
|
|
180
|
+
|
|
181
|
+
if entity_count:
|
|
182
|
+
result["throughput_items_per_sec"] = (entity_count * 1000) / duration_ms
|
|
183
|
+
|
|
184
|
+
if duration_ms > warn_threshold_ms:
|
|
185
|
+
result["exceeded_threshold"] = True
|
|
186
|
+
logger.warning(
|
|
187
|
+
"operation_slow",
|
|
188
|
+
operation_name=operation_name,
|
|
189
|
+
duration_ms=duration_ms,
|
|
190
|
+
entity_count=entity_count,
|
|
191
|
+
threshold_ms=warn_threshold_ms,
|
|
192
|
+
throughput_items_per_sec=result.get("throughput_items_per_sec"),
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
logger.info(
|
|
196
|
+
"operation_completed",
|
|
197
|
+
operation_name=operation_name,
|
|
198
|
+
duration_ms=duration_ms,
|
|
199
|
+
entity_count=entity_count,
|
|
200
|
+
throughput_items_per_sec=result.get("throughput_items_per_sec"),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class OperationTimer:
|
|
205
|
+
"""Class for tracking multi-step operations and performance metrics."""
|
|
206
|
+
|
|
207
|
+
def __init__(self, operation_name: str):
|
|
208
|
+
"""Initialize operation timer.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
operation_name: Name of the overall operation
|
|
212
|
+
"""
|
|
213
|
+
self.operation_name = operation_name
|
|
214
|
+
self.start_time = time.time()
|
|
215
|
+
self.steps = {}
|
|
216
|
+
self.current_step = None
|
|
217
|
+
self.current_step_start = None
|
|
218
|
+
|
|
219
|
+
def start_step(self, step_name: str) -> None:
|
|
220
|
+
"""Start tracking a named step.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
step_name: Name of the step
|
|
224
|
+
"""
|
|
225
|
+
if self.current_step:
|
|
226
|
+
self.end_step()
|
|
227
|
+
|
|
228
|
+
self.current_step = step_name
|
|
229
|
+
self.current_step_start = time.time()
|
|
230
|
+
|
|
231
|
+
def end_step(self) -> None:
|
|
232
|
+
"""End tracking current step."""
|
|
233
|
+
if self.current_step and self.current_step_start:
|
|
234
|
+
duration_ms = (time.time() - self.current_step_start) * 1000
|
|
235
|
+
self.steps[self.current_step] = duration_ms
|
|
236
|
+
logger.debug(
|
|
237
|
+
"step_completed",
|
|
238
|
+
operation_name=self.operation_name,
|
|
239
|
+
step=self.current_step,
|
|
240
|
+
duration_ms=duration_ms,
|
|
241
|
+
)
|
|
242
|
+
self.current_step = None
|
|
243
|
+
self.current_step_start = None
|
|
244
|
+
|
|
245
|
+
def finish(self) -> dict:
|
|
246
|
+
"""Finish timing operation and return summary.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dictionary with timing information
|
|
250
|
+
"""
|
|
251
|
+
if self.current_step:
|
|
252
|
+
self.end_step()
|
|
253
|
+
|
|
254
|
+
total_duration_ms = (time.time() - self.start_time) * 1000
|
|
255
|
+
result = {
|
|
256
|
+
"operation": self.operation_name,
|
|
257
|
+
"total_duration_ms": total_duration_ms,
|
|
258
|
+
"steps": self.steps,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
logger.info(
|
|
262
|
+
"operation_finished",
|
|
263
|
+
operation_name=self.operation_name,
|
|
264
|
+
**result,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return result
|