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,258 @@
1
+ """CLI presenter for Issue DTOs."""
2
+
3
+ from datetime import datetime
4
+
5
+ from rich.markdown import Markdown
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+ from rich.text import Text
9
+
10
+ from roadmap.adapters.cli.dtos import IssueDTO
11
+ from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
12
+ from roadmap.adapters.cli.presentation.table_builders import create_metadata_table
13
+ from roadmap.adapters.cli.styling import PRIORITY_COLORS, STATUS_COLORS
14
+
15
+
16
+ class IssuePresenter(BasePresenter):
17
+ """Presenter for formatting and displaying Issue DTOs."""
18
+
19
+ def render(self, issue_dto: IssueDTO) -> None:
20
+ """Render an issue DTO in detailed view.
21
+
22
+ Args:
23
+ issue_dto: The IssueDTO to render
24
+ """
25
+ # Display header
26
+ header = self._build_issue_header(issue_dto)
27
+ self._get_console().print(Panel(header, border_style="cyan"))
28
+
29
+ # Display metadata
30
+ metadata = self._build_metadata_table(issue_dto)
31
+ self._get_console().print(
32
+ Panel(metadata, title="📋 Metadata", border_style="blue")
33
+ )
34
+
35
+ # Display timeline
36
+ timeline = self._build_timeline_table(issue_dto)
37
+ self._get_console().print(
38
+ Panel(timeline, title="⏱️ Timeline", border_style="yellow")
39
+ )
40
+
41
+ # Display dependencies if any
42
+ deps = self._build_dependencies_table(issue_dto)
43
+ if deps:
44
+ self._get_console().print(
45
+ Panel(deps, title="🔗 Dependencies", border_style="magenta")
46
+ )
47
+
48
+ # Display description if available
49
+ if issue_dto.content:
50
+ description, acceptance = self._extract_description_and_criteria(
51
+ issue_dto.content
52
+ )
53
+
54
+ if description:
55
+ md = Markdown(description)
56
+ self._get_console().print(
57
+ Panel(md, title="📝 Description", border_style="white")
58
+ )
59
+
60
+ if acceptance:
61
+ md = Markdown(acceptance)
62
+ self._get_console().print(
63
+ Panel(md, title="✅ Acceptance Criteria", border_style="green")
64
+ )
65
+ else:
66
+ self._get_console().print(
67
+ Panel(
68
+ "[dim]No description available[/dim]",
69
+ title="📝 Description",
70
+ border_style="white",
71
+ )
72
+ )
73
+
74
+ # Display comments if any
75
+ if issue_dto.comments:
76
+ comments_text = self._build_comments_text(issue_dto.comments)
77
+ self._get_console().print(
78
+ Panel(
79
+ comments_text,
80
+ title=f"💬 Comments ({len(issue_dto.comments)})",
81
+ border_style="cyan",
82
+ )
83
+ )
84
+
85
+ def _build_issue_header(self, issue_dto: IssueDTO) -> Text:
86
+ """Build header text with issue title and metadata.
87
+
88
+ Args:
89
+ issue_dto: The IssueDTO to format
90
+
91
+ Returns:
92
+ Rich Text object with formatted header
93
+ """
94
+ status_color = STATUS_COLORS.get(issue_dto.status, "white")
95
+ priority_color = PRIORITY_COLORS.get(issue_dto.priority, "white")
96
+
97
+ header = Text()
98
+ header.append(f"#{issue_dto.id}", style="bold cyan")
99
+ header.append(" • ", style="dim")
100
+ header.append(issue_dto.title, style="bold white")
101
+ header.append("\n")
102
+ header.append(f"[{issue_dto.status.upper()}]", style=f"bold {status_color}")
103
+ header.append(" • ", style="dim")
104
+ header.append(issue_dto.priority.upper(), style=priority_color)
105
+ header.append(" • ", style="dim")
106
+ header.append(issue_dto.issue_type.title(), style="cyan")
107
+
108
+ return header
109
+
110
+ def _build_metadata_table(self, issue_dto: IssueDTO):
111
+ """Build metadata table for issue DTO.
112
+
113
+ Args:
114
+ issue_dto: The IssueDTO to format
115
+
116
+ Returns:
117
+ Rich Table with metadata rows
118
+ """
119
+ metadata = create_metadata_table()
120
+
121
+ metadata.add_row("Assignee", issue_dto.assignee or "Unassigned")
122
+ metadata.add_row("Milestone", issue_dto.milestone or "None")
123
+ if issue_dto.created:
124
+ metadata.add_row("Created", issue_dto.created.strftime("%Y-%m-%d %H:%M"))
125
+ if issue_dto.updated:
126
+ metadata.add_row("Updated", issue_dto.updated.strftime("%Y-%m-%d %H:%M"))
127
+
128
+ if issue_dto.labels:
129
+ metadata.add_row("Labels", ", ".join(issue_dto.labels))
130
+
131
+ if issue_dto.github_issue:
132
+ github_id = (
133
+ f"#{issue_dto.github_issue}"
134
+ if isinstance(issue_dto.github_issue, int)
135
+ else str(issue_dto.github_issue)
136
+ )
137
+ metadata.add_row("GitHub Issue", github_id)
138
+
139
+ return metadata
140
+
141
+ def _build_timeline_table(self, issue_dto: IssueDTO):
142
+ """Build timeline table for issue DTO.
143
+
144
+ Args:
145
+ issue_dto: The IssueDTO to format
146
+
147
+ Returns:
148
+ Rich Table with timeline rows
149
+ """
150
+ timeline = create_metadata_table()
151
+
152
+ # Display estimated hours
153
+ if issue_dto.estimated_hours:
154
+ timeline.add_row("Estimated", f"{issue_dto.estimated_hours:.1f}h")
155
+ else:
156
+ timeline.add_row("Estimated", "Not estimated")
157
+
158
+ # Display progress
159
+ if issue_dto.progress_percentage is not None:
160
+ timeline.add_row("Progress", f"{issue_dto.progress_percentage}%")
161
+ else:
162
+ timeline.add_row("Progress", "0%")
163
+
164
+ # Display end date if present
165
+ if isinstance(issue_dto.actual_end_date, datetime):
166
+ timeline.add_row(
167
+ "Completed", issue_dto.actual_end_date.strftime("%Y-%m-%d %H:%M")
168
+ )
169
+
170
+ # Display due date if present
171
+ if isinstance(issue_dto.due_date, datetime):
172
+ timeline.add_row("Due Date", issue_dto.due_date.strftime("%Y-%m-%d"))
173
+
174
+ return timeline
175
+
176
+ def _build_dependencies_table(self, issue_dto: IssueDTO):
177
+ """Build dependencies table if exists.
178
+
179
+ Args:
180
+ issue_dto: The IssueDTO to format
181
+
182
+ Returns:
183
+ Rich Table with dependencies or None if no dependencies
184
+ """
185
+ # For now, DTOs don't store dependencies - would need to extend model
186
+ return None
187
+
188
+ def _extract_description_and_criteria(self, content: str) -> tuple[str, str]:
189
+ """Parse issue content to extract description and acceptance criteria.
190
+
191
+ Args:
192
+ content: The issue content/description text
193
+
194
+ Returns:
195
+ Tuple of (description, acceptance_criteria) strings
196
+ """
197
+ content_lines = content.split("\n")
198
+ description_lines = []
199
+ acceptance_lines = []
200
+ in_acceptance = False
201
+
202
+ for line in content_lines:
203
+ if (
204
+ "## Acceptance Criteria" in line
205
+ or "## acceptance criteria" in line.lower()
206
+ ):
207
+ in_acceptance = True
208
+ continue
209
+ elif line.startswith("## ") and in_acceptance:
210
+ in_acceptance = False
211
+
212
+ if in_acceptance:
213
+ acceptance_lines.append(line)
214
+ elif not line.startswith("#"):
215
+ description_lines.append(line)
216
+
217
+ description = "\n".join(description_lines).strip()
218
+ acceptance = "\n".join(acceptance_lines).strip()
219
+
220
+ return description, acceptance
221
+
222
+ def _build_comments_text(self, comments: list) -> Table:
223
+ """Build formatted text for displaying comments.
224
+
225
+ Args:
226
+ comments: List of CommentDTO objects
227
+
228
+ Returns:
229
+ Rich Table object with formatted comments
230
+ """
231
+ # Create a table for comments
232
+ table = Table(show_header=False, box=None, padding=(1, 0))
233
+ table.add_column("Comments", style="white")
234
+
235
+ for comment in comments:
236
+ comment_text = Text()
237
+ # Author and timestamp
238
+ author_line = Text()
239
+ author_line.append(f"@{comment.author}", style="bold magenta")
240
+ author_line.append(
241
+ f" · {comment.created_at.strftime('%Y-%m-%d %H:%M')}", style="dim"
242
+ )
243
+ if comment.in_reply_to:
244
+ author_line.append(
245
+ f" (in reply to #{comment.in_reply_to})", style="dim italic"
246
+ )
247
+ comment_text.append(author_line)
248
+ comment_text.append("\n")
249
+
250
+ # Comment body (using markdown if possible)
251
+ comment_text.append(comment.body, style="white")
252
+
253
+ table.add_row(comment_text)
254
+ # Add separator between comments
255
+ if comment != comments[-1]:
256
+ table.add_row(Text("─" * 60, style="dim"))
257
+
258
+ return table
@@ -0,0 +1,135 @@
1
+ """Presentation layer for milestone list command.
2
+
3
+ Handles all display logic related to:
4
+ - Rendering milestone tables
5
+ - Formatting milestone data
6
+ - Styling milestone information
7
+ - Empty state handling
8
+ """
9
+
10
+ from rich.table import Table
11
+
12
+ from roadmap.common.console import get_console
13
+ from roadmap.common.logging import get_logger
14
+
15
+ console = get_console()
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class MilestoneTablePresenter:
20
+ """Presenter for rendering milestone data in table format."""
21
+
22
+ @staticmethod
23
+ def display_milestone_table(
24
+ milestones: list,
25
+ progress: dict,
26
+ estimates: dict,
27
+ get_due_date_status_fn,
28
+ ) -> None:
29
+ """Display milestones in a formatted Rich table.
30
+
31
+ Args:
32
+ milestones: List of milestone objects
33
+ progress: Dictionary mapping milestone names to progress dicts
34
+ estimates: Dictionary mapping milestone names to estimate strings
35
+ get_due_date_status_fn: Function to get due date string and styling
36
+ """
37
+ try:
38
+ table = Table(show_header=True, header_style="bold magenta")
39
+ table.add_column("Name", style="cyan")
40
+ table.add_column("Description", style="white")
41
+ table.add_column("Due Date", style="yellow", width=12)
42
+ table.add_column("Status", style="green", width=10)
43
+ table.add_column("Progress", style="blue", width=12)
44
+ table.add_column("Estimate", style="green", width=10)
45
+
46
+ for ms in milestones:
47
+ # Get progress
48
+ ms_progress = progress.get(ms.name, {})
49
+ progress_text = (
50
+ f"{ms_progress.get('completed', 0)}/{ms_progress.get('total', 0)}"
51
+ )
52
+
53
+ # Get estimate
54
+ estimate_text = estimates.get(ms.name, "-")
55
+
56
+ # Format due date with styling
57
+ due_date_text, style = get_due_date_status_fn(ms)
58
+ if style:
59
+ due_date_text = f"[{style}]{due_date_text}[/{style}]"
60
+
61
+ # Add row to table
62
+ table.add_row(
63
+ ms.name,
64
+ ms.content or "-",
65
+ due_date_text,
66
+ ms.status.value,
67
+ progress_text,
68
+ estimate_text,
69
+ )
70
+
71
+ console.print(table)
72
+ except Exception as e:
73
+ logger.error(
74
+ "failed_to_display_milestone_table",
75
+ error=str(e),
76
+ severity="operational",
77
+ )
78
+ console.print("❌ Failed to display milestone table", style="bold red")
79
+
80
+
81
+ class MilestoneListPresenter:
82
+ """Presenter for milestone list display."""
83
+
84
+ @staticmethod
85
+ def show_empty_state() -> None:
86
+ """Display empty state when no milestones found."""
87
+ console.print("📋 No milestones found.", style="yellow")
88
+ console.print(
89
+ "Create one with: roadmap milestone create 'Milestone name'",
90
+ style="dim",
91
+ )
92
+
93
+ @staticmethod
94
+ def show_no_upcoming_milestones() -> None:
95
+ """Display message when no upcoming milestones with due dates found."""
96
+ console.print("📋 No upcoming milestones with due dates found.", style="yellow")
97
+ console.print(
98
+ "Create one with: roadmap milestone create 'Milestone name' --due-date YYYY-MM-DD",
99
+ style="dim",
100
+ )
101
+
102
+ @staticmethod
103
+ def show_milestones_list(
104
+ milestones_data: dict,
105
+ get_due_date_status_fn,
106
+ ) -> None:
107
+ """Display list of milestones.
108
+
109
+ Args:
110
+ milestones_data: Dictionary with milestones, progress, estimates, etc.
111
+ get_due_date_status_fn: Function to get due date string and styling
112
+ """
113
+ if not milestones_data["has_data"]:
114
+ MilestoneListPresenter.show_empty_state()
115
+ return
116
+
117
+ # Display table
118
+ MilestoneTablePresenter.display_milestone_table(
119
+ milestones_data["milestones"],
120
+ milestones_data["progress"],
121
+ milestones_data["estimates"],
122
+ get_due_date_status_fn,
123
+ )
124
+
125
+ @staticmethod
126
+ def show_error(error_msg: str) -> None:
127
+ """Display error message.
128
+
129
+ Args:
130
+ error_msg: Error message to display
131
+ """
132
+ console.print(
133
+ f"❌ Failed to list milestones: {error_msg}",
134
+ style="bold red",
135
+ )
@@ -0,0 +1,297 @@
1
+ """CLI presenter for Milestone DTOs."""
2
+
3
+ from datetime import UTC, datetime
4
+
5
+ from rich.markdown import Markdown
6
+ from rich.panel import Panel
7
+ from rich.text import Text
8
+
9
+ from roadmap.adapters.cli.dtos import MilestoneDTO
10
+ from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
11
+ from roadmap.adapters.cli.presentation.table_builders import (
12
+ create_list_table,
13
+ create_metadata_table,
14
+ )
15
+ from roadmap.adapters.cli.styling import PRIORITY_COLORS, STATUS_COLORS
16
+
17
+
18
+ class MilestonePresenter(BasePresenter):
19
+ """Presenter for formatting and displaying Milestone DTOs."""
20
+
21
+ def render(
22
+ self,
23
+ milestone_dto: MilestoneDTO,
24
+ issues: list | None = None,
25
+ progress_data: dict | None = None,
26
+ description_content: str | None = None,
27
+ comments_text: str | None = None,
28
+ ) -> None:
29
+ """Render a milestone DTO in detailed view.
30
+
31
+ Args:
32
+ milestone_dto: The MilestoneDTO to render
33
+ issues: Optional list of issues to display
34
+ progress_data: Optional progress information dict with 'completed' and 'total' keys
35
+ description_content: Optional content with description and goals
36
+ comments_text: Optional formatted comments text
37
+ """
38
+ # Display header
39
+ header = self._build_milestone_header(milestone_dto)
40
+ self._get_console().print(Panel(header, border_style="cyan"))
41
+
42
+ # Display progress if provided
43
+ if progress_data:
44
+ self._display_progress_panel(progress_data)
45
+
46
+ # Display metadata
47
+ metadata = self._build_metadata_table(milestone_dto)
48
+ self._get_console().print(
49
+ Panel(metadata, title="📈 Statistics", border_style="blue")
50
+ )
51
+
52
+ # Display issues if provided
53
+ if issues is not None:
54
+ self._display_issues_panel(milestone_dto.name, issues)
55
+
56
+ # Display description and goals if provided
57
+ if description_content:
58
+ self._display_description_and_goals(description_content)
59
+
60
+ # Display comments if provided
61
+ if comments_text:
62
+ self._get_console().print(
63
+ Panel(
64
+ comments_text,
65
+ title="💬 Comments",
66
+ border_style="cyan",
67
+ )
68
+ )
69
+
70
+ # Display metadata footer
71
+ metadata_footer = (
72
+ f"Created: {milestone_dto.created} • Updated: {milestone_dto.updated}"
73
+ )
74
+ self._get_console().print(f"\n[dim]{metadata_footer}[/dim]")
75
+
76
+ def _build_milestone_header(self, milestone_dto: MilestoneDTO) -> Text:
77
+ """Build header text with milestone name and status.
78
+
79
+ Args:
80
+ milestone_dto: The MilestoneDTO to format
81
+
82
+ Returns:
83
+ Rich Text object with formatted header
84
+ """
85
+ status_color = "green" if milestone_dto.status == "closed" else "yellow"
86
+
87
+ header = Text()
88
+ header.append(milestone_dto.name, style="bold cyan")
89
+ header.append("\n")
90
+ header.append(f"[{milestone_dto.status.upper()}]", style=f"bold {status_color}")
91
+
92
+ # Add due date if present
93
+ if milestone_dto.due_date:
94
+ due_date_str = (
95
+ milestone_dto.due_date.strftime("%Y-%m-%d")
96
+ if hasattr(milestone_dto.due_date, "strftime")
97
+ else milestone_dto.due_date
98
+ )
99
+ now = datetime.now(UTC)
100
+
101
+ due_date_obj = (
102
+ milestone_dto.due_date
103
+ if hasattr(milestone_dto.due_date, "replace")
104
+ else datetime.fromisoformat(str(milestone_dto.due_date))
105
+ )
106
+
107
+ # Normalize due date to an aware datetime in UTC so comparisons
108
+ # between now (UTC-aware) and the due date are consistent.
109
+ if isinstance(due_date_obj, datetime):
110
+ if due_date_obj.tzinfo:
111
+ due_date_aware = due_date_obj.astimezone(UTC)
112
+ else:
113
+ due_date_aware = due_date_obj.replace(tzinfo=UTC)
114
+ else:
115
+ due_date_aware = datetime.fromisoformat(str(due_date_obj)).replace(
116
+ tzinfo=UTC
117
+ )
118
+
119
+ is_overdue = due_date_aware < now and milestone_dto.status == "open"
120
+
121
+ header.append(" • ", style="dim")
122
+ if is_overdue:
123
+ days_overdue = (now - due_date_aware).days
124
+ header.append(f"⚠️ OVERDUE by {days_overdue} days", style="bold red")
125
+ header.append(f" (Due: {due_date_str})", style="red")
126
+ else:
127
+ header.append(f"Due: {due_date_str}", style="white")
128
+
129
+ return header
130
+
131
+ def _build_metadata_table(self, milestone_dto: MilestoneDTO):
132
+ """Build metadata table for milestone DTO.
133
+
134
+ Args:
135
+ milestone_dto: The MilestoneDTO to format
136
+
137
+ Returns:
138
+ Rich Table with metadata rows
139
+ """
140
+ metadata = create_metadata_table()
141
+
142
+ metadata.add_row("Status", milestone_dto.status.upper())
143
+
144
+ if isinstance(milestone_dto.due_date, datetime):
145
+ metadata.add_row("Due Date", milestone_dto.due_date.strftime("%Y-%m-%d"))
146
+ else:
147
+ metadata.add_row("Due Date", "Not set")
148
+
149
+ if milestone_dto.headline:
150
+ metadata.add_row("Headline", milestone_dto.headline)
151
+
152
+ metadata.add_row("Progress", f"{milestone_dto.progress_percentage}%")
153
+ metadata.add_row(
154
+ "Issues",
155
+ f"{milestone_dto.completed_count}/{milestone_dto.issue_count}",
156
+ )
157
+
158
+ if isinstance(milestone_dto.created, datetime):
159
+ metadata.add_row(
160
+ "Created", milestone_dto.created.strftime("%Y-%m-%d %H:%M")
161
+ )
162
+
163
+ if isinstance(milestone_dto.updated, datetime):
164
+ metadata.add_row(
165
+ "Updated", milestone_dto.updated.strftime("%Y-%m-%d %H:%M")
166
+ )
167
+
168
+ return metadata
169
+
170
+ def _display_progress_panel(self, progress_data: dict) -> None:
171
+ """Display milestone progress as a panel.
172
+
173
+ Args:
174
+ progress_data: Dict with 'completed' and 'total' keys
175
+ """
176
+ completed = progress_data.get("completed", 0)
177
+ total = progress_data.get("total", 0)
178
+ percentage = (completed / total * 100) if total > 0 else 0
179
+
180
+ progress_table = create_metadata_table()
181
+ progress_table.add_row("Issues Complete", f"{completed}/{total}")
182
+ progress_table.add_row("Percentage", f"{percentage:.1f}%")
183
+
184
+ self._get_console().print(
185
+ Panel(
186
+ progress_table,
187
+ title="📊 Progress",
188
+ border_style="green" if percentage > 50 else "yellow",
189
+ )
190
+ )
191
+
192
+ def _display_issues_panel(self, milestone_name: str, issues: list) -> None:
193
+ """Display issues in a table panel.
194
+
195
+ Args:
196
+ milestone_name: Milestone name used in follow-up guidance
197
+ issues: List of issue objects with status, priority, etc.
198
+ """
199
+ if not issues:
200
+ self._get_console().print(
201
+ Panel(
202
+ "[dim]No issues assigned to this milestone[/dim]",
203
+ title="📋 Issues",
204
+ border_style="magenta",
205
+ )
206
+ )
207
+ return
208
+
209
+ columns = [
210
+ ("ID", "cyan", 9),
211
+ ("Title", "white", 20),
212
+ ("Status", None, 11),
213
+ ("Priority", None, 9),
214
+ ("Assignee", None, 12),
215
+ ("Progress", None, 10),
216
+ ("Estimate", None, 10),
217
+ ]
218
+ issues_table = create_list_table(columns)
219
+
220
+ for issue in issues[:10]:
221
+ status_color = STATUS_COLORS.get(issue.status.value, "white")
222
+ priority_color = PRIORITY_COLORS.get(issue.priority.value, "white")
223
+ assignee_display = (
224
+ issue.assignee if issue.assignee else "[dim]Unassigned[/dim]"
225
+ )
226
+
227
+ issues_table.add_row(
228
+ issue.id,
229
+ issue.title[:50] + "..." if len(issue.title) > 50 else issue.title,
230
+ f"[{status_color}]{issue.status.value}[/{status_color}]",
231
+ f"[{priority_color}]{issue.priority.value}[/{priority_color}]",
232
+ assignee_display,
233
+ issue.progress_display,
234
+ issue.estimated_time_display,
235
+ )
236
+
237
+ title = (
238
+ f"📋 Issues (First 10 of {len(issues)})"
239
+ if len(issues) > 10
240
+ else "📋 Issues"
241
+ )
242
+ self._get_console().print(
243
+ Panel(issues_table, title=title, border_style="magenta")
244
+ )
245
+
246
+ if len(issues) > 10:
247
+ self._get_console().print(
248
+ "[dim]Showing first 10 issues in milestone view. "
249
+ f"Use 'roadmap issue list -m {milestone_name}' to see all issues.[/dim]"
250
+ )
251
+
252
+ def _display_description_and_goals(self, content: str) -> None:
253
+ """Display description and goals sections from content.
254
+
255
+ Args:
256
+ content: Raw content with markdown sections
257
+ """
258
+ description, goals = self._extract_description_and_goals(content)
259
+
260
+ if description:
261
+ md = Markdown(description)
262
+ self._get_console().print(
263
+ Panel(md, title="📝 Description", border_style="white")
264
+ )
265
+
266
+ if goals:
267
+ md = Markdown(goals)
268
+ self._get_console().print(Panel(md, title="🎯 Goals", border_style="green"))
269
+
270
+ @staticmethod
271
+ def _extract_description_and_goals(content: str) -> tuple[str, str]:
272
+ """Parse content to extract description and goals sections.
273
+
274
+ Args:
275
+ content: Raw content string with markdown sections
276
+
277
+ Returns:
278
+ Tuple of (description, goals) strings
279
+ """
280
+ content_lines = content.split("\n")
281
+ description_lines = []
282
+ goals_lines = []
283
+ in_goals = False
284
+
285
+ for line in content_lines:
286
+ if "## Goals" in line or "## goals" in line.lower():
287
+ in_goals = True
288
+ continue
289
+ elif line.startswith("## ") and in_goals:
290
+ in_goals = False
291
+
292
+ if in_goals:
293
+ goals_lines.append(line)
294
+ elif not line.startswith("#"):
295
+ description_lines.append(line)
296
+
297
+ return "\n".join(description_lines).strip(), "\n".join(goals_lines).strip()