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,236 @@
1
+ """MilestoneArchive command implementation using base class."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from roadmap.adapters.cli.crud import BaseArchive, EntityType
8
+ from roadmap.adapters.persistence.parser import MilestoneParser
9
+
10
+
11
+ class MilestoneArchive(BaseArchive):
12
+ """Archive milestone command implementation."""
13
+
14
+ entity_type = EntityType.MILESTONE
15
+
16
+ def get_entities_to_archive(
17
+ self, entity_id: str | None = None, all_closed: bool = False, **kwargs
18
+ ) -> list[Any]:
19
+ """Get milestones to archive based on criteria.
20
+
21
+ Args:
22
+ entity_id: Specific milestone name to archive
23
+ all_closed: Archive all closed milestones
24
+ **kwargs: Additional arguments
25
+
26
+ Returns:
27
+ List of milestones to archive
28
+ """
29
+ all_milestones = self.core.milestones.list()
30
+
31
+ if entity_id:
32
+ milestone = self.core.milestones.get(entity_id)
33
+ return [milestone] if milestone else []
34
+ elif all_closed:
35
+ return [m for m in all_milestones if m.status.value == "closed"]
36
+
37
+ return []
38
+
39
+ def validate_entity_before_archive(
40
+ self, entity: Any, **kwargs
41
+ ) -> tuple[bool, str | None]:
42
+ """Validate milestone before archiving.
43
+
44
+ Args:
45
+ entity: The milestone to validate
46
+ **kwargs: Additional arguments (e.g., force flag)
47
+
48
+ Returns:
49
+ Tuple of (is_valid, error_message)
50
+ """
51
+ # Allow archiving if force flag is set
52
+ if kwargs.get("force"):
53
+ return True, None
54
+
55
+ if entity.status.value != "closed":
56
+ return False, f"not closed (status: {entity.status.value})"
57
+ return True, None
58
+
59
+ def find_entity_files(self, entities: list[Any]) -> list[Path]:
60
+ """Find milestone files for entities.
61
+
62
+ Args:
63
+ entities: List of milestones
64
+
65
+ Returns:
66
+ List of file paths
67
+ """
68
+ roadmap_dir = Path.cwd() / ".roadmap"
69
+ milestones_dir = roadmap_dir / "milestones"
70
+
71
+ files = []
72
+ for entity in entities:
73
+ # Search for milestone file by name
74
+ for md_file in milestones_dir.glob("*.md"):
75
+ try:
76
+ milestone = MilestoneParser.parse_milestone_file(md_file)
77
+ if milestone.name == entity.name:
78
+ files.append(md_file)
79
+ break
80
+ except Exception as e:
81
+ from roadmap.common.logging import get_logger
82
+
83
+ logger = get_logger(__name__)
84
+ logger.debug(
85
+ "milestone_parse_failed",
86
+ file=str(md_file),
87
+ error=str(e),
88
+ action="find_milestone_files",
89
+ )
90
+ continue
91
+ return files
92
+
93
+ def execute(self, entity_id: str | None = None, **kwargs) -> bool:
94
+ """Execute archive operation.
95
+
96
+ Archives milestones by moving files and setting the archived flag.
97
+
98
+ Args:
99
+ entity_id: Optional milestone name
100
+ **kwargs: Filter options (all_closed, etc.)
101
+
102
+ Returns:
103
+ True if successful
104
+ """
105
+ try:
106
+ entities = self.get_entities_to_archive(entity_id, **kwargs)
107
+
108
+ if not entities:
109
+ self.console.print(
110
+ f"No {self.entity_type.value}s to archive",
111
+ style="yellow",
112
+ )
113
+ return False
114
+
115
+ # Validate entities
116
+ invalid_entities = []
117
+ for entity in entities:
118
+ is_valid, error_msg = self.validate_entity_before_archive(
119
+ entity, **kwargs
120
+ )
121
+ if not is_valid:
122
+ invalid_entities.append((entity, error_msg))
123
+ self.console.print(
124
+ f"⚠️ Cannot archive {entity.name}: {error_msg}",
125
+ style="yellow",
126
+ )
127
+
128
+ if invalid_entities:
129
+ invalid_entity_names = {entity.name for entity, _ in invalid_entities}
130
+ entities = [e for e in entities if e.name not in invalid_entity_names]
131
+ if not entities:
132
+ return False
133
+
134
+ # Find files
135
+ files_to_archive = self.find_entity_files(entities)
136
+
137
+ if not files_to_archive:
138
+ self.console.print(
139
+ f"Could not find files for {len(entities)} {self.entity_type.value}(s)",
140
+ style="yellow",
141
+ )
142
+ return False
143
+
144
+ # Archive with folder structure preservation
145
+ from roadmap.adapters.cli.crud.crud_helpers import (
146
+ display_archive_success,
147
+ get_archive_dir,
148
+ )
149
+
150
+ archive_dir = get_archive_dir(self.entity_type)
151
+ archive_dir.mkdir(parents=True, exist_ok=True)
152
+
153
+ archived_files = []
154
+ failed_count = 0
155
+
156
+ for file_path in files_to_archive:
157
+ try:
158
+ archive_file = archive_dir / file_path.name
159
+ if archive_file.exists():
160
+ failed_count += 1
161
+ continue
162
+
163
+ # Ensure parent directory exists and use rename (mv) for atomicity
164
+ archive_file.parent.mkdir(parents=True, exist_ok=True)
165
+ file_path.rename(archive_file)
166
+ archived_files.append(archive_file)
167
+
168
+ except Exception as e:
169
+ self.console.print(
170
+ f"⚠️ Failed to archive {file_path.name}: {str(e)}",
171
+ style="yellow",
172
+ )
173
+ failed_count += 1
174
+
175
+ # Update state (set archived flag and move associated issues)
176
+ self.post_archive_hook(archived_files, entities, **kwargs)
177
+
178
+ # Display results
179
+ display_archive_success(
180
+ self.entity_type,
181
+ len(archived_files),
182
+ failed_count,
183
+ console=self.console,
184
+ )
185
+
186
+ return True
187
+
188
+ except Exception as e:
189
+ self.console.print(
190
+ f"❌ Archive operation failed: {str(e)}",
191
+ style="red",
192
+ )
193
+ import click
194
+
195
+ raise click.ClickException(str(e)) from e
196
+
197
+ def post_archive_hook(
198
+ self, archived_files: list[Path], entities: list[Any], **kwargs
199
+ ) -> None:
200
+ """Handle post-archive state updates.
201
+
202
+ Moves associated issues folder and sets archived flag.
203
+
204
+ Args:
205
+ archived_files: Files that were archived
206
+ entities: Entities that were archived
207
+ **kwargs: Additional arguments
208
+ """
209
+ # Move associated issues folder to archive
210
+ roadmap_dir = Path.cwd() / ".roadmap"
211
+
212
+ for entity in entities:
213
+ issues_dir = roadmap_dir / "issues" / entity.name
214
+ if issues_dir.exists():
215
+ archive_issues_dir = roadmap_dir / "archive" / "issues" / entity.name
216
+ archive_issues_dir.parent.mkdir(parents=True, exist_ok=True)
217
+
218
+ try:
219
+ shutil.move(str(issues_dir), str(archive_issues_dir))
220
+ except Exception as e:
221
+ self.console.print(
222
+ f"⚠️ Warning: Failed to move issues for milestone {entity.name}: {e}",
223
+ style="yellow",
224
+ )
225
+
226
+ # Set archived flag
227
+ try:
228
+ self.core.milestones.update_milestone(
229
+ name=entity.name,
230
+ archived=True,
231
+ )
232
+ except Exception as e:
233
+ self.console.print(
234
+ f"⚠️ Warning: Failed to mark milestone {entity.name} as archived: {e}",
235
+ style="yellow",
236
+ )
@@ -0,0 +1,62 @@
1
+ """Assign issue to milestone command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
7
+ from roadmap.common.console import get_console
8
+ from roadmap.common.formatters.text.operations import (
9
+ format_operation_failure,
10
+ format_operation_success,
11
+ )
12
+ from roadmap.common.logging import (
13
+ log_command,
14
+ track_database_operation,
15
+ )
16
+
17
+ console = get_console()
18
+
19
+
20
+ @click.command("assign")
21
+ @click.argument("issue_id")
22
+ @click.argument("milestone_name")
23
+ @click.pass_context
24
+ @require_initialized
25
+ @log_command("milestone_assign", entity_type="milestone", track_duration=True)
26
+ def assign_milestone(ctx: click.Context, issue_id: str, milestone_name: str):
27
+ """Assign an issue to a milestone."""
28
+ core = ctx.obj["core"]
29
+
30
+ try:
31
+ with track_database_operation("update", "milestone", warn_threshold_ms=2000):
32
+ success = core.issues.assign_to_milestone(issue_id, milestone_name)
33
+
34
+ if success:
35
+ extra_details = {"Milestone": milestone_name}
36
+ lines = format_operation_success(
37
+ "✅", "Assigned", "", issue_id, None, extra_details
38
+ )
39
+ for line in lines:
40
+ console.print(line, style="green")
41
+ else:
42
+ lines = format_operation_failure(
43
+ "Assign", issue_id, "Issue or milestone not found"
44
+ )
45
+ for line in lines:
46
+ console.print(line, style="bold red")
47
+ raise click.Abort()
48
+ except click.Abort:
49
+ raise
50
+ except Exception as e:
51
+ handle_cli_error(
52
+ error=e,
53
+ operation="milestone_assign",
54
+ entity_type="milestone",
55
+ entity_id=milestone_name,
56
+ context={"issue_id": issue_id, "milestone_name": milestone_name},
57
+ fatal=True,
58
+ )
59
+ lines = format_operation_failure("Assign", issue_id, str(e))
60
+ for line in lines:
61
+ console.print(line, style="bold red")
62
+ raise click.Abort() from e
@@ -0,0 +1,177 @@
1
+ """Close milestone command.
2
+
3
+ SEMANTICS:
4
+ - Close: Changes status to 'closed', keeps files in place (no file movement), archived=false
5
+ - Archive: Moves files to archive directory AND sets archived=true flag (signals sync to delete remotely)
6
+
7
+ This separation allows for flexible status management without workspace reorganization.
8
+ """
9
+
10
+ import click
11
+
12
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
13
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
14
+ from roadmap.common.console import get_console
15
+ from roadmap.common.formatters.text.operations import (
16
+ format_operation_failure,
17
+ format_operation_success,
18
+ )
19
+ from roadmap.common.logging import (
20
+ log_command,
21
+ track_database_operation,
22
+ )
23
+
24
+ console = get_console()
25
+
26
+
27
+ def _display_open_issues(milestone_name: str, open_issues: list) -> None:
28
+ """Display open issues in milestone and provide guidance.
29
+
30
+ Args:
31
+ milestone_name: Name of milestone
32
+ open_issues: List of open issues
33
+ """
34
+ console.print(
35
+ f"\n⚠️ Cannot close milestone: {len(open_issues)} open issue(s) remain",
36
+ style="bold yellow",
37
+ )
38
+ console.print(f"\n📋 Open issues in '{milestone_name}':", style="cyan")
39
+
40
+ for issue in open_issues[:10]: # Show first 10
41
+ console.print(
42
+ f" • {issue.id[:8]} - {issue.title} [{issue.status.value}]",
43
+ style="dim",
44
+ )
45
+
46
+ if len(open_issues) > 10:
47
+ console.print(f" ... and {len(open_issues) - 10} more", style="dim")
48
+
49
+ console.print(
50
+ "\n💡 Options to proceed:",
51
+ style="cyan",
52
+ )
53
+ console.print(
54
+ " 1. Close the open issues: roadmap issue close <issue_id>",
55
+ style="dim",
56
+ )
57
+ console.print(
58
+ " 2. Migrate to backlog: roadmap issue update <issue_id> --milestone ''",
59
+ style="dim",
60
+ )
61
+ console.print(
62
+ " 3. Move to a different milestone: roadmap issue update <issue_id> --milestone 'New Milestone'",
63
+ style="dim",
64
+ )
65
+ console.print(
66
+ f" 4. Use --force to close anyway (not recommended): roadmap milestone close '{milestone_name}' --force",
67
+ style="dim",
68
+ )
69
+
70
+
71
+ def _confirm_milestone_close(
72
+ milestone_name: str, total_issues: int, force: bool
73
+ ) -> bool:
74
+ """Confirm milestone closure with user.
75
+
76
+ Args:
77
+ milestone_name: Name of milestone
78
+ total_issues: Total number of issues
79
+ force: Skip confirmation if True
80
+
81
+ Returns:
82
+ True if user confirmed or force is True
83
+ """
84
+ if force:
85
+ return True
86
+
87
+ return click.confirm(
88
+ f"Close milestone '{milestone_name}'? All {total_issues} issue(s) are completed."
89
+ )
90
+
91
+
92
+ def _close_milestone_in_db(core, milestone_name: str) -> bool:
93
+ """Close milestone in database.
94
+
95
+ Args:
96
+ core: RoadmapCore instance
97
+ milestone_name: Name of milestone
98
+
99
+ Returns:
100
+ True if successful
101
+ """
102
+ from roadmap.core.domain import MilestoneStatus
103
+
104
+ with track_database_operation("update", "milestone"):
105
+ return core.milestones.update(milestone_name, status=MilestoneStatus.CLOSED)
106
+
107
+
108
+ @click.command("close")
109
+ @click.argument("milestone_name")
110
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt")
111
+ @click.pass_context
112
+ @require_initialized
113
+ @log_command("milestone_close", entity_type="milestone", track_duration=True)
114
+ def close_milestone(ctx: click.Context, milestone_name: str, force: bool):
115
+ """Close a milestone.
116
+
117
+ Validates that all issues in the milestone are closed before allowing closure.
118
+ If there are open issues, provides guidance on migration options.
119
+ """
120
+ core = ctx.obj["core"]
121
+
122
+ try:
123
+ # Check if milestone exists
124
+ milestone = core.milestones.get(milestone_name)
125
+ if not milestone:
126
+ lines = format_operation_failure(
127
+ "Close", milestone_name, "Milestone not found"
128
+ )
129
+ for line in lines:
130
+ console.print(line, style="bold red")
131
+ raise click.Abort()
132
+
133
+ # Get all issues in this milestone
134
+ all_issues = core.issues.list(milestone=milestone_name)
135
+
136
+ # Filter for open issues (not closed)
137
+ open_issues = [issue for issue in all_issues if issue.status.value != "closed"]
138
+
139
+ # If there are open issues, provide guidance and return
140
+ if open_issues:
141
+ _display_open_issues(milestone_name, open_issues)
142
+ return
143
+
144
+ # All issues are closed, confirm and proceed
145
+ if not _confirm_milestone_close(milestone_name, len(all_issues), force):
146
+ console.print("❌ Milestone close cancelled.", style="yellow")
147
+ return
148
+
149
+ # Close milestone in database
150
+ success = _close_milestone_in_db(core, milestone_name)
151
+
152
+ if success:
153
+ extra_details = {"Completed issues": str(len(all_issues))}
154
+ lines = format_operation_success(
155
+ "✅", "Closed", milestone_name, "", None, extra_details
156
+ )
157
+ for line in lines:
158
+ console.print(line, style="green")
159
+ else:
160
+ lines = format_operation_failure(
161
+ "Close", milestone_name, "Failed to update status"
162
+ )
163
+ for line in lines:
164
+ console.print(line, style="bold red")
165
+
166
+ except Exception as e:
167
+ handle_cli_error(
168
+ error=e,
169
+ operation="close_milestone",
170
+ entity_type="milestone",
171
+ entity_id=milestone_name,
172
+ context={"force": force},
173
+ fatal=True,
174
+ )
175
+ lines = format_operation_failure("Close", milestone_name, str(e))
176
+ for line in lines:
177
+ console.print(line, style="bold red")
@@ -0,0 +1,131 @@
1
+ """Create milestone command."""
2
+
3
+ import re
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
8
+ from roadmap.adapters.cli.crud import BaseCreate, EntityType
9
+ from roadmap.adapters.cli.crud.entity_builders import MilestoneBuilder
10
+ from roadmap.common.logging import (
11
+ log_command,
12
+ verbose_output,
13
+ )
14
+
15
+
16
+ def _validate_milestone_name(name: str) -> tuple[bool, str | None]:
17
+ """Validate milestone name against naming conventions.
18
+
19
+ Args:
20
+ name: The milestone name to validate
21
+
22
+ Returns:
23
+ Tuple of (is_valid, error_message)
24
+ """
25
+ # Check for invalid characters
26
+ if not re.match(r"^[a-zA-Z0-9_\-\.]+$", name):
27
+ return (
28
+ False,
29
+ "Milestone name contains invalid characters. Use only: letters, numbers, hyphens, underscores, dots",
30
+ )
31
+
32
+ # Check for version patterns with dots (should use hyphens instead)
33
+ if re.match(r"^v\d+\.\d+", name): # e.g., v1.0., v1.0.2, v2.0
34
+ suggested = name.replace(".", "-")
35
+ return (
36
+ False,
37
+ f"Version milestones should use hyphens, not dots.\n"
38
+ f" Current: {name}\n"
39
+ f" Suggested: {suggested}",
40
+ )
41
+
42
+ # Valid milestone name
43
+ return True, None
44
+
45
+
46
+ class MilestoneCreate(BaseCreate):
47
+ """Create milestone command implementation."""
48
+
49
+ entity_type = EntityType.MILESTONE
50
+
51
+ def build_entity_dict(self, title: str, **kwargs) -> dict:
52
+ """Build entity dictionary for milestone creation."""
53
+ create_dict = MilestoneBuilder.build_create_dict(
54
+ name=title,
55
+ description=kwargs.get("description"),
56
+ due_date=kwargs.get("due_date"),
57
+ )
58
+
59
+ # Add project_id if provided or auto-detected
60
+ if kwargs.get("project_id"):
61
+ create_dict["project_id"] = kwargs.get("project_id")
62
+
63
+ return create_dict
64
+
65
+
66
+ @click.command("create")
67
+ @click.option("--title", "-t", required=True, help="Milestone title")
68
+ @click.option("--description", "-d", default="", help="Milestone description")
69
+ @click.option("--due-date", help="Due date for milestone (YYYY-MM-DD format)")
70
+ @click.option("--project", "-p", default=None, help="Project ID to assign milestone to")
71
+ @click.pass_context
72
+ @require_initialized
73
+ @verbose_output
74
+ @log_command("milestone_create", entity_type="milestone", track_duration=True)
75
+ def create_milestone(
76
+ ctx: click.Context,
77
+ title: str,
78
+ description: str,
79
+ due_date: str,
80
+ project: str | None,
81
+ ):
82
+ """Create a new milestone."""
83
+ # Validate milestone name against naming conventions
84
+ is_valid, error_msg = _validate_milestone_name(title)
85
+ if not is_valid:
86
+ if error_msg is None:
87
+ error_msg = "Invalid milestone name"
88
+ raise click.ClickException(error_msg)
89
+
90
+ core = ctx.obj["core"]
91
+ creator = MilestoneCreate(core)
92
+
93
+ # Auto-detect project if not provided
94
+ project_id = project
95
+ if not project_id:
96
+ from roadmap.common.configuration import ConfigManager
97
+
98
+ # Try to get default project from config
99
+ config_manager = ConfigManager(core.config_file)
100
+ config = config_manager.load()
101
+
102
+ if config.behavior.default_project_id:
103
+ project_id = config.behavior.default_project_id
104
+ click.echo(
105
+ f"ℹ️ Assigning milestone to default project: {project_id}", err=False
106
+ )
107
+ else:
108
+ # Check how many projects exist
109
+ all_projects = list(core.projects.list())
110
+ if all_projects:
111
+ # Projects exist but no default - need clarification
112
+ if len(all_projects) > 1:
113
+ raise click.ClickException(
114
+ f"Multiple projects found ({len(all_projects)}). Please specify project with --project flag.\n"
115
+ f"Available projects: {', '.join(p.id for p in all_projects)}"
116
+ )
117
+ else:
118
+ # Auto-assign to the only project
119
+ project_id = all_projects[0].id
120
+ click.echo(
121
+ f"ℹ️ Assigning milestone to project: {all_projects[0].name} ({project_id})",
122
+ err=False,
123
+ )
124
+ # If no projects exist, that's OK - milestone can be created without a project
125
+
126
+ creator.execute(
127
+ title=title,
128
+ description=description,
129
+ due_date=due_date,
130
+ project_id=project_id,
131
+ )
@@ -0,0 +1,27 @@
1
+ """Delete milestone command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.crud import BaseDelete, EntityType
7
+ from roadmap.common.logging import log_command
8
+
9
+
10
+ class MilestoneDelete(BaseDelete):
11
+ """Delete milestone command implementation."""
12
+
13
+ entity_type = EntityType.MILESTONE
14
+
15
+
16
+ @click.command("delete")
17
+ @click.argument("milestone_id")
18
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
19
+ @click.pass_context
20
+ @require_initialized
21
+ @log_command("milestone_delete", entity_type="milestone", track_duration=True)
22
+ def delete_milestone(ctx: click.Context, milestone_id: str, yes: bool):
23
+ """Delete a milestone."""
24
+ core = ctx.obj["core"]
25
+ deleter = MilestoneDelete(core)
26
+
27
+ deleter.execute(entity_id=milestone_id, force=yes)