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