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,155 @@
1
+ """Base class for delete operations across all entity types."""
2
+
3
+ from abc import ABC
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+ from roadmap.adapters.cli.crud.crud_helpers import EntityType
9
+ from roadmap.adapters.cli.crud.crud_utils import (
10
+ delete_entity_by_type,
11
+ format_entity_not_found_error,
12
+ get_entity_by_type,
13
+ get_entity_title,
14
+ )
15
+ from roadmap.adapters.cli.presentation.crud_presenter import DeletePresenter
16
+ from roadmap.common.console import get_console
17
+
18
+
19
+ class BaseDelete(ABC):
20
+ """Abstract base class for entity delete commands.
21
+
22
+ Subclasses implement:
23
+ - entity_type: The EntityType this deletes
24
+
25
+ Uses Template Method pattern for extensibility.
26
+ """
27
+
28
+ entity_type: EntityType
29
+
30
+ def __init__(self, core: Any, console: Any = None) -> None:
31
+ """Initialize delete command.
32
+
33
+ Args:
34
+ core: The core application context
35
+ console: Optional console for output (uses default if not provided)
36
+ """
37
+ self.core = core
38
+ self.console = console or get_console()
39
+
40
+ def post_delete_hook(self, entity_id: str, **kwargs) -> None: # noqa: B027
41
+ """Handle entity deletion completion with optional custom logic.
42
+
43
+ Override to handle cleanup, notifications, etc.
44
+
45
+ Args:
46
+ entity_id: ID of deleted entity
47
+ **kwargs: Original CLI arguments
48
+ """
49
+ pass
50
+
51
+ def execute(self, entity_id: str, force: bool = False, **kwargs) -> bool:
52
+ """Execute the delete operation.
53
+
54
+ Args:
55
+ entity_id: ID of entity to delete
56
+ force: Whether to force deletion without confirmation
57
+ **kwargs: All other CLI arguments
58
+
59
+ Returns:
60
+ True if deletion succeeded, False otherwise
61
+ """
62
+ try:
63
+ # Verify entity exists
64
+ entity = self._get_entity(entity_id)
65
+ if entity is None:
66
+ self.console.print(
67
+ format_entity_not_found_error(self.entity_type, entity_id),
68
+ style="red",
69
+ )
70
+ return False
71
+
72
+ # Request confirmation if not forced
73
+ if not force:
74
+ title = self._get_title(entity)
75
+ if not click.confirm(
76
+ f"Delete {self.entity_type.value} '{title}'? This cannot be undone."
77
+ ):
78
+ self.console.print("⚠️ Delete cancelled", style="yellow")
79
+ return False
80
+
81
+ # Delete via appropriate service
82
+ if not self._delete_entity(entity_id):
83
+ return False
84
+
85
+ # Run post-delete hooks
86
+ self.post_delete_hook(entity_id, **kwargs)
87
+
88
+ # Display success
89
+ self._display_success(entity_id, entity)
90
+
91
+ return True
92
+
93
+ except Exception as e:
94
+ self.console.print(
95
+ f"❌ Failed to delete {self.entity_type.value}: {str(e)}",
96
+ style="red",
97
+ )
98
+ raise click.ClickException(str(e)) from e
99
+
100
+ def _get_entity(self, entity_id: str) -> Any | None:
101
+ """Get entity by ID.
102
+
103
+ Args:
104
+ entity_id: Entity ID to retrieve
105
+
106
+ Returns:
107
+ Entity object or None if not found
108
+ """
109
+ return get_entity_by_type(self.core, self.entity_type, entity_id)
110
+
111
+ def _delete_entity(self, entity_id: str) -> bool:
112
+ """Delete entity via appropriate service.
113
+
114
+ Args:
115
+ entity_id: Entity ID to delete
116
+
117
+ Returns:
118
+ True if deletion succeeded
119
+ """
120
+ try:
121
+ delete_entity_by_type(self.core, self.entity_type, entity_id)
122
+ return True
123
+ except Exception as e:
124
+ from roadmap.common.logging import get_logger
125
+
126
+ logger = get_logger(__name__)
127
+ logger.debug(
128
+ "entity_deletion_failed",
129
+ entity_id=entity_id,
130
+ entity_type=self.entity_type,
131
+ error=str(e),
132
+ action="delete_entity",
133
+ )
134
+ return False
135
+
136
+ def _display_success(self, entity_id: str, entity: Any) -> None:
137
+ """Display success message.
138
+
139
+ Args:
140
+ entity_id: ID of deleted entity
141
+ entity: The deleted entity
142
+ """
143
+ presenter = DeletePresenter()
144
+ presenter.render(entity)
145
+
146
+ def _get_title(self, entity: Any) -> str:
147
+ """Get entity title/name.
148
+
149
+ Args:
150
+ entity: The entity object
151
+
152
+ Returns:
153
+ Title or name string
154
+ """
155
+ return get_entity_title(entity)
@@ -0,0 +1,283 @@
1
+ """Base class for restore operations across all entity types."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import click
8
+
9
+ from roadmap.adapters.cli.crud.crud_helpers import (
10
+ EntityType,
11
+ display_restore_success,
12
+ get_active_dir,
13
+ get_archive_dir,
14
+ )
15
+ from roadmap.common.console import get_console
16
+
17
+
18
+ class BaseRestore(ABC):
19
+ """Abstract base class for entity restore commands using Template Method pattern.
20
+
21
+ Subclasses must implement:
22
+ - entity_type: The EntityType this restores
23
+ - get_archived_files_to_restore(): Determine which archived files to restore
24
+ - post_restore_hook(): Update database and handle entity-specific logic
25
+
26
+ Subclasses may override:
27
+ - validate_entity_before_restore(): Custom validation before restoring
28
+ - check_conflict(): Custom conflict detection logic
29
+
30
+ This class handles the overall restore workflow:
31
+ 1. Find archived files matching criteria
32
+ 2. Validate before restoring
33
+ 3. Check for conflicts with active entities
34
+ 4. Move files from archive to active
35
+ 5. Update state via post_restore_hook
36
+ 6. Display results
37
+ """
38
+
39
+ entity_type: EntityType
40
+
41
+ def __init__(self, core: Any, console: Any = None) -> None:
42
+ """Initialize restore command.
43
+
44
+ Args:
45
+ core: The core application context
46
+ console: Optional console for output (uses default if not provided)
47
+ """
48
+ self.core = core
49
+ self.console = console or get_console()
50
+
51
+ @abstractmethod
52
+ def get_archived_files_to_restore(
53
+ self, entity_id: str | None = None, **filters
54
+ ) -> list[Path]:
55
+ """Determine which archived files should be restored.
56
+
57
+ Args:
58
+ entity_id: Optional specific entity ID to restore
59
+ **filters: Entity-specific filter criteria
60
+
61
+ Returns:
62
+ List of archived file paths to restore
63
+ """
64
+
65
+ @abstractmethod
66
+ def post_restore_hook(self, restored_files: list[Path], **kwargs) -> None:
67
+ """Handle entity-specific state updates after restoring.
68
+
69
+ Override to update database, send notifications, etc.
70
+
71
+ Args:
72
+ restored_files: List of files that were restored
73
+ **kwargs: Original CLI arguments
74
+ """
75
+
76
+ def validate_entity_before_restore(
77
+ self, file_path: Path
78
+ ) -> tuple[bool, str | None]:
79
+ """Validate entity before restoring.
80
+
81
+ Allows subclasses to add entity-specific validation rules.
82
+
83
+ Args:
84
+ file_path: The file being considered for restore
85
+
86
+ Returns:
87
+ Tuple of (is_valid, error_message_or_none)
88
+ """
89
+ return True, None
90
+
91
+ def check_conflict(self, archived_file: Path) -> tuple[bool, str | None]:
92
+ """Check if restoring would cause a conflict.
93
+
94
+ Override for custom conflict detection.
95
+
96
+ Args:
97
+ archived_file: The file being restored
98
+
99
+ Returns:
100
+ Tuple of (has_conflict, conflict_description)
101
+ """
102
+ active_dir = get_active_dir(self.entity_type)
103
+ active_file = active_dir / archived_file.name
104
+ if active_file.exists():
105
+ return True, f"already exists in active {self.entity_type.value}s"
106
+ return False, None
107
+
108
+ def _validate_files_for_restore(
109
+ self, files_to_restore: list[Path]
110
+ ) -> tuple[list[Path], list[tuple[Path, str | None]]]:
111
+ """Validate files before restoration.
112
+
113
+ Args:
114
+ files_to_restore: List of files to validate
115
+
116
+ Returns:
117
+ Tuple of (valid_files, invalid_files_with_reasons)
118
+ """
119
+ invalid_files = []
120
+
121
+ for file_path in files_to_restore:
122
+ is_valid, error_msg = self.validate_entity_before_restore(file_path)
123
+ if not is_valid:
124
+ invalid_files.append((file_path, error_msg))
125
+
126
+ valid_files = [
127
+ f for f in files_to_restore if f not in [f[0] for f in invalid_files]
128
+ ]
129
+
130
+ return valid_files, invalid_files
131
+
132
+ def _check_files_for_conflicts(
133
+ self, files_to_restore: list[Path]
134
+ ) -> tuple[list[Path], list[tuple[Path, str | None]]]:
135
+ """Check files for conflicts with active entities.
136
+
137
+ Args:
138
+ files_to_restore: List of files to check
139
+
140
+ Returns:
141
+ Tuple of (non_conflicting_files, conflicting_files_with_reasons)
142
+ """
143
+ conflicts = []
144
+
145
+ for file_path in files_to_restore:
146
+ has_conflict, conflict_desc = self.check_conflict(file_path)
147
+ if has_conflict:
148
+ conflicts.append((file_path, conflict_desc))
149
+
150
+ non_conflicting = [
151
+ f for f in files_to_restore if f not in [f[0] for f in conflicts]
152
+ ]
153
+
154
+ return non_conflicting, conflicts
155
+
156
+ def _perform_file_restore(
157
+ self, files_to_restore: list[Path]
158
+ ) -> tuple[list[Path], int]:
159
+ """Move files from archive to active directory.
160
+
161
+ Args:
162
+ files_to_restore: List of files to restore
163
+
164
+ Returns:
165
+ Tuple of (successfully_restored_files, failed_count)
166
+ """
167
+ active_dir = get_active_dir(self.entity_type)
168
+ active_dir.mkdir(parents=True, exist_ok=True)
169
+
170
+ restored_files = []
171
+ failed_count = 0
172
+
173
+ for archive_file in files_to_restore:
174
+ try:
175
+ active_file = active_dir / archive_file.name
176
+ active_file.write_text(archive_file.read_text())
177
+ archive_file.unlink()
178
+ restored_files.append(active_file)
179
+ except Exception as e:
180
+ self.console.print(
181
+ f"⚠️ Failed to restore {archive_file.name}: {str(e)}",
182
+ style="yellow",
183
+ )
184
+ failed_count += 1
185
+
186
+ return restored_files, failed_count
187
+
188
+ def _display_validation_errors(
189
+ self, invalid_files: list[tuple[Path, str | None]]
190
+ ) -> None:
191
+ """Display validation errors to user.
192
+
193
+ Args:
194
+ invalid_files: List of files with validation errors
195
+ """
196
+ for file_path, error_msg in invalid_files:
197
+ self.console.print(
198
+ f"⚠️ Cannot restore {file_path.name}: {error_msg}",
199
+ style="yellow",
200
+ )
201
+
202
+ def _display_conflicts(self, conflicts: list[tuple[Path, str | None]]) -> None:
203
+ """Display conflict errors to user.
204
+
205
+ Args:
206
+ conflicts: List of conflicting files
207
+ """
208
+ for file_path, conflict_desc in conflicts:
209
+ self.console.print(
210
+ f"⚠️ Cannot restore {file_path.name}: {conflict_desc}",
211
+ style="yellow",
212
+ )
213
+
214
+ def execute(self, entity_id: str | None = None, **kwargs) -> bool:
215
+ """Execute restore operation (Template Method).
216
+
217
+ Args:
218
+ entity_id: Optional specific entity ID to restore
219
+ **kwargs: Entity-specific filters and options
220
+
221
+ Returns:
222
+ True if restore succeeded
223
+ """
224
+ try:
225
+ # Step 1: Find archived files matching criteria
226
+ archive_dir = get_archive_dir(self.entity_type)
227
+
228
+ if not archive_dir.exists():
229
+ self.console.print(
230
+ f"No archive found for {self.entity_type.value}s",
231
+ style="yellow",
232
+ )
233
+ return False
234
+
235
+ files_to_restore = self.get_archived_files_to_restore(entity_id, **kwargs)
236
+
237
+ if not files_to_restore:
238
+ self.console.print(
239
+ f"No archived {self.entity_type.value}s found to restore",
240
+ style="yellow",
241
+ )
242
+ return False
243
+
244
+ # Step 2: Validate files
245
+ files_to_restore, invalid_files = self._validate_files_for_restore(
246
+ files_to_restore
247
+ )
248
+ if invalid_files:
249
+ self._display_validation_errors(invalid_files)
250
+ if not files_to_restore:
251
+ return False
252
+
253
+ # Step 3: Check for conflicts
254
+ files_to_restore, conflicts = self._check_files_for_conflicts(
255
+ files_to_restore
256
+ )
257
+ if conflicts:
258
+ self._display_conflicts(conflicts)
259
+ if not files_to_restore:
260
+ return False
261
+
262
+ # Step 4: Restore files
263
+ restored_files, failed_count = self._perform_file_restore(files_to_restore)
264
+
265
+ # Step 5: Update state via hook
266
+ self.post_restore_hook(restored_files, **kwargs)
267
+
268
+ # Step 6: Display results
269
+ display_restore_success(
270
+ self.entity_type,
271
+ len(restored_files),
272
+ failed_count,
273
+ console=self.console,
274
+ )
275
+
276
+ return True
277
+
278
+ except Exception as e:
279
+ self.console.print(
280
+ f"❌ Restore operation failed: {str(e)}",
281
+ style="red",
282
+ )
283
+ raise click.ClickException(str(e)) from e
@@ -0,0 +1,193 @@
1
+ """Base class for update operations across all entity types."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+ from roadmap.adapters.cli.crud.crud_helpers import EntityType
9
+ from roadmap.adapters.cli.crud.crud_utils import (
10
+ format_entity_not_found_error,
11
+ get_entity_by_type,
12
+ get_entity_id,
13
+ get_entity_title,
14
+ update_entity_by_type,
15
+ )
16
+ from roadmap.adapters.cli.presentation.crud_presenter import UpdatePresenter
17
+ from roadmap.common.console import get_console
18
+ from roadmap.common.errors.exceptions import ValidationError
19
+ from roadmap.common.logging import log_audit_event
20
+
21
+
22
+ class BaseUpdate(ABC):
23
+ """Abstract base class for entity update commands.
24
+
25
+ Subclasses implement:
26
+ - entity_type: The EntityType this updates
27
+ - build_update_dict(): Convert CLI args to update dict
28
+ - post_update_hook(): Optional hook after update (default: no-op)
29
+
30
+ Uses Template Method pattern for extensibility.
31
+ """
32
+
33
+ entity_type: EntityType
34
+
35
+ def __init__(self, core: Any, console: Any = None) -> None:
36
+ """Initialize update command.
37
+
38
+ Args:
39
+ core: The core application context
40
+ console: Optional console for output (uses default if not provided)
41
+ """
42
+ self.core = core
43
+ self.console = console or get_console()
44
+
45
+ @abstractmethod
46
+ def build_update_dict(self, entity_id: str, **kwargs) -> dict[str, Any]:
47
+ """Convert CLI arguments to update dictionary.
48
+
49
+ Args:
50
+ entity_id: ID of entity to update
51
+ **kwargs: All click option values
52
+
53
+ Returns:
54
+ Dictionary of fields to update
55
+
56
+ Raises:
57
+ ValidationError: If arguments are invalid
58
+ """
59
+ ...
60
+
61
+ def post_update_hook( # noqa: B027
62
+ self, entity: Any, update_dict: dict[str, Any], **kwargs
63
+ ) -> None:
64
+ """Handle entity update completion with optional custom logic.
65
+
66
+ Override to handle:
67
+ - Git branch updates
68
+ - Milestone reassignments
69
+ - Custom notifications
70
+
71
+ Args:
72
+ entity: The updated entity
73
+ update_dict: The update dict that was applied
74
+ **kwargs: Original CLI arguments
75
+ """
76
+ ...
77
+
78
+ def execute(self, entity_id: str, **kwargs) -> Any | None:
79
+ """Execute the update operation.
80
+
81
+ Args:
82
+ entity_id: ID of entity to update
83
+ **kwargs: All other CLI arguments
84
+
85
+ Returns:
86
+ Updated entity, or None if update failed
87
+ """
88
+ try:
89
+ # Verify entity exists
90
+ entity = self._get_entity(entity_id)
91
+ if entity is None:
92
+ self.console.print(
93
+ format_entity_not_found_error(self.entity_type, entity_id),
94
+ style="red",
95
+ )
96
+ raise click.ClickException(f"{self.entity_type.value} not found")
97
+
98
+ # Build update dict from arguments
99
+ update_dict = self.build_update_dict(entity_id=entity_id, **kwargs)
100
+
101
+ # Update via appropriate service
102
+ updated_entity = self._update_entity(entity_id, update_dict)
103
+ if updated_entity is None:
104
+ return None
105
+
106
+ # Run post-update hooks
107
+ self.post_update_hook(updated_entity, update_dict, **kwargs)
108
+
109
+ # Log audit event for successful update
110
+ entity_id = self._get_id(updated_entity)
111
+ log_audit_event(
112
+ action="update",
113
+ entity_type=self.entity_type.value,
114
+ entity_id=entity_id,
115
+ before={"entity_id": entity_id}, # Original only tracked by entity_id
116
+ after=update_dict,
117
+ )
118
+
119
+ # Display success
120
+ self._display_success(updated_entity)
121
+
122
+ return updated_entity
123
+
124
+ except ValidationError:
125
+ # ValidationError will be handled by centralized CLI exception handler
126
+ # which formats it and directs to stderr
127
+ raise
128
+ except Exception:
129
+ # Other exceptions will also be handled by centralized handler
130
+ raise
131
+
132
+ def _get_entity(self, entity_id: str) -> Any | None:
133
+ """Get entity by ID.
134
+
135
+ Args:
136
+ entity_id: Entity ID to retrieve
137
+
138
+ Returns:
139
+ Entity object or None if not found
140
+ """
141
+ return get_entity_by_type(self.core, self.entity_type, entity_id)
142
+
143
+ def _update_entity(self, entity_id: str, update_dict: dict[str, Any]) -> Any | None:
144
+ """Update entity via appropriate service.
145
+
146
+ Args:
147
+ entity_id: Entity ID to update
148
+ update_dict: Dictionary of fields to update
149
+
150
+ Returns:
151
+ Updated entity or None if failed
152
+ """
153
+ result = update_entity_by_type(
154
+ self.core, self.entity_type, entity_id, update_dict
155
+ )
156
+
157
+ # Some coordinator update methods (e.g., milestones) return bool instead
158
+ # of the updated entity. Normalize by fetching the entity after success.
159
+ if isinstance(result, bool):
160
+ return self._get_entity(entity_id) if result else None
161
+
162
+ return result
163
+
164
+ def _display_success(self, entity: Any) -> None:
165
+ """Display success message.
166
+
167
+ Args:
168
+ entity: The updated entity
169
+ """
170
+ presenter = UpdatePresenter()
171
+ presenter.render(entity)
172
+
173
+ def _get_title(self, entity: Any) -> str:
174
+ """Get entity title/name.
175
+
176
+ Args:
177
+ entity: The entity object
178
+
179
+ Returns:
180
+ Title or name string
181
+ """
182
+ return get_entity_title(entity)
183
+
184
+ def _get_id(self, entity: Any) -> str:
185
+ """Get entity ID.
186
+
187
+ Args:
188
+ entity: The entity object
189
+
190
+ Returns:
191
+ Entity ID string
192
+ """
193
+ return get_entity_id(entity)