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,308 @@
1
+ """Link GitHub issue command - link internal issue to GitHub issue."""
2
+
3
+ from typing import Any
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
8
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
9
+ from roadmap.common.console import get_console
10
+ from roadmap.common.formatters.text.operations import (
11
+ format_operation_failure,
12
+ format_operation_success,
13
+ )
14
+ from roadmap.common.logging import (
15
+ log_command,
16
+ )
17
+ from roadmap.core.services.github.github_issue_client import GitHubIssueClient
18
+
19
+ console = get_console()
20
+
21
+
22
+ @click.command("link")
23
+ @click.argument("issue_id")
24
+ @click.option(
25
+ "--github-id",
26
+ type=int,
27
+ required=True,
28
+ help="GitHub issue number to link (must be positive integer)",
29
+ )
30
+ @click.option(
31
+ "--owner",
32
+ type=str,
33
+ help="GitHub repository owner (required if not in config)",
34
+ )
35
+ @click.option(
36
+ "--repo",
37
+ type=str,
38
+ help="GitHub repository name (required if not in config)",
39
+ )
40
+ @click.pass_context
41
+ @log_command("issue_link", entity_type="issue", track_duration=True)
42
+ @require_initialized
43
+ def link_github_issue(
44
+ ctx: click.Context,
45
+ issue_id: str,
46
+ github_id: int,
47
+ owner: str | None,
48
+ repo: str | None,
49
+ ) -> None:
50
+ """Link an internal issue to a GitHub issue.
51
+
52
+ Links the internal issue identified by ISSUE_ID to a GitHub issue
53
+ identified by the GitHub issue number provided via --github-id.
54
+
55
+ Once linked, you can use other commands like sync-github to fetch
56
+ updates from GitHub.
57
+
58
+ Example:
59
+ roadmap issue link abc123 --github-id 456
60
+ roadmap issue link abc123 --github-id 456 --owner owner-name --repo repo-name
61
+ """
62
+ core = ctx.obj["core"]
63
+
64
+ # Validate internal issue
65
+ issue = _validate_internal_issue(ctx, issue_id)
66
+
67
+ # Validate GitHub ID is positive
68
+ _validate_github_id(ctx, github_id)
69
+
70
+ # Check if already linked
71
+ if issue.github_issue is not None:
72
+ if issue.github_issue == github_id:
73
+ _display_already_linked(issue, issue_id)
74
+ return
75
+ else:
76
+ _display_already_linked_different(ctx, issue_id, issue.github_issue)
77
+
78
+ # Get owner/repo from config or command-line
79
+ config_owner, config_repo = _resolve_github_config(ctx, core, owner, repo)
80
+
81
+ # Validate GitHub issue exists
82
+ _validate_github_issue_exists(ctx, config_owner, config_repo, github_id)
83
+
84
+ # Perform the link
85
+ _perform_link(ctx, core, issue, issue_id, github_id, config_owner, config_repo)
86
+
87
+
88
+ def _validate_internal_issue(ctx: click.Context, issue_id: str) -> Any:
89
+ """Validate that an internal issue exists.
90
+
91
+ Args:
92
+ ctx: Click context
93
+ issue_id: Issue ID to validate
94
+
95
+ Returns:
96
+ Issue object
97
+
98
+ Raises:
99
+ SystemExit: If issue not found
100
+ """
101
+ core = ctx.obj["core"]
102
+ try:
103
+ issue = core.issues.get_by_id(issue_id)
104
+ if not issue:
105
+ lines = format_operation_failure("link issue", issue_id, "Issue not found")
106
+ for line in lines:
107
+ console.print(line)
108
+ ctx.exit(1)
109
+ return issue
110
+ except Exception as e:
111
+ handle_cli_error(
112
+ error=e,
113
+ operation="retrieve issue",
114
+ entity_type="issue",
115
+ entity_id=issue_id,
116
+ fatal=True,
117
+ )
118
+
119
+
120
+ def _validate_github_id(ctx: click.Context, github_id: int) -> None:
121
+ """Validate GitHub ID is positive.
122
+
123
+ Args:
124
+ ctx: Click context
125
+ github_id: GitHub issue ID to validate
126
+
127
+ Raises:
128
+ SystemExit: If ID is invalid
129
+ """
130
+ if github_id <= 0:
131
+ lines = format_operation_failure(
132
+ "link issue", "", "Invalid GitHub issue number"
133
+ )
134
+ for line in lines:
135
+ console.print(line)
136
+ ctx.exit(1)
137
+
138
+
139
+ def _display_already_linked(issue: Any, issue_id: str) -> None:
140
+ """Display message when issue is already linked to same GitHub ID.
141
+
142
+ Args:
143
+ issue: Issue object
144
+ issue_id: Issue ID
145
+ """
146
+ lines = format_operation_success("✅", "Linked", issue.title, issue_id)
147
+ for line in lines:
148
+ console.print(line)
149
+
150
+
151
+ def _display_already_linked_different(
152
+ ctx: click.Context, issue_id: str, existing_github_id: int
153
+ ) -> None:
154
+ """Display error when issue is already linked to different GitHub ID.
155
+
156
+ Args:
157
+ ctx: Click context
158
+ issue_id: Issue ID
159
+ existing_github_id: Existing GitHub issue ID
160
+
161
+ Raises:
162
+ SystemExit: Always exits
163
+ """
164
+ lines = format_operation_failure(
165
+ "link issue",
166
+ issue_id,
167
+ f"Already linked to GitHub issue #{existing_github_id}",
168
+ )
169
+ for line in lines:
170
+ console.print(line)
171
+ ctx.exit(1)
172
+
173
+
174
+ def _resolve_github_config(
175
+ ctx: click.Context,
176
+ core: Any,
177
+ owner: str | None,
178
+ repo: str | None,
179
+ ) -> tuple[str, str]:
180
+ """Resolve GitHub owner and repo from config or arguments.
181
+
182
+ Args:
183
+ ctx: Click context
184
+ core: RoadmapCore instance
185
+ owner: Optional owner override
186
+ repo: Optional repo override
187
+
188
+ Returns:
189
+ Tuple of (owner, repo)
190
+
191
+ Raises:
192
+ SystemExit: If config cannot be resolved
193
+ """
194
+ config_owner = owner
195
+ config_repo = repo
196
+
197
+ if not config_owner or not config_repo:
198
+ try:
199
+ from pathlib import Path
200
+
201
+ from roadmap.core.services.github.github_integration_service import (
202
+ GitHubIntegrationService,
203
+ )
204
+
205
+ config_path = Path(core.root_path) / "config.yaml"
206
+ gh_service = GitHubIntegrationService(Path(core.root_path), config_path)
207
+ _, config_owner, config_repo = gh_service.get_github_config()
208
+
209
+ if not config_owner or not config_repo:
210
+ lines = format_operation_failure(
211
+ "configure GitHub", None, "GitHub repository not configured"
212
+ )
213
+ for line in lines:
214
+ console.print(line)
215
+ ctx.exit(1)
216
+ except Exception as e:
217
+ lines = format_operation_failure("get GitHub configuration", None, str(e))
218
+ for line in lines:
219
+ console.print(line)
220
+ ctx.exit(1)
221
+
222
+ # Type narrowing: at this point these must be non-None
223
+ assert config_owner is not None
224
+ assert config_repo is not None
225
+
226
+ return config_owner, config_repo
227
+
228
+
229
+ def _validate_github_issue_exists(
230
+ ctx: click.Context, owner: str, repo: str, github_id: int
231
+ ) -> None:
232
+ """Validate that a GitHub issue exists.
233
+
234
+ Args:
235
+ ctx: Click context
236
+ owner: GitHub repository owner
237
+ repo: GitHub repository name
238
+ github_id: GitHub issue ID
239
+
240
+ Raises:
241
+ SystemExit: If GitHub issue doesn't exist
242
+ """
243
+ try:
244
+ gh_client = GitHubIssueClient()
245
+ exists = gh_client.issue_exists(owner, repo, github_id)
246
+ if not exists:
247
+ lines = format_operation_failure(
248
+ "verify GitHub issue",
249
+ str(github_id),
250
+ f"GitHub issue #{github_id} not found",
251
+ )
252
+ for line in lines:
253
+ console.print(line)
254
+ ctx.exit(1)
255
+ except Exception as e:
256
+ lines = format_operation_failure("verify GitHub issue", str(github_id), str(e))
257
+ for line in lines:
258
+ console.print(line)
259
+ ctx.exit(1)
260
+
261
+
262
+ def _perform_link(
263
+ ctx: click.Context,
264
+ core: Any,
265
+ issue: Any,
266
+ issue_id: str,
267
+ github_id: int,
268
+ owner: str,
269
+ repo: str,
270
+ ) -> None:
271
+ """Perform the actual link operation.
272
+
273
+ Args:
274
+ ctx: Click context
275
+ core: RoadmapCore instance
276
+ issue: Issue object
277
+ issue_id: Issue ID
278
+ github_id: GitHub issue ID
279
+ owner: GitHub repository owner
280
+ repo: GitHub repository name
281
+
282
+ Raises:
283
+ SystemExit: If link fails
284
+ """
285
+ try:
286
+ issue.github_issue = github_id
287
+ core.issues.update(issue)
288
+
289
+ # Success message with link details
290
+ lines = format_operation_success("✅", "Linked", issue.title, issue_id)
291
+ for line in lines:
292
+ console.print(line)
293
+ console.print(
294
+ f" Repository: {owner}/{repo}",
295
+ style="cyan",
296
+ )
297
+ console.print(
298
+ f" You can now use 'roadmap issue sync-github {issue_id}' to sync changes",
299
+ style="dim",
300
+ )
301
+ except Exception as e:
302
+ handle_cli_error(
303
+ error=e,
304
+ operation="link issue",
305
+ entity_type="issue",
306
+ entity_id=issue_id,
307
+ fatal=True,
308
+ )
@@ -0,0 +1,402 @@
1
+ """List issues command."""
2
+
3
+ from dataclasses import asdict
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
8
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
9
+ from roadmap.adapters.cli.decorators import with_output_support
10
+ from roadmap.adapters.cli.dtos import IssueDTO
11
+ from roadmap.adapters.cli.mappers import IssueMapper
12
+ from roadmap.adapters.cli.services.export_manager import ExportManager
13
+ from roadmap.common.console import get_console
14
+ from roadmap.common.errors import ErrorHandler, ValidationError
15
+ from roadmap.common.formatters import IssueTableFormatter
16
+ from roadmap.common.logging import verbose_output
17
+ from roadmap.common.models import ColumnType, IssueListParams
18
+ from roadmap.core.services.issue_helpers import (
19
+ IssueFilterValidator,
20
+ IssueQueryService,
21
+ WorkloadCalculator,
22
+ )
23
+
24
+
25
+ def _get_console():
26
+ """Get console instance at runtime to respect Click's test environment."""
27
+ return get_console()
28
+
29
+
30
+ def _validate_and_get_issues(
31
+ core,
32
+ backlog: bool,
33
+ assignee: str | None,
34
+ my_issues: bool,
35
+ unassigned: bool,
36
+ next_milestone: bool,
37
+ milestone: str | None,
38
+ overdue: bool,
39
+ ) -> tuple[list[IssueDTO] | None, str | None]:
40
+ """Validate filter combinations and get filtered issues as DTOs.
41
+
42
+ Args:
43
+ core: RoadmapCore instance
44
+ backlog: Show backlog issues
45
+ assignee: Filter by assignee
46
+ my_issues: Show current user's issues
47
+ unassigned: Show unassigned issues
48
+ next_milestone: Show next milestone issues
49
+ milestone: Filter by milestone name
50
+ overdue: Show overdue issues
51
+
52
+ Returns:
53
+ Tuple of (issues DTOs list or None, filter description or None)
54
+ """
55
+ is_valid, error_msg = IssueFilterValidator.validate_filters(
56
+ assignee, my_issues, backlog, unassigned, next_milestone, milestone
57
+ )
58
+ if not is_valid:
59
+ _get_console().print(f"❌ {error_msg}", style="bold red")
60
+ return None, None
61
+
62
+ query_service = IssueQueryService(core)
63
+ issues, filter_description = query_service.get_filtered_issues(
64
+ milestone=milestone,
65
+ backlog=backlog,
66
+ overdue=overdue,
67
+ unassigned=unassigned,
68
+ next_milestone=next_milestone,
69
+ assignee=assignee,
70
+ my_issues=my_issues,
71
+ )
72
+
73
+ # Convert domain Issues to DTOs for CLI presentation
74
+ issue_dtos = (
75
+ [IssueMapper.domain_to_dto(issue) for issue in issues] if issues else []
76
+ )
77
+
78
+ return issue_dtos, filter_description
79
+
80
+
81
+ def _handle_no_upcoming_milestones() -> None:
82
+ """Handle case where no upcoming milestones are found."""
83
+ _get_console().print(
84
+ "📋 No upcoming milestones with due dates found.", style="yellow"
85
+ )
86
+ _get_console().print(
87
+ "Create one with: roadmap milestone create 'Milestone name' --due-date YYYY-MM-DD",
88
+ style="dim",
89
+ )
90
+
91
+
92
+ def _apply_additional_filters(
93
+ core,
94
+ issues: list[IssueDTO],
95
+ filter_description: str | None,
96
+ open_flag: bool,
97
+ blocked: bool,
98
+ status: str | None,
99
+ priority: str | None,
100
+ issue_type: str | None,
101
+ ) -> tuple[list[IssueDTO], str]:
102
+ """Apply additional filters to issue DTOs.
103
+
104
+ Args:
105
+ core: RoadmapCore instance
106
+ issues: List of issue DTOs to filter
107
+ filter_description: Current filter description
108
+ open_flag: Show only open issues
109
+ blocked: Show only blocked issues
110
+ status: Filter by status
111
+ priority: Filter by priority
112
+ issue_type: Filter by issue type
113
+
114
+ Returns:
115
+ Tuple of (filtered issue DTOs, updated filter description)
116
+ """
117
+ # Convert DTOs back to domain for filtering via service
118
+ from roadmap.adapters.cli.mappers import IssueMapper
119
+
120
+ domain_issues = [IssueMapper.dto_to_domain(dto) for dto in issues]
121
+
122
+ query_service = IssueQueryService(core)
123
+ filtered_domain_issues, filter_description = query_service.apply_additional_filters(
124
+ domain_issues,
125
+ filter_description or "",
126
+ open_only=open_flag,
127
+ blocked_only=blocked,
128
+ status=status,
129
+ priority=priority,
130
+ issue_type=issue_type,
131
+ )
132
+
133
+ # Convert filtered domain issues back to DTOs
134
+ filtered_dtos = [
135
+ IssueMapper.domain_to_dto(issue) for issue in filtered_domain_issues
136
+ ]
137
+ return filtered_dtos, filter_description
138
+
139
+
140
+ def _normalize_filter_type(params: IssueListParams) -> None:
141
+ """Normalize positional filter aliases into canonical flags."""
142
+ if params.filter_type and params.filter_type.lower() == "backlog":
143
+ params.backlog = True
144
+
145
+
146
+ def _display_no_issues(filter_description: str) -> None:
147
+ """Display standard no-issues message."""
148
+ _get_console().print(f"📋 No {filter_description} issues found.", style="yellow")
149
+ _get_console().print(
150
+ "Create one with: roadmap issue create 'Issue title'", style="dim"
151
+ )
152
+
153
+
154
+ def _print_issue_list_header(issue_count: int, filter_description: str) -> None:
155
+ """Print issue list summary header."""
156
+ header_text = (
157
+ f"📋 {issue_count} {filter_description} issue{'s' if issue_count != 1 else ''}"
158
+ )
159
+ _get_console().print(header_text, style="bold cyan")
160
+
161
+
162
+ def _maybe_export_issues(
163
+ issues: list[IssueDTO], issue_count: int, export: bool
164
+ ) -> None:
165
+ """Export issue DTOs when requested."""
166
+ if not export:
167
+ return
168
+ export_manager = ExportManager()
169
+ export_data = [asdict(dto) for dto in issues]
170
+ _content, export_path = export_manager.export_data(export_data, "issues")
171
+ _get_console().print(
172
+ f"✅ Exported {issue_count} issues to {export_path}", style="green"
173
+ )
174
+
175
+
176
+ def _maybe_display_workload_summary(
177
+ params: IssueListParams, domain_issues: list
178
+ ) -> None:
179
+ """Display workload summary for assignee-focused views."""
180
+ if not ((params.assignee or params.my_issues) and domain_issues):
181
+ return
182
+ assignee_name = params.assignee if params.assignee else "you"
183
+ workload = WorkloadCalculator.calculate_workload(domain_issues)
184
+ IssueTableFormatter.display_workload_summary(
185
+ assignee_name,
186
+ workload["total_hours"],
187
+ workload["status_breakdown"],
188
+ )
189
+
190
+
191
+ def _build_list_error_context(params: IssueListParams) -> dict:
192
+ """Build error context for list_issues failure handling."""
193
+ return {
194
+ "backlog": params.backlog,
195
+ "assignee": params.assignee,
196
+ "my_issues": params.my_issues,
197
+ "filter": params.status,
198
+ }
199
+
200
+
201
+ @click.command("list")
202
+ @click.argument(
203
+ "filter_type",
204
+ required=False,
205
+ default=None,
206
+ type=click.Choice(["backlog"], case_sensitive=False),
207
+ )
208
+ @click.option("--milestone", "-m", help="Filter by milestone")
209
+ @click.option("--backlog", is_flag=True, help="Show only backlog issues (no milestone)")
210
+ @click.option(
211
+ "--unassigned",
212
+ is_flag=True,
213
+ help="Show only unassigned issues (alias for --backlog)",
214
+ )
215
+ @click.option("--open", is_flag=True, help="Show only open issues (not closed)")
216
+ @click.option("--blocked", is_flag=True, help="Show only blocked issues")
217
+ @click.option(
218
+ "--next-milestone", is_flag=True, help="Show issues for the next upcoming milestone"
219
+ )
220
+ @click.option("--assignee", "-a", help="Filter by assignee")
221
+ @click.option("--my-issues", is_flag=True, help="Show only issues assigned to me")
222
+ @click.option(
223
+ "--status",
224
+ "-s",
225
+ type=click.Choice(["todo", "in-progress", "blocked", "review", "closed"]),
226
+ help="Filter by workflow status (todo|in-progress|blocked|review|closed). Use --open for non-closed issues",
227
+ )
228
+ @click.option(
229
+ "--priority",
230
+ "-p",
231
+ type=click.Choice(["critical", "high", "medium", "low"]),
232
+ help="Filter by priority",
233
+ )
234
+ @click.option(
235
+ "--issue-type",
236
+ "-t",
237
+ type=click.Choice(["feature", "bug", "other"]),
238
+ help="Filter by issue type",
239
+ )
240
+ @click.option(
241
+ "--overdue", is_flag=True, help="Show only overdue issues (past due date)"
242
+ )
243
+ @click.option("--verbose", "-v", is_flag=True, help="Show verbose output")
244
+ @click.option(
245
+ "--show-github-ids",
246
+ is_flag=True,
247
+ help="Display GitHub issue IDs for linked issues",
248
+ )
249
+ @click.option(
250
+ "--export",
251
+ "-e",
252
+ is_flag=True,
253
+ help="Export results to file (uses config defaults for format and location)",
254
+ )
255
+ @click.pass_context
256
+ @with_output_support(
257
+ available_columns=[
258
+ "id",
259
+ "title",
260
+ "priority",
261
+ "status",
262
+ "progress",
263
+ "assignee",
264
+ "estimate",
265
+ "milestone",
266
+ ],
267
+ column_types={
268
+ "id": ColumnType.STRING,
269
+ "title": ColumnType.STRING,
270
+ "priority": ColumnType.ENUM,
271
+ "status": ColumnType.ENUM,
272
+ "progress": ColumnType.STRING,
273
+ "assignee": ColumnType.STRING,
274
+ "estimate": ColumnType.STRING,
275
+ "milestone": ColumnType.STRING,
276
+ },
277
+ )
278
+ @verbose_output
279
+ @require_initialized
280
+ def list_issues( # noqa: F841 - verbose is used by decorator
281
+ ctx: click.Context,
282
+ filter_type: str,
283
+ milestone: str,
284
+ backlog: bool,
285
+ unassigned: bool,
286
+ open: bool,
287
+ blocked: bool,
288
+ next_milestone: bool,
289
+ assignee: str,
290
+ my_issues: bool,
291
+ status: str,
292
+ priority: str,
293
+ issue_type: str,
294
+ overdue: bool,
295
+ verbose: bool, # noqa: F841 - used by @verbose_output decorator
296
+ show_github_ids: bool,
297
+ export: bool,
298
+ ):
299
+ """List all issues with various filtering options.
300
+
301
+ Optional positional argument 'backlog' shows only backlog issues (no milestone).
302
+ Equivalent to: roadmap issue list --backlog
303
+
304
+ Supports output formatting with --format, --columns, --sort-by, --filter flags.
305
+ """
306
+ core = ctx.obj["core"]
307
+
308
+ # Create structured parameter object
309
+ params = IssueListParams(
310
+ filter_type=filter_type,
311
+ milestone=milestone,
312
+ backlog=backlog,
313
+ unassigned=unassigned,
314
+ open=open,
315
+ blocked=blocked,
316
+ next_milestone=next_milestone,
317
+ assignee=assignee,
318
+ my_issues=my_issues,
319
+ status=status,
320
+ priority=priority,
321
+ issue_type=issue_type,
322
+ overdue=overdue,
323
+ )
324
+
325
+ try:
326
+ _normalize_filter_type(params)
327
+
328
+ # Validate and get issues
329
+ issues, filter_description = _validate_and_get_issues(
330
+ core,
331
+ params.backlog,
332
+ params.assignee,
333
+ params.my_issues,
334
+ params.unassigned,
335
+ params.next_milestone,
336
+ params.milestone,
337
+ params.overdue,
338
+ )
339
+
340
+ if issues is None:
341
+ return
342
+
343
+ # Handle next milestone not found
344
+ if next_milestone and not issues and not filter_description:
345
+ _handle_no_upcoming_milestones()
346
+ return
347
+
348
+ # Apply additional filters
349
+ issues, filter_description = _apply_additional_filters(
350
+ core,
351
+ issues,
352
+ filter_description,
353
+ params.open,
354
+ params.blocked,
355
+ params.status,
356
+ params.priority,
357
+ params.issue_type,
358
+ )
359
+
360
+ # Handle no issues found
361
+ if not issues:
362
+ _display_no_issues(filter_description)
363
+ return
364
+
365
+ issue_count = len(issues)
366
+ _print_issue_list_header(issue_count, filter_description)
367
+
368
+ # Convert DTOs back to domain objects for table formatter
369
+ # (formatters still work with domain models)
370
+ domain_issues = [IssueMapper.dto_to_domain(dto) for dto in issues]
371
+
372
+ _maybe_export_issues(issues, issue_count, export)
373
+
374
+ # Convert to TableData for structured output
375
+ table_data = IssueTableFormatter.issues_to_table_data(
376
+ domain_issues,
377
+ title="Issues",
378
+ description=filter_description,
379
+ show_github_ids=show_github_ids,
380
+ )
381
+
382
+ _maybe_display_workload_summary(params, domain_issues)
383
+
384
+ # Return TableData for decorator to handle formatting
385
+ return table_data
386
+
387
+ except Exception as e:
388
+ handle_cli_error(
389
+ error=e,
390
+ operation="list_issues",
391
+ entity_type="issue",
392
+ entity_id="all",
393
+ context=_build_list_error_context(params),
394
+ fatal=True,
395
+ )
396
+ error_handler = ErrorHandler()
397
+ error_handler.handle_error(
398
+ ValidationError(
399
+ "Failed to list issues", context={"command": "list"}, cause=e
400
+ ),
401
+ exit_on_critical=False,
402
+ )