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,96 @@
1
+ """ProjectArchive command implementation using base class."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from roadmap.adapters.cli.crud import BaseArchive, EntityType
7
+
8
+
9
+ class ProjectArchive(BaseArchive):
10
+ """Archive project command implementation."""
11
+
12
+ entity_type = EntityType.PROJECT
13
+
14
+ def get_entities_to_archive(
15
+ self, entity_id: str | None = None, **kwargs
16
+ ) -> list[Any]:
17
+ """Get projects to archive.
18
+
19
+ Args:
20
+ entity_id: Specific project ID to archive
21
+ **kwargs: Additional arguments
22
+
23
+ Returns:
24
+ List of projects to archive
25
+ """
26
+ if entity_id:
27
+ project = self.core.projects.get(entity_id)
28
+ return [project] if project else []
29
+
30
+ if kwargs.get("all_closed"):
31
+ return [
32
+ project
33
+ for project in self.core.projects.list()
34
+ if getattr(project.status, "value", project.status) == "completed"
35
+ ]
36
+
37
+ return []
38
+
39
+ def validate_entity_before_archive(
40
+ self, entity: Any, **kwargs
41
+ ) -> tuple[bool, str | None]:
42
+ """Validate project before archiving.
43
+
44
+ Args:
45
+ entity: The project to validate
46
+ **kwargs: Additional arguments (e.g., force flag)
47
+
48
+ Returns:
49
+ Tuple of (is_valid, error_message)
50
+ """
51
+ if kwargs.get("force"):
52
+ return True, None
53
+
54
+ status = getattr(entity.status, "value", entity.status)
55
+ if status != "completed":
56
+ return False, f"not completed (status: {status})"
57
+ return True, None
58
+
59
+ def find_entity_files(self, entities: list[Any]) -> list[Path]:
60
+ """Find project files for entities.
61
+
62
+ Args:
63
+ entities: List of projects
64
+
65
+ Returns:
66
+ List of file paths
67
+ """
68
+ roadmap_dir = Path.cwd() / ".roadmap"
69
+ projects_dir = roadmap_dir / "projects"
70
+
71
+ files = []
72
+ for entity in entities:
73
+ # Search for project file by ID prefix
74
+ matching = list(projects_dir.glob(f"{str(entity.id)[:8]}*.md"))
75
+ if matching:
76
+ files.append(matching[0])
77
+ return files
78
+
79
+ def post_archive_hook(
80
+ self, archived_files: list[Path], entities: list[Any], **kwargs
81
+ ) -> None:
82
+ """Handle post-archive state updates.
83
+
84
+ Args:
85
+ archived_files: Files that were archived
86
+ entities: Entities that were archived
87
+ **kwargs: Additional arguments
88
+ """
89
+ for entity in entities:
90
+ try:
91
+ self.core.db.mark_project_archived(entity.id, archived=True)
92
+ except Exception as e:
93
+ self.console.print(
94
+ f"⚠️ Warning: Failed to mark project {entity.id} as archived: {e}",
95
+ style="yellow",
96
+ )
@@ -0,0 +1,94 @@
1
+ """Close project command - sets status to closed without archiving."""
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
+ from roadmap.core.domain import ProjectStatus
17
+
18
+ console = get_console()
19
+
20
+
21
+ @click.command("close")
22
+ @click.argument("project_id")
23
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt")
24
+ @click.pass_context
25
+ @require_initialized
26
+ @log_command("project_close", entity_type="project", track_duration=True)
27
+ def close_project(ctx: click.Context, project_id: str, force: bool):
28
+ """Close a project (sets status to closed).
29
+
30
+ This command marks a project as completed while keeping its file active.
31
+ Use `roadmap project archive` as the separate cleanup step when the
32
+ project file should move under `.roadmap/archive/projects/`.
33
+ """
34
+ core = ctx.obj["core"]
35
+
36
+ try:
37
+ # Check if project exists
38
+ project = core.projects.get(project_id)
39
+ if not project:
40
+ lines = format_operation_failure("Close", project_id, "Project not found")
41
+ for line in lines:
42
+ console.print(line, style="bold red")
43
+ raise click.Abort()
44
+
45
+ # Confirm closure
46
+ if not force:
47
+ if not click.confirm(f"Close project '{project.name}'?"):
48
+ console.print("❌ Project close cancelled.", style="yellow")
49
+ return
50
+
51
+ # Close project in database
52
+ with track_database_operation(
53
+ "update", "project", entity_id=project_id, warn_threshold_ms=2000
54
+ ):
55
+ updated_project = core.projects.update(
56
+ project_id, status=ProjectStatus.COMPLETED
57
+ )
58
+
59
+ if updated_project:
60
+ extra_details = {"Status": "Closed"}
61
+ lines = format_operation_success(
62
+ emoji="✅",
63
+ action="Closed",
64
+ entity_title=updated_project.name,
65
+ entity_id=project_id,
66
+ extra_details=extra_details,
67
+ )
68
+ for line in lines:
69
+ console.print(line, style="bold green" if "Closed" in line else "cyan")
70
+ else:
71
+ lines = format_operation_failure(
72
+ action="close",
73
+ entity_id=project_id,
74
+ error="Failed to update status",
75
+ )
76
+ for line in lines:
77
+ console.print(line, style="bold red")
78
+
79
+ except Exception as e:
80
+ handle_cli_error(
81
+ error=e,
82
+ operation="close_project",
83
+ entity_type="project",
84
+ entity_id=project_id,
85
+ context={"force": force},
86
+ fatal=True,
87
+ )
88
+ lines = format_operation_failure(
89
+ action="close",
90
+ entity_id=project_id,
91
+ error=str(e),
92
+ )
93
+ for line in lines:
94
+ console.print(line, style="bold red")
@@ -0,0 +1,50 @@
1
+ """Create project command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.crud import BaseCreate, EntityType
7
+ from roadmap.adapters.cli.crud.entity_builders import ProjectBuilder
8
+ from roadmap.common.logging import (
9
+ log_command,
10
+ verbose_output,
11
+ )
12
+
13
+
14
+ class ProjectCreate(BaseCreate):
15
+ """Create project command implementation."""
16
+
17
+ entity_type = EntityType.PROJECT
18
+
19
+ def build_entity_dict(self, title: str, **kwargs) -> dict:
20
+ """Build entity dictionary for project creation."""
21
+ return ProjectBuilder.build_create_dict(
22
+ name=title,
23
+ description=kwargs.get("description"),
24
+ repository=kwargs.get("repository"),
25
+ )
26
+
27
+
28
+ @click.command("create")
29
+ @click.option("--title", "-t", required=True, help="Project title")
30
+ @click.option("--description", "-d", help="Project description")
31
+ @click.option("--repository", "-r", help="Repository URL")
32
+ @click.pass_context
33
+ @require_initialized
34
+ @verbose_output
35
+ @log_command("project_create", entity_type="project", track_duration=True)
36
+ def create_project(
37
+ ctx: click.Context,
38
+ title: str,
39
+ description: str,
40
+ repository: str,
41
+ ):
42
+ """Create a new project."""
43
+ core = ctx.obj["core"]
44
+ creator = ProjectCreate(core)
45
+
46
+ creator.execute(
47
+ title=title,
48
+ description=description,
49
+ repository=repository,
50
+ )
@@ -0,0 +1,27 @@
1
+ """Delete project 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 ProjectDelete(BaseDelete):
11
+ """Delete project command implementation."""
12
+
13
+ entity_type = EntityType.PROJECT
14
+
15
+
16
+ @click.command("delete")
17
+ @click.argument("project_id")
18
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
19
+ @click.pass_context
20
+ @require_initialized
21
+ @log_command("project_delete", entity_type="project", track_duration=True)
22
+ def delete_project(ctx: click.Context, project_id: str, yes: bool):
23
+ """Delete a project."""
24
+ core = ctx.obj["core"]
25
+ deleter = ProjectDelete(core)
26
+
27
+ deleter.execute(entity_id=project_id, force=yes)
@@ -0,0 +1,227 @@
1
+ """List projects command."""
2
+
3
+ from datetime import UTC
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
8
+ from roadmap.adapters.cli.decorators import with_output_support
9
+ from roadmap.common.console import get_console
10
+ from roadmap.common.datetime_parser import UnifiedDateTimeParser
11
+ from roadmap.common.formatters import ProjectTableFormatter
12
+ from roadmap.common.logging import verbose_output
13
+ from roadmap.common.models import ColumnType
14
+
15
+ console = get_console()
16
+
17
+
18
+ def _parse_project_metadata(file_path) -> dict | None:
19
+ """Parse project metadata from markdown file."""
20
+ try:
21
+ content = file_path.read_text()
22
+ if not content.startswith("---"):
23
+ return None
24
+
25
+ yaml_end = content.find("---", 3)
26
+ if yaml_end == -1:
27
+ return None
28
+
29
+ import yaml
30
+
31
+ yaml_content = content[3:yaml_end]
32
+ metadata = yaml.safe_load(yaml_content)
33
+
34
+ # Add ID from frontmatter if present, or extract from filename
35
+ if isinstance(metadata, dict) and not metadata.get("id"):
36
+ # Extract ID from filename (format: {id}-{name}.md)
37
+ filename_stem = file_path.stem
38
+ if "-" in filename_stem:
39
+ metadata["id"] = filename_stem.split("-", 1)[0]
40
+
41
+ return metadata if isinstance(metadata, dict) else None
42
+ except Exception as e:
43
+ from roadmap.common.logging import get_logger
44
+
45
+ logger = get_logger(__name__)
46
+ logger.debug(
47
+ "project_metadata_parse_failed",
48
+ error=str(e),
49
+ action="parse_project_metadata",
50
+ )
51
+ return None
52
+
53
+
54
+ def _apply_status_filter(metadata, status):
55
+ """Check if project matches status filter."""
56
+ if not status:
57
+ return True
58
+ return metadata.get("status") == status
59
+
60
+
61
+ def _apply_owner_filter(metadata, owner):
62
+ """Check if project matches owner filter."""
63
+ if not owner:
64
+ return True
65
+ return metadata.get("owner") == owner
66
+
67
+
68
+ def _apply_priority_filter(metadata, priority):
69
+ """Check if project matches priority filter."""
70
+ if not priority:
71
+ return True
72
+ return metadata.get("priority") == priority
73
+
74
+
75
+ def _apply_overdue_filter(metadata):
76
+ """Check if project is overdue."""
77
+ from datetime import datetime
78
+
79
+ target_end = metadata.get("target_end_date")
80
+ if not target_end:
81
+ return False
82
+
83
+ try:
84
+ if isinstance(target_end, str):
85
+ end_date = UnifiedDateTimeParser.parse_iso_datetime(target_end)
86
+ if not end_date:
87
+ return False
88
+ else:
89
+ end_date = target_end
90
+
91
+ end_date = end_date.replace(tzinfo=None) if end_date.tzinfo else end_date
92
+ now = datetime.now(UTC)
93
+
94
+ project_status = metadata.get("status", "")
95
+ if project_status in ["completed", "cancelled"]:
96
+ return False
97
+
98
+ return end_date < now
99
+ except (ValueError, AttributeError):
100
+ return False
101
+
102
+
103
+ def _filter_projects(projects, status, owner, priority, overdue):
104
+ """Apply all filters to project list."""
105
+ filtered = []
106
+
107
+ for metadata in projects:
108
+ if not _apply_status_filter(metadata, status):
109
+ continue
110
+ if not _apply_owner_filter(metadata, owner):
111
+ continue
112
+ if not _apply_priority_filter(metadata, priority):
113
+ continue
114
+ if overdue and not _apply_overdue_filter(metadata):
115
+ continue
116
+
117
+ filtered.append(metadata)
118
+
119
+ return filtered
120
+
121
+
122
+ @click.command("list")
123
+ @click.option(
124
+ "--status",
125
+ type=click.Choice(["planning", "active", "on-hold", "completed", "cancelled"]),
126
+ help="Filter by status",
127
+ )
128
+ @click.option(
129
+ "--owner",
130
+ help="Filter by owner",
131
+ )
132
+ @click.option(
133
+ "--priority",
134
+ type=click.Choice(["critical", "high", "medium", "low"]),
135
+ help="Filter by priority",
136
+ )
137
+ @click.option(
138
+ "--overdue", is_flag=True, help="Show only overdue projects (past target end date)"
139
+ )
140
+ @click.option("--verbose", "-v", is_flag=True, help="Show verbose output")
141
+ @click.pass_context
142
+ @with_output_support(
143
+ available_columns=["id", "name", "status", "priority", "owner"],
144
+ column_types={
145
+ "id": ColumnType.STRING,
146
+ "name": ColumnType.STRING,
147
+ "status": ColumnType.ENUM,
148
+ "priority": ColumnType.ENUM,
149
+ "owner": ColumnType.STRING,
150
+ },
151
+ )
152
+ @verbose_output
153
+ def list_projects(
154
+ ctx: click.Context,
155
+ status: str | None,
156
+ owner: str | None,
157
+ priority: str | None,
158
+ overdue: bool,
159
+ verbose: bool, # noqa: F841 - Used by @verbose_output decorator
160
+ ):
161
+ """List all projects with optional filtering.
162
+
163
+ Supports output formatting with --format, --columns, --sort-by, --filter flags.
164
+ """
165
+ core = ctx.obj["core"]
166
+
167
+ try:
168
+ projects_dir = core.roadmap_dir / "projects"
169
+
170
+ if not projects_dir.exists():
171
+ console.print(
172
+ "No projects found. Create one with 'roadmap project create'",
173
+ style="yellow",
174
+ )
175
+ return
176
+
177
+ # Get all project files
178
+ project_files = list(projects_dir.rglob("*.md"))
179
+
180
+ if not project_files:
181
+ console.print(
182
+ "No projects found. Create one with 'roadmap project create'",
183
+ style="yellow",
184
+ )
185
+ return
186
+
187
+ # Parse all projects
188
+ project_metadatas = []
189
+ for file_path in project_files:
190
+ metadata = _parse_project_metadata(file_path)
191
+ if metadata:
192
+ project_metadatas.append(metadata)
193
+ else:
194
+ console.print(f"⚠️ Error reading {file_path.name}", style="yellow")
195
+
196
+ # Apply filters
197
+ filtered = _filter_projects(project_metadatas, status, owner, priority, overdue)
198
+
199
+ if not filtered:
200
+ console.print("No projects match the specified filters.", style="yellow")
201
+ return
202
+
203
+ # Convert to TableData for structured output
204
+ description = "filtered" if any([status, owner, priority, overdue]) else "all"
205
+ table_data = ProjectTableFormatter.projects_to_table_data(
206
+ filtered,
207
+ title="Projects",
208
+ description=description,
209
+ )
210
+
211
+ return table_data
212
+
213
+ except Exception as e:
214
+ handle_cli_error(
215
+ error=e,
216
+ operation="list_projects",
217
+ entity_type="project",
218
+ entity_id="all",
219
+ context={
220
+ "status": status,
221
+ "owner": owner,
222
+ "priority": priority,
223
+ "overdue": overdue,
224
+ },
225
+ fatal=True,
226
+ )
227
+ console.print(f"❌ Failed to list projects: {e}", style="bold red")
@@ -0,0 +1,87 @@
1
+ """Restore project command - move archived projects back to active."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.projects.restore_class import ProjectRestore
7
+ from roadmap.common.console import get_console
8
+ from roadmap.common.logging import (
9
+ log_command,
10
+ verbose_output,
11
+ )
12
+
13
+
14
+ @click.command()
15
+ @click.argument("project_name", required=False)
16
+ @click.option(
17
+ "--all",
18
+ is_flag=True,
19
+ help="Restore all archived projects",
20
+ )
21
+ @click.option(
22
+ "--dry-run",
23
+ is_flag=True,
24
+ help="Preview what would be restored without actually doing it",
25
+ )
26
+ @click.option(
27
+ "--force",
28
+ is_flag=True,
29
+ help="Skip confirmation prompt",
30
+ )
31
+ @click.option(
32
+ "--verbose",
33
+ "-v",
34
+ is_flag=True,
35
+ help="Show detailed debug information",
36
+ )
37
+ @click.pass_context
38
+ @verbose_output
39
+ @log_command("project_restore", entity_type="project", track_duration=True)
40
+ @require_initialized
41
+ def restore_project(
42
+ ctx: click.Context,
43
+ project_name: str | None,
44
+ all: bool,
45
+ dry_run: bool,
46
+ force: bool,
47
+ verbose: bool, # noqa: F841
48
+ ):
49
+ """Restore an archived project back to active projects.
50
+
51
+ This moves a project from .roadmap/archive/projects/ back to
52
+ .roadmap/projects/, making it active again.
53
+
54
+ Examples:
55
+ roadmap project restore "Project Name"
56
+ roadmap project restore --all
57
+ roadmap project restore "Project Name" --dry-run
58
+ """
59
+ core = ctx.obj["core"]
60
+ console = get_console()
61
+
62
+ restore = ProjectRestore(core, console)
63
+
64
+ if not project_name and not all:
65
+ console.print(
66
+ "❌ Error: Specify a project name or use --all",
67
+ style="bold red",
68
+ )
69
+ ctx.exit(1)
70
+
71
+ if project_name and all:
72
+ console.print(
73
+ "❌ Error: Cannot specify project name with --all",
74
+ style="bold red",
75
+ )
76
+ ctx.exit(1)
77
+
78
+ try:
79
+ restore.execute(
80
+ entity_id=project_name,
81
+ all=all,
82
+ dry_run=dry_run,
83
+ force=force,
84
+ )
85
+ except Exception as e:
86
+ console.print(f"❌ Restore operation failed: {str(e)}", style="bold red")
87
+ ctx.exit(1)
@@ -0,0 +1,56 @@
1
+ """ProjectRestore command implementation using base class."""
2
+
3
+ from pathlib import Path
4
+
5
+ from roadmap.adapters.cli.crud import BaseRestore, EntityType
6
+
7
+
8
+ class ProjectRestore(BaseRestore):
9
+ """Restore project command implementation."""
10
+
11
+ entity_type = EntityType.PROJECT
12
+
13
+ def get_archived_files_to_restore(
14
+ self, entity_id: str | None = None, **filters
15
+ ) -> list[Path]:
16
+ """Get archived project files to restore.
17
+
18
+ Args:
19
+ entity_id: Specific project ID to restore
20
+ **filters: Additional filter criteria
21
+
22
+ Returns:
23
+ List of archived file paths
24
+ """
25
+ from roadmap.adapters.cli.crud.crud_helpers import get_archive_dir
26
+
27
+ archive_dir = get_archive_dir(self.entity_type)
28
+
29
+ if not archive_dir.exists():
30
+ return []
31
+
32
+ if entity_id:
33
+ # Find specific project file by ID prefix
34
+ matching = list(archive_dir.rglob(f"{entity_id[:8]}*.md"))
35
+ return matching if matching else []
36
+ else:
37
+ # Return all archived projects
38
+ return list(archive_dir.rglob("*.md"))
39
+
40
+ def post_restore_hook(self, restored_files: list[Path], **kwargs) -> None:
41
+ """Handle post-restore state updates.
42
+
43
+ Args:
44
+ restored_files: Files that were restored
45
+ **kwargs: Additional arguments
46
+ """
47
+ for file_path in restored_files:
48
+ try:
49
+ # Extract project ID from filename
50
+ project_id = file_path.stem.split("-")[0]
51
+ self.core.db.mark_project_archived(project_id, archived=False)
52
+ except Exception as e:
53
+ self.console.print(
54
+ f"⚠️ Warning: Failed to update restoration status for {file_path.name}: {e}",
55
+ style="yellow",
56
+ )
@@ -0,0 +1,57 @@
1
+ """Update project command."""
2
+
3
+ import click
4
+
5
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
6
+ from roadmap.adapters.cli.crud import BaseUpdate, EntityType
7
+ from roadmap.adapters.cli.crud.entity_builders import ProjectBuilder
8
+ from roadmap.common.logging import log_command
9
+
10
+
11
+ class ProjectUpdate(BaseUpdate):
12
+ """Update project command implementation."""
13
+
14
+ entity_type = EntityType.PROJECT
15
+
16
+ def build_update_dict(self, entity_id: str, **kwargs) -> dict:
17
+ """Build update dictionary for project."""
18
+ return ProjectBuilder.build_update_dict(
19
+ name=kwargs.get("name"),
20
+ description=kwargs.get("description"),
21
+ repository=kwargs.get("repository"),
22
+ status=kwargs.get("status"),
23
+ )
24
+
25
+
26
+ @click.command("update")
27
+ @click.argument("project_id")
28
+ @click.option("--name", help="Update project name")
29
+ @click.option("--description", "-d", help="Update project description")
30
+ @click.option("--repository", "-r", help="Update repository URL")
31
+ @click.option(
32
+ "--status",
33
+ type=click.Choice(["active", "inactive", "completed"]),
34
+ help="Update project status",
35
+ )
36
+ @click.pass_context
37
+ @require_initialized
38
+ @log_command("project_update", entity_type="project", track_duration=True)
39
+ def update_project(
40
+ ctx: click.Context,
41
+ project_id: str,
42
+ name: str,
43
+ description: str,
44
+ repository: str,
45
+ status: str,
46
+ ):
47
+ """Update an existing project."""
48
+ core = ctx.obj["core"]
49
+ updater = ProjectUpdate(core)
50
+
51
+ updater.execute(
52
+ entity_id=project_id,
53
+ name=name,
54
+ description=description,
55
+ repository=repository,
56
+ status=status,
57
+ )