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,319 @@
1
+ """Unified output management for CLI commands.
2
+
3
+ Centralizes all CLI output handling across:
4
+ - Multiple formats (table/json/csv/markdown)
5
+ - Terminal vs file output
6
+ - Test-friendly rendering (ANSI stripping)
7
+ - Format auto-detection
8
+
9
+ This replaces scattered console.print() and click.secho() calls with
10
+ a unified interface that supports exporting to files.
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ import click
18
+ from rich.console import Console
19
+
20
+ from roadmap.adapters.cli.layout import SmartTableLayout
21
+ from roadmap.common.models import TableData
22
+ from roadmap.common.output_formatter import OutputFormatter
23
+
24
+
25
+ class OutputManager:
26
+ """Unified output management for CLI commands.
27
+
28
+ Handles rendering of TableData in multiple formats:
29
+ - table: Pretty Rich table (default, terminal-friendly)
30
+ - plain: ASCII table (POSIX-compliant, no ANSI codes)
31
+ - json: Machine-readable JSON (API/data export)
32
+ - csv: RFC 4180 CSV (Excel/Sheets import)
33
+ - markdown: Markdown table (documentation)
34
+ - html: HTML table with styling (email/web)
35
+
36
+ Can output to terminal or file with automatic format detection.
37
+
38
+ Example:
39
+ # Terminal output
40
+ manager = OutputManager(format='table')
41
+ manager.render_table(table_data)
42
+
43
+ # File output
44
+ manager = OutputManager(
45
+ format='csv',
46
+ output_file=Path('/tmp/export.csv')
47
+ )
48
+ manager.render_table(table_data)
49
+
50
+ # In Click commands
51
+ @click.option('--format', type=click.Choice(['table', 'json', 'csv', 'html']))
52
+ @click.option('--output', type=click.Path())
53
+ def list_items(format, output):
54
+ manager = OutputManager(
55
+ format=format or 'table',
56
+ output_file=Path(output) if output else None
57
+ )
58
+ table_data = IssueTableFormatter.issues_to_table_data(items)
59
+ manager.render_table(table_data)
60
+ """
61
+
62
+ VALID_FORMATS = {"table", "plain", "json", "csv", "markdown", "html"}
63
+
64
+ def __init__(
65
+ self,
66
+ format: str = "table",
67
+ output_file: Path | None = None,
68
+ force_plain: bool = False,
69
+ ):
70
+ """Initialize OutputManager.
71
+
72
+ Args:
73
+ format: Output format (table/plain/json/csv/markdown/html)
74
+ output_file: Optional file path to save output
75
+ force_plain: Force plain text output (for testing)
76
+
77
+ Raises:
78
+ ValueError: If format is not valid
79
+ """
80
+ if format not in self.VALID_FORMATS:
81
+ raise ValueError(
82
+ f"Invalid format '{format}'. Must be one of: {', '.join(self.VALID_FORMATS)}"
83
+ )
84
+
85
+ self.format = format
86
+ self.output_file = output_file
87
+ self.force_plain = force_plain
88
+
89
+ # Determine color support
90
+ should_use_colors = self._should_use_colors()
91
+
92
+ # Initialize console for Rich output
93
+ # Use test-safe settings that work with Click's test runner
94
+ is_testing = self._is_testing_environment()
95
+ self.console = Console(
96
+ file=sys.stdout,
97
+ force_terminal=should_use_colors and not is_testing,
98
+ no_color=not should_use_colors or is_testing or force_plain,
99
+ width=80 if is_testing else 120,
100
+ )
101
+
102
+ # Initialize smart layout system for responsive table rendering
103
+ self.layout = SmartTableLayout()
104
+
105
+ @staticmethod
106
+ def _is_testing_environment() -> bool:
107
+ """Detect if running in a test environment."""
108
+ return any(
109
+ [
110
+ "PYTEST_CURRENT_TEST" in os.environ,
111
+ "pytest" in sys.modules,
112
+ "_pytest" in [m.split(".")[0] for m in sys.modules.keys()],
113
+ os.environ.get("NO_COLOR") in ("1", "true"),
114
+ ]
115
+ )
116
+
117
+ @staticmethod
118
+ def _should_use_colors() -> bool:
119
+ """Detect if terminal supports colors.
120
+
121
+ Checks:
122
+ 1. NO_COLOR env var (standard no-color support)
123
+ 2. FORCE_COLOR env var (force colors even in non-TTY)
124
+ 3. Terminal type (dumb, emacs, etc. don't support colors)
125
+ 4. isatty() - check if stdout is a terminal
126
+ 5. CI environment detection
127
+
128
+ Returns:
129
+ True if colors should be used, False otherwise
130
+ """
131
+ # Check NO_COLOR first (highest priority to disable)
132
+ if os.environ.get("NO_COLOR", "").lower() in ("1", "true", "yes"):
133
+ return False
134
+
135
+ # Check FORCE_COLOR (explicit enable)
136
+ if os.environ.get("FORCE_COLOR", "").lower() in ("1", "true", "yes"):
137
+ return True
138
+
139
+ # Check terminal type
140
+ term = os.environ.get("TERM", "").lower()
141
+ if term in ("dumb", "emacs", "milk"):
142
+ return False
143
+
144
+ # Check if stdout is a TTY
145
+ try:
146
+ if not sys.stdout.isatty():
147
+ return False
148
+ except (AttributeError, Exception):
149
+ # If isatty() fails, assume no colors
150
+ return False
151
+
152
+ # Check for CI environments (usually don't support interactive colors)
153
+ ci_env_vars = [
154
+ "CI",
155
+ "CONTINUOUS_INTEGRATION",
156
+ "GITHUB_ACTIONS",
157
+ "GITLAB_CI",
158
+ "TRAVIS",
159
+ "CIRCLECI",
160
+ "JENKINS_URL",
161
+ "BUILDKITE",
162
+ "DRONE",
163
+ ]
164
+ if any(var in os.environ for var in ci_env_vars):
165
+ # CI is set, but some CI systems do support colors
166
+ # Check for specific ones that support colors
167
+ if os.environ.get("GITHUB_ACTIONS") == "true":
168
+ return True # GitHub Actions supports colors
169
+ if os.environ.get("GITLAB_CI") == "true":
170
+ return True # GitLab CI supports colors
171
+ # Others might not, so be conservative
172
+ return False
173
+
174
+ # Default: use colors (most modern terminals support them)
175
+ return True
176
+
177
+ def render_table(self, table_data: TableData) -> None:
178
+ """Render a table in the configured format.
179
+
180
+ Handles both terminal output and file saves.
181
+ Automatically detects test environment and strips ANSI codes.
182
+ For 'table' format, uses smart responsive layout system.
183
+
184
+ Args:
185
+ table_data: TableData object to render
186
+ """
187
+ formatter = OutputFormatter(table_data)
188
+
189
+ if self.format == "table":
190
+ # Use smart responsive layout for 'table' format
191
+ renderable = self.layout.render(table_data)
192
+ self.console.print(renderable)
193
+
194
+ elif self.format == "plain":
195
+ # Plain ASCII for POSIX compatibility
196
+ content = formatter.to_plain_text()
197
+ self._output_content(content)
198
+
199
+ elif self.format == "json":
200
+ # JSON for data export
201
+ content = formatter.to_json()
202
+ self._output_content(content)
203
+
204
+ elif self.format == "csv":
205
+ # CSV for spreadsheet import
206
+ content = formatter.to_csv()
207
+ self._output_content(content)
208
+
209
+ elif self.format == "markdown":
210
+ # Markdown for documentation
211
+ content = formatter.to_markdown()
212
+ self._output_content(content)
213
+
214
+ elif self.format == "html":
215
+ # HTML for email/web
216
+ content = formatter.to_html()
217
+ self._output_content(content)
218
+
219
+ def render_rich(self, renderable) -> None:
220
+ """Render a Rich-compatible object.
221
+
222
+ Useful for rendering non-table objects like Panels, etc.
223
+
224
+ Args:
225
+ renderable: Any Rich-compatible renderable object
226
+ """
227
+ self.console.print(renderable)
228
+
229
+ def _output_content(self, content: str) -> None:
230
+ """Output content to file or stdout.
231
+
232
+ Args:
233
+ content: String content to output
234
+ """
235
+ if self.output_file:
236
+ self.output_file.parent.mkdir(parents=True, exist_ok=True)
237
+ self.output_file.write_text(content)
238
+ click.secho(f"✅ Saved to {self.output_file}", fg="green")
239
+ else:
240
+ click.echo(content)
241
+
242
+ def print_message(
243
+ self, message: str, style: str | None = None, fg: str | None = None
244
+ ) -> None:
245
+ """Print a simple message (not a table).
246
+
247
+ Useful for status messages, warnings, etc.
248
+
249
+ Args:
250
+ message: Message to print
251
+ style: Rich style (e.g., 'bold red')
252
+ fg: Click color (e.g., 'green')
253
+ """
254
+ if self.format in {"json", "csv", "markdown"}:
255
+ # Skip non-data messages in data export formats
256
+ return
257
+
258
+ if self.format == "table" or self.format == "plain":
259
+ if fg:
260
+ click.secho(message, fg=fg)
261
+ elif style:
262
+ self.console.print(message, style=style)
263
+ else:
264
+ click.echo(message)
265
+
266
+ def print_styled(
267
+ self,
268
+ text: str,
269
+ color: str | None = None,
270
+ bold: bool = False,
271
+ italic: bool = False,
272
+ ) -> None:
273
+ """Print styled text (terminal output only).
274
+
275
+ Args:
276
+ text: Text to print
277
+ color: Color name (red, green, yellow, etc.)
278
+ bold: Make bold
279
+ italic: Make italic
280
+ """
281
+ if self.format in {"json", "csv", "markdown"}:
282
+ return
283
+
284
+ if color or bold or italic:
285
+ click.secho(text, fg=color, bold=bold)
286
+ else:
287
+ click.echo(text)
288
+
289
+ def print_section_header(self, title: str) -> None:
290
+ """Print a section header.
291
+
292
+ Args:
293
+ title: Section title
294
+ """
295
+ if self.format in {"json", "csv", "markdown"}:
296
+ return
297
+
298
+ click.secho(f"\n{title}", fg="cyan", bold=True)
299
+
300
+
301
+ # Convenience function for quick output
302
+ def create_output_manager(
303
+ format: str | None = None, output_file: str | None = None
304
+ ) -> OutputManager:
305
+ """Create an OutputManager with optional CLI arguments.
306
+
307
+ Useful for Click commands with --format and --output options.
308
+
309
+ Args:
310
+ format: Output format or None (uses 'table' default)
311
+ output_file: Path to output file or None (uses stdout)
312
+
313
+ Returns:
314
+ Configured OutputManager instance
315
+ """
316
+ return OutputManager(
317
+ format=format or "table",
318
+ output_file=Path(output_file) if output_file else None,
319
+ )
@@ -0,0 +1,121 @@
1
+ """Base presenter class for all CLI presenters.
2
+
3
+ This module provides an abstract base class that defines the interface for all
4
+ presenters in the CLI layer. All presenters should inherit from BasePresenter
5
+ to ensure consistent method signatures and behavior.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+ from rich.console import Console
12
+
13
+ from roadmap.common.console import get_console
14
+
15
+
16
+ class BasePresenter(ABC):
17
+ """Abstract base class for all CLI presenters.
18
+
19
+ Defines the standard interface that all presenters must implement.
20
+ Presenters are responsible for formatting and rendering data to the console.
21
+
22
+ Example:
23
+ class MyPresenter(BasePresenter):
24
+ def render(self, data: dict) -> None:
25
+ console = get_console()
26
+ console.print(data["title"])
27
+ """
28
+
29
+ @abstractmethod
30
+ def render(self, data: Any) -> None:
31
+ """Render data to console.
32
+
33
+ This is the main entry point for presentation. Implementers should
34
+ format the provided data and output it using the console.
35
+
36
+ Args:
37
+ data: The data to render. Type depends on specific presenter.
38
+
39
+ Raises:
40
+ ValueError: If data is invalid or incomplete
41
+ """
42
+ pass
43
+
44
+ # ─────────────────────────────────────────────────────────────────────────
45
+ # Common Utility Methods (Optional for subclasses to use)
46
+ # ─────────────────────────────────────────────────────────────────────────
47
+
48
+ @staticmethod
49
+ def _get_console() -> Console:
50
+ """Get the standard console instance.
51
+
52
+ Returns:
53
+ The global console instance for output
54
+ """
55
+ return get_console()
56
+
57
+ @staticmethod
58
+ def _render_header(title: str, style: str = "bold cyan") -> None:
59
+ """Render a section header.
60
+
61
+ Args:
62
+ title: Header text
63
+ style: Rich style string (default: bold cyan)
64
+ """
65
+ console = get_console()
66
+ console.print(f"\n[{style}]{title}[/{style}]")
67
+
68
+ @staticmethod
69
+ def _render_section(title: str, content: str, style: str = "dim") -> None:
70
+ """Render a named section with indented content.
71
+
72
+ Args:
73
+ title: Section title
74
+ content: Section content
75
+ style: Rich style for title (default: dim)
76
+ """
77
+ console = get_console()
78
+ console.print(f"[{style}]{title}:[/{style}] {content}")
79
+
80
+ @staticmethod
81
+ def _render_footer(message: str | None = None) -> None:
82
+ """Render footer content.
83
+
84
+ Args:
85
+ message: Optional footer message
86
+ """
87
+ console = get_console()
88
+ if message:
89
+ console.print(f"\n[dim]{message}[/dim]")
90
+ else:
91
+ console.print()
92
+
93
+ @staticmethod
94
+ def _render_warning(message: str) -> None:
95
+ """Render a warning message.
96
+
97
+ Args:
98
+ message: Warning text
99
+ """
100
+ console = get_console()
101
+ console.print(f"[yellow]⚠️ {message}[/yellow]")
102
+
103
+ @staticmethod
104
+ def _render_error(message: str) -> None:
105
+ """Render an error message.
106
+
107
+ Args:
108
+ message: Error text
109
+ """
110
+ console = get_console()
111
+ console.print(f"[red]❌ {message}[/red]")
112
+
113
+ @staticmethod
114
+ def _render_success(message: str) -> None:
115
+ """Render a success message.
116
+
117
+ Args:
118
+ message: Success text
119
+ """
120
+ console = get_console()
121
+ console.print(f"[green]✅ {message}[/green]")
@@ -0,0 +1,235 @@
1
+ """Presenter for cleanup command output."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+ from roadmap.core.services.health.backup_cleanup_service import (
9
+ BackupCleanupResult as BackupResult,
10
+ )
11
+ from roadmap.core.services.health.file_repair_service import FileRepairResult
12
+
13
+
14
+ class CleanupPresenter:
15
+ """Handles all console output for cleanup operations."""
16
+
17
+ def __init__(self):
18
+ """Initialize presenter."""
19
+ pass
20
+
21
+ def present_no_roadmap(self) -> None:
22
+ """Show message that roadmap is not initialized."""
23
+ click.secho(
24
+ "❌ Roadmap not initialized. Run 'roadmap init' first.",
25
+ fg="red",
26
+ bold=True,
27
+ )
28
+
29
+ def present_no_issues_dir(self) -> None:
30
+ """Show message that issues directory not found."""
31
+ click.secho("📋 No issues directory found.", fg="yellow")
32
+
33
+ def present_folder_check_clean(self) -> None:
34
+ """Show message that all issues are in correct folders."""
35
+ click.secho(
36
+ "✅ All issues appear to be in correct folders.",
37
+ fg="green",
38
+ )
39
+
40
+ def present_folder_issues(self, issues: dict[str, Any]) -> None:
41
+ """Show folder structure issues found."""
42
+ if "misplaced" in issues and issues["misplaced"]:
43
+ click.secho(
44
+ f"\n⚠️ Found {len(issues['misplaced'])} issue(s) in wrong folders:\n",
45
+ fg="yellow",
46
+ bold=True,
47
+ )
48
+ for item in issues["misplaced"]:
49
+ click.secho(f" • {item['issue_id']}: {item['title']}", fg="cyan")
50
+ click.echo(f" Current: {item['current_location']}")
51
+ click.echo(f" Expected: {item['expected_location']}")
52
+ click.echo()
53
+
54
+ if "orphaned" in issues and issues["orphaned"]:
55
+ click.secho(
56
+ f"\n⚠️ Found {len(issues['orphaned'])} orphaned issue(s):\n",
57
+ fg="yellow",
58
+ bold=True,
59
+ )
60
+ for item in issues["orphaned"]:
61
+ click.secho(f" • {item['issue_id']}: {item['title']}", fg="cyan")
62
+ click.echo(f" Location: {item['location']}")
63
+ click.echo()
64
+
65
+ def present_duplicates_check_clean(self) -> None:
66
+ """Show message that no duplicates found."""
67
+ click.secho(
68
+ "✅ No duplicate issues found.",
69
+ fg="green",
70
+ )
71
+
72
+ def present_duplicate_issues(
73
+ self, duplicates: dict[str, list[Path]], roadmap_dir: Path
74
+ ) -> None:
75
+ """Show duplicate issues found."""
76
+ click.secho(
77
+ f"\n⚠️ Found {len(duplicates)} issue(s) with multiple copies:\n",
78
+ fg="yellow",
79
+ bold=True,
80
+ )
81
+
82
+ for issue_id, files in sorted(duplicates.items()):
83
+ click.secho(f" Issue ID: {issue_id}", fg="cyan")
84
+ for file_path in sorted(files):
85
+ click.echo(f" • {file_path.relative_to(roadmap_dir)}")
86
+ click.echo()
87
+
88
+ def present_malformed_check_clean(self) -> None:
89
+ """Show message that no malformed files found."""
90
+ click.secho(
91
+ "✅ No malformed YAML files found.",
92
+ fg="green",
93
+ )
94
+
95
+ def present_malformed_files(self, malformed_files: list[str]) -> None:
96
+ """Show malformed files found."""
97
+ click.secho(
98
+ f"\n⚠️ Found {len(malformed_files)} malformed file(s):\n",
99
+ fg="yellow",
100
+ bold=True,
101
+ )
102
+
103
+ for file_rel in malformed_files:
104
+ click.secho(f" • {file_rel}", fg="cyan")
105
+ click.echo()
106
+
107
+ def present_backup_cleanup_dry_run(
108
+ self, files_to_delete: list[dict[str, Any]]
109
+ ) -> None:
110
+ """Show what backups would be deleted in dry-run mode."""
111
+ total_size = sum(f["size"] for f in files_to_delete)
112
+ size_mb = total_size / (1024 * 1024)
113
+
114
+ click.secho(
115
+ f"\n🔍 [DRY RUN] Would delete {len(files_to_delete)} backup file(s) ({size_mb:.2f} MB):\n",
116
+ fg="blue",
117
+ bold=True,
118
+ )
119
+ for backup in files_to_delete:
120
+ click.secho(
121
+ f" • {backup['path'].name} ({backup['size']} bytes)",
122
+ fg="cyan",
123
+ )
124
+ click.secho(
125
+ f"\nWould free approximately {size_mb:.2f} MB of disk space.",
126
+ fg="green",
127
+ )
128
+
129
+ def present_backup_cleanup_result(self, result: BackupResult) -> None:
130
+ """Show backup cleanup results."""
131
+ click.secho(
132
+ f"✅ Cleaned up {result.deleted_count} backup file(s), freed ~{result.freed_mb:.2f} MB",
133
+ fg="green",
134
+ bold=True,
135
+ )
136
+ if result.failed_count > 0:
137
+ click.secho(
138
+ f"⚠️ {result.failed_count} file(s) failed to delete", fg="yellow"
139
+ )
140
+
141
+ def present_malformed_repair_result(self, result: FileRepairResult) -> None:
142
+ """Show malformed file repair results."""
143
+ if result.fixed_files:
144
+ click.secho(
145
+ f"✅ Fixed {len(result.fixed_files)} file(s)",
146
+ fg="green",
147
+ )
148
+ for file_rel in result.fixed_files:
149
+ click.echo(f" • {file_rel}")
150
+
151
+ if result.errors:
152
+ click.secho(
153
+ f"⚠️ Could not fix {len(result.errors)} file(s)",
154
+ fg="yellow",
155
+ )
156
+ for file_rel in result.errors:
157
+ click.echo(f" • {file_rel}")
158
+
159
+ def present_folder_moves(self, moves_to_make: list[dict[str, Any]]) -> None:
160
+ """Show folder moves that will be made."""
161
+ click.secho(
162
+ f"\n🔧 Resolving {len(moves_to_make)} issue(s) with folder structure problems:\n",
163
+ fg="blue",
164
+ bold=True,
165
+ )
166
+
167
+ for move in moves_to_make:
168
+ click.secho(
169
+ f" {move['issue_id']}: {move['from']} → {move['to']}",
170
+ fg="cyan",
171
+ )
172
+
173
+ def present_folder_moves_result(self, moved_count: int, failed_count: int) -> None:
174
+ """Show folder move results."""
175
+ click.secho(
176
+ f"✅ Moved {moved_count} issue file(s) to correct folders",
177
+ fg="green",
178
+ bold=True,
179
+ )
180
+ if failed_count > 0:
181
+ click.secho(f"⚠️ {failed_count} file(s) failed to move", fg="yellow")
182
+
183
+ def present_duplicate_resolution(
184
+ self, duplicates: dict[str, list[Path]], roadmap_dir: Path
185
+ ) -> None:
186
+ """Show duplicate issue resolution."""
187
+ click.secho(
188
+ f"\n�� Resolving {len(duplicates)} duplicate issue(s) (keeping latest):\n",
189
+ fg="blue",
190
+ bold=True,
191
+ )
192
+
193
+ for issue_id, files in sorted(duplicates.items()):
194
+ click.secho(f" Issue ID: {issue_id}", fg="cyan")
195
+
196
+ sorted_files = sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
197
+ keeper = sorted_files[0]
198
+ to_remove = sorted_files[1:]
199
+
200
+ click.secho(f" Keeping: {keeper.relative_to(roadmap_dir)}", fg="green")
201
+ for f in to_remove:
202
+ click.secho(f" Removing: {f.relative_to(roadmap_dir)}", fg="red")
203
+
204
+ def present_duplicate_resolution_result(
205
+ self, deleted_count: int, failed_count: int
206
+ ) -> None:
207
+ """Show duplicate resolution results."""
208
+ click.secho(
209
+ f"✅ Deleted {deleted_count} duplicate file(s)",
210
+ fg="green",
211
+ bold=True,
212
+ )
213
+ if failed_count > 0:
214
+ click.secho(f"⚠️ {failed_count} file(s) failed to delete", fg="yellow")
215
+
216
+ def present_cleanup_error(self, error: str) -> None:
217
+ """Show cleanup error."""
218
+ click.secho(f"❌ Cleanup failed: {error}", fg="red", bold=True)
219
+
220
+ def present_dry_run_skipped(self) -> None:
221
+ """Show message about dry-run skipping certain operations."""
222
+ click.echo(
223
+ "\n[DRY RUN] Folder and duplicate resolution skipped in preview mode"
224
+ )
225
+
226
+ def present_no_backups(self) -> None:
227
+ """Show message that no backups found."""
228
+ click.secho("📋 No backup files found.", fg="yellow")
229
+
230
+ def present_no_cleanup_needed(self) -> None:
231
+ """Show message that no cleanup is needed."""
232
+ click.secho(
233
+ "✅ No backup files need to be cleaned up.",
234
+ fg="green",
235
+ )