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.
Files changed (521) hide show
  1. roadmap/__init__.py +27 -0
  2. roadmap/adapters/__init__.py +1 -0
  3. roadmap/adapters/base_paginated_adapter.py +117 -0
  4. roadmap/adapters/cli/__init__.py +249 -0
  5. roadmap/adapters/cli/analysis/__init__.py +5 -0
  6. roadmap/adapters/cli/analysis/commands.py +370 -0
  7. roadmap/adapters/cli/analysis/presenter.py +125 -0
  8. roadmap/adapters/cli/archive_operations.py +86 -0
  9. roadmap/adapters/cli/cli_command_helpers.py +109 -0
  10. roadmap/adapters/cli/cli_confirmations.py +74 -0
  11. roadmap/adapters/cli/cli_error_handlers.py +146 -0
  12. roadmap/adapters/cli/cli_validators.py +39 -0
  13. roadmap/adapters/cli/commands/sync_status.py +308 -0
  14. roadmap/adapters/cli/comment/__init__.py +5 -0
  15. roadmap/adapters/cli/comment/commands.py +106 -0
  16. roadmap/adapters/cli/config/__init__.py +5 -0
  17. roadmap/adapters/cli/config/commands.py +198 -0
  18. roadmap/adapters/cli/console_exports.py +10 -0
  19. roadmap/adapters/cli/core.py +26 -0
  20. roadmap/adapters/cli/crud/__init__.py +47 -0
  21. roadmap/adapters/cli/crud/base_archive.py +208 -0
  22. roadmap/adapters/cli/crud/base_create.py +154 -0
  23. roadmap/adapters/cli/crud/base_delete.py +155 -0
  24. roadmap/adapters/cli/crud/base_restore.py +283 -0
  25. roadmap/adapters/cli/crud/base_update.py +193 -0
  26. roadmap/adapters/cli/crud/crud_helpers.py +206 -0
  27. roadmap/adapters/cli/crud/crud_utils.py +152 -0
  28. roadmap/adapters/cli/crud/entity_builders.py +374 -0
  29. roadmap/adapters/cli/data/__init__.py +8 -0
  30. roadmap/adapters/cli/data/commands.py +149 -0
  31. roadmap/adapters/cli/decorators.py +208 -0
  32. roadmap/adapters/cli/dtos/__init__.py +155 -0
  33. roadmap/adapters/cli/exception_handler.py +105 -0
  34. roadmap/adapters/cli/git/__init__.py +8 -0
  35. roadmap/adapters/cli/git/commands.py +475 -0
  36. roadmap/adapters/cli/git/handlers/__init__.py +1 -0
  37. roadmap/adapters/cli/git/handlers/git_authentication_handler.py +124 -0
  38. roadmap/adapters/cli/git/handlers/git_branch_handler.py +238 -0
  39. roadmap/adapters/cli/git/handlers/git_connectivity_handler.py +122 -0
  40. roadmap/adapters/cli/git/handlers/git_hooks_handler.py +105 -0
  41. roadmap/adapters/cli/git/hooks_config.py +327 -0
  42. roadmap/adapters/cli/git/status_display.py +158 -0
  43. roadmap/adapters/cli/health/__init__.py +8 -0
  44. roadmap/adapters/cli/health/db_integrity.py +204 -0
  45. roadmap/adapters/cli/health/enhancer.py +227 -0
  46. roadmap/adapters/cli/health/fixer.py +295 -0
  47. roadmap/adapters/cli/health/fixers/__init__.py +10 -0
  48. roadmap/adapters/cli/health/fixers/corrupted_comments_fixer.py +231 -0
  49. roadmap/adapters/cli/health/fixers/data_integrity_fixer.py +137 -0
  50. roadmap/adapters/cli/health/fixers/duplicate_issues_fixer.py +138 -0
  51. roadmap/adapters/cli/health/fixers/folder_structure_fixer.py +114 -0
  52. roadmap/adapters/cli/health/fixers/label_normalization_fixer.py +162 -0
  53. roadmap/adapters/cli/health/fixers/milestone_name_normalization_fixer.py +181 -0
  54. roadmap/adapters/cli/health/fixers/milestone_naming_compliance_fixer.py +244 -0
  55. roadmap/adapters/cli/health/fixers/milestone_parse_error_fixer.py +162 -0
  56. roadmap/adapters/cli/health/fixers/milestone_validation_fixer.py +211 -0
  57. roadmap/adapters/cli/health/fixers/old_backups_fixer.py +135 -0
  58. roadmap/adapters/cli/health/fixers/orphaned_issues_fixer.py +252 -0
  59. roadmap/adapters/cli/health/formatter.py +250 -0
  60. roadmap/adapters/cli/health/formatters.py +427 -0
  61. roadmap/adapters/cli/health/scan.py +293 -0
  62. roadmap/adapters/cli/init/__init__.py +5 -0
  63. roadmap/adapters/cli/init/commands.py +587 -0
  64. roadmap/adapters/cli/init.py +9 -0
  65. roadmap/adapters/cli/issues/__init__.py +70 -0
  66. roadmap/adapters/cli/issues/archive.py +137 -0
  67. roadmap/adapters/cli/issues/archive_class.py +253 -0
  68. roadmap/adapters/cli/issues/block.py +39 -0
  69. roadmap/adapters/cli/issues/close.py +216 -0
  70. roadmap/adapters/cli/issues/comment.py +178 -0
  71. roadmap/adapters/cli/issues/create.py +162 -0
  72. roadmap/adapters/cli/issues/delete.py +31 -0
  73. roadmap/adapters/cli/issues/deps.py +161 -0
  74. roadmap/adapters/cli/issues/issue_status_helpers.py +111 -0
  75. roadmap/adapters/cli/issues/link.py +308 -0
  76. roadmap/adapters/cli/issues/list.py +402 -0
  77. roadmap/adapters/cli/issues/lookup.py +96 -0
  78. roadmap/adapters/cli/issues/progress.py +102 -0
  79. roadmap/adapters/cli/issues/restore.py +96 -0
  80. roadmap/adapters/cli/issues/restore_class.py +57 -0
  81. roadmap/adapters/cli/issues/start.py +165 -0
  82. roadmap/adapters/cli/issues/sync_status.py +374 -0
  83. roadmap/adapters/cli/issues/unblock.py +43 -0
  84. roadmap/adapters/cli/issues/unlink.py +104 -0
  85. roadmap/adapters/cli/issues/update.py +120 -0
  86. roadmap/adapters/cli/issues/view.py +36 -0
  87. roadmap/adapters/cli/layout.py +220 -0
  88. roadmap/adapters/cli/mappers.py +200 -0
  89. roadmap/adapters/cli/milestones/__init__.py +52 -0
  90. roadmap/adapters/cli/milestones/archive.py +125 -0
  91. roadmap/adapters/cli/milestones/archive_class.py +236 -0
  92. roadmap/adapters/cli/milestones/assign.py +62 -0
  93. roadmap/adapters/cli/milestones/close.py +177 -0
  94. roadmap/adapters/cli/milestones/create.py +131 -0
  95. roadmap/adapters/cli/milestones/delete.py +27 -0
  96. roadmap/adapters/cli/milestones/kanban.py +123 -0
  97. roadmap/adapters/cli/milestones/list.py +87 -0
  98. roadmap/adapters/cli/milestones/recalculate.py +79 -0
  99. roadmap/adapters/cli/milestones/restore.py +87 -0
  100. roadmap/adapters/cli/milestones/restore_class.py +99 -0
  101. roadmap/adapters/cli/milestones/update.py +61 -0
  102. roadmap/adapters/cli/milestones/view.py +142 -0
  103. roadmap/adapters/cli/output_manager.py +319 -0
  104. roadmap/adapters/cli/presentation/base_presenter.py +121 -0
  105. roadmap/adapters/cli/presentation/cleanup_presenter.py +235 -0
  106. roadmap/adapters/cli/presentation/core_initialization_presenter.py +277 -0
  107. roadmap/adapters/cli/presentation/crud_presenter.py +134 -0
  108. roadmap/adapters/cli/presentation/daily_summary_presenter.py +266 -0
  109. roadmap/adapters/cli/presentation/issue_presenter.py +258 -0
  110. roadmap/adapters/cli/presentation/milestone_list_presenter.py +135 -0
  111. roadmap/adapters/cli/presentation/milestone_presenter.py +297 -0
  112. roadmap/adapters/cli/presentation/project_presenter.py +233 -0
  113. roadmap/adapters/cli/presentation/project_status_presenter.py +186 -0
  114. roadmap/adapters/cli/presentation/table_builders.py +95 -0
  115. roadmap/adapters/cli/projects/__init__.py +42 -0
  116. roadmap/adapters/cli/projects/archive.py +127 -0
  117. roadmap/adapters/cli/projects/archive_class.py +96 -0
  118. roadmap/adapters/cli/projects/close.py +94 -0
  119. roadmap/adapters/cli/projects/create.py +50 -0
  120. roadmap/adapters/cli/projects/delete.py +27 -0
  121. roadmap/adapters/cli/projects/list.py +227 -0
  122. roadmap/adapters/cli/projects/restore.py +87 -0
  123. roadmap/adapters/cli/projects/restore_class.py +56 -0
  124. roadmap/adapters/cli/projects/update.py +57 -0
  125. roadmap/adapters/cli/projects/view.py +157 -0
  126. roadmap/adapters/cli/services/__init__.py +1 -0
  127. roadmap/adapters/cli/services/daily_summary_service.py +313 -0
  128. roadmap/adapters/cli/services/export_manager.py +268 -0
  129. roadmap/adapters/cli/services/milestone_list_service.py +266 -0
  130. roadmap/adapters/cli/services/project_initialization_service.py +29 -0
  131. roadmap/adapters/cli/services/project_status_service.py +462 -0
  132. roadmap/adapters/cli/services/sync_service.py +62 -0
  133. roadmap/adapters/cli/status.py +408 -0
  134. roadmap/adapters/cli/styling.py +21 -0
  135. roadmap/adapters/cli/sync.py +1117 -0
  136. roadmap/adapters/cli/sync_context.py +432 -0
  137. roadmap/adapters/cli/sync_handlers/__init__.py +77 -0
  138. roadmap/adapters/cli/sync_handlers/apply_ops.py +522 -0
  139. roadmap/adapters/cli/sync_handlers/baseline_ops.py +366 -0
  140. roadmap/adapters/cli/sync_handlers/conflict_ops.py +161 -0
  141. roadmap/adapters/cli/sync_handlers/dry_run_display.py +237 -0
  142. roadmap/adapters/cli/sync_handlers/interactive_resolver.py +396 -0
  143. roadmap/adapters/cli/sync_handlers/progress_tracker.py +128 -0
  144. roadmap/adapters/cli/sync_metrics_command.py +190 -0
  145. roadmap/adapters/cli/sync_presenter.py +58 -0
  146. roadmap/adapters/cli/sync_validation.py +295 -0
  147. roadmap/adapters/cli/today.py +52 -0
  148. roadmap/adapters/cli/utils/click_options.py +38 -0
  149. roadmap/adapters/git/__init__.py +1 -0
  150. roadmap/adapters/git/git.py +208 -0
  151. roadmap/adapters/git/git_branch_manager.py +400 -0
  152. roadmap/adapters/git/git_command_executor.py +56 -0
  153. roadmap/adapters/git/git_commit_analyzer.py +334 -0
  154. roadmap/adapters/git/git_hooks.py +22 -0
  155. roadmap/adapters/git/git_hooks_manager.py +455 -0
  156. roadmap/adapters/git/git_repository_info.py +65 -0
  157. roadmap/adapters/git/hook_installer.py +106 -0
  158. roadmap/adapters/git/hook_registry.py +97 -0
  159. roadmap/adapters/git/hook_script_generator.py +58 -0
  160. roadmap/adapters/git/sync_monitor.py +409 -0
  161. roadmap/adapters/git/workflow_automation.py +286 -0
  162. roadmap/adapters/github/__init__.py +1 -0
  163. roadmap/adapters/github/github.py +360 -0
  164. roadmap/adapters/github/handlers/__init__.py +17 -0
  165. roadmap/adapters/github/handlers/base.py +337 -0
  166. roadmap/adapters/github/handlers/collaborators.py +165 -0
  167. roadmap/adapters/github/handlers/comments.py +118 -0
  168. roadmap/adapters/github/handlers/issues.py +150 -0
  169. roadmap/adapters/github/handlers/labels.py +164 -0
  170. roadmap/adapters/github/handlers/milestones.py +104 -0
  171. roadmap/adapters/persistence/__init__.py +35 -0
  172. roadmap/adapters/persistence/conflict_resolver.py +190 -0
  173. roadmap/adapters/persistence/database_manager.py +492 -0
  174. roadmap/adapters/persistence/entity_sync_coordinators.py +672 -0
  175. roadmap/adapters/persistence/file_locking.py +348 -0
  176. roadmap/adapters/persistence/file_parser.py +92 -0
  177. roadmap/adapters/persistence/file_synchronizer.py +107 -0
  178. roadmap/adapters/persistence/focused_managers.py +140 -0
  179. roadmap/adapters/persistence/git_history.py +330 -0
  180. roadmap/adapters/persistence/parser/__init__.py +20 -0
  181. roadmap/adapters/persistence/parser/frontmatter.py +152 -0
  182. roadmap/adapters/persistence/parser/issue.py +245 -0
  183. roadmap/adapters/persistence/parser/milestone.py +146 -0
  184. roadmap/adapters/persistence/parser/project.py +106 -0
  185. roadmap/adapters/persistence/persistence.py +288 -0
  186. roadmap/adapters/persistence/repositories/__init__.py +15 -0
  187. roadmap/adapters/persistence/repositories/issue_repository.py +223 -0
  188. roadmap/adapters/persistence/repositories/milestone_repository.py +140 -0
  189. roadmap/adapters/persistence/repositories/project_repository.py +168 -0
  190. roadmap/adapters/persistence/repositories/remote_link_repository.py +355 -0
  191. roadmap/adapters/persistence/repositories/sync_state_repository.py +66 -0
  192. roadmap/adapters/persistence/storage/__init__.py +36 -0
  193. roadmap/adapters/persistence/storage/conflicts.py +101 -0
  194. roadmap/adapters/persistence/storage/connection_manager.py +79 -0
  195. roadmap/adapters/persistence/storage/issue_storage.py +89 -0
  196. roadmap/adapters/persistence/storage/milestone_storage.py +80 -0
  197. roadmap/adapters/persistence/storage/project_storage.py +102 -0
  198. roadmap/adapters/persistence/storage/queries.py +227 -0
  199. roadmap/adapters/persistence/storage/state_manager.py +652 -0
  200. roadmap/adapters/persistence/storage/sync_state_storage.py +207 -0
  201. roadmap/adapters/persistence/sync_metrics_repository.py +342 -0
  202. roadmap/adapters/persistence/sync_orchestrator.py +293 -0
  203. roadmap/adapters/persistence/sync_state_tracker.py +86 -0
  204. roadmap/adapters/persistence/yaml_repositories.py +711 -0
  205. roadmap/adapters/sync/__init__.py +21 -0
  206. roadmap/adapters/sync/backend_factory.py +108 -0
  207. roadmap/adapters/sync/backends/__init__.py +15 -0
  208. roadmap/adapters/sync/backends/converters.py +209 -0
  209. roadmap/adapters/sync/backends/github_backend_helpers.py +380 -0
  210. roadmap/adapters/sync/backends/github_client.py +99 -0
  211. roadmap/adapters/sync/backends/github_sync_backend.py +792 -0
  212. roadmap/adapters/sync/backends/github_sync_ops.py +908 -0
  213. roadmap/adapters/sync/backends/services/__init__.py +1 -0
  214. roadmap/adapters/sync/backends/services/github_authentication_service.py +105 -0
  215. roadmap/adapters/sync/backends/services/github_issue_delete_service.py +423 -0
  216. roadmap/adapters/sync/backends/services/github_issue_dependency_service.py +63 -0
  217. roadmap/adapters/sync/backends/services/github_issue_fetch_service.py +158 -0
  218. roadmap/adapters/sync/backends/services/github_label_sync_service.py +126 -0
  219. roadmap/adapters/sync/backends/services/github_local_issue_sync_service.py +229 -0
  220. roadmap/adapters/sync/backends/services/github_milestone_fetch_service.py +204 -0
  221. roadmap/adapters/sync/backends/vanilla_git_sync_backend.py +198 -0
  222. roadmap/adapters/sync/services/__init__.py +11 -0
  223. roadmap/adapters/sync/services/baseline_state_handler.py +83 -0
  224. roadmap/adapters/sync/services/conflict_converter.py +85 -0
  225. roadmap/adapters/sync/services/issue_persistence_service.py +263 -0
  226. roadmap/adapters/sync/services/issue_state_service.py +243 -0
  227. roadmap/adapters/sync/services/local_change_filter.py +129 -0
  228. roadmap/adapters/sync/services/pull_result_processor.py +71 -0
  229. roadmap/adapters/sync/services/remote_issue_creation_service.py +112 -0
  230. roadmap/adapters/sync/services/sync_analysis_service.py +250 -0
  231. roadmap/adapters/sync/services/sync_authentication_service.py +57 -0
  232. roadmap/adapters/sync/services/sync_data_fetch_service.py +212 -0
  233. roadmap/adapters/sync/services/sync_linking_service.py +329 -0
  234. roadmap/adapters/sync/services/sync_report_service.py +62 -0
  235. roadmap/adapters/sync/services/sync_state_update_service.py +51 -0
  236. roadmap/adapters/sync/sync_cache_orchestrator.py +454 -0
  237. roadmap/adapters/sync/sync_merge_engine.py +416 -0
  238. roadmap/adapters/sync/sync_merge_orchestrator.py +1313 -0
  239. roadmap/adapters/sync/sync_retrieval_orchestrator.py +810 -0
  240. roadmap/application/services/__init__.py +1 -0
  241. roadmap/application/services/deduplicate_service.py +517 -0
  242. roadmap/application/use_cases/__init__.py +1 -0
  243. roadmap/common/__init__.py +308 -0
  244. roadmap/common/cache.py +209 -0
  245. roadmap/common/cli_errors.py +147 -0
  246. roadmap/common/configuration/__init__.py +33 -0
  247. roadmap/common/configuration/config_loader.py +264 -0
  248. roadmap/common/configuration/config_manager.py +164 -0
  249. roadmap/common/configuration/config_schema.py +145 -0
  250. roadmap/common/configuration/github/__init__.py +9 -0
  251. roadmap/common/configuration/github/config_manager.py +154 -0
  252. roadmap/common/configuration/github/token_resolver.py +84 -0
  253. roadmap/common/console.py +184 -0
  254. roadmap/common/constants.py +134 -0
  255. roadmap/common/datetime_parser.py +319 -0
  256. roadmap/common/error_formatter.py +84 -0
  257. roadmap/common/errors/__init__.py +143 -0
  258. roadmap/common/errors/error_base.py +73 -0
  259. roadmap/common/errors/error_file.py +82 -0
  260. roadmap/common/errors/error_git.py +67 -0
  261. roadmap/common/errors/error_handler.py +166 -0
  262. roadmap/common/errors/error_network.py +90 -0
  263. roadmap/common/errors/error_security.py +78 -0
  264. roadmap/common/errors/error_standards.py +561 -0
  265. roadmap/common/errors/error_validation.py +111 -0
  266. roadmap/common/errors/exceptions.py +432 -0
  267. roadmap/common/formatters/__init__.py +29 -0
  268. roadmap/common/formatters/base_table_formatter.py +96 -0
  269. roadmap/common/formatters/export/__init__.py +7 -0
  270. roadmap/common/formatters/export/issue_exporter.py +97 -0
  271. roadmap/common/formatters/helpers.py +28 -0
  272. roadmap/common/formatters/kanban/__init__.py +9 -0
  273. roadmap/common/formatters/kanban/layout.py +48 -0
  274. roadmap/common/formatters/kanban/organizer.py +81 -0
  275. roadmap/common/formatters/output/__init__.py +15 -0
  276. roadmap/common/formatters/tables/__init__.py +11 -0
  277. roadmap/common/formatters/tables/column_factory.py +312 -0
  278. roadmap/common/formatters/tables/issue_table.py +276 -0
  279. roadmap/common/formatters/tables/milestone_table.py +247 -0
  280. roadmap/common/formatters/tables/project_table.py +195 -0
  281. roadmap/common/formatters/text/__init__.py +57 -0
  282. roadmap/common/formatters/text/basic.py +217 -0
  283. roadmap/common/formatters/text/display.py +42 -0
  284. roadmap/common/formatters/text/duration.py +36 -0
  285. roadmap/common/formatters/text/operations.py +325 -0
  286. roadmap/common/formatters/text/status_badges.py +57 -0
  287. roadmap/common/initialization/github/__init__.py +11 -0
  288. roadmap/common/initialization/github/setup_service.py +238 -0
  289. roadmap/common/initialization/github/setup_validator.py +78 -0
  290. roadmap/common/logging/__init__.py +113 -0
  291. roadmap/common/logging/audit_logging.py +5 -0
  292. roadmap/common/logging/correlation.py +45 -0
  293. roadmap/common/logging/decorators.py +267 -0
  294. roadmap/common/logging/error_logging.py +217 -0
  295. roadmap/common/logging/formatters.py +124 -0
  296. roadmap/common/logging/loggers.py +68 -0
  297. roadmap/common/logging/performance_tracking.py +267 -0
  298. roadmap/common/logging/utils.py +335 -0
  299. roadmap/common/models/__init__.py +44 -0
  300. roadmap/common/models/cli_models.py +118 -0
  301. roadmap/common/models/config_models.py +199 -0
  302. roadmap/common/models/output_models.py +381 -0
  303. roadmap/common/observability/__init__.py +28 -0
  304. roadmap/common/observability/instrumentation.py +103 -0
  305. roadmap/common/observability/observability.py +76 -0
  306. roadmap/common/observability/otel_init.py +109 -0
  307. roadmap/common/output_formatter.py +393 -0
  308. roadmap/common/progress.py +379 -0
  309. roadmap/common/result.py +396 -0
  310. roadmap/common/security/__init__.py +48 -0
  311. roadmap/common/security/exceptions.py +13 -0
  312. roadmap/common/security/export_cleanup.py +79 -0
  313. roadmap/common/security/file_operations.py +104 -0
  314. roadmap/common/security/filename_sanitization.py +54 -0
  315. roadmap/common/security/logging.py +27 -0
  316. roadmap/common/security/path_validation.py +137 -0
  317. roadmap/common/security/temp_files.py +41 -0
  318. roadmap/common/services/__init__.py +65 -0
  319. roadmap/common/services/decorators.py +183 -0
  320. roadmap/common/services/logging_utils.py +208 -0
  321. roadmap/common/services/metrics.py +161 -0
  322. roadmap/common/services/performance.py +219 -0
  323. roadmap/common/services/profiling.py +288 -0
  324. roadmap/common/services/retry.py +288 -0
  325. roadmap/common/status_style_manager.py +114 -0
  326. roadmap/common/union_find.py +88 -0
  327. roadmap/common/update_constants.py +13 -0
  328. roadmap/common/utils/__init__.py +65 -0
  329. roadmap/common/utils/cli_helpers.py +381 -0
  330. roadmap/common/utils/file_utils.py +440 -0
  331. roadmap/common/utils/path_utils.py +38 -0
  332. roadmap/common/utils/status_utils.py +130 -0
  333. roadmap/common/utils/timezone_utils.py +480 -0
  334. roadmap/common/validation/__init__.py +37 -0
  335. roadmap/common/validation/field_validator.py +104 -0
  336. roadmap/common/validation/result.py +37 -0
  337. roadmap/common/validation/roadmap_validator.py +310 -0
  338. roadmap/common/validation/schema_validator.py +36 -0
  339. roadmap/common/validation/validators.py +50 -0
  340. roadmap/core/__init__.py +1 -0
  341. roadmap/core/domain/__init__.py +30 -0
  342. roadmap/core/domain/comment.py +24 -0
  343. roadmap/core/domain/health.py +15 -0
  344. roadmap/core/domain/issue.py +274 -0
  345. roadmap/core/domain/milestone.py +173 -0
  346. roadmap/core/domain/ports/__init__.py +1 -0
  347. roadmap/core/domain/ports/baseline_repository.py +24 -0
  348. roadmap/core/domain/ports/issue_repository.py +34 -0
  349. roadmap/core/domain/ports/remote_backend_port.py +31 -0
  350. roadmap/core/domain/project.py +130 -0
  351. roadmap/core/interfaces/__init__.py +109 -0
  352. roadmap/core/interfaces/assignee_validator.py +40 -0
  353. roadmap/core/interfaces/backend_factory.py +175 -0
  354. roadmap/core/interfaces/github.py +102 -0
  355. roadmap/core/interfaces/parsers.py +140 -0
  356. roadmap/core/interfaces/persistence.py +45 -0
  357. roadmap/core/interfaces/repositories.py +174 -0
  358. roadmap/core/interfaces/state_managers.py +111 -0
  359. roadmap/core/interfaces/state_storage.py +64 -0
  360. roadmap/core/interfaces/sync_backend.py +275 -0
  361. roadmap/core/interfaces/sync_services.py +119 -0
  362. roadmap/core/interfaces/sync_validators.py +180 -0
  363. roadmap/core/models/__init__.py +31 -0
  364. roadmap/core/models/sync_models.py +162 -0
  365. roadmap/core/models.py +115 -0
  366. roadmap/core/observability/__init__.py +17 -0
  367. roadmap/core/observability/sync_metrics.py +699 -0
  368. roadmap/core/repositories.py +200 -0
  369. roadmap/core/services/__init__.py +313 -0
  370. roadmap/core/services/baseline/__init__.py +1 -0
  371. roadmap/core/services/baseline/baseline_builder_progress.py +274 -0
  372. roadmap/core/services/baseline/baseline_retriever.py +63 -0
  373. roadmap/core/services/baseline/baseline_selector.py +248 -0
  374. roadmap/core/services/baseline/baseline_state_retriever.py +284 -0
  375. roadmap/core/services/baseline/optimized_baseline_builder.py +425 -0
  376. roadmap/core/services/comment/__init__.py +1 -0
  377. roadmap/core/services/comment/comment_service.py +277 -0
  378. roadmap/core/services/git/__init__.py +1 -0
  379. roadmap/core/services/git/git_hook_auto_sync_service.py +403 -0
  380. roadmap/core/services/github/__init__.py +1 -0
  381. roadmap/core/services/github/github_change_detector.py +162 -0
  382. roadmap/core/services/github/github_config_validator.py +148 -0
  383. roadmap/core/services/github/github_conflict_detector.py +179 -0
  384. roadmap/core/services/github/github_entity_classifier.py +51 -0
  385. roadmap/core/services/github/github_integration_service.py +439 -0
  386. roadmap/core/services/github/github_issue_client.py +346 -0
  387. roadmap/core/services/health/__init__.py +1 -0
  388. roadmap/core/services/health/backup_cleanup_service.py +202 -0
  389. roadmap/core/services/health/data_integrity_validator_service.py +90 -0
  390. roadmap/core/services/health/entity_health_scanner.py +460 -0
  391. roadmap/core/services/health/file_repair_service.py +193 -0
  392. roadmap/core/services/health/health_check_service.py +166 -0
  393. roadmap/core/services/health/health_models.py +80 -0
  394. roadmap/core/services/health/infrastructure_validator_service.py +328 -0
  395. roadmap/core/services/health/issue_health_scanner.py +263 -0
  396. roadmap/core/services/helpers/__init__.py +18 -0
  397. roadmap/core/services/helpers/status_change_helpers.py +88 -0
  398. roadmap/core/services/initialization/__init__.py +19 -0
  399. roadmap/core/services/initialization/utils.py +104 -0
  400. roadmap/core/services/initialization/validator.py +78 -0
  401. roadmap/core/services/initialization/workflow.py +182 -0
  402. roadmap/core/services/initialization_service.py +111 -0
  403. roadmap/core/services/issue/__init__.py +1 -0
  404. roadmap/core/services/issue/assignee_validation_service.py +312 -0
  405. roadmap/core/services/issue/issue_creation_service.py +309 -0
  406. roadmap/core/services/issue/issue_filter_service.py +302 -0
  407. roadmap/core/services/issue/issue_matching_service.py +127 -0
  408. roadmap/core/services/issue/issue_service.py +765 -0
  409. roadmap/core/services/issue/issue_update_service.py +144 -0
  410. roadmap/core/services/issue/start_issue_service.py +174 -0
  411. roadmap/core/services/issue_helpers/__init__.py +18 -0
  412. roadmap/core/services/issue_helpers/issue_filters.py +296 -0
  413. roadmap/core/services/milestone_service.py +438 -0
  414. roadmap/core/services/project/__init__.py +1 -0
  415. roadmap/core/services/project/project_service.py +434 -0
  416. roadmap/core/services/project/project_status_service.py +125 -0
  417. roadmap/core/services/project_init/__init__.py +22 -0
  418. roadmap/core/services/project_init/context_detection.py +146 -0
  419. roadmap/core/services/project_init/creation.py +76 -0
  420. roadmap/core/services/project_init/detection.py +51 -0
  421. roadmap/core/services/project_init/template.py +228 -0
  422. roadmap/core/services/status_change_service.py +88 -0
  423. roadmap/core/services/sync/__init__.py +1 -0
  424. roadmap/core/services/sync/batch_processor.py +290 -0
  425. roadmap/core/services/sync/conflict_resolver.py +154 -0
  426. roadmap/core/services/sync/dependency_resolver.py +367 -0
  427. roadmap/core/services/sync/duplicate_detector.py +771 -0
  428. roadmap/core/services/sync/duplicate_resolver.py +315 -0
  429. roadmap/core/services/sync/error_classification.py +470 -0
  430. roadmap/core/services/sync/performance.py +263 -0
  431. roadmap/core/services/sync/sync_change_computer.py +211 -0
  432. roadmap/core/services/sync/sync_checkpoint.py +348 -0
  433. roadmap/core/services/sync/sync_conflict_detector.py +147 -0
  434. roadmap/core/services/sync/sync_conflict_resolver.py +448 -0
  435. roadmap/core/services/sync/sync_errors.py +383 -0
  436. roadmap/core/services/sync/sync_key_normalizer.py +222 -0
  437. roadmap/core/services/sync/sync_metadata_service.py +361 -0
  438. roadmap/core/services/sync/sync_plan.py +171 -0
  439. roadmap/core/services/sync/sync_plan_executor.py +447 -0
  440. roadmap/core/services/sync/sync_report.py +296 -0
  441. roadmap/core/services/sync/sync_state.py +173 -0
  442. roadmap/core/services/sync/sync_state_comparator.py +690 -0
  443. roadmap/core/services/sync/sync_state_manager.py +431 -0
  444. roadmap/core/services/sync/sync_state_normalizer.py +98 -0
  445. roadmap/core/services/sync/sync_three_way.py +78 -0
  446. roadmap/core/services/sync/three_way_merger.py +178 -0
  447. roadmap/core/services/utils/__init__.py +1 -0
  448. roadmap/core/services/utils/configuration_service.py +113 -0
  449. roadmap/core/services/utils/critical_path_calculator.py +418 -0
  450. roadmap/core/services/utils/dependency_analyzer.py +360 -0
  451. roadmap/core/services/utils/field_conflict_detector.py +126 -0
  452. roadmap/core/services/utils/remote_fetcher.py +202 -0
  453. roadmap/core/services/utils/remote_state_normalizer.py +110 -0
  454. roadmap/core/services/utils/retry_policy.py +51 -0
  455. roadmap/core/services/validator_base.py +130 -0
  456. roadmap/core/services/validators/__init__.py +28 -0
  457. roadmap/core/services/validators/_utils.py +20 -0
  458. roadmap/core/services/validators/archivable_issues_validator.py +71 -0
  459. roadmap/core/services/validators/archivable_milestones_validator.py +69 -0
  460. roadmap/core/services/validators/backup_validator.py +89 -0
  461. roadmap/core/services/validators/data_integrity_validator.py +84 -0
  462. roadmap/core/services/validators/duplicate_issues_validator.py +75 -0
  463. roadmap/core/services/validators/duplicate_milestones_validator.py +141 -0
  464. roadmap/core/services/validators/folder_structure_validator.py +165 -0
  465. roadmap/core/services/validators/health_status_utils.py +28 -0
  466. roadmap/core/services/validators/milestone_naming_validator.py +146 -0
  467. roadmap/core/services/validators/missing_headlines_validator.py +97 -0
  468. roadmap/core/services/validators/orphaned_issues_validator.py +142 -0
  469. roadmap/core/services/validators/orphaned_milestones_validator.py +85 -0
  470. roadmap/core/utils/git_remote_parser.py +83 -0
  471. roadmap/core/utils/github_urls.py +66 -0
  472. roadmap/domain/validation.py +30 -0
  473. roadmap/infrastructure/__init__.py +55 -0
  474. roadmap/infrastructure/coordination/__init__.py +8 -0
  475. roadmap/infrastructure/coordination/coordinator_params.py +82 -0
  476. roadmap/infrastructure/coordination/core.py +446 -0
  477. roadmap/infrastructure/coordination/git_coordinator.py +160 -0
  478. roadmap/infrastructure/coordination/initialization.py +348 -0
  479. roadmap/infrastructure/coordination/issue_coordinator.py +154 -0
  480. roadmap/infrastructure/coordination/issue_operations.py +456 -0
  481. roadmap/infrastructure/coordination/milestone_coordinator.py +135 -0
  482. roadmap/infrastructure/coordination/milestone_operations.py +201 -0
  483. roadmap/infrastructure/coordination/project_coordinator.py +113 -0
  484. roadmap/infrastructure/coordination/project_operations.py +144 -0
  485. roadmap/infrastructure/coordination/team_coordinator.py +107 -0
  486. roadmap/infrastructure/coordination/user_operations.py +150 -0
  487. roadmap/infrastructure/coordination/validation_coordinator.py +249 -0
  488. roadmap/infrastructure/coordination_gateway.py +152 -0
  489. roadmap/infrastructure/git/__init__.py +6 -0
  490. roadmap/infrastructure/git/git_integration_ops.py +234 -0
  491. roadmap/infrastructure/git_gateway.py +35 -0
  492. roadmap/infrastructure/github_gateway.py +52 -0
  493. roadmap/infrastructure/maintenance/__init__.py +5 -0
  494. roadmap/infrastructure/maintenance/cleanup.py +483 -0
  495. roadmap/infrastructure/observability/__init__.py +7 -0
  496. roadmap/infrastructure/observability/health.py +196 -0
  497. roadmap/infrastructure/observability/health_formatter.py +73 -0
  498. roadmap/infrastructure/observability/specialized_health_checkers.py +188 -0
  499. roadmap/infrastructure/persistence_gateway.py +164 -0
  500. roadmap/infrastructure/security/__init__.py +1 -0
  501. roadmap/infrastructure/security/credentials.py +489 -0
  502. roadmap/infrastructure/sync_gateway.py +94 -0
  503. roadmap/infrastructure/validation/__init__.py +7 -0
  504. roadmap/infrastructure/validation/file_enumeration.py +219 -0
  505. roadmap/infrastructure/validation/github_assignee_validator.py +47 -0
  506. roadmap/infrastructure/validation/github_validator.py +101 -0
  507. roadmap/infrastructure/validation/milestone_consistency_validator.py +96 -0
  508. roadmap/infrastructure/validation/vanilla_assignee_validator.py +37 -0
  509. roadmap/infrastructure/validation_gateway.py +96 -0
  510. roadmap/presentation/formatters/sync_metrics_formatter.py +355 -0
  511. roadmap/settings.py +345 -0
  512. roadmap/templates/github_workflows/ci-cd-roadmap.yml +241 -0
  513. roadmap/templates/github_workflows/issue-lifecycle.yml +236 -0
  514. roadmap/templates/github_workflows/roadmap-integration.yml +137 -0
  515. roadmap/templates/github_workflows/roadmap-starter.yml +59 -0
  516. roadmap/version.py +381 -0
  517. roadmap_cli-0.1.1.dist-info/METADATA +448 -0
  518. roadmap_cli-0.1.1.dist-info/RECORD +521 -0
  519. roadmap_cli-0.1.1.dist-info/WHEEL +4 -0
  520. roadmap_cli-0.1.1.dist-info/entry_points.txt +2 -0
  521. 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