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,198 @@
1
+ """Configuration management commands."""
2
+
3
+ import click
4
+ from structlog import get_logger
5
+
6
+ from roadmap.common.configuration import ConfigLoader
7
+ from roadmap.common.console import get_console
8
+
9
+ logger = get_logger()
10
+ console = get_console()
11
+
12
+
13
+ @click.group("config")
14
+ def config():
15
+ """Manage roadmap configuration."""
16
+
17
+
18
+ @config.command("view")
19
+ @click.option(
20
+ "--project",
21
+ is_flag=True,
22
+ help="Show project-level config instead of user-level",
23
+ )
24
+ @click.option(
25
+ "--level",
26
+ type=click.Choice(["user", "project", "merged"], case_sensitive=False),
27
+ default="merged",
28
+ help="Which config level to view (default: merged)",
29
+ )
30
+ def view(project: bool, level: str):
31
+ """View current configuration."""
32
+ # Handle legacy --project flag
33
+ if project:
34
+ level = "project"
35
+
36
+ try:
37
+ if level == "user":
38
+ config_obj = ConfigLoader.load_user_config()
39
+ source = "User-level config (~/.roadmap/config.yaml)"
40
+ elif level == "project":
41
+ config_obj = ConfigLoader.load_project_config()
42
+ source = "Project-level config (.roadmap/config.yaml)"
43
+ else: # merged
44
+ config_obj = ConfigLoader.load_config()
45
+ source = "Merged configuration (defaults + user + project)"
46
+
47
+ if config_obj is None and level != "merged":
48
+ console.print(f"[yellow]No {level} configuration file found.[/yellow]")
49
+ return
50
+
51
+ console.print(f"\n[bold blue]{source}[/bold blue]")
52
+ console.print("=" * 60)
53
+
54
+ if config_obj:
55
+ # Pretty-print as YAML-like structure
56
+ data = config_obj.model_dump(exclude_none=True)
57
+ _print_config_dict(data, indent=0)
58
+ else:
59
+ console.print("[dim]Using all defaults[/dim]")
60
+
61
+ console.print()
62
+
63
+ except Exception as e:
64
+ console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
65
+ raise click.ClickException(str(e)) from e
66
+
67
+
68
+ @config.command("get")
69
+ @click.argument("key")
70
+ def get_cmd(key: str):
71
+ """Get a configuration value by key (e.g., 'export.directory')."""
72
+ try:
73
+ value = ConfigLoader.get_config_value(key)
74
+ if value is None:
75
+ console.print(
76
+ f"[yellow]Configuration key '{key}' not found.[/yellow]",
77
+ style="yellow",
78
+ )
79
+ else:
80
+ console.print(f"[cyan]{key}[/cyan]: {value}")
81
+ except Exception as e:
82
+ console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
83
+ raise click.ClickException(str(e)) from e
84
+
85
+
86
+ @config.command("set")
87
+ @click.argument("key")
88
+ @click.argument("value")
89
+ @click.option(
90
+ "--project",
91
+ is_flag=True,
92
+ help="Set in project-level config instead of user-level",
93
+ )
94
+ def set_cmd(key: str, value: str, project: bool):
95
+ """Set a configuration value by key (e.g., 'export.format json')."""
96
+ try:
97
+ # Try to parse value as boolean or other types
98
+ parsed_value = _parse_config_value(value)
99
+
100
+ success = ConfigLoader.set_config_value(
101
+ key, parsed_value, project_level=project
102
+ )
103
+
104
+ if success:
105
+ level = "project" if project else "user"
106
+ console.print(
107
+ f"[green]✓[/green] Set [cyan]{key}[/cyan] = {parsed_value} in {level} config"
108
+ )
109
+ else:
110
+ console.print(f"[red]✗[/red] Failed to set {key}", style="bold red")
111
+ raise click.ClickException(f"Failed to set {key}")
112
+
113
+ except Exception as e:
114
+ console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
115
+ raise click.ClickException(str(e)) from e
116
+
117
+
118
+ @config.command("reset")
119
+ @click.option(
120
+ "--project",
121
+ is_flag=True,
122
+ help="Reset project-level config instead of user-level",
123
+ )
124
+ @click.confirmation_option(prompt="Are you sure you want to reset the configuration?")
125
+ def reset(project: bool):
126
+ """Reset configuration to defaults."""
127
+ try:
128
+ if project:
129
+ path = ConfigLoader.get_project_config_path()
130
+ path.unlink(missing_ok=True)
131
+ console.print(
132
+ "[green]✓[/green] Reset project-level configuration", style="green"
133
+ )
134
+ else:
135
+ path = ConfigLoader.get_user_config_path()
136
+ path.unlink(missing_ok=True)
137
+ console.print(
138
+ "[green]✓[/green] Reset user-level configuration", style="green"
139
+ )
140
+ except Exception as e:
141
+ console.print(f"[bold red]Error:[/bold red] {str(e)}", style="bold red")
142
+ raise click.ClickException(str(e)) from e
143
+
144
+
145
+ def _print_config_dict(data: dict, indent: int = 0):
146
+ """Pretty-print configuration dictionary.
147
+
148
+ Args:
149
+ data: Dictionary to print
150
+ indent: Current indentation level
151
+ """
152
+ for key, value in data.items():
153
+ if isinstance(value, dict):
154
+ console.print(f"{' ' * indent}[bold]{key}:[/bold]")
155
+ _print_config_dict(value, indent + 1)
156
+ else:
157
+ console.print(f"{' ' * indent}[cyan]{key}[/cyan]: {value}")
158
+
159
+
160
+ def _parse_config_value(value: str):
161
+ """Parse string value to appropriate Python type.
162
+
163
+ Args:
164
+ value: String value to parse
165
+
166
+ Returns:
167
+ Parsed value (bool, int, float, or str)
168
+ """
169
+ # Try boolean
170
+ if value.lower() in ("true", "yes", "on"):
171
+ return True
172
+ if value.lower() in ("false", "no", "off"):
173
+ return False
174
+
175
+ # Try integer
176
+ try:
177
+ return int(value)
178
+ except ValueError as e:
179
+ logger.debug(
180
+ "int_conversion_failed",
181
+ value=value,
182
+ error=str(e),
183
+ action="parse_config_value",
184
+ )
185
+
186
+ # Try float
187
+ try:
188
+ return float(value)
189
+ except ValueError as e:
190
+ logger.debug(
191
+ "float_conversion_failed",
192
+ value=value,
193
+ error=str(e),
194
+ action="parse_config_value",
195
+ )
196
+
197
+ # Return as string
198
+ return value
@@ -0,0 +1,10 @@
1
+ """Utility functions for CLI operations.
2
+
3
+ This module re-exports core utilities from roadmap.common to maintain
4
+ backward compatibility with existing CLI code.
5
+ """
6
+
7
+ # Re-export console utilities from common module to avoid duplication
8
+ from roadmap.common.console import get_console, is_testing_environment
9
+
10
+ __all__ = ["get_console", "is_testing_environment"]
@@ -0,0 +1,26 @@
1
+ """Core CLI commands: init and status.
2
+
3
+ These are the fundamental commands needed to get started with Roadmap.
4
+ """
5
+
6
+ import click
7
+
8
+ from roadmap.adapters.cli.init import init
9
+ from roadmap.adapters.cli.status import health, status
10
+ from roadmap.common.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ @click.group()
16
+ @click.pass_context
17
+ def core(ctx: click.Context) -> None:
18
+ """Core roadmap commands for initialization and status."""
19
+ if ctx.obj is None:
20
+ ctx.obj = {}
21
+
22
+
23
+ # Register commands
24
+ core.add_command(init)
25
+ core.add_command(status)
26
+ core.add_command(health)
@@ -0,0 +1,47 @@
1
+ """CRUD operations base classes and shared utilities.
2
+
3
+ This module provides:
4
+ - Base classes for Create, Update, Delete, Archive, Restore operations
5
+ - Shared CRUD helpers and utilities
6
+ - Entity-specific builders and validators
7
+ - Template method pattern for extensible CRUD implementations
8
+ """
9
+
10
+ from roadmap.adapters.cli.crud.base_archive import BaseArchive
11
+ from roadmap.adapters.cli.crud.base_create import BaseCreate
12
+ from roadmap.adapters.cli.crud.base_delete import BaseDelete
13
+ from roadmap.adapters.cli.crud.base_restore import BaseRestore
14
+ from roadmap.adapters.cli.crud.base_update import BaseUpdate
15
+ from roadmap.adapters.cli.crud.crud_helpers import (
16
+ CRUDOperation,
17
+ EntityType,
18
+ collect_archive_files,
19
+ display_archive_success,
20
+ display_restore_success,
21
+ format_entity_id,
22
+ format_entity_title,
23
+ get_active_dir,
24
+ get_archive_dir,
25
+ validate_entity_exists,
26
+ )
27
+
28
+ __all__ = [
29
+ # Enums
30
+ "EntityType",
31
+ "CRUDOperation",
32
+ # Base classes
33
+ "BaseCreate",
34
+ "BaseUpdate",
35
+ "BaseDelete",
36
+ "BaseArchive",
37
+ "BaseRestore",
38
+ # Helpers
39
+ "validate_entity_exists",
40
+ "format_entity_title",
41
+ "format_entity_id",
42
+ "collect_archive_files",
43
+ "get_archive_dir",
44
+ "get_active_dir",
45
+ "display_archive_success",
46
+ "display_restore_success",
47
+ ]
@@ -0,0 +1,208 @@
1
+ """Base class for archive 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_archive_success,
12
+ get_active_dir,
13
+ get_archive_dir,
14
+ )
15
+ from roadmap.common.console import get_console
16
+
17
+
18
+ class BaseArchive(ABC):
19
+ """Abstract base class for entity archive commands using Template Method pattern.
20
+
21
+ Subclasses must implement:
22
+ - entity_type: The EntityType this archives
23
+ - get_entities_to_archive(): Determine what entities to archive based on filters
24
+ - post_archive_hook(): Update database and handle entity-specific logic
25
+
26
+ Subclasses may override:
27
+ - validate_entity_before_archive(): Custom validation before archiving
28
+ - find_entity_files(): Custom file finding logic (default: filesystem search)
29
+
30
+ This class handles the overall archive workflow:
31
+ 1. Determine entities to archive (via get_entities_to_archive)
32
+ 2. Find corresponding files
33
+ 3. Move files to archive
34
+ 4. Update state via post_archive_hook
35
+ 5. Display results
36
+ """
37
+
38
+ entity_type: EntityType
39
+
40
+ def __init__(self, core: Any, console: Any = None) -> None:
41
+ """Initialize archive command.
42
+
43
+ Args:
44
+ core: The core application context
45
+ console: Optional console for output (uses default if not provided)
46
+ """
47
+ self.core = core
48
+ self.console = console or get_console()
49
+
50
+ @abstractmethod
51
+ def get_entities_to_archive(
52
+ self, entity_id: str | None = None, **filters
53
+ ) -> list[Any]:
54
+ """Determine which entities should be archived.
55
+
56
+ Args:
57
+ entity_id: Optional specific entity ID to archive
58
+ **filters: Entity-specific filter criteria (e.g., all_closed, orphaned)
59
+
60
+ Returns:
61
+ List of entities to archive
62
+ """
63
+
64
+ @abstractmethod
65
+ def post_archive_hook(
66
+ self, archived_files: list[Path], entities: list[Any], **kwargs
67
+ ) -> None:
68
+ """Handle entity-specific state updates after archiving.
69
+
70
+ Override to update database, send notifications, etc.
71
+
72
+ Args:
73
+ archived_files: List of files that were archived
74
+ entities: List of entities that were archived
75
+ **kwargs: Original CLI arguments
76
+ """
77
+
78
+ def validate_entity_before_archive(
79
+ self, entity: Any, **kwargs
80
+ ) -> tuple[bool, str | None]:
81
+ """Validate entity before archiving.
82
+
83
+ Allows subclasses to add entity-specific validation rules.
84
+
85
+ Args:
86
+ entity: The entity being considered for archive
87
+ **kwargs: Original CLI arguments (e.g., force flag)
88
+
89
+ Returns:
90
+ Tuple of (is_valid, error_message_or_none)
91
+ """
92
+ return True, None
93
+
94
+ def find_entity_files(self, entities: list[Any]) -> list[Path]:
95
+ """Find file paths corresponding to entities.
96
+
97
+ Override if entity-to-file mapping is non-standard.
98
+
99
+ Args:
100
+ entities: List of entities to find files for
101
+
102
+ Returns:
103
+ List of file paths
104
+ """
105
+ active_dir = get_active_dir(self.entity_type)
106
+ files = []
107
+ for entity in entities:
108
+ # Search by entity ID prefix
109
+ matching = list(active_dir.rglob(f"{str(entity.id)[:8]}*.md"))
110
+ if matching:
111
+ files.append(matching[0])
112
+ return files
113
+
114
+ def execute(self, entity_id: str | None = None, **kwargs) -> bool:
115
+ """Execute archive operation (Template Method).
116
+
117
+ Args:
118
+ entity_id: Optional specific entity ID to archive
119
+ **kwargs: Entity-specific filters and options
120
+
121
+ Returns:
122
+ True if archive succeeded
123
+ """
124
+ try:
125
+ # Step 1: Determine which entities to archive
126
+ entities = self.get_entities_to_archive(entity_id, **kwargs)
127
+
128
+ if not entities:
129
+ self.console.print(
130
+ f"No {self.entity_type.value}s to archive",
131
+ style="yellow",
132
+ )
133
+ return False
134
+
135
+ # Step 2: Validate entities
136
+ invalid_entities = []
137
+ for entity in entities:
138
+ is_valid, error_msg = self.validate_entity_before_archive(
139
+ entity, **kwargs
140
+ )
141
+ if not is_valid:
142
+ invalid_entities.append((entity, error_msg))
143
+
144
+ if invalid_entities:
145
+ for entity, error_msg in invalid_entities:
146
+ self.console.print(
147
+ f"⚠️ Cannot archive {entity.id}: {error_msg}",
148
+ style="yellow",
149
+ )
150
+ # Continue with valid entities if any
151
+ entities = [e for e in entities if (e, None) not in invalid_entities]
152
+ if not entities:
153
+ return False
154
+
155
+ # Step 3: Find corresponding files
156
+ files_to_archive = self.find_entity_files(entities)
157
+
158
+ if not files_to_archive:
159
+ self.console.print(
160
+ f"Could not find files for {len(entities)} {self.entity_type.value}(s)",
161
+ style="yellow",
162
+ )
163
+ return False
164
+
165
+ # Step 4: Move files to archive
166
+ archive_dir = get_archive_dir(self.entity_type)
167
+ archive_dir.mkdir(parents=True, exist_ok=True)
168
+
169
+ archived_files = []
170
+ failed_count = 0
171
+
172
+ for file_path in files_to_archive:
173
+ try:
174
+ archive_file = archive_dir / file_path.name
175
+ if archive_file.exists():
176
+ failed_count += 1
177
+ continue
178
+
179
+ archive_file.write_text(file_path.read_text())
180
+ file_path.unlink()
181
+ archived_files.append(archive_file)
182
+
183
+ except Exception as e:
184
+ self.console.print(
185
+ f"⚠️ Failed to archive {file_path.name}: {str(e)}",
186
+ style="yellow",
187
+ )
188
+ failed_count += 1
189
+
190
+ # Step 5: Update state via hook
191
+ self.post_archive_hook(archived_files, entities, **kwargs)
192
+
193
+ # Step 6: Display results
194
+ display_archive_success(
195
+ self.entity_type,
196
+ len(archived_files),
197
+ failed_count,
198
+ console=self.console,
199
+ )
200
+
201
+ return True
202
+
203
+ except Exception as e:
204
+ self.console.print(
205
+ f"❌ Archive operation failed: {str(e)}",
206
+ style="red",
207
+ )
208
+ raise click.ClickException(str(e)) from e
@@ -0,0 +1,154 @@
1
+ """Base class for create operations across all entity types."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+ from roadmap.adapters.cli.crud.crud_helpers import EntityType
7
+ from roadmap.adapters.cli.crud.crud_utils import (
8
+ create_entity_by_type,
9
+ get_entity_id,
10
+ get_entity_title,
11
+ )
12
+ from roadmap.adapters.cli.presentation.crud_presenter import CreatePresenter
13
+ from roadmap.common.console import get_console
14
+ from roadmap.common.errors.exceptions import ValidationError
15
+ from roadmap.common.logging import log_audit_event
16
+
17
+
18
+ class BaseCreate(ABC):
19
+ """Abstract base class for entity creation commands.
20
+
21
+ Subclasses implement:
22
+ - entity_type: The EntityType this creates
23
+ - build_entity_dict(): Convert CLI args to entity dict
24
+ - post_create_hook(): Optional hook after creation (default: no-op)
25
+
26
+ Uses Template Method pattern for extensibility.
27
+ """
28
+
29
+ entity_type: EntityType
30
+
31
+ def __init__(self, core: Any, console: Any = None) -> None:
32
+ """Initialize create command.
33
+
34
+ Args:
35
+ core: The core application context
36
+ console: Optional console for output (uses default if not provided)
37
+ """
38
+ self.core = core
39
+ self.console = console or get_console()
40
+
41
+ @abstractmethod
42
+ def build_entity_dict(self, **kwargs) -> dict[str, Any]:
43
+ """Convert CLI arguments to entity dictionary.
44
+
45
+ Args:
46
+ **kwargs: All click option values
47
+
48
+ Returns:
49
+ Dictionary of entity fields to create
50
+
51
+ Raises:
52
+ ValidationError: If arguments are invalid
53
+ """
54
+ ...
55
+
56
+ def post_create_hook(self, entity: Any, **kwargs) -> None: # noqa: B027
57
+ """Handle entity creation completion with optional custom logic.
58
+
59
+ Override to handle:
60
+ - Git branch creation
61
+ - Milestone assignments
62
+ - Custom notifications
63
+
64
+ Args:
65
+ entity: The created entity
66
+ **kwargs: Original CLI arguments
67
+ """
68
+ ...
69
+
70
+ def execute(self, title: str, **kwargs) -> Any | None:
71
+ """Execute the create operation.
72
+
73
+ Args:
74
+ title: Entity title/name
75
+ **kwargs: All other CLI arguments
76
+
77
+ Returns:
78
+ Created entity, or None if creation failed
79
+ """
80
+ try:
81
+ # Build entity from arguments
82
+ entity_dict = self.build_entity_dict(title=title, **kwargs)
83
+
84
+ # Create via appropriate service
85
+ entity = self._create_entity(entity_dict)
86
+ if entity is None:
87
+ return None
88
+
89
+ # Run post-creation hooks
90
+ self.post_create_hook(entity, **kwargs)
91
+
92
+ # Log audit event for successful creation
93
+ entity_id = self._get_id(entity)
94
+ log_audit_event(
95
+ action="create",
96
+ entity_type=self.entity_type.value,
97
+ entity_id=entity_id,
98
+ after=entity_dict,
99
+ )
100
+
101
+ # Display success
102
+ self._display_success(entity)
103
+
104
+ return entity
105
+
106
+ except ValidationError:
107
+ # ValidationError will be handled by centralized CLI exception handler
108
+ # which formats it and directs to stderr
109
+ raise
110
+ except Exception:
111
+ # Other exceptions will also be handled by centralized handler
112
+ raise
113
+
114
+ def _create_entity(self, entity_dict: dict[str, Any]) -> Any | None:
115
+ """Create entity via appropriate service.
116
+
117
+ Args:
118
+ entity_dict: Dictionary of entity fields
119
+
120
+ Returns:
121
+ Created entity or None if failed
122
+ """
123
+ return create_entity_by_type(self.core, self.entity_type, entity_dict)
124
+
125
+ def _display_success(self, entity: Any) -> None:
126
+ """Display success message using presenter.
127
+
128
+ Args:
129
+ entity: The created entity
130
+ """
131
+ presenter = CreatePresenter()
132
+ presenter.render(entity)
133
+
134
+ def _get_title(self, entity: Any) -> str:
135
+ """Get entity title/name.
136
+
137
+ Args:
138
+ entity: The entity object
139
+
140
+ Returns:
141
+ Title or name string
142
+ """
143
+ return get_entity_title(entity)
144
+
145
+ def _get_id(self, entity: Any) -> str:
146
+ """Get entity ID.
147
+
148
+ Args:
149
+ entity: The entity object
150
+
151
+ Returns:
152
+ Entity ID string
153
+ """
154
+ return get_entity_id(entity)