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,440 @@
1
+ """Unified file operations utilities for roadmap CLI application.
2
+
3
+ This module provides centralized, consistent file operations to eliminate
4
+ DRY violations across the codebase. All file operations should use these
5
+ utilities to ensure consistent error handling, logging, and behavior.
6
+
7
+ Classes:
8
+ FileOperationError: Base exception for file operation failures
9
+ SecureFileManager: Context manager for secure file operations
10
+
11
+ Functions:
12
+ ensure_directory_exists: Safely create directories with proper error handling
13
+ safe_write_file: Write files with atomic operations and backup support
14
+ safe_read_file: Read files with proper error handling and encoding
15
+ file_exists_check: Unified file existence checking with optional validation
16
+ get_file_size: Get file size with error handling
17
+ backup_file: Create backup of existing file before modification
18
+ """
19
+
20
+ import os
21
+ import shutil
22
+ import tempfile
23
+ from contextlib import contextmanager
24
+ from pathlib import Path
25
+
26
+ from structlog import get_logger
27
+
28
+ # File operations logger
29
+ file_logger = get_logger()
30
+
31
+
32
+ class FileOperationError(Exception):
33
+ """Base exception for file operation failures."""
34
+
35
+ def __init__(
36
+ self, message: str, path: Path | None = None, operation: str | None = None
37
+ ):
38
+ """Initialize FileOperationError.
39
+
40
+ Args:
41
+ message: Error message.
42
+ path: Path involved in the operation.
43
+ operation: Name of the operation that failed.
44
+ """
45
+ super().__init__(message)
46
+ self.path = path
47
+ self.operation = operation
48
+ file_logger.error(
49
+ f"FileOperationError: {message} (path: {path}, operation: {operation})"
50
+ )
51
+
52
+
53
+ class DirectoryCreationError(FileOperationError):
54
+ """Exception raised for directory creation failures."""
55
+
56
+ pass
57
+
58
+
59
+ class FileReadError(FileOperationError):
60
+ """Exception raised for file reading failures."""
61
+
62
+ pass
63
+
64
+
65
+ class FileWriteError(FileOperationError):
66
+ """Exception raised for file writing failures."""
67
+
68
+ pass
69
+
70
+
71
+ @contextmanager
72
+ def SecureFileManager(file_path: str | Path, mode: str = "w", **kwargs):
73
+ """Context manager for secure file operations with automatic cleanup.
74
+
75
+ Args:
76
+ file_path: Path to the file
77
+ mode: File open mode
78
+ **kwargs: Additional arguments passed to open()
79
+
80
+ Yields:
81
+ File object
82
+
83
+ Raises:
84
+ FileOperationError: If file operation fails
85
+ """
86
+ file_path = Path(file_path)
87
+ temp_file = None
88
+
89
+ try:
90
+ # Ensure parent directory exists
91
+ ensure_directory_exists(file_path.parent)
92
+
93
+ # For write operations, use temporary file for atomicity
94
+ if "w" in mode or "a" in mode:
95
+ temp_file = tempfile.NamedTemporaryFile(
96
+ mode=mode, dir=file_path.parent, delete=False, **kwargs
97
+ )
98
+ yield temp_file
99
+ temp_file.close()
100
+
101
+ # Atomic move to final location
102
+ shutil.move(temp_file.name, file_path)
103
+ temp_file = None
104
+
105
+ else:
106
+ # For read operations, open directly
107
+ with open(file_path, mode, **kwargs) as f:
108
+ yield f
109
+
110
+ except Exception as e:
111
+ if temp_file and hasattr(temp_file, "name"):
112
+ try:
113
+ os.unlink(temp_file.name)
114
+ except OSError as cleanup_error:
115
+ file_logger.debug(
116
+ "failed_to_cleanup_temp_file",
117
+ error=str(cleanup_error),
118
+ severity="operational",
119
+ )
120
+ raise FileOperationError(
121
+ f"Secure file operation failed: {e}", Path(file_path), mode
122
+ ) from e
123
+
124
+
125
+ def ensure_directory_exists(
126
+ directory_path: str | Path,
127
+ permissions: int = 0o755,
128
+ parents: bool = True,
129
+ exist_ok: bool = True,
130
+ ) -> Path:
131
+ """Safely create directory with proper error handling and logging.
132
+
133
+ This function centralizes all directory creation logic to eliminate
134
+ the repeated pattern: directory.mkdir(parents=True, exist_ok=True)
135
+
136
+ Args:
137
+ directory_path: Path to the directory to create
138
+ permissions: Directory permissions (default: 0o755)
139
+ parents: Create parent directories if needed (default: True)
140
+ exist_ok: Don't raise error if directory already exists (default: True)
141
+
142
+ Returns:
143
+ Path object of the created directory
144
+
145
+ Raises:
146
+ DirectoryCreationError: If directory creation fails
147
+ """
148
+ try:
149
+ directory_path = Path(directory_path)
150
+ directory_path.mkdir(mode=permissions, parents=parents, exist_ok=exist_ok)
151
+
152
+ file_logger.debug(
153
+ "directory_ensured",
154
+ directory_path=str(directory_path),
155
+ )
156
+ return directory_path
157
+
158
+ except OSError as e:
159
+ raise DirectoryCreationError(
160
+ f"Failed to create directory: {e}", Path(directory_path), "mkdir"
161
+ ) from e
162
+
163
+
164
+ def safe_write_file(
165
+ file_path: str | Path,
166
+ content: str,
167
+ encoding: str = "utf-8",
168
+ backup: bool = False,
169
+ atomic: bool = True,
170
+ ) -> Path:
171
+ """Write file content safely with atomic operations and optional backup.
172
+
173
+ Args:
174
+ file_path: Path to the file to write
175
+ content: Content to write to the file
176
+ encoding: File encoding (default: utf-8)
177
+ backup: Create backup of existing file (default: False)
178
+ atomic: Use atomic write operation (default: True)
179
+
180
+ Returns:
181
+ Path object of the written file
182
+
183
+ Raises:
184
+ FileWriteError: If file writing fails
185
+ """
186
+ try:
187
+ file_path = Path(file_path)
188
+
189
+ # Ensure parent directory exists
190
+ ensure_directory_exists(file_path.parent)
191
+
192
+ # Create backup if requested and file exists
193
+ if backup and file_path.exists():
194
+ backup_file(file_path)
195
+
196
+ if atomic:
197
+ # Use atomic write with temporary file
198
+ with SecureFileManager(file_path, "w", encoding=encoding) as f:
199
+ f.write(content)
200
+ else:
201
+ # Direct write
202
+ with open(file_path, "w", encoding=encoding) as f:
203
+ f.write(content)
204
+
205
+ file_logger.debug(
206
+ "file_written_safely",
207
+ file_path=str(file_path),
208
+ char_count=len(content),
209
+ )
210
+ return file_path
211
+
212
+ except Exception as e:
213
+ raise FileWriteError(
214
+ f"Failed to write file: {e}", Path(file_path), "write"
215
+ ) from e
216
+
217
+
218
+ def safe_read_file(
219
+ file_path: str | Path, encoding: str = "utf-8", default: str | None = None
220
+ ) -> str:
221
+ """Read file content safely with proper error handling.
222
+
223
+ Args:
224
+ file_path: Path to the file to read
225
+ encoding: File encoding (default: utf-8)
226
+ default: Default content to return if file doesn't exist
227
+
228
+ Returns:
229
+ File content as string
230
+
231
+ Raises:
232
+ FileReadError: If file reading fails
233
+ FileNotFoundError: If file doesn't exist and no default provided
234
+ """
235
+ try:
236
+ file_path = Path(file_path)
237
+
238
+ if not file_path.exists():
239
+ if default is not None:
240
+ file_logger.debug(
241
+ "file_not_found_returning_default",
242
+ file_path=str(file_path),
243
+ )
244
+ return default
245
+ else:
246
+ raise FileNotFoundError(f"File not found: {file_path}")
247
+
248
+ content = file_path.read_text(encoding=encoding)
249
+ file_logger.debug(
250
+ "file_read_safely",
251
+ file_path=str(file_path),
252
+ char_count=len(content),
253
+ )
254
+ return content
255
+
256
+ except FileNotFoundError:
257
+ raise
258
+ except Exception as e:
259
+ raise FileReadError(f"Failed to read file: {e}", Path(file_path), "read") from e
260
+
261
+
262
+ def file_exists_check(
263
+ file_path: str | Path,
264
+ must_be_file: bool = True,
265
+ must_be_readable: bool = False,
266
+ min_size: int | None = None,
267
+ ) -> bool:
268
+ """Unified file existence checking with optional validation.
269
+
270
+ This function centralizes all file existence checks to eliminate
271
+ the repeated patterns: if file.exists() or if not file.exists()
272
+
273
+ Args:
274
+ file_path: Path to check
275
+ must_be_file: Ensure path is a file, not directory (default: True)
276
+ must_be_readable: Ensure file is readable (default: False)
277
+ min_size: Minimum file size in bytes (default: None)
278
+
279
+ Returns:
280
+ True if file exists and meets all criteria, False otherwise
281
+ """
282
+ try:
283
+ file_path = Path(file_path)
284
+
285
+ # Basic existence check
286
+ if not file_path.exists():
287
+ return False
288
+
289
+ # Check if it's actually a file
290
+ if must_be_file and not file_path.is_file():
291
+ return False
292
+
293
+ # Check if file is readable
294
+ if must_be_readable and not os.access(file_path, os.R_OK):
295
+ return False
296
+
297
+ # Check minimum size
298
+ if min_size is not None:
299
+ if file_path.stat().st_size < min_size:
300
+ return False
301
+
302
+ return True
303
+
304
+ except Exception as e:
305
+ file_logger.warning(
306
+ "file_existence_check_failed",
307
+ file_path=str(file_path),
308
+ error=str(e),
309
+ severity="operational",
310
+ )
311
+ return False
312
+
313
+
314
+ def get_file_size(file_path: str | Path) -> int:
315
+ """Get file size with error handling.
316
+
317
+ Args:
318
+ file_path: Path to the file
319
+
320
+ Returns:
321
+ File size in bytes
322
+
323
+ Raises:
324
+ FileOperationError: If unable to get file size
325
+ """
326
+ try:
327
+ file_path = Path(file_path)
328
+ if not file_path.exists():
329
+ raise FileNotFoundError(f"File not found: {file_path}")
330
+
331
+ size = file_path.stat().st_size
332
+ file_logger.debug(
333
+ "file_size_retrieved",
334
+ file_path=str(file_path),
335
+ size_bytes=size,
336
+ )
337
+ return size
338
+
339
+ except Exception as e:
340
+ raise FileOperationError(
341
+ f"Failed to get file size: {e}", Path(file_path), "stat"
342
+ ) from e
343
+
344
+
345
+ def backup_file(
346
+ file_path: str | Path,
347
+ backup_suffix: str = ".backup",
348
+ backup_dir: str | Path | None = None,
349
+ ) -> Path | None:
350
+ """Create backup of existing file before modification.
351
+
352
+ Args:
353
+ file_path: Path to the file to backup
354
+ backup_suffix: Suffix to add to backup file (default: .backup)
355
+ backup_dir: Directory to store backups (default: same as original)
356
+
357
+ Returns:
358
+ Path to backup file if created, None if original doesn't exist
359
+
360
+ Raises:
361
+ FileOperationError: If backup creation fails
362
+ """
363
+ try:
364
+ file_path = Path(file_path)
365
+
366
+ if not file_path.exists():
367
+ return None
368
+
369
+ if backup_dir:
370
+ backup_dir = Path(backup_dir)
371
+ ensure_directory_exists(backup_dir)
372
+ backup_path = backup_dir / f"{file_path.name}{backup_suffix}"
373
+ else:
374
+ backup_path = file_path.with_suffix(f"{file_path.suffix}{backup_suffix}")
375
+
376
+ shutil.copy2(file_path, backup_path)
377
+ file_logger.info(
378
+ "file_backed_up",
379
+ file_path=str(file_path),
380
+ backup_path=str(backup_path),
381
+ )
382
+ return backup_path
383
+
384
+ except Exception as e:
385
+ raise FileOperationError(
386
+ f"Failed to create backup: {e}", Path(file_path), "backup"
387
+ ) from e
388
+
389
+
390
+ def cleanup_temp_files(directory: str | Path, pattern: str = "*.tmp") -> int:
391
+ """Clean up temporary files in a directory.
392
+
393
+ Args:
394
+ directory: Directory to clean
395
+ pattern: File pattern to match (default: *.tmp)
396
+
397
+ Returns:
398
+ Number of files cleaned up
399
+
400
+ Raises:
401
+ FileOperationError: If cleanup fails
402
+ """
403
+ try:
404
+ directory = Path(directory)
405
+ count = 0
406
+
407
+ if not directory.exists():
408
+ return 0
409
+
410
+ for temp_file in directory.glob(pattern):
411
+ try:
412
+ temp_file.unlink()
413
+ count += 1
414
+ file_logger.debug(
415
+ "temp_file_cleaned_up",
416
+ temp_file=str(temp_file),
417
+ )
418
+ except OSError as e:
419
+ file_logger.warning(
420
+ "failed_to_remove_temp_file",
421
+ temp_file=str(temp_file),
422
+ error=str(e),
423
+ severity="operational",
424
+ )
425
+
426
+ if count > 0:
427
+ file_logger.info(
428
+ "temporary_files_cleaned_up",
429
+ count=count,
430
+ directory=str(directory),
431
+ )
432
+
433
+ return count
434
+
435
+ except Exception as e:
436
+ raise FileOperationError(
437
+ f"Failed to cleanup temp files: {e}",
438
+ Path(directory) if isinstance(directory, str) else directory,
439
+ "cleanup",
440
+ ) from e
@@ -0,0 +1,38 @@
1
+ """Shared utilities for roadmap directory path construction."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+
7
+ def build_roadmap_paths(
8
+ root_path: Path, roadmap_dir_name: str = ".roadmap"
9
+ ) -> dict[str, Any]:
10
+ """Build all standard roadmap directory and file paths.
11
+
12
+ Consolidates the common pattern of constructing roadmap paths
13
+ used across the application (core, initialization, etc).
14
+
15
+ Args:
16
+ root_path: Root directory of the project
17
+ roadmap_dir_name: Name of the roadmap directory (default: .roadmap)
18
+
19
+ Returns:
20
+ Dictionary containing all standard roadmap paths
21
+ """
22
+ roadmap_dir = root_path / roadmap_dir_name
23
+
24
+ return {
25
+ "root_path": root_path,
26
+ "roadmap_dir_name": roadmap_dir_name,
27
+ "roadmap_dir": roadmap_dir,
28
+ "issues_dir": roadmap_dir / "issues",
29
+ "milestones_dir": roadmap_dir / "milestones",
30
+ "projects_dir": roadmap_dir / "projects",
31
+ "templates_dir": roadmap_dir / "templates",
32
+ "artifacts_dir": roadmap_dir / "artifacts",
33
+ "config_file": roadmap_dir / "config.yaml",
34
+ "db_dir": roadmap_dir / "db",
35
+ }
36
+
37
+
38
+ __all__ = ["build_roadmap_paths"]
@@ -0,0 +1,130 @@
1
+ """Utilities for status summary calculations.
2
+
3
+ Consolidates repeated patterns for aggregating status counts from
4
+ health checks and validation results.
5
+
6
+ Eliminates duplicate status counting logic from:
7
+ - HealthCheckService.get_health_summary()
8
+ - Infrastructure validators
9
+ - Data integrity validators
10
+ - Other status aggregation code
11
+ """
12
+
13
+ from collections import Counter
14
+ from collections.abc import Mapping, Sequence
15
+ from enum import Enum
16
+
17
+
18
+ class StatusSummary:
19
+ """Utility for computing status summaries from check results.
20
+
21
+ Provides consistent methods for:
22
+ - Counting items by status enum value
23
+ - Aggregating health check results
24
+ - Creating summary dictionaries
25
+
26
+ Usage:
27
+
28
+ # Count items by status
29
+ counts = StatusSummary.count_by_status([
30
+ ("check1", HealthStatus.HEALTHY),
31
+ ("check2", HealthStatus.DEGRADED),
32
+ ("check3", HealthStatus.HEALTHY),
33
+ ])
34
+ # Returns: {"healthy": 2, "degraded": 1}
35
+
36
+ # Summarize health checks dict
37
+ summary = StatusSummary.summarize_checks({
38
+ "roadmap_dir": (HealthStatus.HEALTHY, "OK"),
39
+ "database": (HealthStatus.DEGRADED, "Lag detected"),
40
+ "git": (HealthStatus.UNHEALTHY, "Not a repo"),
41
+ })
42
+ # Returns: {
43
+ # "total": 3,
44
+ # "healthy": 1,
45
+ # "degraded": 1,
46
+ # "unhealthy": 1,
47
+ # }
48
+ """
49
+
50
+ @staticmethod
51
+ def count_by_status(items: Sequence[tuple[str, Enum]]) -> dict[str, int]:
52
+ """Count items by status enum value.
53
+
54
+ Args:
55
+ items: List of (label, status_enum) tuples
56
+
57
+ Returns:
58
+ Dict mapping status values to counts
59
+
60
+ Example:
61
+ items = [
62
+ ("check1", HealthStatus.HEALTHY),
63
+ ("check2", HealthStatus.HEALTHY),
64
+ ("check3", HealthStatus.DEGRADED),
65
+ ]
66
+ counts = StatusSummary.count_by_status(items)
67
+ # Returns: {"healthy": 2, "degraded": 1}
68
+ """
69
+ counter = Counter(status.value for _, status in items)
70
+ return dict(counter)
71
+
72
+ @staticmethod
73
+ def summarize_checks(
74
+ checks: Mapping[str, tuple[Enum, str]], status_enum_class=None
75
+ ) -> dict[str, int]:
76
+ """Get summary counts from checks dict with generic status enum.
77
+
78
+ Aggregates check results into overall counts by status, without
79
+ hardcoding specific status values. The method is generic and works
80
+ with any status enum.
81
+
82
+ Args:
83
+ checks: Dict of {check_name: (status_enum, message)}
84
+ status_enum_class: Optional status enum class for type validation.
85
+ If provided, all unique status values will be counted.
86
+
87
+ Returns:
88
+ Dict with counts: {
89
+ "total": count,
90
+ "status1": count,
91
+ "status2": count,
92
+ ...
93
+ }
94
+
95
+ Example:
96
+ from roadmap.core.domain.health import HealthStatus
97
+
98
+ checks = {
99
+ "roadmap_dir": (HealthStatus.HEALTHY, "OK"),
100
+ "database": (HealthStatus.DEGRADED, "Lag"),
101
+ "git": (HealthStatus.UNHEALTHY, "Not a repo"),
102
+ }
103
+ summary = StatusSummary.summarize_checks(checks, HealthStatus)
104
+ # Returns: {
105
+ # "total": 3,
106
+ # "healthy": 1,
107
+ # "degraded": 1,
108
+ # "unhealthy": 1,
109
+ # }
110
+ """
111
+ statuses = [status for status, _ in checks.values()]
112
+
113
+ total = len(statuses)
114
+ counts = {"total": total}
115
+
116
+ # Count by status value
117
+ counter = Counter(s.value for s in statuses)
118
+ counts.update(dict(counter))
119
+
120
+ # If no explicit enum class, try to infer from first status
121
+ if not status_enum_class and statuses:
122
+ status_enum_class = type(statuses[0])
123
+
124
+ # Ensure all enum values are represented (with 0 count if missing)
125
+ if status_enum_class:
126
+ for member in status_enum_class:
127
+ if member.value not in counts:
128
+ counts[member.value] = 0
129
+
130
+ return counts