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,277 @@
1
+ """Presenter for core initialization command output."""
2
+
3
+ from typing import TYPE_CHECKING, Union
4
+
5
+ if TYPE_CHECKING:
6
+ from roadmap.core.domain.health import HealthStatus
7
+
8
+ import click # type: ignore[import]
9
+
10
+
11
+ class CoreInitializationPresenter:
12
+ """Handles all console output for core initialization commands."""
13
+
14
+ def __init__(self):
15
+ """Initialize presenter."""
16
+ pass
17
+
18
+ # ========== Init Command Output ==========
19
+
20
+ def present_initialization_header(self) -> None:
21
+ """Show initialization header."""
22
+ click.secho("šŸš€ Roadmap CLI Initialization", fg="cyan", bold=True)
23
+ click.echo()
24
+
25
+ def present_force_reinitialize_warning(self, name: str) -> None:
26
+ """Show warning about force re-initialization."""
27
+ click.secho(
28
+ f"āš ļø --force specified: removing existing {name}/",
29
+ fg="yellow",
30
+ )
31
+
32
+ def present_already_initialized_info(self, name: str) -> None:
33
+ """Show info that roadmap is already initialized."""
34
+ click.secho(
35
+ f"ā„¹ļø Roadmap already initialized in {name}/",
36
+ fg="cyan",
37
+ )
38
+ click.echo(
39
+ " Updating configuration while preserving existing data.", err=False
40
+ )
41
+ click.echo()
42
+
43
+ def present_initialization_error(self, error: str) -> None:
44
+ """Show initialization error."""
45
+ click.secho(f"āŒ {error}", fg="red", bold=True)
46
+
47
+ def present_initialization_tip(self) -> None:
48
+ """Show helpful tip for initialization errors."""
49
+ click.secho(
50
+ "Tip: use --force to reinitialize or --dry-run to preview.",
51
+ fg="yellow",
52
+ )
53
+
54
+ def present_already_in_progress_error(self) -> None:
55
+ """Show error that initialization is already in progress."""
56
+ click.secho(
57
+ "āŒ Initialization already in progress. Try again later.",
58
+ fg="red",
59
+ bold=True,
60
+ )
61
+
62
+ def present_creating_structure(self, name: str) -> None:
63
+ """Show status while creating structure."""
64
+ click.secho(f"šŸ—‚ļø Creating roadmap structure in {name}/...", fg="white")
65
+
66
+ def present_existing_projects_found(self, count: int) -> None:
67
+ """Show info about existing projects found."""
68
+ click.secho(
69
+ f"\n šŸ’” {count} projects found. All will be available.",
70
+ fg="white",
71
+ )
72
+
73
+ def present_project_created(self, project_name: str) -> None:
74
+ """Show that a project was created."""
75
+ click.secho(f"āœ… Created project: {project_name}", fg="green")
76
+
77
+ def present_project_joined(self, project_name: str) -> None:
78
+ """Show that team member joined existing project."""
79
+ click.secho(f"āœ… Joined existing project: {project_name}", fg="green")
80
+
81
+ def present_projects_joined(self, main_project: str, count: int) -> None:
82
+ """Show that team member joined with multiple projects available."""
83
+ click.secho(
84
+ f"āœ… Joined {main_project} ({count} projects available)", fg="green"
85
+ )
86
+
87
+ def present_initialization_warning(self, message: str) -> None:
88
+ """Show warning message after initialization."""
89
+ click.secho(
90
+ "āš ļø Initialization completed with warnings; see above.",
91
+ fg="yellow",
92
+ )
93
+
94
+ def present_initialization_failed(self, error: str) -> None:
95
+ """Show initialization failure message."""
96
+ click.secho(f"āŒ Failed to initialize roadmap: {error}", fg="red", bold=True)
97
+
98
+ # ========== Status Command Output ==========
99
+
100
+ def present_status_header(self) -> None:
101
+ """Show status command header."""
102
+ click.secho("šŸ“Š Roadmap Status Report", fg="cyan", bold=True)
103
+ click.echo()
104
+
105
+ def present_status_section(self, title: str) -> None:
106
+ """Show status section header."""
107
+ click.secho(f"\n{title}", fg="cyan", bold=True)
108
+ click.secho("─" * (len(title)), fg="white")
109
+
110
+ def present_status_item(self, label: str, value: str, ok: bool = True) -> None:
111
+ """Show a status item."""
112
+ color = "green" if ok else "yellow"
113
+ icon = "āœ“" if ok else "⚠"
114
+ click.secho(f"{icon} {label}: {value}", fg=color)
115
+
116
+ def present_status_not_initialized(self) -> None:
117
+ """Show that roadmap is not initialized."""
118
+ click.secho("āŒ Roadmap not initialized in .roadmap/", fg="red")
119
+ click.echo("Run 'roadmap init' to get started.")
120
+
121
+ # ========== Health Check Output ==========
122
+
123
+ def present_health_header(self) -> None:
124
+ """Show health check header."""
125
+ click.secho("šŸ„ Roadmap Health Check", fg="cyan", bold=True)
126
+ click.echo()
127
+
128
+ def present_health_section(self, title: str) -> None:
129
+ """Show health section header."""
130
+ click.secho(f"\n{title}", fg="cyan", bold=True)
131
+ click.secho("─" * (len(title)), fg="white")
132
+
133
+ def present_health_check(
134
+ self, check_name: str, status: Union[str, "HealthStatus"], message: str = ""
135
+ ) -> None:
136
+ """Show individual health check result.
137
+
138
+ Args:
139
+ check_name: Name of the health check
140
+ status: HealthStatus enum value or string
141
+ message: Optional details about the check
142
+ """
143
+ from roadmap.infrastructure.observability.health import HealthStatus
144
+
145
+ # Convert status to string if it's an enum
146
+ status_str = status.name if isinstance(status, HealthStatus) else str(status)
147
+
148
+ # Map status to display values
149
+ status_map = {
150
+ "HEALTHY": ("āœ…", "green"),
151
+ "DEGRADED": ("āš ļø", "yellow"),
152
+ "UNHEALTHY": ("āŒ", "red"),
153
+ "healthy": ("āœ…", "green"),
154
+ "warning": ("āš ļø", "yellow"),
155
+ "error": ("āŒ", "red"),
156
+ }
157
+
158
+ icon, color = status_map.get(status_str, ("?", "white"))
159
+ display_name = check_name.replace("_", " ").title()
160
+
161
+ click.secho(f"{icon} {display_name}: {status_str}", fg=color)
162
+ if message:
163
+ click.echo(f" {message}")
164
+
165
+ def present_overall_health(self, status: Union[str, "HealthStatus"]) -> None:
166
+ """Show overall health status.
167
+
168
+ Args:
169
+ status: HealthStatus enum value or string
170
+ """
171
+ from roadmap.infrastructure.observability.health import HealthStatus
172
+
173
+ # Convert status to string if it's an enum
174
+ status_str = status.name if isinstance(status, HealthStatus) else str(status)
175
+
176
+ status_map = {
177
+ "HEALTHY": ("āœ…", "green"),
178
+ "DEGRADED": ("āš ļø", "yellow"),
179
+ "UNHEALTHY": ("āŒ", "red"),
180
+ "healthy": ("āœ…", "green"),
181
+ "warning": ("āš ļø", "yellow"),
182
+ "error": ("āŒ", "red"),
183
+ }
184
+
185
+ icon, color = status_map.get(status_str, ("?", "white"))
186
+
187
+ click.echo()
188
+ click.secho(
189
+ f"{icon} Overall Status: {status_str}",
190
+ fg=color,
191
+ bold=True,
192
+ )
193
+
194
+ def present_health_warning(self, message: str) -> None:
195
+ """Show health warning."""
196
+ click.secho(f"āš ļø {message}", fg="yellow")
197
+
198
+ # ========== GitHub Integration Output ==========
199
+
200
+ def present_github_testing(self) -> None:
201
+ """Show message while testing GitHub connection."""
202
+ click.secho("šŸ” Testing GitHub connection...", fg="yellow")
203
+
204
+ def present_github_credentials_stored(self) -> None:
205
+ """Show message that credentials were stored."""
206
+ click.secho("šŸ”’ Credentials stored securely", fg="green")
207
+
208
+ def present_github_unavailable(self, reason: str) -> None:
209
+ """Show GitHub integration unavailable message."""
210
+ click.secho(
211
+ f"āš ļø GitHub integration not available: {reason}",
212
+ fg="yellow",
213
+ )
214
+
215
+ def present_github_setup_failed(self, error: str) -> None:
216
+ """Show GitHub setup failure."""
217
+ click.secho(f"āŒ GitHub setup failed: {error}", fg="red")
218
+
219
+ # ========== Generic Output Methods ==========
220
+
221
+ def present_error(self, message: str) -> None:
222
+ """Show generic error message."""
223
+ click.secho(f"āŒ {message}", fg="red", bold=True)
224
+
225
+ def present_warning(self, message: str) -> None:
226
+ """Show generic warning message."""
227
+ click.secho(f"āš ļø {message}", fg="yellow")
228
+
229
+ def present_info(self, message: str) -> None:
230
+ """Show generic info message."""
231
+ click.secho(f"ā„¹ļø {message}", fg="cyan")
232
+
233
+ def present_success(self, message: str) -> None:
234
+ """Show generic success message."""
235
+ click.secho(f"āœ… {message}", fg="green", bold=True)
236
+
237
+ def present_initialization_complete(self, created_new: bool) -> None:
238
+ """Show initialization completion with next steps.
239
+
240
+ Args:
241
+ created_new: Whether a new roadmap was created or existing was joined
242
+ """
243
+ click.echo()
244
+ if created_new:
245
+ click.secho("āœ… Roadmap initialized successfully!", fg="green", bold=True)
246
+ click.echo(" šŸ“ Data stored in: .roadmap/")
247
+ else:
248
+ click.secho("āœ… Initialization complete!", fg="green", bold=True)
249
+
250
+ click.echo()
251
+ click.echo("Next steps:")
252
+ click.echo(' • roadmap issue create "Your first task"')
253
+ click.echo(' • roadmap milestone create "v1.0"')
254
+ click.echo(" • roadmap list")
255
+ click.echo()
256
+
257
+ def present_existing_projects_info(self, project_names: list) -> None:
258
+ """Show info about existing projects found.
259
+
260
+ Args:
261
+ project_names: List of project names that were found
262
+ """
263
+ click.echo()
264
+ if len(project_names) == 1:
265
+ click.secho(
266
+ f"ā„¹ļø Found existing project: {project_names[0]}",
267
+ fg="cyan",
268
+ )
269
+ else:
270
+ projects_str = ", ".join(project_names[:3])
271
+ if len(project_names) > 3:
272
+ projects_str += f", +{len(project_names) - 3} more"
273
+ click.secho(
274
+ f"ā„¹ļø Found {len(project_names)} projects: {projects_str}",
275
+ fg="cyan",
276
+ )
277
+ click.secho(" Run 'roadmap project list' to see all", fg="white")
@@ -0,0 +1,134 @@
1
+ """Presenter for CRUD operation results.
2
+
3
+ This presenter handles displaying results of create, update, delete, archive,
4
+ and restore operations. It shows a brief success message with key entity details.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
10
+
11
+
12
+ def _infer_entity_type(entity: Any) -> str:
13
+ """Infer entity type from class name or attributes.
14
+
15
+ Handles both real objects and mocks used in tests.
16
+
17
+ Args:
18
+ entity: The entity object
19
+
20
+ Returns:
21
+ Entity type string (Issue, Project, Milestone, Entity)
22
+ """
23
+ class_name = entity.__class__.__name__
24
+
25
+ # Handle mocks in tests
26
+ if class_name == "Mock":
27
+ # Try to infer from entity attributes
28
+ if hasattr(entity, "priority") and hasattr(entity, "issue_type"):
29
+ return "Issue"
30
+ elif hasattr(entity, "repository"):
31
+ return "Project"
32
+ elif hasattr(entity, "start_date") or hasattr(entity, "end_date"):
33
+ return "Milestone"
34
+ return "Entity"
35
+
36
+ return class_name
37
+
38
+
39
+ def _get_entity_id(entity: Any) -> str:
40
+ """Extract entity ID."""
41
+ if hasattr(entity, "id"):
42
+ return str(entity.id)
43
+ if hasattr(entity, "entity_id"):
44
+ return str(entity.entity_id)
45
+ return "unknown"
46
+
47
+
48
+ def _get_entity_title(entity: Any) -> str:
49
+ """Extract entity title/name."""
50
+ if hasattr(entity, "name"):
51
+ return entity.name
52
+ if hasattr(entity, "title"):
53
+ return entity.title
54
+ if hasattr(entity, "summary"):
55
+ return entity.summary
56
+ return "Untitled"
57
+
58
+
59
+ class CreatePresenter(BasePresenter):
60
+ """Presenter for create operation results."""
61
+
62
+ def render(self, entity: Any) -> None:
63
+ """Render created entity summary.
64
+
65
+ Args:
66
+ entity: The created entity object
67
+ """
68
+ console = self._get_console()
69
+ entity_type = _infer_entity_type(entity)
70
+ entity_id = _get_entity_id(entity)
71
+ title = _get_entity_title(entity)
72
+
73
+ console.print(
74
+ f"āœ… Created {entity_type.lower()}: {title} [{entity_id}]",
75
+ style="green",
76
+ )
77
+
78
+
79
+ class UpdatePresenter(BasePresenter):
80
+ """Presenter for update operation results."""
81
+
82
+ def render(self, entity: Any, _updates: dict[str, Any] | None = None) -> None:
83
+ """Render updated entity summary.
84
+
85
+ Args:
86
+ entity: The updated entity object
87
+ _updates: Optional dict of fields that were updated
88
+ """
89
+ console = self._get_console()
90
+ entity_type = _infer_entity_type(entity)
91
+ entity_id = _get_entity_id(entity)
92
+ title = _get_entity_title(entity)
93
+
94
+ console.print(
95
+ f"āœ… Updated {entity_type.lower()}: {title} [{entity_id}]",
96
+ style="green",
97
+ )
98
+
99
+ # Display updated fields if provided
100
+ if _updates:
101
+ for field, value in _updates.items():
102
+ if value is not None and field != "id": # Skip id field
103
+ if field == "estimated_hours" and value is not None:
104
+ # Format estimate as "Xh" or "Xd"
105
+ hours = float(value)
106
+ days = hours / 8.0
107
+ if days >= 1:
108
+ console.print(f" estimate: {days:.1f}d", style="cyan")
109
+ else:
110
+ console.print(f" estimate: {hours:.1f}h", style="cyan")
111
+ else:
112
+ # Format the field name nicely
113
+ display_field = field.replace("_", " ").title()
114
+ console.print(f" {display_field}: {value}", style="cyan")
115
+
116
+
117
+ class DeletePresenter(BasePresenter):
118
+ """Presenter for delete operation results."""
119
+
120
+ def render(self, entity: Any) -> None:
121
+ """Render deleted entity summary.
122
+
123
+ Args:
124
+ entity: The deleted entity object
125
+ """
126
+ console = self._get_console()
127
+ entity_type = _infer_entity_type(entity)
128
+ entity_id = _get_entity_id(entity)
129
+ title = _get_entity_title(entity)
130
+
131
+ console.print(
132
+ f"āœ… Deleted {entity_type.lower()}: {title} [{entity_id}]",
133
+ style="green",
134
+ )
@@ -0,0 +1,266 @@
1
+ """Daily summary presenter for formatting and rendering output.
2
+
3
+ Handles all display formatting for the daily summary, including
4
+ table rendering and output styling.
5
+ """
6
+
7
+ from datetime import UTC, datetime
8
+
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from roadmap.adapters.cli.presentation.base_presenter import BasePresenter
14
+ from roadmap.common.console import get_console
15
+
16
+ console = get_console()
17
+
18
+
19
+ class DailySummaryPresenter(BasePresenter):
20
+ """Presenter for daily summary output.
21
+
22
+ Handles formatting and rendering of daily summary data
23
+ including tables, panels, and styling.
24
+ """
25
+
26
+ def render(self, data: dict) -> None:
27
+ """Render complete daily summary to console.
28
+
29
+ Args:
30
+ data: Dictionary from DailySummaryService.get_daily_summary_data()
31
+ """
32
+ if not data["has_issues"]:
33
+ milestone = data["milestone"]
34
+ console.print(
35
+ f"āœ… No issues assigned to you in [bold]{milestone.name}[/bold]",
36
+ style="green",
37
+ )
38
+ return
39
+
40
+ # Render header
41
+ self._render_header(data)
42
+
43
+ # Render sections
44
+ issues = data["issues"]
45
+ self._render_in_progress(issues["in_progress"])
46
+ self._render_overdue(issues["overdue"])
47
+ self._render_blocked(issues["blocked"])
48
+ self._render_up_next(issues["todo_high_priority"])
49
+ self._render_completed_today(issues["completed_today"])
50
+
51
+ # Render summary and tips
52
+ self._render_summary(issues)
53
+ self._render_tips(data)
54
+
55
+ @staticmethod
56
+ def _render_header(data: dict) -> None:
57
+ """Render daily summary header with user and milestone info."""
58
+ current_user = data["current_user"]
59
+ milestone = data["milestone"]
60
+
61
+ header = Text()
62
+ header.append("Daily Summary - ", style="dim")
63
+ header.append(current_user, style="bold cyan")
64
+ header.append("\nMilestone: ", style="dim")
65
+ header.append(milestone.name, style="bold yellow")
66
+
67
+ if milestone.due_date:
68
+ header.append(
69
+ f" (due {milestone.due_date.strftime('%Y-%m-%d')})",
70
+ style="dim",
71
+ )
72
+
73
+ header.append(f"\n{datetime.now(UTC).strftime('%A, %B %d, %Y')}", style="dim")
74
+
75
+ console.print(Panel(header, border_style="cyan"))
76
+
77
+ @staticmethod
78
+ def _render_in_progress(issues: list) -> None:
79
+ """Render in-progress issues section."""
80
+ if issues:
81
+ progress_table = Table(
82
+ show_header=True,
83
+ header_style="bold yellow",
84
+ title="šŸš€ In Progress",
85
+ title_style="bold yellow",
86
+ )
87
+ progress_table.add_column("ID", style="cyan", width=10)
88
+ progress_table.add_column("Title", style="white")
89
+ progress_table.add_column("Progress", width=12)
90
+ progress_table.add_column("Priority", width=10)
91
+
92
+ for issue in issues:
93
+ progress_table.add_row(
94
+ issue.id,
95
+ issue.title[:60] + "..." if len(issue.title) > 60 else issue.title,
96
+ issue.progress_display,
97
+ issue.priority.value,
98
+ )
99
+
100
+ console.print(progress_table)
101
+ console.print()
102
+ else:
103
+ console.print(
104
+ Panel(
105
+ "[dim]No issues currently in progress[/dim]",
106
+ title="šŸš€ In Progress",
107
+ title_align="left",
108
+ border_style="yellow",
109
+ )
110
+ )
111
+ console.print()
112
+
113
+ @staticmethod
114
+ def _render_overdue(issues: list) -> None:
115
+ """Render overdue issues section."""
116
+ if issues:
117
+ overdue_table = Table(
118
+ show_header=True,
119
+ header_style="bold red",
120
+ title="āš ļø Overdue",
121
+ title_style="bold red",
122
+ )
123
+ overdue_table.add_column("ID", style="cyan", width=10)
124
+ overdue_table.add_column("Title", style="white")
125
+ overdue_table.add_column("Due Date", width=12, style="red")
126
+ overdue_table.add_column("Priority", width=10)
127
+
128
+ for issue in issues:
129
+ due_date_obj = issue.due_date
130
+ # Normalize issue due_date to UTC-aware datetime
131
+ if isinstance(due_date_obj, datetime):
132
+ if due_date_obj.tzinfo:
133
+ due_date_aware = due_date_obj.astimezone(UTC)
134
+ else:
135
+ due_date_aware = due_date_obj.replace(tzinfo=UTC)
136
+ else:
137
+ due_date_aware = datetime.fromisoformat(str(due_date_obj)).replace(
138
+ tzinfo=UTC
139
+ )
140
+
141
+ days_overdue = (datetime.now(UTC) - due_date_aware).days
142
+ overdue_table.add_row(
143
+ issue.id,
144
+ issue.title[:50] + "..." if len(issue.title) > 50 else issue.title,
145
+ f"{due_date_aware.strftime('%Y-%m-%d')} ({days_overdue}d)",
146
+ issue.priority.value,
147
+ )
148
+
149
+ console.print(overdue_table)
150
+ console.print()
151
+
152
+ @staticmethod
153
+ def _render_blocked(issues: list) -> None:
154
+ """Render blocked issues section."""
155
+ if issues:
156
+ blocked_table = Table(
157
+ show_header=True,
158
+ header_style="bold red",
159
+ title="🚫 Blocked",
160
+ title_style="bold red",
161
+ )
162
+ blocked_table.add_column("ID", style="cyan", width=10)
163
+ blocked_table.add_column("Title", style="white")
164
+ blocked_table.add_column("Priority", width=10)
165
+
166
+ for issue in issues:
167
+ blocked_table.add_row(
168
+ issue.id,
169
+ issue.title[:60] + "..." if len(issue.title) > 60 else issue.title,
170
+ issue.priority.value,
171
+ )
172
+
173
+ console.print(blocked_table)
174
+ console.print()
175
+
176
+ @staticmethod
177
+ def _render_up_next(issues: list) -> None:
178
+ """Render up next (high priority todos) section."""
179
+ if issues:
180
+ next_table = Table(
181
+ show_header=True,
182
+ header_style="bold blue",
183
+ title="šŸ“‹ Up Next (High Priority)",
184
+ title_style="bold blue",
185
+ )
186
+ next_table.add_column("ID", style="cyan", width=10)
187
+ next_table.add_column("Title", style="white")
188
+ next_table.add_column("Priority", width=10)
189
+ next_table.add_column("Estimate", width=10)
190
+
191
+ for issue in issues:
192
+ priority_color = (
193
+ "bold red" if issue.priority.value == "critical" else "red"
194
+ )
195
+ next_table.add_row(
196
+ issue.id,
197
+ issue.title[:55] + "..." if len(issue.title) > 55 else issue.title,
198
+ f"[{priority_color}]{issue.priority.value}[/{priority_color}]",
199
+ issue.estimated_time_display,
200
+ )
201
+
202
+ console.print(next_table)
203
+ console.print()
204
+
205
+ @staticmethod
206
+ def _render_completed_today(issues: list) -> None:
207
+ """Render completed today section."""
208
+ if issues:
209
+ done_table = Table(
210
+ show_header=True,
211
+ header_style="bold green",
212
+ title="āœ… Completed Today",
213
+ title_style="bold green",
214
+ )
215
+ done_table.add_column("ID", style="cyan", width=10)
216
+ done_table.add_column("Title", style="white")
217
+ done_table.add_column("Completed", width=12)
218
+
219
+ for issue in issues:
220
+ done_table.add_row(
221
+ issue.id,
222
+ issue.title[:60] + "..." if len(issue.title) > 60 else issue.title,
223
+ issue.actual_end_date.strftime("%H:%M"),
224
+ )
225
+
226
+ console.print(done_table)
227
+ console.print()
228
+
229
+ @staticmethod
230
+ def _render_summary(issues: dict) -> None:
231
+ """Render summary footer with counters."""
232
+ summary = Text()
233
+ summary.append("\nšŸ“Š Summary: ", style="bold")
234
+ summary.append(f"{len(issues['in_progress'])} in progress", style="yellow")
235
+ summary.append(" • ", style="dim")
236
+
237
+ overdue_style = "red" if issues["overdue"] else "dim"
238
+ summary.append(f"{len(issues['overdue'])} overdue", style=overdue_style)
239
+ summary.append(" • ", style="dim")
240
+
241
+ blocked_style = "red" if issues["blocked"] else "dim"
242
+ summary.append(f"{len(issues['blocked'])} blocked", style=blocked_style)
243
+ summary.append(" • ", style="dim")
244
+ summary.append(
245
+ f"{len(issues['completed_today'])} completed today", style="green"
246
+ )
247
+
248
+ console.print(summary)
249
+
250
+ @staticmethod
251
+ def _render_tips(data: dict) -> None:
252
+ """Render helpful tips based on data."""
253
+ issues = data["issues"]
254
+
255
+ if not issues["in_progress"] and issues["todo_high_priority"]:
256
+ issue = issues["todo_high_priority"][0]
257
+ console.print(
258
+ f"\nšŸ’” Tip: Start work on an issue with: [cyan]roadmap issue start {issue.id}[/cyan]",
259
+ style="dim",
260
+ )
261
+ elif issues["overdue"]:
262
+ issue = issues["overdue"][0]
263
+ console.print(
264
+ f"\nšŸ’” Tip: View overdue issue details with: [cyan]roadmap issue view {issue.id}[/cyan]",
265
+ style="dim",
266
+ )