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,178 @@
1
+ """Comment management commands for issues."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import (
6
+ ensure_entity_exists,
7
+ require_initialized,
8
+ )
9
+ from roadmap.common.console import get_console
10
+ from roadmap.common.logging import log_command, verbose_output
11
+ from roadmap.core.services.comment.comment_service import CommentService
12
+
13
+
14
+ @click.group("comment")
15
+ def comment_group():
16
+ """Manage comments on issues."""
17
+ pass
18
+
19
+
20
+ @click.command("add")
21
+ @click.argument("issue_id")
22
+ @click.argument("body")
23
+ @click.option(
24
+ "--author",
25
+ "-a",
26
+ default=None,
27
+ help="Author of comment (defaults to current user)",
28
+ )
29
+ @click.option(
30
+ "--reply-to",
31
+ "-r",
32
+ type=int,
33
+ default=None,
34
+ help="Comment ID this is a reply to",
35
+ )
36
+ @click.pass_context
37
+ @require_initialized
38
+ @verbose_output
39
+ @log_command("issue_comment_add", entity_type="comment", track_duration=True)
40
+ def add_comment(
41
+ ctx: click.Context,
42
+ issue_id: str,
43
+ body: str,
44
+ author: str | None,
45
+ reply_to: int | None,
46
+ ):
47
+ """Add a comment to an issue.
48
+
49
+ BODY: Comment text (markdown supported)
50
+
51
+ Example:
52
+ roadmap issue comment add abc123 "Great work! This looks good."
53
+ roadmap issue comment add abc123 "I disagree" --reply-to 12345
54
+ """
55
+ core = ctx.obj["core"]
56
+ console = get_console()
57
+
58
+ # Ensure issue exists
59
+ issue = ensure_entity_exists(core, "issue", issue_id)
60
+
61
+ # Resolve author (use provided or default to system user)
62
+ if not author:
63
+ import getpass
64
+
65
+ author = getpass.getuser()
66
+
67
+ # Validate comment body
68
+ if not body or not body.strip():
69
+ console.print("❌ Comment body cannot be empty", style="red")
70
+ return
71
+
72
+ # Create the comment
73
+ try:
74
+ comment = CommentService.create_comment(
75
+ author=author,
76
+ body=body,
77
+ entity_id=issue.id,
78
+ in_reply_to=reply_to,
79
+ )
80
+ except Exception as e:
81
+ console.print(f"❌ Failed to create comment: {e}", style="red")
82
+ return
83
+
84
+ # If replying to a comment, validate that comment exists
85
+ if reply_to is not None:
86
+ if not any(c.id == reply_to for c in issue.comments):
87
+ console.print(
88
+ f"❌ Cannot find comment {reply_to} to reply to",
89
+ style="red",
90
+ )
91
+ return
92
+
93
+ # Add comment to issue
94
+ issue.comments.append(comment)
95
+
96
+ # Update the issue
97
+ try:
98
+ core.issues.update(issue.id, {"comments": issue.comments})
99
+ console.print(
100
+ f"✅ Comment added to issue {issue.id}",
101
+ style="green",
102
+ )
103
+ console.print(f" By: {comment.author}")
104
+ console.print(f" ID: {comment.id}")
105
+ if reply_to:
106
+ console.print(f" Replying to: {reply_to}")
107
+ except Exception as e:
108
+ console.print(f"❌ Failed to update issue: {e}", style="red")
109
+
110
+
111
+ @click.command("list")
112
+ @click.argument("issue_id")
113
+ @click.option(
114
+ "--format",
115
+ "-f",
116
+ type=click.Choice(["text", "json"]),
117
+ default="text",
118
+ help="Output format",
119
+ )
120
+ @click.pass_context
121
+ @require_initialized
122
+ def list_comments(ctx: click.Context, issue_id: str, format: str):
123
+ """List comments on an issue.
124
+
125
+ Example:
126
+ roadmap issue comment list abc123
127
+ roadmap issue comment list abc123 --format json
128
+ """
129
+ core = ctx.obj["core"]
130
+ console = get_console()
131
+
132
+ # Ensure issue exists
133
+ issue = ensure_entity_exists(core, "issue", issue_id)
134
+
135
+ if not issue.comments:
136
+ console.print("[dim]No comments yet[/dim]")
137
+ return
138
+
139
+ if format == "json":
140
+ # Convert comments to JSON-serializable format
141
+ comments_data = []
142
+ for comment in issue.comments:
143
+ comments_data.append(
144
+ {
145
+ "id": comment.id,
146
+ "author": comment.author,
147
+ "body": comment.body,
148
+ "created_at": comment.created_at.isoformat(),
149
+ "updated_at": comment.updated_at.isoformat(),
150
+ "in_reply_to": comment.in_reply_to,
151
+ }
152
+ )
153
+ console.print_json(data=comments_data)
154
+ else:
155
+ # Text format - show comments in thread view
156
+ threads = CommentService.build_comment_threads(issue.comments)
157
+
158
+ # Show top-level comments and their replies
159
+ top_level_ids = [k for k in threads.keys() if k is not None]
160
+ for top_level_id in sorted(top_level_ids):
161
+ for comment in threads.get(top_level_id, []):
162
+ console.print(
163
+ CommentService.format_comment_for_display(comment, indent=0)
164
+ )
165
+
166
+ # Show replies to this comment
167
+ if comment.id in threads:
168
+ for reply in threads[comment.id]:
169
+ console.print(
170
+ CommentService.format_comment_for_display(reply, indent=1)
171
+ )
172
+
173
+ console.print() # Blank line between threads
174
+
175
+
176
+ # Register commands with group
177
+ comment_group.add_command(add_comment, name="add")
178
+ comment_group.add_command(list_comments, name="list")
@@ -0,0 +1,162 @@
1
+ """Create issue command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.crud import BaseCreate, EntityType
7
+ from roadmap.adapters.cli.crud.entity_builders import IssueBuilder
8
+ from roadmap.common.logging import (
9
+ log_command,
10
+ verbose_output,
11
+ )
12
+ from roadmap.common.models import IssueCreateParams, IssueGitParams
13
+
14
+
15
+ class IssueCreate(BaseCreate):
16
+ """Create issue command implementation."""
17
+
18
+ entity_type = EntityType.ISSUE
19
+
20
+ def build_entity_dict(self, title: str, **kwargs) -> dict:
21
+ """Build entity dictionary for issue creation."""
22
+ from roadmap.core.services import IssueCreationService
23
+
24
+ # Resolve and validate assignee (with auto-detection from Git)
25
+ assignee = kwargs.get("assignee")
26
+ service = IssueCreationService(self.core)
27
+ assignee = service.resolve_and_validate_assignee(assignee, auto_detect=True)
28
+
29
+ return IssueBuilder.build_create_dict(
30
+ title=title,
31
+ priority=kwargs.get("priority"),
32
+ issue_type=kwargs.get("issue_type"),
33
+ milestone=kwargs.get("milestone"),
34
+ assignee=assignee,
35
+ labels=list(kwargs.get("labels", [])),
36
+ estimate=kwargs.get("estimate"),
37
+ depends_on=list(kwargs.get("depends_on", [])),
38
+ blocks=list(kwargs.get("blocks", [])),
39
+ description=kwargs.get("content"),
40
+ )
41
+
42
+ def _display_success(self, entity) -> None:
43
+ """Display success message using presenter."""
44
+ # Use detailed service display which includes assignee, priority, etc.
45
+ from roadmap.core.services import IssueCreationService
46
+
47
+ service = IssueCreationService(self.core)
48
+ service.format_created_issue_display(
49
+ entity, milestone=getattr(entity, "milestone", None)
50
+ )
51
+
52
+ def post_create_hook(self, entity, **kwargs) -> None:
53
+ """Handle post-creation tasks like Git branch creation."""
54
+ if kwargs.get("git_branch"):
55
+ from roadmap.core.services import IssueCreationService
56
+
57
+ service = IssueCreationService(self.core)
58
+ service.create_branch_for_issue(
59
+ entity,
60
+ kwargs.get("branch_name"),
61
+ kwargs.get("checkout", True),
62
+ kwargs.get("force", False),
63
+ )
64
+
65
+
66
+ @click.command("create")
67
+ @click.option("--title", required=True, help="Issue title")
68
+ @click.option(
69
+ "--priority",
70
+ "-p",
71
+ type=click.Choice(["critical", "high", "medium", "low"]),
72
+ default="medium",
73
+ help="Issue priority",
74
+ )
75
+ @click.option(
76
+ "--type",
77
+ "-t",
78
+ "issue_type",
79
+ type=click.Choice(["feature", "bug", "other"]),
80
+ default="other",
81
+ help="Issue type",
82
+ )
83
+ @click.option("--milestone", "-m", help="Assign to milestone")
84
+ @click.option("--assignee", "-a", help="Assign to team member")
85
+ @click.option("--labels", "-l", multiple=True, help="Add labels")
86
+ @click.option(
87
+ "--estimate", "-e", type=float, help="Estimated time to complete (in hours)"
88
+ )
89
+ @click.option("--depends-on", multiple=True, help="Issue IDs this depends on")
90
+ @click.option("--blocks", multiple=True, help="Issue IDs this blocks")
91
+ @click.option("--content", "-d", help="Markdown content/description of the issue")
92
+ @click.option("--git-branch", is_flag=True, help="Create a Git branch for this issue")
93
+ @click.option(
94
+ "--checkout/--no-checkout",
95
+ default=True,
96
+ help="Checkout the branch after creation (with --git-branch)",
97
+ )
98
+ @click.option("--branch-name", default=None, help="Override suggested branch name")
99
+ @click.option(
100
+ "--force", is_flag=True, help="Force branch creation even if working tree is dirty"
101
+ )
102
+ @click.pass_context
103
+ @verbose_output
104
+ @log_command("issue_create", entity_type="issue", track_duration=True)
105
+ @require_initialized
106
+ def create_issue(
107
+ ctx: click.Context,
108
+ title: str,
109
+ priority: str,
110
+ issue_type: str,
111
+ milestone: str,
112
+ assignee: str,
113
+ labels: tuple,
114
+ estimate: float,
115
+ depends_on: tuple,
116
+ blocks: tuple,
117
+ content: str,
118
+ git_branch: bool,
119
+ checkout: bool,
120
+ branch_name: str,
121
+ force: bool,
122
+ ):
123
+ """Create a new issue."""
124
+ core = ctx.obj["core"]
125
+ creator = IssueCreate(core)
126
+
127
+ # Create structured parameter objects
128
+ issue_params = IssueCreateParams(
129
+ title=title,
130
+ priority=priority,
131
+ issue_type=issue_type,
132
+ milestone=milestone,
133
+ assignee=assignee,
134
+ labels=labels,
135
+ estimate=estimate,
136
+ depends_on=depends_on,
137
+ blocks=blocks,
138
+ content=content,
139
+ )
140
+ git_params = IssueGitParams(
141
+ git_branch=git_branch,
142
+ checkout=checkout,
143
+ branch_name=branch_name,
144
+ force=force,
145
+ )
146
+
147
+ creator.execute(
148
+ title=issue_params.title,
149
+ priority=issue_params.priority,
150
+ issue_type=issue_params.issue_type,
151
+ milestone=issue_params.milestone,
152
+ assignee=issue_params.assignee,
153
+ labels=issue_params.labels,
154
+ estimate=issue_params.estimate,
155
+ depends_on=issue_params.depends_on,
156
+ blocks=issue_params.blocks,
157
+ content=issue_params.content,
158
+ git_branch=git_params.git_branch,
159
+ checkout=git_params.checkout,
160
+ branch_name=git_params.branch_name,
161
+ force=git_params.force,
162
+ )
@@ -0,0 +1,31 @@
1
+ """Delete issue command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.crud import BaseDelete, EntityType
7
+ from roadmap.common.logging import log_command
8
+
9
+
10
+ class IssueDelete(BaseDelete):
11
+ """Delete issue command implementation."""
12
+
13
+ entity_type = EntityType.ISSUE
14
+
15
+
16
+ @click.command("delete")
17
+ @click.argument("issue_id")
18
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
19
+ @click.pass_context
20
+ @log_command("issue_delete", entity_type="issue", track_duration=True)
21
+ @require_initialized
22
+ def delete_issue(
23
+ ctx: click.Context,
24
+ issue_id: str,
25
+ yes: bool,
26
+ ):
27
+ """Delete an issue."""
28
+ core = ctx.obj["core"]
29
+ deleter = IssueDelete(core)
30
+
31
+ deleter.execute(entity_id=issue_id, force=yes)
@@ -0,0 +1,161 @@
1
+ """Dependencies command group."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import (
6
+ ensure_entity_exists,
7
+ require_initialized,
8
+ )
9
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
10
+ from roadmap.common.console import get_console
11
+
12
+ console = get_console()
13
+
14
+
15
+ @click.group("deps")
16
+ def deps():
17
+ """Manage issue dependencies."""
18
+ pass
19
+
20
+
21
+ @deps.command("add")
22
+ @click.argument("issue_id")
23
+ @click.argument("dependency_id")
24
+ @click.pass_context
25
+ @require_initialized
26
+ def add_dependency(ctx: click.Context, issue_id: str, dependency_id: str):
27
+ """Add a dependency to an issue."""
28
+ core = ctx.obj["core"]
29
+
30
+ try:
31
+ # Get the issue
32
+ issue = ensure_entity_exists(core, "issue", issue_id)
33
+
34
+ # Check if dependency issue exists
35
+ dependency_issue = ensure_entity_exists(core, "issue", dependency_id)
36
+
37
+ # Add dependency
38
+ current_deps = issue.depends_on or []
39
+ if dependency_id not in current_deps:
40
+ current_deps.append(dependency_id)
41
+ core.issues.update(issue_id, depends_on=current_deps)
42
+ console.print(
43
+ f"✅ Added dependency: {dependency_issue.title}", style="bold green"
44
+ )
45
+ console.print(
46
+ f" {issue.title} now depends on {dependency_issue.title}", style="dim"
47
+ )
48
+ else:
49
+ console.print("⚠️ Dependency already exists", style="yellow")
50
+
51
+ except Exception as e:
52
+ handle_cli_error(
53
+ error=e,
54
+ operation="add_dependency",
55
+ entity_type="issue",
56
+ entity_id=issue_id,
57
+ context={"dependency_id": dependency_id},
58
+ fatal=True,
59
+ )
60
+ console.print(f"❌ Failed to add dependency: {e}", style="bold red")
61
+
62
+
63
+ @deps.command("remove")
64
+ @click.argument("issue_id")
65
+ @click.argument("dependency_id")
66
+ @click.pass_context
67
+ @require_initialized
68
+ def remove_dependency(ctx: click.Context, issue_id: str, dependency_id: str):
69
+ """Remove a dependency from an issue."""
70
+ core = ctx.obj["core"]
71
+
72
+ try:
73
+ issue = ensure_entity_exists(core, "issue", issue_id)
74
+
75
+ current_deps = list(issue.depends_on or [])
76
+ if dependency_id not in current_deps:
77
+ console.print(
78
+ f"⚠️ Dependency '{dependency_id}' not found on issue {issue_id}",
79
+ style="yellow",
80
+ )
81
+ return
82
+
83
+ updated_deps = [dep for dep in current_deps if dep != dependency_id]
84
+ core.issues.update(issue_id, depends_on=updated_deps)
85
+
86
+ console.print(
87
+ f"✅ Removed dependency: {dependency_id}",
88
+ style="bold green",
89
+ )
90
+ console.print(
91
+ f" {issue.title} no longer depends on {dependency_id}", style="dim"
92
+ )
93
+
94
+ except Exception as e:
95
+ handle_cli_error(
96
+ error=e,
97
+ operation="remove_dependency",
98
+ entity_type="issue",
99
+ entity_id=issue_id,
100
+ context={"dependency_id": dependency_id},
101
+ fatal=True,
102
+ )
103
+ console.print(f"❌ Failed to remove dependency: {e}", style="bold red")
104
+
105
+
106
+ @deps.command("update")
107
+ @click.argument("issue_id")
108
+ @click.argument("old_dependency_id")
109
+ @click.argument("new_dependency_id")
110
+ @click.pass_context
111
+ @require_initialized
112
+ def update_dependency(
113
+ ctx: click.Context,
114
+ issue_id: str,
115
+ old_dependency_id: str,
116
+ new_dependency_id: str,
117
+ ):
118
+ """Replace one dependency with another on an issue."""
119
+ core = ctx.obj["core"]
120
+
121
+ try:
122
+ issue = ensure_entity_exists(core, "issue", issue_id)
123
+ ensure_entity_exists(core, "issue", new_dependency_id)
124
+
125
+ current_deps = list(issue.depends_on or [])
126
+ if old_dependency_id not in current_deps:
127
+ console.print(
128
+ (f"⚠️ Dependency '{old_dependency_id}' not found on issue {issue_id}"),
129
+ style="yellow",
130
+ )
131
+ return
132
+
133
+ if new_dependency_id in current_deps and new_dependency_id != old_dependency_id:
134
+ updated_deps = [dep for dep in current_deps if dep != old_dependency_id]
135
+ else:
136
+ updated_deps = [
137
+ (new_dependency_id if dep == old_dependency_id else dep)
138
+ for dep in current_deps
139
+ ]
140
+
141
+ core.issues.update(issue_id, depends_on=updated_deps)
142
+
143
+ console.print(
144
+ f"✅ Updated dependency: {old_dependency_id} → {new_dependency_id}",
145
+ style="bold green",
146
+ )
147
+ console.print(f" {issue.title} dependency updated successfully", style="dim")
148
+
149
+ except Exception as e:
150
+ handle_cli_error(
151
+ error=e,
152
+ operation="update_dependency",
153
+ entity_type="issue",
154
+ entity_id=issue_id,
155
+ context={
156
+ "old_dependency_id": old_dependency_id,
157
+ "new_dependency_id": new_dependency_id,
158
+ },
159
+ fatal=True,
160
+ )
161
+ console.print(f"❌ Failed to update dependency: {e}", style="bold red")
@@ -0,0 +1,111 @@
1
+ """Generic helper for status change commands (block, unblock, close, start, etc.).
2
+
3
+ This module provides a unified pattern for syntactic sugar commands that update
4
+ issue status. Each command defines a StatusChangeConfig and calls apply_status_change()
5
+ to handle validation, updates, and user feedback consistently.
6
+ """
7
+
8
+ from collections.abc import Callable
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ import click
13
+
14
+ from roadmap.common.console import get_console
15
+ from roadmap.common.formatters.text.operations import (
16
+ format_operation_failure,
17
+ format_operation_success,
18
+ )
19
+ from roadmap.core.domain import Status
20
+
21
+ if TYPE_CHECKING:
22
+ from roadmap.infrastructure.coordination.core import RoadmapCore # noqa: F401
23
+
24
+ console = get_console()
25
+
26
+
27
+ @dataclass
28
+ class StatusChangeConfig:
29
+ """Configuration for a status change command.
30
+
31
+ Attributes:
32
+ status: Target Status value
33
+ emoji: Emoji to display in success message
34
+ title_verb: Verb describing the action ("Blocked", "Unblocked", "Closed")
35
+ title_style: Rich style for title line ("bold red", "bold green", etc.)
36
+ status_display: String to show in status output ("🚫 Blocked", "🔄 In Progress", etc.)
37
+ pre_check: Optional validation function that returns (is_valid: bool, error_msg: str | None)
38
+ """
39
+
40
+ status: Status
41
+ emoji: str
42
+ title_verb: str
43
+ title_style: str
44
+ status_display: str
45
+ pre_check: Callable[[Any], tuple[bool, str | None]] | None = None
46
+
47
+
48
+ def apply_status_change(
49
+ core: "RoadmapCore",
50
+ issue_id: str,
51
+ config: StatusChangeConfig,
52
+ reason: str | None = None,
53
+ ) -> None:
54
+ """Apply a status change to an issue with consistent feedback.
55
+
56
+ This function:
57
+ 1. Ensures the issue exists
58
+ 2. Runs optional pre-checks (e.g., "is issue already blocked?")
59
+ 3. Updates the issue status
60
+ 4. Displays consistent success/failure feedback
61
+ 5. Raises click.Abort on failure
62
+
63
+ Args:
64
+ core: Core application object with issues manager
65
+ issue_id: ID of issue to update
66
+ config: StatusChangeConfig defining behavior
67
+ reason: Optional reason for the change (displayed in feedback)
68
+
69
+ Raises:
70
+ click.Abort: If issue doesn't exist or update fails
71
+ """
72
+ from roadmap.adapters.cli.cli_command_helpers import ensure_entity_exists
73
+
74
+ # Ensure issue exists
75
+ issue = ensure_entity_exists(core, "issue", issue_id)
76
+
77
+ # Run pre-check if configured (e.g., "is issue already blocked?")
78
+ if config.pre_check:
79
+ is_valid, error_msg = config.pre_check(issue)
80
+ if not is_valid:
81
+ console.print(f"[yellow]⚠️ {error_msg}[/yellow]")
82
+ return
83
+
84
+ # Update status
85
+ updated = core.issues.update(issue_id, status=config.status)
86
+
87
+ if updated:
88
+ # Build extra details for display
89
+ extra_details = {"Status": config.status_display}
90
+
91
+ # Use formatter for consistent output
92
+ lines = format_operation_success(
93
+ emoji=config.emoji,
94
+ action=config.title_verb,
95
+ entity_title=updated.title,
96
+ entity_id=issue_id,
97
+ reason=reason,
98
+ extra_details=extra_details,
99
+ )
100
+ for line in lines:
101
+ console.print(line, style=config.title_style)
102
+ else:
103
+ # Failure feedback
104
+ lines = format_operation_failure(
105
+ action=config.title_verb.lower(),
106
+ entity_id=issue_id,
107
+ error="Failed to update status",
108
+ )
109
+ for line in lines:
110
+ console.print(line, style="bold red")
111
+ raise click.Abort()