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,374 @@
1
+ """Sync status command - View sync history and statistics for GitHub-linked issues."""
2
+
3
+ import sys
4
+
5
+ import click
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+ from rich.text import Text
9
+
10
+ from roadmap.adapters.cli.cli_command_helpers import ensure_entity_exists
11
+ from roadmap.common.console import get_console
12
+ from roadmap.common.datetime_parser import UnifiedDateTimeParser
13
+ from roadmap.core.services.sync.sync_metadata_service import SyncMetadataService
14
+
15
+
16
+ def _get_console():
17
+ """Get a fresh console instance for test compatibility."""
18
+ return get_console()
19
+
20
+
21
+ console = get_console()
22
+
23
+
24
+ def _format_timestamp(iso_str: str) -> str:
25
+ """Format ISO timestamp to human-readable format."""
26
+ try:
27
+ dt = UnifiedDateTimeParser.parse_iso_datetime(iso_str)
28
+ if dt:
29
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
30
+ return iso_str
31
+ except Exception as e:
32
+ from roadmap.common.logging import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+ logger.debug(
36
+ "iso_datetime_format_failed",
37
+ iso_str=iso_str,
38
+ error=str(e),
39
+ action="format_iso_timestamp",
40
+ )
41
+ return iso_str
42
+
43
+
44
+ def _build_sync_status_header(issue, metadata):
45
+ """Build header text with issue title and sync status."""
46
+ header = Text()
47
+ header.append(f"#{issue.id}", style="bold cyan")
48
+ header.append(" • ", style="dim")
49
+ header.append(issue.title, style="bold white")
50
+ header.append("\n")
51
+
52
+ # Add sync status
53
+ status_color_map = {
54
+ "success": "green",
55
+ "conflict": "yellow",
56
+ "error": "red",
57
+ "never": "dim",
58
+ }
59
+ status_color = status_color_map.get(metadata.last_sync_status, "white")
60
+ header.append(
61
+ f"[{metadata.last_sync_status.upper()}]", style=f"bold {status_color}"
62
+ )
63
+
64
+ if metadata.last_sync_time:
65
+ header.append(" • ", style="dim")
66
+ header.append(
67
+ f"Last synced: {_format_timestamp(metadata.last_sync_time)}",
68
+ style="dim",
69
+ )
70
+
71
+ return header
72
+
73
+
74
+ def _build_sync_metadata_table(issue, metadata):
75
+ """Build metadata table showing sync statistics."""
76
+ table = Table(show_header=False, box=None, padding=(0, 1))
77
+
78
+ if metadata.github_issue_id:
79
+ table.add_row("GitHub Issue", f"#{metadata.github_issue_id}")
80
+
81
+ table.add_row("Total Syncs", str(metadata.sync_count))
82
+ table.add_row("Successful", str(metadata.successful_syncs))
83
+
84
+ if metadata.sync_count > 0:
85
+ success_rate = metadata.get_success_rate()
86
+ rate_color = (
87
+ "green"
88
+ if success_rate == 100
89
+ else "yellow"
90
+ if success_rate >= 75
91
+ else "red"
92
+ )
93
+ table.add_row(
94
+ "Success Rate", f"[{rate_color}]{success_rate:.1f}%[/{rate_color}]"
95
+ )
96
+
97
+ conflict_count = sum(
98
+ 1 for record in metadata.sync_history if record.conflict_resolution
99
+ )
100
+ if conflict_count > 0:
101
+ table.add_row("Conflicts Resolved", str(conflict_count))
102
+
103
+ return table
104
+
105
+
106
+ def _build_sync_history_table(records):
107
+ """Build table showing recent sync history."""
108
+ table = Table(
109
+ title="Sync History (Most Recent First)",
110
+ show_header=True,
111
+ header_style="bold cyan",
112
+ )
113
+
114
+ table.add_column("Time", style="dim", width=20)
115
+ table.add_column("Status", width=12)
116
+ table.add_column("Details", width=30)
117
+
118
+ for record in records:
119
+ time_str = _format_timestamp(record.sync_timestamp)
120
+
121
+ # Status
122
+ if record.success:
123
+ status_text = "[green]✓ SUCCESS[/green]"
124
+ elif record.conflict_resolution:
125
+ status_text = "[yellow]⚠ CONFLICT[/yellow]"
126
+ else:
127
+ status_text = "[red]✗ ERROR[/red]"
128
+
129
+ # Details
130
+ details_parts = []
131
+ if record.local_changes:
132
+ details_parts.append(f"Local: {len(record.local_changes)} changes")
133
+ if record.github_changes:
134
+ details_parts.append(f"GitHub: {len(record.github_changes)} changes")
135
+ if record.conflict_resolution:
136
+ details_parts.append(f"Resolved: {record.conflict_resolution}")
137
+ if record.error_message:
138
+ # Truncate long error messages
139
+ error = record.error_message
140
+ if len(error) > 40:
141
+ error = error[:37] + "..."
142
+ details_parts.append(f"Error: {error}")
143
+
144
+ details_text = " • ".join(details_parts) if details_parts else "—"
145
+
146
+ table.add_row(time_str, status_text, details_text)
147
+
148
+ return table
149
+
150
+
151
+ def _build_aggregate_stats_table(stats):
152
+ """Build table showing aggregate statistics across issues."""
153
+ table = Table(show_header=False, box=None, padding=(0, 1))
154
+
155
+ table.add_row("Total Issues", str(stats["total_issues"]))
156
+ table.add_row("Total Sync Attempts", str(stats["total_sync_attempts"]))
157
+ table.add_row("Successful Syncs", str(stats["successful_syncs"]))
158
+
159
+ if stats["total_sync_attempts"] > 0:
160
+ rate_color = (
161
+ "green"
162
+ if stats["success_rate"] == 100
163
+ else "yellow"
164
+ if stats["success_rate"] >= 75
165
+ else "red"
166
+ )
167
+ table.add_row(
168
+ "Overall Success Rate",
169
+ f"[{rate_color}]{stats['success_rate']:.1f}%[/{rate_color}]",
170
+ )
171
+
172
+ if stats["total_conflicts"] > 0:
173
+ conflict_color = (
174
+ "green"
175
+ if stats["conflict_rate"] < 5
176
+ else "yellow"
177
+ if stats["conflict_rate"] < 20
178
+ else "red"
179
+ )
180
+ table.add_row(
181
+ "Conflict Rate",
182
+ f"[{conflict_color}]{stats['conflict_rate']:.1f}%[/{conflict_color}]",
183
+ )
184
+
185
+ table.add_row("Never Synced", str(stats["never_synced"]))
186
+
187
+ return table
188
+
189
+
190
+ def _handle_sync_statistics(
191
+ core, console_inst, metadata_service: "SyncMetadataService"
192
+ ) -> None:
193
+ """Display aggregate sync statistics across all GitHub-linked issues."""
194
+ all_issues = core.issues.all()
195
+ github_issues = [i for i in all_issues if getattr(i, "github_issue", None)]
196
+ if not github_issues:
197
+ console_inst.print("[yellow]⚠️ No GitHub-linked issues found[/yellow]")
198
+ return
199
+ stats = metadata_service.get_statistics(github_issues)
200
+ panel = Panel(
201
+ _build_aggregate_stats_table(stats),
202
+ title="[bold cyan]GitHub Sync Statistics[/bold cyan]",
203
+ expand=False,
204
+ )
205
+ console_inst.print(panel)
206
+
207
+
208
+ def _handle_all_issues_status(
209
+ core, console_inst, metadata_service: "SyncMetadataService"
210
+ ) -> None:
211
+ """Display a brief sync-status overview for every GitHub-linked issue."""
212
+ all_issues = core.issues.all()
213
+ github_issues = [i for i in all_issues if getattr(i, "github_issue", None)]
214
+ if not github_issues:
215
+ console_inst.print("[yellow]⚠️ No GitHub-linked issues found[/yellow]")
216
+ return
217
+
218
+ table = Table(
219
+ title=f"GitHub Sync Status ({len(github_issues)} issues)",
220
+ show_header=True,
221
+ header_style="bold cyan",
222
+ )
223
+ table.add_column("Issue ID", style="cyan")
224
+ table.add_column("GitHub", width=10)
225
+ table.add_column("Title", width=35)
226
+ table.add_column("Status", width=12)
227
+ table.add_column("Last Sync", width=20)
228
+ table.add_column("Success %", width=12)
229
+
230
+ status_color_map = {
231
+ "success": "green",
232
+ "conflict": "yellow",
233
+ "error": "red",
234
+ "never": "dim",
235
+ }
236
+ for issue in github_issues:
237
+ metadata = metadata_service.get_metadata(issue)
238
+ status_color = status_color_map.get(metadata.last_sync_status, "white")
239
+ last_sync = (
240
+ _format_timestamp(metadata.last_sync_time)
241
+ if metadata.last_sync_time
242
+ else "Never"
243
+ )
244
+ success_rate = (
245
+ f"{metadata.get_success_rate():.0f}%" if metadata.sync_count > 0 else "—"
246
+ )
247
+ table.add_row(
248
+ issue.id,
249
+ f"#{metadata.github_issue_id}",
250
+ issue.title[:32],
251
+ f"[{status_color}]{metadata.last_sync_status}[/{status_color}]",
252
+ last_sync,
253
+ success_rate,
254
+ )
255
+ console_inst.print(table)
256
+
257
+
258
+ def _handle_single_issue_status(
259
+ issue_id: str,
260
+ show_history: bool,
261
+ core,
262
+ console_inst,
263
+ metadata_service: "SyncMetadataService",
264
+ ) -> None:
265
+ """Display full sync status detail for a single GitHub-linked issue."""
266
+ if not issue_id:
267
+ console_inst.print(
268
+ "[red]❌ Must provide ISSUE_ID or use --all or --statistics[/red]"
269
+ )
270
+ sys.exit(1)
271
+
272
+ issue = ensure_entity_exists(core.issues, issue_id, "Issue")
273
+ if not issue:
274
+ return
275
+
276
+ if not getattr(issue, "github_issue", None):
277
+ console_inst.print(
278
+ f"[yellow]⚠️ Issue #{issue_id} is not linked to GitHub[/yellow]"
279
+ )
280
+ return
281
+
282
+ metadata = metadata_service.get_metadata(issue)
283
+
284
+ console_inst.print()
285
+ console_inst.print(Panel(_build_sync_status_header(issue, metadata), expand=False))
286
+ console_inst.print()
287
+ console_inst.print(
288
+ Panel(
289
+ _build_sync_metadata_table(issue, metadata),
290
+ title="[bold cyan]Sync Statistics[/bold cyan]",
291
+ expand=False,
292
+ )
293
+ )
294
+ console_inst.print()
295
+
296
+ if metadata.sync_history:
297
+ history_records = list(reversed(metadata.sync_history))
298
+ if not show_history:
299
+ history_records = history_records[:5]
300
+ console_inst.print(_build_sync_history_table(history_records))
301
+ if not show_history and len(metadata.sync_history) > 5:
302
+ console_inst.print(
303
+ f"\n[dim]... showing last 5 of {len(metadata.sync_history)} syncs. "
304
+ f"Use --history to see all[/dim]"
305
+ )
306
+ else:
307
+ console_inst.print(
308
+ Panel(
309
+ "[dim]No sync history recorded yet[/dim]",
310
+ title="[bold cyan]Sync History[/bold cyan]",
311
+ expand=False,
312
+ )
313
+ )
314
+
315
+ console_inst.print()
316
+
317
+
318
+ @click.command(name="sync-status")
319
+ @click.argument("issue_id", default="", required=False)
320
+ @click.option("--history", "show_history", is_flag=True, help="Show full sync history")
321
+ @click.option(
322
+ "--statistics",
323
+ "show_statistics",
324
+ is_flag=True,
325
+ help="Show aggregate statistics across all issues",
326
+ )
327
+ @click.option(
328
+ "--all",
329
+ "show_all_issues",
330
+ is_flag=True,
331
+ help="Show sync status for all GitHub-linked issues",
332
+ )
333
+ @click.pass_context
334
+ def sync_status(
335
+ ctx: click.Context,
336
+ issue_id: str,
337
+ show_history: bool,
338
+ show_statistics: bool,
339
+ show_all_issues: bool,
340
+ ) -> None:
341
+ """Show sync status and history for GitHub-linked issues.
342
+
343
+ Examples:
344
+ # Show status for a single issue
345
+ roadmap issue sync-status ISSUE_ID
346
+
347
+ # Show full sync history
348
+ roadmap issue sync-status ISSUE_ID --history
349
+
350
+ # Show aggregate statistics
351
+ roadmap issue sync-status --statistics
352
+
353
+ # Show status for all linked issues
354
+ roadmap issue sync-status --all
355
+
356
+ The command displays:
357
+ - Last sync time and status
358
+ - Total sync attempts and success rate
359
+ - Recent sync history with change details
360
+ - Conflict resolution information
361
+ """
362
+ core = ctx.obj
363
+ console_inst = get_console()
364
+ metadata_service = SyncMetadataService(core)
365
+
366
+ if show_statistics:
367
+ _handle_sync_statistics(core, console_inst, metadata_service)
368
+ return
369
+ if show_all_issues:
370
+ _handle_all_issues_status(core, console_inst, metadata_service)
371
+ return
372
+ _handle_single_issue_status(
373
+ issue_id, show_history, core, console_inst, metadata_service
374
+ )
@@ -0,0 +1,43 @@
1
+ """Unblock issue command - thin wrapper around update.
2
+
3
+ This command is syntactic sugar for: roadmap issue update <ID> --status in-progress
4
+ """
5
+
6
+ import click
7
+
8
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
9
+ from roadmap.common.cli_errors import handle_cli_errors
10
+ from roadmap.core.domain import Status
11
+
12
+ from .issue_status_helpers import StatusChangeConfig, apply_status_change
13
+
14
+
15
+ @click.command("unblock")
16
+ @click.argument("issue_id")
17
+ @click.option("--reason", "-r", help="Reason for unblocking")
18
+ @click.pass_context
19
+ @handle_cli_errors(command_name="issue unblock")
20
+ @require_initialized
21
+ def unblock_issue(ctx: click.Context, issue_id: str, reason: str):
22
+ """Unblock an issue by setting status to in-progress.
23
+
24
+ Syntactic sugar for: roadmap issue update <ID> --status in-progress
25
+ """
26
+ core = ctx.obj["core"]
27
+
28
+ def check_is_blocked(issue):
29
+ """Verify issue is currently blocked before unblocking."""
30
+ status_val = getattr(issue.status, "value", str(issue.status))
31
+ if status_val != "blocked":
32
+ return False, f"Issue is not blocked (current: {status_val})"
33
+ return True, None
34
+
35
+ config = StatusChangeConfig(
36
+ status=Status.IN_PROGRESS,
37
+ emoji="✅",
38
+ title_verb="Unblocked",
39
+ title_style="bold green",
40
+ status_display="🔄 In Progress",
41
+ pre_check=check_is_blocked,
42
+ )
43
+ apply_status_change(core, issue_id, config, reason)
@@ -0,0 +1,104 @@
1
+ """Unlink GitHub issue command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
7
+ from roadmap.common.console import get_console
8
+ from roadmap.common.formatters.text.operations import (
9
+ format_operation_failure,
10
+ format_operation_success,
11
+ )
12
+ from roadmap.common.logging import (
13
+ log_command,
14
+ track_database_operation,
15
+ )
16
+
17
+ console = get_console()
18
+
19
+
20
+ @click.command("unlink-github")
21
+ @click.argument("issue_id")
22
+ @click.pass_context
23
+ @log_command("issue_unlink", entity_type="issue", track_duration=True)
24
+ @require_initialized
25
+ def unlink_github_issue(ctx: click.Context, issue_id: str) -> None:
26
+ """Remove GitHub link from a local issue.
27
+
28
+ Args:
29
+ ctx: Click context with core object
30
+ issue_id: ID of the local issue to unlink
31
+ """
32
+ core = ctx.obj["core"]
33
+
34
+ try:
35
+ # Get the issue
36
+ issue = core.issues.get(issue_id)
37
+ if not issue:
38
+ lines = format_operation_failure(
39
+ action="unlink",
40
+ entity_id=issue_id,
41
+ error="Issue not found",
42
+ )
43
+ for line in lines:
44
+ console.print(line, style="bold red")
45
+ return
46
+
47
+ # Check if issue is linked
48
+ if issue.github_issue is None:
49
+ console.print(
50
+ f"⚠️ Issue '{issue_id}' is not linked to GitHub",
51
+ style="yellow",
52
+ )
53
+ return
54
+
55
+ # Store the GitHub ID for display
56
+ github_id = issue.github_issue
57
+
58
+ # Remove the link
59
+ with track_database_operation(
60
+ "update", "issue", entity_id=issue_id, warn_threshold_ms=2000
61
+ ):
62
+ updated_issue = core.issues.update(issue_id, github_issue=None)
63
+
64
+ if updated_issue:
65
+ extra_details = {
66
+ "GitHub Issue": f"#{github_id}",
67
+ "Local Issue": issue_id,
68
+ }
69
+ lines = format_operation_success(
70
+ emoji="✅",
71
+ action="Unlinked",
72
+ entity_title=updated_issue.title,
73
+ entity_id=issue_id,
74
+ extra_details=extra_details,
75
+ )
76
+ for line in lines:
77
+ console.print(
78
+ line, style="bold green" if "Unlinked" in line else "cyan"
79
+ )
80
+ else:
81
+ lines = format_operation_failure(
82
+ action="unlink",
83
+ entity_id=issue_id,
84
+ error="Failed to update issue",
85
+ )
86
+ for line in lines:
87
+ console.print(line, style="bold red")
88
+
89
+ except Exception as e:
90
+ handle_cli_error(
91
+ error=e,
92
+ operation="unlink_github_issue",
93
+ entity_type="issue",
94
+ entity_id=issue_id,
95
+ context={},
96
+ fatal=True,
97
+ )
98
+ lines = format_operation_failure(
99
+ action="unlink",
100
+ entity_id=issue_id,
101
+ error=str(e),
102
+ )
103
+ for line in lines:
104
+ console.print(line, style="bold red")
@@ -0,0 +1,120 @@
1
+ """Update 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 BaseUpdate, EntityType
7
+ from roadmap.adapters.cli.crud.entity_builders import IssueBuilder
8
+ from roadmap.adapters.cli.presentation.crud_presenter import UpdatePresenter
9
+ from roadmap.common.logging import (
10
+ log_command,
11
+ )
12
+ from roadmap.common.models import IssueUpdateParams
13
+
14
+
15
+ class IssueUpdate(BaseUpdate):
16
+ """Update issue command implementation."""
17
+
18
+ entity_type = EntityType.ISSUE
19
+
20
+ def __init__(self, core):
21
+ """Initialize IssueUpdate."""
22
+ super().__init__(core)
23
+ self._current_update_dict = {}
24
+ self._current_reason = None
25
+
26
+ def build_update_dict(self, entity_id: str, **kwargs) -> dict:
27
+ """Build update dictionary for issue."""
28
+ return IssueBuilder.build_update_dict(
29
+ title=kwargs.get("title"),
30
+ priority=kwargs.get("priority"),
31
+ status=kwargs.get("status"),
32
+ assignee=kwargs.get("assignee"),
33
+ milestone=kwargs.get("milestone"),
34
+ description=kwargs.get("description"),
35
+ estimate=kwargs.get("estimate"),
36
+ )
37
+
38
+ def execute(self, entity_id: str, **kwargs):
39
+ """Execute update and display results using proper formatter.
40
+
41
+ Overrides parent to store update_dict and reason for display.
42
+ """
43
+ # Store for use in _display_success
44
+ self._current_update_dict = self.build_update_dict(
45
+ entity_id=entity_id, **kwargs
46
+ )
47
+ self._current_reason = kwargs.get("reason")
48
+
49
+ # Call parent execute which will call _display_success
50
+ return super().execute(entity_id=entity_id, **kwargs)
51
+
52
+ def _display_success(self, entity) -> None:
53
+ """Display success message using presenter."""
54
+ presenter = UpdatePresenter()
55
+ presenter.render(entity, self._current_update_dict)
56
+
57
+
58
+ @click.command("update")
59
+ @click.argument("issue_id")
60
+ @click.option("--title", help="Update issue title")
61
+ @click.option(
62
+ "--priority",
63
+ "-p",
64
+ type=click.Choice(["critical", "high", "medium", "low"]),
65
+ help="Update priority",
66
+ )
67
+ @click.option(
68
+ "--status",
69
+ "-s",
70
+ type=click.Choice(["todo", "in-progress", "blocked", "review", "closed"]),
71
+ help="Update status",
72
+ )
73
+ @click.option("--assignee", "-a", help="Update assignee")
74
+ @click.option("--milestone", "-m", help="Update milestone")
75
+ @click.option("--description", "-d", help="Update description")
76
+ @click.option("--estimate", "-e", type=float, help="Update estimated time (in hours)")
77
+ @click.option("--reason", "-r", help="Reason for the update")
78
+ @click.pass_context
79
+ @log_command("issue_update", entity_type="issue", track_duration=True)
80
+ @require_initialized
81
+ def update_issue(
82
+ ctx: click.Context,
83
+ issue_id: str,
84
+ title: str,
85
+ priority: str,
86
+ status: str,
87
+ assignee: str,
88
+ milestone: str,
89
+ description: str,
90
+ estimate: float,
91
+ reason: str,
92
+ ):
93
+ """Update an existing issue."""
94
+ core = ctx.obj["core"]
95
+ updater = IssueUpdate(core)
96
+
97
+ # Create structured parameter object
98
+ params = IssueUpdateParams(
99
+ issue_id=issue_id,
100
+ title=title,
101
+ priority=priority,
102
+ status=status,
103
+ assignee=assignee,
104
+ milestone=milestone,
105
+ content=description,
106
+ estimate=estimate,
107
+ reason=reason,
108
+ )
109
+
110
+ updater.execute(
111
+ entity_id=params.issue_id,
112
+ title=params.title,
113
+ priority=params.priority,
114
+ status=params.status,
115
+ assignee=params.assignee,
116
+ milestone=params.milestone,
117
+ content=params.content,
118
+ estimate=params.estimate,
119
+ reason=params.reason,
120
+ )
@@ -0,0 +1,36 @@
1
+ """View issue command."""
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.mappers import IssueMapper
10
+ from roadmap.adapters.cli.presentation.issue_presenter import IssuePresenter
11
+
12
+
13
+ @click.command("view")
14
+ @click.argument("issue_id")
15
+ @click.pass_context
16
+ @require_initialized
17
+ def view_issue(ctx: click.Context, issue_id: str):
18
+ """Display detailed information about a specific issue.
19
+
20
+ Shows complete issue details including metadata, timeline, description,
21
+ and acceptance criteria in a formatted view.
22
+
23
+ Example:
24
+ roadmap issue view abc123def
25
+ """
26
+ core = ctx.obj["core"]
27
+
28
+ # Retrieve the domain issue from the repository
29
+ issue = ensure_entity_exists(core, "issue", issue_id)
30
+
31
+ # Convert domain issue to DTO for presentation
32
+ issue_dto = IssueMapper.domain_to_dto(issue)
33
+
34
+ # Use presenter to render the issue
35
+ presenter = IssuePresenter()
36
+ presenter.render(issue_dto)