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,370 @@
1
+ """Analysis commands for roadmap insights and reporting."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import click
7
+ from structlog import get_logger
8
+
9
+ from roadmap.common.console import get_console
10
+ from roadmap.common.logging import verbose_output
11
+ from roadmap.core.services.issue_helpers.issue_filters import IssueQueryService
12
+ from roadmap.core.services.utils.critical_path_calculator import CriticalPathCalculator
13
+
14
+ console = get_console()
15
+ logger = get_logger()
16
+
17
+
18
+ @click.group()
19
+ def analysis():
20
+ """Analysis and insights commands."""
21
+ pass
22
+
23
+
24
+ @analysis.command("critical-path")
25
+ @click.option(
26
+ "--milestone",
27
+ "-m",
28
+ help="Analyze critical path for specific milestone only",
29
+ )
30
+ @click.option(
31
+ "--include-closed",
32
+ is_flag=True,
33
+ help="Include closed issues in analysis",
34
+ )
35
+ @click.option(
36
+ "--export",
37
+ type=click.Choice(["json", "csv"]),
38
+ help="Export format instead of displaying ASCII graph",
39
+ )
40
+ @click.option(
41
+ "--output",
42
+ "-o",
43
+ help="Output file path for export",
44
+ )
45
+ @click.pass_context
46
+ @verbose_output
47
+ def critical_path(
48
+ ctx: click.Context,
49
+ milestone: str | None,
50
+ include_closed: bool,
51
+ export: str | None,
52
+ output: str | None,
53
+ ):
54
+ """Analyze critical path of issues and dependencies.
55
+
56
+ Displays the longest dependency chain in your roadmap and identifies
57
+ the issues that have the most impact on project completion.
58
+
59
+ By default, shows an ASCII dependency graph with a summary of key metrics.
60
+ """
61
+ try:
62
+ logger.debug(
63
+ "Starting critical path analysis",
64
+ extra={
65
+ "milestone": milestone,
66
+ "include_closed": include_closed,
67
+ "export": export,
68
+ },
69
+ )
70
+
71
+ core = _get_core(ctx)
72
+ if not core or not core.is_initialized():
73
+ logger.warning("Roadmap not initialized", severity="operational")
74
+ console.print(
75
+ "❌ Roadmap not initialized. Run 'roadmap init' first.",
76
+ style="bold red",
77
+ )
78
+ return
79
+
80
+ # Load issues using query service
81
+ logger.debug("Loading issues from query service")
82
+ try:
83
+ query_service = IssueQueryService(core)
84
+ if milestone:
85
+ logger.debug(
86
+ "filtering_issues_for_milestone",
87
+ milestone=milestone,
88
+ )
89
+ issues_domain, filter_desc = query_service.get_filtered_issues(
90
+ milestone=milestone
91
+ )
92
+ else:
93
+ logger.debug("Loading all issues")
94
+ issues_domain, filter_desc = query_service.get_filtered_issues()
95
+ except Exception as e:
96
+ logger.error(
97
+ "failed_to_load_issues",
98
+ error=str(e),
99
+ severity="operational",
100
+ exc_info=True,
101
+ )
102
+ console.print(f"❌ Failed to load issues: {str(e)}", style="bold red")
103
+ return
104
+
105
+ logger.debug(
106
+ "issues_loaded",
107
+ count=len(issues_domain),
108
+ )
109
+
110
+ # Filter closed issues unless explicitly included
111
+ if not include_closed:
112
+ original_count = len(issues_domain)
113
+ issues_domain = [i for i in issues_domain if i.status != "closed"]
114
+ filtered_count = original_count - len(issues_domain)
115
+ if filtered_count > 0:
116
+ logger.debug(
117
+ "issues_filtered_for_status",
118
+ filtered_count=filtered_count,
119
+ remaining_count=len(issues_domain),
120
+ )
121
+
122
+ if not issues_domain:
123
+ logger.info(
124
+ "No active issues to analyze",
125
+ extra={"milestone": milestone, "include_closed": include_closed},
126
+ )
127
+ console.print(
128
+ f"ℹ️ No active issues to analyze.{f' In milestone: {milestone}' if milestone else ''}",
129
+ style="yellow",
130
+ )
131
+ return
132
+
133
+ # Calculate critical path
134
+ logger.debug(
135
+ "calculating_critical_path",
136
+ issue_count=len(issues_domain),
137
+ )
138
+ try:
139
+ calculator = CriticalPathCalculator()
140
+ result = calculator.calculate_critical_path(issues_domain)
141
+ logger.debug(
142
+ "Critical path calculation complete",
143
+ extra={
144
+ "total_duration": result.total_duration,
145
+ "critical_issues": len(result.critical_issue_ids),
146
+ },
147
+ )
148
+ except Exception as e:
149
+ logger.error(
150
+ "critical_path_calculation_failed",
151
+ error=str(e),
152
+ severity="operational",
153
+ exc_info=True,
154
+ )
155
+ console.print(
156
+ f"❌ Critical path calculation failed: {str(e)}", style="bold red"
157
+ )
158
+ return
159
+
160
+ # Output results
161
+ logger.debug(
162
+ "outputting_critical_path_results",
163
+ export_format=export or "display",
164
+ )
165
+ if export:
166
+ _export_critical_path(result, issues_domain, export, output)
167
+ else:
168
+ _display_critical_path(result, milestone)
169
+
170
+ logger.info("Critical path analysis completed successfully")
171
+
172
+ except Exception as e:
173
+ logger.error(
174
+ "critical_path_command_failed",
175
+ error=str(e),
176
+ severity="operational",
177
+ exc_info=True,
178
+ )
179
+ console.print(f"❌ Unexpected error: {str(e)}", style="bold red")
180
+
181
+
182
+ def _get_core(ctx: click.Context):
183
+ """Get RoadmapCore from context.
184
+
185
+ Args:
186
+ ctx: Click context object
187
+
188
+ Returns:
189
+ RoadmapCore instance or None if not available
190
+ """
191
+ try:
192
+ return ctx.obj.get("core") if ctx.obj else None
193
+ except (AttributeError, TypeError) as e:
194
+ logger.warning(
195
+ "failed_to_get_core_from_context",
196
+ error=str(e),
197
+ severity="operational",
198
+ )
199
+ return None
200
+
201
+
202
+ def _display_critical_path(result, milestone: str | None = None):
203
+ """Display critical path as ASCII graph with summary.
204
+
205
+ Args:
206
+ result: CriticalPathResult from calculator
207
+ milestone: Optional milestone name for context
208
+ """
209
+ try:
210
+ logger.debug("Formatting critical path for display")
211
+ from roadmap.adapters.cli.analysis.presenter import CriticalPathPresenter
212
+
213
+ presenter = CriticalPathPresenter()
214
+ output = presenter.format_critical_path(result, milestone)
215
+ console.print(output)
216
+ logger.debug("Critical path displayed successfully")
217
+ except Exception as e:
218
+ logger.error(
219
+ "failed_to_format_critical_path_display",
220
+ error=str(e),
221
+ severity="operational",
222
+ exc_info=True,
223
+ )
224
+ console.print(f"❌ Failed to format output: {str(e)}", style="bold red")
225
+
226
+
227
+ def _export_critical_path(result, issues: list, format: str, output_path: str | None):
228
+ """Export critical path data to file or stdout.
229
+
230
+ Args:
231
+ result: CriticalPathResult from calculator
232
+ issues: List of Issue domain objects
233
+ format: Export format ('json' or 'csv')
234
+ output_path: Optional file path for export
235
+ """
236
+ try:
237
+ logger.debug(
238
+ "exporting_critical_path",
239
+ export_format=format,
240
+ )
241
+
242
+ if format == "json":
243
+ data = _format_json_export(result, issues)
244
+ output_str = json.dumps(data, indent=2, default=str)
245
+ elif format == "csv":
246
+ output_str = _format_csv_export(result, issues)
247
+ else:
248
+ logger.error(
249
+ "unsupported_export_format",
250
+ format=format,
251
+ error=None,
252
+ severity="config_error",
253
+ )
254
+ console.print("❌ Unsupported export format", style="bold red")
255
+ return
256
+
257
+ if output_path:
258
+ try:
259
+ path = Path(output_path)
260
+ path.parent.mkdir(parents=True, exist_ok=True)
261
+ path.write_text(output_str)
262
+ logger.info(
263
+ "critical_path_exported",
264
+ output_path=str(path),
265
+ )
266
+ console.print(f"✅ Exported to {path}", style="bold green")
267
+ except OSError as e:
268
+ logger.error(
269
+ "failed_to_write_export_file",
270
+ error=str(e),
271
+ severity="operational",
272
+ exc_info=True,
273
+ )
274
+ console.print(
275
+ f"❌ Failed to write export file: {str(e)}", style="bold red"
276
+ )
277
+ else:
278
+ logger.debug("Outputting export to stdout")
279
+ console.print(output_str)
280
+
281
+ except Exception as e:
282
+ logger.error(
283
+ "critical_path_export_failed",
284
+ error=str(e),
285
+ severity="operational",
286
+ exc_info=True,
287
+ )
288
+ console.print(f"❌ Export failed: {str(e)}", style="bold red")
289
+
290
+
291
+ def _format_json_export(result, issues: list) -> dict:
292
+ """Format critical path data as JSON.
293
+
294
+ Args:
295
+ result: CriticalPathResult from calculator
296
+ issues: List of Issue domain objects
297
+
298
+ Returns:
299
+ Dictionary with critical path and summary data
300
+ """
301
+ try:
302
+ logger.debug("Formatting critical path as JSON")
303
+ return {
304
+ "critical_path": [
305
+ {
306
+ "issue_id": node.issue_id,
307
+ "title": node.issue_title,
308
+ "duration_hours": node.duration_hours,
309
+ "is_critical": node.is_critical,
310
+ "slack_time": node.slack_time,
311
+ }
312
+ for node in result.critical_path
313
+ ],
314
+ "summary": {
315
+ "total_duration": result.total_duration,
316
+ "critical_issue_count": len(result.critical_issue_ids),
317
+ "blocking_issues": result.blocking_issues,
318
+ "project_end_date": result.project_end_date.isoformat()
319
+ if result.project_end_date
320
+ else None,
321
+ },
322
+ }
323
+ except Exception as e:
324
+ logger.error(
325
+ "json_formatting_failed",
326
+ error=str(e),
327
+ severity="operational",
328
+ exc_info=True,
329
+ )
330
+ raise
331
+
332
+
333
+ def _format_csv_export(result, issues: list) -> str:
334
+ """Format critical path data as CSV.
335
+
336
+ Args:
337
+ result: CriticalPathResult from calculator
338
+ issues: List of Issue domain objects
339
+
340
+ Returns:
341
+ CSV-formatted string
342
+ """
343
+ try:
344
+ logger.debug("Formatting critical path as CSV")
345
+ lines = [
346
+ "issue_id,title,duration_hours,dependencies,slack_hours,is_critical,status"
347
+ ]
348
+
349
+ for issue in issues:
350
+ deps = ",".join(issue.depends_on) if issue.depends_on else ""
351
+ # Find corresponding node
352
+ node = next(
353
+ (n for n in result.critical_path if n.issue_id == issue.id), None
354
+ )
355
+ slack = f"{node.slack_time:.1f}" if node else "N/A"
356
+ critical = "yes" if issue.id in result.critical_issue_ids else "no"
357
+
358
+ lines.append(
359
+ f'"{issue.id}","{issue.title}",{issue.estimated_hours or 0},"{deps}",{slack},{critical},{issue.status}'
360
+ )
361
+
362
+ return "\n".join(lines)
363
+ except Exception as e:
364
+ logger.error(
365
+ "csv_formatting_failed",
366
+ error=str(e),
367
+ severity="operational",
368
+ exc_info=True,
369
+ )
370
+ raise
@@ -0,0 +1,125 @@
1
+ """Presenter for critical path analysis output."""
2
+
3
+ from roadmap.core.services.utils.critical_path_calculator import CriticalPathResult
4
+
5
+
6
+ class CriticalPathPresenter:
7
+ """Formats critical path analysis for CLI display."""
8
+
9
+ def format_critical_path(
10
+ self, result: CriticalPathResult, milestone: str | None = None
11
+ ) -> str:
12
+ """Format critical path as ASCII graph with summary.
13
+
14
+ Args:
15
+ result: CriticalPathResult from calculator
16
+ milestone: Optional milestone name for context
17
+
18
+ Returns:
19
+ Formatted string with dependency graph and summary
20
+ """
21
+ lines = []
22
+
23
+ # Title
24
+ title = "🎯 Critical Path Analysis"
25
+ if milestone:
26
+ title += f" - {milestone}"
27
+ lines.append(f"\n{title}")
28
+ lines.append("=" * len(title))
29
+ lines.append("")
30
+
31
+ # Dependency graph
32
+ if result.critical_path:
33
+ lines.extend(self._format_dependency_graph(result.critical_path))
34
+ else:
35
+ lines.append("(No critical path - all issues independent)")
36
+
37
+ lines.append("")
38
+
39
+ # Summary
40
+ lines.extend(self._format_summary(result))
41
+
42
+ return "\n".join(lines)
43
+
44
+ def _format_dependency_graph(self, critical_path: list) -> list[str]:
45
+ """Format the critical path as an ASCII dependency graph."""
46
+ lines = ["📊 Critical Path (Longest Dependency Chain):", ""]
47
+
48
+ for i, node in enumerate(critical_path):
49
+ # Draw the node
50
+ prefix = " " if i > 0 else ""
51
+ connector = "└─ " if i > 0 else ""
52
+
53
+ duration_str = f"{node.duration_hours:.1f}h"
54
+ slack_str = (
55
+ f"(slack: {node.slack_time:.1f}h)" if node.slack_time > 0 else ""
56
+ )
57
+
58
+ line = f"{prefix}{connector}{node.issue_id}: {node.issue_title[:40]:<40} [{duration_str:>6}] {slack_str}"
59
+ lines.append(line)
60
+
61
+ # Add vertical connector if not last
62
+ if i < len(critical_path) - 1:
63
+ lines.append(" │")
64
+
65
+ return lines
66
+
67
+ def _format_summary(self, result: CriticalPathResult) -> list[str]:
68
+ """Format the summary section."""
69
+ lines = ["📈 Summary", "─" * 50]
70
+
71
+ # Duration and count
72
+ lines.append(f"Total Duration: {result.total_duration:.1f} hours")
73
+ if result.total_duration >= 8:
74
+ business_days = result.total_duration / 8.0
75
+ lines.append(f" ~{business_days:.1f} business days")
76
+
77
+ lines.append(f"Critical Issues: {len(result.critical_issue_ids)}")
78
+
79
+ # Risk assessment
80
+ risk_level = self._assess_risk(result)
81
+ lines.append(f"Risk Level: {risk_level}")
82
+
83
+ # Top blockers
84
+ if result.blocking_issues:
85
+ blockers = [
86
+ (issue_id, len(blocked))
87
+ for issue_id, blocked in result.blocking_issues.items()
88
+ if len(blocked) > 0
89
+ ]
90
+ blockers.sort(key=lambda x: x[1], reverse=True)
91
+
92
+ if blockers:
93
+ lines.append("")
94
+ lines.append("🔴 Top Blockers (most dependent issues):")
95
+ for issue_id, count in blockers[:3]:
96
+ lines.append(
97
+ f" • {issue_id} blocks {count} issue{'s' if count != 1 else ''}"
98
+ )
99
+
100
+ # Project end date
101
+ if result.project_end_date:
102
+ date_str = result.project_end_date.strftime("%b %d, %Y")
103
+ lines.append(f"Estimated Complete: {date_str}")
104
+
105
+ return lines
106
+
107
+ def _assess_risk(self, result: CriticalPathResult) -> str:
108
+ """Assess risk level based on critical path metrics."""
109
+ if not result.critical_issue_ids:
110
+ return "🟢 LOW (no critical path)"
111
+
112
+ # Calculate risk based on slack time and duration
113
+ avg_slack = (
114
+ sum(n.slack_time for n in result.critical_path) / len(result.critical_path)
115
+ if result.critical_path
116
+ else 0
117
+ )
118
+
119
+ # Heuristic: if average slack is low relative to duration, risk is high
120
+ if avg_slack < 4: # Less than half a business day
121
+ return "🔴 HIGH (minimal slack time)"
122
+ elif avg_slack < 12: # Less than 1.5 business days
123
+ return "🟡 MEDIUM (some slack available)"
124
+ else:
125
+ return "🟢 LOW (adequate slack time)"
@@ -0,0 +1,86 @@
1
+ """Shared utilities for archive and restore CLI commands.
2
+
3
+ Consolidates common error handling and validation patterns across
4
+ archive/restore commands for issues, milestones, and projects.
5
+ """
6
+
7
+ from rich.console import Console
8
+
9
+ from roadmap.adapters.cli.cli_error_handlers import handle_cli_error
10
+ from roadmap.common.console import get_console
11
+
12
+
13
+ def handle_archive_parse_error(
14
+ error: Exception,
15
+ entity_type: str,
16
+ entity_id: str,
17
+ archive_dir: str,
18
+ console: Console | None = None,
19
+ extra_context: dict | None = None,
20
+ ) -> None:
21
+ """Handle errors when parsing archived entity files.
22
+
23
+ Provides consistent error handling across archive/restore commands.
24
+
25
+ Args:
26
+ error: The exception that occurred
27
+ entity_type: Type of entity ("issue", "milestone", "project")
28
+ entity_id: ID or name of the entity
29
+ archive_dir: Path to the archive directory
30
+ console: Optional console instance (uses default if not provided)
31
+ extra_context: Optional additional context dict to merge with base context
32
+ """
33
+ if console is None:
34
+ console = get_console()
35
+
36
+ context = {"archive_dir": str(archive_dir)}
37
+ if extra_context:
38
+ context.update(extra_context)
39
+
40
+ handle_cli_error(
41
+ error=error,
42
+ operation=f"parse_archived_{entity_type}",
43
+ entity_type=entity_type,
44
+ entity_id=entity_id,
45
+ context=context,
46
+ fatal=False,
47
+ )
48
+ console.print(f" • {entity_id} (parse error)", style="red") # type: ignore[union-attr]
49
+
50
+
51
+ def handle_restore_parse_error(
52
+ error: Exception,
53
+ entity_type: str,
54
+ entity_id: str,
55
+ archive_dir: str,
56
+ console: Console | None = None,
57
+ extra_context: dict | None = None,
58
+ ) -> None:
59
+ """Handle errors when parsing files during restore.
60
+
61
+ Provides consistent error handling across restore commands.
62
+
63
+ Args:
64
+ error: The exception that occurred
65
+ entity_type: Type of entity ("issue", "milestone", "project")
66
+ entity_id: ID or name of the entity
67
+ archive_dir: Path to the archive directory
68
+ console: Optional console instance (uses default if not provided)
69
+ extra_context: Optional additional context dict to merge with base context
70
+ """
71
+ if console is None:
72
+ console = get_console()
73
+
74
+ context = {"archive_dir": str(archive_dir)}
75
+ if extra_context:
76
+ context.update(extra_context)
77
+
78
+ handle_cli_error(
79
+ error=error,
80
+ operation=f"restore_{entity_type}",
81
+ entity_type=entity_type,
82
+ entity_id=entity_id,
83
+ context=context,
84
+ fatal=False,
85
+ )
86
+ console.print(f" • {entity_id} (restore error)", style="red") # type: ignore[union-attr]
@@ -0,0 +1,109 @@
1
+ """CLI command helpers - Consolidated patterns for common CLI operations.
2
+
3
+ Provides decorators and functions to reduce duplication in CLI commands:
4
+ - Initialization checks
5
+ - Entity validation
6
+ - Operation error handling
7
+ - Confirmation dialogs
8
+ """
9
+
10
+ import functools
11
+ import sys
12
+ from collections.abc import Callable
13
+ from typing import Any, TypeVar
14
+
15
+ import click # type: ignore[import-not-found]
16
+ from rich.console import Console # type: ignore[import-not-found]
17
+
18
+ from roadmap.common.logging import get_logger
19
+
20
+ logger = get_logger(__name__)
21
+ console = Console()
22
+
23
+ F = TypeVar("F", bound=Callable[..., Any])
24
+
25
+
26
+ def require_initialized(func: Callable) -> Callable:
27
+ """Require roadmap to be initialized before command execution.
28
+
29
+ Usage:
30
+ @click.command()
31
+ @click.pass_context
32
+ @require_initialized
33
+ def my_command(ctx: click.Context):
34
+ # ctx.obj["core"] is guaranteed to be initialized
35
+ pass
36
+
37
+ Exits with error code 1 if not initialized.
38
+ """
39
+
40
+ @functools.wraps(func)
41
+ def wrapper(ctx: click.Context, *args: Any, **kwargs: Any) -> Any:
42
+ core = ctx.obj.get("core")
43
+ if not core or not core.is_initialized():
44
+ console.print(
45
+ "❌ Roadmap not initialized. Run 'roadmap init' first.",
46
+ style="bold red",
47
+ )
48
+ ctx.exit(1)
49
+ return func(ctx, *args, **kwargs)
50
+
51
+ return wrapper
52
+
53
+
54
+ def ensure_entity_exists(
55
+ core: Any, entity_type: str, entity_id: str, entity: Any = None
56
+ ) -> Any:
57
+ """Ensure entity exists, exit with error if not found.
58
+
59
+ Args:
60
+ core: RoadmapCore instance
61
+ entity_type: Type of entity ('issue', 'project', 'milestone')
62
+ entity_id: ID of entity to look up
63
+ entity: Pre-fetched entity (if None, will look up via core)
64
+
65
+ Returns:
66
+ The entity object
67
+
68
+ Exits with error code 1 if entity not found.
69
+ """
70
+ if entity is None:
71
+ # Fetch from core
72
+ entity_collection = getattr(core, f"{entity_type}s", None)
73
+ if entity_collection is None:
74
+ console.print(
75
+ f"❌ Invalid entity type: {entity_type}",
76
+ style="bold red",
77
+ )
78
+ sys.exit(1)
79
+ entity = entity_collection.get(entity_id)
80
+
81
+ if not entity:
82
+ console.print(
83
+ f"❌ {entity_type.capitalize()} not found: {entity_id}",
84
+ style="bold red",
85
+ )
86
+ sys.exit(1)
87
+
88
+ return entity
89
+
90
+
91
+ def confirm_action(prompt: str, default: bool = False, force: bool = False) -> bool:
92
+ """Prompt user to confirm an action.
93
+
94
+ Args:
95
+ prompt: Confirmation prompt text
96
+ default: Default answer if user just presses Enter
97
+ force: If True, skip prompt and return True (for --force flag)
98
+
99
+ Returns:
100
+ True if user confirms, False otherwise (displays cancellation message)
101
+ """
102
+ if force:
103
+ return True
104
+
105
+ if not click.confirm(prompt, default=default):
106
+ console.print("❌ Cancelled.", style="yellow")
107
+ return False
108
+
109
+ return True