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,483 @@
1
+ """Cleanup command - comprehensive roadmap maintenance and optimization."""
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.presentation.cleanup_presenter import CleanupPresenter
8
+ from roadmap.common.console import get_console
9
+ from roadmap.common.logging import verbose_output
10
+ from roadmap.common.models import CleanupParams
11
+ from roadmap.core.services.health.backup_cleanup_service import BackupCleanupService
12
+ from roadmap.core.services.health.file_repair_service import FileRepairService
13
+ from roadmap.core.services.validators import (
14
+ DataIntegrityValidator,
15
+ DuplicateIssuesValidator,
16
+ FolderStructureValidator,
17
+ )
18
+
19
+ console = get_console()
20
+
21
+
22
+ @click.command()
23
+ @click.option(
24
+ "--keep",
25
+ type=int,
26
+ default=10,
27
+ help="Number of most recent backups to keep per issue (default: 10)",
28
+ )
29
+ @click.option(
30
+ "--days",
31
+ type=int,
32
+ help="Delete backups older than this many days (default: no age-based limit)",
33
+ )
34
+ @click.option(
35
+ "--dry-run",
36
+ is_flag=True,
37
+ help="Preview all cleanup actions without making changes",
38
+ )
39
+ @click.option(
40
+ "--force",
41
+ is_flag=True,
42
+ help="Skip confirmation prompts",
43
+ )
44
+ @click.option(
45
+ "--backups-only",
46
+ is_flag=True,
47
+ help="Only clean up old backup files (skip folder and duplicate fixes)",
48
+ )
49
+ @click.option(
50
+ "--check-folders",
51
+ is_flag=True,
52
+ help="Only check/report folder structure issues",
53
+ )
54
+ @click.option(
55
+ "--check-duplicates",
56
+ is_flag=True,
57
+ help="Only check/report duplicate issues",
58
+ )
59
+ @click.option(
60
+ "--check-malformed",
61
+ is_flag=True,
62
+ help="Only check/report malformed YAML files",
63
+ )
64
+ @click.option(
65
+ "--verbose",
66
+ "-v",
67
+ is_flag=True,
68
+ help="Show detailed debug information",
69
+ )
70
+ @click.pass_context
71
+ @verbose_output
72
+ def cleanup(
73
+ ctx: click.Context,
74
+ keep: int,
75
+ days: int | None,
76
+ dry_run: bool,
77
+ force: bool,
78
+ backups_only: bool,
79
+ check_folders: bool,
80
+ check_duplicates: bool,
81
+ check_malformed: bool,
82
+ verbose: bool,
83
+ ):
84
+ """Comprehensive roadmap cleanup - fix backups, folders, duplicates, and malformed files.
85
+
86
+ By default, performs all cleanup operations:
87
+ 1. Removes old backup files (keeps 10 most recent per issue)
88
+ 2. Moves misplaced issues to correct milestone folders
89
+ 3. Resolves duplicate issues (keeps latest version)
90
+ 4. Fixes malformed YAML in issue files
91
+
92
+ Use --dry-run to preview all changes without making them.
93
+ Use --force to skip confirmation prompts.
94
+
95
+ Use individual options to target specific cleanup tasks:
96
+ - --backups-only: Only clean up old backups
97
+ - --check-folders: Only report folder structure issues
98
+ - --check-duplicates: Only report duplicate issues
99
+ - --check-malformed: Only report and fix malformed files
100
+
101
+ Examples:
102
+ roadmap cleanup # Fix everything interactively
103
+ roadmap cleanup --dry-run # Preview all changes
104
+ roadmap cleanup --force # Fix everything without prompting
105
+ roadmap cleanup --backups-only # Only clean up backups
106
+ roadmap cleanup --check-folders # Only check folder issues
107
+ roadmap cleanup --check-malformed # Only fix malformed YAML
108
+ """
109
+ core = ctx.obj["core"]
110
+ presenter = CleanupPresenter()
111
+
112
+ # Create structured parameter object
113
+ params = CleanupParams(
114
+ keep=keep,
115
+ days=days,
116
+ dry_run=dry_run,
117
+ force=force,
118
+ backups_only=backups_only,
119
+ check_folders=check_folders,
120
+ check_duplicates=check_duplicates,
121
+ check_malformed=check_malformed,
122
+ verbose=verbose,
123
+ )
124
+
125
+ if not core.is_initialized():
126
+ presenter.present_no_roadmap()
127
+ ctx.exit(1)
128
+
129
+ roadmap_dir = Path.cwd() / ".roadmap"
130
+ issues_dir = roadmap_dir / "issues"
131
+
132
+ # Handle specific flag modes
133
+ if params.check_folders:
134
+ _handle_check_folders(issues_dir, roadmap_dir, core, presenter)
135
+ return
136
+
137
+ if params.check_duplicates:
138
+ _handle_check_duplicates(issues_dir, roadmap_dir, presenter)
139
+ return
140
+
141
+ if params.check_malformed:
142
+ _handle_check_malformed(
143
+ issues_dir, roadmap_dir, params.dry_run, params.force, presenter
144
+ )
145
+ return
146
+
147
+ # Default or backups-only mode: run comprehensive cleanup
148
+ try:
149
+ # 1. Clean up backups
150
+ _run_backup_cleanup(
151
+ roadmap_dir,
152
+ params.keep,
153
+ params.days,
154
+ params.dry_run,
155
+ params.force,
156
+ presenter,
157
+ )
158
+
159
+ # 2. Move misplaced issues and duplicates (skip if backups-only)
160
+ if not params.backups_only:
161
+ if not params.dry_run:
162
+ _resolve_folder_issues(issues_dir, roadmap_dir, core, force, presenter)
163
+ _resolve_duplicates(issues_dir, roadmap_dir, force, presenter)
164
+
165
+ # 3. Fix malformed YAML files
166
+ _fix_malformed_files(issues_dir, presenter)
167
+ else:
168
+ presenter.present_dry_run_skipped()
169
+
170
+ except Exception as e:
171
+ presenter.present_cleanup_error(str(e))
172
+ ctx.exit(1)
173
+
174
+
175
+ def _handle_check_folders(
176
+ issues_dir: Path, roadmap_dir: Path, core: object, presenter: CleanupPresenter
177
+ ) -> None:
178
+ """Check and report folder structure issues."""
179
+ if not issues_dir.exists():
180
+ presenter.present_no_issues_dir()
181
+ return
182
+
183
+ issues = FolderStructureValidator.scan_for_folder_structure_issues(issues_dir, core)
184
+
185
+ if not issues:
186
+ presenter.present_folder_check_clean()
187
+ return
188
+
189
+ presenter.present_folder_issues(issues)
190
+
191
+
192
+ def _handle_check_duplicates(
193
+ issues_dir: Path, roadmap_dir: Path, presenter: CleanupPresenter
194
+ ) -> None:
195
+ """Check and report duplicate issues."""
196
+ if not issues_dir.exists():
197
+ presenter.present_no_issues_dir()
198
+ return
199
+
200
+ duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
201
+
202
+ if not duplicates:
203
+ presenter.present_duplicates_check_clean()
204
+ return
205
+
206
+ presenter.present_duplicate_issues(duplicates, roadmap_dir)
207
+
208
+
209
+ def _handle_check_malformed(
210
+ issues_dir: Path,
211
+ roadmap_dir: Path,
212
+ dry_run: bool,
213
+ force: bool,
214
+ presenter: CleanupPresenter,
215
+ ) -> None:
216
+ """Check and fix malformed YAML files."""
217
+ if not issues_dir.exists():
218
+ presenter.present_no_issues_dir()
219
+ return
220
+
221
+ malformed = DataIntegrityValidator.scan_for_data_integrity_issues(issues_dir)
222
+
223
+ if not malformed["malformed_files"]:
224
+ presenter.present_malformed_check_clean()
225
+ return
226
+
227
+ presenter.present_malformed_files(malformed["malformed_files"])
228
+
229
+ if dry_run:
230
+ console.print("[DRY RUN] Would fix the above files", style="dim")
231
+ return
232
+
233
+ if not force:
234
+ if not click.confirm("Fix malformed files?", default=True):
235
+ console.print("Skipped.", style="yellow")
236
+ return
237
+
238
+ repair_service = FileRepairService()
239
+ result = repair_service.repair_files(
240
+ issues_dir, malformed["malformed_files"], dry_run=False
241
+ )
242
+ presenter.present_malformed_repair_result(result)
243
+
244
+
245
+ def _run_backup_cleanup(
246
+ roadmap_dir: Path,
247
+ keep: int,
248
+ days: int | None,
249
+ dry_run: bool,
250
+ force: bool,
251
+ presenter: CleanupPresenter,
252
+ ) -> None:
253
+ """Run backup cleanup using service."""
254
+ backup_service = BackupCleanupService()
255
+
256
+ # Get files that would be deleted
257
+ backups_dir = roadmap_dir / "backups"
258
+ if not backups_dir.exists():
259
+ presenter.present_no_backups()
260
+ return
261
+
262
+ files_to_delete = backup_service._select_backups_for_deletion(
263
+ backups_dir, keep, days
264
+ )
265
+
266
+ if not files_to_delete:
267
+ presenter.present_no_cleanup_needed()
268
+ return
269
+
270
+ # Dry run: show what would be deleted
271
+ if dry_run:
272
+ presenter.present_backup_cleanup_dry_run(list(files_to_delete)) # type: ignore
273
+ return
274
+
275
+ # Confirm deletion
276
+ total_size = sum(f["size"] for f in files_to_delete)
277
+ size_mb = total_size / (1024 * 1024)
278
+
279
+ if not force:
280
+ console.print(
281
+ f"\n⚠️ About to delete {len(files_to_delete)} backup file(s) ({size_mb:.2f} MB):",
282
+ style="bold yellow",
283
+ )
284
+ console.print(
285
+ f" This will free approximately {size_mb:.2f} MB of disk space.",
286
+ style="yellow",
287
+ )
288
+
289
+ if not click.confirm("\nProceed with cleanup?", default=False):
290
+ console.print("❌ Cancelled.", style="yellow")
291
+ return
292
+
293
+ # Perform cleanup
294
+ result = backup_service.cleanup_backups(roadmap_dir, keep, days, dry_run=False)
295
+ presenter.present_backup_cleanup_result(result)
296
+
297
+
298
+ def _build_move_list(issues_dir: Path, issues: dict) -> list:
299
+ """Build list of moves to make from issue structure issues.
300
+
301
+ Args:
302
+ issues_dir: Path to issues directory
303
+ issues: Dictionary of misplaced and orphaned issues
304
+
305
+ Returns:
306
+ List of move operations
307
+ """
308
+ moves_to_make = []
309
+
310
+ if issues.get("misplaced"):
311
+ for item in issues["misplaced"]:
312
+ moves_to_make.append(
313
+ {
314
+ "from": Path(item["current_location"]),
315
+ "to": Path(item["expected_location"]),
316
+ "issue_id": item["issue_id"],
317
+ }
318
+ )
319
+
320
+ if issues.get("orphaned"):
321
+ for item in issues["orphaned"]:
322
+ backlog_dest = issues_dir / "backlog" / Path(item["location"]).name
323
+ moves_to_make.append(
324
+ {
325
+ "from": Path(item["location"]),
326
+ "to": backlog_dest,
327
+ "issue_id": item["issue_id"],
328
+ }
329
+ )
330
+
331
+ return moves_to_make
332
+
333
+
334
+ def _confirm_folder_moves(force: bool) -> bool:
335
+ """Confirm folder moves with user.
336
+
337
+ Args:
338
+ force: Skip confirmation if True
339
+
340
+ Returns:
341
+ True if user confirmed or force is True
342
+ """
343
+ if force:
344
+ return True
345
+
346
+ console.print(
347
+ "\n⚠️ About to move files:",
348
+ style="bold yellow",
349
+ )
350
+ return click.confirm("\nProceed with moves?", default=False)
351
+
352
+
353
+ def _perform_folder_moves(moves_to_make: list) -> tuple[int, int]:
354
+ """Perform folder move operations.
355
+
356
+ Args:
357
+ moves_to_make: List of move operations to perform
358
+
359
+ Returns:
360
+ Tuple of (moved_count, failed_count)
361
+ """
362
+ moved_count = 0
363
+ failed_count = 0
364
+
365
+ for move in moves_to_make:
366
+ try:
367
+ move["to"].parent.mkdir(parents=True, exist_ok=True)
368
+ move["from"].rename(move["to"])
369
+ moved_count += 1
370
+ except Exception as e:
371
+ console.print(
372
+ f"⚠️ Failed to move {move['from'].name}: {e}",
373
+ style="yellow",
374
+ )
375
+ failed_count += 1
376
+
377
+ return moved_count, failed_count
378
+
379
+
380
+ def _resolve_folder_issues(
381
+ issues_dir: Path,
382
+ roadmap_dir: Path,
383
+ core: object,
384
+ force: bool,
385
+ presenter: CleanupPresenter,
386
+ ) -> None:
387
+ """Move misplaced and orphaned issues to correct folders."""
388
+ if not issues_dir.exists():
389
+ return
390
+
391
+ issues = FolderStructureValidator.scan_for_folder_structure_issues(issues_dir, core)
392
+
393
+ if not issues:
394
+ return
395
+
396
+ misplaced_count = len(issues.get("misplaced", []))
397
+ orphaned_count = len(issues.get("orphaned", []))
398
+ total = misplaced_count + orphaned_count
399
+
400
+ if total == 0:
401
+ return
402
+
403
+ moves_to_make = _build_move_list(issues_dir, issues)
404
+
405
+ if not moves_to_make:
406
+ return
407
+
408
+ presenter.present_folder_moves(moves_to_make)
409
+
410
+ # Confirm moves
411
+ if not _confirm_folder_moves(force):
412
+ console.print("❌ Cancelled.", style="yellow")
413
+ return
414
+
415
+ # Perform moves
416
+ moved_count, failed_count = _perform_folder_moves(moves_to_make)
417
+ presenter.present_folder_moves_result(moved_count, failed_count)
418
+
419
+
420
+ def _resolve_duplicates(
421
+ issues_dir: Path, roadmap_dir: Path, force: bool, presenter: CleanupPresenter
422
+ ) -> None:
423
+ """Resolve duplicate issues by keeping the latest version."""
424
+ if not issues_dir.exists():
425
+ return
426
+
427
+ duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
428
+
429
+ if not duplicates:
430
+ return
431
+
432
+ presenter.present_duplicate_resolution(duplicates, roadmap_dir)
433
+
434
+ files_to_delete = []
435
+ # Orchestrate deletion: keep newest, remove others
436
+ for _issue_id, files in sorted(duplicates.items()):
437
+ sorted_files = sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
438
+ to_remove = sorted_files[1:]
439
+ files_to_delete.extend(to_remove)
440
+
441
+ if not files_to_delete:
442
+ return
443
+
444
+ # Confirm deletion
445
+ if not force:
446
+ console.print(
447
+ f"\n⚠️ About to delete {len(files_to_delete)} duplicate file(s):",
448
+ style="bold yellow",
449
+ )
450
+
451
+ if not click.confirm("\nProceed with deletion?", default=False):
452
+ console.print("❌ Cancelled.", style="yellow")
453
+ return
454
+
455
+ # Perform deletion (application layer only)
456
+ deleted_count = 0
457
+ failed_count = 0
458
+ for f in files_to_delete:
459
+ try:
460
+ f.unlink()
461
+ deleted_count += 1
462
+ except Exception as e:
463
+ console.print(
464
+ f"⚠️ Failed to delete {f.name}: {e}",
465
+ style="yellow",
466
+ )
467
+ failed_count += 1
468
+
469
+ presenter.present_duplicate_resolution_result(deleted_count, failed_count)
470
+
471
+
472
+ def _fix_malformed_files(issues_dir: Path, presenter: CleanupPresenter) -> None:
473
+ """Fix malformed YAML files in issues directory."""
474
+ malformed = DataIntegrityValidator.scan_for_data_integrity_issues(issues_dir)
475
+
476
+ if not malformed["malformed_files"]:
477
+ return
478
+
479
+ repair_service = FileRepairService()
480
+ result = repair_service.repair_files(
481
+ issues_dir, malformed["malformed_files"], dry_run=False
482
+ )
483
+ presenter.present_malformed_repair_result(result)
@@ -0,0 +1,7 @@
1
+ """Observability layer - Health checks and monitoring.
2
+
3
+ This module provides:
4
+ - Health checks: Application and infrastructure health monitoring
5
+ - Specialized health checkers: Directory, data, and entity health checks
6
+ - Health formatters: Output formatting for health check results
7
+ """
@@ -0,0 +1,196 @@
1
+ """Health checks for monitoring application and infrastructure status.
2
+
3
+ This module orchestrates health checks by delegating to specialized checkers:
4
+ - DirectoryHealthChecker: File system structure checks
5
+ - DataHealthChecker: Database and data integrity checks
6
+ - EntityHealthChecker: Business entity validation checks
7
+
8
+ These replace the original monolithic HealthCheck class to improve separation of concerns.
9
+ """
10
+
11
+ from collections.abc import Mapping
12
+
13
+ from roadmap.common.logging import get_logger
14
+ from roadmap.core.domain.health import HealthStatus
15
+ from roadmap.infrastructure.observability.specialized_health_checkers import (
16
+ DataHealthChecker,
17
+ DirectoryHealthChecker,
18
+ EntityHealthChecker,
19
+ )
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class HealthCheck:
25
+ """Application health checks orchestrator.
26
+
27
+ Delegates to specialized health checkers organized by concern:
28
+ - DirectoryHealthChecker: File system and directory checks
29
+ - DataHealthChecker: Database and data integrity checks
30
+ - EntityHealthChecker: Business entity validation checks
31
+
32
+ This thin facade coordinates checks across multiple domain areas
33
+ while keeping each area's logic properly separated.
34
+ """
35
+
36
+ def __init__(self):
37
+ """Initialize HealthCheck with specialized checkers."""
38
+ self.directory_checker = DirectoryHealthChecker()
39
+ self.data_checker = DataHealthChecker()
40
+ self.entity_checker = EntityHealthChecker()
41
+
42
+ # Delegate to DirectoryHealthChecker
43
+ @staticmethod
44
+ def check_roadmap_directory() -> tuple[HealthStatus, str]:
45
+ """Check if .roadmap directory exists and is accessible."""
46
+ return DirectoryHealthChecker.check_roadmap_directory()
47
+
48
+ @staticmethod
49
+ def check_state_file() -> tuple[HealthStatus, str]:
50
+ """Check if state database exists and is readable."""
51
+ return DirectoryHealthChecker.check_state_file()
52
+
53
+ @staticmethod
54
+ def check_issues_directory() -> tuple[HealthStatus, str]:
55
+ """Check if issues directory exists and is accessible."""
56
+ return DirectoryHealthChecker.check_issues_directory()
57
+
58
+ @staticmethod
59
+ def check_milestones_directory() -> tuple[HealthStatus, str]:
60
+ """Check if milestones directory exists and is accessible."""
61
+ return DirectoryHealthChecker.check_milestones_directory()
62
+
63
+ @staticmethod
64
+ def check_folder_structure(core) -> tuple[HealthStatus, str]:
65
+ """Check if issues are in correct milestone folders."""
66
+ return DirectoryHealthChecker.check_folder_structure(core)
67
+
68
+ @staticmethod
69
+ def check_old_backups() -> tuple[HealthStatus, str]:
70
+ """Check for old backup files that could be cleaned up."""
71
+ return DirectoryHealthChecker.check_old_backups()
72
+
73
+ # Delegate to DataHealthChecker
74
+ @staticmethod
75
+ def check_git_repository() -> tuple[HealthStatus, str]:
76
+ """Check if Git repository exists and is accessible."""
77
+ return DataHealthChecker.check_git_repository()
78
+
79
+ @staticmethod
80
+ def check_database_integrity() -> tuple[HealthStatus, str]:
81
+ """Check SQLite database integrity."""
82
+ return DataHealthChecker.check_database_integrity()
83
+
84
+ @staticmethod
85
+ def check_data_integrity() -> tuple[HealthStatus, str]:
86
+ """Run comprehensive data integrity checks."""
87
+ return DataHealthChecker.check_data_integrity()
88
+
89
+ # Delegate to EntityHealthChecker
90
+ @staticmethod
91
+ def check_duplicate_issues(core) -> tuple[HealthStatus, str]:
92
+ """Check for duplicate issues."""
93
+ return EntityHealthChecker.check_duplicate_issues(core)
94
+
95
+ @staticmethod
96
+ def check_archivable_issues(core) -> tuple[HealthStatus, str]:
97
+ """Check for closed issues that could be archived."""
98
+ return EntityHealthChecker.check_archivable_issues(core)
99
+
100
+ @staticmethod
101
+ def check_archivable_milestones(core) -> tuple[HealthStatus, str]:
102
+ """Check for closed milestones that could be archived."""
103
+ return EntityHealthChecker.check_archivable_milestones(core)
104
+
105
+ @staticmethod
106
+ def check_orphaned_issues(core) -> tuple[HealthStatus, str]:
107
+ """Check for issues not assigned to any milestone."""
108
+ return EntityHealthChecker.check_orphaned_issues(core)
109
+
110
+ @staticmethod
111
+ def check_orphaned_milestones(core) -> tuple[HealthStatus, str]:
112
+ """Check for empty milestones with no issues."""
113
+ return EntityHealthChecker.check_orphaned_milestones(core)
114
+
115
+ @staticmethod
116
+ def check_duplicate_milestones(core) -> tuple[HealthStatus, str]:
117
+ """Check for duplicate milestone names."""
118
+ return EntityHealthChecker.check_duplicate_milestones(core)
119
+
120
+ @staticmethod
121
+ def check_unlinked_issues(core) -> tuple[HealthStatus, str]:
122
+ """Check for remote issues not linked to local issues."""
123
+ return EntityHealthChecker.check_unlinked_issues(core)
124
+
125
+ @staticmethod
126
+ def run_all_checks(core) -> dict[str, tuple[HealthStatus, str]]:
127
+ """Run all health checks.
128
+
129
+ Args:
130
+ core: RoadmapCore instance for entity checks
131
+
132
+ Returns:
133
+ Dictionary mapping check names to (status, message) tuples
134
+ """
135
+ checks = {}
136
+
137
+ # Directory checks
138
+ checks["roadmap_directory"] = HealthCheck.check_roadmap_directory()
139
+ checks["state_file"] = HealthCheck.check_state_file()
140
+ checks["issues_directory"] = HealthCheck.check_issues_directory()
141
+ checks["milestones_directory"] = HealthCheck.check_milestones_directory()
142
+
143
+ # Data checks
144
+ checks["git_repository"] = HealthCheck.check_git_repository()
145
+ checks["database_integrity"] = HealthCheck.check_database_integrity()
146
+ checks["data_integrity"] = HealthCheck.check_data_integrity()
147
+
148
+ # Entity checks (require core)
149
+ if core:
150
+ checks["folder_structure"] = HealthCheck.check_folder_structure(core)
151
+ checks["duplicate_issues"] = HealthCheck.check_duplicate_issues(core)
152
+ checks["archivable_issues"] = HealthCheck.check_archivable_issues(core)
153
+ checks["archivable_milestones"] = HealthCheck.check_archivable_milestones(
154
+ core
155
+ )
156
+ checks["orphaned_issues"] = HealthCheck.check_orphaned_issues(core)
157
+ checks["orphaned_milestones"] = HealthCheck.check_orphaned_milestones(core)
158
+ checks["duplicate_milestones"] = HealthCheck.check_duplicate_milestones(
159
+ core
160
+ )
161
+ checks["unlinked_issues"] = HealthCheck.check_unlinked_issues(core)
162
+
163
+ checks["old_backups"] = HealthCheck.check_old_backups()
164
+
165
+ return checks
166
+
167
+ @staticmethod
168
+ def get_overall_status(
169
+ checks: Mapping[str, tuple[HealthStatus, str]],
170
+ ) -> HealthStatus:
171
+ """Get overall health status from check results.
172
+
173
+ Args:
174
+ checks: Dictionary of check results
175
+
176
+ Returns:
177
+ Overall HealthStatus (UNHEALTHY if any unhealthy, DEGRADED if any degraded, else HEALTHY)
178
+ """
179
+ if not checks:
180
+ return HealthStatus.HEALTHY
181
+
182
+ has_unhealthy = False
183
+ has_degraded = False
184
+
185
+ for status, _message in checks.values():
186
+ if status == HealthStatus.UNHEALTHY:
187
+ has_unhealthy = True
188
+ elif status == HealthStatus.DEGRADED:
189
+ has_degraded = True
190
+
191
+ if has_unhealthy:
192
+ return HealthStatus.UNHEALTHY
193
+ elif has_degraded:
194
+ return HealthStatus.DEGRADED
195
+ else:
196
+ return HealthStatus.HEALTHY